From 3308c775e60fd8d71cae73fc4f860e234c68b52b Mon Sep 17 00:00:00 2001 From: Abubeker Yasin Date: Wed, 1 Jul 2026 10:24:42 +0300 Subject: [PATCH] feat: ( fayda ) use platform-specific Fayda redirect_uri --- .../src/config/fayda.config.ts | 6 ++++++ .../src/modules/verifayda/verifayda.dto.ts | 2 +- .../verifayda/verifayda.service.spec.ts | 19 ++++++++++++++++-- .../modules/verifayda/verifayda.service.ts | 20 ++++++++++++++++--- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/apps/edr-passenger-api/src/config/fayda.config.ts b/apps/edr-passenger-api/src/config/fayda.config.ts index d8c4b1868..a25289159 100644 --- a/apps/edr-passenger-api/src/config/fayda.config.ts +++ b/apps/edr-passenger-api/src/config/fayda.config.ts @@ -23,7 +23,10 @@ export interface FaydaConfig { authorizationEndpoint: string; tokenEndpoint: string; userInfoEndpoint: string; + /** OAuth redirect_uri sent to eSignet for MOBILE clients. */ redirectUri: string; + /** OAuth redirect_uri sent to eSignet for WEB clients. Falls back to `redirectUri`. */ + webRedirectUri: string; privateJwk: FaydaJwk; scope: string; acrValues: string; @@ -73,6 +76,7 @@ export default registerAs('fayda', (): FaydaConfig => { const claimsLocales = process.env.FAYDA_CLAIMS_LOCALES ?? 'en am'; const sessionTtl = Number.parseInt(process.env.FAYDA_SESSION_TTL_MINUTES ?? '10', 10); const redirectUri = process.env.FAYDA_REDIRECT_URI ?? ''; + const webRedirectUri = process.env.FAYDA_WEB_REDIRECT_URI || redirectUri; if (!enabled) { return { enabled: false, @@ -81,6 +85,7 @@ export default registerAs('fayda', (): FaydaConfig => { tokenEndpoint: process.env.FAYDA_TOKEN_ENDPOINT ?? '', userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT ?? '', redirectUri, + webRedirectUri, privateJwk: { kty: 'RSA', n: '', e: '', d: '' }, scope, acrValues, @@ -111,6 +116,7 @@ export default registerAs('fayda', (): FaydaConfig => { tokenEndpoint: process.env.FAYDA_TOKEN_ENDPOINT!, userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT!, redirectUri, + webRedirectUri, privateJwk: decodePrivateJwk(process.env.FAYDA_PRIVATE_KEY_BASE64!), scope, acrValues, diff --git a/apps/edr-passenger-api/src/modules/verifayda/verifayda.dto.ts b/apps/edr-passenger-api/src/modules/verifayda/verifayda.dto.ts index 8e5e575e0..26108a452 100644 --- a/apps/edr-passenger-api/src/modules/verifayda/verifayda.dto.ts +++ b/apps/edr-passenger-api/src/modules/verifayda/verifayda.dto.ts @@ -16,7 +16,7 @@ export class StartVerificationDto { enum: ['WEB', 'MOBILE'], default: 'WEB', description: - 'Client platform. Decides where /callback redirects on completion: a web https URL (WEB) or a custom-scheme deep link the Flutter app intercepts (MOBILE).', + '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.', }) @IsOptional() @IsIn(['WEB', 'MOBILE']) diff --git a/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.spec.ts b/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.spec.ts index ede9aa9e8..0a821f235 100644 --- a/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.spec.ts +++ b/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.spec.ts @@ -35,6 +35,7 @@ function buildConfig(overrides?: Partial): FaydaConfig { tokenEndpoint: 'https://esignet.test/token', userInfoEndpoint: 'https://esignet.test/userinfo', redirectUri: 'http://localhost:4000/fayda/verification/complete', + webRedirectUri: 'http://localhost:5174/fayda/verification/complete', privateJwk: { kty: 'RSA', n: '', e: '', d: '' }, scope: 'openid profile email', acrValues: 'mosip:idp:acr:generated-code', @@ -101,13 +102,14 @@ describe('VerifaydaService (OIDC, client-callback)', () => { expect(parsed.origin + parsed.pathname).toBe('https://esignet.test/authorize'); expect(parsed.searchParams.get('client_id')).toBe('edr-test-client'); expect(parsed.searchParams.get('code_challenge_method')).toBe('S256'); + // Default platform is WEB → webRedirectUri. expect(parsed.searchParams.get('redirect_uri')).toBe( - 'http://localhost:4000/fayda/verification/complete', + 'http://localhost:5174/fayda/verification/complete', ); expect(parsed.searchParams.get('state')).toBe(created.state); }); - it('uses the same single redirect_uri regardless of platform (platform is only recorded)', async () => { + it('sends the MOBILE redirect_uri (base redirectUri) for MOBILE sessions', async () => { prisma.faydaVerificationSession.create.mockResolvedValue({}); const url = await service.startVerification({ @@ -122,6 +124,19 @@ describe('VerifaydaService (OIDC, client-callback)', () => { ); }); + it('sends the WEB redirect_uri (webRedirectUri) for WEB sessions', async () => { + prisma.faydaVerificationSession.create.mockResolvedValue({}); + + const url = await service.startVerification({ + purpose: 'VERIFY', + platform: 'WEB', + }); + + expect(new URL(url).searchParams.get('redirect_uri')).toBe( + 'http://localhost:5174/fayda/verification/complete', + ); + }); + it('throws ServiceUnavailable when fayda integration is disabled', async () => { const disabledService = new VerifaydaService( buildConfigService(buildConfig({ enabled: false })), diff --git a/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.ts b/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.ts index e51bf1846..99b1d1d7c 100644 --- a/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.ts +++ b/apps/edr-passenger-api/src/modules/verifayda/verifayda.service.ts @@ -161,7 +161,18 @@ export class VerifaydaService { `Fayda verification started: purpose=${input.purpose} platform=${input.platform ?? 'WEB'} userId=${input.userId ?? 'none'}`, ); - return this.buildAuthorizationUrl({ state, codeChallenge }); + return this.buildAuthorizationUrl({ + state, + codeChallenge, + redirectUri: this.redirectUriForPlatform(input.platform ?? 'WEB'), + }); + } + + /** WEB clients use `webRedirectUri`; MOBILE uses the base `redirectUri`. */ + private redirectUriForPlatform(platform?: FaydaPlatform): string { + return platform === 'MOBILE' + ? this.faydaConfig.redirectUri + : this.faydaConfig.webRedirectUri; } @@ -213,6 +224,7 @@ export class VerifaydaService { const tokens = await this.exchangeCodeForTokens( query.code, session.codeVerifier, + this.redirectUriForPlatform(session.platform as FaydaPlatform), ); const userInfo = await this.fetchUserInfo(tokens.access_token); const normalized = this.normalizeUserInfo(userInfo); @@ -307,11 +319,12 @@ export class VerifaydaService { private buildAuthorizationUrl(args: { state: string; codeChallenge: string; + redirectUri: string; }): string { const params = new URLSearchParams({ client_id: this.faydaConfig.clientId, response_type: 'code', - redirect_uri: this.faydaConfig.redirectUri, + redirect_uri: args.redirectUri, scope: this.faydaConfig.scope, state: args.state, code_challenge: args.codeChallenge, @@ -344,6 +357,7 @@ export class VerifaydaService { private async exchangeCodeForTokens( code: string, codeVerifier: string, + redirectUri: string, ): Promise { const clientAssertion = await generateClientAssertion({ clientId: this.faydaConfig.clientId, @@ -354,7 +368,7 @@ export class VerifaydaService { const body = new URLSearchParams({ grant_type: 'authorization_code', code, - redirect_uri: this.faydaConfig.redirectUri, + redirect_uri: redirectUri, client_id: this.faydaConfig.clientId, client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',