deckhearth/pages/api/auth-utils.js
Randall Stillwell 5d72277355 fix(auth): centralize JWT secret + 24h TTL (Brief 1 of fix-auth-bypass)
- New `lib/auth-secret.js` is the single source of truth for `JWT_SECRET`
  and the canonical `JWT_TOKEN_TTL = '24h'`. Module throws at import time
  if `process.env.JWT_SECRET` is unset — no silent fallback to the literal
  `'your-secret-key-change-in-production'`.

- 7 callers refactored to import from the helper:
    lib/permission-middleware.js
    pages/api/auth-utils.js   (also drops unused `'7d'` → JWT_TOKEN_TTL)
    pages/api/auth/login.js   (also routes via auth-utils.generateToken)
    pages/api/auth/register.js (same)
    pages/api/auth/verify.js  (Brief 2 still owns the no-token admin branch)
    pages/api/favorites.js
    pages/api/users/search.js

- `process.env.JWT_SECRET` now appears exactly once in the JS source
  (lib/auth-secret.js). `your-secret-key-change-in-production` is gone.

- TTL drift reconciled: auth-utils used `'7d'`, login/register used
  inline `'24h'`. Both now route through imported `JWT_TOKEN_TTL` (24h).

Pre-deploy reminder: Vercel must have `JWT_SECRET` set before merge or
serverless functions refuse to boot. Existing tokens (signed against the
fallback literal) will be invalidated — users will need to log in again.

Resolves AGENTS.md gotcha #3. Brief 2/3/4/5 still pending in convoy.

Convoy: fix-auth-bypass / Brief 1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 09:47:11 -05:00

57 lines
No EOL
1.3 KiB
JavaScript

import jwt from 'jsonwebtoken';
import { db } from '../../lib/database.js';
import { JWT_SECRET, JWT_TOKEN_TTL } from '../../lib/auth-secret.js';
export async function hashPassword(password) {
const bcrypt = await import('bcryptjs');
return await bcrypt.hash(password, 12);
}
export async function verifyPassword(password, hashedPassword) {
const bcrypt = await import('bcryptjs');
return await bcrypt.compare(password, hashedPassword);
}
export function generateToken(user) {
return jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
JWT_SECRET,
{ expiresIn: JWT_TOKEN_TTL }
);
}
export function verifyToken(token) {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
return null;
}
}
export async function isAdmin(userId) {
try {
const result = await db.query(`
SELECT role FROM users WHERE id = $1
`, [userId]);
return result.rows[0]?.role === 'admin';
} catch (error) {
console.error('Error checking admin status:', error);
return false;
}
}
export async function getUserById(userId) {
try {
const result = await db.query(`
SELECT id, email, role, created_at FROM users WHERE id = $1
`, [userId]);
return result.rows[0];
} catch (error) {
console.error('Error getting user:', error);
return null;
}
}