189 lines
4.3 KiB
TypeScript
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;
|
|
}
|