From c518b3bab1f8cf48e392e7f79a70879096f03e6c Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Fri, 22 May 2026 16:32:35 +0800 Subject: [PATCH] fix(auth): throw on missing JWT_SECRET in prod, add rate-limit cleanup Co-Authored-By: Claude Opus 4.7 --- server/service/auth/lib/jwt.ts | 11 ++++++++--- server/service/auth/lib/rate-limit.ts | 9 +++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/server/service/auth/lib/jwt.ts b/server/service/auth/lib/jwt.ts index bd7f640..464697d 100644 --- a/server/service/auth/lib/jwt.ts +++ b/server/service/auth/lib/jwt.ts @@ -1,9 +1,14 @@ import { SignJWT, jwtVerify, decodeJwt } from "jose"; import type { JWTPayload } from "jose"; -const JWT_SECRET = new TextEncoder().encode( - process.env.JWT_SECRET || "dev-secret-change-in-production" -); +const rawSecret = process.env.JWT_SECRET; +if (!rawSecret) { + if (process.env.NODE_ENV === "production") { + throw new Error("JWT_SECRET environment variable is required in production"); + } + rawSecret = "dev-secret-change-in-production"; +} +const JWT_SECRET = new TextEncoder().encode(rawSecret); const ACCESS_TOKEN_EXPIRY = "15m"; export interface AccessTokenPayload extends JWTPayload { diff --git a/server/service/auth/lib/rate-limit.ts b/server/service/auth/lib/rate-limit.ts index 956e017..0dfedcc 100644 --- a/server/service/auth/lib/rate-limit.ts +++ b/server/service/auth/lib/rate-limit.ts @@ -8,11 +8,20 @@ const WINDOW_MS = 60_000; // 1 minute window const MAX_ATTEMPTS = 5; // max 5 attempts per window const LOCKOUT_MS = 15 * 60_000; // 15 minute lockout +function cleanupExpired(now: number): void { + for (const [ip, entry] of loginAttempts.entries()) { + if (now > entry.resetAt) { + loginAttempts.delete(ip); + } + } +} + export function checkRateLimit(ip: string): { allowed: boolean; retryAfterMs: number; } { const now = Date.now(); + cleanupExpired(now); const entry = loginAttempts.get(ip); if (!entry || now > entry.resetAt) {