51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import { createHmac, timingSafeEqual } from 'crypto';
|
|
import { cookies } from 'next/headers';
|
|
|
|
export const ADMIN_SESSION_COOKIE = 'shahi_admin_session';
|
|
const SESSION_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
|
function getSessionSecret(): string {
|
|
return process.env.ADMIN_SESSION_SECRET ?? 'shahi-kitchen-admin-dev-secret';
|
|
}
|
|
|
|
export function createAdminSessionToken(): string {
|
|
const issuedAt = Date.now().toString();
|
|
const payload = `admin:${issuedAt}`;
|
|
const signature = createHmac('sha256', getSessionSecret()).update(payload).digest('hex');
|
|
return `${payload}.${signature}`;
|
|
}
|
|
|
|
export function verifyAdminSessionToken(token: string | undefined | null): boolean {
|
|
if (!token) return false;
|
|
|
|
const [payload, signature] = token.split('.');
|
|
if (!payload || !signature) return false;
|
|
|
|
const expected = createHmac('sha256', getSessionSecret()).update(payload).digest('hex');
|
|
const sigBuffer = Buffer.from(signature);
|
|
const expectedBuffer = Buffer.from(expected);
|
|
if (sigBuffer.length !== expectedBuffer.length) return false;
|
|
if (!timingSafeEqual(sigBuffer, expectedBuffer)) return false;
|
|
|
|
const [, issuedAtRaw] = payload.split(':');
|
|
const issuedAt = Number(issuedAtRaw);
|
|
if (!Number.isFinite(issuedAt)) return false;
|
|
|
|
return Date.now() - issuedAt <= SESSION_MAX_AGE_MS;
|
|
}
|
|
|
|
export async function isAdminAuthenticated(): Promise<boolean> {
|
|
const cookieStore = await cookies();
|
|
const token = cookieStore.get(ADMIN_SESSION_COOKIE)?.value;
|
|
return verifyAdminSessionToken(token);
|
|
}
|
|
|
|
export function getAdminSessionCookieOptions() {
|
|
return {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
sameSite: 'lax' as const,
|
|
path: '/',
|
|
maxAge: SESSION_MAX_AGE_MS / 1000,
|
|
};
|
|
} |