import { registerAs } from '@nestjs/config'; export interface FaydaJwk { kty: 'RSA'; use?: string; kid?: string; alg?: string; n: string; e: string; d: string; p?: string; q?: string; dp?: string; dq?: string; qi?: string; } export type FaydaPlatform = 'WEB' | 'MOBILE'; export interface FaydaConfig { enabled: boolean; clientId: string; authorizationEndpoint: string; tokenEndpoint: string; userInfoEndpoint: string; redirectUri: string; privateJwk: FaydaJwk; scope: string; acrValues: string; claimsLocales: string; sessionTtlMinutes: number; } const REQUIRED_VARS = [ 'FAYDA_CLIENT_ID', 'FAYDA_AUTHORIZATION_ENDPOINT', 'FAYDA_TOKEN_ENDPOINT', 'FAYDA_USERINFO_ENDPOINT', 'FAYDA_PRIVATE_KEY_BASE64', ] as const; function decodePrivateJwk(base64: string): FaydaJwk { let jwk: unknown; try { const json = Buffer.from(base64, 'base64').toString('utf8'); jwk = JSON.parse(json); } catch (err) { throw new Error( `FAYDA_PRIVATE_KEY_BASE64 is not valid Base64-encoded JSON: ${(err as Error).message}`, ); } if (!jwk || typeof jwk !== 'object') { throw new Error('FAYDA_PRIVATE_KEY_BASE64 must decode to a JSON object'); } const candidate = jwk as Partial; if (candidate.kty !== 'RSA') { throw new Error('FAYDA_PRIVATE_KEY_BASE64 JWK must have kty="RSA"'); } if (!candidate.n || !candidate.e || !candidate.d) { throw new Error( 'FAYDA_PRIVATE_KEY_BASE64 JWK is missing required RSA private-key fields (n, e, d)', ); } return candidate as FaydaJwk; } export default registerAs('fayda', (): FaydaConfig => { const enabled = (process.env.FAYDA_ENABLED ?? 'false').toLowerCase() === 'true'; const scope = process.env.FAYDA_SCOPE ?? 'openid profile email'; const acrValues = process.env.FAYDA_ACR_VALUES ?? 'mosip:idp:acr:generated-code'; 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 ?? ''; if (!enabled) { return { enabled: false, clientId: process.env.FAYDA_CLIENT_ID ?? '', authorizationEndpoint: process.env.FAYDA_AUTHORIZATION_ENDPOINT ?? '', tokenEndpoint: process.env.FAYDA_TOKEN_ENDPOINT ?? '', userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT ?? '', redirectUri, privateJwk: { kty: 'RSA', n: '', e: '', d: '' }, scope, acrValues, claimsLocales, sessionTtlMinutes: Number.isNaN(sessionTtl) || sessionTtl <= 0 ? 10 : sessionTtl, }; } const missing = REQUIRED_VARS.filter((name) => !process.env[name]); if (missing.length > 0) { throw new Error( `Fayda integration is enabled (FAYDA_ENABLED=true) but the following env vars are missing: ${missing.join(', ')}`, ); } if (!redirectUri) { throw new Error( 'Fayda integration is enabled but the redirect URI is missing: set FAYDA_REDIRECT_URI', ); } if (Number.isNaN(sessionTtl) || sessionTtl <= 0) { throw new Error('FAYDA_SESSION_TTL_MINUTES must be a positive integer'); } return { enabled: true, clientId: process.env.FAYDA_CLIENT_ID!, authorizationEndpoint: process.env.FAYDA_AUTHORIZATION_ENDPOINT!, tokenEndpoint: process.env.FAYDA_TOKEN_ENDPOINT!, userInfoEndpoint: process.env.FAYDA_USERINFO_ENDPOINT!, redirectUri, privateJwk: decodePrivateJwk(process.env.FAYDA_PRIVATE_KEY_BASE64!), scope, acrValues, claimsLocales, sessionTtlMinutes: sessionTtl, }; });