Add WebAuthn support for admin login and kiosk mode

This commit is contained in:
2026-07-26 21:12:59 +02:00
parent 3c8d9297d7
commit 2b85152bb9
13 changed files with 466 additions and 9 deletions
+43
View File
@@ -11,8 +11,16 @@ type SessionPayload = {
exp: number;
};
type AdminMfaPayload = {
uid: number;
purpose: "admin-mfa";
exp: number;
};
export const SESSION_COOKIE = "moped_session";
export const ADMIN_MFA_COOKIE = "moped_admin_mfa";
const SESSION_TTL_SECONDS = 60 * 60 * 24 * 30;
const ADMIN_MFA_TTL_SECONDS = 60 * 5;
function getSecretKey() {
return new TextEncoder().encode(getAuthSecret());
@@ -43,6 +51,41 @@ export async function clearSessionCookie() {
(await cookies()).delete(SESSION_COOKIE);
}
export async function setPendingAdminMfaCookie(userId: number) {
const token = await new SignJWT({ uid: userId, purpose: "admin-mfa" })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime(`${ADMIN_MFA_TTL_SECONDS}s`)
.sign(getSecretKey());
const cookieStore = await cookies();
cookieStore.set(ADMIN_MFA_COOKIE, token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: ADMIN_MFA_TTL_SECONDS,
path: "/",
priority: "high",
});
}
export async function getPendingAdminMfaUserId() {
const token = (await cookies()).get(ADMIN_MFA_COOKIE)?.value;
if (!token) return null;
try {
const result = await jwtVerify(token, getSecretKey());
const payload = result.payload as unknown as AdminMfaPayload;
return Number.isSafeInteger(payload.uid) && payload.uid > 0 && payload.purpose === "admin-mfa"
? payload.uid
: null;
} catch {
return null;
}
}
export async function clearPendingAdminMfaCookie() {
(await cookies()).delete(ADMIN_MFA_COOKIE);
}
export async function getSessionUser(): Promise<SessionUser | null> {
const token = (await cookies()).get(SESSION_COOKIE)?.value;
if (!token) return null;