import { Body, Controller, Get, HttpCode, HttpStatus, Post, Query, Req, UseGuards, } from "@nestjs/common"; import { ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags, } from "@nestjs/swagger"; import type { TCurrentUser } from "@tria-plc/api-common/modules/auth/types/current-user.type"; import { IsPublic } from "@tria-plc/api-common/modules/auth/decorators/public.decorator"; import { JwtGuard } from "../../common/jwt.guard"; import { OptionalJwtGuard } from "./optional-jwt.guard"; import { CompleteVerificationResultDto, StartVerificationDto, VerifaydaCallbackDto, VerificationStatusDto, } from "./verifayda.dto"; import { VerifaydaService } from "./verifayda.service"; /** Minimal slices of the Express req we touch (avoids a hard dependency on * `@types/express`, which isn't resolved in this package). */ interface RequestWithOptionalUser { user?: TCurrentUser; } interface RequestWithUser { user: TCurrentUser; } @ApiTags("Fayda Verification") @Controller("fayda/verification") // @Throttle({ auth: { limit: 5, ttl: 60_000 } }) export class VerifaydaController { constructor(private readonly service: VerifaydaService) {} @Post("start") @IsPublic() @HttpCode(HttpStatus.OK) @UseGuards(OptionalJwtGuard) @ApiBearerAuth("JWT-auth") @ApiOperation({ summary: "Start a VeriFayda 2.0 verification session", description: `Creates a verification session and returns the eSignet authorize URL the frontend should send the user to. - Works for **logged-in users** and **guests**. If a valid bearer token is present, the verification is tied to that user. - **VERIFY** (default): the user proves their identity and \`/complete\` returns the verified attributes (name, email, phone, dob, gender). - **LOGIN**: \`/complete\` resolves/creates the user and returns a JWT. - The returned \`authorizationUrl\` already carries the PKCE \`code_challenge\`, CSRF \`state\`, requested \`claims\`, and \`code_challenge_method=S256\`. The frontend simply navigates to it (full page or popup).`, }) @ApiOkResponse({ description: "Authorize URL the frontend should redirect the user to.", schema: { example: { authorizationUrl: "https://esignet.example.com/authorize?client_id=...&state=...&code_challenge=...", }, }, }) async start( @Body() dto: StartVerificationDto, @Req() req: RequestWithOptionalUser, ): Promise<{ authorizationUrl: string }> { const authorizationUrl = await this.service.startVerification({ purpose: dto.purpose ?? "VERIFY", platform: dto.platform ?? "WEB", userId: req.user?.id, wantsPasswordSetup: dto.wantsPasswordSetup ?? false, }); return { authorizationUrl }; } @Get("complete") @IsPublic() @ApiOperation({ summary: "Complete a verification (Fayda redirect / client callback lands here)", description: `This is the registered Fayda \`redirect_uri\`. Fayda redirects the browser here with \`?code&state\``, }) @ApiOkResponse({ type: CompleteVerificationResultDto }) async complete( @Query() dto: VerifaydaCallbackDto, ): Promise { return this.service.completeVerification(dto); } @Get("status") @UseGuards(JwtGuard) @ApiBearerAuth("JWT-auth") @ApiOperation({ summary: "Get the current user's Fayda verification status", description: "Returns whether the authenticated user has linked a verified Fayda identity to their account, when, and the name on file.", }) @ApiOkResponse({ type: VerificationStatusDto }) async status(@Req() req: RequestWithUser): Promise { return this.service.getVerificationStatus(req.user.id); } }