@@ -30,6 +30,7 @@ import { VerifyUpdateEmailDto } from './dto/verify-update-email.dto';
3030import { MobileGoogleAuthDto } from './dto/mobile-google-auth.dto' ;
3131import { MobileGitHubAuthDto } from './dto/mobile-github-auth.dto' ;
3232import { ForgetPasswordDto } from './dto/forget-password.dto' ;
33+ import { ExchangeTokenDto } from './dto/exchange-token.dto' ;
3334import {
3435 ApiBearerAuth ,
3536 ApiBody ,
@@ -63,6 +64,7 @@ import {
6364 change_password_swagger ,
6465 check_identifier_swagger ,
6566 confirm_password_swagger ,
67+ exchange_token_swagger ,
6668 facebook_callback_swagger ,
6769 facebook_oauth_swagger ,
6870 forget_password_swagger ,
@@ -281,6 +283,38 @@ export class AuthController {
281283 return { access_token } ;
282284 }
283285
286+ @ApiOperation ( exchange_token_swagger . operation )
287+ @ApiBody ( { type : ExchangeTokenDto } )
288+ @ApiOkResponse ( exchange_token_swagger . responses . completion_success )
289+ @ApiUnauthorizedErrorResponse ( ERROR_MESSAGES . INVALID_OR_EXPIRED_TOKEN )
290+ @ResponseMessage ( SUCCESS_MESSAGES . TOKEN_EXCHANGE_SUCCESS )
291+ @Post ( 'exchange-token' )
292+ async exchangeToken ( @Body ( ) body : ExchangeTokenDto , @Res ( ) res : Response ) {
293+ const { exchange_token } = body ;
294+ const payload = await this . auth_service . validateExchangeToken ( exchange_token ) ;
295+
296+ if ( payload . type === 'auth' ) {
297+ if ( ! payload . user_id ) {
298+ throw new BadRequestException ( ERROR_MESSAGES . INVALID_OR_EXPIRED_TOKEN ) ;
299+ }
300+
301+ const { access_token, refresh_token } = await this . auth_service . generateTokens (
302+ payload . user_id
303+ ) ;
304+ this . httpOnlyRefreshToken ( res , refresh_token ) ;
305+
306+ return res . json ( {
307+ type : 'auth' ,
308+ access_token,
309+ } ) ;
310+ } else {
311+ return res . json ( {
312+ type : 'completion' ,
313+ session_token : payload . session_token ,
314+ } ) ;
315+ }
316+ }
317+
284318 @ApiOperation ( captcha_swagger . operation )
285319 @ApiResponse ( captcha_swagger . responses . success )
286320 @ResponseMessage ( 'ReCAPTCHA site key retrieved successfully' )
@@ -403,8 +437,13 @@ export class AuthController {
403437 // if the user doesn't have a record for that email in DB, we will need to redirect the user to complete his data
404438 if ( req . user ?. needs_completion ) {
405439 const session_token = await this . auth_service . createOAuthSession ( req . user . user ) ;
440+ const exchange_token = await this . auth_service . createExchangeToken ( {
441+ session_token,
442+ type : 'completion' ,
443+ } ) ;
444+
406445 return res . redirect (
407- `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/oauth-complete?session =${ encodeURIComponent ( session_token ) } &provider=google`
446+ `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/oauth-complete?exchange_token =${ encodeURIComponent ( exchange_token ) } &provider=google`
408447 ) ;
409448 }
410449
@@ -417,15 +456,14 @@ export class AuthController {
417456 }
418457
419458 // Normal OAuth flow for existing users
420- const { access_token, refresh_token } = await this . auth_service . generateTokens (
421- req . user . id
422- ) ;
423-
424- // Set refresh token in HTTP-only cookie
425- this . httpOnlyRefreshToken ( res , refresh_token ) ;
426-
427- // Redirect to frontend with access token
428- const frontend_url = `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/success?token=${ encodeURIComponent ( access_token ) } ` ;
459+ // Create secure exchange token with user_id (tokens will be generated on exchange)
460+ const exchange_token = await this . auth_service . createExchangeToken ( {
461+ user_id : req . user . id ,
462+ type : 'auth' ,
463+ } ) ;
464+
465+ // Redirect to frontend with exchange token
466+ const frontend_url = `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/success?exchange_token=${ encodeURIComponent ( exchange_token ) } &provider=google` ;
429467 return res . redirect ( frontend_url ) ;
430468 } catch ( error ) {
431469 console . log ( 'Google callback error:' , error ) ;
@@ -456,8 +494,13 @@ export class AuthController {
456494 // if the user doesn't have a record for that email in DB, we will need to redirect the user to complete his data
457495 if ( req . user ?. needs_completion ) {
458496 const session_token = await this . auth_service . createOAuthSession ( req . user . user ) ;
497+ const exchange_token = await this . auth_service . createExchangeToken ( {
498+ session_token,
499+ type : 'completion' ,
500+ } ) ;
501+
459502 return res . redirect (
460- `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/oauth-complete?session =${ encodeURIComponent ( session_token ) } &provider=facebook`
503+ `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/oauth-complete?exchange_token =${ encodeURIComponent ( exchange_token ) } &provider=facebook`
461504 ) ;
462505 }
463506
@@ -470,15 +513,13 @@ export class AuthController {
470513 }
471514
472515 // Normal OAuth flow for existing users
473- const { access_token, refresh_token } = await this . auth_service . generateTokens (
474- req . user . id
475- ) ;
516+ const exchange_token = await this . auth_service . createExchangeToken ( {
517+ user_id : req . user . id ,
518+ type : 'auth' ,
519+ } ) ;
476520
477- // Set refresh token in HTTP-only cookie
478- this . httpOnlyRefreshToken ( res , refresh_token ) ;
479-
480- // Redirect to frontend with access token
481- const frontend_url = `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/success?token=${ encodeURIComponent ( access_token ) } ` ;
521+ // Redirect to frontend with exchange token
522+ const frontend_url = `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/success?exchange_token=${ encodeURIComponent ( exchange_token ) } &provider=facebook` ;
482523 return res . redirect ( frontend_url ) ;
483524 } catch ( error ) {
484525 console . log ( 'Facebook callback error:' , error ) ;
@@ -549,8 +590,13 @@ export class AuthController {
549590 // if the user doesn't have a record for that email in DB, we will need to redirect the user to complete his data
550591 if ( req . user ?. needs_completion ) {
551592 const session_token = await this . auth_service . createOAuthSession ( req . user . user ) ;
593+ const exchange_token = await this . auth_service . createExchangeToken ( {
594+ session_token,
595+ type : 'completion' ,
596+ } ) ;
597+
552598 return res . redirect (
553- `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/oauth-complete?session =${ encodeURIComponent ( session_token ) } &provider=github`
599+ `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/oauth-complete?exchange_token =${ encodeURIComponent ( exchange_token ) } &provider=github`
554600 ) ;
555601 }
556602
@@ -563,15 +609,13 @@ export class AuthController {
563609 }
564610
565611 // Normal OAuth flow for existing users
566- const { access_token, refresh_token } = await this . auth_service . generateTokens (
567- req . user . id
568- ) ;
569-
570- // Set refresh token in HTTP-only cookie
571- this . httpOnlyRefreshToken ( res , refresh_token ) ;
612+ const exchange_token = await this . auth_service . createExchangeToken ( {
613+ user_id : req . user . id ,
614+ type : 'auth' ,
615+ } ) ;
572616
573- // Redirect to frontend with access token
574- const frontend_url = `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/success?token =${ encodeURIComponent ( access_token ) } ` ;
617+ // Redirect to frontend with exchange token
618+ const frontend_url = `${ process . env . FRONTEND_URL || 'http://localhost:3001' } /auth/success?exchange_token =${ encodeURIComponent ( exchange_token ) } &provider=github ` ;
575619 return res . redirect ( frontend_url ) ;
576620 } catch ( error ) {
577621 console . log ( 'Github callback error:' , error ) ;
0 commit comments