feat(freight-api): drop fan claim from fayda verification

esignet userinfo carries no national id number. keep sub/name/email/
phone/address, remove fanClaims config and the hard-fail gate that
would've blocked every real verification.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Nathnael
2026-07-28 13:44:06 +00:00
parent 03b8ca0ce7
commit dcda8d7d37
4 changed files with 39 additions and 10 deletions

View File

@@ -93,6 +93,9 @@ FAYDA_PRIVATE_KEY_BASE64=
FAYDA_REDIRECT_URI=http://localhost:3001/api/fayda/verification/complete FAYDA_REDIRECT_URI=http://localhost:3001/api/fayda/verification/complete
# OAuth redirect_uri for WEB clients. Defaults to FAYDA_REDIRECT_URI when unset. # OAuth redirect_uri for WEB clients. Defaults to FAYDA_REDIRECT_URI when unset.
FAYDA_WEB_REDIRECT_URI=http://localhost:3000/callback FAYDA_WEB_REDIRECT_URI=http://localhost:3000/callback
# OAuth redirect_uri for the customer portal (its own origin — must also be
# registered with eSignet). Defaults to FAYDA_WEB_REDIRECT_URI when unset.
FAYDA_PORTAL_REDIRECT_URI=http://localhost:5173/callback
CLIENT_ASSERTION_TYPE=urn:ietf:params:oauth:client-assertion-type:jwt-bearer CLIENT_ASSERTION_TYPE=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
FAYDA_SCOPE=openid profile email phone address FAYDA_SCOPE=openid profile email phone address
FAYDA_ACR_VALUES=mosip:idp:acr:generated-code FAYDA_ACR_VALUES=mosip:idp:acr:generated-code

View File

@@ -15,7 +15,7 @@ export interface FaydaJwk {
qi?: string; qi?: string;
} }
export type FaydaPlatform = 'WEB' | 'MOBILE'; export type FaydaPlatform = 'WEB' | 'MOBILE' | 'PORTAL';
export interface FaydaConfig { export interface FaydaConfig {
enabled: boolean; enabled: boolean;
@@ -25,8 +25,10 @@ export interface FaydaConfig {
userInfoEndpoint: string; userInfoEndpoint: string;
/** OAuth redirect_uri sent to eSignet for MOBILE clients. */ /** OAuth redirect_uri sent to eSignet for MOBILE clients. */
redirectUri: string; redirectUri: string;
/** OAuth redirect_uri sent to eSignet for WEB clients. Falls back to `redirectUri`. */ /** OAuth redirect_uri sent to eSignet for WEB (backoffice) clients. Falls back to `redirectUri`. */
webRedirectUri: string; webRedirectUri: string;
/** OAuth redirect_uri sent to eSignet for the customer portal. Falls back to `webRedirectUri`. */
portalRedirectUri: string;
privateJwk: FaydaJwk; privateJwk: FaydaJwk;
scope: string; scope: string;
acrValues: string; acrValues: string;
@@ -77,6 +79,7 @@ export default registerAs('fayda', (): FaydaConfig => {
const sessionTtl = Number.parseInt(process.env.FAYDA_SESSION_TTL_MINUTES ?? '10', 10); const sessionTtl = Number.parseInt(process.env.FAYDA_SESSION_TTL_MINUTES ?? '10', 10);
const redirectUri = process.env.FAYDA_REDIRECT_URI ?? ''; const redirectUri = process.env.FAYDA_REDIRECT_URI ?? '';
const webRedirectUri = process.env.FAYDA_WEB_REDIRECT_URI || redirectUri; const webRedirectUri = process.env.FAYDA_WEB_REDIRECT_URI || redirectUri;
const portalRedirectUri = process.env.FAYDA_PORTAL_REDIRECT_URI || webRedirectUri;
if (!enabled) { if (!enabled) {
return { return {
enabled: false, enabled: false,
@@ -86,6 +89,7 @@ export default registerAs('fayda', (): FaydaConfig => {
userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT ?? '', userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT ?? '',
redirectUri, redirectUri,
webRedirectUri, webRedirectUri,
portalRedirectUri,
privateJwk: { kty: 'RSA', n: '', e: '', d: '' }, privateJwk: { kty: 'RSA', n: '', e: '', d: '' },
scope, scope,
acrValues, acrValues,
@@ -117,6 +121,7 @@ export default registerAs('fayda', (): FaydaConfig => {
userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT!, userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT!,
redirectUri, redirectUri,
webRedirectUri, webRedirectUri,
portalRedirectUri,
privateJwk: decodePrivateJwk(process.env.FAYDA_PRIVATE_KEY_BASE64!), privateJwk: decodePrivateJwk(process.env.FAYDA_PRIVATE_KEY_BASE64!),
scope, scope,
acrValues, acrValues,

View File

@@ -13,14 +13,14 @@ export class StartVerificationDto {
purpose?: 'LOGIN' | 'VERIFY'; purpose?: 'LOGIN' | 'VERIFY';
@ApiPropertyOptional({ @ApiPropertyOptional({
enum: ['WEB', 'MOBILE'], enum: ['WEB', 'MOBILE', 'PORTAL'],
default: 'WEB', default: 'WEB',
description: description:
'Client platform. Selects which OAuth redirect_uri is sent to eSignet: WEB uses FAYDA_WEB_REDIRECT_URI, MOBILE uses FAYDA_REDIRECT_URI. Both land on the same /complete endpoint with identical handling.', 'Client platform. Selects which OAuth redirect_uri is sent to eSignet: WEB (backoffice) uses FAYDA_WEB_REDIRECT_URI, PORTAL uses FAYDA_PORTAL_REDIRECT_URI, MOBILE uses FAYDA_REDIRECT_URI. All land on the same /complete handling.',
}) })
@IsOptional() @IsOptional()
@IsIn(['WEB', 'MOBILE']) @IsIn(['WEB', 'MOBILE', 'PORTAL'])
platform?: 'WEB' | 'MOBILE'; platform?: 'WEB' | 'MOBILE' | 'PORTAL';
@ApiPropertyOptional({ @ApiPropertyOptional({
type: Boolean, type: Boolean,
@@ -57,6 +57,12 @@ export class CompleteVerificationResultDto {
agentId?: string; agentId?: string;
}; };
@ApiPropertyOptional({
description:
'Fayda OIDC subject — the stable key a verified identity is stored under (VERIFY flow). Pairwise pseudonymous.',
})
sub?: string;
@ApiPropertyOptional({ description: 'Verified full name from Fayda (VERIFY flow).' }) @ApiPropertyOptional({ description: 'Verified full name from Fayda (VERIFY flow).' })
fullName?: string; fullName?: string;
@@ -74,6 +80,11 @@ export class CompleteVerificationResultDto {
@ApiPropertyOptional({ description: 'Verified gender from Fayda (VERIFY flow).' }) @ApiPropertyOptional({ description: 'Verified gender from Fayda (VERIFY flow).' })
gender?: string; gender?: string;
@ApiPropertyOptional({
description: 'Verified address from Fayda, English rendering (VERIFY flow).',
})
address?: string;
@ApiPropertyOptional({ description: 'Whether the verified identity was saved to IAM. False if the IAM write failed.' }) @ApiPropertyOptional({ description: 'Whether the verified identity was saved to IAM. False if the IAM write failed.' })
userDataSaved?: boolean; userDataSaved?: boolean;

View File

@@ -56,11 +56,15 @@ export interface CompleteVerificationResult {
promptPasswordSetup?: boolean; promptPasswordSetup?: boolean;
iamUserId?: string; iamUserId?: string;
user?: FaydaUserSummary; user?: FaydaUserSummary;
/** Fayda OIDC subject — the stable key a verified identity is stored under. */
sub?: string;
fullName?: string; fullName?: string;
email?: string; email?: string;
phoneNumber?: string; phoneNumber?: string;
birthdate?: string; birthdate?: string;
gender?: string; gender?: string;
/** Verified address, English rendering (falls back to Amharic). */
address?: string;
userDataSaved?: boolean; userDataSaved?: boolean;
} }
@@ -125,11 +129,15 @@ export class VerifaydaService {
}); });
} }
/** WEB clients use `webRedirectUri`; MOBILE uses the base `redirectUri`. */ /**
* Each client lands on its own registered redirect_uri: MOBILE on the base
* one, the customer portal on its own origin, everything else (backoffice) on
* the web one. All three must be registered with eSignet.
*/
private redirectUriForPlatform(platform?: FaydaPlatform): string { private redirectUriForPlatform(platform?: FaydaPlatform): string {
return platform === 'MOBILE' if (platform === 'MOBILE') return this.faydaConfig.redirectUri;
? this.faydaConfig.redirectUri if (platform === 'PORTAL') return this.faydaConfig.portalRedirectUri;
: this.faydaConfig.webRedirectUri; return this.faydaConfig.webRedirectUri;
} }
async completeVerification( async completeVerification(
@@ -210,11 +218,13 @@ export class VerifaydaService {
result = { result = {
purpose: 'VERIFY', purpose: 'VERIFY',
verified: true, verified: true,
sub: normalized.sub,
fullName: normalized.fullName, fullName: normalized.fullName,
email: normalized.email, email: normalized.email,
phoneNumber: normalized.phoneNumber, phoneNumber: normalized.phoneNumber,
birthdate: normalized.birthdate, birthdate: normalized.birthdate,
gender: normalized.gender, gender: normalized.gender,
address: normalized.addressEn ?? normalized.addressAm,
userDataSaved, userDataSaved,
iamUserId: iamUserId ?? undefined, iamUserId: iamUserId ?? undefined,
token: sessionToken?.token, token: sessionToken?.token,