Skip to content

Commit 5bea5a2

Browse files
committed
can login again
1 parent c4bd93c commit 5bea5a2

5 files changed

Lines changed: 369 additions & 385 deletions

File tree

components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ declare module 'vue' {
2222
ElCollapse: typeof import('element-plus/es')['ElCollapse']
2323
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
2424
ElContainer: typeof import('element-plus/es')['ElContainer']
25+
ElDialog: typeof import('element-plus/es')['ElDialog']
2526
ElDivider: typeof import('element-plus/es')['ElDivider']
2627
ElDropdown: typeof import('element-plus/es')['ElDropdown']
2728
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']

server/app.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,8 @@ import { OAuth2CallbackController } from './controllers/OAuth2CallbackController
5050
import { OAuth2ConnectController } from './controllers/OAuth2ConnectController.js'
5151
import { OAuth2ProvidersController } from './controllers/OAuth2ProvidersController.js'
5252

53-
// Import middlewares
54-
import OAuth2AuthorizationMiddleware from './middlewares/OAuth2AuthorizationMiddleware.js'
55-
import OAuth2CallbackMiddleware from './middlewares/OAuth2CallbackMiddleware.js'
53+
// Import OAuth2 routes (plain Express, not routing-controllers)
54+
import oauth2Routes from './routes/oauth2.js'
5655

5756
// ES module equivalent of __dirname
5857
const __filename = fileURLToPath(import.meta.url)
@@ -226,18 +225,14 @@ let instance: any
226225

227226
const routePrefix = '/api'
228227

228+
// Register OAuth2 routes BEFORE routing-controllers (plain Express)
229+
app.use(routePrefix, oauth2Routes)
230+
console.log('OAuth2 routes registered (plain Express)')
231+
229232
const server = useExpressServer(app, {
230233
routePrefix: routePrefix,
231-
controllers: [
232-
OpeyController,
233-
OBPController,
234-
StatusController,
235-
UserController,
236-
OAuth2CallbackController,
237-
OAuth2ConnectController,
238-
OAuth2ProvidersController
239-
],
240-
middlewares: [OAuth2AuthorizationMiddleware, OAuth2CallbackMiddleware]
234+
controllers: [OpeyController, OBPController, StatusController, UserController],
235+
middlewares: []
241236
})
242237

243238
instance = server.listen(port)

server/controllers/OAuth2CallbackController.ts

Lines changed: 24 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,25 @@
2525
*
2626
*/
2727

28-
import { Controller, Req, Res, Get, QueryParam } from 'routing-controllers'
28+
import { Controller, Req, Res, Get, UseBefore } from 'routing-controllers'
2929
import type { Request, Response } from 'express'
30-
import { Service, Container } from 'typedi'
31-
import { OAuth2Service } from '../services/OAuth2Service.js'
32-
import { OAuth2ProviderManager } from '../services/OAuth2ProviderManager.js'
33-
import type { UserInfo } from '../types/oauth2.js'
30+
import { Service } from 'typedi'
31+
import OAuth2CallbackMiddleware from '../middlewares/OAuth2CallbackMiddleware.js'
3432

3533
/**
36-
* OAuth2 Callback Controller (Multi-Provider)
34+
* OAuth2 Callback Controller
3735
*
38-
* Handles the OAuth2/OIDC callback from any configured identity provider.
36+
* Handles the OAuth2/OIDC callback from the identity provider.
3937
* This controller receives the authorization code and state parameter
4038
* after the user authenticates with the OIDC provider.
4139
*
42-
* This controller handles:
40+
* The OAuth2CallbackMiddleware handles:
4341
* - State validation (CSRF protection)
4442
* - Authorization code exchange for tokens
4543
* - User info retrieval
4644
* - Session storage
4745
* - Redirect to original page
4846
*
49-
* Supports both multi-provider mode (retrieves provider from session) and
50-
* legacy single-provider mode (uses existing OAuth2Service).
51-
*
5247
* Endpoint: GET /oauth2/callback
5348
*
5449
* Query Parameters (from OIDC provider):
@@ -57,23 +52,21 @@ import type { UserInfo } from '../types/oauth2.js'
5752
* - error (optional): Error code if authentication failed
5853
* - error_description (optional): Human-readable error description
5954
*
60-
* Multi-Provider Flow:
55+
* Flow:
6156
* OIDC Provider → /oauth2/callback?code=XXX&state=YYY
62-
* → Retrieve provider from session → Use correct OAuth2 client
63-
* → Exchange code for tokens → Original Page (with authenticated session)
57+
* → OAuth2CallbackMiddleware → Original Page (with authenticated session)
6458
*
6559
* Success Flow:
6660
* 1. Validate state parameter
67-
* 2. Retrieve provider from session (or use legacy service)
68-
* 3. Exchange authorization code for tokens (access, refresh, ID)
69-
* 4. Fetch user information from UserInfo endpoint
70-
* 5. Store tokens, provider name, and user data in session
71-
* 6. Redirect to original page or home
61+
* 2. Exchange authorization code for tokens (access, refresh, ID)
62+
* 3. Fetch user information from UserInfo endpoint
63+
* 4. Store tokens and user data in session
64+
* 5. Redirect to original page or home
7265
*
7366
* Error Flow:
7467
* 1. Parse error from query parameters
75-
* 2. Log error details
76-
* 3. Redirect to home with error parameter
68+
* 2. Display user-friendly error page
69+
* 3. Allow user to retry authentication
7770
*
7871
* @example
7972
* // Successful callback URL from OIDC provider
@@ -84,216 +77,22 @@ import type { UserInfo } from '../types/oauth2.js'
8477
*/
8578
@Service()
8679
@Controller()
80+
@UseBefore(OAuth2CallbackMiddleware)
8781
export class OAuth2CallbackController {
88-
private providerManager: OAuth2ProviderManager
89-
private legacyOAuth2Service: OAuth2Service
90-
91-
constructor() {
92-
this.providerManager = Container.get(OAuth2ProviderManager)
93-
this.legacyOAuth2Service = Container.get(OAuth2Service)
94-
}
95-
9682
/**
9783
* Handle OAuth2/OIDC callback
9884
*
99-
* Processes the callback from any configured OIDC provider.
100-
* Supports both multi-provider mode and legacy single-provider mode.
85+
* The actual logic is handled by OAuth2CallbackMiddleware.
86+
* This method exists only as the routing endpoint definition.
10187
*
102-
* @param code - Authorization code from OIDC provider
103-
* @param state - State parameter for CSRF validation
104-
* @param error - Error code if authentication failed
105-
* @param errorDescription - Human-readable error description
106-
* @param request - Express request object
107-
* @param response - Express response object
108-
* @returns Response with redirect to original page or error page
88+
* @param {Request} request - Express request object with query params (code, state)
89+
* @param {Response} response - Express response object (redirected by middleware)
90+
* @returns {Response} Response object (handled by middleware)
10991
*/
11092
@Get('/oauth2/callback')
111-
async callback(
112-
@QueryParam('code') code: string,
113-
@QueryParam('state') state: string,
114-
@QueryParam('error') error: string,
115-
@QueryParam('error_description') errorDescription: string,
116-
@Req() request: Request,
117-
@Res() response: Response
118-
): Promise<Response> {
119-
console.log('OAuth2CallbackController: Processing OAuth2 callback')
120-
121-
const session = request.session as any
122-
123-
// Handle error from provider
124-
if (error) {
125-
console.error(`OAuth2CallbackController: Error from provider: ${error}`)
126-
console.error(`OAuth2CallbackController: Description: ${errorDescription || 'N/A'}`)
127-
return response.redirect(`/?oauth2_error=${encodeURIComponent(error)}`)
128-
}
129-
130-
// Validate required parameters
131-
if (!code) {
132-
console.error('OAuth2CallbackController: Missing authorization code')
133-
return response.redirect('/?oauth2_error=missing_code')
134-
}
135-
136-
if (!state) {
137-
console.error('OAuth2CallbackController: Missing state parameter')
138-
return response.redirect('/?oauth2_error=missing_state')
139-
}
140-
141-
// Validate state (CSRF protection)
142-
const storedState = session.oauth2_state
143-
if (!storedState || storedState !== state) {
144-
console.error('OAuth2CallbackController: State mismatch (CSRF protection)')
145-
console.error(` Expected: ${storedState}`)
146-
console.error(` Received: ${state}`)
147-
return response.redirect('/?oauth2_error=invalid_state')
148-
}
149-
150-
// Get code verifier from session (PKCE)
151-
const codeVerifier = session.oauth2_code_verifier
152-
if (!codeVerifier) {
153-
console.error('OAuth2CallbackController: Code verifier not found in session')
154-
return response.redirect('/?oauth2_error=missing_verifier')
155-
}
156-
157-
// Check if multi-provider mode (provider stored in session)
158-
const provider = session.oauth2_provider
159-
160-
try {
161-
if (provider) {
162-
// Multi-provider mode
163-
await this.handleMultiProviderCallback(session, code, codeVerifier, provider)
164-
} else {
165-
// Legacy single-provider mode
166-
await this.handleLegacyCallback(session, code, codeVerifier)
167-
}
168-
169-
// Clean up temporary session data
170-
delete session.oauth2_code_verifier
171-
delete session.oauth2_state
172-
173-
// Redirect to original page
174-
const redirectUrl = session.oauth2_redirect_page || '/'
175-
delete session.oauth2_redirect_page
176-
177-
console.log(
178-
`OAuth2CallbackController: Authentication successful, redirecting to: ${redirectUrl}`
179-
)
180-
return response.redirect(redirectUrl)
181-
} catch (error) {
182-
console.error('OAuth2CallbackController: Token exchange failed:', error)
183-
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
184-
return response.redirect(
185-
`/?oauth2_error=token_exchange_failed&details=${encodeURIComponent(errorMessage)}`
186-
)
187-
}
188-
}
189-
190-
/**
191-
* Handle multi-provider callback
192-
*/
193-
private async handleMultiProviderCallback(
194-
session: any,
195-
code: string,
196-
codeVerifier: string,
197-
provider: string
198-
): Promise<void> {
199-
console.log(`OAuth2CallbackController: Multi-provider mode - ${provider}`)
200-
201-
const client = this.providerManager.getProvider(provider)
202-
if (!client) {
203-
throw new Error(`Provider not found: ${provider}`)
204-
}
205-
206-
// Exchange code for tokens
207-
console.log(`OAuth2CallbackController: Exchanging authorization code for tokens`)
208-
const tokens = await client.exchangeAuthorizationCode(code, codeVerifier)
209-
210-
// Store tokens in session
211-
session.oauth2_access_token = tokens.accessToken
212-
session.oauth2_refresh_token = tokens.refreshToken
213-
session.oauth2_id_token = tokens.idToken
214-
session.oauth2_provider = provider
215-
216-
console.log(`OAuth2CallbackController: Tokens received and stored`)
217-
218-
// Fetch user info
219-
console.log(`OAuth2CallbackController: Fetching user info`)
220-
const userInfo = await this.fetchUserInfo(client, tokens.accessToken)
221-
222-
// Store user in session
223-
session.user = {
224-
username: userInfo.preferred_username || userInfo.email || userInfo.sub,
225-
email: userInfo.email,
226-
name: userInfo.name,
227-
provider: provider,
228-
sub: userInfo.sub
229-
}
230-
231-
console.log(
232-
`OAuth2CallbackController: User authenticated via ${provider}: ${session.user.username}`
233-
)
234-
}
235-
236-
/**
237-
* Handle legacy single-provider callback
238-
*/
239-
private async handleLegacyCallback(
240-
session: any,
241-
code: string,
242-
codeVerifier: string
243-
): Promise<void> {
244-
console.log('OAuth2CallbackController: Legacy single-provider mode')
245-
246-
// Exchange code for tokens using legacy service
247-
console.log(`OAuth2CallbackController: Exchanging authorization code for tokens`)
248-
const tokens = await this.legacyOAuth2Service.exchangeCodeForTokens(code, codeVerifier)
249-
250-
// Store tokens in session
251-
session.oauth2_access_token = tokens.accessToken
252-
session.oauth2_refresh_token = tokens.refreshToken
253-
session.oauth2_id_token = tokens.idToken
254-
255-
console.log(`OAuth2CallbackController: Tokens received and stored`)
256-
257-
// Fetch user info
258-
console.log(`OAuth2CallbackController: Fetching user info`)
259-
const userInfo = await this.legacyOAuth2Service.getUserInfo(tokens.accessToken)
260-
261-
// Store user in session
262-
session.user = {
263-
username: userInfo.preferred_username || userInfo.email || userInfo.sub,
264-
email: userInfo.email,
265-
name: userInfo.name,
266-
sub: userInfo.sub
267-
}
268-
269-
console.log(`OAuth2CallbackController: User authenticated (legacy): ${session.user.username}`)
270-
}
271-
272-
/**
273-
* Fetch user info from UserInfo endpoint
274-
*/
275-
private async fetchUserInfo(client: any, accessToken: string): Promise<UserInfo> {
276-
const userInfoEndpoint = client.getUserInfoEndpoint()
277-
278-
console.log(`OAuth2CallbackController: Calling UserInfo endpoint: ${userInfoEndpoint}`)
279-
280-
const response = await fetch(userInfoEndpoint, {
281-
headers: {
282-
Authorization: `Bearer ${accessToken}`,
283-
Accept: 'application/json'
284-
}
285-
})
286-
287-
if (!response.ok) {
288-
const errorText = await response.text()
289-
throw new Error(
290-
`UserInfo request failed: ${response.status} ${response.statusText} - ${errorText}`
291-
)
292-
}
293-
294-
const userInfo = await response.json()
295-
console.log(`OAuth2CallbackController: UserInfo retrieved: ${userInfo.sub}`)
296-
297-
return userInfo as UserInfo
93+
callback(@Req() request: Request, @Res() response: Response): Response {
94+
// The middleware handles all the logic and redirects the user
95+
// This method should never actually execute
96+
return response
29897
}
29998
}

0 commit comments

Comments
 (0)