Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -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).
19 changes: 16 additions & 3 deletions src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -79,15 +92,15 @@ 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 });
}

/**
* Verify and decode JWT token
*/
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;
Expand Down