- sessions: require REMIX_SECRET in prod (no "secret" fallback); base Secure cookie on NODE_ENV; add maxAge - login: safeRedirect() blocks open-redirect via redirectTo (loader + action) - SSR loaders: encodeURIComponent on user path params before internal API fetch (product, store product, seller) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
1.9 KiB
TypeScript
49 lines
1.9 KiB
TypeScript
import { createCookieSessionStorage, Session } from "@remix-run/node";
|
|
import { isProduction } from "./utils/get-env";
|
|
|
|
const isProd = process.env.NODE_ENV === "production";
|
|
const sessionSecret = process.env.REMIX_SECRET;
|
|
|
|
// Fail closed: never ship a guessable signing secret to production. A signed
|
|
// cookie with a known secret can be forged, allowing session tampering.
|
|
if (isProd && !sessionSecret) {
|
|
throw new Error(
|
|
"REMIX_SECRET environment variable must be set in production."
|
|
);
|
|
}
|
|
|
|
// export the whole sessionStorage object
|
|
export const sessionStorage = createCookieSessionStorage({
|
|
cookie: {
|
|
name: "vitrown_session", // use name you want here
|
|
sameSite: "lax", // this helps with CSRF
|
|
path: "/", // remember to add this so the cookie will work in all routes
|
|
httpOnly: true, // for security reasons, make this cookie http only
|
|
maxAge: 60 * 60 * 24 * 30, // 30 days (matches refresh-token lifetime)
|
|
// Dev-only fallback; production is guarded by the check above.
|
|
secrets: [sessionSecret ?? "dev-only-insecure-secret"],
|
|
// Send only over HTTPS in production (NODE_ENV is the reliable server signal).
|
|
secure: isProd || isProduction(),
|
|
},
|
|
});
|
|
export const { getSession, commitSession, destroySession } = sessionStorage;
|
|
|
|
export interface UserTrackerSessionType {
|
|
token: string;
|
|
session: Session;
|
|
}
|
|
|
|
// Helper function to check if access_token is expired (1 week)
|
|
export function isAccessTokenExpired(tokenTimestamp: number): boolean {
|
|
const oneWeekInMs = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
|
|
const currentTime = Date.now();
|
|
return currentTime - tokenTimestamp > oneWeekInMs;
|
|
}
|
|
|
|
// Helper function to check if refresh_token is expired (1 month)
|
|
export function isRefreshTokenExpired(tokenTimestamp: number): boolean {
|
|
const oneMonthInMs = 30 * 24 * 60 * 60 * 1000; // ~30 days in milliseconds
|
|
const currentTime = Date.now();
|
|
return currentTime - tokenTimestamp > oneMonthInMs;
|
|
}
|