/** * Admin Auth API handlers * Platform admin endpoints for managing authentication providers and settings */ import type { IApiContext, IApiResponse } from '../router.ts'; import { AuthProvider, PlatformSettings } from '../../models/index.ts'; import { cryptoService } from '../../services/crypto.service.ts'; import { externalAuthService } from '../../services/external.auth.service.ts'; import { AuditService } from '../../services/audit.service.ts'; import type { ICreateAuthProviderDto, IUpdateAuthProviderDto, } from '../../interfaces/auth.interfaces.ts'; export class AdminAuthApi { /** * Check if actor is platform admin */ private requirePlatformAdmin(ctx: IApiContext): IApiResponse | null { if (!ctx.actor?.userId || !ctx.actor.user?.isPlatformAdmin) { return { status: 403, body: { error: 'Platform admin access required' }, }; } return null; } /** * GET /api/v1/admin/auth/providers * List all authentication providers */ public async listProviders(ctx: IApiContext): Promise { const authError = this.requirePlatformAdmin(ctx); if (authError) return authError; try { const providers = await AuthProvider.getAllProviders(); return { status: 200, body: { providers: providers.map((p) => p.toAdminInfo()), }, }; } catch (error) { console.error('[AdminAuthApi] List providers error:', error); return { status: 500, body: { error: 'Failed to list providers' }, }; } } /** * POST /api/v1/admin/auth/providers * Create a new authentication provider */ public async createProvider(ctx: IApiContext): Promise { const authError = this.requirePlatformAdmin(ctx); if (authError) return authError; try { const body = (await ctx.request.json()) as ICreateAuthProviderDto; // Validate required fields if (!body.name || !body.displayName || !body.type) { return { status: 400, body: { error: 'name, displayName, and type are required' }, }; } // Check name uniqueness const existing = await AuthProvider.findByName(body.name); if (existing) { return { status: 409, body: { error: 'Provider name already exists' }, }; } // Validate type-specific config if (body.type === 'oidc' && !body.oauthConfig) { return { status: 400, body: { error: 'oauthConfig is required for OIDC provider' }, }; } if (body.type === 'ldap' && !body.ldapConfig) { return { status: 400, body: { error: 'ldapConfig is required for LDAP provider' }, }; } let provider: AuthProvider; if (body.type === 'oidc' && body.oauthConfig) { // Encrypt client secret const encryptedSecret = await cryptoService.encrypt(body.oauthConfig.clientSecretEncrypted); provider = await AuthProvider.createOAuthProvider({ name: body.name, displayName: body.displayName, oauthConfig: { ...body.oauthConfig, clientSecretEncrypted: encryptedSecret, }, attributeMapping: body.attributeMapping, provisioning: body.provisioning, createdById: ctx.actor!.userId, }); } else if (body.type === 'ldap' && body.ldapConfig) { // Encrypt bind password const encryptedPassword = await cryptoService.encrypt(body.ldapConfig.bindPasswordEncrypted); provider = await AuthProvider.createLdapProvider({ name: body.name, displayName: body.displayName, ldapConfig: { ...body.ldapConfig, bindPasswordEncrypted: encryptedPassword, }, attributeMapping: body.attributeMapping, provisioning: body.provisioning, createdById: ctx.actor!.userId, }); } else { return { status: 400, body: { error: 'Invalid provider type' }, }; } // Audit log await AuditService.withContext({ actorId: ctx.actor!.userId, actorType: 'user', actorIp: ctx.ip, }).log('ORGANIZATION_CREATED', 'system', { resourceId: provider.id, success: true, metadata: { action: 'auth_provider_created', providerName: provider.name, providerType: provider.type, }, }); return { status: 201, body: provider.toAdminInfo(), }; } catch (error) { console.error('[AdminAuthApi] Create provider error:', error); return { status: 500, body: { error: 'Failed to create provider' }, }; } } /** * GET /api/v1/admin/auth/providers/:id * Get a specific authentication provider */ public async getProvider(ctx: IApiContext): Promise { const authError = this.requirePlatformAdmin(ctx); if (authError) return authError; try { const { id } = ctx.params; const provider = await AuthProvider.findById(id); if (!provider) { return { status: 404, body: { error: 'Provider not found' }, }; } return { status: 200, body: provider.toAdminInfo(), }; } catch (error) { console.error('[AdminAuthApi] Get provider error:', error); return { status: 500, body: { error: 'Failed to get provider' }, }; } } /** * PUT /api/v1/admin/auth/providers/:id * Update an authentication provider */ public async updateProvider(ctx: IApiContext): Promise { const authError = this.requirePlatformAdmin(ctx); if (authError) return authError; try { const { id } = ctx.params; const provider = await AuthProvider.findById(id); if (!provider) { return { status: 404, body: { error: 'Provider not found' }, }; } const body = (await ctx.request.json()) as IUpdateAuthProviderDto; // Update basic fields if (body.displayName !== undefined) provider.displayName = body.displayName; if (body.status !== undefined) provider.status = body.status; if (body.priority !== undefined) provider.priority = body.priority; // Update OAuth config if (body.oauthConfig && provider.oauthConfig) { const newOAuthConfig = { ...provider.oauthConfig, ...body.oauthConfig }; // Encrypt new client secret if provided and not already encrypted if ( body.oauthConfig.clientSecretEncrypted && !cryptoService.isEncrypted(body.oauthConfig.clientSecretEncrypted) ) { newOAuthConfig.clientSecretEncrypted = await cryptoService.encrypt( body.oauthConfig.clientSecretEncrypted ); } provider.oauthConfig = newOAuthConfig; } // Update LDAP config if (body.ldapConfig && provider.ldapConfig) { const newLdapConfig = { ...provider.ldapConfig, ...body.ldapConfig }; // Encrypt new bind password if provided and not already encrypted if ( body.ldapConfig.bindPasswordEncrypted && !cryptoService.isEncrypted(body.ldapConfig.bindPasswordEncrypted) ) { newLdapConfig.bindPasswordEncrypted = await cryptoService.encrypt( body.ldapConfig.bindPasswordEncrypted ); } provider.ldapConfig = newLdapConfig; } // Update attribute mapping if (body.attributeMapping) { provider.attributeMapping = { ...provider.attributeMapping, ...body.attributeMapping }; } // Update provisioning settings if (body.provisioning) { provider.provisioning = { ...provider.provisioning, ...body.provisioning }; } await provider.save(); // Audit log await AuditService.withContext({ actorId: ctx.actor!.userId, actorType: 'user', actorIp: ctx.ip, }).log('ORGANIZATION_UPDATED', 'system', { resourceId: provider.id, success: true, metadata: { action: 'auth_provider_updated', providerName: provider.name, }, }); return { status: 200, body: provider.toAdminInfo(), }; } catch (error) { console.error('[AdminAuthApi] Update provider error:', error); return { status: 500, body: { error: 'Failed to update provider' }, }; } } /** * DELETE /api/v1/admin/auth/providers/:id * Delete (or disable) an authentication provider */ public async deleteProvider(ctx: IApiContext): Promise { const authError = this.requirePlatformAdmin(ctx); if (authError) return authError; try { const { id } = ctx.params; const provider = await AuthProvider.findById(id); if (!provider) { return { status: 404, body: { error: 'Provider not found' }, }; } // For now, just disable the provider instead of deleting // This preserves audit history and linked identities provider.status = 'disabled'; await provider.save(); // Audit log await AuditService.withContext({ actorId: ctx.actor!.userId, actorType: 'user', actorIp: ctx.ip, }).log('ORGANIZATION_DELETED', 'system', { resourceId: provider.id, success: true, metadata: { action: 'auth_provider_disabled', providerName: provider.name, }, }); return { status: 200, body: { message: 'Provider disabled' }, }; } catch (error) { console.error('[AdminAuthApi] Delete provider error:', error); return { status: 500, body: { error: 'Failed to delete provider' }, }; } } /** * POST /api/v1/admin/auth/providers/:id/test * Test provider connection */ public async testProvider(ctx: IApiContext): Promise { const authError = this.requirePlatformAdmin(ctx); if (authError) return authError; try { const { id } = ctx.params; const result = await externalAuthService.testConnection(id); // Audit log await AuditService.withContext({ actorId: ctx.actor!.userId, actorType: 'user', actorIp: ctx.ip, }).log('ORGANIZATION_UPDATED', 'system', { resourceId: id, success: result.success, metadata: { action: 'auth_provider_tested', result: result.success ? 'success' : 'failure', latencyMs: result.latencyMs, error: result.error, }, }); return { status: 200, body: result, }; } catch (error) { console.error('[AdminAuthApi] Test provider error:', error); return { status: 500, body: { error: 'Failed to test provider' }, }; } } /** * GET /api/v1/admin/auth/settings * Get platform settings */ public async getSettings(ctx: IApiContext): Promise { const authError = this.requirePlatformAdmin(ctx); if (authError) return authError; try { const settings = await PlatformSettings.get(); return { status: 200, body: { id: settings.id, auth: settings.auth, updatedAt: settings.updatedAt, updatedById: settings.updatedById, }, }; } catch (error) { console.error('[AdminAuthApi] Get settings error:', error); return { status: 500, body: { error: 'Failed to get settings' }, }; } } /** * PUT /api/v1/admin/auth/settings * Update platform settings */ public async updateSettings(ctx: IApiContext): Promise { const authError = this.requirePlatformAdmin(ctx); if (authError) return authError; try { const body = await ctx.request.json(); const settings = await PlatformSettings.get(); if (body.auth) { await settings.updateAuthSettings(body.auth, ctx.actor!.userId); } // Audit log await AuditService.withContext({ actorId: ctx.actor!.userId, actorType: 'user', actorIp: ctx.ip, }).log('ORGANIZATION_UPDATED', 'system', { resourceId: 'platform-settings', success: true, metadata: { action: 'platform_settings_updated', }, }); return { status: 200, body: { id: settings.id, auth: settings.auth, updatedAt: settings.updatedAt, updatedById: settings.updatedById, }, }; } catch (error) { console.error('[AdminAuthApi] Update settings error:', error); return { status: 500, body: { error: 'Failed to update settings' }, }; } } }