Admin User v1

This commit is contained in:
Eric Gullickson
2025-11-05 19:04:06 -06:00
parent e4e7e32a4f
commit 8174e0d5f9
48 changed files with 11289 additions and 1112 deletions

View File

@@ -0,0 +1,130 @@
/**
* @ai-summary Admin feature business logic
* @ai-context Handles admin user management with audit logging
*/
import { AdminRepository } from '../data/admin.repository';
import { AdminUser, AdminAuditLog } from './admin.types';
import { logger } from '../../../core/logging/logger';
export class AdminService {
constructor(private repository: AdminRepository) {}
async getAdminByAuth0Sub(auth0Sub: string): Promise<AdminUser | null> {
try {
return await this.repository.getAdminByAuth0Sub(auth0Sub);
} catch (error) {
logger.error('Error getting admin by auth0_sub', { error });
throw error;
}
}
async getAdminByEmail(email: string): Promise<AdminUser | null> {
try {
return await this.repository.getAdminByEmail(email);
} catch (error) {
logger.error('Error getting admin by email', { error });
throw error;
}
}
async getAllAdmins(): Promise<AdminUser[]> {
try {
return await this.repository.getAllAdmins();
} catch (error) {
logger.error('Error getting all admins', { error });
throw error;
}
}
async getActiveAdmins(): Promise<AdminUser[]> {
try {
return await this.repository.getActiveAdmins();
} catch (error) {
logger.error('Error getting active admins', { error });
throw error;
}
}
async createAdmin(email: string, role: string, auth0Sub: string, createdBy: string): Promise<AdminUser> {
try {
// Check if admin already exists
const normalizedEmail = email.trim().toLowerCase();
const existing = await this.repository.getAdminByEmail(normalizedEmail);
if (existing) {
throw new Error(`Admin user with email ${normalizedEmail} already exists`);
}
// Create new admin
const admin = await this.repository.createAdmin(auth0Sub, normalizedEmail, role, createdBy);
// Log audit action
await this.repository.logAuditAction(createdBy, 'CREATE', admin.auth0Sub, 'admin_user', admin.email, {
email,
role
});
logger.info('Admin user created', { email, role });
return admin;
} catch (error) {
logger.error('Error creating admin', { error, email });
throw error;
}
}
async revokeAdmin(auth0Sub: string, revokedBy: string): Promise<AdminUser> {
try {
// Check that at least one active admin will remain
const activeAdmins = await this.repository.getActiveAdmins();
if (activeAdmins.length <= 1) {
throw new Error('Cannot revoke the last active admin');
}
// Revoke the admin
const admin = await this.repository.revokeAdmin(auth0Sub);
// Log audit action
await this.repository.logAuditAction(revokedBy, 'REVOKE', auth0Sub, 'admin_user', admin.email);
logger.info('Admin user revoked', { auth0Sub, email: admin.email });
return admin;
} catch (error) {
logger.error('Error revoking admin', { error, auth0Sub });
throw error;
}
}
async reinstateAdmin(auth0Sub: string, reinstatedBy: string): Promise<AdminUser> {
try {
// Reinstate the admin
const admin = await this.repository.reinstateAdmin(auth0Sub);
// Log audit action
await this.repository.logAuditAction(reinstatedBy, 'REINSTATE', auth0Sub, 'admin_user', admin.email);
logger.info('Admin user reinstated', { auth0Sub, email: admin.email });
return admin;
} catch (error) {
logger.error('Error reinstating admin', { error, auth0Sub });
throw error;
}
}
async getAuditLogs(limit: number = 100, offset: number = 0): Promise<{ logs: AdminAuditLog[]; total: number }> {
try {
return await this.repository.getAuditLogs(limit, offset);
} catch (error) {
logger.error('Error fetching audit logs', { error });
throw error;
}
}
async linkAdminAuth0Sub(email: string, auth0Sub: string): Promise<AdminUser> {
try {
return await this.repository.updateAuth0SubByEmail(email.trim().toLowerCase(), auth0Sub);
} catch (error) {
logger.error('Error linking admin auth0_sub to email', { error, email, auth0Sub });
throw error;
}
}
}