Skip to content

Commit

Permalink
[Feat/#329] 리프레시 토큰 로직 추가 (#331)
Browse files Browse the repository at this point in the history
* feat: 액세스 토큰 재발급 기능 추가

* feat: 토큰 에러에 따른 분기처리 추가

* feat: 스픽커 승인시 재로그인 로직 추가

* style: eslint오류 수정

* fix: 코드리뷰 반영
  • Loading branch information
gudusol authored Sep 26, 2024
1 parent 08a8919 commit fb7597d
Show file tree
Hide file tree
Showing 7 changed files with 5,065 additions and 2,857 deletions.
85 changes: 71 additions & 14 deletions src/apis/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import axios from 'axios';
import axios, { AxiosError } from 'axios';

import { clearLocalStorage } from '@utils';

import { components } from '@schema';
import { ApiResponseType, ErrorType } from '@types';

type AccessTokenGetSuccess = components['schemas']['AccessTokenGetSuccess'];

export const instance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_URL,
Expand All @@ -9,19 +16,6 @@ export const instance = axios.create({
},
});

//모임 Id를 계속 쏠 필요가 있는가? 명세서 보니깐 path로 받는 것 같은데...
//일단 주석 처리 해놓겠습니다
// instance.interceptors.request.use(
// (config) => {
// const memberId = 1; //예시 멤버 ID
// config.headers['memberId'] = memberId;
// return config;
// },
// (error) => {
// return Promise.reject(error);
// },
// );

export function get<T>(...args: Parameters<typeof instance.get>) {
return instance.get<T>(...args);
}
Expand All @@ -41,3 +35,66 @@ export function patch<T>(...args: Parameters<typeof instance.patch>) {
export function del<T>(...args: Parameters<typeof instance.delete>) {
return instance.delete<T>(...args);
}

const fetchAccessToken = async (): Promise<string | null> => {
const refreshToken = localStorage.getItem('refreshToken');

try {
const response = await axios.get<ApiResponseType<AccessTokenGetSuccess>>(
`${import.meta.env.VITE_APP_BASE_URL}/v1/user/token-refresh?refreshToken=${refreshToken}`
);

const accessToken = response.data.data.accessToken;
if (!accessToken) {
return null;
}
localStorage.setItem('accessToken', accessToken);
instance.defaults.headers.Authorization = `Bearer ${accessToken}`;
return accessToken;
} catch (error) {
console.error('토큰 재발급 실패:', error);

const errorData = (error as AxiosError)?.response?.data as ErrorType;

// 리프레시 토큰 만료 시. 로그아웃 처리
if (errorData.status === 40101) {
console.error('리프레시 토큰 만료:', errorData);
clearLocalStorage();
window.location.href = '/login';
return Promise.reject(error);
}
return null;
}
};

instance.interceptors.response.use(
(response) => response, // 성공적인 응답은 그대로 반환
async (error) => {
const errorData = error?.response.data;
const accessToken = localStorage.getItem('accessToken');

if (errorData && errorData.status && accessToken) {
// 액세스 토큰 만료 시. 리프레시 토큰으로 재발급 시도
if (errorData.status === 40100) {
const originalRequest = error.config;

try {
// 새로운 accessToken 발급 시도
const newAccessToken = await fetchAccessToken();

// 새로운 accessToken으로 헤더 업데이트
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;

// 요청 재시도
return axios.request(originalRequest);
} catch (tokenError) {
console.error('토큰 갱신 후 재시도 실패:', tokenError);
return Promise.reject(tokenError);
}
}
}

console.error('응답 에러:', error);
return Promise.reject(error);
}
);
3 changes: 2 additions & 1 deletion src/apis/domains/user/usePostKakaoLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ export const usePostKakaoLogin = () => {

setUser({ ...user, guestNickname, guestId, hostNickname, hostId });

if (token && token.accessToken) {
if (token && token.accessToken && token.refreshToken) {
localStorage.setItem('accessToken', token.accessToken);
localStorage.setItem('refreshToken', token.refreshToken);
instance.defaults.headers.Authorization = `Bearer ${token.accessToken}`;
}

Expand Down
4 changes: 2 additions & 2 deletions src/apis/domains/user/usePostLogout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { post } from '@apis/api';

import { useEasyNavigate } from '@hooks';
import { userAtom } from '@stores';
import { clearLocalStorage } from '@utils';

const postLogout = async () => {
try {
Expand All @@ -22,8 +23,7 @@ export const usePostLogout = () => {
return useMutation({
mutationFn: () => postLogout(),
onSuccess: () => {
localStorage.removeItem('accessToken');
localStorage.removeItem('user');
clearLocalStorage();
setUser(RESET);
goHome();
},
Expand Down
1 change: 1 addition & 0 deletions src/apis/queryKeys/queryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const QUERY_KEY = {
HEALTH: 'health',
TEST: 'test',
KAKAO_LOGIN: 'kakaoLogin',
ACCESS_TOKEN: 'accessToken',
MOIM_CATEGORIES: 'moimCategories',
HOST_APPLY: 'hostApply',
QUESTION_LIST: 'questionList',
Expand Down
20 changes: 12 additions & 8 deletions src/pages/myPage/page/HostMyPage/HostMyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import HostInfoCardWithLink from '@pages/myPage/components/HostInfoCardWithLink/
import LogoutModal from '@pages/myPage/components/LogoutModal/LogoutModal';
import { userAtom } from '@stores';
import { IcNext } from '@svg';
import { clearLocalStorage } from '@utils';

import {
divdier,
Expand Down Expand Up @@ -48,12 +49,13 @@ const isErrorResponseType = (data: unknown): data is ErrorType => {
return false;
};
const HostMyPage = () => {
const [, setUser] = useAtom(userAtom);

const [user] = useAtom(userAtom);
const { data: hostInfoResponse, isSuccess, isLoading } = useFetchMyHost();
const { goGuestMyPage } = useEasyNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);

const { hostId: jotaiHostId, hostNickname: jotaiHostNickname } = user;

let hostInfoData: HostGetResponse | null = null;
let errorData: ErrorType | null = null;

Expand Down Expand Up @@ -82,15 +84,17 @@ const HostMyPage = () => {
};

useEffect(() => {
if (isSuccess && hostInfoData) {
if (isSuccess && hostInfoData && !jotaiHostId && !jotaiHostNickname) {
const { hostId, hostNickName } = hostInfoData;
if (hostId && hostNickName) {
setUser((prevState) => {
return { ...prevState, hostId, hostNickname: hostNickName };
});
const isFirstApproval = hostId && hostNickName;
if (isFirstApproval) {
// TODO: alert 2번 뜨는 문제 해결
alert('호스트 최초 승인 후 재로그인이 필요합니다.');
clearLocalStorage();
window.location.href = '/login';
}
}
}, [isSuccess, hostInfoData, setUser]);
}, [isSuccess, hostInfoData, jotaiHostId, jotaiHostNickname]);

return (
<>
Expand Down
Loading

0 comments on commit fb7597d

Please sign in to comment.