65 lines
2 KiB
TypeScript
65 lines
2 KiB
TypeScript
import 'server-only';
|
|
import { cookies } from 'next/headers';
|
|
import { redirect } from 'next/navigation';
|
|
import { randomBytes } from 'node:crypto';
|
|
import { db } from './db';
|
|
|
|
export { hashPassword, verifyPassword } from '@/lib/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;
|
|
};
|
|
|
|
/** Create a session row and set the httpOnly cookie (works in Route Handlers and Server Actions). */
|
|
export async function createSession(userId: string): Promise<string> {
|
|
const token = randomBytes(32).toString('hex');
|
|
const expiresAt = new Date(Date.now() + TTL_MS);
|
|
await db.session.create({ data: { token, userId, expiresAt } });
|
|
const store = await cookies();
|
|
store.set(SESSION_COOKIE, token, {
|
|
httpOnly: true,
|
|
sameSite: 'lax',
|
|
secure: process.env.NODE_ENV === 'production',
|
|
path: '/',
|
|
expires: expiresAt,
|
|
});
|
|
return token;
|
|
}
|
|
|
|
export async function destroySession(): Promise<void> {
|
|
const store = await cookies();
|
|
const token = store.get(SESSION_COOKIE)?.value;
|
|
if (token) await db.session.deleteMany({ where: { token } });
|
|
store.delete(SESSION_COOKIE);
|
|
}
|
|
|
|
export async function getCurrentUser(): Promise<SessionUser | null> {
|
|
const store = await cookies();
|
|
const token = store.get(SESSION_COOKIE)?.value;
|
|
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 };
|
|
}
|
|
|
|
/** Page guard: redirect to /login when unauthenticated. */
|
|
export async function requireUser(): Promise<SessionUser> {
|
|
const user = await getCurrentUser();
|
|
if (!user) redirect('/login');
|
|
return user;
|
|
}
|
|
|
|
/** Page guard: require the admin role. */
|
|
export async function requireAdmin(): Promise<SessionUser> {
|
|
const user = await requireUser();
|
|
if (user.role !== 'admin') redirect('/dashboard');
|
|
return user;
|
|
}
|