113 lines
2.8 KiB
TypeScript
113 lines
2.8 KiB
TypeScript
import bcrypt from "bcryptjs";
|
|
import { createCookieSessionStorage, redirect } from "@remix-run/node";
|
|
import { prisma } from "./db.server";
|
|
import type { User } from "@prisma/client";
|
|
|
|
// Session configuration
|
|
const sessionSecret = process.env.SESSION_SECRET;
|
|
if (!sessionSecret) {
|
|
throw new Error("SESSION_SECRET must be set");
|
|
}
|
|
|
|
// Create session storage
|
|
const storage = createCookieSessionStorage({
|
|
cookie: {
|
|
name: "car_maintenance_session",
|
|
secure: process.env.NODE_ENV === "production",
|
|
secrets: [sessionSecret],
|
|
sameSite: "lax",
|
|
path: "/",
|
|
maxAge: 60 * 60 * 24 * 30, // 30 days
|
|
httpOnly: true,
|
|
},
|
|
});
|
|
|
|
// Password hashing utilities
|
|
export async function hashPassword(password: string): Promise<string> {
|
|
return bcrypt.hash(password, 12);
|
|
}
|
|
|
|
export async function verifyPassword(
|
|
password: string,
|
|
hashedPassword: string
|
|
): Promise<boolean> {
|
|
return bcrypt.compare(password, hashedPassword);
|
|
}
|
|
|
|
// Session management functions
|
|
export async function createUserSession(
|
|
userId: number,
|
|
redirectTo: string = "/dashboard"
|
|
) {
|
|
const session = await storage.getSession();
|
|
session.set("userId", userId);
|
|
return redirect(redirectTo, {
|
|
headers: {
|
|
"Set-Cookie": await storage.commitSession(session),
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function getUserSession(request: Request) {
|
|
return storage.getSession(request.headers.get("Cookie"));
|
|
}
|
|
|
|
export async function getUserId(request: Request): Promise<number | null> {
|
|
const session = await getUserSession(request);
|
|
const userId = session.get("userId");
|
|
if (!userId || typeof userId !== "number") return null;
|
|
return userId;
|
|
}
|
|
|
|
export async function requireUserId(
|
|
request: Request,
|
|
redirectTo: string = new URL(request.url).pathname
|
|
) {
|
|
const userId = await getUserId(request);
|
|
if (!userId) {
|
|
const searchParams = new URLSearchParams([["redirectTo", redirectTo]]);
|
|
throw redirect(`/signin?${searchParams}`);
|
|
}
|
|
return userId;
|
|
}
|
|
|
|
export async function getUser(request: Request): Promise<Omit<User, 'password'> | null> {
|
|
const userId = await getUserId(request);
|
|
if (!userId) return null;
|
|
|
|
try {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
username: true,
|
|
email: true,
|
|
status: true,
|
|
authLevel: true,
|
|
createdDate: true,
|
|
editDate: true,
|
|
},
|
|
});
|
|
return user;
|
|
} catch {
|
|
throw logout(request);
|
|
}
|
|
}
|
|
|
|
export async function requireUser(request: Request) {
|
|
const user = await getUser(request);
|
|
if (!user) {
|
|
throw logout(request);
|
|
}
|
|
return user;
|
|
}
|
|
|
|
export async function logout(request: Request) {
|
|
const session = await getUserSession(request);
|
|
return redirect("/signin", {
|
|
headers: {
|
|
"Set-Cookie": await storage.destroySession(session),
|
|
},
|
|
});
|
|
} |