Files
award/src/backend/scripts/admin-cli.js
Joerg e88537754f feat: implement comprehensive admin functionality
- Add admin role system with role and isAdmin fields to users table
- Create admin_actions audit log table for tracking all admin operations
- Implement admin CLI tool for user management (create, promote, demote, list, check)
- Add admin authentication with role-based access control
- Create admin service layer with system statistics and user management
- Implement user impersonation system with proper security checks
- Add admin API endpoints for user management and system statistics
- Create admin dashboard UI with overview, users, and action logs
- Fix admin stats endpoint and user deletion with proper foreign key handling
- Add admin link to navigation bar for admin users

Database:
- Add role and isAdmin columns to users table
- Create admin_actions table for audit trail
- Migration script: add-admin-functionality.js

CLI:
- src/backend/scripts/admin-cli.js - Admin user management tool

Backend:
- src/backend/services/admin.service.js - Admin business logic
- Updated auth.service.js with admin helper functions
- Enhanced index.js with admin routes and middleware
- Export sqlite connection from config for raw SQL operations

Frontend:
- src/frontend/src/routes/admin/+page.svelte - Admin dashboard
- Updated api.js with adminAPI functions
- Added Admin link to navigation bar

Security:
- Admin-only endpoints with role verification
- Audit logging for all admin actions
- Impersonation with 1-hour token expiration
- Foreign key constraint handling for user deletion
- Cannot delete self or other admins
- Last admin protection
2026-01-21 09:43:56 +01:00

254 lines
6.8 KiB
JavaScript

#!/usr/bin/env bun
/**
* Admin CLI Tool
*
* Usage:
* bun src/backend/scripts/admin-cli.js create <email> <password> <callsign>
* bun src/backend/scripts/admin-cli.js promote <email>
* bun src/backend/scripts/admin-cli.js demote <email>
* bun src/backend/scripts/admin-cli.js list
* bun src/backend/scripts/admin-cli.js check <email>
* bun src/backend/scripts/admin-cli.js help
*/
import Database from 'bun:sqlite';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
// ES module equivalent of __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const dbPath = join(__dirname, '../award.db');
const sqlite = new Database(dbPath);
// Enable foreign keys
sqlite.exec('PRAGMA foreign_keys = ON');
function help() {
console.log(`
Admin CLI Tool - Manage admin users
Commands:
create <email> <password> <callsign> Create a new admin user
promote <email> Promote existing user to admin
demote <email> Demote admin to regular user
list List all admin users
check <email> Check if user is admin
help Show this help message
Examples:
bun src/backend/scripts/admin-cli.js create admin@example.com secretPassword ADMIN
bun src/backend/scripts/admin-cli.js promote user@example.com
bun src/backend/scripts/admin-cli.js list
bun src/backend/scripts/admin-cli.js check user@example.com
`);
}
function createAdminUser(email, password, callsign) {
console.log(`Creating admin user: ${email}`);
// Check if user already exists
const existingUser = sqlite.query(`
SELECT id, email FROM users WHERE email = ?
`).get(email);
if (existingUser) {
console.error(`Error: User with email ${email} already exists`);
process.exit(1);
}
// Hash password
const passwordHash = Bun.password.hashSync(password, {
algorithm: 'bcrypt',
cost: 10,
});
// Ensure passwordHash is a string
const hashString = String(passwordHash);
// Insert admin user
const result = sqlite.query(`
INSERT INTO users (email, password_hash, callsign, role, is_admin, created_at, updated_at)
VALUES (?, ?, ?, 'admin', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000)
`).run(email, hashString, callsign);
console.log(`✓ Admin user created successfully!`);
console.log(` ID: ${result.lastInsertRowid}`);
console.log(` Email: ${email}`);
console.log(` Callsign: ${callsign}`);
console.log(` Role: admin`);
console.log(`\nYou can now log in with these credentials.`);
}
function promoteUser(email) {
console.log(`Promoting user to admin: ${email}`);
// Check if user exists
const user = sqlite.query(`
SELECT id, email, role, is_admin FROM users WHERE email = ?
`).get(email);
if (!user) {
console.error(`Error: User with email ${email} not found`);
process.exit(1);
}
if (user.role === 'admin' && user.is_admin === 1) {
console.log(`User ${email} is already an admin`);
return;
}
// Update user to admin
sqlite.query(`
UPDATE users
SET role = 'admin', is_admin = 1, updated_at = strftime('%s', 'now') * 1000
WHERE email = ?
`).run(email);
console.log(`✓ User ${email} has been promoted to admin`);
}
function demoteUser(email) {
console.log(`Demoting admin to regular user: ${email}`);
// Check if user exists
const user = sqlite.query(`
SELECT id, email, role, is_admin FROM users WHERE email = ?
`).get(email);
if (!user) {
console.error(`Error: User with email ${email} not found`);
process.exit(1);
}
if (user.role !== 'admin' || user.is_admin !== 1) {
console.log(`User ${email} is not an admin`);
return;
}
// Check if this is the last admin
const adminCount = sqlite.query(`
SELECT COUNT(*) as count FROM users WHERE role = 'admin' AND is_admin = 1
`).get();
if (adminCount.count === 1) {
console.error(`Error: Cannot demote the last admin user. At least one admin must exist.`);
process.exit(1);
}
// Update user to regular user
sqlite.query(`
UPDATE users
SET role = 'user', is_admin = 0, updated_at = strftime('%s', 'now') * 1000
WHERE email = ?
`).run(email);
console.log(`✓ User ${email} has been demoted to regular user`);
}
function listAdmins() {
console.log('Listing all admin users...\n');
const admins = sqlite.query(`
SELECT id, email, callsign, created_at
FROM users
WHERE role = 'admin' AND is_admin = 1
ORDER BY created_at ASC
`).all();
if (admins.length === 0) {
console.log('No admin users found');
return;
}
console.log(`Found ${admins.length} admin user(s):\n`);
console.log('ID | Email | Callsign | Created At');
console.log('----+----------------------------+----------+---------------------');
admins.forEach((admin) => {
const createdAt = new Date(admin.created_at).toLocaleString();
console.log(`${String(admin.id).padEnd(3)} | ${admin.email.padEnd(26)} | ${admin.callsign.padEnd(8)} | ${createdAt}`);
});
}
function checkUser(email) {
console.log(`Checking user status: ${email}\n`);
const user = sqlite.query(`
SELECT id, email, callsign, role, is_admin FROM users WHERE email = ?
`).get(email);
if (!user) {
console.log(`User not found: ${email}`);
process.exit(1);
}
const isAdmin = user.role === 'admin' && user.is_admin === 1;
console.log(`User found:`);
console.log(` Email: ${user.email}`);
console.log(` Callsign: ${user.callsign}`);
console.log(` Role: ${user.role}`);
console.log(` Is Admin: ${isAdmin ? 'Yes ✓' : 'No'}`);
}
// Main CLI logic
const command = process.argv[2];
const args = process.argv.slice(3);
switch (command) {
case 'create':
if (args.length !== 3) {
console.error('Error: create command requires 3 arguments: <email> <password> <callsign>');
help();
process.exit(1);
}
createAdminUser(args[0], args[1], args[2]);
break;
case 'promote':
if (args.length !== 1) {
console.error('Error: promote command requires 1 argument: <email>');
help();
process.exit(1);
}
promoteUser(args[0]);
break;
case 'demote':
if (args.length !== 1) {
console.error('Error: demote command requires 1 argument: <email>');
help();
process.exit(1);
}
demoteUser(args[0]);
break;
case 'list':
listAdmins();
break;
case 'check':
if (args.length !== 1) {
console.error('Error: check command requires 1 argument: <email>');
help();
process.exit(1);
}
checkUser(args[0]);
break;
case 'help':
case '--help':
case '-h':
help();
break;
default:
console.error(`Error: Unknown command '${command}'`);
help();
process.exit(1);
}
sqlite.close();