hy2-panel/lib/session.ts
2026-04-14 18:05:32 +08:00

189 lines
4.3 KiB
TypeScript

import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { getEnv } from "@/lib/env";
import { createOpaqueToken, createStableId, sha256 } from "@/lib/password";
import {
createAdminSessionRecord,
createUserSessionRecord,
deleteAdminSessionByHash,
deleteUserSessionByHash,
getAdminSessionByHash,
getUserSessionByHash,
touchAdminSession,
touchUserSession,
} from "@/lib/store";
const ADMIN_COOKIE_NAME = "hy2_panel_admin_session";
const USER_COOKIE_NAME = "hy2_panel_user_session";
function shouldUseSecureCookies() {
const env = getEnv();
if (env.SESSION_COOKIE_SECURE === "true") {
return true;
}
if (env.SESSION_COOKIE_SECURE === "false") {
return false;
}
return new URL(env.APP_URL).protocol === "https:";
}
export async function createAdminSession(adminId: number) {
const env = getEnv();
const token = createOpaqueToken(32);
const expiresAt = new Date(
Date.now() + env.SESSION_TTL_HOURS * 60 * 60 * 1000,
).toISOString();
await createAdminSessionRecord({
id: createStableId(),
adminId,
sessionHash: sha256(`${env.SESSION_SECRET}:${token}`),
expiresAt,
});
const cookieStore = await cookies();
cookieStore.set(ADMIN_COOKIE_NAME, token, {
httpOnly: true,
sameSite: "lax",
secure: shouldUseSecureCookies(),
path: "/",
expires: new Date(expiresAt),
});
}
export async function createUserSession(userId: number) {
const env = getEnv();
const token = createOpaqueToken(32);
const expiresAt = new Date(
Date.now() + env.SESSION_TTL_HOURS * 60 * 60 * 1000,
).toISOString();
await createUserSessionRecord({
id: createStableId(),
userId,
sessionHash: sha256(`${env.SESSION_SECRET}:${token}`),
expiresAt,
});
const cookieStore = await cookies();
cookieStore.set(USER_COOKIE_NAME, token, {
httpOnly: true,
sameSite: "lax",
secure: shouldUseSecureCookies(),
path: "/",
expires: new Date(expiresAt),
});
}
export async function destroyAdminSession() {
const cookieStore = await cookies();
const raw = cookieStore.get(ADMIN_COOKIE_NAME)?.value;
if (raw) {
await deleteAdminSessionByHash(sha256(`${getEnv().SESSION_SECRET}:${raw}`));
}
cookieStore.delete(ADMIN_COOKIE_NAME);
}
export async function destroyUserSession() {
const cookieStore = await cookies();
const raw = cookieStore.get(USER_COOKIE_NAME)?.value;
if (raw) {
await deleteUserSessionByHash(sha256(`${getEnv().SESSION_SECRET}:${raw}`));
}
cookieStore.delete(USER_COOKIE_NAME);
}
export async function getAdminSession() {
const raw = (await cookies()).get(ADMIN_COOKIE_NAME)?.value;
if (!raw) return null;
const session = await getAdminSessionByHash(
sha256(`${getEnv().SESSION_SECRET}:${raw}`),
);
if (!session) {
return null;
}
if (new Date(session.expires_at).getTime() <= Date.now()) {
await deleteAdminSessionByHash(sha256(`${getEnv().SESSION_SECRET}:${raw}`));
return null;
}
if (Date.now() - new Date(session.last_seen_at).getTime() > 5 * 60 * 1000) {
await touchAdminSession(session.id);
}
return session;
}
export async function getUserSession() {
const raw = (await cookies()).get(USER_COOKIE_NAME)?.value;
if (!raw) return null;
const session = await getUserSessionByHash(
sha256(`${getEnv().SESSION_SECRET}:${raw}`),
);
if (!session) {
return null;
}
if (new Date(session.expires_at).getTime() <= Date.now()) {
await deleteUserSessionByHash(sha256(`${getEnv().SESSION_SECRET}:${raw}`));
return null;
}
if (Date.now() - new Date(session.last_seen_at).getTime() > 5 * 60 * 1000) {
await touchUserSession(session.id);
}
return session;
}
export async function requireAdminSession() {
const session = await getAdminSession();
if (!session) {
redirect("/login");
}
return session;
}
export async function requireAdminAccess() {
const adminSession = await getAdminSession();
if (adminSession) {
return {
username: adminSession.username,
source: "admin" as const,
};
}
const userSession = await getUserSession();
if (userSession?.is_admin) {
return {
username: userSession.username,
source: "user" as const,
};
}
redirect("/login");
}
export async function requireUserSession() {
const session = await getUserSession();
if (!session) {
redirect("/login");
}
return session;
}