92 lines
2.4 KiB
TypeScript
92 lines
2.4 KiB
TypeScript
import crypto from "node:crypto";
|
|
import { cookies } from "next/headers";
|
|
import { redirect } from "next/navigation";
|
|
import { cache } from "react";
|
|
import { prisma } from "./prisma";
|
|
import { encryptSessionId, decryptSessionId } from "./crypto";
|
|
|
|
/**
|
|
* Create a new session for the given user.
|
|
* Sets an encrypted session cookie with a 7-day expiry.
|
|
* Returns the encrypted cookie value.
|
|
*/
|
|
export async function createSession(userId: string): Promise<string> {
|
|
const token = crypto.randomBytes(32).toString("hex");
|
|
|
|
const session = await prisma.session.create({
|
|
data: {
|
|
sessionToken: token,
|
|
userId,
|
|
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
},
|
|
});
|
|
|
|
const encryptedValue = encryptSessionId(session.id);
|
|
|
|
const cookieStore = await cookies();
|
|
cookieStore.set("session", encryptedValue, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
path: "/",
|
|
maxAge: 7 * 24 * 60 * 60,
|
|
});
|
|
|
|
return encryptedValue;
|
|
}
|
|
|
|
/**
|
|
* Delete the current session cookie and its database record.
|
|
*/
|
|
export async function deleteSession(): Promise<void> {
|
|
const cookieStore = await cookies();
|
|
const sessionCookie = cookieStore.get("session")?.value;
|
|
|
|
if (sessionCookie) {
|
|
const sessionId = decryptSessionId(sessionCookie);
|
|
if (sessionId) {
|
|
await prisma.session.delete({ where: { id: sessionId } }).catch(() => {});
|
|
}
|
|
}
|
|
|
|
cookieStore.delete("session");
|
|
}
|
|
|
|
/**
|
|
* Get the current authenticated user from the session cookie.
|
|
* Memoized per-request with React cache().
|
|
*/
|
|
export const getCurrentUser = cache(
|
|
async (): Promise<{ id: string; email: string } | null> => {
|
|
const cookieStore = await cookies();
|
|
const sessionCookie = cookieStore.get("session")?.value;
|
|
|
|
if (!sessionCookie) return null;
|
|
|
|
const sessionId = decryptSessionId(sessionCookie);
|
|
if (!sessionId) return null;
|
|
|
|
const session = await prisma.session.findUnique({
|
|
where: { id: sessionId },
|
|
include: { user: true },
|
|
});
|
|
|
|
if (!session) return null;
|
|
if (session.expiresAt < new Date()) return null;
|
|
|
|
return { id: session.user.id, email: session.user.email };
|
|
},
|
|
);
|
|
|
|
/**
|
|
* Get the current user or redirect to /login if unauthenticated.
|
|
*/
|
|
export async function requireCurrentUser(): Promise<{
|
|
id: string;
|
|
email: string;
|
|
}> {
|
|
const user = await getCurrentUser();
|
|
if (!user) redirect("/login");
|
|
return user;
|
|
}
|