mirror of
https://github.com/OpenBankProject/API-Explorer-II.git
synced 2026-02-06 10:47:04 +00:00
added /debug/oidc
This commit is contained in:
parent
99c4d4d22c
commit
10e14a2738
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -38,6 +38,8 @@ declare module 'vue' {
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
GlossarySearchNav: typeof import('./src/components/GlossarySearchNav.vue')['default']
|
||||
|
||||
@ -218,4 +218,265 @@ router.get('/status/providers', (req: Request, res: Response) => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* GET /status/oidc-debug
|
||||
* Get detailed OIDC discovery information for debugging
|
||||
* Shows the full discovery process and configuration for all providers
|
||||
*/
|
||||
router.get('/status/oidc-debug', async (req: Request, res: Response) => {
|
||||
try {
|
||||
console.log('OIDC Debug: Starting detailed discovery process...')
|
||||
|
||||
// Step 1: Get OBP API well-known endpoint info
|
||||
const obpApiHost = obpClientService.getOBPClientConfig().baseUri
|
||||
const wellKnownEndpoint = `${obpApiHost}/obp/v5.1.0/well-known`
|
||||
|
||||
const step1 = {
|
||||
description: 'Discovery of OIDC providers from OBP API',
|
||||
endpoint: wellKnownEndpoint,
|
||||
success: false,
|
||||
response: null as any,
|
||||
error: null as string | null,
|
||||
providers: [] as any[]
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`OIDC Debug: Fetching from ${wellKnownEndpoint}`)
|
||||
const wellKnownResponse = await obpClientService.get('/obp/v5.1.0/well-known', null)
|
||||
step1.response = wellKnownResponse
|
||||
step1.success = !!(wellKnownResponse && wellKnownResponse.well_known_uris)
|
||||
step1.providers = wellKnownResponse.well_known_uris || []
|
||||
console.log(`OIDC Debug: Found ${step1.providers.length} providers`)
|
||||
} catch (error) {
|
||||
step1.error = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error('OIDC Debug: Error fetching OBP well-known:', error)
|
||||
}
|
||||
|
||||
// Step 2: For each provider, fetch their OIDC configuration
|
||||
const providerDetails = []
|
||||
|
||||
for (const provider of step1.providers) {
|
||||
console.log(`OIDC Debug: Fetching OIDC config for ${provider.provider}`)
|
||||
const detail = {
|
||||
providerName: provider.provider,
|
||||
wellKnownUrl: provider.url,
|
||||
success: false,
|
||||
oidcConfiguration: null as any,
|
||||
error: null as string | null,
|
||||
endpoints: {
|
||||
authorization: null as string | null,
|
||||
token: null as string | null,
|
||||
userinfo: null as string | null,
|
||||
jwks: null as string | null
|
||||
},
|
||||
issuer: null as string | null,
|
||||
supportedFeatures: {
|
||||
pkce: false,
|
||||
scopes: [] as string[],
|
||||
responseTypes: [] as string[],
|
||||
grantTypes: [] as string[]
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(provider.url)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const config = await response.json()
|
||||
detail.oidcConfiguration = config
|
||||
detail.success = true
|
||||
detail.issuer = config.issuer
|
||||
|
||||
// Extract endpoints
|
||||
detail.endpoints.authorization = config.authorization_endpoint
|
||||
detail.endpoints.token = config.token_endpoint
|
||||
detail.endpoints.userinfo = config.userinfo_endpoint
|
||||
detail.endpoints.jwks = config.jwks_uri
|
||||
|
||||
// Extract supported features
|
||||
detail.supportedFeatures.pkce =
|
||||
config.code_challenge_methods_supported?.includes('S256') || false
|
||||
detail.supportedFeatures.scopes = config.scopes_supported || []
|
||||
detail.supportedFeatures.responseTypes = config.response_types_supported || []
|
||||
detail.supportedFeatures.grantTypes = config.grant_types_supported || []
|
||||
|
||||
console.log(`OIDC Debug: Successfully fetched config for ${provider.provider}`)
|
||||
} catch (error) {
|
||||
detail.error = error instanceof Error ? error.message : 'Unknown error'
|
||||
console.error(`OIDC Debug: Error fetching config for ${provider.provider}:`, error)
|
||||
}
|
||||
|
||||
providerDetails.push(detail)
|
||||
}
|
||||
|
||||
// Step 3: Get current provider status from manager
|
||||
const currentStatus = providerManager.getAllProviderStatus()
|
||||
const availableProviders = providerManager.getAvailableProviders()
|
||||
|
||||
// Step 4: Get environment configuration
|
||||
const maskCredential = (value: string | undefined): string => {
|
||||
if (!value || value.length < 6) {
|
||||
return value ? '***masked***' : 'not configured'
|
||||
}
|
||||
return `${value.substring(0, 2)}...${value.substring(value.length - 2)}`
|
||||
}
|
||||
|
||||
const envConfig = {
|
||||
obpOidc: {
|
||||
clientId: maskCredential(process.env.VITE_OBP_OIDC_CLIENT_ID),
|
||||
clientSecret: process.env.VITE_OBP_OIDC_CLIENT_SECRET ? 'configured' : 'not configured',
|
||||
configured: !!(
|
||||
process.env.VITE_OBP_OIDC_CLIENT_ID && process.env.VITE_OBP_OIDC_CLIENT_SECRET
|
||||
)
|
||||
},
|
||||
keycloak: {
|
||||
clientId: maskCredential(process.env.VITE_KEYCLOAK_CLIENT_ID),
|
||||
clientSecret: process.env.VITE_KEYCLOAK_CLIENT_SECRET ? 'configured' : 'not configured',
|
||||
configured: !!(
|
||||
process.env.VITE_KEYCLOAK_CLIENT_ID && process.env.VITE_KEYCLOAK_CLIENT_SECRET
|
||||
)
|
||||
},
|
||||
google: {
|
||||
clientId: maskCredential(process.env.VITE_GOOGLE_CLIENT_ID),
|
||||
clientSecret: process.env.VITE_GOOGLE_CLIENT_SECRET ? 'configured' : 'not configured',
|
||||
configured: !!(process.env.VITE_GOOGLE_CLIENT_ID && process.env.VITE_GOOGLE_CLIENT_SECRET)
|
||||
},
|
||||
github: {
|
||||
clientId: maskCredential(process.env.VITE_GITHUB_CLIENT_ID),
|
||||
clientSecret: process.env.VITE_GITHUB_CLIENT_SECRET ? 'configured' : 'not configured',
|
||||
configured: !!(process.env.VITE_GITHUB_CLIENT_ID && process.env.VITE_GITHUB_CLIENT_SECRET)
|
||||
},
|
||||
custom: {
|
||||
providerName: process.env.VITE_CUSTOM_OIDC_PROVIDER_NAME || 'not configured',
|
||||
clientId: maskCredential(process.env.VITE_CUSTOM_OIDC_CLIENT_ID),
|
||||
clientSecret: process.env.VITE_CUSTOM_OIDC_CLIENT_SECRET ? 'configured' : 'not configured',
|
||||
configured: !!(
|
||||
process.env.VITE_CUSTOM_OIDC_CLIENT_ID && process.env.VITE_CUSTOM_OIDC_CLIENT_SECRET
|
||||
)
|
||||
},
|
||||
shared: {
|
||||
redirectUrl: process.env.VITE_OAUTH2_REDIRECT_URL || 'not configured',
|
||||
obpApiHost: process.env.VITE_OBP_API_HOST || 'not configured'
|
||||
}
|
||||
}
|
||||
|
||||
// Compile summary
|
||||
const summary = {
|
||||
timestamp: new Date().toISOString(),
|
||||
obpApiReachable: step1.success,
|
||||
totalProvidersDiscovered: step1.providers.length,
|
||||
successfulConfigurations: providerDetails.filter((p) => p.success).length,
|
||||
failedConfigurations: providerDetails.filter((p) => !p.success).length,
|
||||
currentlyAvailable: availableProviders.length,
|
||||
configuredInEnvironment: Object.values(envConfig).filter(
|
||||
(c) => typeof c === 'object' && 'configured' in c && c.configured
|
||||
).length
|
||||
}
|
||||
|
||||
res.json({
|
||||
summary,
|
||||
discoveryProcess: {
|
||||
step1_obpApiDiscovery: step1,
|
||||
step2_providerConfigurations: providerDetails,
|
||||
step3_currentStatus: currentStatus
|
||||
},
|
||||
environment: envConfig,
|
||||
recommendations: generateRecommendations(step1, providerDetails, envConfig, currentStatus),
|
||||
note: 'This debug information shows the complete OIDC discovery process for troubleshooting'
|
||||
})
|
||||
|
||||
console.log('OIDC Debug: Response sent successfully')
|
||||
} catch (error) {
|
||||
console.error('OIDC Debug: Error generating debug info:', error)
|
||||
res.status(500).json({
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
stack: error instanceof Error ? error.stack : undefined
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Generate troubleshooting recommendations based on the discovery results
|
||||
*/
|
||||
function generateRecommendations(
|
||||
step1: any,
|
||||
providerDetails: any[],
|
||||
envConfig: any,
|
||||
currentStatus: any[]
|
||||
): string[] {
|
||||
const recommendations: string[] = []
|
||||
|
||||
// Check if OBP API is reachable
|
||||
if (!step1.success) {
|
||||
recommendations.push(
|
||||
'❌ OBP API well-known endpoint is not reachable. Check that VITE_OBP_API_HOST is correct and the API server is running.'
|
||||
)
|
||||
recommendations.push(` Current endpoint: ${step1.endpoint}`)
|
||||
if (step1.error) {
|
||||
recommendations.push(` Error: ${step1.error}`)
|
||||
}
|
||||
} else {
|
||||
recommendations.push('✅ OBP API well-known endpoint is reachable')
|
||||
}
|
||||
|
||||
// Check if any providers were discovered
|
||||
if (step1.providers.length === 0) {
|
||||
recommendations.push(
|
||||
'⚠️ No OIDC providers found in OBP API response. The OBP API may not have any providers configured.'
|
||||
)
|
||||
} else {
|
||||
recommendations.push(`✅ Found ${step1.providers.length} provider(s) from OBP API`)
|
||||
}
|
||||
|
||||
// Check each provider's configuration
|
||||
providerDetails.forEach((provider) => {
|
||||
if (!provider.success) {
|
||||
recommendations.push(
|
||||
`❌ Provider '${provider.providerName}' OIDC configuration failed to load`
|
||||
)
|
||||
recommendations.push(` Well-known URL: ${provider.wellKnownUrl}`)
|
||||
if (provider.error) {
|
||||
recommendations.push(` Error: ${provider.error}`)
|
||||
}
|
||||
recommendations.push(
|
||||
` Check that the provider's well-known endpoint is accessible and returning valid JSON`
|
||||
)
|
||||
} else {
|
||||
recommendations.push(
|
||||
`✅ Provider '${provider.providerName}' OIDC configuration loaded successfully`
|
||||
)
|
||||
}
|
||||
|
||||
// Check if provider has environment credentials
|
||||
const envKey = provider.providerName.replace('-', '')
|
||||
const providerEnv = envConfig[envKey] || envConfig[provider.providerName]
|
||||
if (providerEnv && !providerEnv.configured) {
|
||||
recommendations.push(
|
||||
`⚠️ Provider '${provider.providerName}' is missing environment credentials`
|
||||
)
|
||||
const upperName = provider.providerName.toUpperCase().replace('-', '_')
|
||||
recommendations.push(` Set VITE_${upperName}_CLIENT_ID and VITE_${upperName}_CLIENT_SECRET`)
|
||||
}
|
||||
})
|
||||
|
||||
// Check current provider status
|
||||
currentStatus.forEach((status) => {
|
||||
if (!status.available) {
|
||||
recommendations.push(`⚠️ Provider '${status.name}' is currently unavailable`)
|
||||
if (status.error) {
|
||||
recommendations.push(` Error: ${status.error}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (recommendations.length === 0) {
|
||||
recommendations.push('✅ All checks passed! OIDC configuration looks good.')
|
||||
}
|
||||
|
||||
return recommendations
|
||||
}
|
||||
|
||||
export default router
|
||||
|
||||
@ -52,6 +52,9 @@ const logoffurl = ref('')
|
||||
const obpApiVersions = ref(inject(obpApiActiveVersionsKey) || [])
|
||||
const obpMessageDocs = ref(Object.keys(inject(obpGroupedMessageDocsKey) || {}))
|
||||
|
||||
// Debug menu items
|
||||
const debugMenuItems = ref(['/debug/providers-status', '/debug/oidc'])
|
||||
|
||||
// Split versions into main and other
|
||||
const mainVersions = ['BGv1.3', 'OBPv5.1.0', 'OBPv6.0.0', 'UKv3.1', 'dynamic-endpoints', 'dynamic-entities', 'OBPdynamic-endpoint', 'OBPdynamic-entity']
|
||||
const sortedVersions = computed(() => {
|
||||
@ -205,6 +208,9 @@ const handleMore = (command: string) => {
|
||||
} else if (command.includes('_')) {
|
||||
console.log('Navigating to message docs:', command)
|
||||
router.push({ name: 'message-docs', params: { id: command } })
|
||||
} else if (command.startsWith('/debug/')) {
|
||||
console.log('Navigating to debug page:', command)
|
||||
router.push(command)
|
||||
} else {
|
||||
console.log('Navigating to resource docs:', `/resource-docs/${command}`)
|
||||
console.log('Current route:', route.path)
|
||||
@ -291,6 +297,15 @@ const getCurrentPath = () => {
|
||||
:background-color="headerLinksBackgroundColor"
|
||||
@select="handleMore"
|
||||
/>
|
||||
<SvelteDropdown
|
||||
class="menu-right"
|
||||
id="header-nav-debug"
|
||||
label="Debug"
|
||||
:items="debugMenuItems"
|
||||
:hover-color="headerLinksHoverColor"
|
||||
:background-color="headerLinksBackgroundColor"
|
||||
@select="handleMore"
|
||||
/>
|
||||
<!--<span class="el-dropdown-link">
|
||||
<RouterLink class="router-link" id="header-nav-spaces" to="/spaces">{{
|
||||
$t('header.spaces')
|
||||
@ -494,7 +509,8 @@ button.login-button-disabled {
|
||||
|
||||
/* Custom dropdown containers */
|
||||
#header-nav-versions,
|
||||
#header-nav-message-docs {
|
||||
#header-nav-message-docs,
|
||||
#header-nav-debug {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ import APIServerStatusView from '../views/APIServerStatusView.vue'
|
||||
import { isServerUp, OBP_API_DEFAULT_RESOURCE_DOC_VERSION } from '../obp'
|
||||
import MessageDocsContent from '@/components/CodeBlock.vue'
|
||||
import ProvidersStatusView from '../views/ProvidersStatusView.vue'
|
||||
import OIDCDebugView from '../views/OIDCDebugView.vue'
|
||||
|
||||
export default async function router(): Promise<any> {
|
||||
const isServerActive = await isServerUp()
|
||||
@ -60,6 +61,11 @@ export default async function router(): Promise<any> {
|
||||
name: 'providers-status',
|
||||
component: ProvidersStatusView
|
||||
},
|
||||
{
|
||||
path: '/debug/oidc',
|
||||
name: 'oidc-debug',
|
||||
component: OIDCDebugView
|
||||
},
|
||||
{
|
||||
path: '/glossary',
|
||||
name: 'glossary',
|
||||
|
||||
455
src/views/OIDCDebugView.vue
Normal file
455
src/views/OIDCDebugView.vue
Normal file
@ -0,0 +1,455 @@
|
||||
<!--
|
||||
Open Bank Project - API Explorer II
|
||||
Copyright (C) 2023-2025, TESOBE GmbH
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Email: contact@tesobe.com
|
||||
TESOBE GmbH
|
||||
Osloerstrasse 16/17
|
||||
Berlin 13359, Germany
|
||||
|
||||
This product includes software developed at
|
||||
TESOBE (http://www.tesobe.com/)
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="oidc-debug-view">
|
||||
<div class="header">
|
||||
<h1>OIDC Provider Discovery</h1>
|
||||
<el-button type="primary" @click="refreshDebugInfo" :loading="loading">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
Refresh
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-alert type="info" :closable="false" style="margin-bottom: 20px">
|
||||
<p><strong>Step 1:</strong> API Explorer discovers providers from OBP API: <code>{{ obpWellKnownUrl }}</code></p>
|
||||
<p><strong>Step 2:</strong> For each provider, fetch their OIDC configuration from their .well-known URL</p>
|
||||
</el-alert>
|
||||
|
||||
<div v-if="loading" v-loading="loading" class="loading-container">
|
||||
<p>Loading provider information...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="error-container">
|
||||
<el-alert type="error" :closable="false">
|
||||
<p><strong>Error:</strong> {{ error }}</p>
|
||||
</el-alert>
|
||||
</div>
|
||||
|
||||
<div v-else-if="debugInfo">
|
||||
<!-- OBP API Discovery Status -->
|
||||
<el-card class="section-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>Step 1: OBP API Provider Discovery</span>
|
||||
<el-tag :type="debugInfo.discoveryProcess.step1_obpApiDiscovery.success ? 'success' : 'danger'">
|
||||
{{ debugInfo.discoveryProcess.step1_obpApiDiscovery.success ? 'Success' : 'Failed' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="section-content">
|
||||
<div class="info-row">
|
||||
<label>OBP API Endpoint:</label>
|
||||
<code>{{ debugInfo.discoveryProcess.step1_obpApiDiscovery.endpoint }}</code>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<label>Providers Discovered:</label>
|
||||
<span>{{ debugInfo.discoveryProcess.step1_obpApiDiscovery.providers.length }}</span>
|
||||
</div>
|
||||
<div v-if="debugInfo.discoveryProcess.step1_obpApiDiscovery.error" class="error-message">
|
||||
<strong>Error:</strong> {{ debugInfo.discoveryProcess.step1_obpApiDiscovery.error }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- Provider List -->
|
||||
<el-card class="section-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>Step 2: Provider OIDC Configurations</span>
|
||||
<el-tag type="info">{{ debugInfo.discoveryProcess.step2_providerConfigurations.length }} Provider(s)</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<div class="section-content">
|
||||
<div v-if="debugInfo.discoveryProcess.step2_providerConfigurations.length === 0" class="no-data">
|
||||
<el-empty description="No providers found" />
|
||||
</div>
|
||||
|
||||
<div v-else class="provider-list">
|
||||
<div
|
||||
v-for="provider in debugInfo.discoveryProcess.step2_providerConfigurations"
|
||||
:key="provider.providerName"
|
||||
class="provider-item"
|
||||
:class="{ 'provider-success': provider.success, 'provider-error': !provider.success }"
|
||||
>
|
||||
<div class="provider-header">
|
||||
<h3>{{ provider.providerName }}</h3>
|
||||
<el-tag :type="provider.success ? 'success' : 'danger'" size="small">
|
||||
{{ provider.success ? 'Success' : 'Failed' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="provider-details">
|
||||
<div class="detail-row">
|
||||
<label>Well-Known URL:</label>
|
||||
<div class="url-display">
|
||||
<code>{{ provider.wellKnownUrl }}</code>
|
||||
<el-button size="small" @click="copyToClipboard(provider.wellKnownUrl)" text>
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
Copy
|
||||
</el-button>
|
||||
<el-button size="small" @click="testEndpoint(provider.wellKnownUrl)" text>
|
||||
<el-icon><Link /></el-icon>
|
||||
Test
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="provider.error" class="error-message">
|
||||
<strong>Error:</strong> {{ provider.error }}
|
||||
</div>
|
||||
|
||||
<div v-if="provider.success" class="endpoints-section">
|
||||
<label>OIDC Endpoints:</label>
|
||||
<div class="endpoints-list">
|
||||
<div class="endpoint-row">
|
||||
<span class="endpoint-label">Issuer:</span>
|
||||
<code>{{ provider.issuer }}</code>
|
||||
</div>
|
||||
<div class="endpoint-row">
|
||||
<span class="endpoint-label">Authorization:</span>
|
||||
<code>{{ provider.endpoints.authorization }}</code>
|
||||
</div>
|
||||
<div class="endpoint-row">
|
||||
<span class="endpoint-label">Token:</span>
|
||||
<code>{{ provider.endpoints.token }}</code>
|
||||
</div>
|
||||
<div class="endpoint-row">
|
||||
<span class="endpoint-label">UserInfo:</span>
|
||||
<code>{{ provider.endpoints.userinfo }}</code>
|
||||
</div>
|
||||
<div class="endpoint-row">
|
||||
<span class="endpoint-label">JWKS:</span>
|
||||
<code>{{ provider.endpoints.jwks }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Refresh, CopyDocument, Link } from '@element-plus/icons-vue'
|
||||
|
||||
interface DebugInfo {
|
||||
discoveryProcess: {
|
||||
step1_obpApiDiscovery: {
|
||||
endpoint: string
|
||||
success: boolean
|
||||
error: string | null
|
||||
providers: Array<{ provider: string; url: string }>
|
||||
}
|
||||
step2_providerConfigurations: Array<{
|
||||
providerName: string
|
||||
wellKnownUrl: string
|
||||
success: boolean
|
||||
error: string | null
|
||||
endpoints: {
|
||||
authorization: string | null
|
||||
token: string | null
|
||||
userinfo: string | null
|
||||
jwks: string | null
|
||||
}
|
||||
issuer: string | null
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
const loading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
const debugInfo = ref<DebugInfo | null>(null)
|
||||
|
||||
const obpWellKnownUrl = computed(() => {
|
||||
return debugInfo.value?.discoveryProcess.step1_obpApiDiscovery.endpoint || 'Loading...'
|
||||
})
|
||||
|
||||
const fetchDebugInfo = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/status/oidc-debug')
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||
}
|
||||
debugInfo.value = await response.json()
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : 'Unknown error'
|
||||
ElMessage.error('Failed to load provider information')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refreshDebugInfo = async () => {
|
||||
ElMessage.info('Refreshing provider information...')
|
||||
await fetchDebugInfo()
|
||||
ElMessage.success('Provider information refreshed')
|
||||
}
|
||||
|
||||
const copyToClipboard = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
ElMessage.success('Copied to clipboard')
|
||||
} catch (err) {
|
||||
ElMessage.error('Failed to copy to clipboard')
|
||||
}
|
||||
}
|
||||
|
||||
const testEndpoint = (url: string) => {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDebugInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.oidc-debug-view {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px;
|
||||
font-size: 16px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-row label {
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.info-row code {
|
||||
background: #f4f4f5;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.provider-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.provider-item {
|
||||
padding: 20px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.provider-success {
|
||||
border-color: #67c23a;
|
||||
background: #f0f9ff;
|
||||
}
|
||||
|
||||
.provider-error {
|
||||
border-color: #f56c6c;
|
||||
background: #fef0f0;
|
||||
}
|
||||
|
||||
.provider-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.provider-header h3 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.provider-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.detail-row label {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.url-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.url-display code {
|
||||
flex: 1;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
padding: 10px;
|
||||
background: #fef0f0;
|
||||
border-left: 4px solid #f56c6c;
|
||||
border-radius: 4px;
|
||||
color: #f56c6c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.endpoints-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.endpoints-section label {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.endpoints-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.endpoint-row {
|
||||
display: grid;
|
||||
grid-template-columns: 120px 1fr;
|
||||
gap: 10px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.endpoint-label {
|
||||
font-weight: 500;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.endpoint-row code {
|
||||
background: #f4f4f5;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.endpoint-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -31,6 +31,13 @@
|
||||
<el-alert type="info" :closable="false" style="margin-bottom: 20px">
|
||||
<p>This page shows which OAuth2/OIDC identity providers are configured and available for login.</p>
|
||||
<p><strong>Note:</strong> Client secrets are masked for security.</p>
|
||||
<p>
|
||||
<strong>Need more details?</strong> Visit the
|
||||
<router-link to="/debug/oidc" style="color: #409eff; text-decoration: underline;">
|
||||
OIDC Debug Page
|
||||
</router-link>
|
||||
for detailed discovery process information.
|
||||
</p>
|
||||
</el-alert>
|
||||
|
||||
<div v-if="loading" v-loading="loading" class="loading-container">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user