This commit is contained in:
Bastian Wagner
2026-07-17 12:16:05 +02:00
parent ce2fd507fe
commit 33b6ad5b5f
3 changed files with 72 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ export enum ApplicationErrorCode {
EXTERNAL_API_REQUEST_FAILED = 'EXTERNAL_API_REQUEST_FAILED',
FILE_GENERATION_FAILED = 'FILE_GENERATION_FAILED',
OIDC_AUTHORIZATION_ERROR = 'OIDC_AUTHORIZATION_ERROR',
OIDC_INTERACTION_SESSION_NOT_FOUND = 'OIDC_INTERACTION_SESSION_NOT_FOUND',
OIDC_PROVIDER_ERROR = 'OIDC_PROVIDER_ERROR',
PASSWORD_RESET_EMAIL_SEND_FAILED = 'PASSWORD_RESET_EMAIL_SEND_FAILED',
REGISTRATION_APPROVAL_NOTIFICATION_FAILED = 'REGISTRATION_APPROVAL_NOTIFICATION_FAILED',

View File

@@ -2,6 +2,9 @@ import { Body, Controller, Get, Param, Post, Req, Res, UnauthorizedException } f
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')
@@ -9,11 +12,28 @@ export class OidcInteractionController {
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) {
const details = await this.oidc.interactionDetails(request, response);
let details: Interaction;
try {
details = await this.oidc.interactionDetails(request, response);
} catch (error) {
await this.logInteractionSessionError(error, uid, request);
response
.status(400)
.send(
this.page(
'Anmeldung abgelaufen',
'<p class="message error" role="alert">Die SSO-Anmeldung ist abgelaufen oder konnte nicht zugeordnet werden. Bitte starten Sie die Anmeldung erneut aus der Anwendung.</p>',
),
);
return;
}
if (details.uid !== uid) {
response.status(400).send(this.page('Ungueltige Anfrage', '<p>Die OIDC-Interaktion ist ungueltig.</p>'));
return;
@@ -194,4 +214,33 @@ export class OidcInteractionController {
private isInvalidCredentialsError(error: unknown): boolean {
return error instanceof UnauthorizedException && error.message === 'Ungueltige Zugangsdaten.';
}
private async logInteractionSessionError(error: unknown, uid: string, request: Request): Promise<void> {
const cookieHeader = request.headers.cookie ?? '';
await this.applicationErrorLogger.log({
error,
category: ApplicationErrorCategory.OIDC,
code: ApplicationErrorCode.OIDC_INTERACTION_SESSION_NOT_FOUND,
module: 'OidcModule',
service: OidcInteractionController.name,
operation: 'viewInteraction',
requestContext: {
...this.requestContext.get(),
method: request.method,
path: request.originalUrl || request.url,
statusCode: 400,
},
context: {
uid,
hasCookieHeader: Boolean(cookieHeader),
hasInteractionCookie: /(?:^|;\s*)_interaction=/.test(cookieHeader),
forwardedProto: request.headers['x-forwarded-proto'],
forwardedHost: request.headers['x-forwarded-host'],
host: request.headers.host,
referer: request.headers.referer,
userAgent: request.headers['user-agent'],
},
handled: true,
});
}
}

View File

@@ -285,6 +285,27 @@ export class OidcProviderService implements OnModuleInit {
provider.on('access_token.issued', (token) => {
void this.audit.record({ type: 'oidc.access_token_issued', username: token.accountId, metadata: { clientId: token.clientId } });
});
provider.on('interaction.started', (ctx, prompt) => {
const oidcContext = ctx as {
path?: string;
oidc?: {
client?: { clientId?: string };
params?: Record<string, unknown>;
entities?: { Interaction?: { uid?: string } };
};
};
void this.audit.record({
type: 'oidc.interaction_started',
metadata: {
prompt: typeof prompt === 'object' && prompt && 'name' in prompt ? String(prompt.name) : undefined,
path: oidcContext.path,
clientId: oidcContext.oidc?.client?.clientId ?? oidcContext.oidc?.params?.client_id,
redirectUri: oidcContext.oidc?.params?.redirect_uri,
scope: oidcContext.oidc?.params?.scope,
interactionUid: oidcContext.oidc?.entities?.Interaction?.uid,
},
});
});
}
private registerErrorEvents(provider: Provider): void {