75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
import { cookies } from "next/headers";
|
|
import { createHmac } from "crypto";
|
|
|
|
const SECRET = "sec-cybert-label-session-key";
|
|
const SESSION_COOKIE = "session";
|
|
const SESSION_MAX_AGE_MS = 8 * 60 * 60 * 1000; // 8 hours
|
|
|
|
interface SessionPayload {
|
|
annotatorId: string;
|
|
createdAt: number;
|
|
}
|
|
|
|
function sign(payload: string): string {
|
|
return createHmac("sha256", SECRET).update(payload).digest("hex");
|
|
}
|
|
|
|
function encodeSession(payload: SessionPayload): string {
|
|
const json = JSON.stringify(payload);
|
|
const b64 = Buffer.from(json).toString("base64");
|
|
const signature = sign(b64);
|
|
return `${b64}.${signature}`;
|
|
}
|
|
|
|
export function verifyAndDecode(raw: string): SessionPayload | null {
|
|
const dotIndex = raw.lastIndexOf(".");
|
|
if (dotIndex === -1) return null;
|
|
|
|
const b64 = raw.slice(0, dotIndex);
|
|
const signature = raw.slice(dotIndex + 1);
|
|
|
|
const expected = sign(b64);
|
|
if (signature !== expected) return null;
|
|
|
|
try {
|
|
const json = Buffer.from(b64, "base64").toString("utf-8");
|
|
const payload = JSON.parse(json) as SessionPayload;
|
|
|
|
if (!payload.annotatorId || !payload.createdAt) return null;
|
|
|
|
const age = Date.now() - payload.createdAt;
|
|
if (age > SESSION_MAX_AGE_MS) return null;
|
|
|
|
return payload;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function createSession(annotatorId: string): Promise<void> {
|
|
const cookieStore = await cookies();
|
|
const value = encodeSession({ annotatorId, createdAt: Date.now() });
|
|
cookieStore.set(SESSION_COOKIE, value, {
|
|
httpOnly: true,
|
|
sameSite: "lax",
|
|
path: "/",
|
|
maxAge: SESSION_MAX_AGE_MS / 1000,
|
|
});
|
|
}
|
|
|
|
export async function getSession(): Promise<{ annotatorId: string } | null> {
|
|
const cookieStore = await cookies();
|
|
const raw = cookieStore.get(SESSION_COOKIE)?.value;
|
|
if (!raw) return null;
|
|
|
|
const payload = verifyAndDecode(raw);
|
|
if (!payload) return null;
|
|
|
|
return { annotatorId: payload.annotatorId };
|
|
}
|
|
|
|
export async function destroySession(): Promise<void> {
|
|
const cookieStore = await cookies();
|
|
cookieStore.delete(SESSION_COOKIE);
|
|
}
|