mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
scripts/reset-admin.js signed a JWT with a synthetic id ("recovery-XXX")
and instructed the operator to paste it into localStorage. But the
requireAuth middleware always SELECTs the user row by id, so every
authed API call under the recovery token returned 401 "User not found"
and the recovery flow was effectively dead.
Fix:
- reset-admin.js now sets a `recovery: true` claim on the JWT.
- requireAuth / optionalAuth short-circuit the DB lookup when
decoded.recovery === true and synthesize a req.user record in
memory (role: admin, plan_id: enterprise). The synthetic user is
never persisted, so FK-constrained writes that expect a real
user (creating devices, etc.) will still fail — which is fine,
recovery is only meant to let the operator reset a password or
create a fresh admin via the Settings UI.
Security: a recovery token still requires the jwtSecret to sign,
so only someone with filesystem access to the server can mint one.
Token TTL remains 1h.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
88 lines
2.7 KiB
JavaScript
88 lines
2.7 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const config = require('../config');
|
|
const { db } = require('../db/database');
|
|
|
|
function generateToken(user) {
|
|
return jwt.sign(
|
|
{ id: user.id, email: user.email, role: user.role },
|
|
config.jwtSecret,
|
|
{ algorithm: 'HS256', expiresIn: config.jwtExpiry }
|
|
);
|
|
}
|
|
|
|
function verifyToken(token) {
|
|
return jwt.verify(token, config.jwtSecret, { algorithms: ['HS256'] });
|
|
}
|
|
|
|
// Synthetic user record for recovery tokens (scripts/reset-admin.js). Not
|
|
// persisted; only exists for the lifetime of the request.
|
|
function recoveryUser(decoded) {
|
|
return {
|
|
id: decoded.id,
|
|
email: decoded.email || 'admin@localhost',
|
|
name: 'Recovery Admin',
|
|
role: decoded.role || 'admin',
|
|
auth_provider: 'recovery',
|
|
avatar_url: null,
|
|
plan_id: 'enterprise'
|
|
};
|
|
}
|
|
|
|
// Express middleware - requires valid JWT
|
|
function requireAuth(req, res, next) {
|
|
const authHeader = req.headers.authorization;
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
try {
|
|
const token = authHeader.split(' ')[1];
|
|
const decoded = verifyToken(token);
|
|
if (decoded.recovery) {
|
|
req.user = recoveryUser(decoded);
|
|
return next();
|
|
}
|
|
const user = db.prepare('SELECT id, email, name, role, auth_provider, avatar_url, plan_id FROM users WHERE id = ?').get(decoded.id);
|
|
if (!user) return res.status(401).json({ error: 'User not found' });
|
|
req.user = user;
|
|
next();
|
|
} catch (err) {
|
|
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
}
|
|
}
|
|
|
|
// Optional auth - sets req.user if token present, continues either way
|
|
function optionalAuth(req, res, next) {
|
|
const authHeader = req.headers.authorization;
|
|
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
try {
|
|
const token = authHeader.split(' ')[1];
|
|
const decoded = verifyToken(token);
|
|
req.user = decoded.recovery
|
|
? recoveryUser(decoded)
|
|
: db.prepare('SELECT id, email, name, role, auth_provider, avatar_url, plan_id FROM users WHERE id = ?').get(decoded.id);
|
|
} catch (err) {
|
|
// Token invalid, continue without user
|
|
}
|
|
}
|
|
next();
|
|
}
|
|
|
|
// Require admin role (admin or superadmin)
|
|
function requireAdmin(req, res, next) {
|
|
if (!req.user || !['admin', 'superadmin'].includes(req.user.role)) {
|
|
return res.status(403).json({ error: 'Admin access required' });
|
|
}
|
|
next();
|
|
}
|
|
|
|
// Require superadmin role (platform owner only)
|
|
function requireSuperAdmin(req, res, next) {
|
|
if (!req.user || req.user.role !== 'superadmin') {
|
|
return res.status(403).json({ error: 'Platform admin access required' });
|
|
}
|
|
next();
|
|
}
|
|
|
|
module.exports = { generateToken, verifyToken, requireAuth, optionalAuth, requireAdmin, requireSuperAdmin };
|