From a594affdfafceee9d670c1bb186bfb5523e1875f Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sat, 7 Feb 2026 14:48:39 +0100 Subject: [PATCH] feat: add auth to gatekeeper --- .../gatekeeper/src/app/api/verify/route.ts | 28 +++++- .../gatekeeper/src/app/api/whoami/route.ts | 26 +++++ packages/gatekeeper/src/app/login/page.tsx | 96 +++++++++++++++---- 3 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 packages/gatekeeper/src/app/api/whoami/route.ts diff --git a/packages/gatekeeper/src/app/api/verify/route.ts b/packages/gatekeeper/src/app/api/verify/route.ts index 2a7c2b0..e19beae 100644 --- a/packages/gatekeeper/src/app/api/verify/route.ts +++ b/packages/gatekeeper/src/app/api/verify/route.ts @@ -9,8 +9,32 @@ export async function GET(req: NextRequest) { const session = cookieStore.get(authCookieName); - if (session?.value === password) { - return new NextResponse("OK", { status: 200 }); + let isAuthenticated = false; + let identity = "Guest"; + + if (session?.value) { + if (session.value === password) { + isAuthenticated = true; + } else { + try { + const payload = JSON.parse(session.value); + if (payload.identity) { + isAuthenticated = true; + identity = payload.identity; + } + } catch (e) { + // Fallback or old format + } + } + } + + if (isAuthenticated) { + return new NextResponse("OK", { + status: 200, + headers: { + "X-Auth-User": identity, + }, + }); } // Traefik ForwardAuth headers diff --git a/packages/gatekeeper/src/app/api/whoami/route.ts b/packages/gatekeeper/src/app/api/whoami/route.ts new file mode 100644 index 0000000..2efd39e --- /dev/null +++ b/packages/gatekeeper/src/app/api/whoami/route.ts @@ -0,0 +1,26 @@ +import { NextRequest, NextResponse } from "next/server"; +import { cookies } from "next/headers"; + +export async function GET(req: NextRequest) { + const cookieStore = await cookies(); + const authCookieName = + process.env.AUTH_COOKIE_NAME || "mintel_gatekeeper_session"; + const session = cookieStore.get(authCookieName); + + if (!session?.value) { + return NextResponse.json({ authenticated: false }, { status: 401 }); + } + + let identity = "Guest"; + try { + const payload = JSON.parse(session.value); + identity = payload.identity || "Guest"; + } catch (e) { + // Old format probably just the password + } + + return NextResponse.json({ + authenticated: true, + identity: identity, + }); +} diff --git a/packages/gatekeeper/src/app/login/page.tsx b/packages/gatekeeper/src/app/login/page.tsx index 14e00e7..f62f392 100644 --- a/packages/gatekeeper/src/app/login/page.tsx +++ b/packages/gatekeeper/src/app/login/page.tsx @@ -17,16 +17,69 @@ export default async function LoginPage({ searchParams }: LoginPageProps) { async function login(formData: FormData) { "use server"; - const password = formData.get("password"); - const expectedPassword = process.env.GATEKEEPER_PASSWORD || "mintel"; + const email = formData.get("email") as string; + const password = formData.get("password") as string; + + const expectedCode = process.env.GATEKEEPER_PASSWORD || "mintel"; + const adminEmail = process.env.DIRECTUS_ADMIN_EMAIL; + const adminPassword = process.env.DIRECTUS_ADMIN_PASSWORD; const authCookieName = process.env.AUTH_COOKIE_NAME || "mintel_gatekeeper_session"; const targetRedirect = formData.get("redirect") as string; const cookieDomain = process.env.COOKIE_DOMAIN; - if (password === expectedPassword) { + let userIdentity = ""; + + // 1. Check Global Admin (from ENV) + if ( + adminEmail && + adminPassword && + email === adminEmail && + password === adminPassword + ) { + userIdentity = "Admin"; + } + // 2. Check Generic Code (Guest) + else if (!email && password === expectedCode) { + userIdentity = "Guest"; + } + // 3. Check Directus if email is provided + if (email && password && process.env.DIRECTUS_URL) { + try { + const loginRes = await fetch(`${process.env.DIRECTUS_URL}/auth/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password }), + }); + + if (loginRes.ok) { + const { data } = await loginRes.json(); + const accessToken = data.access_token; + + // Fetch user info to get a nice display name + const userRes = await fetch(`${process.env.DIRECTUS_URL}/users/me`, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + if (userRes.ok) { + const { data: user } = await userRes.json(); + userIdentity = user.first_name || user.email; + } + } + } catch (e) { + console.error("Directus Auth Error:", e); + } + } + + if (userIdentity) { const cookieStore = await cookies(); - cookieStore.set(authCookieName, expectedPassword, { + // Store identity in the cookie (simplified for now, ideally signed) + const sessionValue = JSON.stringify({ + identity: userIdentity, + timestamp: Date.now(), + }); + + cookieStore.set(authCookieName, sessionValue, { httpOnly: true, secure: true, path: "/", @@ -85,24 +138,35 @@ export default async function LoginPage({ searchParams }: LoginPageProps) { )} -
+ -
- +
+
+ +
+ +
+ +