use plain express 5 with new files

This commit is contained in:
simonredfern 2025-12-29 09:07:21 +01:00
parent 5cb5cfc229
commit c755b47e80
5 changed files with 932 additions and 4 deletions

244
server/routes/obp.ts Normal file
View File

@ -0,0 +1,244 @@
/*
* 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/)
*
*/
import { Router } from 'express'
import type { Request, Response } from 'express'
import { Container } from 'typedi'
import OBPClientService from '../services/OBPClientService.js'
import { OAuth2Service } from '../services/OAuth2Service.js'
const router = Router()
// Get services from container
const obpClientService = Container.get(OBPClientService)
const oauth2Service = Container.get(OAuth2Service)
/**
* Check if access token is expired and refresh it if needed
* This ensures API calls always use a valid token
*/
async function ensureValidToken(session: any): Promise<boolean> {
const accessToken = session.oauth2_access_token
const refreshToken = session.oauth2_refresh_token
// If no access token, user is not authenticated
if (!accessToken) {
return false
}
// Check if token is expired
if (oauth2Service.isTokenExpired(accessToken)) {
console.log('OBP: Access token expired, attempting refresh')
if (!refreshToken) {
console.log('OBP: No refresh token available')
return false
}
try {
const newTokens = await oauth2Service.refreshAccessToken(refreshToken)
// Update session with new tokens
session.oauth2_access_token = newTokens.accessToken
session.oauth2_refresh_token = newTokens.refreshToken || refreshToken
session.oauth2_id_token = newTokens.idToken
session.oauth2_token_timestamp = Date.now()
session.oauth2_expires_in = newTokens.expiresIn
// Update clientConfig with new access token
if (session.clientConfig && session.clientConfig.oauth2) {
session.clientConfig.oauth2.accessToken = newTokens.accessToken
console.log('OBP: Updated clientConfig with refreshed token')
}
console.log('OBP: Token refresh successful')
return true
} catch (error) {
console.error('OBP: Token refresh failed:', error)
return false
}
}
// Token is still valid
return true
}
/**
* GET /get
* Proxy GET requests to OBP API
* Query params:
* - path: OBP API path to call (e.g., /obp/v5.1.0/banks)
*/
router.get('/get', async (req: Request, res: Response) => {
try {
const path = req.query.path as string
const session = req.session as any
// Ensure token is valid before making the request
const tokenValid = await ensureValidToken(session)
if (!tokenValid && session.oauth2_user) {
console.log('OBP: Token expired and refresh failed')
return res.status(401).json({
code: 401,
message: 'Session expired. Please log in again.'
})
}
const oauthConfig = session.clientConfig
const result = await obpClientService.get(path, oauthConfig)
res.json(result)
} catch (error: any) {
// 401 errors are expected when user is not authenticated - log as info, not error
if (error.status === 401) {
console.log(`OBP: 401 Unauthorized for path: ${req.query.path} (user not authenticated)`)
} else {
console.error('OBP: GET request error:', error)
}
res.status(error.status || 500).json({
code: error.status || 500,
message: error.message || 'Internal server error'
})
}
})
/**
* POST /create
* Proxy POST requests to OBP API
* Query params:
* - path: OBP API path to call
* Body: JSON data to send to OBP API
*/
router.post('/create', async (req: Request, res: Response) => {
try {
const path = req.query.path as string
const data = req.body
const session = req.session as any
// Ensure token is valid before making the request
const tokenValid = await ensureValidToken(session)
if (!tokenValid && session.oauth2_user) {
console.log('OBP: Token expired and refresh failed')
return res.status(401).json({
code: 401,
message: 'Session expired. Please log in again.'
})
}
const oauthConfig = session.clientConfig
// Debug logging to diagnose authentication issues
console.log('OBP.create - Debug Info:')
console.log(' Path:', path)
console.log(' Session exists:', !!session)
console.log(' clientConfig exists:', !!oauthConfig)
console.log(' oauth2 exists:', oauthConfig?.oauth2 ? 'YES' : 'NO')
console.log(' accessToken exists:', oauthConfig?.oauth2?.accessToken ? 'YES' : 'NO')
console.log(' oauth2_user exists:', session?.oauth2_user ? 'YES' : 'NO')
const result = await obpClientService.create(path, data, oauthConfig)
res.json(result)
} catch (error: any) {
console.error('OBP.create error:', error)
res.status(error.status || 500).json({
code: error.status || 500,
message: error.message || 'Internal server error'
})
}
})
/**
* PUT /update
* Proxy PUT requests to OBP API
* Query params:
* - path: OBP API path to call
* Body: JSON data to send to OBP API
*/
router.put('/update', async (req: Request, res: Response) => {
try {
const path = req.query.path as string
const data = req.body
const session = req.session as any
// Ensure token is valid before making the request
const tokenValid = await ensureValidToken(session)
if (!tokenValid && session.oauth2_user) {
console.log('OBP: Token expired and refresh failed')
return res.status(401).json({
code: 401,
message: 'Session expired. Please log in again.'
})
}
const oauthConfig = session.clientConfig
const result = await obpClientService.update(path, data, oauthConfig)
res.json(result)
} catch (error: any) {
console.error('OBP.update error:', error)
res.status(error.status || 500).json({
code: error.status || 500,
message: error.message || 'Internal server error'
})
}
})
/**
* DELETE /delete
* Proxy DELETE requests to OBP API
* Query params:
* - path: OBP API path to call
*/
router.delete('/delete', async (req: Request, res: Response) => {
try {
const path = req.query.path as string
const session = req.session as any
// Ensure token is valid before making the request
const tokenValid = await ensureValidToken(session)
if (!tokenValid && session.oauth2_user) {
console.log('OBP: Token expired and refresh failed')
return res.status(401).json({
code: 401,
message: 'Session expired. Please log in again.'
})
}
const oauthConfig = session.clientConfig
const result = await obpClientService.discard(path, oauthConfig)
res.json(result)
} catch (error: any) {
console.error('OBP.delete error:', error)
res.status(error.status || 500).json({
code: error.status || 500,
message: error.message || 'Internal server error'
})
}
})
export default router

280
server/routes/opey.ts Normal file
View File

@ -0,0 +1,280 @@
/*
* 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/)
*
*/
import { Router } from 'express'
import type { Request, Response } from 'express'
import { Readable } from 'node:stream'
import { ReadableStream as WebReadableStream } from 'stream/web'
import { Container } from 'typedi'
import OBPClientService from '../services/OBPClientService.js'
import OpeyClientService from '../services/OpeyClientService.js'
import OBPConsentsService from '../services/OBPConsentsService.js'
import { UserInput } from '../schema/OpeySchema.js'
const router = Router()
// Get services from container
const obpClientService = Container.get(OBPClientService)
const opeyClientService = Container.get(OpeyClientService)
const obpConsentsService = Container.get(OBPConsentsService)
/**
* Helper function to convert web stream to Node.js stream
*/
function safeFromWeb(webStream: WebReadableStream<any>): Readable {
if (typeof Readable.fromWeb === 'function') {
return Readable.fromWeb(webStream)
} else {
console.warn('Readable.fromWeb is not available, using a polyfill')
// Create a Node.js Readable stream
const nodeReadable = new Readable({
read() {}
})
// Pump data from webreadable to node readable stream
const reader = webStream.getReader()
;(async () => {
try {
while (true) {
const { done, value } = await reader.read()
if (done) {
nodeReadable.push(null) // end stream
break
}
nodeReadable.push(value)
}
} catch (error) {
console.error('Error reading from web stream:', error)
nodeReadable.destroy(error instanceof Error ? error : new Error(String(error)))
}
})()
return nodeReadable
}
}
/**
* GET /opey
* Check Opey chatbot status
*/
router.get('/opey', async (req: Request, res: Response) => {
try {
const opeyStatus = await opeyClientService.getOpeyStatus()
console.log('Opey status: ', opeyStatus)
res.status(200).json({ status: 'Opey is running' })
} catch (error) {
console.error('Error in /opey endpoint: ', error)
res.status(500).json({ error: 'Internal Server Error' })
}
})
/**
* POST /opey/stream
* Stream chatbot responses
* Body: { message, thread_id, is_tool_call_approval }
*/
router.post('/opey/stream', async (req: Request, res: Response) => {
try {
const session = req.session as any
if (!session) {
console.error('Session not found')
return res.status(401).json({ error: 'Session Time Out' })
}
// Check if the consent is in the session
const opeyConfig = session.opeyConfig
if (!opeyConfig) {
console.error('Opey config not found in session')
return res.status(500).json({ error: 'Internal Server Error' })
}
// Read user input from request body
let user_input: UserInput
try {
console.log('Request body: ', req.body)
user_input = {
message: req.body.message,
thread_id: req.body.thread_id,
is_tool_call_approval: req.body.is_tool_call_approval
}
} catch (error) {
console.error('Error in stream endpoint, could not parse into UserInput: ', error)
return res.status(500).json({ error: 'Internal Server Error' })
}
// Transform to decode and log the stream
const frontendTransformer = new TransformStream({
transform(chunk, controller) {
// Decode the chunk to a string
const decodedChunk = new TextDecoder().decode(chunk)
console.log('Sending chunk', decodedChunk)
controller.enqueue(decodedChunk)
},
flush(controller) {
console.log('[flush]')
// Close ReadableStream when done
controller.terminate()
}
})
let stream: ReadableStream | null = null
try {
// Read web stream from OpeyClientService
console.log('Calling OpeyClientService.stream')
stream = await opeyClientService.stream(user_input, opeyConfig)
} catch (error) {
console.error('Error reading stream: ', error)
return res.status(500).json({ error: 'Internal Server Error' })
}
if (!stream) {
console.error('Stream is not received or not readable')
return res.status(500).json({ error: 'Internal Server Error' })
}
// Transform our stream
const frontendStream: ReadableStream = stream.pipeThrough(frontendTransformer)
const nodeStream = safeFromWeb(frontendStream as WebReadableStream<any>)
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
nodeStream.pipe(res)
// Handle stream completion
nodeStream.on('end', () => {
console.log('Stream ended successfully')
})
nodeStream.on('error', (error) => {
console.error('Stream error:', error)
})
// Add a timeout to prevent hanging
const timeout = setTimeout(() => {
console.warn('Stream timeout reached')
nodeStream.destroy()
}, 30000)
// Clear the timeout when stream ends
nodeStream.on('end', () => clearTimeout(timeout))
nodeStream.on('error', () => clearTimeout(timeout))
} catch (error) {
console.error('Error in /opey/stream:', error)
if (!res.headersSent) {
res.status(500).json({ error: 'Internal Server Error' })
}
}
})
/**
* POST /opey/invoke
* Invoke chatbot without streaming
* Body: { message, thread_id, is_tool_call_approval }
*/
router.post('/opey/invoke', async (req: Request, res: Response) => {
try {
const session = req.session as any
// Check if the consent is in the session
const opeyConfig = session.opeyConfig
if (!opeyConfig) {
console.error('Opey config not found in session')
return res.status(500).json({ error: 'Internal Server Error' })
}
let user_input: UserInput
try {
user_input = {
message: req.body.message,
thread_id: req.body.thread_id,
is_tool_call_approval: req.body.is_tool_call_approval
}
} catch (error) {
console.error('Error in invoke endpoint, could not parse into UserInput: ', error)
return res.status(500).json({ error: 'Internal Server Error' })
}
const opey_response = await opeyClientService.invoke(user_input, opeyConfig)
res.status(200).json(opey_response)
} catch (error) {
console.error('Error in /opey/invoke:', error)
res.status(500).json({ error: 'Internal Server Error' })
}
})
/**
* POST /opey/consent
* Retrieve or create a consent for Opey to access OBP on user's behalf
*/
router.post('/opey/consent', async (req: Request, res: Response) => {
try {
const session = req.session as any
// Create consent as logged in user
const opeyConfig = await opeyClientService.getOpeyConfig()
session.opeyConfig = opeyConfig
// Check if user already has a consent for opey
const consentId = await obpConsentsService.getExistingOpeyConsentId(session)
if (consentId) {
console.log('Existing consent ID: ', consentId)
// If we have a consent id, we can get the consent from OBP
const consent = await obpConsentsService.getConsentByConsentId(session, consentId)
return res.status(200).json({ consent_id: consent.consent_id, jwt: consent.jwt })
} else {
console.log('No existing consent ID found')
}
await obpConsentsService.createConsent(session)
console.log('Consent at controller: ', session.opeyConfig)
const authConfig = session.opeyConfig?.authConfig
res.status(200).json({
consent_id: authConfig?.obpConsent.consent_id,
jwt: authConfig?.obpConsent.jwt
})
} catch (error) {
console.error('Error in /opey/consent endpoint: ', error)
res.status(500).json({ error: 'Internal Server Error' })
}
})
export default router

228
server/routes/status.ts Normal file
View File

@ -0,0 +1,228 @@
/*
* 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/)
*
*/
import { Router } from 'express'
import type { Request, Response } from 'express'
import { Container } from 'typedi'
import OBPClientService from '../services/OBPClientService.js'
import { OAuth2Service } from '../services/OAuth2Service.js'
import { commitId } from '../app.js'
import {
RESOURCE_DOCS_API_VERSION,
MESSAGE_DOCS_API_VERSION,
API_VERSIONS_LIST_API_VERSION
} from '../../src/shared-constants.js'
const router = Router()
// Get services from container
const obpClientService = Container.get(OBPClientService)
const oauth2Service = Container.get(OAuth2Service)
const connectors = ['akka_vDec2018', 'rest_vMar2019', 'stored_procedure_vDec2019', 'rabbitmq_vOct2024']
/**
* Helper function to check if response contains an error
*/
function isCodeError(response: any, path: string): boolean {
console.log(`Validating ${path} response...`)
if (!response || Object.keys(response).length === 0) return true
if (Object.keys(response).includes('code')) {
const code = response['code']
if (code >= 400) {
console.log(response) // Log error response
return true
}
}
return false
}
/**
* Check if resource docs are accessible
*/
async function checkResourceDocs(oauthConfig: any, version: string): Promise<boolean> {
try {
const path = `/obp/${RESOURCE_DOCS_API_VERSION}/resource-docs/${version}/obp`
const resourceDocs = await obpClientService.get(path, oauthConfig)
return !isCodeError(resourceDocs, path)
} catch (error) {
return false
}
}
/**
* Check if message docs are accessible
*/
async function checkMessageDocs(oauthConfig: any, version: string): Promise<boolean> {
try {
const messageDocsCodeResult = await Promise.all(
connectors.map(async (connector) => {
const path = `/obp/${MESSAGE_DOCS_API_VERSION}/message-docs/${connector}`
return !isCodeError(await obpClientService.get(path, oauthConfig), path)
})
)
return messageDocsCodeResult.every((isCodeError: boolean) => isCodeError)
} catch (error) {
return false
}
}
/**
* Check if API versions are accessible
*/
async function checkApiVersions(oauthConfig: any, version: string): Promise<boolean> {
try {
const path = `/obp/${API_VERSIONS_LIST_API_VERSION}/api/versions`
const versions = await obpClientService.get(path, oauthConfig)
return !isCodeError(versions, path)
} catch (error) {
return false
}
}
/**
* GET /status
* Get application status and health checks
*/
router.get('/status', async (req: Request, res: Response) => {
try {
const session = req.session as any
const oauthConfig = session.clientConfig
const version = obpClientService.getOBPVersion()
// Check if user is authenticated
const isAuthenticated = oauthConfig && oauthConfig.oauth2?.accessToken
let currentUser = null
let apiVersions = false
let messageDocs = false
let resourceDocs = false
if (isAuthenticated) {
try {
currentUser = await obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
apiVersions = await checkApiVersions(oauthConfig, version)
messageDocs = await checkMessageDocs(oauthConfig, version)
resourceDocs = await checkResourceDocs(oauthConfig, version)
} catch (error) {
console.error('Status: Error fetching authenticated data:', error)
}
}
res.json({
status: apiVersions && messageDocs && resourceDocs,
apiVersions,
messageDocs,
resourceDocs,
currentUser,
isAuthenticated,
commitId
})
} catch (error) {
console.error('Status: Error getting status:', error)
res.status(500).json({
status: false,
error: error instanceof Error ? error.message : 'Unknown error'
})
}
})
/**
* GET /status/oauth2
* Get OAuth2/OIDC status
*/
router.get('/status/oauth2', (req: Request, res: Response) => {
try {
const isInitialized = oauth2Service.isInitialized()
const oidcConfig = oauth2Service.getOIDCConfiguration()
const healthCheckActive = oauth2Service.isHealthCheckActive()
const healthCheckAttempts = oauth2Service.getHealthCheckAttempts()
res.json({
available: isInitialized,
message: isInitialized
? 'OAuth2/OIDC is ready for authentication'
: 'OAuth2/OIDC is not available',
issuer: oidcConfig?.issuer || null,
authorizationEndpoint: oidcConfig?.authorization_endpoint || null,
wellKnownUrl: process.env.VITE_OBP_OAUTH2_WELL_KNOWN_URL || null,
healthCheck: {
active: healthCheckActive,
attempts: healthCheckAttempts
}
})
} catch (error) {
res.status(500).json({
available: false,
message: 'Error checking OAuth2 status',
error: error instanceof Error ? error.message : 'Unknown error'
})
}
})
/**
* GET /status/oauth2/reconnect
* Attempt to reconnect OAuth2/OIDC
*/
router.get('/status/oauth2/reconnect', async (req: Request, res: Response) => {
try {
if (oauth2Service.isInitialized()) {
return res.json({
success: true,
message: 'OAuth2 is already connected',
alreadyConnected: true
})
}
const wellKnownUrl = process.env.VITE_OBP_OAUTH2_WELL_KNOWN_URL
if (!wellKnownUrl) {
return res.status(400).json({
success: false,
message: 'VITE_OBP_OAUTH2_WELL_KNOWN_URL not configured'
})
}
console.log('Manual OAuth2 reconnection attempt triggered...')
await oauth2Service.initializeFromWellKnown(wellKnownUrl)
console.log('Manual OAuth2 reconnection successful!')
res.json({
success: true,
message: 'OAuth2 reconnection successful',
issuer: oauth2Service.getOIDCConfiguration()?.issuer || null
})
} catch (error) {
console.error('Manual OAuth2 reconnection failed:', error)
res.status(500).json({
success: false,
message: 'OAuth2 reconnection failed',
error: error instanceof Error ? error.message : 'Unknown error'
})
}
})
export default router

173
server/routes/user.ts Normal file
View File

@ -0,0 +1,173 @@
/*
* 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/)
*
*/
import { Router } from 'express'
import type { Request, Response } from 'express'
import { Container } from 'typedi'
import OBPClientService from '../services/OBPClientService.js'
import { OAuth2Service } from '../services/OAuth2Service.js'
import { DEFAULT_OBP_API_VERSION } from '../../src/shared-constants.js'
const router = Router()
// Get services from container
const obpClientService = Container.get(OBPClientService)
const oauth2Service = Container.get(OAuth2Service)
const obpExplorerHome = process.env.VITE_OBP_API_EXPLORER_HOST
/**
* GET /user/current
* Get current logged in user information
*/
router.get('/user/current', async (req: Request, res: Response) => {
try {
console.log('User: Getting current user')
const session = req.session as any
// Check OAuth2 session
if (!session.oauth2_user) {
console.log('User: No authentication session found')
return res.json({})
}
console.log('User: Returning OAuth2 user info')
const oauth2User = session.oauth2_user
const accessToken = session.oauth2_access_token
const refreshToken = session.oauth2_refresh_token
// Check if access token is expired and needs refresh
if (accessToken && oauth2Service.isTokenExpired(accessToken)) {
console.log('User: Access token expired')
if (refreshToken) {
console.log('User: Attempting token refresh')
try {
const newTokens = await oauth2Service.refreshAccessToken(refreshToken)
// Update session with new tokens
session.oauth2_access_token = newTokens.accessToken
session.oauth2_refresh_token = newTokens.refreshToken || refreshToken
session.oauth2_id_token = newTokens.idToken
session.oauth2_token_timestamp = Date.now()
session.oauth2_expires_in = newTokens.expiresIn
// Update clientConfig with new access token
if (session.clientConfig && session.clientConfig.oauth2) {
session.clientConfig.oauth2.accessToken = newTokens.accessToken
console.log('User: Updated clientConfig with new access token')
}
console.log('User: Token refresh successful')
} catch (error) {
console.error('User: Token refresh failed:', error)
return res.json({})
}
} else {
console.log('User: No refresh token available, user needs to re-authenticate')
return res.json({})
}
}
// Get actual user ID from OBP-API
let obpUserId = oauth2User.sub // Default to sub if OBP call fails
const clientConfig = session.clientConfig
if (clientConfig && clientConfig.oauth2?.accessToken) {
try {
const version = DEFAULT_OBP_API_VERSION
console.log('User: Fetching OBP user from /obp/' + version + '/users/current')
const obpUser = await obpClientService.get(`/obp/${version}/users/current`, clientConfig)
if (obpUser && obpUser.user_id) {
obpUserId = obpUser.user_id
console.log('User: Got OBP user ID:', obpUserId, '(was:', oauth2User.sub, ')')
} else {
console.warn('User: OBP user response has no user_id:', obpUser)
}
} catch (error: any) {
console.warn('User: Could not fetch OBP user ID, using token sub:', oauth2User.sub)
console.warn('User: Error details:', error.message)
}
} else {
console.warn('User: No valid clientConfig or access token, using token sub:', oauth2User.sub)
}
// Return user info in format compatible with frontend
res.json({
user_id: obpUserId,
username: oauth2User.username,
email: oauth2User.email,
email_verified: oauth2User.email_verified,
name: oauth2User.name,
given_name: oauth2User.given_name,
family_name: oauth2User.family_name,
provider: oauth2User.provider || 'oauth2'
})
} catch (error) {
console.error('User: Error getting current user:', error)
res.json({})
}
})
/**
* GET /user/logoff
* Logout user and clear session
* Query params:
* - redirect: URL to redirect to after logout (optional)
*/
router.get('/user/logoff', (req: Request, res: Response) => {
console.log('User: Logging out user')
const session = req.session as any
// Clear OAuth2 session data
delete session.oauth2_access_token
delete session.oauth2_refresh_token
delete session.oauth2_id_token
delete session.oauth2_token_type
delete session.oauth2_expires_in
delete session.oauth2_token_timestamp
delete session.oauth2_user_info
delete session.oauth2_user
delete session.oauth2_provider
delete session.clientConfig
delete session.opeyConfig
// Destroy the session completely
session.destroy((err: any) => {
if (err) {
console.error('User: Error destroying session:', err)
} else {
console.log('User: Session destroyed successfully')
}
const redirectPage = (req.query.redirect as string) || obpExplorerHome || '/'
console.log('User: Redirecting to:', redirectPage)
res.redirect(redirectPage)
})
})
export default router

View File

@ -48,6 +48,7 @@ import type { OIDCConfiguration, TokenResponse } from '../types/oauth2.js'
export class OAuth2ClientWithConfig extends OAuth2Client {
public OIDCConfig?: OIDCConfiguration
public provider: string
public wellKnownUri?: string
private _clientSecret: string
private _redirectUri: string
@ -67,14 +68,16 @@ export class OAuth2ClientWithConfig extends OAuth2Client {
* @example
* await client.initOIDCConfig('http://localhost:9000/obp-oidc/.well-known/openid-configuration')
*/
async initOIDCConfig(oidcConfigUrl: string): Promise<void> {
async initOIDCConfig(wellKnownUrl: string): Promise<void> {
console.log(
`OAuth2ClientWithConfig: Fetching OIDC config for ${this.provider} from:`,
oidcConfigUrl
`OAuth2ClientWithConfig: Fetching OIDC config for ${this.provider} from: ${wellKnownUrl}`
)
// Store the well-known URL for health checks
this.wellKnownUri = wellKnownUrl
try {
const response = await fetch(oidcConfigUrl)
const response = await fetch(wellKnownUrl)
if (!response.ok) {
throw new Error(