mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Merge pull request #1344 from Tria-plc/alpha
fix: ( fayda ) block one Fayda identity from verifying multiple passe…
This commit is contained in:
@@ -57,6 +57,14 @@ export class CompleteVerificationResultDto {
|
||||
agentId?: string;
|
||||
};
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
'eSignet subject identifier for the verified individual (VERIFY flow). A PSUT — ' +
|
||||
'pairwise and stable per client_id, never the FIN. The booking flow compares it across ' +
|
||||
'passengers so one Fayda identity cannot verify more than one passenger on a booking.',
|
||||
})
|
||||
faydaSub?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Verified full name from Fayda (VERIFY flow).' })
|
||||
fullName?: string;
|
||||
|
||||
|
||||
@@ -70,11 +70,19 @@ export interface FaydaUserSummary {
|
||||
/**
|
||||
* Result of completing a verification. `verified` is always true on success.
|
||||
* LOGIN additionally returns a JWT + user; VERIFY returns the verified identity
|
||||
* attributes (name, email, phone, dob, gender) for the caller to consume.
|
||||
* attributes (name, email, phone, dob, gender, faydaSub) for the caller to consume.
|
||||
*/
|
||||
export interface CompleteVerificationResult {
|
||||
purpose: VerifaydaPurpose;
|
||||
verified: boolean;
|
||||
/**
|
||||
* eSignet subject identifier for the verified individual. This is a PSUT —
|
||||
* pairwise and stable per `client_id`, never the FIN — so it is safe to hand
|
||||
* to the browser, and it is the same value `/passengers/me` already returns.
|
||||
* The booking flow uses it to stop one Fayda identity from verifying more
|
||||
* than one passenger on the same booking.
|
||||
*/
|
||||
faydaSub?: string;
|
||||
token?: string;
|
||||
refreshToken?: string;
|
||||
requiresPassword?: boolean;
|
||||
@@ -280,6 +288,7 @@ export class VerifaydaService {
|
||||
result = {
|
||||
purpose: 'VERIFY',
|
||||
verified: true,
|
||||
faydaSub: normalized.sub,
|
||||
fullName: normalized.fullName,
|
||||
email: normalized.email,
|
||||
phoneNumber: normalized.phoneNumber,
|
||||
@@ -357,6 +366,13 @@ export class VerifaydaService {
|
||||
code_challenge_method: 'S256',
|
||||
acr_values: this.faydaConfig.acrValues,
|
||||
claims_locales: this.faydaConfig.claimsLocales,
|
||||
// Force a fresh authentication instead of silently reusing the eSignet
|
||||
// SSO session. A booking can carry several passengers, each of whom must
|
||||
// verify with their OWN Fayda; without this, the second and third
|
||||
// "Verify with Fayda" clicks round-trip in a couple of seconds and hand
|
||||
// back the first passenger's identity, which the booking flow then has to
|
||||
// reject with no way for the user to authenticate as the right person.
|
||||
prompt: 'login',
|
||||
});
|
||||
|
||||
// Every claim is marked essential so eSignet shows them locked/pre-checked
|
||||
|
||||
@@ -761,6 +761,10 @@ function PassengersForm() {
|
||||
passportExpiryDate: stored.passportExpiryDate || '',
|
||||
passportIssuingAuthority: stored.passportIssuingAuthority || '',
|
||||
faydaVerified: stored.faydaVerified || false,
|
||||
// Restore the identity that verified this passenger, so returning here from a
|
||||
// later step (e.g. Back from /booking/seats) doesn't silently reopen the slot to
|
||||
// an already-used Fayda.
|
||||
faydaSub: stored.faydaSub || undefined,
|
||||
formExpanded: true,
|
||||
};
|
||||
}
|
||||
@@ -873,13 +877,22 @@ function PassengersForm() {
|
||||
if (d?.verified) {
|
||||
const faydaSub: string | undefined = d.sub || d.faydaSub || d.fin;
|
||||
|
||||
// A single Fayda identity can't be reused across two different passengers.
|
||||
const usedByOther = faydaSub && passengers.some(
|
||||
(p, i) => i !== targetIndex && (p as any).faydaSub === faydaSub,
|
||||
);
|
||||
// A single Fayda identity can't be reused across two different passengers. Read the
|
||||
// live form rather than the `passengers` captured when this effect was created — the
|
||||
// snapshot restore repopulates the array as the form initializes.
|
||||
const currentPassengers = watch('passengers') || [];
|
||||
const conflictIndex = faydaSub
|
||||
? currentPassengers.findIndex(
|
||||
(p, i) => i !== targetIndex && (p as any)?.faydaSub === faydaSub,
|
||||
)
|
||||
: -1;
|
||||
|
||||
if (usedByOther) {
|
||||
setFaydaErrors((prev) => ({ ...prev, [targetIndex]: 'This Fayda identity is already linked to another passenger on this booking.' }));
|
||||
if (conflictIndex >= 0) {
|
||||
const conflictName = currentPassengers[conflictIndex]?.name?.trim();
|
||||
setFaydaErrors((prev) => ({
|
||||
...prev,
|
||||
[targetIndex]: `This Fayda ID has already been used to verify Passenger ${conflictIndex + 1}${conflictName ? ` (${conflictName})` : ''}. Each traveller must verify with their own Fayda.`,
|
||||
}));
|
||||
setVerificationStatus((prev) => ({ ...prev, [targetIndex]: 'error' }));
|
||||
} else {
|
||||
// Convert "1980/12/01" → "1980-12-01"
|
||||
@@ -989,6 +1002,13 @@ function PassengersForm() {
|
||||
const emailVal = pick(passengerData.email, user.email);
|
||||
if (emailVal) setValue('passengers.0.email', emailVal);
|
||||
|
||||
// A logged-in, already-verified user occupies slot 0 without going through a fresh
|
||||
// Fayda round trip, so the callback never records their sub on the form. Seed it from
|
||||
// the account here, otherwise the duplicate-identity check has nothing to compare
|
||||
// against and the account holder can re-use their own Fayda on passenger 2.
|
||||
const accountFaydaSub = pick(passengerData.faydaSub, (user as any).faydaSub);
|
||||
if (accountFaydaSub) setValue('passengers.0.faydaSub', accountFaydaSub);
|
||||
|
||||
if (mustVerifyFayda) {
|
||||
// Force the Fayda gate: leave name/DOB/gender empty and keep the form collapsed so the
|
||||
// "Verify with Fayda" screen is shown instead of an editable, pre-filled form.
|
||||
@@ -1103,6 +1123,13 @@ function PassengersForm() {
|
||||
gender: p.gender,
|
||||
nationality: p.nationality,
|
||||
nationalId: p.nationalId,
|
||||
// Carry the verified Fayda identity into the booking store so the duplicate-identity
|
||||
// check still has it if the user comes back to this page from /booking/seats. Without
|
||||
// it the restore path below rebuilds each passenger without a sub, and one Fayda could
|
||||
// then re-verify every passenger. Stripped server-side by the global ValidationPipe
|
||||
// (whitelist: true), so sending it to /passengers/save-details is a no-op there.
|
||||
faydaVerified: p.faydaVerified,
|
||||
faydaSub: p.faydaSub,
|
||||
passportNumber: p.passportNumber,
|
||||
passportCountry: p.passportCountry,
|
||||
passportIssueDate: p.passportIssueDate,
|
||||
|
||||
Reference in New Issue
Block a user