@@ -218,4 +218,265 @@ router.get('/status/providers', (req: Request, res: Response) => {
218218 }
219219} )
220220
221+ /**
222+ * GET /status/oidc-debug
223+ * Get detailed OIDC discovery information for debugging
224+ * Shows the full discovery process and configuration for all providers
225+ */
226+ router . get ( '/status/oidc-debug' , async ( req : Request , res : Response ) => {
227+ try {
228+ console . log ( 'OIDC Debug: Starting detailed discovery process...' )
229+
230+ // Step 1: Get OBP API well-known endpoint info
231+ const obpApiHost = obpClientService . getOBPClientConfig ( ) . baseUri
232+ const wellKnownEndpoint = `${ obpApiHost } /obp/v5.1.0/well-known`
233+
234+ const step1 = {
235+ description : 'Discovery of OIDC providers from OBP API' ,
236+ endpoint : wellKnownEndpoint ,
237+ success : false ,
238+ response : null as any ,
239+ error : null as string | null ,
240+ providers : [ ] as any [ ]
241+ }
242+
243+ try {
244+ console . log ( `OIDC Debug: Fetching from ${ wellKnownEndpoint } ` )
245+ const wellKnownResponse = await obpClientService . get ( '/obp/v5.1.0/well-known' , null )
246+ step1 . response = wellKnownResponse
247+ step1 . success = ! ! ( wellKnownResponse && wellKnownResponse . well_known_uris )
248+ step1 . providers = wellKnownResponse . well_known_uris || [ ]
249+ console . log ( `OIDC Debug: Found ${ step1 . providers . length } providers` )
250+ } catch ( error ) {
251+ step1 . error = error instanceof Error ? error . message : 'Unknown error'
252+ console . error ( 'OIDC Debug: Error fetching OBP well-known:' , error )
253+ }
254+
255+ // Step 2: For each provider, fetch their OIDC configuration
256+ const providerDetails = [ ]
257+
258+ for ( const provider of step1 . providers ) {
259+ console . log ( `OIDC Debug: Fetching OIDC config for ${ provider . provider } ` )
260+ const detail = {
261+ providerName : provider . provider ,
262+ wellKnownUrl : provider . url ,
263+ success : false ,
264+ oidcConfiguration : null as any ,
265+ error : null as string | null ,
266+ endpoints : {
267+ authorization : null as string | null ,
268+ token : null as string | null ,
269+ userinfo : null as string | null ,
270+ jwks : null as string | null
271+ } ,
272+ issuer : null as string | null ,
273+ supportedFeatures : {
274+ pkce : false ,
275+ scopes : [ ] as string [ ] ,
276+ responseTypes : [ ] as string [ ] ,
277+ grantTypes : [ ] as string [ ]
278+ }
279+ }
280+
281+ try {
282+ const response = await fetch ( provider . url )
283+
284+ if ( ! response . ok ) {
285+ throw new Error ( `HTTP ${ response . status } : ${ response . statusText } ` )
286+ }
287+
288+ const config = await response . json ( )
289+ detail . oidcConfiguration = config
290+ detail . success = true
291+ detail . issuer = config . issuer
292+
293+ // Extract endpoints
294+ detail . endpoints . authorization = config . authorization_endpoint
295+ detail . endpoints . token = config . token_endpoint
296+ detail . endpoints . userinfo = config . userinfo_endpoint
297+ detail . endpoints . jwks = config . jwks_uri
298+
299+ // Extract supported features
300+ detail . supportedFeatures . pkce =
301+ config . code_challenge_methods_supported ?. includes ( 'S256' ) || false
302+ detail . supportedFeatures . scopes = config . scopes_supported || [ ]
303+ detail . supportedFeatures . responseTypes = config . response_types_supported || [ ]
304+ detail . supportedFeatures . grantTypes = config . grant_types_supported || [ ]
305+
306+ console . log ( `OIDC Debug: Successfully fetched config for ${ provider . provider } ` )
307+ } catch ( error ) {
308+ detail . error = error instanceof Error ? error . message : 'Unknown error'
309+ console . error ( `OIDC Debug: Error fetching config for ${ provider . provider } :` , error )
310+ }
311+
312+ providerDetails . push ( detail )
313+ }
314+
315+ // Step 3: Get current provider status from manager
316+ const currentStatus = providerManager . getAllProviderStatus ( )
317+ const availableProviders = providerManager . getAvailableProviders ( )
318+
319+ // Step 4: Get environment configuration
320+ const maskCredential = ( value : string | undefined ) : string => {
321+ if ( ! value || value . length < 6 ) {
322+ return value ? '***masked***' : 'not configured'
323+ }
324+ return `${ value . substring ( 0 , 2 ) } ...${ value . substring ( value . length - 2 ) } `
325+ }
326+
327+ const envConfig = {
328+ obpOidc : {
329+ clientId : maskCredential ( process . env . VITE_OBP_OIDC_CLIENT_ID ) ,
330+ clientSecret : process . env . VITE_OBP_OIDC_CLIENT_SECRET ? 'configured' : 'not configured' ,
331+ configured : ! ! (
332+ process . env . VITE_OBP_OIDC_CLIENT_ID && process . env . VITE_OBP_OIDC_CLIENT_SECRET
333+ )
334+ } ,
335+ keycloak : {
336+ clientId : maskCredential ( process . env . VITE_KEYCLOAK_CLIENT_ID ) ,
337+ clientSecret : process . env . VITE_KEYCLOAK_CLIENT_SECRET ? 'configured' : 'not configured' ,
338+ configured : ! ! (
339+ process . env . VITE_KEYCLOAK_CLIENT_ID && process . env . VITE_KEYCLOAK_CLIENT_SECRET
340+ )
341+ } ,
342+ google : {
343+ clientId : maskCredential ( process . env . VITE_GOOGLE_CLIENT_ID ) ,
344+ clientSecret : process . env . VITE_GOOGLE_CLIENT_SECRET ? 'configured' : 'not configured' ,
345+ configured : ! ! ( process . env . VITE_GOOGLE_CLIENT_ID && process . env . VITE_GOOGLE_CLIENT_SECRET )
346+ } ,
347+ github : {
348+ clientId : maskCredential ( process . env . VITE_GITHUB_CLIENT_ID ) ,
349+ clientSecret : process . env . VITE_GITHUB_CLIENT_SECRET ? 'configured' : 'not configured' ,
350+ configured : ! ! ( process . env . VITE_GITHUB_CLIENT_ID && process . env . VITE_GITHUB_CLIENT_SECRET )
351+ } ,
352+ custom : {
353+ providerName : process . env . VITE_CUSTOM_OIDC_PROVIDER_NAME || 'not configured' ,
354+ clientId : maskCredential ( process . env . VITE_CUSTOM_OIDC_CLIENT_ID ) ,
355+ clientSecret : process . env . VITE_CUSTOM_OIDC_CLIENT_SECRET ? 'configured' : 'not configured' ,
356+ configured : ! ! (
357+ process . env . VITE_CUSTOM_OIDC_CLIENT_ID && process . env . VITE_CUSTOM_OIDC_CLIENT_SECRET
358+ )
359+ } ,
360+ shared : {
361+ redirectUrl : process . env . VITE_OAUTH2_REDIRECT_URL || 'not configured' ,
362+ obpApiHost : process . env . VITE_OBP_API_HOST || 'not configured'
363+ }
364+ }
365+
366+ // Compile summary
367+ const summary = {
368+ timestamp : new Date ( ) . toISOString ( ) ,
369+ obpApiReachable : step1 . success ,
370+ totalProvidersDiscovered : step1 . providers . length ,
371+ successfulConfigurations : providerDetails . filter ( ( p ) => p . success ) . length ,
372+ failedConfigurations : providerDetails . filter ( ( p ) => ! p . success ) . length ,
373+ currentlyAvailable : availableProviders . length ,
374+ configuredInEnvironment : Object . values ( envConfig ) . filter (
375+ ( c ) => typeof c === 'object' && 'configured' in c && c . configured
376+ ) . length
377+ }
378+
379+ res . json ( {
380+ summary,
381+ discoveryProcess : {
382+ step1_obpApiDiscovery : step1 ,
383+ step2_providerConfigurations : providerDetails ,
384+ step3_currentStatus : currentStatus
385+ } ,
386+ environment : envConfig ,
387+ recommendations : generateRecommendations ( step1 , providerDetails , envConfig , currentStatus ) ,
388+ note : 'This debug information shows the complete OIDC discovery process for troubleshooting'
389+ } )
390+
391+ console . log ( 'OIDC Debug: Response sent successfully' )
392+ } catch ( error ) {
393+ console . error ( 'OIDC Debug: Error generating debug info:' , error )
394+ res . status ( 500 ) . json ( {
395+ error : error instanceof Error ? error . message : 'Unknown error' ,
396+ stack : error instanceof Error ? error . stack : undefined
397+ } )
398+ }
399+ } )
400+
401+ /**
402+ * Generate troubleshooting recommendations based on the discovery results
403+ */
404+ function generateRecommendations (
405+ step1 : any ,
406+ providerDetails : any [ ] ,
407+ envConfig : any ,
408+ currentStatus : any [ ]
409+ ) : string [ ] {
410+ const recommendations : string [ ] = [ ]
411+
412+ // Check if OBP API is reachable
413+ if ( ! step1 . success ) {
414+ recommendations . push (
415+ '❌ OBP API well-known endpoint is not reachable. Check that VITE_OBP_API_HOST is correct and the API server is running.'
416+ )
417+ recommendations . push ( ` Current endpoint: ${ step1 . endpoint } ` )
418+ if ( step1 . error ) {
419+ recommendations . push ( ` Error: ${ step1 . error } ` )
420+ }
421+ } else {
422+ recommendations . push ( '✅ OBP API well-known endpoint is reachable' )
423+ }
424+
425+ // Check if any providers were discovered
426+ if ( step1 . providers . length === 0 ) {
427+ recommendations . push (
428+ '⚠️ No OIDC providers found in OBP API response. The OBP API may not have any providers configured.'
429+ )
430+ } else {
431+ recommendations . push ( `✅ Found ${ step1 . providers . length } provider(s) from OBP API` )
432+ }
433+
434+ // Check each provider's configuration
435+ providerDetails . forEach ( ( provider ) => {
436+ if ( ! provider . success ) {
437+ recommendations . push (
438+ `❌ Provider '${ provider . providerName } ' OIDC configuration failed to load`
439+ )
440+ recommendations . push ( ` Well-known URL: ${ provider . wellKnownUrl } ` )
441+ if ( provider . error ) {
442+ recommendations . push ( ` Error: ${ provider . error } ` )
443+ }
444+ recommendations . push (
445+ ` Check that the provider's well-known endpoint is accessible and returning valid JSON`
446+ )
447+ } else {
448+ recommendations . push (
449+ `✅ Provider '${ provider . providerName } ' OIDC configuration loaded successfully`
450+ )
451+ }
452+
453+ // Check if provider has environment credentials
454+ const envKey = provider . providerName . replace ( '-' , '' )
455+ const providerEnv = envConfig [ envKey ] || envConfig [ provider . providerName ]
456+ if ( providerEnv && ! providerEnv . configured ) {
457+ recommendations . push (
458+ `⚠️ Provider '${ provider . providerName } ' is missing environment credentials`
459+ )
460+ const upperName = provider . providerName . toUpperCase ( ) . replace ( '-' , '_' )
461+ recommendations . push ( ` Set VITE_${ upperName } _CLIENT_ID and VITE_${ upperName } _CLIENT_SECRET` )
462+ }
463+ } )
464+
465+ // Check current provider status
466+ currentStatus . forEach ( ( status ) => {
467+ if ( ! status . available ) {
468+ recommendations . push ( `⚠️ Provider '${ status . name } ' is currently unavailable` )
469+ if ( status . error ) {
470+ recommendations . push ( ` Error: ${ status . error } ` )
471+ }
472+ }
473+ } )
474+
475+ if ( recommendations . length === 0 ) {
476+ recommendations . push ( '✅ All checks passed! OIDC configuration looks good.' )
477+ }
478+
479+ return recommendations
480+ }
481+
221482export default router
0 commit comments