Spaces:
Runtime error
Runtime error
| import { db } from "@/db/db"; | |
| import { sessions, users } from "@/db/schema"; | |
| import { routes } from "@/lib/routes"; | |
| import { and, eq, gt } from "drizzle-orm"; | |
| import { randomBytes, scryptSync, timingSafeEqual } from "crypto"; | |
| import { cookies } from "next/headers"; | |
| import { redirect } from "next/navigation"; | |
| import { cache } from "react"; | |
| const SESSION_COOKIE_NAME = "shopsmart_session"; | |
| const SESSION_DURATION_SECONDS = 60 * 60 * 24 * 30; | |
| function getSessionCookieOptions(expires: Date) { | |
| const isProduction = process.env.NODE_ENV === "production"; | |
| const isHuggingFaceSpace = Boolean(process.env.SPACE_HOST); | |
| const useCrossSiteCookie = isProduction && isHuggingFaceSpace; | |
| return { | |
| httpOnly: true, | |
| sameSite: useCrossSiteCookie ? "none" : "lax", | |
| secure: isProduction, | |
| expires, | |
| path: "/", | |
| } as const; | |
| } | |
| export function hashPassword(password: string) { | |
| const salt = randomBytes(16).toString("hex"); | |
| const hash = scryptSync(password, salt, 64).toString("hex"); | |
| return `${salt}:${hash}`; | |
| } | |
| export function verifyPassword(password: string, storedHash: string | null) { | |
| if (!storedHash) return false; | |
| const [salt, storedKey] = storedHash.split(":"); | |
| if (!salt || !storedKey) return false; | |
| const derivedKey = scryptSync(password, salt, 64); | |
| const storedKeyBuffer = Buffer.from(storedKey, "hex"); | |
| if (derivedKey.length !== storedKeyBuffer.length) return false; | |
| return timingSafeEqual(derivedKey, storedKeyBuffer); | |
| } | |
| export const getCurrentUser = cache(async () => { | |
| const sessionToken = cookies().get(SESSION_COOKIE_NAME)?.value; | |
| if (!sessionToken) { | |
| return null; | |
| } | |
| try { | |
| const [record] = await db | |
| .select({ | |
| id: users.id, | |
| name: users.name, | |
| email: users.email, | |
| passwordHash: users.passwordHash, | |
| storeId: users.storeId, | |
| createdAt: users.createdAt, | |
| }) | |
| .from(sessions) | |
| .innerJoin(users, eq(sessions.userId, users.id)) | |
| .where( | |
| and( | |
| eq(sessions.sessionToken, sessionToken), | |
| gt(sessions.expiresAt, Math.floor(Date.now() / 1000)) | |
| ) | |
| ); | |
| return record ?? null; | |
| } catch (error) { | |
| console.error("Failed to load the current user from the session store.", error); | |
| return null; | |
| } | |
| }); | |
| export async function requireUser() { | |
| const user = await getCurrentUser(); | |
| if (!user) { | |
| redirect(routes.signIn); | |
| } | |
| return user; | |
| } | |
| export async function createSession(userId: number) { | |
| const sessionToken = randomBytes(32).toString("hex"); | |
| const expiresAt = | |
| Math.floor(Date.now() / 1000) + SESSION_DURATION_SECONDS; | |
| await db.insert(sessions).values({ | |
| sessionToken, | |
| userId, | |
| expiresAt, | |
| }); | |
| cookies().set( | |
| SESSION_COOKIE_NAME, | |
| sessionToken, | |
| getSessionCookieOptions(new Date(expiresAt * 1000)) | |
| ); | |
| } | |
| export async function destroySession() { | |
| const sessionToken = cookies().get(SESSION_COOKIE_NAME)?.value; | |
| if (sessionToken) { | |
| await db.delete(sessions).where(eq(sessions.sessionToken, sessionToken)); | |
| } | |
| cookies().set( | |
| SESSION_COOKIE_NAME, | |
| "", | |
| getSessionCookieOptions(new Date(0)) | |
| ); | |
| } | |