remember me fix

This commit is contained in:
Michael Freno
2026-01-12 16:17:22 -05:00
parent 0286fae8aa
commit 4d35935462
3 changed files with 9 additions and 7 deletions

View File

@@ -17,7 +17,7 @@
* *
* Timing Decisions: * Timing Decisions:
* - 15m access: Balance between security (short exposure) and UX (not too frequent refreshes) * - 15m access: Balance between security (short exposure) and UX (not too frequent refreshes)
* - 1d session: DB cleanup for session-only logins (cookie expires on browser close anyway) * - 7d session: DB expiry for non-remember-me (cookie is session-only but accommodates users who keep browser open)
* - 90d remember: Extended convenience for trusted devices (both DB and cookie persist) * - 90d remember: Extended convenience for trusted devices (both DB and cookie persist)
* - 5s reuse window: Handles race conditions in distributed systems * - 5s reuse window: Handles race conditions in distributed systems
*/ */
@@ -27,12 +27,12 @@ export const AUTH_CONFIG = {
ACCESS_TOKEN_EXPIRY_DEV: "2m" as const, // 2 minutes for faster testing ACCESS_TOKEN_EXPIRY_DEV: "2m" as const, // 2 minutes for faster testing
// Refresh Token (opaque token in separate cookie) // Refresh Token (opaque token in separate cookie)
REFRESH_TOKEN_EXPIRY_SHORT: "1d" as const, // 1 day (DB expiry, cookie is session-only - non-remember me) REFRESH_TOKEN_EXPIRY_SHORT: "7d" as const, // 7 days (DB expiry for non-remember me - accommodates users who keep browser open)
REFRESH_TOKEN_EXPIRY_LONG: "90d" as const, // 90 days (remember me - both DB and cookie persist) REFRESH_TOKEN_EXPIRY_LONG: "90d" as const, // 90 days (remember me - both DB and cookie persist)
// Security Settings // Security Settings
REFRESH_TOKEN_ROTATION_ENABLED: true, // Enable token rotation REFRESH_TOKEN_ROTATION_ENABLED: true, // Enable token rotation
MAX_ROTATION_COUNT: 100, // Max rotations before forcing re-login MAX_ROTATION_COUNT: 1000, // Max rotations before forcing re-login (1000 * 15m = 10.4 days in prod, 1000 * 2m = 33 hours in dev)
REFRESH_TOKEN_REUSE_WINDOW_MS: 5000, // 5s grace period for race conditions REFRESH_TOKEN_REUSE_WINDOW_MS: 5000, // 5s grace period for race conditions
// Session Cleanup (serverless-friendly opportunistic cleanup) // Session Cleanup (serverless-friendly opportunistic cleanup)

View File

@@ -910,7 +910,7 @@ export const authRouter = createTRPCRouter({
emailRegistration: publicProcedure emailRegistration: publicProcedure
.input(registerUserSchema) .input(registerUserSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { email, password, passwordConfirmation } = input; const { email, password, passwordConfirmation, rememberMe } = input;
// Apply rate limiting // Apply rate limiting
const clientIP = getClientIP(getH3Event(ctx)); const clientIP = getClientIP(getH3Event(ctx));
@@ -977,7 +977,7 @@ export const authRouter = createTRPCRouter({
await createAuthSession( await createAuthSession(
getH3Event(ctx), getH3Event(ctx),
userId, userId,
true, // Always use persistent sessions rememberMe ?? true, // Default to persistent sessions for registration
clientIP, clientIP,
userAgent userAgent
); );

View File

@@ -48,7 +48,8 @@ export const registerUserSchema = z
.object({ .object({
email: z.string().email(), email: z.string().email(),
password: securePasswordSchema, password: securePasswordSchema,
passwordConfirmation: z.string().min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH) passwordConfirmation: z.string().min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH),
rememberMe: z.boolean().optional().default(true)
}) })
.refine((data) => data.password === data.passwordConfirmation, { .refine((data) => data.password === data.passwordConfirmation, {
message: "Passwords do not match", message: "Passwords do not match",
@@ -60,7 +61,8 @@ export const registerUserSchema = z
*/ */
export const loginUserSchema = z.object({ export const loginUserSchema = z.object({
email: z.string().email(), email: z.string().email(),
password: z.string().min(1, "Password is required") password: z.string().min(1, "Password is required"),
rememberMe: z.boolean().optional().default(false)
}); });
/** /**