From 1a17032898830030171f35b9e996ba5a83c49f7a Mon Sep 17 00:00:00 2001 From: seon022 Date: Mon, 9 Dec 2024 16:54:19 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20:=20=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=97=90=EB=9F=AC=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 --- .../(auth)/login/callback/page.tsx | 30 ++++++++--------- src/app/(empty-layout)/(auth)/login/page.tsx | 4 +-- src/entities/User/api/userService.ts | 15 ++++++--- src/entities/User/model/store/authStore.ts | 33 ++++++++++++++----- src/entities/User/model/types/AuthState.ts | 2 +- src/features/auth/ui/LoginContainer.tsx | 6 ++-- src/middleware.ts | 14 ++++---- src/mocks/handlers/authHandlers.ts | 1 - src/widgets/header/ui/CustomHeader.tsx | 2 +- src/widgets/header/ui/Header.tsx | 27 ++------------- 10 files changed, 65 insertions(+), 69 deletions(-) diff --git a/src/app/(empty-layout)/(auth)/login/callback/page.tsx b/src/app/(empty-layout)/(auth)/login/callback/page.tsx index a316c32..27908c2 100644 --- a/src/app/(empty-layout)/(auth)/login/callback/page.tsx +++ b/src/app/(empty-layout)/(auth)/login/callback/page.tsx @@ -12,30 +12,30 @@ export default function LoginCallbackPage() { useEffect(() => { const handleKakaoLoginCallback = async () => { const urlParams = new URLSearchParams(window.location.search); - const code = urlParams.get('code'); - const state = urlParams.get('state'); - // state 검증 - const savedState = localStorage.getItem('kakao_oauth_state'); - if (!state || state !== savedState) { - toast.error('인증 상태가 일치하지 않습니다.'); - router.push('/login'); - return; - } + const accessToken = urlParams.get('accessToken'); + const refreshToken = urlParams.get('refreshToken'); + const userId = urlParams.get('userId'); - if (!code) { - toast.error('유효하지 않은 인증 정보입니다.'); + // 토큰 유효성 검사 + if (!accessToken || !refreshToken || !userId) { + toast.error('유효하지 않은 로그인 정보입니다.'); router.push('/login'); return; } try { - const result = await userService.kakaoLoginCallback(code, state); - await socialLoginComplete(result); + const loginResult = { + authentication: 'Bearer', + access_token: accessToken, + refresh_token: refreshToken, + userId + }; - // state 제거 - localStorage.removeItem('kakao_oauth_state'); + // 소셜 로그인 완료 처리 + await socialLoginComplete(loginResult); + localStorage.removeItem('kakao_oauth_state'); toast.success('카카오 로그인 성공'); router.push('/'); } catch (error) { diff --git a/src/app/(empty-layout)/(auth)/login/page.tsx b/src/app/(empty-layout)/(auth)/login/page.tsx index 5d9ca65..ab3e62e 100644 --- a/src/app/(empty-layout)/(auth)/login/page.tsx +++ b/src/app/(empty-layout)/(auth)/login/page.tsx @@ -4,7 +4,7 @@ import Link from 'next/link'; import { toast } from 'react-toastify'; import useRedirect from '@/features/auth/hooks/useRedirect'; import LoginContainer from '@/features/auth/ui/LoginContainer'; -import { useAuthStore } from '@/entities/user'; +import { useAuthStore } from '@/entities/User'; export default function LoginPage() { const { isAuthenticated, userId, tokens, kakaoLogin } = useAuthStore(); @@ -14,7 +14,7 @@ export default function LoginPage() { const handleKakaoLogin = async () => { try { // 상태(state) 생성 및 저장 - const state = crypto.randomUUID(); + const state = window.btoa(crypto.randomUUID()); localStorage.setItem('kakao_oauth_state', state); // 카카오 로그인 URL로 리다이렉트 diff --git a/src/entities/User/api/userService.ts b/src/entities/User/api/userService.ts index 012d13d..042ca12 100644 --- a/src/entities/User/api/userService.ts +++ b/src/entities/User/api/userService.ts @@ -66,13 +66,20 @@ export const userService = { throw error; } }, - kakaoLoginCallback: async (code: string, state: string) => { + kakaoLoginCallback: async ( + code: string, + state: string, + fcmToken?: string + ) => { try { const response = await axiosInstance.post( `${process.env.NEXT_PUBLIC_API_URL}/api/oauth2/kakao/callback`, - { code, state } + { + code, + state, + fcmToken + } ); - return { authentication: 'Bearer', userId: response.data.userId, @@ -117,7 +124,7 @@ export const userService = { logout: async () => { try { - await api.user.logout(); + await api.user.logout('Bearer'); } catch (error) { if (axios.isAxiosError(error)) { const errorResponse = error.response?.data as ErrorResponseDTO; diff --git a/src/entities/User/model/store/authStore.ts b/src/entities/User/model/store/authStore.ts index 16d45fa..c52d941 100644 --- a/src/entities/User/model/store/authStore.ts +++ b/src/entities/User/model/store/authStore.ts @@ -93,27 +93,45 @@ export const useAuthStore = create()( }, socialLoginComplete: async (loginResponse: { authentication: string; - access_token: string; - refresh_token: string; + accessToken?: string; + refreshToken?: string; userId: string; }) => { - // 기존 로그인 완료 로직과 동일 + // 키 이름 유연하게 처리 + const access_token = loginResponse.accessToken; + const refresh_token = loginResponse.refreshToken; + + // 쿠키에 사용자 토큰 정보 저장 document.cookie = `auth-storage=${JSON.stringify({ state: { userId: loginResponse.userId, tokens: { - access_token: loginResponse.access_token, - refresh_token: loginResponse.refresh_token + access_token, + refresh_token }, isAuthenticated: true } })}; path=/; secure; samesite=strict; max-age=86400`; + // 로컬 스토리지에 사용자 정보 저장 + localStorage.setItem( + 'auth-storage', + JSON.stringify({ + userId: loginResponse.userId, + tokens: { + access_token, + refresh_token + }, + isAuthenticated: true + }) + ); + + // Zustand 스토어 상태 업데이트 set({ userId: loginResponse.userId, tokens: { - access_token: loginResponse.access_token, - refresh_token: loginResponse.refresh_token + access_token, + refresh_token }, isAuthenticated: true, isLoading: false, @@ -122,7 +140,6 @@ export const useAuthStore = create()( return true; }, - join: async (joinData: UserJoinDTO) => { set({ isLoading: true, error: null }); diff --git a/src/entities/User/model/types/AuthState.ts b/src/entities/User/model/types/AuthState.ts index 0dcf8cc..70b472a 100644 --- a/src/entities/User/model/types/AuthState.ts +++ b/src/entities/User/model/types/AuthState.ts @@ -15,7 +15,7 @@ export interface AuthState { login: (loginData: UserLoginDTO) => Promise; socialLogin: (provider: SocialProvider) => Promise; join: (joinData: UserJoinDTO) => Promise; - logout: () => void; + logout: (authorization: string) => Promise; refreshToken: () => Promise; deleteUser: () => Promise; socialLoginComplete: (loginResponse: LoginResponse) => Promise; diff --git a/src/features/auth/ui/LoginContainer.tsx b/src/features/auth/ui/LoginContainer.tsx index 0a95149..a198dd3 100644 --- a/src/features/auth/ui/LoginContainer.tsx +++ b/src/features/auth/ui/LoginContainer.tsx @@ -2,9 +2,8 @@ import { useEffect, useState } from 'react'; import LoginForm from './LoginForm'; import { useRouter } from 'next/navigation'; -import { useAuthStore } from '@/entities/user'; -import ErrorMessage from '@/shared/ui/components/Error/ErrorMsg'; import { requestNotificationPermission } from '@/shared/lib/firebase'; +import { useAuthStore } from '@/entities/User'; export default function LoginContainer() { const router = useRouter(); @@ -33,8 +32,7 @@ export default function LoginContainer() { const success = await login({ email, password, - fcmToken, - passwordEmpty: true + fcmToken }); if (success) { diff --git a/src/middleware.ts b/src/middleware.ts index d14024b..a3ee4f4 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -2,26 +2,24 @@ import { NextResponse, type NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; - const authToken = request.cookies.get('auth-storage'); + const authToken = request.cookies.get('auth-storage')?.value; - // 공개 경로 정의 - const publicPaths = ['/login', '/']; + console.log('Pathname:', pathname); + console.log('Auth Token:', authToken); // 인증이 필요한 경로 const protectedPaths = ['/profile']; - if (publicPaths.includes(pathname) && authToken) { - return NextResponse.redirect(new URL('/', request.url)); - } - if (protectedPaths.includes(pathname) && !authToken) { return NextResponse.redirect(new URL('/login', request.url)); } + console.log('Pathname222222:', pathname); + console.log('Auth Token222222222:', authToken); return NextResponse.next(); } // 미들웨어가 적용될 경로 export const config = { - matcher: ['/login', '/profile'] + matcher: ['/profile'] }; diff --git a/src/mocks/handlers/authHandlers.ts b/src/mocks/handlers/authHandlers.ts index e24142d..3c30ae3 100644 --- a/src/mocks/handlers/authHandlers.ts +++ b/src/mocks/handlers/authHandlers.ts @@ -65,7 +65,6 @@ export const authHandlers = [ const verificationCode = faker.string.numeric(6); emailVerificationCodes[email] = verificationCode; - console.log(`Verification code for ${email}: ${verificationCode}`); return HttpResponse.json(null, { status: 200 }); }), diff --git a/src/widgets/header/ui/CustomHeader.tsx b/src/widgets/header/ui/CustomHeader.tsx index 3df4f90..1a6f019 100644 --- a/src/widgets/header/ui/CustomHeader.tsx +++ b/src/widgets/header/ui/CustomHeader.tsx @@ -13,7 +13,7 @@ interface CustomHeaderProps { export const CustomHeader = ({ title, rightButton }: CustomHeaderProps) => { const router = useRouter(); - const pathname = usePathname() ?? '/'; // null일 경우 기본값 '/' 사용 + const pathname = usePathname() ?? '/'; const handleGoBack = () => { const pathSegments = pathname.split('/').filter(Boolean); diff --git a/src/widgets/header/ui/Header.tsx b/src/widgets/header/ui/Header.tsx index de02799..0eb524b 100644 --- a/src/widgets/header/ui/Header.tsx +++ b/src/widgets/header/ui/Header.tsx @@ -17,14 +17,9 @@ const getDefaultTitle = (pathname: string) => { }; export const Header = ({ detailTitle }: HeaderProps) => { - const { hasNewNotifications } = useNotifications(); const router = useRouter(); const pathname = usePathname(); - const notificationIcon = hasNewNotifications - ? '/icons/notification-new.png' - : '/icons/notification.png'; - const isDetailPage = DETAIL_PAGE_PATTERNS.some(pattern => pathname?.includes(pattern) ); @@ -33,7 +28,7 @@ export const Header = ({ detailTitle }: HeaderProps) => { if (isDetailPage) { return (
-
+
-