/** * Federation Auth Controller * * API endpoints for federated OIDC authentication. * Issue #272: Rate limiting applied to prevent DoS attacks */ import { Controller, Post, Get, Delete, Body, Param, Req, UseGuards, Logger } from "@nestjs/common"; import { Throttle } from "@nestjs/throttler"; import { OIDCService } from "./oidc.service"; import { FederationAuditService } from "./audit.service"; import { AuthGuard } from "../auth/guards/auth.guard"; import type { AuthenticatedRequest } from "../common/types/user.types"; import type { FederatedIdentity, FederatedTokenValidation } from "./types/oidc.types"; import { InitiateFederatedAuthDto, LinkFederatedIdentityDto, ValidateFederatedTokenDto, } from "./dto/federated-auth.dto"; @Controller("api/v1/federation/auth") export class FederationAuthController { private readonly logger = new Logger(FederationAuthController.name); constructor( private readonly oidcService: OIDCService, private readonly auditService: FederationAuditService ) {} /** * Initiate federated authentication flow * Returns authorization URL to redirect user to * Rate limit: "medium" tier (20 req/min) - authenticated endpoint */ @Post("initiate") @UseGuards(AuthGuard) @Throttle({ medium: { limit: 20, ttl: 60000 } }) initiateAuth( @Req() req: AuthenticatedRequest, @Body() dto: InitiateFederatedAuthDto ): { authUrl: string; state: string } { if (!req.user) { throw new Error("User not authenticated"); } this.logger.log(`User ${req.user.id} initiating federated auth with ${dto.remoteInstanceId}`); const authUrl = this.oidcService.generateAuthUrl(dto.remoteInstanceId, dto.redirectUrl); // Audit log this.auditService.logFederatedAuthInitiation(req.user.id, dto.remoteInstanceId); return { authUrl, state: dto.remoteInstanceId, }; } /** * Link federated identity to local user * Rate limit: "medium" tier (20 req/min) - authenticated endpoint */ @Post("link") @UseGuards(AuthGuard) @Throttle({ medium: { limit: 20, ttl: 60000 } }) async linkIdentity( @Req() req: AuthenticatedRequest, @Body() dto: LinkFederatedIdentityDto ): Promise { if (!req.user) { throw new Error("User not authenticated"); } this.logger.log(`User ${req.user.id} linking federated identity with ${dto.remoteInstanceId}`); const identity = await this.oidcService.linkFederatedIdentity( req.user.id, dto.remoteUserId, dto.remoteInstanceId, dto.oidcSubject, dto.email, dto.metadata ); // Audit log this.auditService.logFederatedIdentityLinked(req.user.id, dto.remoteInstanceId); return identity; } /** * Get user's federated identities * Rate limit: "long" tier (200 req/hour) - read-only endpoint */ @Get("identities") @UseGuards(AuthGuard) @Throttle({ long: { limit: 200, ttl: 3600000 } }) async getIdentities(@Req() req: AuthenticatedRequest): Promise { if (!req.user) { throw new Error("User not authenticated"); } return this.oidcService.getUserFederatedIdentities(req.user.id); } /** * Revoke a federated identity * Rate limit: "medium" tier (20 req/min) - authenticated endpoint */ @Delete("identities/:instanceId") @UseGuards(AuthGuard) @Throttle({ medium: { limit: 20, ttl: 60000 } }) async revokeIdentity( @Req() req: AuthenticatedRequest, @Param("instanceId") instanceId: string ): Promise<{ success: boolean }> { if (!req.user) { throw new Error("User not authenticated"); } this.logger.log(`User ${req.user.id} revoking federated identity with ${instanceId}`); await this.oidcService.revokeFederatedIdentity(req.user.id, instanceId); // Audit log this.auditService.logFederatedIdentityRevoked(req.user.id, instanceId); return { success: true }; } /** * Validate a federated token * Public endpoint (no auth required) - used by federated instances * Rate limit: "short" tier (3 req/sec) - CRITICAL DoS protection (Issue #272) */ @Post("validate") @Throttle({ short: { limit: 3, ttl: 1000 } }) async validateToken(@Body() dto: ValidateFederatedTokenDto): Promise { this.logger.debug(`Validating federated token from ${dto.instanceId}`); return this.oidcService.validateToken(dto.token, dto.instanceId); } }