pediatric-ai-scribe-v3/admin-cli.js
ifedan-ed ac8e7bb890 v3.0.0: Auth, admin panel, security fixes, per-tab model selector
- Add authMiddleware to all AI/transcribe routes (were unauthenticated)
- Add full admin panel: user management, registration toggle, stats
- Fix XSS in email verification (escape user.name in HTML)
- Fix missing APP_URL fallback in password reset email
- Add per-tab model selector (respects OpenRouter/Bedrock/Azure lists)
- Fix transcribeAudio to send Authorization header
- Fix labs input: textarea instead of single-line input
- Add structured logging: audit_log, api_log, access_log tables
- Add admin CLI (admin-cli.js) for Docker exec management
- Fix duplicate var duration declaration in ai.js catch block
- Fix RETURNING check case-sensitivity in database.js
2026-03-21 19:25:51 -04:00

187 lines
8.5 KiB
JavaScript

#!/usr/bin/env node
// ============================================================
// ADMIN CLI — Command line admin tool
// Run inside Docker: docker exec pediatric-ai-scribe node admin-cli.js <command>
// ============================================================
require('dotenv').config();
var db = require('./src/db/database');
var bcrypt = require('bcryptjs');
var args = process.argv.slice(2);
var command = args[0];
async function main() {
// Verify DB connection before proceeding
await db.query('SELECT 1', []);
switch (command) {
case 'make-admin': {
var email = args[1];
if (!email) { console.log('Usage: node admin-cli.js make-admin <email>'); process.exit(1); }
var user = await db.get('SELECT id, name, role FROM users WHERE email = ?', [email.toLowerCase()]);
if (!user) { console.log('❌ User not found: ' + email); process.exit(1); }
await db.run('UPDATE users SET role = ? WHERE id = ?', ['admin', user.id]);
console.log('✅ ' + user.name + ' (' + email + ') is now ADMIN');
break;
}
case 'remove-admin': {
var email = args[1];
if (!email) { console.log('Usage: node admin-cli.js remove-admin <email>'); process.exit(1); }
var user = await db.get('SELECT id, name, role FROM users WHERE email = ?', [email.toLowerCase()]);
if (!user) { console.log('❌ User not found: ' + email); process.exit(1); }
await db.run('UPDATE users SET role = ? WHERE id = ?', ['user', user.id]);
console.log('✅ ' + user.name + ' (' + email + ') is now regular user');
break;
}
case 'verify-user': {
var email = args[1];
if (!email) { console.log('Usage: node admin-cli.js verify-user <email>'); process.exit(1); }
var user = await db.get('SELECT id, name FROM users WHERE email = ?', [email.toLowerCase()]);
if (!user) { console.log('❌ User not found: ' + email); process.exit(1); }
await db.run('UPDATE users SET email_verified = true, verify_token = NULL WHERE id = ?', [user.id]);
console.log('✅ ' + user.name + ' (' + email + ') email verified');
break;
}
case 'disable-user': {
var email = args[1];
if (!email) { console.log('Usage: node admin-cli.js disable-user <email>'); process.exit(1); }
var user = await db.get('SELECT id, name FROM users WHERE email = ?', [email.toLowerCase()]);
if (!user) { console.log('❌ User not found: ' + email); process.exit(1); }
await db.run('UPDATE users SET disabled = true WHERE id = ?', [user.id]);
console.log('✅ ' + user.name + ' (' + email + ') DISABLED');
break;
}
case 'enable-user': {
var email = args[1];
if (!email) { console.log('Usage: node admin-cli.js enable-user <email>'); process.exit(1); }
var user = await db.get('SELECT id, name FROM users WHERE email = ?', [email.toLowerCase()]);
if (!user) { console.log('❌ User not found: ' + email); process.exit(1); }
await db.run('UPDATE users SET disabled = false WHERE id = ?', [user.id]);
console.log('✅ ' + user.name + ' (' + email + ') ENABLED');
break;
}
case 'delete-user': {
var email = args[1];
if (!email) { console.log('Usage: node admin-cli.js delete-user <email>'); process.exit(1); }
var user = await db.get('SELECT id, name FROM users WHERE email = ?', [email.toLowerCase()]);
if (!user) { console.log('❌ User not found: ' + email); process.exit(1); }
await db.run('DELETE FROM users WHERE id = ?', [user.id]);
console.log('✅ ' + user.name + ' (' + email + ') DELETED');
break;
}
case 'reset-password': {
var email = args[1];
var newPass = args[2];
if (!email || !newPass) { console.log('Usage: node admin-cli.js reset-password <email> <new-password>'); process.exit(1); }
if (newPass.length < 8) { console.log('❌ Password must be 8+ characters'); process.exit(1); }
var user = await db.get('SELECT id, name FROM users WHERE email = ?', [email.toLowerCase()]);
if (!user) { console.log('❌ User not found: ' + email); process.exit(1); }
var hash = await bcrypt.hash(newPass, 12);
await db.run('UPDATE users SET password = ? WHERE id = ?', [hash, user.id]);
console.log('✅ Password reset for ' + user.name + ' (' + email + ')');
break;
}
case 'create-admin': {
var email = args[1];
var password = args[2];
var name = args[3];
if (!email || !password || !name) {
console.log('Usage: node admin-cli.js create-admin <email> <password> <name>');
process.exit(1);
}
if (password.length < 8) { console.log('❌ Password must be 8+ characters'); process.exit(1); }
var existing = await db.get('SELECT id FROM users WHERE email = ?', [email.toLowerCase()]);
if (existing) { console.log('❌ Email already exists: ' + email); process.exit(1); }
var hash = await bcrypt.hash(password, 12);
await db.run(
'INSERT INTO users (email, password, name, role, email_verified) VALUES (?, ?, ?, ?, true)',
[email.toLowerCase(), hash, name, 'admin']
);
console.log('✅ Admin user created: ' + name + ' (' + email + ')');
break;
}
case 'list-users': {
var users = await db.all('SELECT id, email, name, role, email_verified, disabled, created_at FROM users ORDER BY id', []);
console.log('');
console.log('ID | Email | Name | Role | Verified | Disabled');
console.log('---+---------------------------+-------------------+-------+----------+---------');
users.forEach(function(u) {
console.log(
String(u.id).padEnd(3) + '| ' +
u.email.padEnd(26) + '| ' +
(u.name || '').padEnd(18) + '| ' +
(u.role || 'user').padEnd(6) + '| ' +
(u.email_verified ? '✅' : '❌').padEnd(9) + '| ' +
(u.disabled ? '🚫 Yes' : '✅ No')
);
});
console.log('');
console.log('Total: ' + users.length + ' users');
break;
}
case 'toggle-registration': {
var current = await db.getSetting('registration_enabled');
var newValue = current === 'false' ? 'true' : 'false';
await db.setSetting('registration_enabled', newValue);
console.log('✅ Registration is now: ' + (newValue === 'true' ? 'ENABLED' : 'DISABLED'));
break;
}
case 'stats': {
var userCount = await db.get('SELECT COUNT(*) as count FROM users', []);
var apiCount = await db.get('SELECT COUNT(*) as count FROM api_log', []);
var todayApi = await db.get("SELECT COUNT(*) as count FROM api_log WHERE timestamp > NOW() - INTERVAL '1 day'", []);
var regEnabled = await db.getSetting('registration_enabled');
console.log('');
console.log('📊 App Statistics');
console.log('─────────────────');
console.log('Users: ' + (userCount ? userCount.count : 0));
console.log('Total API calls: ' + (apiCount ? apiCount.count : 0));
console.log('Today API calls: ' + (todayApi ? todayApi.count : 0));
console.log('Registration: ' + (regEnabled !== 'false' ? '✅ Enabled' : '❌ Disabled'));
console.log('');
break;
}
default:
console.log('');
console.log('🏥 Pediatric AI Scribe — Admin CLI');
console.log('');
console.log('Usage: docker exec pediatric-ai-scribe node admin-cli.js <command>');
console.log('');
console.log('User Management:');
console.log(' list-users List all users');
console.log(' create-admin <email> <password> <name> Create admin user');
console.log(' make-admin <email> Promote user to admin');
console.log(' remove-admin <email> Demote admin to user');
console.log(' verify-user <email> Verify user email');
console.log(' disable-user <email> Disable user account');
console.log(' enable-user <email> Enable user account');
console.log(' delete-user <email> Delete user permanently');
console.log(' reset-password <email> <new-password> Reset user password');
console.log('');
console.log('App Settings:');
console.log(' toggle-registration Toggle registration on/off');
console.log(' stats Show app statistics');
console.log('');
}
process.exit(0);
}
main().catch(function(err) {
console.error('❌ Error:', err.message);
process.exit(1);
});