import "server-only"; import { randomBytes } from "crypto"; import { cookies } from "next/headers"; import type { User } from "@prisma/client"; import { prisma } from "@/lib/db"; import { SESSION_COOKIE, SESSION_MAX_AGE_DAYS } from "./constants"; function sessionExpiry() { return new Date(Date.now() + SESSION_MAX_AGE_DAYS * 24 * 60 * 60 * 1000); } export async function createSession(userId: string) { const token = randomBytes(32).toString("hex"); const expiresAt = sessionExpiry(); await prisma.session.create({ data: { userId, token, expiresAt }, }); const cookieStore = await cookies(); cookieStore.set(SESSION_COOKIE, token, { httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production", path: "/", expires: expiresAt, }); return token; } export async function destroySession() { const cookieStore = await cookies(); const token = cookieStore.get(SESSION_COOKIE)?.value; if (token) { await prisma.session.deleteMany({ where: { token } }).catch(() => undefined); cookieStore.delete(SESSION_COOKIE); } } export async function getSessionUser(): Promise { const cookieStore = await cookies(); const token = cookieStore.get(SESSION_COOKIE)?.value; if (!token) return null; const session = await prisma.session.findUnique({ where: { token }, include: { user: true }, }); if (!session || session.expiresAt < new Date()) { if (session) { await prisma.session.delete({ where: { id: session.id } }).catch(() => undefined); } return null; } return session.user; } export async function requireSessionUser(): Promise { const user = await getSessionUser(); if (!user) { throw new AuthError("請先登入", 401); } return user; } export class AuthError extends Error { status: number; constructor(message: string, status = 401) { super(message); this.name = "AuthError"; this.status = status; } }