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