91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import type { SessionConfig } from "vinxi/http";
|
|
import { AUTH_CONFIG, expiryToSeconds } from "~/config";
|
|
|
|
/**
|
|
* Session data stored in encrypted cookie
|
|
* This is synced with database Session table for serverless persistence
|
|
*/
|
|
export interface SessionData {
|
|
/** User ID */
|
|
userId: string;
|
|
/** Session ID for database lookup and revocation */
|
|
sessionId: string;
|
|
/** Token family for rotation chain tracking */
|
|
tokenFamily: string;
|
|
/** Whether user is admin (cached from DB) */
|
|
isAdmin: boolean;
|
|
/** Refresh token for rotation (opaque, hashed in DB) */
|
|
refreshToken: string;
|
|
/** Remember me preference for session duration */
|
|
rememberMe: boolean;
|
|
}
|
|
|
|
/**
|
|
* Get session password directly from process.env
|
|
* This avoids any bundler-time substitution issues with the validated env object
|
|
*/
|
|
function getSessionPassword(): string {
|
|
// Read directly from process.env at runtime, not from bundled env object
|
|
const password = process.env.JWT_SECRET_KEY;
|
|
if (!password || password.trim() === "") {
|
|
console.error(
|
|
`[SessionConfig] JWT_SECRET_KEY missing from process.env! Keys available:`,
|
|
Object.keys(process.env)
|
|
.filter((k) => k.includes("JWT") || k.includes("SECRET"))
|
|
.join(", ") || "none matching JWT/SECRET"
|
|
);
|
|
throw new Error(
|
|
`JWT_SECRET_KEY is empty at runtime. Ensure it is set as a runtime environment variable in Vercel (not just build-time).`
|
|
);
|
|
}
|
|
return password;
|
|
}
|
|
|
|
/**
|
|
* Get session config with runtime password validation
|
|
* Returns a fresh config each time to ensure env vars are read at call time,
|
|
* not at module load time (important for serverless cold starts)
|
|
*/
|
|
export function getSessionConfig(): SessionConfig {
|
|
return {
|
|
password: getSessionPassword(),
|
|
name: "session",
|
|
cookie: {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
path: "/"
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Vinxi session configuration
|
|
* Using a getter ensures password is evaluated at access time, not module load time
|
|
*/
|
|
export const sessionConfig: SessionConfig = {
|
|
get password() {
|
|
return getSessionPassword();
|
|
},
|
|
name: "session",
|
|
cookie: {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
path: "/"
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get session cookie options with appropriate maxAge
|
|
* @param rememberMe - Whether to use extended session duration
|
|
*/
|
|
export function getSessionCookieOptions(rememberMe: boolean) {
|
|
return {
|
|
...sessionConfig.cookie,
|
|
maxAge: rememberMe
|
|
? expiryToSeconds(AUTH_CONFIG.REFRESH_TOKEN_EXPIRY_LONG)
|
|
: undefined // Session cookie (expires on browser close)
|
|
};
|
|
}
|