diff --git a/server/app.ts b/server/app.ts index a376a21..7ac812f 100644 --- a/server/app.ts +++ b/server/app.ts @@ -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 diff --git a/server/controllers/UserController.ts b/server/controllers/UserController.ts index 4664b61..80ab3e4 100644 --- a/server/controllers/UserController.ts +++ b/server/controllers/UserController.ts @@ -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({}) + } } }