fix(admin): persist data directory and pass env vars
This commit is contained in:
parent
99e8201745
commit
1d7e7c41c9
39 changed files with 1297 additions and 32 deletions
69
lib/admin/auth.ts
Normal file
69
lib/admin/auth.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { createHmac, timingSafeEqual } from "crypto";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export const adminSessionCookie = "fs_admin_session";
|
||||
|
||||
const sessionMaxAgeSeconds = 60 * 60 * 8;
|
||||
|
||||
function secret() {
|
||||
return process.env.ADMIN_SESSION_SECRET ?? "dev-admin-session-secret-change-me";
|
||||
}
|
||||
|
||||
function adminEmail() {
|
||||
return process.env.ADMIN_EMAIL;
|
||||
}
|
||||
|
||||
function adminPassword() {
|
||||
return process.env.ADMIN_PASSWORD;
|
||||
}
|
||||
|
||||
function sign(value: string) {
|
||||
return createHmac("sha256", secret()).update(value).digest("base64url");
|
||||
}
|
||||
|
||||
function safeEqual(left: string, right: string) {
|
||||
const leftBuffer = Buffer.from(left);
|
||||
const rightBuffer = Buffer.from(right);
|
||||
return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
|
||||
}
|
||||
|
||||
export function validateAdminCredentials(email: string, password: string) {
|
||||
const configuredEmail = adminEmail();
|
||||
const configuredPassword = adminPassword();
|
||||
if (!configuredEmail || !configuredPassword) return false;
|
||||
return safeEqual(email.trim().toLowerCase(), configuredEmail.toLowerCase()) && safeEqual(password, configuredPassword);
|
||||
}
|
||||
|
||||
export function createAdminSession() {
|
||||
const expiresAt = Date.now() + sessionMaxAgeSeconds * 1000;
|
||||
const payload = Buffer.from(JSON.stringify({ sub: adminEmail() ?? "admin", exp: expiresAt })).toString("base64url");
|
||||
return `${payload}.${sign(payload)}`;
|
||||
}
|
||||
|
||||
export function verifyAdminSession(token?: string) {
|
||||
if (!token) return false;
|
||||
const [payload, signature] = token.split(".");
|
||||
if (!payload || !signature || !safeEqual(signature, sign(payload))) return false;
|
||||
|
||||
try {
|
||||
const session = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as { exp?: number };
|
||||
return typeof session.exp === "number" && session.exp > Date.now();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function requireAdminSession() {
|
||||
const cookieStore = await cookies();
|
||||
return verifyAdminSession(cookieStore.get(adminSessionCookie)?.value);
|
||||
}
|
||||
|
||||
export function adminCookieOptions() {
|
||||
return {
|
||||
httpOnly: true,
|
||||
sameSite: "lax" as const,
|
||||
secure: process.env.AUTH_COOKIE_SECURE === "true",
|
||||
path: "/",
|
||||
maxAge: sessionMaxAgeSeconds,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue