Skip to content

Commit b2df3a9

Browse files
committed
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
1 parent 86295f8 commit b2df3a9

File tree

2 files changed

+159
-27
lines changed

2 files changed

+159
-27
lines changed

server/app.ts

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ import express, { Application } from 'express'
3434
import { useExpressServer, useContainer } from 'routing-controllers'
3535
import { Container } from 'typedi'
3636
import path from 'path'
37-
import { execSync } from 'child_process';
37+
import { execSync } from 'child_process'
38+
import { OAuth2Service } from './services/OAuth2Service'
3839

3940
const port = 8085
4041
const app: Application = express()
@@ -106,6 +107,46 @@ if (app.get('env') === 'production') {
106107
app.use(session(sessionObject))
107108
useContainer(Container)
108109

110+
// Initialize OAuth2 Service
111+
console.log(`--- OAuth2/OIDC setup -------------------------------------------`)
112+
const useOAuth2 = process.env.VITE_USE_OAUTH2 === 'true'
113+
console.log(`OAuth2/OIDC enabled: ${useOAuth2}`)
114+
115+
if (useOAuth2) {
116+
const wellKnownUrl = process.env.VITE_OBP_OAUTH2_WELL_KNOWN_URL
117+
118+
if (!wellKnownUrl) {
119+
console.warn('VITE_OBP_OAUTH2_WELL_KNOWN_URL not set. OAuth2 will not function.')
120+
} else {
121+
console.log(`OIDC Well-Known URL: ${wellKnownUrl}`)
122+
123+
// Get OAuth2Service from container
124+
const oauth2Service = Container.get(OAuth2Service)
125+
126+
// Initialize OAuth2 service from OIDC discovery document
127+
oauth2Service
128+
.initializeFromWellKnown(wellKnownUrl)
129+
.then(() => {
130+
console.log('OAuth2Service: Initialization successful')
131+
console.log(' Client ID:', process.env.VITE_OBP_OAUTH2_CLIENT_ID || 'NOT SET')
132+
console.log(' Redirect URI:', process.env.VITE_OBP_OAUTH2_REDIRECT_URL || 'NOT SET')
133+
console.log('OAuth2/OIDC ready for authentication')
134+
})
135+
.catch((error) => {
136+
console.error('OAuth2Service: Initialization failed:', error.message)
137+
console.error('OAuth2/OIDC authentication will not be available')
138+
console.error('Please check:')
139+
console.error(' 1. OBP-OIDC server is running')
140+
console.error(' 2. VITE_OBP_OAUTH2_WELL_KNOWN_URL is correct')
141+
console.error(' 3. Network connectivity to OIDC provider')
142+
})
143+
}
144+
} else {
145+
console.log('OAuth2/OIDC is disabled. Using OAuth 1.0a authentication.')
146+
console.log('To enable OAuth2, set VITE_USE_OAUTH2=true in environment')
147+
}
148+
console.log(`-----------------------------------------------------------------`)
149+
109150
const routePrefix = '/api'
110151

111152
const server = useExpressServer(app, {
@@ -121,31 +162,31 @@ console.log(
121162
)
122163

123164
// Get commit ID
124-
export let commitId = '';
165+
export let commitId = ''
125166

126167
try {
127-
// Try to get the commit ID
128-
commitId = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
129-
console.log('Current Commit ID:', commitId);
168+
// Try to get the commit ID
169+
commitId = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim()
170+
console.log('Current Commit ID:', commitId)
130171
} catch (error) {
131-
// Log the error but do not terminate the process
132-
console.error('Warning: Failed to retrieve the commit ID. Proceeding without it.');
133-
console.error('Error details:', error.message);
134-
commitId = 'unknown'; // Assign a fallback value
172+
// Log the error but do not terminate the process
173+
console.error('Warning: Failed to retrieve the commit ID. Proceeding without it.')
174+
console.error('Error details:', error.message)
175+
commitId = 'unknown' // Assign a fallback value
135176
}
136177
// Continue execution with or without a valid commit ID
137-
console.log('Execution continues with commitId:', commitId);
178+
console.log('Execution continues with commitId:', commitId)
138179

139180
// Error Handling to Shut Down the App
140181
server.on('error', (err) => {
141-
redisClient.disconnect();
182+
redisClient.disconnect()
142183
if (err.code === 'EADDRINUSE') {
143-
console.error(`Port ${port} is already in use.`);
144-
process.exit(1);
145-
// Shut down the app
184+
console.error(`Port ${port} is already in use.`)
185+
process.exit(1)
186+
// Shut down the app
146187
} else {
147-
console.error('An error occurred:', err);
188+
console.error('An error occurred:', err)
148189
}
149-
});
190+
})
150191

151192
export default app

server/controllers/UserController.ts

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,35 +31,60 @@ import OBPClientService from '../services/OBPClientService'
3131
import OauthInjectedService from '../services/OauthInjectedService'
3232
import { Service } from 'typedi'
3333
import superagent from 'superagent'
34+
import { OAuth2Service } from '../services/OAuth2Service'
3435

3536
@Service()
3637
@Controller('/user')
3738
export class UserController {
3839
private obpExplorerHome = process.env.VITE_OBP_API_EXPLORER_HOST
40+
private useOAuth2 = process.env.VITE_USE_OAUTH2 === 'true'
41+
3942
constructor(
4043
private obpClientService: OBPClientService,
41-
private oauthInjectedService: OauthInjectedService
44+
private oauthInjectedService: OauthInjectedService,
45+
private oauth2Service: OAuth2Service
4246
) {}
4347
@Get('/logoff')
4448
async logout(
4549
@Session() session: any,
4650
@Req() request: Request,
4751
@Res() response: Response
4852
): Response {
53+
console.log('UserController: Logging out user')
54+
55+
// Clear OAuth 1.0a session data
4956
this.oauthInjectedService.requestTokenKey = undefined
5057
this.oauthInjectedService.requestTokenSecret = undefined
5158
session['clientConfig'] = undefined
5259

53-
if (request.query.redirect) {
54-
response.redirect(request.query.redirect as string)
55-
} else {
56-
if(!this.obpExplorerHome) {
57-
console.error(`VITE_OBP_API_EXPLORER_HOST: ${this.obpExplorerHome}`)
60+
// Clear OAuth2 session data
61+
delete session['oauth2_access_token']
62+
delete session['oauth2_refresh_token']
63+
delete session['oauth2_id_token']
64+
delete session['oauth2_token_type']
65+
delete session['oauth2_expires_in']
66+
delete session['oauth2_token_timestamp']
67+
delete session['oauth2_user_info']
68+
delete session['oauth2_user']
69+
70+
// Destroy the session completely
71+
session.destroy((err: any) => {
72+
if (err) {
73+
console.error('UserController: Error destroying session:', err)
74+
} else {
75+
console.log('UserController: Session destroyed successfully')
5876
}
59-
response.redirect(this.obpExplorerHome)
77+
})
78+
79+
const redirectPage = (request.query.redirect as string) || this.obpExplorerHome || '/'
80+
81+
if (!this.obpExplorerHome) {
82+
console.error(`VITE_OBP_API_EXPLORER_HOST: ${this.obpExplorerHome}`)
6083
}
6184

62-
85+
console.log('UserController: Redirecting to:', redirectPage)
86+
response.redirect(redirectPage)
87+
6388
return response
6489
}
6590

@@ -69,10 +94,76 @@ export class UserController {
6994
@Req() request: Request,
7095
@Res() response: Response
7196
): Response {
97+
console.log('UserController: Getting current user')
98+
console.log(' OAuth2 enabled:', this.useOAuth2)
99+
100+
// Check OAuth2 session first (if OAuth2 is enabled)
101+
if (this.useOAuth2 && session['oauth2_user']) {
102+
console.log('UserController: Returning OAuth2 user info')
103+
const oauth2User = session['oauth2_user']
104+
105+
// Check if access token is expired and needs refresh
106+
const accessToken = session['oauth2_access_token']
107+
const refreshToken = session['oauth2_refresh_token']
108+
109+
if (accessToken && this.oauth2Service.isTokenExpired(accessToken)) {
110+
console.log('UserController: Access token expired')
111+
112+
if (refreshToken) {
113+
console.log('UserController: Attempting token refresh')
114+
try {
115+
const newTokens = await this.oauth2Service.refreshAccessToken(refreshToken)
116+
117+
// Update session with new tokens
118+
session['oauth2_access_token'] = newTokens.accessToken
119+
session['oauth2_refresh_token'] = newTokens.refreshToken || refreshToken
120+
session['oauth2_id_token'] = newTokens.idToken
121+
session['oauth2_token_timestamp'] = Date.now()
122+
session['oauth2_expires_in'] = newTokens.expiresIn
123+
124+
console.log('UserController: Token refresh successful')
125+
} catch (error) {
126+
console.error('UserController: Token refresh failed:', error)
127+
// Return empty object to indicate user needs to re-authenticate
128+
return response.json({})
129+
}
130+
} else {
131+
console.log('UserController: No refresh token available, user needs to re-authenticate')
132+
return response.json({})
133+
}
134+
}
135+
136+
// Return user info in format compatible with frontend
137+
return response.json({
138+
user_id: oauth2User.sub,
139+
username: oauth2User.username,
140+
email: oauth2User.email,
141+
email_verified: oauth2User.email_verified,
142+
name: oauth2User.name,
143+
given_name: oauth2User.given_name,
144+
family_name: oauth2User.family_name,
145+
provider: oauth2User.provider || 'oauth2'
146+
})
147+
}
148+
149+
// Fall back to OAuth 1.0a
150+
console.log('UserController: Checking OAuth 1.0a session')
72151
const oauthConfig = session['clientConfig']
152+
153+
if (!oauthConfig) {
154+
console.log('UserController: No authentication session found')
155+
return response.json({})
156+
}
157+
158+
console.log('UserController: Returning OAuth 1.0a user info')
73159
const version = this.obpClientService.getOBPVersion()
74-
return response.json(
75-
await this.obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
76-
)
160+
161+
try {
162+
const userData = await this.obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
163+
return response.json(userData)
164+
} catch (error) {
165+
console.error('UserController: Failed to get user from OBP API:', error)
166+
return response.json({})
167+
}
77168
}
78169
}

0 commit comments

Comments
 (0)