|
5 | 5 | * of the MIT license. See the LICENSE file for details. |
6 | 6 | */ |
7 | 7 |
|
8 | | -// Re-export error handling from sdk-oidc (uses RTK Query types) |
9 | 8 | export { createWellknownError } from '@forgerock/sdk-oidc'; |
10 | | - |
11 | | -// Re-export URL validation from sdk-utilities (pure utility) |
12 | 9 | export { isValidWellknownUrl } from '@forgerock/sdk-utilities'; |
13 | 10 |
|
14 | 11 | import type { AsyncJourneyClientConfig, JourneyConfigInput } from './config.types.js'; |
15 | 12 |
|
| 13 | +const REALM_PATTERNS = [ |
| 14 | + /\/oauth2\/realms\/root\/realms\/(.+)$/, // Legacy subrealm: /oauth2/realms/root/realms/{realm} |
| 15 | + /\/oauth2\/realms\/(root)$/, // Legacy root: /oauth2/realms/root |
| 16 | + /\/oauth2\/([^/]+)$/, // Simplified: /oauth2/{realm} |
| 17 | +] as const; |
| 18 | + |
16 | 19 | /** |
17 | | - * Attempts to infer the realm path from a ForgeRock AM issuer URL. |
18 | | - * |
19 | | - * AM issuer URLs follow one of two patterns: |
20 | | - * - Simplified: `https://{host}/am/oauth2/{realm}` (e.g., `/am/oauth2/alpha`) |
21 | | - * - Legacy: `https://{host}/am/oauth2/realms/root/realms/{realm}` |
22 | | - * |
23 | | - * This function extracts the realm from either format. |
24 | | - * Returns undefined for non-AM issuers (e.g., PingOne, generic OIDC). |
25 | | - * |
26 | | - * @param issuer - The issuer URL from the well-known response |
27 | | - * @returns The inferred realm path, or undefined if it cannot be determined |
28 | | - * |
29 | | - * @example |
30 | | - * ```typescript |
31 | | - * // Simplified format (common in ForgeBlocks) |
32 | | - * inferRealmFromIssuer('https://openam-sdks.forgeblocks.com/am/oauth2/alpha') |
33 | | - * // Returns: 'alpha' |
34 | | - * |
35 | | - * // Legacy format with explicit realm path |
36 | | - * inferRealmFromIssuer('https://am.example.com/am/oauth2/realms/root/realms/alpha') |
37 | | - * // Returns: 'alpha' |
38 | | - * |
39 | | - * // Nested subrealm (legacy format) |
40 | | - * inferRealmFromIssuer('https://am.example.com/am/oauth2/realms/root/realms/customers/realms/premium') |
41 | | - * // Returns: 'customers/realms/premium' |
42 | | - * |
43 | | - * // Non-AM issuer (e.g., PingOne) |
44 | | - * inferRealmFromIssuer('https://auth.pingone.com/env-id/as') |
45 | | - * // Returns: undefined |
46 | | - * ``` |
| 20 | + * Infers the realm path from a ForgeRock AM issuer URL. |
| 21 | + * Returns undefined for non-AM issuers. |
47 | 22 | */ |
48 | 23 | export function inferRealmFromIssuer(issuer: string): string | undefined { |
49 | 24 | try { |
50 | | - const url = new URL(issuer); |
51 | | - const pathname = url.pathname; |
52 | | - |
53 | | - // Pattern 1: Legacy subrealm - /oauth2/realms/root/realms/{subrealm} |
54 | | - const legacySubRealmMatch = pathname.match(/\/oauth2\/realms\/root\/realms\/(.+)$/); |
55 | | - if (legacySubRealmMatch) { |
56 | | - return legacySubRealmMatch[1]; |
57 | | - } |
58 | | - |
59 | | - // Pattern 2: Legacy root realm - /oauth2/realms/root |
60 | | - const legacyRootMatch = pathname.match(/\/oauth2\/realms\/(root)$/); |
61 | | - if (legacyRootMatch) { |
62 | | - return legacyRootMatch[1]; |
63 | | - } |
64 | | - |
65 | | - // Pattern 3: Simplified format - /oauth2/{realm} (e.g., /am/oauth2/alpha) |
66 | | - const simplifiedMatch = pathname.match(/\/oauth2\/([^/]+)$/); |
67 | | - if (simplifiedMatch) { |
68 | | - return simplifiedMatch[1]; |
| 25 | + const pathname = new URL(issuer).pathname; |
| 26 | + for (const pattern of REALM_PATTERNS) { |
| 27 | + const match = pathname.match(pattern); |
| 28 | + if (match) return match[1]; |
69 | 29 | } |
70 | | - |
71 | 30 | return undefined; |
72 | 31 | } catch { |
73 | 32 | return undefined; |
74 | 33 | } |
75 | 34 | } |
76 | 35 |
|
77 | 36 | /** |
78 | | - * Type guard to determine if the configuration includes well-known endpoint discovery. |
79 | | - * |
80 | | - * @param config - The journey client configuration (union of sync and async configs) |
81 | | - * @returns True if the config has a wellknown property in serverConfig |
82 | | - * |
83 | | - * @example |
84 | | - * ```typescript |
85 | | - * const config: JourneyConfigInput = { |
86 | | - * serverConfig: { |
87 | | - * baseUrl: 'https://am.example.com/am/', |
88 | | - * wellknown: 'https://am.example.com/am/oauth2/realms/root/realms/alpha/.well-known/openid-configuration' |
89 | | - * } |
90 | | - * }; |
91 | | - * |
92 | | - * if (hasWellknownConfig(config)) { |
93 | | - * // TypeScript now knows config is AsyncJourneyClientConfig |
94 | | - * const wellknownUrl = config.serverConfig.wellknown; |
95 | | - * } |
96 | | - * ``` |
| 37 | + * Type guard to check if config uses well-known endpoint discovery. |
97 | 38 | */ |
98 | 39 | export function hasWellknownConfig(config: JourneyConfigInput): config is AsyncJourneyClientConfig { |
99 | 40 | return ( |
100 | 41 | 'serverConfig' in config && |
101 | 42 | typeof config.serverConfig === 'object' && |
102 | 43 | config.serverConfig !== null && |
| 44 | + 'baseUrl' in config.serverConfig && |
| 45 | + typeof config.serverConfig.baseUrl === 'string' && |
| 46 | + config.serverConfig.baseUrl.length > 0 && |
103 | 47 | 'wellknown' in config.serverConfig && |
104 | 48 | typeof config.serverConfig.wellknown === 'string' && |
105 | 49 | config.serverConfig.wellknown.length > 0 |
|
0 commit comments