2525 *
2626 */
2727
28- import { Controller , Req , Res , Get , QueryParam } from 'routing-controllers'
28+ import { Controller , Req , Res , Get , UseBefore } from 'routing-controllers'
2929import 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 )
8781export 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