-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] OAuth 로그인 인가 코드 전송 후 회원가입/로그인 성공 분기 작업 #177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a1af2d4
7e414df
1a50170
bbf1fba
518f673
8b3c0f7
65ef832
d9c7db2
a2a1b3a
423de87
012070d
1fa9b28
0e9761b
ce9808e
35a217a
ecbeede
84ae31b
09e8a79
89cd48b
8514849
3e354c9
44ccd1c
31ee8ac
26dc7ab
ddbf011
4c751d1
4ddcf0e
efa9b9f
9868180
15dac1a
4b43dac
91557e4
4505c99
2f2eeaf
e3e78de
00059ac
d6de32c
d8b2fa2
fbbed2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,41 +1,54 @@ | ||
| import axios from 'axios'; | ||
| import { useEffect } from 'react'; | ||
| import { useNavigate } from 'react-router-dom'; | ||
| import { LoadingSpinner } from '@/shared/components/LoadingSpinner'; | ||
| import { postAuthCode } from './api/postAuthCode'; | ||
| import { setAccessToken, setTemporaryToken } from '../Signup/utils/token'; | ||
| import type { ErrorResponse } from '../Signup/type/error'; | ||
| import type { AxiosError } from 'axios'; | ||
|
|
||
| interface LoginSuccessResponse { | ||
| status: 'LOGIN_SUCCESS'; | ||
| accessToken: string; | ||
| refreshToken: string; | ||
| } | ||
|
|
||
| interface RegistrationRequiredResponse { | ||
| status: 'REGISTRATION_REQUIRED'; | ||
| temporaryToken: string; | ||
| } | ||
|
|
||
| type LoginResponse = LoginSuccessResponse | RegistrationRequiredResponse; | ||
|
|
||
| export const KakaoCallback = () => { | ||
| const navigate = useNavigate(); | ||
|
|
||
| useEffect(() => { | ||
| const code = new URL(window.location.href).searchParams.get('code'); | ||
| if (!code) return; | ||
|
|
||
| console.log(code); | ||
| if (!code) { | ||
| navigate('/login'); | ||
| return; | ||
| } | ||
|
|
||
| const fetchToken = async () => { | ||
| try { | ||
| const res = axios.post(`${import.meta.env.VITE_API_BASE_URL}/auth/kakao/login`, { | ||
| authorizationCode: code, | ||
| }); | ||
| console.log('응답res ', res); | ||
|
|
||
| // CASE 1) 기존 회원 | ||
|
|
||
| // 1-1. accessToken, refreshToken 발급 | ||
| // localStorage.setItem('accessToken', res.data.accessToken); | ||
| // localStorage.setItem('refreshToken ', res.data.refreshToken)- (수정전) | ||
| // refreshToken은 httpOnly 관리(수정후) | ||
| // ------------------------------------------------------------ | ||
| // 2-2 main 페이지 이동 | ||
| // navigate('/'); // 로그인 후 홈으로 이동 | ||
|
|
||
| // CASE 2) 기존 회원 | ||
| // 2-1. 임시 토큰 | ||
| // 2-2. navigate('/signup') | ||
| } catch (error) { | ||
| console.log('error:', error); | ||
| const response: LoginResponse = await postAuthCode(code); | ||
|
|
||
| switch (response.status) { | ||
| case 'LOGIN_SUCCESS': | ||
| setAccessToken(response.accessToken); | ||
| navigate('/'); | ||
| break; | ||
| case 'REGISTRATION_REQUIRED': | ||
| setTemporaryToken(response.temporaryToken); | ||
| navigate('/signup'); | ||
| break; | ||
| } | ||
| } catch (e) { | ||
| const error = e as AxiosError<ErrorResponse>; | ||
| return new Error(error.response?.data.message); | ||
| } | ||
|
Comment on lines
47
to
50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }; | ||
|
|
||
| fetchToken(); | ||
| }, [navigate]); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import axios from 'axios'; | ||
| import type { AxiosInstance, CreateAxiosDefaults } from 'axios'; | ||
|
|
||
| const initInstance = (config: CreateAxiosDefaults): AxiosInstance => { | ||
| const instance = axios.create({ | ||
| timeout: 10000, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| ...config.headers, | ||
| }, | ||
| // TODO 0. interceptor 적용지점(동아리 운영자) | ||
| ...config, | ||
| }); | ||
|
|
||
| return instance; | ||
| }; | ||
|
|
||
| export const apiInstance = initInstance({ | ||
| baseURL: import.meta.env.VITE_API_BASE_URL, | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { apiInstance } from './initInstance'; | ||
| import type { AxiosResponse } from 'axios'; | ||
|
|
||
| interface LoginSuccessResponse { | ||
| status: 'LOGIN_SUCCESS'; | ||
| accessToken: string; | ||
| refreshToken: string; | ||
| } | ||
|
|
||
| interface RegistrationRequiredResponse { | ||
| status: 'REGISTRATION_REQUIRED'; | ||
| temporaryToken: string; | ||
| } | ||
|
|
||
| type LoginResponse = LoginSuccessResponse | RegistrationRequiredResponse; | ||
|
|
||
| export const postAuthCode = async (code: string): Promise<LoginResponse> => { | ||
| const response: AxiosResponse<LoginResponse> = await apiInstance.post('/auth/kakao/login', { | ||
| authorizationCode: code, | ||
| }); | ||
| return response.data; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,43 @@ | ||
| import axios, { AxiosError, type AxiosResponse } from 'axios'; | ||
| import { apiInstance } from '../../Login/api/initInstance'; | ||
| import type { ErrorResponse } from '../type/error'; | ||
| import type { SignupFormInputs } from '../type/signup'; | ||
|
|
||
| export const postSignupForm = async (formData: SignupFormInputs): Promise<SignupFormInputs> => { | ||
| const url = `${import.meta.env.VITE_API_BASE_URL}/auth/register`; | ||
| const response = await fetch(url, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify(formData), | ||
| }); | ||
| export interface RegisterSuccessResponse { | ||
| status: 'REGISTER_SUCCESS'; | ||
| accessToken: string; | ||
| refreshToken: string; | ||
| } | ||
|
|
||
| if (!response.ok) throw new Error('회원 가입 양식을 제출하지 못했습니다.'); | ||
| return await response.json(); | ||
| export const postSignupForm = async ( | ||
| formData: SignupFormInputs, | ||
| tempToken: string, | ||
| ): Promise<RegisterSuccessResponse> => { | ||
| try { | ||
| const response: AxiosResponse<RegisterSuccessResponse> = await apiInstance.post( | ||
| '/auth/register', | ||
| formData, | ||
| { | ||
| headers: { Authorization: `Bearer ${tempToken}` }, | ||
| }, | ||
| ); | ||
| return response.data; | ||
| } catch (e: unknown) { | ||
| if (axios.isAxiosError(e)) { | ||
| const error = e as AxiosError<ErrorResponse>; | ||
| const status = error.response?.status; | ||
| const detailMsg = error.response?.data.detail; | ||
| switch (status) { | ||
| case 400: | ||
| throw new Error(`입력 오류: ${detailMsg}`); | ||
| case 401: | ||
| throw new Error(`권한 오류: ${detailMsg}`); | ||
| case 409: | ||
| throw new Error(`중복 오류: ${detailMsg}`); | ||
| default: | ||
| throw new Error(`알 수 없는 오류: ${e.message}`); | ||
| } | ||
| } | ||
| throw e; | ||
| } | ||
|
Comment on lines
25
to
42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-Axios errors are not propagated. The error handling only catches Axios errors. Network failures, timeouts, or other non-Axios errors will fall through silently without notifying the caller. Apply this diff to ensure all errors are properly propagated: }
+ throw new Error('알 수 없는 오류가 발생했습니다.');
}
}
};🤖 Prompt for AI Agents |
||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export interface ErrorResponse { | ||
| status: number; | ||
| message: string; | ||
| detail: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export const setAccessToken = (token: string) => localStorage.setItem('accessToken', token); | ||
| export const getAccessToken = () => localStorage.getItem('accessToken'); | ||
| export const getRefreshToken = () => localStorage.getItem('refreshToken'); | ||
| export const setTemporaryToken = (token: string) => sessionStorage.setItem('temporaryToken', token); | ||
| export const getTemporaryToken = () => sessionStorage.getItem('temporaryToken'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LoginSuccessResponse에refreshToken이 포함되어 있지만, 저장되지 않고 있습니다. 애플리케이션이 리프레시 토큰을 사용하여 세션을 유지하도록 설계되었다면,refreshToken도localStorage나 안전한 쿠키 등에 저장해야 합니다.