import { Body, Controller, Get, Logger, Param, Post, Req, Res, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Request, Response } from 'express'; import type { Interaction } from 'oidc-provider'; import { ApplicationErrorCategory, ApplicationErrorCode } from '../application-error-log/application-error-codes'; import { ApplicationErrorLoggerService } from '../application-error-log/application-error-logger.service'; import { RequestContextService } from '../common/request-context.service'; import { OidcProviderService } from './oidc-provider.service'; @Controller('interaction') export class OidcInteractionController { private readonly logger = new Logger(OidcInteractionController.name); constructor( private readonly oidc: OidcProviderService, private readonly config: ConfigService, private readonly applicationErrorLogger: ApplicationErrorLoggerService, private readonly requestContext: RequestContextService, ) {} @Get(':uid') async view(@Param('uid') uid: string, @Req() request: Request, @Res() response: Response) { let details: Interaction; try { details = await this.oidc.interactionDetails(request, response); } catch (error) { this.logInteractionSessionError(error, uid, request).catch((logError) => { const message = logError instanceof Error ? logError.message : String(logError); this.logger.error(`OIDC interaction session logging failed: ${message}`); }); response .status(400) .send( this.page( 'Anmeldung abgelaufen', '
', ), ); return; } if (details.uid !== uid) { response.status(400).send(this.page('Ungueltige Anfrage', 'Die OIDC-Interaktion ist ungueltig.
')); return; } if (details.prompt.name === 'login') { response.send(this.page('Anmelden', this.loginForm(uid))); return; } if (details.prompt.name === 'consent') { const clientId = String(details.params.client_id ?? ''); if (clientId && (await this.oidc.isFirstPartyClient(clientId))) { await this.oidc.finishConsent(request, response, uid, { autoGranted: true }); return; } response.send(this.page('Zugriff erlauben', this.consentView(uid, details))); return; } response.status(400).send(this.page('OIDC', 'Diese Interaktion wird noch nicht unterstuetzt.
')); } @Post(':uid/login') async login( @Param('uid') uid: string, @Body() body: { username?: string; password?: string }, @Req() request: Request, @Res() response: Response, ) { const username = body.username ?? ''; try { await this.oidc.finishLogin(request, response, uid, username, body.password ?? ''); } catch (error) { if (this.isInvalidCredentialsError(error)) { response .status(401) .send(this.page('Anmelden', this.loginForm(uid, username, 'Ungueltige Zugangsdaten.'))); return; } throw error; } } @Post(':uid/confirm') async confirm(@Param('uid') uid: string, @Req() request: Request, @Res() response: Response) { await this.oidc.finishConsent(request, response, uid); } @Post(':uid/abort') async abort(@Req() request: Request, @Res() response: Response) { await this.oidc.abortInteraction(request, response); } private loginForm(uid: string, username = '', errorMessage = ''): string { const encodedUid = encodeURIComponent(uid); const error = errorMessage ? `` : ''; return ` ${error} `; } private consentView(uid: string, details: Interaction): string { const encodedUid = encodeURIComponent(uid); const clientId = String(details.params.client_id ?? ''); const clientName = String(details.params.name ?? (clientId || 'Unbekannte Anwendung')); const redirectUri = String(details.params.redirect_uri ?? ''); const scope = String(details.params.scope ?? 'openid'); return `Die Anwendung ${this.escape(clientName)} moechte auf dein Konto zugreifen.
${redirectUri ? `` : ''}