diff --git a/app/api/users/security/route.ts b/app/api/users/security/route.ts new file mode 100644 index 0000000..134d1a0 --- /dev/null +++ b/app/api/users/security/route.ts @@ -0,0 +1,76 @@ +import { NextResponse } from "next/server" +import { auth } from "@/auth" +import { prisma } from "@/lib/prisma" +import axios from "axios" + +interface Authenticator { + name: string, +} + +interface SecurityResults { + authentik: { + authenticators: Authenticator[], + passwordChangeDate: Date | null, + }, +} + +export async function GET() { + const session = await auth() + if (!session || !session.user?.email) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const user = await prisma.user.findUnique({ + where: { email: session.user.email }, + }) + if (!user) { + return NextResponse.json({ error: "User not found" }, { status: 404 }) + } + + const results: SecurityResults = { + authentik: { + authenticators: [], + passwordChangeDate: null, + }, + } + + /* ===== AUTHENTIK ===== */ + + /* 1. Get user info for future requests + - User ID + - Password change date + */ + const atkUserRes = await axios.get(`${process.env.AUTHENTIK_API_URL}/core/users/?email=${encodeURIComponent(user.email)}`, { + headers: { + "Authorization": `Bearer ${process.env.AUTHENTIK_API_KEY}` + } + }) + const userID = atkUserRes.data.results[0].pk + if (atkUserRes.data.results.length > 1) { + return NextResponse.json({ error: "Multiple Authentik accounts found" }, { status: 400 }) + } + const passwordChangeDate = atkUserRes.data.results[0].password_change_date + if (passwordChangeDate) { + results.authentik.passwordChangeDate = new Date(passwordChangeDate) // pushes to results array + } + + // 2. Check authenticators + const atkAuthenticatorsRes = await axios.get(`${process.env.AUTHENTIK_API_URL}/authenticators/admin/all/?user=${userID}`, { + headers: { + "Authorization": `Bearer ${process.env.AUTHENTIK_API_KEY}` + } + }) + const authenticators = atkAuthenticatorsRes.data + if (authenticators.length === 0) { + return NextResponse.json({ error: "No authenticators found" }, { status: 400 }) + } + authenticators.forEach((authenticator: Authenticator) => { + results.authentik.authenticators.push({ + name: authenticator.name, + }) + }) + + return NextResponse.json(results) +} + +export type { SecurityResults, Authenticator } \ No newline at end of file diff --git a/components/pages/dashboard/SecurityTab.tsx b/components/pages/dashboard/SecurityTab.tsx index b859aa2..f26bc91 100644 --- a/components/pages/dashboard/SecurityTab.tsx +++ b/components/pages/dashboard/SecurityTab.tsx @@ -1,38 +1,130 @@ import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { ShieldCheck } from "lucide-react"; +import { AlertCircle, CheckCircleIcon, XCircleIcon, Loader2, ShieldCheck, Search, Lightbulb } from "lucide-react" +import { useState } from "react" +import { SiAuthentik } from "react-icons/si" +import { type SecurityResults } from "@/app/api/users/security/route" export const SecurityTab = () => { + const [scanning, setScanning] = useState(false) + const [scanResults, setScanResults] = useState(null) + const [error, setError] = useState(null) + + const scanAcc = async () => { + setScanning(true) + try { + const res = await fetch("/api/users/security") + const data = await res.json() + setScanResults(data) + setScanning(false) + } catch (error) { + console.error(error) + setError(error instanceof Error ? error.message : "An unknown error occurred") + setScanning(false) + } + } + + // If password was changed over 3 months ago, this will be true + const shouldResetPass = scanResults?.authentik?.passwordChangeDate && scanResults?.authentik?.passwordChangeDate < new Date(Date.now() - 90 * 24 * 60 * 60 * 1000) + + // If user has no 2FA methods setup, this will be true + const insufficient2FA = scanResults?.authentik?.authenticators.length === 0 + return (
- {/* TODO: Implement security checks */} - Security Check - Evaluate the security of your account with a simple check! + + + Account Security Scan + + Evaluate the security of your account with a simple button click! -

Automatic security scans will be arriving shortly!

- + {error ? ( +
+
+ +

Error

+
+

{error}

+
+ ) : scanResults ? ( + <> +
+ +

Authentik

+
+ + {shouldResetPass ? ( +
+ +

Password last changed {"on " + (scanResults?.authentik?.passwordChangeDate ? new Date(scanResults.authentik.passwordChangeDate).toLocaleDateString() : "never")}

+
+ ) : ( +
+ +

Password last changed {"on " + (scanResults?.authentik?.passwordChangeDate ? new Date(scanResults.authentik.passwordChangeDate).toLocaleDateString() : "never")}

+
+ )} + + {insufficient2FA ? ( +
+ +

No 2FA methods setup

+
+ ) : ( +
+ +

{scanResults?.authentik?.authenticators.length} two-factor authentication methods setup

+
+ )} + + ) : ( + <> + {scanning ? ( + + ) : ( + + )} + + )}
- Security Recommendations + + + Recommendations + Steps you can take to improve your account's security
    -
  • Enable Two-Factor Authentication
  • -
  • Use a strong and unique password
  • -
  • Run security checks often (just in case)
  • -
  • Always double-check the URL (librecloud.cc only!)
  • +
  • Enable Two-Factor Authentication
  • +
  • Use a strong and unique password
  • +
  • Always make sure the URL matches librecloud.cc
  • +
    + + + https://librecloud.cc + + +
    +
    + + + https://libre-cloud-login.com + + +
) -} - +} \ No newline at end of file