- 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
254 lines
6.8 KiB
JavaScript
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();
|