mirror of
https://github.com/OpenBankProject/API-Explorer-II.git
synced 2026-02-06 10:47:04 +00:00
Phase 2: Integrate OAuth2 into application
Application Integration: - Update server/app.ts to initialize OAuth2Service on startup * Conditional initialization based on VITE_USE_OAUTH2 flag * OIDC discovery document fetching * Comprehensive error handling and logging * Graceful fallback if OIDC provider unavailable UserController Updates: - Support dual authentication (OAuth 1.0a and OAuth2) - OAuth2 user session detection and retrieval - Automatic token refresh when access token expires - Unified user data format for both auth methods - Enhanced logout to clear both OAuth 1.0a and OAuth2 sessions - Comprehensive logging for debugging Features: - Seamless switching between auth methods via feature flag - Backward compatibility maintained - Automatic token refresh before expiry - Session cleanup on logout - Error handling with fallback to empty response Next phase: Update frontend components for OAuth2 login flow
This commit is contained in:
parent
86295f827a
commit
b2df3a9791
@ -34,7 +34,8 @@ import express, { Application } from 'express'
|
||||
import { useExpressServer, useContainer } from 'routing-controllers'
|
||||
import { Container } from 'typedi'
|
||||
import path from 'path'
|
||||
import { execSync } from 'child_process';
|
||||
import { execSync } from 'child_process'
|
||||
import { OAuth2Service } from './services/OAuth2Service'
|
||||
|
||||
const port = 8085
|
||||
const app: Application = express()
|
||||
@ -106,6 +107,46 @@ if (app.get('env') === 'production') {
|
||||
app.use(session(sessionObject))
|
||||
useContainer(Container)
|
||||
|
||||
// Initialize OAuth2 Service
|
||||
console.log(`--- OAuth2/OIDC setup -------------------------------------------`)
|
||||
const useOAuth2 = process.env.VITE_USE_OAUTH2 === 'true'
|
||||
console.log(`OAuth2/OIDC enabled: ${useOAuth2}`)
|
||||
|
||||
if (useOAuth2) {
|
||||
const wellKnownUrl = process.env.VITE_OBP_OAUTH2_WELL_KNOWN_URL
|
||||
|
||||
if (!wellKnownUrl) {
|
||||
console.warn('VITE_OBP_OAUTH2_WELL_KNOWN_URL not set. OAuth2 will not function.')
|
||||
} else {
|
||||
console.log(`OIDC Well-Known URL: ${wellKnownUrl}`)
|
||||
|
||||
// Get OAuth2Service from container
|
||||
const oauth2Service = Container.get(OAuth2Service)
|
||||
|
||||
// Initialize OAuth2 service from OIDC discovery document
|
||||
oauth2Service
|
||||
.initializeFromWellKnown(wellKnownUrl)
|
||||
.then(() => {
|
||||
console.log('OAuth2Service: Initialization successful')
|
||||
console.log(' Client ID:', process.env.VITE_OBP_OAUTH2_CLIENT_ID || 'NOT SET')
|
||||
console.log(' Redirect URI:', process.env.VITE_OBP_OAUTH2_REDIRECT_URL || 'NOT SET')
|
||||
console.log('OAuth2/OIDC ready for authentication')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('OAuth2Service: Initialization failed:', error.message)
|
||||
console.error('OAuth2/OIDC authentication will not be available')
|
||||
console.error('Please check:')
|
||||
console.error(' 1. OBP-OIDC server is running')
|
||||
console.error(' 2. VITE_OBP_OAUTH2_WELL_KNOWN_URL is correct')
|
||||
console.error(' 3. Network connectivity to OIDC provider')
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.log('OAuth2/OIDC is disabled. Using OAuth 1.0a authentication.')
|
||||
console.log('To enable OAuth2, set VITE_USE_OAUTH2=true in environment')
|
||||
}
|
||||
console.log(`-----------------------------------------------------------------`)
|
||||
|
||||
const routePrefix = '/api'
|
||||
|
||||
const server = useExpressServer(app, {
|
||||
@ -121,31 +162,31 @@ console.log(
|
||||
)
|
||||
|
||||
// Get commit ID
|
||||
export let commitId = '';
|
||||
export let commitId = ''
|
||||
|
||||
try {
|
||||
// Try to get the commit ID
|
||||
commitId = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
|
||||
console.log('Current Commit ID:', commitId);
|
||||
// Try to get the commit ID
|
||||
commitId = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim()
|
||||
console.log('Current Commit ID:', commitId)
|
||||
} catch (error) {
|
||||
// Log the error but do not terminate the process
|
||||
console.error('Warning: Failed to retrieve the commit ID. Proceeding without it.');
|
||||
console.error('Error details:', error.message);
|
||||
commitId = 'unknown'; // Assign a fallback value
|
||||
// Log the error but do not terminate the process
|
||||
console.error('Warning: Failed to retrieve the commit ID. Proceeding without it.')
|
||||
console.error('Error details:', error.message)
|
||||
commitId = 'unknown' // Assign a fallback value
|
||||
}
|
||||
// Continue execution with or without a valid commit ID
|
||||
console.log('Execution continues with commitId:', commitId);
|
||||
console.log('Execution continues with commitId:', commitId)
|
||||
|
||||
// Error Handling to Shut Down the App
|
||||
server.on('error', (err) => {
|
||||
redisClient.disconnect();
|
||||
redisClient.disconnect()
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.error(`Port ${port} is already in use.`);
|
||||
process.exit(1);
|
||||
// Shut down the app
|
||||
console.error(`Port ${port} is already in use.`)
|
||||
process.exit(1)
|
||||
// Shut down the app
|
||||
} else {
|
||||
console.error('An error occurred:', err);
|
||||
console.error('An error occurred:', err)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
export default app
|
||||
|
||||
@ -31,14 +31,18 @@ import OBPClientService from '../services/OBPClientService'
|
||||
import OauthInjectedService from '../services/OauthInjectedService'
|
||||
import { Service } from 'typedi'
|
||||
import superagent from 'superagent'
|
||||
import { OAuth2Service } from '../services/OAuth2Service'
|
||||
|
||||
@Service()
|
||||
@Controller('/user')
|
||||
export class UserController {
|
||||
private obpExplorerHome = process.env.VITE_OBP_API_EXPLORER_HOST
|
||||
private useOAuth2 = process.env.VITE_USE_OAUTH2 === 'true'
|
||||
|
||||
constructor(
|
||||
private obpClientService: OBPClientService,
|
||||
private oauthInjectedService: OauthInjectedService
|
||||
private oauthInjectedService: OauthInjectedService,
|
||||
private oauth2Service: OAuth2Service
|
||||
) {}
|
||||
@Get('/logoff')
|
||||
async logout(
|
||||
@ -46,20 +50,41 @@ export class UserController {
|
||||
@Req() request: Request,
|
||||
@Res() response: Response
|
||||
): Response {
|
||||
console.log('UserController: Logging out user')
|
||||
|
||||
// Clear OAuth 1.0a session data
|
||||
this.oauthInjectedService.requestTokenKey = undefined
|
||||
this.oauthInjectedService.requestTokenSecret = undefined
|
||||
session['clientConfig'] = undefined
|
||||
|
||||
if (request.query.redirect) {
|
||||
response.redirect(request.query.redirect as string)
|
||||
} else {
|
||||
if(!this.obpExplorerHome) {
|
||||
console.error(`VITE_OBP_API_EXPLORER_HOST: ${this.obpExplorerHome}`)
|
||||
// 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']
|
||||
|
||||
// Destroy the session completely
|
||||
session.destroy((err: any) => {
|
||||
if (err) {
|
||||
console.error('UserController: Error destroying session:', err)
|
||||
} else {
|
||||
console.log('UserController: Session destroyed successfully')
|
||||
}
|
||||
response.redirect(this.obpExplorerHome)
|
||||
})
|
||||
|
||||
const redirectPage = (request.query.redirect as string) || this.obpExplorerHome || '/'
|
||||
|
||||
if (!this.obpExplorerHome) {
|
||||
console.error(`VITE_OBP_API_EXPLORER_HOST: ${this.obpExplorerHome}`)
|
||||
}
|
||||
|
||||
|
||||
console.log('UserController: Redirecting to:', redirectPage)
|
||||
response.redirect(redirectPage)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
@ -69,10 +94,76 @@ export class UserController {
|
||||
@Req() request: Request,
|
||||
@Res() response: Response
|
||||
): Response {
|
||||
console.log('UserController: Getting current user')
|
||||
console.log(' OAuth2 enabled:', this.useOAuth2)
|
||||
|
||||
// Check OAuth2 session first (if OAuth2 is enabled)
|
||||
if (this.useOAuth2 && session['oauth2_user']) {
|
||||
console.log('UserController: Returning OAuth2 user info')
|
||||
const oauth2User = session['oauth2_user']
|
||||
|
||||
// Check if access token is expired and needs refresh
|
||||
const accessToken = session['oauth2_access_token']
|
||||
const refreshToken = session['oauth2_refresh_token']
|
||||
|
||||
if (accessToken && this.oauth2Service.isTokenExpired(accessToken)) {
|
||||
console.log('UserController: Access token expired')
|
||||
|
||||
if (refreshToken) {
|
||||
console.log('UserController: Attempting token refresh')
|
||||
try {
|
||||
const newTokens = await this.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
|
||||
|
||||
console.log('UserController: Token refresh successful')
|
||||
} catch (error) {
|
||||
console.error('UserController: Token refresh failed:', error)
|
||||
// Return empty object to indicate user needs to re-authenticate
|
||||
return response.json({})
|
||||
}
|
||||
} else {
|
||||
console.log('UserController: No refresh token available, user needs to re-authenticate')
|
||||
return response.json({})
|
||||
}
|
||||
}
|
||||
|
||||
// Return user info in format compatible with frontend
|
||||
return response.json({
|
||||
user_id: oauth2User.sub,
|
||||
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'
|
||||
})
|
||||
}
|
||||
|
||||
// Fall back to OAuth 1.0a
|
||||
console.log('UserController: Checking OAuth 1.0a session')
|
||||
const oauthConfig = session['clientConfig']
|
||||
|
||||
if (!oauthConfig) {
|
||||
console.log('UserController: No authentication session found')
|
||||
return response.json({})
|
||||
}
|
||||
|
||||
console.log('UserController: Returning OAuth 1.0a user info')
|
||||
const version = this.obpClientService.getOBPVersion()
|
||||
return response.json(
|
||||
await this.obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
|
||||
)
|
||||
|
||||
try {
|
||||
const userData = await this.obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
|
||||
return response.json(userData)
|
||||
} catch (error) {
|
||||
console.error('UserController: Failed to get user from OBP API:', error)
|
||||
return response.json({})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user