config centralized
This commit is contained in:
@@ -46,6 +46,7 @@ import {
|
||||
import { logAuditEvent } from "~/server/audit";
|
||||
import type { H3Event } from "vinxi/http";
|
||||
import type { Context } from "../utils";
|
||||
import { AUTH_CONFIG, NETWORK_CONFIG, COOLDOWN_TIMERS } from "~/config";
|
||||
|
||||
/**
|
||||
* Safely extract H3Event from Context
|
||||
@@ -70,7 +71,7 @@ function getH3Event(ctx: Context): H3Event {
|
||||
async function createJWT(
|
||||
userId: string,
|
||||
sessionId: string,
|
||||
expiresIn: string = "14d"
|
||||
expiresIn: string = AUTH_CONFIG.JWT_EXPIRY
|
||||
): Promise<string> {
|
||||
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
|
||||
const token = await new SignJWT({
|
||||
@@ -173,15 +174,15 @@ async function sendEmail(to: string, subject: string, htmlContent: string) {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(sendinblueData),
|
||||
timeout: 15000
|
||||
timeout: NETWORK_CONFIG.EMAIL_API_TIMEOUT_MS
|
||||
});
|
||||
|
||||
await checkResponse(response);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
maxRetries: 2,
|
||||
retryDelay: 1000
|
||||
maxRetries: NETWORK_CONFIG.MAX_RETRIES,
|
||||
retryDelay: NETWORK_CONFIG.RETRY_DELAY_MS
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -206,7 +207,7 @@ export const authRouter = createTRPCRouter({
|
||||
client_secret: env.GITHUB_CLIENT_SECRET,
|
||||
code
|
||||
}),
|
||||
timeout: 15000
|
||||
timeout: NETWORK_CONFIG.GITHUB_API_TIMEOUT_MS
|
||||
}
|
||||
);
|
||||
|
||||
@@ -226,7 +227,7 @@ export const authRouter = createTRPCRouter({
|
||||
headers: {
|
||||
Authorization: `token ${access_token}`
|
||||
},
|
||||
timeout: 15000
|
||||
timeout: NETWORK_CONFIG.GITHUB_API_TIMEOUT_MS
|
||||
}
|
||||
);
|
||||
|
||||
@@ -241,7 +242,7 @@ export const authRouter = createTRPCRouter({
|
||||
headers: {
|
||||
Authorization: `token ${access_token}`
|
||||
},
|
||||
timeout: 15000
|
||||
timeout: NETWORK_CONFIG.GITHUB_API_TIMEOUT_MS
|
||||
}
|
||||
);
|
||||
|
||||
@@ -319,7 +320,7 @@ export const authRouter = createTRPCRouter({
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
const sessionId = await createSession(
|
||||
userId,
|
||||
"14d",
|
||||
AUTH_CONFIG.JWT_EXPIRY,
|
||||
clientIP,
|
||||
userAgent
|
||||
);
|
||||
@@ -327,7 +328,7 @@ export const authRouter = createTRPCRouter({
|
||||
const token = await createJWT(userId, sessionId);
|
||||
|
||||
setCookie(getH3Event(ctx), "userIDToken", token, {
|
||||
maxAge: 60 * 60 * 24 * 14, // 14 days
|
||||
maxAge: AUTH_CONFIG.SESSION_COOKIE_MAX_AGE,
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: env.NODE_ENV === "production",
|
||||
@@ -417,7 +418,7 @@ export const authRouter = createTRPCRouter({
|
||||
redirect_uri: `${env.VITE_DOMAIN || "https://freno.me"}/api/auth/callback/google`,
|
||||
grant_type: "authorization_code"
|
||||
}),
|
||||
timeout: 15000
|
||||
timeout: NETWORK_CONFIG.GOOGLE_API_TIMEOUT_MS
|
||||
}
|
||||
);
|
||||
|
||||
@@ -437,7 +438,7 @@ export const authRouter = createTRPCRouter({
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`
|
||||
},
|
||||
timeout: 15000
|
||||
timeout: NETWORK_CONFIG.GOOGLE_API_TIMEOUT_MS
|
||||
}
|
||||
);
|
||||
|
||||
@@ -501,7 +502,7 @@ export const authRouter = createTRPCRouter({
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
const sessionId = await createSession(
|
||||
userId,
|
||||
"14d",
|
||||
AUTH_CONFIG.JWT_EXPIRY,
|
||||
clientIP,
|
||||
userAgent
|
||||
);
|
||||
@@ -509,7 +510,7 @@ export const authRouter = createTRPCRouter({
|
||||
const token = await createJWT(userId, sessionId);
|
||||
|
||||
setCookie(getH3Event(ctx), "userIDToken", token, {
|
||||
maxAge: 60 * 60 * 24 * 14, // 14 days
|
||||
maxAge: AUTH_CONFIG.SESSION_COOKIE_MAX_AGE,
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: env.NODE_ENV === "production",
|
||||
@@ -618,7 +619,9 @@ export const authRouter = createTRPCRouter({
|
||||
// Create session with client info
|
||||
const clientIP = getClientIP(getH3Event(ctx));
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
const expiresIn = rememberMe ? "14d" : "12h";
|
||||
const expiresIn = rememberMe
|
||||
? AUTH_CONFIG.JWT_EXPIRY
|
||||
: AUTH_CONFIG.JWT_EXPIRY_SHORT;
|
||||
const sessionId = await createSession(
|
||||
userId,
|
||||
expiresIn,
|
||||
@@ -636,7 +639,7 @@ export const authRouter = createTRPCRouter({
|
||||
};
|
||||
|
||||
if (rememberMe) {
|
||||
cookieOptions.maxAge = 60 * 60 * 24 * 14;
|
||||
cookieOptions.maxAge = AUTH_CONFIG.REMEMBER_ME_MAX_AGE;
|
||||
}
|
||||
|
||||
setCookie(getH3Event(ctx), "userIDToken", userToken, cookieOptions);
|
||||
@@ -790,7 +793,7 @@ export const authRouter = createTRPCRouter({
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
const sessionId = await createSession(
|
||||
userId,
|
||||
"14d",
|
||||
AUTH_CONFIG.JWT_EXPIRY,
|
||||
clientIP,
|
||||
userAgent
|
||||
);
|
||||
@@ -798,7 +801,7 @@ export const authRouter = createTRPCRouter({
|
||||
const token = await createJWT(userId, sessionId);
|
||||
|
||||
setCookie(getH3Event(ctx), "userIDToken", token, {
|
||||
maxAge: 60 * 60 * 24 * 14, // 14 days
|
||||
maxAge: AUTH_CONFIG.SESSION_COOKIE_MAX_AGE,
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
secure: env.NODE_ENV === "production",
|
||||
@@ -958,7 +961,9 @@ export const authRouter = createTRPCRouter({
|
||||
// Reset failed attempts on successful login
|
||||
await resetFailedAttempts(user.id);
|
||||
|
||||
const expiresIn = rememberMe ? "14d" : "12h";
|
||||
const expiresIn = rememberMe
|
||||
? AUTH_CONFIG.JWT_EXPIRY
|
||||
: AUTH_CONFIG.JWT_EXPIRY_SHORT;
|
||||
|
||||
// Create session with client info (reuse clientIP from rate limiting)
|
||||
const userAgent = getUserAgent(getH3Event(ctx));
|
||||
@@ -979,7 +984,7 @@ export const authRouter = createTRPCRouter({
|
||||
};
|
||||
|
||||
if (rememberMe) {
|
||||
cookieOptions.maxAge = 60 * 60 * 24 * 14; // 14 days
|
||||
cookieOptions.maxAge = AUTH_CONFIG.REMEMBER_ME_MAX_AGE;
|
||||
}
|
||||
|
||||
setCookie(getH3Event(ctx), "userIDToken", token, cookieOptions);
|
||||
@@ -1110,13 +1115,13 @@ export const authRouter = createTRPCRouter({
|
||||
|
||||
await sendEmail(email, "freno.me login link", htmlContent);
|
||||
|
||||
const exp = new Date(Date.now() + 2 * 60 * 1000);
|
||||
const exp = new Date(Date.now() + COOLDOWN_TIMERS.EMAIL_LOGIN_LINK_MS);
|
||||
setCookie(
|
||||
getH3Event(ctx),
|
||||
"emailLoginLinkRequested",
|
||||
exp.toUTCString(),
|
||||
{
|
||||
maxAge: 2 * 60,
|
||||
maxAge: COOLDOWN_TIMERS.EMAIL_LOGIN_LINK_COOKIE_MAX_AGE,
|
||||
path: "/"
|
||||
}
|
||||
);
|
||||
@@ -1228,13 +1233,15 @@ export const authRouter = createTRPCRouter({
|
||||
|
||||
await sendEmail(email, "password reset", htmlContent);
|
||||
|
||||
const exp = new Date(Date.now() + 5 * 60 * 1000);
|
||||
const exp = new Date(
|
||||
Date.now() + COOLDOWN_TIMERS.PASSWORD_RESET_REQUEST_MS
|
||||
);
|
||||
setCookie(
|
||||
getH3Event(ctx),
|
||||
"passwordResetRequested",
|
||||
exp.toUTCString(),
|
||||
{
|
||||
maxAge: 5 * 60,
|
||||
maxAge: COOLDOWN_TIMERS.PASSWORD_RESET_REQUEST_COOKIE_MAX_AGE,
|
||||
path: "/"
|
||||
}
|
||||
);
|
||||
@@ -1417,9 +1424,9 @@ export const authRouter = createTRPCRouter({
|
||||
if (requested) {
|
||||
const time = parseInt(requested);
|
||||
const currentTime = Date.now();
|
||||
const difference = (currentTime - time) / (1000 * 60);
|
||||
const difference = (currentTime - time) / 1000;
|
||||
|
||||
if (difference < 15) {
|
||||
if (difference * 1000 < COOLDOWN_TIMERS.EMAIL_VERIFICATION_MS) {
|
||||
throw new TRPCError({
|
||||
code: "TOO_MANY_REQUESTS",
|
||||
message:
|
||||
@@ -1492,7 +1499,7 @@ export const authRouter = createTRPCRouter({
|
||||
"emailVerificationRequested",
|
||||
Date.now().toString(),
|
||||
{
|
||||
maxAge: 15 * 60,
|
||||
maxAge: COOLDOWN_TIMERS.EMAIL_VERIFICATION_COOKIE_MAX_AGE,
|
||||
path: "/"
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,8 +3,9 @@ import { ConnectionFactory } from "~/server/utils";
|
||||
import { withCacheAndStale } from "~/server/cache";
|
||||
import { incrementPostReadSchema } from "../schemas/blog";
|
||||
import type { PostWithCommentsAndLikes } from "~/db/types";
|
||||
import { CACHE_CONFIG } from "~/config";
|
||||
|
||||
const BLOG_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
||||
const BLOG_CACHE_TTL = CACHE_CONFIG.BLOG_CACHE_TTL_MS;
|
||||
|
||||
// Shared cache function for all blog posts
|
||||
const getAllPostsData = async (privilegeLevel: string) => {
|
||||
|
||||
@@ -31,8 +31,9 @@ import {
|
||||
updateUserImageSchema,
|
||||
updateUserEmailSchema
|
||||
} from "../schemas/database";
|
||||
import { CACHE_CONFIG } from "~/config";
|
||||
|
||||
const BLOG_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
||||
const BLOG_CACHE_TTL = CACHE_CONFIG.BLOG_CACHE_TTL_MS;
|
||||
|
||||
export const databaseRouter = createTRPCRouter({
|
||||
getCommentReactions: publicProcedure
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
import { createTRPCRouter, publicProcedure } from "../utils";
|
||||
import { env } from "~/env/server";
|
||||
import { withCacheAndStale } from "~/server/cache";
|
||||
import { CACHE_CONFIG } from "~/config";
|
||||
import {
|
||||
fetchWithTimeout,
|
||||
checkResponse,
|
||||
@@ -30,7 +31,7 @@ export const gitActivityRouter = createTRPCRouter({
|
||||
.query(async ({ input }) => {
|
||||
return withCacheAndStale(
|
||||
`github-commits-${input.limit}`,
|
||||
10 * 60 * 1000, // 10 minutes
|
||||
CACHE_CONFIG.GIT_ACTIVITY_CACHE_TTL_MS,
|
||||
async () => {
|
||||
const reposResponse = await fetchWithTimeout(
|
||||
`https://api.github.com/users/MikeFreno/repos?sort=pushed&per_page=10`,
|
||||
@@ -108,7 +109,7 @@ export const gitActivityRouter = createTRPCRouter({
|
||||
|
||||
return allCommits.slice(0, input.limit);
|
||||
},
|
||||
{ maxStaleMs: 24 * 60 * 60 * 1000 } // Accept stale data up to 24 hours old
|
||||
{ maxStaleMs: CACHE_CONFIG.GIT_ACTIVITY_MAX_STALE_MS }
|
||||
).catch((error) => {
|
||||
if (error instanceof NetworkError) {
|
||||
console.error("GitHub API unavailable (network error)");
|
||||
@@ -130,7 +131,7 @@ export const gitActivityRouter = createTRPCRouter({
|
||||
.query(async ({ input }) => {
|
||||
return withCacheAndStale(
|
||||
`gitea-commits-${input.limit}`,
|
||||
10 * 60 * 1000, // 10 minutes
|
||||
CACHE_CONFIG.GIT_ACTIVITY_CACHE_TTL_MS,
|
||||
async () => {
|
||||
const reposResponse = await fetchWithTimeout(
|
||||
`${env.GITEA_URL}/api/v1/users/Mike/repos?limit=100`,
|
||||
@@ -210,7 +211,7 @@ export const gitActivityRouter = createTRPCRouter({
|
||||
|
||||
return allCommits.slice(0, input.limit);
|
||||
},
|
||||
{ maxStaleMs: 24 * 60 * 60 * 1000 }
|
||||
{ maxStaleMs: CACHE_CONFIG.GIT_ACTIVITY_MAX_STALE_MS }
|
||||
).catch((error) => {
|
||||
if (error instanceof NetworkError) {
|
||||
console.error("Gitea API unavailable (network error)");
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
TimeoutError,
|
||||
APIError
|
||||
} from "~/server/fetch-utils";
|
||||
import { NETWORK_CONFIG, COOLDOWN_TIMERS, VALIDATION_CONFIG } from "~/config";
|
||||
const assets: Record<string, string> = {
|
||||
"shapes-with-abigail": "shapes-with-abigail.apk",
|
||||
"magic-delve": "magic-delve.apk",
|
||||
@@ -257,7 +258,10 @@ export const miscRouter = createTRPCRouter({
|
||||
z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email(),
|
||||
message: z.string().min(1).max(500)
|
||||
message: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(VALIDATION_CONFIG.MAX_CONTACT_MESSAGE_LENGTH)
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
@@ -300,19 +304,19 @@ export const miscRouter = createTRPCRouter({
|
||||
"content-type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(sendinblueData),
|
||||
timeout: 15000
|
||||
timeout: NETWORK_CONFIG.EMAIL_API_TIMEOUT_MS
|
||||
});
|
||||
|
||||
await checkResponse(response);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
maxRetries: 2,
|
||||
retryDelay: 1000
|
||||
maxRetries: NETWORK_CONFIG.MAX_RETRIES,
|
||||
retryDelay: NETWORK_CONFIG.RETRY_DELAY_MS
|
||||
}
|
||||
);
|
||||
|
||||
const exp = new Date(Date.now() + 1 * 60 * 1000);
|
||||
const exp = new Date(Date.now() + COOLDOWN_TIMERS.CONTACT_REQUEST_MS);
|
||||
setCookie("contactRequestSent", exp.toUTCString(), {
|
||||
expires: exp,
|
||||
path: "/"
|
||||
@@ -415,12 +419,15 @@ export const miscRouter = createTRPCRouter({
|
||||
"content-type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(sendinblueMyData),
|
||||
timeout: 15000
|
||||
timeout: NETWORK_CONFIG.EMAIL_API_TIMEOUT_MS
|
||||
});
|
||||
await checkResponse(response);
|
||||
return response;
|
||||
},
|
||||
{ maxRetries: 2, retryDelay: 1000 }
|
||||
{
|
||||
maxRetries: NETWORK_CONFIG.MAX_RETRIES,
|
||||
retryDelay: NETWORK_CONFIG.RETRY_DELAY_MS
|
||||
}
|
||||
),
|
||||
fetchWithRetry(
|
||||
async () => {
|
||||
@@ -432,16 +439,19 @@ export const miscRouter = createTRPCRouter({
|
||||
"content-type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(sendinblueUserData),
|
||||
timeout: 15000
|
||||
timeout: NETWORK_CONFIG.EMAIL_API_TIMEOUT_MS
|
||||
});
|
||||
await checkResponse(response);
|
||||
return response;
|
||||
},
|
||||
{ maxRetries: 2, retryDelay: 1000 }
|
||||
{
|
||||
maxRetries: NETWORK_CONFIG.MAX_RETRIES,
|
||||
retryDelay: NETWORK_CONFIG.RETRY_DELAY_MS
|
||||
}
|
||||
)
|
||||
]);
|
||||
|
||||
const exp = new Date(Date.now() + 1 * 60 * 1000);
|
||||
const exp = new Date(Date.now() + COOLDOWN_TIMERS.CONTACT_REQUEST_MS);
|
||||
setCookie("deletionRequestSent", exp.toUTCString(), {
|
||||
expires: exp,
|
||||
path: "/"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { validatePassword } from "~/lib/validation";
|
||||
import { VALIDATION_CONFIG } from "~/config";
|
||||
|
||||
/**
|
||||
* User API Validation Schemas
|
||||
@@ -14,11 +15,14 @@ import { validatePassword } from "~/lib/validation";
|
||||
|
||||
/**
|
||||
* Secure password validation with strength requirements
|
||||
* Minimum 12 characters, uppercase, lowercase, number, and special character
|
||||
* Minimum length from config, uppercase, lowercase, number, and special character
|
||||
*/
|
||||
const securePasswordSchema = z
|
||||
.string()
|
||||
.min(12, "Password must be at least 12 characters")
|
||||
.min(
|
||||
VALIDATION_CONFIG.MIN_PASSWORD_LENGTH,
|
||||
`Password must be at least ${VALIDATION_CONFIG.MIN_PASSWORD_LENGTH} characters`
|
||||
)
|
||||
.refine(
|
||||
(password) => {
|
||||
const result = validatePassword(password);
|
||||
@@ -44,7 +48,7 @@ export const registerUserSchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: securePasswordSchema,
|
||||
passwordConfirmation: z.string().min(12)
|
||||
passwordConfirmation: z.string().min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH)
|
||||
})
|
||||
.refine((data) => data.password === data.passwordConfirmation, {
|
||||
message: "Passwords do not match",
|
||||
@@ -100,7 +104,9 @@ export const changePasswordSchema = z
|
||||
.object({
|
||||
oldPassword: z.string().min(1, "Current password is required"),
|
||||
newPassword: securePasswordSchema,
|
||||
newPasswordConfirmation: z.string().min(12)
|
||||
newPasswordConfirmation: z
|
||||
.string()
|
||||
.min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH)
|
||||
})
|
||||
.refine((data) => data.newPassword === data.newPasswordConfirmation, {
|
||||
message: "Passwords do not match",
|
||||
@@ -117,7 +123,9 @@ export const changePasswordSchema = z
|
||||
export const setPasswordSchema = z
|
||||
.object({
|
||||
newPassword: securePasswordSchema,
|
||||
newPasswordConfirmation: z.string().min(12)
|
||||
newPasswordConfirmation: z
|
||||
.string()
|
||||
.min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH)
|
||||
})
|
||||
.refine((data) => data.newPassword === data.newPasswordConfirmation, {
|
||||
message: "Passwords do not match",
|
||||
@@ -138,7 +146,9 @@ export const resetPasswordSchema = z
|
||||
.object({
|
||||
token: z.string().min(1),
|
||||
newPassword: securePasswordSchema,
|
||||
newPasswordConfirmation: z.string().min(12)
|
||||
newPasswordConfirmation: z
|
||||
.string()
|
||||
.min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH)
|
||||
})
|
||||
.refine((data) => data.newPassword === data.newPasswordConfirmation, {
|
||||
message: "Passwords do not match",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CACHE_CONFIG } from "~/config";
|
||||
|
||||
interface CacheEntry<T> {
|
||||
data: T;
|
||||
timestamp: number;
|
||||
@@ -80,7 +82,8 @@ export async function withCacheAndStale<T>(
|
||||
logErrors?: boolean;
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
const { maxStaleMs = 7 * 24 * 60 * 60 * 1000, logErrors = true } = options;
|
||||
const { maxStaleMs = CACHE_CONFIG.MAX_STALE_DATA_MS, logErrors = true } =
|
||||
options;
|
||||
|
||||
const cached = cache.get<T>(key, ttlMs);
|
||||
if (cached !== null) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { SignJWT } from "jose";
|
||||
import { env } from "~/env/server";
|
||||
import { AUTH_CONFIG } from "~/config";
|
||||
|
||||
export const LINEAGE_JWT_EXPIRY = "14d";
|
||||
export const LINEAGE_JWT_EXPIRY = AUTH_CONFIG.LINEAGE_JWT_EXPIRY;
|
||||
|
||||
export async function sendEmailVerification(userEmail: string): Promise<{
|
||||
success: boolean;
|
||||
|
||||
@@ -4,6 +4,13 @@ import type { H3Event } from "vinxi/http";
|
||||
import { t } from "~/server/api/utils";
|
||||
import { logAuditEvent } from "~/server/audit";
|
||||
import { env } from "~/env/server";
|
||||
import {
|
||||
AUTH_CONFIG,
|
||||
RATE_LIMITS as CONFIG_RATE_LIMITS,
|
||||
RATE_LIMIT_CLEANUP_INTERVAL_MS,
|
||||
ACCOUNT_LOCKOUT as CONFIG_ACCOUNT_LOCKOUT,
|
||||
PASSWORD_RESET_CONFIG as CONFIG_PASSWORD_RESET
|
||||
} from "~/config";
|
||||
|
||||
/**
|
||||
* Extract cookie value from H3Event (works in both production and tests)
|
||||
@@ -106,7 +113,7 @@ export function generateCSRFToken(): string {
|
||||
export function setCSRFToken(event: H3Event): string {
|
||||
const token = generateCSRFToken();
|
||||
setCookieValue(event, "csrf-token", token, {
|
||||
maxAge: 60 * 60 * 24 * 14, // 14 days - same as session
|
||||
maxAge: AUTH_CONFIG.CSRF_TOKEN_MAX_AGE,
|
||||
path: "/",
|
||||
httpOnly: false, // Must be readable by client JS
|
||||
secure: env.NODE_ENV === "production",
|
||||
@@ -207,17 +214,14 @@ export function clearRateLimitStore(): void {
|
||||
/**
|
||||
* Cleanup expired rate limit entries every 5 minutes
|
||||
*/
|
||||
setInterval(
|
||||
() => {
|
||||
const now = Date.now();
|
||||
for (const [key, record] of rateLimitStore.entries()) {
|
||||
if (now > record.resetAt) {
|
||||
rateLimitStore.delete(key);
|
||||
}
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const [key, record] of rateLimitStore.entries()) {
|
||||
if (now > record.resetAt) {
|
||||
rateLimitStore.delete(key);
|
||||
}
|
||||
},
|
||||
5 * 60 * 1000
|
||||
);
|
||||
}
|
||||
}, RATE_LIMIT_CLEANUP_INTERVAL_MS);
|
||||
|
||||
/**
|
||||
* Get client IP address from request headers
|
||||
@@ -320,19 +324,9 @@ export function checkRateLimit(
|
||||
|
||||
/**
|
||||
* Rate limit configuration for different operations
|
||||
* Re-exported from config for backward compatibility
|
||||
*/
|
||||
export const RATE_LIMITS = {
|
||||
// Login: 5 attempts per 15 minutes per IP
|
||||
LOGIN_IP: { maxAttempts: 5, windowMs: 15 * 60 * 1000 },
|
||||
// Login: 3 attempts per hour per email
|
||||
LOGIN_EMAIL: { maxAttempts: 3, windowMs: 60 * 60 * 1000 },
|
||||
// Password reset: 3 attempts per hour per IP
|
||||
PASSWORD_RESET_IP: { maxAttempts: 3, windowMs: 60 * 60 * 1000 },
|
||||
// Registration: 3 attempts per hour per IP
|
||||
REGISTRATION_IP: { maxAttempts: 3, windowMs: 60 * 60 * 1000 },
|
||||
// Email verification: 5 attempts per 15 minutes per IP
|
||||
EMAIL_VERIFICATION_IP: { maxAttempts: 5, windowMs: 15 * 60 * 1000 }
|
||||
} as const;
|
||||
export const RATE_LIMITS = CONFIG_RATE_LIMITS;
|
||||
|
||||
/**
|
||||
* Rate limiting middleware for login operations
|
||||
@@ -405,11 +399,9 @@ export function rateLimitEmailVerification(
|
||||
|
||||
/**
|
||||
* Account lockout configuration
|
||||
* Re-exported from config for backward compatibility
|
||||
*/
|
||||
export const ACCOUNT_LOCKOUT = {
|
||||
MAX_FAILED_ATTEMPTS: 5,
|
||||
LOCKOUT_DURATION_MS: 5 * 60 * 1000 // 5 minutes
|
||||
} as const;
|
||||
export const ACCOUNT_LOCKOUT = CONFIG_ACCOUNT_LOCKOUT;
|
||||
|
||||
/**
|
||||
* Check if an account is locked
|
||||
@@ -527,10 +519,9 @@ export async function resetFailedAttempts(userId: string): Promise<void> {
|
||||
|
||||
/**
|
||||
* Password reset token configuration
|
||||
* Re-exported from config for backward compatibility
|
||||
*/
|
||||
export const PASSWORD_RESET_CONFIG = {
|
||||
TOKEN_EXPIRY_MS: 60 * 60 * 1000 // 1 hour
|
||||
} as const;
|
||||
export const PASSWORD_RESET_CONFIG = CONFIG_PASSWORD_RESET;
|
||||
|
||||
/**
|
||||
* Create a password reset token
|
||||
|
||||
Reference in New Issue
Block a user