59 lines
2 KiB
TypeScript
59 lines
2 KiB
TypeScript
import { randomBytes } from 'node:crypto';
|
|
import type { NitroEvent } from './h3-event';
|
|
import { db } from './db';
|
|
|
|
export { hashPassword, verifyPassword } from '#shared/password';
|
|
|
|
export const SESSION_COOKIE = 'w4c_session';
|
|
const TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
|
|
export type SessionUser = {
|
|
id: string;
|
|
email: string;
|
|
name: string | null;
|
|
role: string;
|
|
};
|
|
|
|
export async function createSession(event: NitroEvent, userId: string): Promise<string> {
|
|
const token = randomBytes(32).toString('hex');
|
|
const expires = new Date(Date.now() + TTL_MS);
|
|
await db.session.create({ data: { token, userId, expiresAt: expires } });
|
|
setCookie(event, SESSION_COOKIE, token, {
|
|
httpOnly: true,
|
|
sameSite: 'lax',
|
|
secure: process.env.NODE_ENV === 'production',
|
|
path: '/',
|
|
expires,
|
|
});
|
|
return token;
|
|
}
|
|
|
|
export async function destroySession(event: NitroEvent): Promise<void> {
|
|
const token = getCookie(event, SESSION_COOKIE);
|
|
if (token) await db.session.deleteMany({ where: { token } });
|
|
deleteCookie(event, SESSION_COOKIE, { path: '/' });
|
|
}
|
|
|
|
export async function getCurrentUser(event: NitroEvent): Promise<SessionUser | null> {
|
|
const token = getCookie(event, SESSION_COOKIE);
|
|
if (!token) return null;
|
|
const session = await db.session.findUnique({ where: { token }, include: { user: true } });
|
|
if (!session || session.expiresAt < new Date()) return null;
|
|
const { id, email, name, role } = session.user;
|
|
return { id, email, name, role };
|
|
}
|
|
|
|
/** API guard: 401 when unauthenticated. */
|
|
export async function requireUser(event: NitroEvent): Promise<SessionUser> {
|
|
const user = await getCurrentUser(event);
|
|
if (!user) throw createError({ statusCode: 401, statusMessage: 'Unauthorized' });
|
|
return user;
|
|
}
|
|
|
|
/** API guard: 403 unless the admin role. */
|
|
export async function requireAdmin(event: NitroEvent): Promise<SessionUser> {
|
|
const user = await requireUser(event);
|
|
if (user.role !== 'admin') throw createError({ statusCode: 403, statusMessage: 'Forbidden' });
|
|
return user;
|
|
}
|