88 occurrences of res.status(500).json({ error: e.message }) (or
err.message) swept to generic 'Request failed'. Server-side
console.error / logger.error calls are untouched, so the full detail
still lands in logs and Grafana.
Covers: admin, adminConfig, adminMilestones, chartReview, documents,
encounters, hospitalCourse, hpi, learningAdmin, learningAI, learningHub,
logs, memories, milestones, oidc, refine, sessions, sickVisit, soap,
userPreferences, wellVisit.
Also extends .gitignore to exclude .env.backup-* files.
241 lines
9.9 KiB
JavaScript
241 lines
9.9 KiB
JavaScript
// ============================================================
|
|
// ADMIN ROUTES — User management, settings
|
|
// ============================================================
|
|
|
|
var express = require('express');
|
|
var router = express.Router();
|
|
var bcrypt = require('bcryptjs');
|
|
var db = require('../db/database');
|
|
var { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
|
var logger = require('../utils/logger');
|
|
|
|
// All admin routes require auth + admin role
|
|
router.use(authMiddleware);
|
|
router.use(adminMiddleware);
|
|
|
|
// ============================================================
|
|
// GET ALL USERS
|
|
// ============================================================
|
|
router.get('/users', async function(req, res) {
|
|
try {
|
|
var users = await db.all(
|
|
'SELECT id, email, name, role, email_verified, totp_enabled, disabled, created_at FROM users ORDER BY created_at DESC',
|
|
[]
|
|
);
|
|
res.json({ success: true, users: users });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// GET SINGLE USER DETAILS
|
|
// ============================================================
|
|
router.get('/users/:id', async function(req, res) {
|
|
try {
|
|
var user = await db.get(
|
|
'SELECT id, email, name, role, email_verified, totp_enabled, disabled, nextcloud_url, created_at, updated_at FROM users WHERE id = ?',
|
|
[req.params.id]
|
|
);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
// Get user stats
|
|
var apiCount = await db.get('SELECT COUNT(*) as count FROM api_log WHERE user_id = ?', [req.params.id]);
|
|
var lastLogin = await db.get("SELECT timestamp FROM audit_log WHERE user_id = ? AND action = 'login' ORDER BY timestamp DESC LIMIT 1", [req.params.id]);
|
|
|
|
user.api_calls = apiCount ? parseInt(apiCount.count) : 0;
|
|
user.last_login = lastLogin ? lastLogin.timestamp : null;
|
|
|
|
res.json({ success: true, user: user });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// VERIFY USER EMAIL (admin override)
|
|
// ============================================================
|
|
router.post('/users/:id/verify', async function(req, res) {
|
|
try {
|
|
var user = await db.get('SELECT id, email, name FROM users WHERE id = ?', [req.params.id]);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
await db.run('UPDATE users SET email_verified = true, verify_token = NULL, verify_expires = NULL WHERE id = ?', [user.id]);
|
|
|
|
logger.audit(req.user.id, 'admin_verify_user', 'Verified user: ' + user.email, req, { category: 'admin' });
|
|
|
|
res.json({ success: true, message: user.email + ' verified' });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// DISABLE USER
|
|
// ============================================================
|
|
router.post('/users/:id/disable', async function(req, res) {
|
|
try {
|
|
var userId = parseInt(req.params.id);
|
|
|
|
// Prevent disabling yourself
|
|
if (userId === req.user.id) {
|
|
return res.status(400).json({ error: 'Cannot disable your own account' });
|
|
}
|
|
|
|
var user = await db.get('SELECT id, email, name FROM users WHERE id = ?', [userId]);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
await db.run('UPDATE users SET disabled = true WHERE id = ?', [userId]);
|
|
|
|
logger.audit(req.user.id, 'admin_disable_user', 'Disabled user: ' + user.email, req, { category: 'admin' });
|
|
|
|
res.json({ success: true, message: user.email + ' disabled' });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// ENABLE USER
|
|
// ============================================================
|
|
router.post('/users/:id/enable', async function(req, res) {
|
|
try {
|
|
var user = await db.get('SELECT id, email, name FROM users WHERE id = ?', [req.params.id]);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
await db.run('UPDATE users SET disabled = false WHERE id = ?', [user.id]);
|
|
|
|
logger.audit(req.user.id, 'admin_enable_user', 'Enabled user: ' + user.email, req, { category: 'admin' });
|
|
|
|
res.json({ success: true, message: user.email + ' enabled' });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// SET USER ROLE (admin/user)
|
|
// ============================================================
|
|
router.post('/users/:id/role', async function(req, res) {
|
|
try {
|
|
var userId = parseInt(req.params.id);
|
|
var { role } = req.body;
|
|
|
|
if (!role || (role !== 'admin' && role !== 'user' && role !== 'moderator')) {
|
|
return res.status(400).json({ error: 'Role must be "admin", "user", or "moderator"' });
|
|
}
|
|
|
|
// Prevent removing your own admin
|
|
if (userId === req.user.id && role !== 'admin') {
|
|
return res.status(400).json({ error: 'Cannot remove your own admin role' });
|
|
}
|
|
|
|
var user = await db.get('SELECT id, email FROM users WHERE id = ?', [userId]);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
await db.run('UPDATE users SET role = ? WHERE id = ?', [role, userId]);
|
|
|
|
logger.audit(req.user.id, 'admin_change_role', 'Set ' + user.email + ' role to ' + role, req, { category: 'admin' });
|
|
|
|
res.json({ success: true, message: user.email + ' is now ' + role });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// DELETE USER
|
|
// ============================================================
|
|
router.delete('/users/:id', async function(req, res) {
|
|
try {
|
|
var userId = parseInt(req.params.id);
|
|
|
|
if (userId === req.user.id) {
|
|
return res.status(400).json({ error: 'Cannot delete your own account' });
|
|
}
|
|
|
|
var user = await db.get('SELECT id, email, name FROM users WHERE id = ?', [userId]);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
// Delete user (audit/api logs keep user_id as NULL due to ON DELETE SET NULL)
|
|
await db.run('DELETE FROM users WHERE id = ?', [userId]);
|
|
|
|
logger.audit(req.user.id, 'admin_delete_user', 'Deleted user: ' + user.email + ' (' + user.name + ')', req, { category: 'admin' });
|
|
|
|
res.json({ success: true, message: user.email + ' deleted' });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// RESET USER PASSWORD (admin override)
|
|
// ============================================================
|
|
router.post('/users/:id/reset-password', async function(req, res) {
|
|
try {
|
|
var { newPassword } = req.body;
|
|
if (!newPassword || newPassword.length < 8) return res.status(400).json({ error: 'Password must be 8+ characters' });
|
|
|
|
var user = await db.get('SELECT id, email FROM users WHERE id = ?', [req.params.id]);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
var hash = await bcrypt.hash(newPassword, 12);
|
|
await db.run('UPDATE users SET password = ?, reset_token = NULL, reset_expires = NULL WHERE id = ?', [hash, user.id]);
|
|
|
|
logger.audit(req.user.id, 'admin_reset_password', 'Reset password for: ' + user.email, req, { category: 'admin' });
|
|
|
|
res.json({ success: true, message: 'Password reset for ' + user.email });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// TOGGLE REGISTRATION
|
|
// ============================================================
|
|
router.post('/settings/registration', async function(req, res) {
|
|
try {
|
|
var { enabled } = req.body;
|
|
var value = enabled ? 'true' : 'false';
|
|
|
|
await db.setSetting('registration_enabled', value);
|
|
|
|
logger.audit(req.user.id, 'admin_toggle_registration', 'Registration ' + (enabled ? 'enabled' : 'disabled'), req, { category: 'admin' });
|
|
|
|
res.json({ success: true, registrationEnabled: enabled, message: 'Registration ' + (enabled ? 'enabled' : 'disabled') });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// GET APP SETTINGS
|
|
// ============================================================
|
|
router.get('/settings', async function(req, res) {
|
|
try {
|
|
var regEnabled = await db.getSetting('registration_enabled');
|
|
var userCount = await db.get('SELECT COUNT(*) as count FROM users', []);
|
|
var apiCount = await db.get('SELECT COUNT(*) as count FROM api_log', []);
|
|
var todayApiCount = await db.get("SELECT COUNT(*) as count FROM api_log WHERE timestamp > NOW() - INTERVAL '1 day'", []);
|
|
|
|
res.json({
|
|
success: true,
|
|
settings: {
|
|
registrationEnabled: regEnabled !== 'false'
|
|
},
|
|
stats: {
|
|
totalUsers: userCount ? parseInt(userCount.count) : 0,
|
|
totalApiCalls: apiCount ? parseInt(apiCount.count) : 0,
|
|
todayApiCalls: todayApiCount ? parseInt(todayApiCount.count) : 0
|
|
}
|
|
});
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
// ============================================================
|
|
// GET ALL LOGS (admin view)
|
|
// ============================================================
|
|
router.get('/logs/all', async function(req, res) {
|
|
try {
|
|
var limit = Math.min(parseInt(req.query.limit) || 100, 500);
|
|
var category = req.query.category;
|
|
|
|
var sql = 'SELECT a.*, u.email as user_email, u.name as user_name FROM audit_log a LEFT JOIN users u ON a.user_id = u.id';
|
|
var params = [];
|
|
|
|
if (category) {
|
|
sql += ' WHERE a.category = ?';
|
|
params.push(category);
|
|
}
|
|
|
|
sql += ' ORDER BY a.timestamp DESC LIMIT ?';
|
|
params.push(limit);
|
|
|
|
var logs = await db.all(sql, params);
|
|
res.json({ success: true, logs: logs });
|
|
} catch (err) { res.status(500).json({ error: 'Request failed' }); }
|
|
});
|
|
|
|
module.exports = router;
|