feat: add auth to gatekeeper
All checks were successful
Monorepo Pipeline / 🧪 Quality Assurance (push) Successful in 5m0s
Monorepo Pipeline / 🚀 Release (push) Successful in 3m43s
Monorepo Pipeline / 🐳 Build & Push Images (push) Successful in 5m16s

This commit is contained in:
2026-02-07 14:48:39 +01:00
parent 61e78ea672
commit a594affdfa
3 changed files with 132 additions and 18 deletions

View File

@@ -9,8 +9,32 @@ export async function GET(req: NextRequest) {
const session = cookieStore.get(authCookieName); const session = cookieStore.get(authCookieName);
if (session?.value === password) { let isAuthenticated = false;
return new NextResponse("OK", { status: 200 }); 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 // Traefik ForwardAuth headers

View File

@@ -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,
});
}

View File

@@ -17,16 +17,69 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
async function login(formData: FormData) { async function login(formData: FormData) {
"use server"; "use server";
const password = formData.get("password"); const email = formData.get("email") as string;
const expectedPassword = process.env.GATEKEEPER_PASSWORD || "mintel"; 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 = const authCookieName =
process.env.AUTH_COOKIE_NAME || "mintel_gatekeeper_session"; process.env.AUTH_COOKIE_NAME || "mintel_gatekeeper_session";
const targetRedirect = formData.get("redirect") as string; const targetRedirect = formData.get("redirect") as string;
const cookieDomain = process.env.COOKIE_DOMAIN; 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(); 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, httpOnly: true,
secure: true, secure: true,
path: "/", path: "/",
@@ -85,24 +138,35 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
</div> </div>
)} )}
<form action={login} className="space-y-6"> <form action={login} className="space-y-4">
<input type="hidden" name="redirect" value={redirectUrl} /> <input type="hidden" name="redirect" value={redirectUrl} />
<div className="relative group"> <div className="space-y-2">
<input <div className="relative group">
type="password" <input
name="password" type="email"
required name="email"
autoFocus placeholder="EMAIL (OPTIONAL)"
autoComplete="current-password" className="w-full bg-slate-50/50 border border-slate-200 rounded-2xl px-6 py-4 focus:outline-none focus:border-slate-900 focus:bg-white transition-all text-[10px] font-sans font-bold tracking-[0.2em] uppercase placeholder:text-slate-300 shadow-sm shadow-slate-50"
placeholder="GATEKEEPER CODE" />
className="w-full bg-slate-50/50 border border-slate-200 rounded-2xl px-6 py-4 focus:outline-none focus:border-slate-900 focus:bg-white transition-all text-sm font-sans font-bold tracking-[0.3em] uppercase placeholder:text-slate-300 placeholder:tracking-widest shadow-sm shadow-slate-50" </div>
/>
<div className="relative group">
<input
type="password"
name="password"
required
autoFocus
autoComplete="current-password"
placeholder="ACCESS CODE"
className="w-full bg-slate-50/50 border border-slate-200 rounded-2xl px-6 py-4 focus:outline-none focus:border-slate-900 focus:bg-white transition-all text-sm font-sans font-bold tracking-[0.3em] uppercase placeholder:text-slate-300 placeholder:tracking-widest shadow-sm shadow-slate-50"
/>
</div>
</div> </div>
<button <button
type="submit" type="submit"
className="btn btn-primary w-full py-5 rounded-2xl text-[10px] shadow-lg shadow-slate-100" className="btn btn-primary w-full py-5 rounded-2xl text-[10px] shadow-lg shadow-slate-100 flex items-center justify-center"
> >
Unlock Access Unlock Access
<ArrowRight className="ml-3 w-3 h-3 group-hover:translate-x-1 transition-transform" /> <ArrowRight className="ml-3 w-3 h-3 group-hover:translate-x-1 transition-transform" />