@@ -2,6 +2,20 @@ import { appleOAuth2CodeResponse } from "@/types/auth";
22
33import { toast } from "@/lib/zustand/useToastStore" ;
44
5+ // 오픈 리다이렉트 공격 방지를 위한 redirect 파라미터 검증
6+ // 단일 "/"로 시작하고 "//"나 "://"를 포함하지 않는 내부 경로만 허용
7+ export const validateSafeRedirect = ( redirectParam : string | null ) : string => {
8+ if ( ! redirectParam || typeof redirectParam !== "string" ) {
9+ return "/" ;
10+ }
11+
12+ if ( redirectParam . startsWith ( "/" ) && ! redirectParam . startsWith ( "//" ) && ! redirectParam . includes ( "://" ) ) {
13+ return redirectParam ;
14+ }
15+
16+ return "/" ;
17+ } ;
18+
519export const authProviderName = ( provider : "KAKAO" | "APPLE" | "EMAIL" ) : string => {
620 if ( provider === "KAKAO" ) {
721 return "카카오" ;
@@ -16,8 +30,20 @@ export const authProviderName = (provider: "KAKAO" | "APPLE" | "EMAIL"): string
1630
1731export const kakaoLogin = ( ) => {
1832 if ( window . Kakao && window . Kakao . Auth ) {
33+ // 현재 URL에서 redirect 파라미터 추출 및 검증
34+ const urlParams = new URLSearchParams ( window . location . search ) ;
35+ const redirectParam = urlParams . get ( "redirect" ) ;
36+ const safeRedirect = validateSafeRedirect ( redirectParam ) ;
37+
38+ // 검증된 redirect 파라미터를 callback URL에 전달
39+ let redirectUri = `${ process . env . NEXT_PUBLIC_WEB_URL } /login/kakao/callback` ;
40+ // 기본값 "/"가 아닌 경우에만 redirect 파라미터 추가 (기본값이면 생략 가능)
41+ if ( safeRedirect !== "/" ) {
42+ redirectUri += `?redirect=${ encodeURIComponent ( safeRedirect ) } ` ;
43+ }
44+
1945 window . Kakao . Auth . authorize ( {
20- redirectUri : ` ${ process . env . NEXT_PUBLIC_WEB_URL } /login/kakao/callback` ,
46+ redirectUri,
2147 } ) ;
2248 } else {
2349 toast . error ( "Kakao SDK를 불러오는 중입니다. 잠시 후 다시 시도해주세요." ) ;
@@ -30,17 +56,34 @@ export const appleLogin = async () => {
3056 return ;
3157 }
3258
59+ // 현재 URL에서 redirect 파라미터 추출 및 검증
60+ const urlParams = new URLSearchParams ( window . location . search ) ;
61+ const redirectParam = urlParams . get ( "redirect" ) ;
62+ const safeRedirect = validateSafeRedirect ( redirectParam ) ;
63+
64+ // 검증된 redirect 파라미터를 callback URL에 전달
65+ let redirectURI = `${ process . env . NEXT_PUBLIC_WEB_URL } /login/apple/callback` ;
66+ // 기본값 "/"가 아닌 경우에만 redirect 파라미터 추가 (기본값이면 생략 가능)
67+ if ( safeRedirect !== "/" ) {
68+ redirectURI += `?redirect=${ encodeURIComponent ( safeRedirect ) } ` ;
69+ }
70+
3371 window . AppleID . auth . init ( {
3472 clientId : process . env . NEXT_PUBLIC_APPLE_CLIENT_ID ,
3573 scope : process . env . NEXT_PUBLIC_APPLE_SCOPE ,
36- redirectURI : ` ${ process . env . NEXT_PUBLIC_WEB_URL } /login/apple/callback` ,
74+ redirectURI,
3775 usePopup : true ,
3876 } ) ;
3977
4078 try {
4179 const res : appleOAuth2CodeResponse = await window . AppleID . auth . signIn ( ) ;
4280 if ( res . authorization ) {
43- window . location . href = `/login/apple/callback?code=${ encodeURIComponent ( res . authorization . code ) } ` ;
81+ // 검증된 redirect 파라미터를 callback URL에 전달
82+ let callbackUrl = `/login/apple/callback?code=${ encodeURIComponent ( res . authorization . code ) } ` ;
83+ if ( safeRedirect !== "/" ) {
84+ callbackUrl += `&redirect=${ encodeURIComponent ( safeRedirect ) } ` ;
85+ }
86+ window . location . href = callbackUrl ;
4487 }
4588 } catch ( error ) {
4689 // Log error for developers
0 commit comments