feat(project): init
This commit is contained in:
142
lib/auth.ts
Normal file
142
lib/auth.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user