From 664bf42446e7e84dad9176ecc3ec2a2ee4a2cb08 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 15 Apr 2025 09:12:55 -0400 Subject: [PATCH] feat: add authentik password changing, split into different sections, create initial api --- app/account/dashboard/settings/page.tsx | 17 +-- app/api/auth/password/route.ts | 62 +++++++++ .../Settings/ChangeAuthentikPassword.tsx | 122 +++++++++++++++++ .../Settings/ChangeEmailPassword.tsx | 114 ++++++++++++++++ .../dashboard/Settings/ChangePassword.tsx | 127 ------------------ .../cards/dashboard/Settings/MyAccount.tsx | 24 ++++ 6 files changed, 327 insertions(+), 139 deletions(-) create mode 100644 app/api/auth/password/route.ts create mode 100644 components/cards/dashboard/Settings/ChangeAuthentikPassword.tsx create mode 100644 components/cards/dashboard/Settings/ChangeEmailPassword.tsx delete mode 100644 components/cards/dashboard/Settings/ChangePassword.tsx create mode 100644 components/cards/dashboard/Settings/MyAccount.tsx diff --git a/app/account/dashboard/settings/page.tsx b/app/account/dashboard/settings/page.tsx index ee02964..71526c8 100644 --- a/app/account/dashboard/settings/page.tsx +++ b/app/account/dashboard/settings/page.tsx @@ -1,19 +1,12 @@ "use client" -import { motion } from "motion/react" import { Switch } from "@/components/ui/switch" import { Label } from "@/components/ui/label" import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" -import { ChangePassword } from "@/components/cards/dashboard/Settings/ChangePassword" +import MyAccount from "@/components/cards/dashboard/Settings/MyAccount" import { useState, useEffect } from "react" import { LayoutDashboard } from "lucide-react" -const fadeIn = { - initial: { opacity: 0, y: 20 }, - animate: { opacity: 1, y: 0 }, - transition: { duration: 0.4 }, -} - export default function Settings() { const [settings, setSettings] = useState({ hideGenAI: false, @@ -85,10 +78,10 @@ export default function Settings() { }; return ( - -

Settings

+ <> +

Settings

- + @@ -130,7 +123,7 @@ export default function Settings() {
-
+ ) } diff --git a/app/api/auth/password/route.ts b/app/api/auth/password/route.ts new file mode 100644 index 0000000..7ac92d2 --- /dev/null +++ b/app/api/auth/password/route.ts @@ -0,0 +1,62 @@ +import { auth } from "@/auth" +import axios from "axios" +import { NextResponse } from "next/server" + +export async function POST(request: Request) { + try { + const session = await auth() + const body = await request.json() + const { password } = body + + if (!session || !session.user?.email) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } else if (!password || typeof password !== "string") { + return NextResponse.json({ error: "Invalid password" }, { status: 400 }) + } + + // Get user ID from email + const user = await axios.request({ + method: "get", + url: `${process.env.AUTHENTIK_API_URL}/core/users/?email=${session.user.email}`, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.AUTHENTIK_API_KEY}`, + }, + validateStatus: () => true, + }) + + const userId = user.data.results[0].pk + + if (!userId) { + console.error(`[!] User ID not found in response: ${session.user.email}`) + return NextResponse.json({ error: "User not found" }, { status: 404 }) + } + + const updCfg = await axios.request({ + method: "post", + maxBodyLength: Number.POSITIVE_INFINITY, + url: `${process.env.AUTHENTIK_API_URL}/core/users/${userId}/set_password/`, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.AUTHENTIK_API_KEY}`, + }, + data: { password }, + validateStatus: () => true, + }) + + if (updCfg.data?.detail) { + console.error("[!] Password setting issue:", updCfg.data.detail) + return NextResponse.json({ error: "Failed to change password" }, { status: 400 }) + } + + if (updCfg.status === 204) { + return NextResponse.json({ success: true }) + } else { + return NextResponse.json({ error: "Failed to change password" }, { status: 400 }) + } + + } catch (error) { + console.error("[!]", error) + return NextResponse.json({ error: "Server error" }, { status: 500 }) + } +} \ No newline at end of file diff --git a/components/cards/dashboard/Settings/ChangeAuthentikPassword.tsx b/components/cards/dashboard/Settings/ChangeAuthentikPassword.tsx new file mode 100644 index 0000000..a5da567 --- /dev/null +++ b/components/cards/dashboard/Settings/ChangeAuthentikPassword.tsx @@ -0,0 +1,122 @@ +"use client" + +import React, { useState } from "react" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { Label } from "@/components/ui/label" +import { Key, Loader2 } from "lucide-react" +import Link from "next/link" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger +} from "@/components/ui/dialog" + +export function ChangeAuthentikPassword() { + const [newPassword, setNewPassword] = useState("") + const [loading, setLoading] = useState(false) + const [message, setMessage] = useState(null) + const [open, setOpen] = useState(false) + + const handlePasswordChange = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + setMessage(null) + try { + const response = await fetch("/api/auth/password", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ password: newPassword }), + }) + const resData = await response.json() + + if (response.ok && resData.success) { + setMessage("Password Updated") + setLoading(false) + // Close dialog after change + setTimeout(() => { + setOpen(false) + setNewPassword("") + }, 1500) + // TODO: Show a toast that password was changed + } else if (resData.error) { + setMessage(resData.error) + setLoading(false) + } else { + setMessage("[1] Failed to Update") + setLoading(false) + } + } catch (error) { + console.log(error) + setMessage("[2] Failed to Update") + setLoading(false) + } + }; + + return ( + + + + + + + Change Your Password + + This only applies to your + + Authentik + account. + Make sure it's secure, and consider using + + LibreCloud Pass + to keep it safe! + + +
+
+ + setNewPassword(e.target.value)} + className="mt-1.5" + /> +

+ Password must be at least 8 characters long. +

+
+ {message && ( +

+ {message} +

+ )} + + + +
+
+
+ ) +} + +export default ChangeAuthentikPassword \ No newline at end of file diff --git a/components/cards/dashboard/Settings/ChangeEmailPassword.tsx b/components/cards/dashboard/Settings/ChangeEmailPassword.tsx new file mode 100644 index 0000000..dc19471 --- /dev/null +++ b/components/cards/dashboard/Settings/ChangeEmailPassword.tsx @@ -0,0 +1,114 @@ +"use client" + +import React, { useState } from "react" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { Label } from "@/components/ui/label" +import { Key, Loader2 } from "lucide-react" +import Link from "next/link" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger +} from "@/components/ui/dialog" + +export function ChangeEmailPassword() { + const [newPassword, setNewPassword] = useState("") + const [loading, setLoading] = useState(false) + const [message, setMessage] = useState(null) + const [open, setOpen] = useState(false) + + const handlePasswordChange = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + setMessage(null) + try { + const response = await fetch("/api/mail/password", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ password: newPassword }), + }) + const resData = await response.json() + + if (response.ok && resData.success) { + setMessage("Password Updated") + setLoading(false) + // Close dialog after change + setTimeout(() => { + setOpen(false) + setNewPassword("") + }, 1500) + } else if (resData.error) { + setMessage(resData.error) + setLoading(false) + } else { + setMessage("[1] Failed to Update") + setLoading(false) + } + } catch (error) { + console.log(error) + setMessage("[2] Failed to Update") + setLoading(false) + } + } + + return ( + + + + + + + Change Your Password + + This only applies to your email account. + Make sure it's secure, and consider using + + LibreCloud Pass + to keep it safe! + + +
+
+ + setNewPassword(e.target.value)} + className="mt-1.5" + /> +

+ Password must be at least 8 characters long. +

+
+ {message && ( +

+ {message} +

+ )} + + + +
+
+
+ ) +} + +export default ChangeEmailPassword \ No newline at end of file diff --git a/components/cards/dashboard/Settings/ChangePassword.tsx b/components/cards/dashboard/Settings/ChangePassword.tsx deleted file mode 100644 index 25af8c1..0000000 --- a/components/cards/dashboard/Settings/ChangePassword.tsx +++ /dev/null @@ -1,127 +0,0 @@ -"use client" - -import React, { useState } from "react" -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" -import { Input } from "@/components/ui/input" -import { Button } from "@/components/ui/button" -import { Label } from "@/components/ui/label" -import { Key, Loader2, User } from "lucide-react" -import Link from "next/link" -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger -} from "@/components/ui/dialog" - -export function ChangePassword() { - const [newPassword, setNewPassword] = useState(""); - const [loading, setLoading] = useState(false); - const [message, setMessage] = useState(null); - const [open, setOpen] = useState(false); - - const handlePasswordChange = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - setMessage(null); - try { - const response = await fetch("/api/mail/password", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ password: newPassword }), - }); - const resData = await response.json(); - - if (response.ok && resData.success) { - setMessage("Password Updated"); - setLoading(false); - // Close dialog after change - setTimeout(() => { - setOpen(false); - setNewPassword(""); - }, 1500); - } else if (resData.error) { - setMessage(resData.error); - setLoading(false); - } else { - setMessage("[1] Failed to Update"); - setLoading(false); - } - } catch (error) { - console.log(error) - setMessage("[2] Failed to Update"); - setLoading(false); - } - }; - - return ( - - - - - My Account - - LibreCloud makes it easy to manage your account - - -

Actions

- - - - - - - Change Your Password - - This only applies to your Authentik account. - Make sure it's secure, and consider using - - LibreCloud Pass - to keep it safe. - - -
-
- - setNewPassword(e.target.value)} - className="mt-1.5" - /> -

- Password must be at least 8 characters long. -

-
- {message && ( -

- {message} -

- )} - - - -
-
-
-
-
- ); -} - -export default ChangePassword; \ No newline at end of file diff --git a/components/cards/dashboard/Settings/MyAccount.tsx b/components/cards/dashboard/Settings/MyAccount.tsx new file mode 100644 index 0000000..2cfc299 --- /dev/null +++ b/components/cards/dashboard/Settings/MyAccount.tsx @@ -0,0 +1,24 @@ +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card" +import ChangeAuthentikPassword from "@/components/cards/dashboard/Settings/ChangeAuthentikPassword" +import ChangeEmailPassword from "@/components/cards/dashboard/Settings/ChangeEmailPassword" +import { User } from "lucide-react" + +export default function MyAccount() { + return ( + + + + + My Account + + LibreCloud makes it easy to manage your account + + +

Email

+ +

Authentik

+ +
+
+ ) +} \ No newline at end of file