Files
2026-04-25 11:26:33 +08:00

143 lines
3.0 KiB
TypeScript

import "server-only";
import { randomUUID } from "node:crypto";
import bcrypt from "bcryptjs";
import { cache } from "react";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import {
createSessionRecord,
deleteSessionRecord,
getSessionUser,
getUserByUsername,
normalizeUsername,
} from "@/lib/db";
import { appConfig } from "@/lib/config";
import type { PublicUser, SessionRecord, UserRecord } from "@/lib/types";
export const SESSION_COOKIE_NAME = "image_prompt_session";
export function toPublicUser(user: UserRecord | null): PublicUser | null {
if (!user) {
return null;
}
return {
id: user.id,
username: user.username,
role: user.role,
isActive: user.isActive,
monthlyQuota: user.monthlyQuota,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
};
}
export function hashPassword(password: string): string {
return bcrypt.hashSync(String(password), 12);
}
export function verifyPassword(
password: string,
passwordHash: string,
): boolean {
return bcrypt.compareSync(String(password), passwordHash);
}
export function authenticateUser(
username: string,
password: string,
): UserRecord | null {
const user = getUserByUsername(normalizeUsername(username));
if (!user || !user.isActive) {
return null;
}
if (!verifyPassword(password, user.passwordHash)) {
return null;
}
return user;
}
export function createSessionForUser(userId: string): SessionRecord {
const sessionId = randomUUID();
const expiresAt = new Date(
Date.now() + appConfig.sessionDays * 24 * 60 * 60 * 1000,
).toISOString();
createSessionRecord({
sessionId,
userId,
expiresAt,
});
return {
sessionId,
userId,
expiresAt,
};
}
export function getSessionCookieOptions(expiresAt: string) {
return {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax" as const,
path: "/",
expires: new Date(expiresAt),
};
}
export function clearSessionById(sessionId: string | undefined): void {
if (!sessionId) {
return;
}
deleteSessionRecord(sessionId);
}
export function getUserFromSessionToken(
sessionToken: string | undefined,
): UserRecord | null {
if (!sessionToken) {
return null;
}
return getSessionUser(sessionToken);
}
export async function getCurrentUser(): Promise<UserRecord | null> {
const cookieStore = await cookies();
const sessionToken = cookieStore.get(SESSION_COOKIE_NAME)?.value;
return getUserFromSessionToken(sessionToken);
}
export const getCachedCurrentUser = cache(getCurrentUser);
export async function requireCurrentUser(): Promise<UserRecord> {
const user = await getCachedCurrentUser();
if (!user) {
redirect("/login");
}
if (!user.isActive) {
redirect("/login?reason=inactive");
}
return user;
}
export async function requireAdminUser(): Promise<UserRecord> {
const user = await requireCurrentUser();
if (user.role !== "admin") {
redirect("/studio");
}
return user;
}