Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
636 changes: 198 additions & 438 deletions README.md

Large diffs are not rendered by default.

113 changes: 83 additions & 30 deletions src/renderer/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,51 @@ const api: AxiosInstance = axios.create({
},
});

/**
* 리프레시 토큰으로 액세스 토큰 재발급
*/
const refreshAccessToken = async (): Promise<void> => {
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
throw new Error('Refresh token not found');
}

try {
const { data: newToken } = await axios.post<RefreshResponse>(
`${import.meta.env.VITE_BASE_URL}/auth/refresh`,
{ refreshToken },
{ withCredentials: true },
);

// AUTH-102 코드인 경우 유효하지 않은 리프레시 토큰
if (newToken.code?.toUpperCase() === 'AUTH-102') {
throw new Error('Invalid refresh token');
}

if (!newToken.success || !newToken.data) {
throw new Error('Refresh token expired');
}

// 새로 발급 받은 토큰 저장
const newAccessToken = newToken.data.accessToken;
const newRefreshToken = newToken.data.refreshToken;

localStorage.setItem('accessToken', newAccessToken);
localStorage.setItem('refreshToken', newRefreshToken);

api.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
} catch (error) {
// AUTH-102 에러를 명시적으로 처리
if (axios.isAxiosError(error) && error.response?.data) {
const errorData = error.response.data as { code?: string };
if (errorData.code?.toUpperCase() === 'AUTH-102') {
throw new Error('Invalid refresh token');
}
}
throw error;
}
};

api.interceptors.request.use(
(config) => {
const accessToken = localStorage.getItem('accessToken');
Expand All @@ -33,21 +78,42 @@ api.interceptors.request.use(
);

api.interceptors.response.use(
(response) => {
// 200 OK 응답이지만 세션 만료 코드인 경우 처리
async (response) => {
// 200 OK 응답이지만 토큰 관련 에러 코드인 경우 처리
const responseData = response.data as {
code?: string;
success?: boolean;
message?: string;
};

// AUTH-101 코드인 경우 세션 만료로 처리 (대소문자 구분 없음)
if (responseData.code?.toUpperCase() === 'AUTH-101') {
// 세션 만료 처리
const errorCode = responseData.code?.toUpperCase();

// AUTH-102 코드인 경우 유효하지 않은 리프레시 토큰 → 로그아웃 처리
if (errorCode === 'AUTH-102') {
localStorage.clear();
window.location.href = '/';
// 에러로 처리하여 이후 로직 실행 방지
throw new Error(responseData.message || 'Session expired');
throw new Error(responseData.message || 'Invalid refresh token');
}

// AUTH-101 코드인 경우 토큰 만료로 처리 (대소문자 구분 없음)
if (errorCode === 'AUTH-101') {
try {
// 리프레시 토큰으로 재발급 시도
await refreshAccessToken();

// 재발급 성공 시 원래 요청을 새 토큰으로 재시도
const originalRequest = response.config;
const newAccessToken = localStorage.getItem('accessToken');
if (newAccessToken && originalRequest.headers) {
originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
}
return api(originalRequest);
} catch (refreshError) {
// 리프레시 토큰도 만료되거나 유효하지 않은 경우 로그아웃 처리
localStorage.clear();
window.location.href = '/';
throw new Error(responseData.message || 'Session expired');
}
}

return response;
Expand All @@ -64,30 +130,17 @@ api.interceptors.response.use(
) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refreshToken');
await refreshAccessToken();

const { data: newToken } = await axios.post<RefreshResponse>(
`${import.meta.env.VITE_BASE_URL}/auth/refresh`,
{ refreshToken },
{ withCredentials: true },
);

/* success가 false이거나 응답으로 온 데이터가 비었을 때 */
if (!newToken.success || !newToken.data) {
throw new Error('Refresh token expired');
}

/* 새로 발급 받은 토큰 저장 */
const newAccessToken = newToken.data.accessToken;
const newRefreshToken = newToken.data.refreshToken;

localStorage.setItem('accessToken', newAccessToken);
localStorage.setItem('refreshToken', newRefreshToken);

api.defaults.headers.common['Authorization'] =
`Bearer ${newAccessToken}`;
if (originalRequest.headers) {
originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
// 재발급 성공 시 원래 요청을 새 토큰으로 재시도
const newAccessToken = localStorage.getItem('accessToken');
if (newAccessToken) {
api.defaults.headers.common['Authorization'] =
`Bearer ${newAccessToken}`;
if (originalRequest.headers) {
originalRequest.headers['Authorization'] =
`Bearer ${newAccessToken}`;
}
}

return api(originalRequest);
Expand Down
Loading