diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..a7ca9123 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-05-22 - Hardcoded JWT Secret Fallback Removed +**Vulnerability:** A hardcoded string (`'your-secret-key-change-this'`) was used as a fallback for `JWT_SECRET` in `src/lib/auth.ts`. +**Learning:** This existed to prevent crashes in development environments where `NEXTAUTH_SECRET` might not be set. However, it creates a critical vulnerability where tokens could be predictably forged in production if the environment variable is accidentally missing or misconfigured. +**Prevention:** Authentication mechanisms must "fail securely." In production, if critical secrets are missing, the application should throw a fatal error rather than silently falling back to insecure defaults. In development, generate a cryptographically strong random string (e.g., using `crypto.randomBytes`) so that the application still works but remains secure (even though tokens will be invalidated on restart). diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 9a936ce5..fa43622f 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -2,8 +2,21 @@ import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; import { IUser } from '@/models/User'; import { NextRequest } from 'next/server'; +import crypto from 'crypto'; + +// Security Fix: Prevent use of hardcoded secret key for JWT signing. +// If NEXTAUTH_SECRET is not provided, we fail securely in production. +// In development, we use a strong random byte string so tokens are secure (though they invalidate on restart). +let JWT_SECRET = process.env.NEXTAUTH_SECRET; +if (!JWT_SECRET) { + if (process.env.NODE_ENV === 'production') { + throw new Error('CRITICAL SECURITY ERROR: NEXTAUTH_SECRET environment variable is not set.'); + } else { + console.warn('WARNING: NEXTAUTH_SECRET is not set. Generating a temporary random secret for development.'); + JWT_SECRET = crypto.randomBytes(32).toString('hex'); + } +} -const JWT_SECRET = process.env.NEXTAUTH_SECRET || 'your-secret-key-change-this'; const JWT_EXPIRY = '7d'; // 7 days export interface JWTPayload { @@ -79,7 +92,7 @@ export function generateToken(user: IUser): string { assignedTournaments: user.assignedTournaments || [], }; - return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRY }); + return jwt.sign(payload, JWT_SECRET as string, { expiresIn: JWT_EXPIRY }); } /** @@ -87,7 +100,7 @@ export function generateToken(user: IUser): string { */ export function verifyToken(token: string): JWTPayload | null { try { - const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload; + const decoded = jwt.verify(token, JWT_SECRET as string) as JWTPayload; return decoded; } catch (error) { return null;