Files
edr-platform/apps/edr-passenger-api/src/modules/verifayda/verifayda.controller.ts
2026-07-14 10:46:27 +03:00

108 lines
3.6 KiB
TypeScript

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<CompleteVerificationResultDto> {
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<VerificationStatusDto> {
return this.service.getVerificationStatus(req.user.id);
}
}