Browse Source

fix(auth): throw on missing JWT_SECRET in prod, add rate-limit cleanup

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
npmrun 1 week ago
parent
commit
c518b3bab1
  1. 11
      server/service/auth/lib/jwt.ts
  2. 9
      server/service/auth/lib/rate-limit.ts

11
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 {

9
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) {

Loading…
Cancel
Save