mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
fix: ( fayda ) block one Fayda identity from verifying multiple passengers
This commit is contained in:
@@ -57,6 +57,14 @@ export class CompleteVerificationResultDto {
|
|||||||
agentId?: string;
|
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).' })
|
@ApiPropertyOptional({ description: 'Verified full name from Fayda (VERIFY flow).' })
|
||||||
fullName?: string;
|
fullName?: string;
|
||||||
|
|
||||||
|
|||||||
@@ -70,11 +70,19 @@ export interface FaydaUserSummary {
|
|||||||
/**
|
/**
|
||||||
* Result of completing a verification. `verified` is always true on success.
|
* Result of completing a verification. `verified` is always true on success.
|
||||||
* LOGIN additionally returns a JWT + user; VERIFY returns the verified identity
|
* 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 {
|
export interface CompleteVerificationResult {
|
||||||
purpose: VerifaydaPurpose;
|
purpose: VerifaydaPurpose;
|
||||||
verified: boolean;
|
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;
|
token?: string;
|
||||||
refreshToken?: string;
|
refreshToken?: string;
|
||||||
requiresPassword?: boolean;
|
requiresPassword?: boolean;
|
||||||
@@ -280,6 +288,7 @@ export class VerifaydaService {
|
|||||||
result = {
|
result = {
|
||||||
purpose: 'VERIFY',
|
purpose: 'VERIFY',
|
||||||
verified: true,
|
verified: true,
|
||||||
|
faydaSub: normalized.sub,
|
||||||
fullName: normalized.fullName,
|
fullName: normalized.fullName,
|
||||||
email: normalized.email,
|
email: normalized.email,
|
||||||
phoneNumber: normalized.phoneNumber,
|
phoneNumber: normalized.phoneNumber,
|
||||||
@@ -357,6 +366,13 @@ export class VerifaydaService {
|
|||||||
code_challenge_method: 'S256',
|
code_challenge_method: 'S256',
|
||||||
acr_values: this.faydaConfig.acrValues,
|
acr_values: this.faydaConfig.acrValues,
|
||||||
claims_locales: this.faydaConfig.claimsLocales,
|
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
|
// Every claim is marked essential so eSignet shows them locked/pre-checked
|
||||||
|
|||||||
@@ -740,6 +740,10 @@ function PassengersForm() {
|
|||||||
passportExpiryDate: stored.passportExpiryDate || '',
|
passportExpiryDate: stored.passportExpiryDate || '',
|
||||||
passportIssuingAuthority: stored.passportIssuingAuthority || '',
|
passportIssuingAuthority: stored.passportIssuingAuthority || '',
|
||||||
faydaVerified: stored.faydaVerified || false,
|
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,
|
formExpanded: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -852,13 +856,22 @@ function PassengersForm() {
|
|||||||
if (d?.verified) {
|
if (d?.verified) {
|
||||||
const faydaSub: string | undefined = d.sub || d.faydaSub || d.fin;
|
const faydaSub: string | undefined = d.sub || d.faydaSub || d.fin;
|
||||||
|
|
||||||
// A single Fayda identity can't be reused across two different passengers.
|
// A single Fayda identity can't be reused across two different passengers. Read the
|
||||||
const usedByOther = faydaSub && passengers.some(
|
// live form rather than the `passengers` captured when this effect was created — the
|
||||||
(p, i) => i !== targetIndex && (p as any).faydaSub === faydaSub,
|
// 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) {
|
if (conflictIndex >= 0) {
|
||||||
setFaydaErrors((prev) => ({ ...prev, [targetIndex]: 'This Fayda identity is already linked to another passenger on this booking.' }));
|
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' }));
|
setVerificationStatus((prev) => ({ ...prev, [targetIndex]: 'error' }));
|
||||||
} else {
|
} else {
|
||||||
// Convert "1980/12/01" → "1980-12-01"
|
// Convert "1980/12/01" → "1980-12-01"
|
||||||
@@ -963,6 +976,13 @@ function PassengersForm() {
|
|||||||
const emailVal = pick(passengerData.email, user.email);
|
const emailVal = pick(passengerData.email, user.email);
|
||||||
if (emailVal) setValue('passengers.0.email', emailVal);
|
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) {
|
if (mustVerifyFayda) {
|
||||||
// Force the Fayda gate: leave name/DOB/gender empty and keep the form collapsed so the
|
// 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.
|
// "Verify with Fayda" screen is shown instead of an editable, pre-filled form.
|
||||||
@@ -1077,6 +1097,13 @@ function PassengersForm() {
|
|||||||
gender: p.gender,
|
gender: p.gender,
|
||||||
nationality: p.nationality,
|
nationality: p.nationality,
|
||||||
nationalId: p.nationalId,
|
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,
|
passportNumber: p.passportNumber,
|
||||||
passportCountry: p.passportCountry,
|
passportCountry: p.passportCountry,
|
||||||
passportIssueDate: p.passportIssueDate,
|
passportIssueDate: p.passportIssueDate,
|
||||||
|
|||||||
Reference in New Issue
Block a user