haixunMaster/lib/auth/session.ts

78 lines
1.9 KiB
TypeScript

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<User | null> {
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<User> {
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;
}
}