mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 00:08:18 +00:00
feat(passenger-api): expose fayda verification endpoints and wire into app
This commit is contained in:
@@ -36,6 +36,7 @@ import { AgentsModule } from './modules/agents/agents.module';
|
|||||||
import { ReportsModule } from './modules/reports/reports.module';
|
import { ReportsModule } from './modules/reports/reports.module';
|
||||||
import { FraudModule } from './modules/fraud/fraud.module';
|
import { FraudModule } from './modules/fraud/fraud.module';
|
||||||
import { SeatClassesModule } from './modules/seat-classes/seat-classes.module';
|
import { SeatClassesModule } from './modules/seat-classes/seat-classes.module';
|
||||||
|
import { VerifaydaModule } from './modules/verifayda/verifayda.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -79,6 +80,7 @@ import { SeatClassesModule } from './modules/seat-classes/seat-classes.module';
|
|||||||
ReportsModule,
|
ReportsModule,
|
||||||
FraudModule,
|
FraudModule,
|
||||||
SeatClassesModule,
|
SeatClassesModule,
|
||||||
|
VerifaydaModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule implements NestModule {
|
export class AppModule implements NestModule {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link JwtGuard}, but never rejects the request.
|
||||||
|
*
|
||||||
|
* When a valid `Authorization: Bearer <jwt>` is present, `request.user` is
|
||||||
|
* populated from the JWT strategy (`{ userId, ... }`). When the token is
|
||||||
|
* missing or invalid, the request still proceeds with `request.user`
|
||||||
|
* undefined — the handler decides what to do.
|
||||||
|
*
|
||||||
|
* Used on `POST /fayda/verification/start`, which must work for both
|
||||||
|
* logged-in users (who can opt to save the verification to their account)
|
||||||
|
* and guests (anchored to a booking only).
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class OptionalJwtGuard extends AuthGuard('jwt') {
|
||||||
|
handleRequest<TUser = unknown>(_err: unknown, user: TUser): TUser {
|
||||||
|
return (user ?? null) as TUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
Req,
|
||||||
|
Res,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiOkResponse,
|
||||||
|
ApiOperation,
|
||||||
|
ApiTags,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
import { JwtGuard } from '../../common/jwt.guard';
|
||||||
|
import { OptionalJwtGuard } from './optional-jwt.guard';
|
||||||
|
import {
|
||||||
|
StartVerificationDto,
|
||||||
|
VerifaydaCallbackDto,
|
||||||
|
VerificationStatusDto,
|
||||||
|
} from './verifayda.dto';
|
||||||
|
import { VerifaydaService } from './verifayda.service';
|
||||||
|
|
||||||
|
/** Shape the JWT strategy puts on `request.user` (see common/jwt.strategy.ts). */
|
||||||
|
interface AuthedUser {
|
||||||
|
userId: string;
|
||||||
|
email?: string;
|
||||||
|
role?: string;
|
||||||
|
passengerId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimal slices of the Express req/res we actually touch (avoids a hard
|
||||||
|
* dependency on `@types/express`, which isn't resolved in this package). */
|
||||||
|
interface RequestWithOptionalUser {
|
||||||
|
user?: AuthedUser;
|
||||||
|
}
|
||||||
|
interface RequestWithUser {
|
||||||
|
user: AuthedUser;
|
||||||
|
}
|
||||||
|
interface RedirectableResponse {
|
||||||
|
redirect(url: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiTags('Fayda Verification')
|
||||||
|
@Controller('fayda/verification')
|
||||||
|
export class VerifaydaController {
|
||||||
|
constructor(private readonly service: VerifaydaService) {}
|
||||||
|
|
||||||
|
@Post('start')
|
||||||
|
@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; when \`saveToAccount\` is true their account is marked verified on success.
|
||||||
|
- For a **PURCHASE** flow, pass \`bookingId\` to stamp the booking's seats as Fayda-verified.
|
||||||
|
- 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 ?? 'PURCHASE',
|
||||||
|
userId: req.user?.userId,
|
||||||
|
bookingId: dto.bookingId,
|
||||||
|
saveToAccount: dto.saveToAccount,
|
||||||
|
});
|
||||||
|
return { authorizationUrl };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('callback')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'eSignet redirect callback (browser lands here)',
|
||||||
|
description: `Fayda/eSignet redirects the user's browser here with \`?code&state\` (success) or \`?error&error_description\` (failure).
|
||||||
|
|
||||||
|
This endpoint is **not** authenticated — Fayda sends no bearer token. It exchanges the code for tokens, fetches the verified claims, records the result, and **302-redirects** the browser to the configured success or failure frontend URL (failures carry a \`?reason=\` the frontend can switch on).`,
|
||||||
|
})
|
||||||
|
async callback(
|
||||||
|
@Query() query: VerifaydaCallbackDto,
|
||||||
|
@Res() res: RedirectableResponse,
|
||||||
|
): Promise<void> {
|
||||||
|
const redirectUrl = await this.service.handleCallback(query);
|
||||||
|
res.redirect(redirectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { VerifaydaController } from './verifayda.controller';
|
||||||
import { VerifaydaService } from './verifayda.service';
|
import { VerifaydaService } from './verifayda.service';
|
||||||
import { PrismaModule } from '../../common/prisma.module';
|
import { PrismaModule } from '../../common/prisma.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule],
|
imports: [PrismaModule],
|
||||||
|
controllers: [VerifaydaController],
|
||||||
providers: [VerifaydaService],
|
providers: [VerifaydaService],
|
||||||
exports: [VerifaydaService],
|
exports: [VerifaydaService],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user