deckhearth/pages/api/auth/verify.js
Randall Stillwell 258e479dc5 fix(auth): remove synthetic-admin bypass (Brief 2 of fix-auth-bypass)
Closes AGENTS.md gotcha #2: getUserFromRequest no longer returns a
hardcoded { userId: 1, email: 'admin@tcgvault.com', role: 'admin' }
when the Authorization header is missing or malformed.

lib/permission-middleware.js
  - getUserFromRequest now returns null for missing/malformed Bearer
    headers. No console.warn, no NODE_ENV gate — the fallback is gone,
    period.
  - Token-verify path and DB lookup unchanged.

pages/api/auth/verify.js
  - No-token branch now returns 401 instead of fetching the seed admin
    via `WHERE email = 'admin@tcgvault.com'`. Closes the admin-record-
    leak side of the same bypass.
  - JWT-verify branch unchanged.

Known follow-up (flagged but NOT addressed in this PR):
  pages/api/collections/[identifier]/cards.js POST/PUT/DELETE handlers
  dereference user.userId without a null guard. Previously masked by
  the synthetic admin (anonymous-write-as-admin on collections owned
  by user 1 was the security hole). Now degrades to NPE → 500 instead
  of a clean 401. Security is improved either way; cosmetic 500-vs-401
  fix lives in a separate one-line follow-up PR.

Convoy: fix-auth-bypass / Brief 2
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 10:57:23 -05:00

56 lines
No EOL
1.6 KiB
JavaScript

import { sql } from '@vercel/postgres';
import jwt from 'jsonwebtoken';
import { JWT_SECRET } from '../../../lib/auth-secret.js';
export default async function handler(req, res) {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Handle preflight requests
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Authentication required' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, JWT_SECRET);
// Get user data from database
const result = await sql`
SELECT id, email, role, created_at
FROM users
WHERE id = ${decoded.userId}
`;
if (result.rows.length === 0) {
return res.status(401).json({ error: 'User not found' });
}
const user = result.rows[0];
res.status(200).json(user);
} catch (jwtError) {
console.error('JWT verification error:', jwtError);
return res.status(401).json({ error: 'Invalid token' });
}
} catch (error) {
console.error('Auth verification error:', error);
res.status(500).json({ error: 'Internal server error' });
}
}