mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 23:35:42 +00:00
feat: ( fayda ) use platform-specific Fayda redirect_uri
This commit is contained in:
@@ -23,7 +23,10 @@ export interface FaydaConfig {
|
|||||||
authorizationEndpoint: string;
|
authorizationEndpoint: string;
|
||||||
tokenEndpoint: string;
|
tokenEndpoint: string;
|
||||||
userInfoEndpoint: string;
|
userInfoEndpoint: string;
|
||||||
|
/** 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`. */
|
||||||
|
webRedirectUri: string;
|
||||||
privateJwk: FaydaJwk;
|
privateJwk: FaydaJwk;
|
||||||
scope: string;
|
scope: string;
|
||||||
acrValues: string;
|
acrValues: string;
|
||||||
@@ -73,6 +76,7 @@ export default registerAs('fayda', (): FaydaConfig => {
|
|||||||
const claimsLocales = process.env.FAYDA_CLAIMS_LOCALES ?? 'en am';
|
const claimsLocales = process.env.FAYDA_CLAIMS_LOCALES ?? 'en am';
|
||||||
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;
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return {
|
return {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -81,6 +85,7 @@ export default registerAs('fayda', (): FaydaConfig => {
|
|||||||
tokenEndpoint: process.env.FAYDA_TOKEN_ENDPOINT ?? '',
|
tokenEndpoint: process.env.FAYDA_TOKEN_ENDPOINT ?? '',
|
||||||
userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT ?? '',
|
userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT ?? '',
|
||||||
redirectUri,
|
redirectUri,
|
||||||
|
webRedirectUri,
|
||||||
privateJwk: { kty: 'RSA', n: '', e: '', d: '' },
|
privateJwk: { kty: 'RSA', n: '', e: '', d: '' },
|
||||||
scope,
|
scope,
|
||||||
acrValues,
|
acrValues,
|
||||||
@@ -111,6 +116,7 @@ export default registerAs('fayda', (): FaydaConfig => {
|
|||||||
tokenEndpoint: process.env.FAYDA_TOKEN_ENDPOINT!,
|
tokenEndpoint: process.env.FAYDA_TOKEN_ENDPOINT!,
|
||||||
userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT!,
|
userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT!,
|
||||||
redirectUri,
|
redirectUri,
|
||||||
|
webRedirectUri,
|
||||||
privateJwk: decodePrivateJwk(process.env.FAYDA_PRIVATE_KEY_BASE64!),
|
privateJwk: decodePrivateJwk(process.env.FAYDA_PRIVATE_KEY_BASE64!),
|
||||||
scope,
|
scope,
|
||||||
acrValues,
|
acrValues,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class StartVerificationDto {
|
|||||||
enum: ['WEB', 'MOBILE'],
|
enum: ['WEB', 'MOBILE'],
|
||||||
default: 'WEB',
|
default: 'WEB',
|
||||||
description:
|
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()
|
@IsOptional()
|
||||||
@IsIn(['WEB', 'MOBILE'])
|
@IsIn(['WEB', 'MOBILE'])
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ function buildConfig(overrides?: Partial<FaydaConfig>): FaydaConfig {
|
|||||||
tokenEndpoint: 'https://esignet.test/token',
|
tokenEndpoint: 'https://esignet.test/token',
|
||||||
userInfoEndpoint: 'https://esignet.test/userinfo',
|
userInfoEndpoint: 'https://esignet.test/userinfo',
|
||||||
redirectUri: 'http://localhost:4000/fayda/verification/complete',
|
redirectUri: 'http://localhost:4000/fayda/verification/complete',
|
||||||
|
webRedirectUri: 'http://localhost:5174/fayda/verification/complete',
|
||||||
privateJwk: { kty: 'RSA', n: '', e: '', d: '' },
|
privateJwk: { kty: 'RSA', n: '', e: '', d: '' },
|
||||||
scope: 'openid profile email',
|
scope: 'openid profile email',
|
||||||
acrValues: 'mosip:idp:acr:generated-code',
|
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.origin + parsed.pathname).toBe('https://esignet.test/authorize');
|
||||||
expect(parsed.searchParams.get('client_id')).toBe('edr-test-client');
|
expect(parsed.searchParams.get('client_id')).toBe('edr-test-client');
|
||||||
expect(parsed.searchParams.get('code_challenge_method')).toBe('S256');
|
expect(parsed.searchParams.get('code_challenge_method')).toBe('S256');
|
||||||
|
// Default platform is WEB → webRedirectUri.
|
||||||
expect(parsed.searchParams.get('redirect_uri')).toBe(
|
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);
|
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({});
|
prisma.faydaVerificationSession.create.mockResolvedValue({});
|
||||||
|
|
||||||
const url = await service.startVerification({
|
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 () => {
|
it('throws ServiceUnavailable when fayda integration is disabled', async () => {
|
||||||
const disabledService = new VerifaydaService(
|
const disabledService = new VerifaydaService(
|
||||||
buildConfigService(buildConfig({ enabled: false })),
|
buildConfigService(buildConfig({ enabled: false })),
|
||||||
|
|||||||
@@ -161,7 +161,18 @@ export class VerifaydaService {
|
|||||||
`Fayda verification started: purpose=${input.purpose} platform=${input.platform ?? 'WEB'} userId=${input.userId ?? 'none'}`,
|
`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(
|
const tokens = await this.exchangeCodeForTokens(
|
||||||
query.code,
|
query.code,
|
||||||
session.codeVerifier,
|
session.codeVerifier,
|
||||||
|
this.redirectUriForPlatform(session.platform as FaydaPlatform),
|
||||||
);
|
);
|
||||||
const userInfo = await this.fetchUserInfo(tokens.access_token);
|
const userInfo = await this.fetchUserInfo(tokens.access_token);
|
||||||
const normalized = this.normalizeUserInfo(userInfo);
|
const normalized = this.normalizeUserInfo(userInfo);
|
||||||
@@ -307,11 +319,12 @@ export class VerifaydaService {
|
|||||||
private buildAuthorizationUrl(args: {
|
private buildAuthorizationUrl(args: {
|
||||||
state: string;
|
state: string;
|
||||||
codeChallenge: string;
|
codeChallenge: string;
|
||||||
|
redirectUri: string;
|
||||||
}): string {
|
}): string {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
client_id: this.faydaConfig.clientId,
|
client_id: this.faydaConfig.clientId,
|
||||||
response_type: 'code',
|
response_type: 'code',
|
||||||
redirect_uri: this.faydaConfig.redirectUri,
|
redirect_uri: args.redirectUri,
|
||||||
scope: this.faydaConfig.scope,
|
scope: this.faydaConfig.scope,
|
||||||
state: args.state,
|
state: args.state,
|
||||||
code_challenge: args.codeChallenge,
|
code_challenge: args.codeChallenge,
|
||||||
@@ -344,6 +357,7 @@ export class VerifaydaService {
|
|||||||
private async exchangeCodeForTokens(
|
private async exchangeCodeForTokens(
|
||||||
code: string,
|
code: string,
|
||||||
codeVerifier: string,
|
codeVerifier: string,
|
||||||
|
redirectUri: string,
|
||||||
): Promise<FaydaTokenResponse> {
|
): Promise<FaydaTokenResponse> {
|
||||||
const clientAssertion = await generateClientAssertion({
|
const clientAssertion = await generateClientAssertion({
|
||||||
clientId: this.faydaConfig.clientId,
|
clientId: this.faydaConfig.clientId,
|
||||||
@@ -354,7 +368,7 @@ export class VerifaydaService {
|
|||||||
const body = new URLSearchParams({
|
const body = new URLSearchParams({
|
||||||
grant_type: 'authorization_code',
|
grant_type: 'authorization_code',
|
||||||
code,
|
code,
|
||||||
redirect_uri: this.faydaConfig.redirectUri,
|
redirect_uri: redirectUri,
|
||||||
client_id: this.faydaConfig.clientId,
|
client_id: this.faydaConfig.clientId,
|
||||||
client_assertion_type:
|
client_assertion_type:
|
||||||
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
||||||
|
|||||||
Reference in New Issue
Block a user