Dashboard mit Abholslots und Filter-Scrolling, Anwesenheit mit Audit-Verlauf sowie Elternanmeldung, Warteliste und bestätigte Aufnahme ergänzen. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
44 lines
1.6 KiB
TypeScript
44 lines
1.6 KiB
TypeScript
import { createHash, randomBytes } from "node:crypto";
|
|
import { cookies } from "next/headers";
|
|
import { redirect } from "next/navigation";
|
|
import { db } from "./db";
|
|
|
|
const COOKIE = "kibu_session";
|
|
export const tokenHash = (token: string) => createHash("sha256").update(token).digest("hex");
|
|
export type Staff = { id: string; email: string; name: string };
|
|
|
|
export async function currentStaff(): Promise<Staff | null> {
|
|
const token = (await cookies()).get(COOKIE)?.value;
|
|
if (!token || !/^[a-f0-9]{64}$/.test(token)) return null;
|
|
const result = await db.query<Staff>(
|
|
`SELECT s.id, s.email, s.name FROM sessions t JOIN staff s ON s.id = t.staff_id
|
|
WHERE t.token_hash = $1 AND t.expires_at > now()`, [tokenHash(token)],
|
|
);
|
|
return result.rows[0] ?? null;
|
|
}
|
|
|
|
export async function requireStaff() {
|
|
const staff = await currentStaff();
|
|
if (!staff) redirect("/login");
|
|
return staff;
|
|
}
|
|
|
|
export async function createSession(staffId: string) {
|
|
const token = randomBytes(32).toString("hex");
|
|
const expires = new Date(Date.now() + 12 * 60 * 60 * 1000);
|
|
await db.query("DELETE FROM sessions WHERE expires_at <= now()");
|
|
await db.query("INSERT INTO sessions(token_hash, staff_id, expires_at) VALUES ($1, $2, $3)",
|
|
[tokenHash(token), staffId, expires]);
|
|
(await cookies()).set(COOKIE, token, {
|
|
httpOnly: true, sameSite: "lax", secure: process.env.SESSION_SECURE === "true",
|
|
path: "/", expires,
|
|
});
|
|
}
|
|
|
|
export async function deleteSession() {
|
|
const jar = await cookies();
|
|
const token = jar.get(COOKIE)?.value;
|
|
if (token) await db.query("DELETE FROM sessions WHERE token_hash = $1", [tokenHash(token)]);
|
|
jar.delete(COOKIE);
|
|
}
|