init
This commit is contained in:
+188
@@ -0,0 +1,188 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user