From 56f53410f9f6a6e767d0e566530b4fd0d00cee22 Mon Sep 17 00:00:00 2001 From: liverforpresent Date: Mon, 20 Oct 2025 02:44:56 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20redirection=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authorize-oauth/authorize-oauth.use-case.ts | 7 +++++-- .../authorize-oauth/dto/authorize-oauth.request.dto.ts | 5 ++++- .../core/infrastructure/oauth/base-oauth.provider.ts | 2 +- src/shared/core/infrastructure/oauth/kakao.provider.ts | 5 ++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/auth/application/authorize-oauth/authorize-oauth.use-case.ts b/src/auth/application/authorize-oauth/authorize-oauth.use-case.ts index 19199ca..2bd6828 100644 --- a/src/auth/application/authorize-oauth/authorize-oauth.use-case.ts +++ b/src/auth/application/authorize-oauth/authorize-oauth.use-case.ts @@ -8,9 +8,12 @@ export class AuthorizeOAuthUseCase { constructor(private readonly oAuthProviderFactory: OAuthProviderFactory) {} execute(requestDto: AuthorizeOAuthRequestDto): AuthorizeOAuthResponseDto { - const { oAuthProviderType } = requestDto; + const { oAuthProviderType, redirectUrl } = requestDto; const provider = this.oAuthProviderFactory.getProvider(oAuthProviderType); - const authUrl = provider.getAuthorizationUrl(); + + const encodedState = encodeURIComponent(redirectUrl || ''); + + const authUrl = provider.getAuthorizationUrl(encodedState); return { authUrl }; } diff --git a/src/auth/application/authorize-oauth/dto/authorize-oauth.request.dto.ts b/src/auth/application/authorize-oauth/dto/authorize-oauth.request.dto.ts index be78566..028223b 100644 --- a/src/auth/application/authorize-oauth/dto/authorize-oauth.request.dto.ts +++ b/src/auth/application/authorize-oauth/dto/authorize-oauth.request.dto.ts @@ -1,8 +1,11 @@ -import { IsEnum, IsNotEmpty } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; import { OAuthProviderType } from 'src/auth/domain/value-object/oauth-provider.enum'; export class AuthorizeOAuthRequestDto { @IsEnum(OAuthProviderType) @IsNotEmpty() oAuthProviderType: OAuthProviderType; + + @IsString() + redirectUrl?: string; } diff --git a/src/shared/core/infrastructure/oauth/base-oauth.provider.ts b/src/shared/core/infrastructure/oauth/base-oauth.provider.ts index 301f96d..649d4dd 100644 --- a/src/shared/core/infrastructure/oauth/base-oauth.provider.ts +++ b/src/shared/core/infrastructure/oauth/base-oauth.provider.ts @@ -3,7 +3,7 @@ import { OAuthProviderType } from 'src/auth/domain/value-object/oauth-provider.e export interface BaseOAuthProvider { getToken(code: string): Promise; getUserInfo(token: string): Promise; - getAuthorizationUrl(): string; + getAuthorizationUrl(state?: string): string; unlinkAccount(userId: string): Promise; } diff --git a/src/shared/core/infrastructure/oauth/kakao.provider.ts b/src/shared/core/infrastructure/oauth/kakao.provider.ts index 3429ac2..d2de518 100644 --- a/src/shared/core/infrastructure/oauth/kakao.provider.ts +++ b/src/shared/core/infrastructure/oauth/kakao.provider.ts @@ -71,7 +71,7 @@ export class KakaoOAuthProvider implements BaseOAuthProvider { return res.data.access_token; } - getAuthorizationUrl(): string { + getAuthorizationUrl(state?: string): string { const authorizeUrl = 'https://kauth.kakao.com/oauth/authorize'; const clientId = this.configService.get('kakao.clientId'); const redirectUri = this.configService.get('kakao.redirectUri'); @@ -84,6 +84,9 @@ export class KakaoOAuthProvider implements BaseOAuthProvider { params.append('response_type', 'code'); params.append('client_id', clientId); params.append('redirect_uri', redirectUri); + if (state) { + params.append('state', state); + } return `${authorizeUrl}?${params.toString()}`; } From 0d8b1c096aff7d8db30a0a034f4d16b4d6937c0f Mon Sep 17 00:00:00 2001 From: liverforpresent Date: Mon, 20 Oct 2025 02:45:29 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9C=A0=EC=A0=80=20id=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/oauth-login/dto/oauth-login.response.dto.ts | 4 ++++ src/auth/application/oauth-login/oauth-login.handler.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/auth/application/oauth-login/dto/oauth-login.response.dto.ts b/src/auth/application/oauth-login/dto/oauth-login.response.dto.ts index d96259c..33fe864 100644 --- a/src/auth/application/oauth-login/dto/oauth-login.response.dto.ts +++ b/src/auth/application/oauth-login/dto/oauth-login.response.dto.ts @@ -8,4 +8,8 @@ export class OAuthLoginResponseDto { @IsString() @IsNotEmpty() refreshToken: string; + + @IsString() + @IsNotEmpty() + userId: string; } diff --git a/src/auth/application/oauth-login/oauth-login.handler.ts b/src/auth/application/oauth-login/oauth-login.handler.ts index bd5fc74..6380eec 100644 --- a/src/auth/application/oauth-login/oauth-login.handler.ts +++ b/src/auth/application/oauth-login/oauth-login.handler.ts @@ -34,7 +34,7 @@ export class OAuthLoginUseCase { const auth = await this.findOrCreateAuth(oauthId, provider, email); const { accessToken, refreshToken } = await this.generateAndSaveTokens(auth); - return { accessToken, refreshToken }; + return { accessToken, refreshToken, userId: auth.userId.value }; } // 소셜로그인 유저저 정보 가져오기 From b1227cc5a9e3518c8e80a6dde88c6cc083436cda Mon Sep 17 00:00:00 2001 From: liverforpresent Date: Mon, 20 Oct 2025 02:45:58 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=EC=BD=9C=EB=B0=B1=ED=95=A8=EC=88=98?= =?UTF-8?q?=20http=20=EB=B0=A9=EC=8B=9D=20post=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/presentation/auth.controller.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/auth/presentation/auth.controller.ts b/src/auth/presentation/auth.controller.ts index 5071a18..f79633e 100644 --- a/src/auth/presentation/auth.controller.ts +++ b/src/auth/presentation/auth.controller.ts @@ -25,20 +25,25 @@ export class AuthController { @Get('oauth/authorization') @AuthDocs('oauthAuthorization') - authorizeOAuth(@Res() res: Response) { - const { authUrl } = this.authorizeOAuthUseCase.execute({ oAuthProviderType: OAuthProviderType.KAKAO }); + authorizeOAuth(@Query('redirectUrl') redirectUrl?: string) { + const { authUrl } = this.authorizeOAuthUseCase.execute({ oAuthProviderType: OAuthProviderType.KAKAO, redirectUrl }); - res.redirect(authUrl); + return authUrl; } - @Get('login/oauth/callback') + @Post('login/oauth/callback') @AuthDocs('oauthCallback') - async oAuthLogin(@Res() res: Response, @Query('code') code: string, @Query('error') error?: string) { + async oAuthLogin( + @Res() res: Response, + @Query('code') code: string, + @Query('error') error?: string, + //@Query('state') state?: string, + ) { if (error && error === 'access_denied') { - return res.redirect(`${this.configService.getOrThrow('frontend.url')}/login?error=cancelled`); + return res.status(HttpStatus.UNAUTHORIZED).json({ message: 'Access denied' }); } - const { accessToken, refreshToken } = await this.oAuthLoginUseCase.execute({ + const { accessToken, refreshToken, userId } = await this.oAuthLoginUseCase.execute({ oAuthProviderType: OAuthProviderType.KAKAO, code, }); @@ -46,9 +51,7 @@ export class AuthController { res.cookie('accessToken', accessToken, accessTokenCookieOptions); res.cookie('refreshToken', refreshToken, refreshTokenCookieOptions); - res.redirect( - `${this.configService.getOrThrow('frontend.url')}/${this.configService.getOrThrow('frontend.loginRedirectPath')}`, - ); + res.json({ userId }); } @Get('refresh') From 356adb412c76ecb2b803abb18604cbeabb1c9808 Mon Sep 17 00:00:00 2001 From: liverforpresent Date: Wed, 22 Oct 2025 00:45:02 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20DB=EC=97=90=EC=84=9C=20auth=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=A0=84=EC=9A=A9=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/domain/entity/auth.ts | 4 ++++ src/auth/infrastructure/mapper/auth.mapper.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/auth/domain/entity/auth.ts b/src/auth/domain/entity/auth.ts index 6f46780..ad54c97 100644 --- a/src/auth/domain/entity/auth.ts +++ b/src/auth/domain/entity/auth.ts @@ -27,6 +27,10 @@ export class Auth extends AggregateRoot { return auth; } + public static of(props: AuthProps): Auth { + return new Auth(props); + } + public validate(): void { if (!this.props.oauthId) { throw new CustomException(CustomExceptionCode.AUTH_OAUTH_ID_EMPTY); diff --git a/src/auth/infrastructure/mapper/auth.mapper.ts b/src/auth/infrastructure/mapper/auth.mapper.ts index 5647274..82637be 100644 --- a/src/auth/infrastructure/mapper/auth.mapper.ts +++ b/src/auth/infrastructure/mapper/auth.mapper.ts @@ -6,7 +6,7 @@ import { UserEntity } from 'src/user/command/infrastructure/user.entity'; export class AuthMapper { static toDomain(entity: AuthEntity): Auth { - return Auth.create({ + return Auth.of({ id: Identifier.from(entity.id), createdAt: entity.createdAt, updatedAt: entity.updatedAt, From faf33037fe3ffbf7d76c6635565122d0d49fd27c Mon Sep 17 00:00:00 2001 From: liverforpresent Date: Fri, 24 Oct 2025 22:05:17 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=A0=ED=8A=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20redirectPath=20=EC=A7=80=EC=A0=95=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth-login/dto/oauth-login.response.dto.ts | 3 +++ .../oauth-login/oauth-login.command.ts | 1 + .../oauth-login/oauth-login.handler.ts | 12 +++++++++++- src/auth/presentation/auth.controller.ts | 16 ++++++++++------ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/auth/application/oauth-login/dto/oauth-login.response.dto.ts b/src/auth/application/oauth-login/dto/oauth-login.response.dto.ts index 33fe864..baa225d 100644 --- a/src/auth/application/oauth-login/dto/oauth-login.response.dto.ts +++ b/src/auth/application/oauth-login/dto/oauth-login.response.dto.ts @@ -12,4 +12,7 @@ export class OAuthLoginResponseDto { @IsString() @IsNotEmpty() userId: string; + + @IsString() + redirectUrl: string; } diff --git a/src/auth/application/oauth-login/oauth-login.command.ts b/src/auth/application/oauth-login/oauth-login.command.ts index 1b5cc00..6a05f95 100644 --- a/src/auth/application/oauth-login/oauth-login.command.ts +++ b/src/auth/application/oauth-login/oauth-login.command.ts @@ -5,5 +5,6 @@ export class OAuthLoginCommand implements ICommand { constructor( public readonly oAuthProviderType: OAuthProviderType, public readonly code: string, + public readonly state?: string, ) {} } diff --git a/src/auth/application/oauth-login/oauth-login.handler.ts b/src/auth/application/oauth-login/oauth-login.handler.ts index 6380eec..76c5a70 100644 --- a/src/auth/application/oauth-login/oauth-login.handler.ts +++ b/src/auth/application/oauth-login/oauth-login.handler.ts @@ -33,8 +33,9 @@ export class OAuthLoginUseCase { const { oauthId, provider, email } = await this.getOAuthUserInfo(oAuthProviderType, code); const auth = await this.findOrCreateAuth(oauthId, provider, email); const { accessToken, refreshToken } = await this.generateAndSaveTokens(auth); + const redirectUrl = this.decodeRedirectUrl(command.state); - return { accessToken, refreshToken, userId: auth.userId.value }; + return { accessToken, refreshToken, userId: auth.userId.value, redirectUrl }; } // 소셜로그인 유저저 정보 가져오기 @@ -85,4 +86,13 @@ export class OAuthLoginUseCase { return { accessToken, refreshToken }; } + + // 리다이렉트 url 복호화 및 반환 + private decodeRedirectUrl(state?: string): string { + if (!state) return ''; + + const decodedState = decodeURIComponent(state); + + return decodedState; + } } diff --git a/src/auth/presentation/auth.controller.ts b/src/auth/presentation/auth.controller.ts index f79633e..0dbf8ac 100644 --- a/src/auth/presentation/auth.controller.ts +++ b/src/auth/presentation/auth.controller.ts @@ -25,10 +25,13 @@ export class AuthController { @Get('oauth/authorization') @AuthDocs('oauthAuthorization') - authorizeOAuth(@Query('redirectUrl') redirectUrl?: string) { - const { authUrl } = this.authorizeOAuthUseCase.execute({ oAuthProviderType: OAuthProviderType.KAKAO, redirectUrl }); + authorizeOAuth(@Query('redirectPath') redirectPath?: string) { + const { authUrl } = this.authorizeOAuthUseCase.execute({ + oAuthProviderType: OAuthProviderType.KAKAO, + redirectUrl: redirectPath, + }); - return authUrl; + return { authUrl, redirectPath }; } @Post('login/oauth/callback') @@ -37,21 +40,22 @@ export class AuthController { @Res() res: Response, @Query('code') code: string, @Query('error') error?: string, - //@Query('state') state?: string, + @Query('state') state?: string, ) { if (error && error === 'access_denied') { return res.status(HttpStatus.UNAUTHORIZED).json({ message: 'Access denied' }); } - const { accessToken, refreshToken, userId } = await this.oAuthLoginUseCase.execute({ + const { accessToken, refreshToken, userId, redirectUrl } = await this.oAuthLoginUseCase.execute({ oAuthProviderType: OAuthProviderType.KAKAO, code, + state, }); res.cookie('accessToken', accessToken, accessTokenCookieOptions); res.cookie('refreshToken', refreshToken, refreshTokenCookieOptions); - res.json({ userId }); + res.json({ userId, redirectUrl }); } @Get('refresh') From 322e2b7e005eccff9a5595548c90a7858b4d22e2 Mon Sep 17 00:00:00 2001 From: liverforpresent Date: Fri, 24 Oct 2025 23:57:48 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/auth/presentation/auth.controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auth/presentation/auth.controller.ts b/src/auth/presentation/auth.controller.ts index 0dbf8ac..5d63a7d 100644 --- a/src/auth/presentation/auth.controller.ts +++ b/src/auth/presentation/auth.controller.ts @@ -25,13 +25,13 @@ export class AuthController { @Get('oauth/authorization') @AuthDocs('oauthAuthorization') - authorizeOAuth(@Query('redirectPath') redirectPath?: string) { + authorizeOAuth(@Query('returnPath') returnPath?: string) { const { authUrl } = this.authorizeOAuthUseCase.execute({ oAuthProviderType: OAuthProviderType.KAKAO, - redirectUrl: redirectPath, + redirectUrl: returnPath, }); - return { authUrl, redirectPath }; + return { authUrl, returnPath }; } @Post('login/oauth/callback')