Files
registry/ts/api/handlers/admin.auth.api.ts

462 lines
13 KiB
TypeScript
Raw Normal View History

/**
* 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<IApiResponse> {
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<IApiResponse> {
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<IApiResponse> {
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<IApiResponse> {
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<IApiResponse> {
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<IApiResponse> {
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<IApiResponse> {
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<IApiResponse> {
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' },
};
}
}
}