@payez/next-mvp 3.6.2 → 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -187,18 +187,22 @@ export async function getIDPClientConfig(forceRefresh: boolean = false): Promise
187
187
  }
188
188
 
189
189
  // Layer 3: Fetch from IDP
190
+ const internalIdpUrl = process.env.INTERNAL_IDP_URL;
190
191
  const idpUrl = process.env.IDP_URL;
191
192
  const clientIdStr = process.env.CLIENT_ID || process.env.NEXT_PUBLIC_CLIENT_ID;
192
193
 
193
- if (!idpUrl) {
194
- throw new Error('[IDP_CONFIG] FATAL: IDP_URL must be set');
195
- }
196
194
  if (!clientIdStr) {
197
195
  throw new Error('[IDP_CONFIG] FATAL: CLIENT_ID or NEXT_PUBLIC_CLIENT_ID must be set');
198
196
  }
197
+ if (!internalIdpUrl && !idpUrl) {
198
+ throw new Error('[IDP_CONFIG] FATAL: INTERNAL_IDP_URL or IDP_URL must be set');
199
+ }
199
200
 
200
201
  // Start fetch and store promise so concurrent callers wait for same result
201
- pendingFetch = fetchConfigFromIDP(idpUrl, clientIdStr)
202
+ const fetcher = internalIdpUrl
203
+ ? fetchConfigFromInternalIDP(internalIdpUrl, clientIdStr)
204
+ : fetchConfigFromIDP(idpUrl!, clientIdStr);
205
+ pendingFetch = fetcher
202
206
  .then(async config => {
203
207
  // Cache with TTL from response (default 5 minutes)
204
208
  cachedConfig = config;
@@ -250,6 +254,84 @@ export function getEnabledProviders(config: IDPClientConfig): OAuthProviderConfi
250
254
  // Internal Functions
251
255
  // ============================================================================
252
256
 
257
+ async function fetchConfigFromInternalIDP(internalIdpUrl: string, clientIdStr: string): Promise<IDPClientConfig> {
258
+ const containersKey = process.env.CONTAINERS_KEY;
259
+ if (!containersKey) {
260
+ throw new Error('[IDP_CONFIG] FATAL: CONTAINERS_KEY is required when using INTERNAL_IDP_URL');
261
+ }
262
+
263
+ const url = `${internalIdpUrl.replace(/\/$/, '')}/InternalClientConfig/${encodeURIComponent(clientIdStr)}`;
264
+ console.log(`[IDP_CONFIG] Fetching config from internal IDP: ${url}`);
265
+
266
+ const resp = await fetch(url, {
267
+ method: 'GET',
268
+ headers: {
269
+ 'Accept': 'application/json',
270
+ 'Authorization': `Secret ${containersKey}`,
271
+ },
272
+ cache: 'no-store'
273
+ } as RequestInit);
274
+
275
+ if (!resp.ok) {
276
+ const txt = await resp.text().catch(() => 'Unknown error');
277
+ throw new Error(`[IDP_CONFIG] FATAL: Internal IDP returned ${resp.status} - ${txt}`);
278
+ }
279
+
280
+ const body: any = await resp.json().catch(() => null);
281
+ if (!body) {
282
+ throw new Error('[IDP_CONFIG] FATAL: Internal IDP returned empty or invalid JSON');
283
+ }
284
+
285
+ const configData = body?.data ?? body;
286
+
287
+ const rawClientId = configData.clientId ?? configData.client_id;
288
+ if (rawClientId === undefined || rawClientId === null) {
289
+ throw new Error(`[IDP_CONFIG] FATAL: Internal IDP response missing clientId. Got: ${JSON.stringify(Object.keys(configData))}`);
290
+ }
291
+
292
+ const config: IDPClientConfig = {
293
+ clientId: String(rawClientId),
294
+ clientSlug: configData.clientSlug ?? configData.client_slug ?? configData.slug ?? '',
295
+ nextAuthSecret: configData.nextAuthSecret ?? configData.next_auth_secret ?? '',
296
+ configCacheTtlSeconds: configData.configCacheTtlSeconds ?? configData.config_cache_ttl_seconds ?? 300,
297
+ oauthProviders: (configData.oauthProviders ?? configData.oauth_providers ?? []).map((p: any) => ({
298
+ provider: p.provider ?? '',
299
+ enabled: p.enabled ?? false,
300
+ clientId: p.clientId ?? p.client_id ?? '',
301
+ clientSecret: p.clientSecret ?? p.client_secret ?? '',
302
+ scopes: p.scopes,
303
+ additionalParams: p.additionalParams ?? p.additional_params
304
+ })),
305
+ authSettings: {
306
+ require2FA: configData.authSettings?.require2FA ?? configData.auth_settings?.require_2fa ?? true,
307
+ allowed2FAMethods: configData.authSettings?.allowed2FAMethods ?? configData.auth_settings?.allowed_2fa_methods ?? ['email', 'sms'],
308
+ mfaGracePeriodHours: configData.authSettings?.mfaGracePeriodHours ?? configData.auth_settings?.mfa_grace_period_hours ?? 24,
309
+ mfaRememberDeviceDays: configData.authSettings?.mfaRememberDeviceDays ?? configData.auth_settings?.mfa_remember_device_days ?? 30,
310
+ sessionTimeoutMinutes: configData.authSettings?.sessionTimeoutMinutes ?? configData.auth_settings?.session_timeout_minutes ?? 60,
311
+ idleTimeoutMinutes: configData.authSettings?.idleTimeoutMinutes ?? configData.auth_settings?.idle_timeout_minutes ?? 15,
312
+ allowRememberMe: configData.authSettings?.allowRememberMe ?? configData.auth_settings?.allow_remember_me ?? true,
313
+ rememberMeDays: configData.authSettings?.rememberMeDays ?? configData.auth_settings?.remember_me_days ?? 30,
314
+ lockoutThreshold: configData.authSettings?.lockoutThreshold ?? configData.auth_settings?.lockout_threshold ?? 5,
315
+ lockoutDurationMinutes: configData.authSettings?.lockoutDurationMinutes ?? configData.auth_settings?.lockout_duration_minutes ?? 15
316
+ },
317
+ branding: {
318
+ theme: configData.branding?.theme,
319
+ primaryColor: configData.branding?.primaryColor ?? configData.branding?.primary_color,
320
+ secondaryColor: configData.branding?.secondaryColor ?? configData.branding?.secondary_color,
321
+ logoUrl: configData.branding?.logoUrl ?? configData.branding?.logo_url
322
+ },
323
+ baseClientUrl: configData.baseClientUrl ?? configData.base_client_url ?? configData.BaseClientUrl
324
+ };
325
+
326
+ if (!config.nextAuthSecret) {
327
+ throw new Error('[IDP_CONFIG] FATAL: Internal IDP did not return nextAuthSecret');
328
+ }
329
+
330
+ console.log(`[IDP_CONFIG] Internal IDP config loaded for ${clientIdStr}`);
331
+ consecutiveFailures = 0;
332
+ return config;
333
+ }
334
+
253
335
  async function fetchConfigFromIDP(idpUrl: string, clientIdStr: string): Promise<IDPClientConfig> {
254
336
  // =========================================================================
255
337
  // Circuit Breaker Check
@@ -291,7 +373,7 @@ async function fetchConfigFromIDP(idpUrl: string, clientIdStr: string): Promise<
291
373
  issuer: clientIdStr,
292
374
  subject: clientIdStr,
293
375
  audience: 'urn:payez:externalauth:clientconfig',
294
- expires_in: 60
376
+ expires_in: 60,
295
377
  };
296
378
 
297
379
  const signingResp = await fetch(signingUrl, {
@@ -35,13 +35,12 @@ export async function resolveNextAuthSecret(): Promise<string> {
35
35
  // Step 1: Request IDP to sign a client assertion (IDP has the keys, not us)
36
36
 
37
37
  const signingUrl = new URL(`${base.replace(/\/$/, '')}/api/ExternalAuth/sign-client-assertion`);
38
- // Client ID passed via X-Client-Id header, not query string
39
38
 
40
39
  const signingPayload = {
41
40
  issuer: clientIdStr,
42
41
  subject: clientIdStr,
43
42
  audience: 'urn:payez:externalauth:nextauthsecret',
44
- expires_in: 60
43
+ expires_in: 60,
45
44
  };
46
45
 
47
46
  const signingResp = await fetch(signingUrl.toString(), {