feat(auth): Add external authentication (OAuth/OIDC & LDAP) with admin management, UI, and encryption support
This commit is contained in:
461
ts/api/handlers/admin.auth.api.ts
Normal file
461
ts/api/handlers/admin.auth.api.ts
Normal file
@@ -0,0 +1,461 @@
|
||||
/**
|
||||
* 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' },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
188
ts/api/handlers/oauth.api.ts
Normal file
188
ts/api/handlers/oauth.api.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* OAuth API handlers
|
||||
* Public endpoints for OAuth/OIDC and LDAP authentication flows
|
||||
*/
|
||||
|
||||
import type { IApiContext, IApiResponse } from '../router.ts';
|
||||
import { AuthProvider, PlatformSettings } from '../../models/index.ts';
|
||||
import { externalAuthService } from '../../services/external.auth.service.ts';
|
||||
|
||||
export class OAuthApi {
|
||||
/**
|
||||
* GET /api/v1/auth/providers
|
||||
* List active authentication providers (public info only)
|
||||
*/
|
||||
public async listProviders(ctx: IApiContext): Promise<IApiResponse> {
|
||||
try {
|
||||
const settings = await PlatformSettings.get();
|
||||
const providers = await AuthProvider.getActiveProviders();
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
providers: providers.map((p) => p.toPublicInfo()),
|
||||
localAuthEnabled: settings.auth.localAuthEnabled,
|
||||
defaultProviderId: settings.auth.defaultProviderId,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[OAuthApi] List providers error:', error);
|
||||
return {
|
||||
status: 500,
|
||||
body: { error: 'Failed to list providers' },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/auth/oauth/:id/authorize
|
||||
* Initiate OAuth flow - redirects to provider
|
||||
*/
|
||||
public async authorize(ctx: IApiContext): Promise<IApiResponse> {
|
||||
try {
|
||||
const { id } = ctx.params;
|
||||
const returnUrl = ctx.url.searchParams.get('returnUrl') || undefined;
|
||||
|
||||
const { authUrl } = await externalAuthService.initiateOAuth(id, returnUrl);
|
||||
|
||||
// Return redirect response
|
||||
return {
|
||||
status: 302,
|
||||
headers: { Location: authUrl },
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[OAuthApi] Authorize error:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Authorization failed';
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: `/login?error=${encodeURIComponent(errorMessage)}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/auth/oauth/:id/callback
|
||||
* Handle OAuth callback from provider
|
||||
*/
|
||||
public async callback(ctx: IApiContext): Promise<IApiResponse> {
|
||||
try {
|
||||
const code = ctx.url.searchParams.get('code');
|
||||
const state = ctx.url.searchParams.get('state');
|
||||
const error = ctx.url.searchParams.get('error');
|
||||
const errorDescription = ctx.url.searchParams.get('error_description');
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: `/login?error=${encodeURIComponent(errorDescription || error)}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!code || !state) {
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: '/login?error=missing_parameters',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const result = await externalAuthService.handleOAuthCallback(
|
||||
{ code, state },
|
||||
{ ipAddress: ctx.ip, userAgent: ctx.userAgent }
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: `/login?error=${encodeURIComponent(result.errorCode || 'auth_failed')}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Redirect to OAuth callback page with tokens
|
||||
const params = new URLSearchParams({
|
||||
accessToken: result.accessToken!,
|
||||
refreshToken: result.refreshToken!,
|
||||
sessionId: result.sessionId!,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: `/oauth-callback?${params.toString()}`,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[OAuthApi] Callback error:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Callback failed';
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: `/login?error=${encodeURIComponent(errorMessage)}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/auth/ldap/:id/login
|
||||
* LDAP authentication with username/password
|
||||
*/
|
||||
public async ldapLogin(ctx: IApiContext): Promise<IApiResponse> {
|
||||
try {
|
||||
const { id } = ctx.params;
|
||||
const body = await ctx.request.json();
|
||||
const { username, password } = body;
|
||||
|
||||
if (!username || !password) {
|
||||
return {
|
||||
status: 400,
|
||||
body: { error: 'Username and password are required' },
|
||||
};
|
||||
}
|
||||
|
||||
const result = await externalAuthService.authenticateLdap(id, username, password, {
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
status: 401,
|
||||
body: {
|
||||
error: result.errorMessage,
|
||||
code: result.errorCode,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
user: {
|
||||
id: result.user!.id,
|
||||
email: result.user!.email,
|
||||
username: result.user!.username,
|
||||
displayName: result.user!.displayName,
|
||||
isSystemAdmin: result.user!.isSystemAdmin,
|
||||
},
|
||||
accessToken: result.accessToken,
|
||||
refreshToken: result.refreshToken,
|
||||
sessionId: result.sessionId,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[OAuthApi] LDAP login error:', error);
|
||||
return {
|
||||
status: 500,
|
||||
body: { error: 'LDAP login failed' },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import { RepositoryApi } from './handlers/repository.api.ts';
|
||||
import { PackageApi } from './handlers/package.api.ts';
|
||||
import { TokenApi } from './handlers/token.api.ts';
|
||||
import { AuditApi } from './handlers/audit.api.ts';
|
||||
import { AdminAuthApi } from './handlers/admin.auth.api.ts';
|
||||
import { OAuthApi } from './handlers/oauth.api.ts';
|
||||
|
||||
export interface IApiContext {
|
||||
request: Request;
|
||||
@@ -57,6 +59,8 @@ export class ApiRouter {
|
||||
private packageApi: PackageApi;
|
||||
private tokenApi: TokenApi;
|
||||
private auditApi: AuditApi;
|
||||
private adminAuthApi: AdminAuthApi;
|
||||
private oauthApi: OAuthApi;
|
||||
|
||||
constructor() {
|
||||
this.authService = new AuthService();
|
||||
@@ -71,6 +75,8 @@ export class ApiRouter {
|
||||
this.packageApi = new PackageApi(this.permissionService);
|
||||
this.tokenApi = new TokenApi(this.tokenService);
|
||||
this.auditApi = new AuditApi(this.permissionService);
|
||||
this.adminAuthApi = new AdminAuthApi();
|
||||
this.oauthApi = new OAuthApi();
|
||||
|
||||
this.registerRoutes();
|
||||
}
|
||||
@@ -124,6 +130,22 @@ export class ApiRouter {
|
||||
|
||||
// Audit routes
|
||||
this.addRoute('GET', '/api/v1/audit', (ctx) => this.auditApi.query(ctx));
|
||||
|
||||
// OAuth/External auth routes (public)
|
||||
this.addRoute('GET', '/api/v1/auth/providers', (ctx) => this.oauthApi.listProviders(ctx));
|
||||
this.addRoute('GET', '/api/v1/auth/oauth/:id/authorize', (ctx) => this.oauthApi.authorize(ctx));
|
||||
this.addRoute('GET', '/api/v1/auth/oauth/:id/callback', (ctx) => this.oauthApi.callback(ctx));
|
||||
this.addRoute('POST', '/api/v1/auth/ldap/:id/login', (ctx) => this.oauthApi.ldapLogin(ctx));
|
||||
|
||||
// Admin auth routes (platform admin only)
|
||||
this.addRoute('GET', '/api/v1/admin/auth/providers', (ctx) => this.adminAuthApi.listProviders(ctx));
|
||||
this.addRoute('POST', '/api/v1/admin/auth/providers', (ctx) => this.adminAuthApi.createProvider(ctx));
|
||||
this.addRoute('GET', '/api/v1/admin/auth/providers/:id', (ctx) => this.adminAuthApi.getProvider(ctx));
|
||||
this.addRoute('PUT', '/api/v1/admin/auth/providers/:id', (ctx) => this.adminAuthApi.updateProvider(ctx));
|
||||
this.addRoute('DELETE', '/api/v1/admin/auth/providers/:id', (ctx) => this.adminAuthApi.deleteProvider(ctx));
|
||||
this.addRoute('POST', '/api/v1/admin/auth/providers/:id/test', (ctx) => this.adminAuthApi.testProvider(ctx));
|
||||
this.addRoute('GET', '/api/v1/admin/auth/settings', (ctx) => this.adminAuthApi.getSettings(ctx));
|
||||
this.addRoute('PUT', '/api/v1/admin/auth/settings', (ctx) => this.adminAuthApi.updateSettings(ctx));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user