diff --git a/src/api/index.tsx b/src/api/index.tsx index 4b01cc1a..c5a6ca4c 100644 --- a/src/api/index.tsx +++ b/src/api/index.tsx @@ -1,96 +1,71 @@ import axios from 'axios'; import { HTTP_STATUS_CODE } from '../constants/api'; import { getCookie, removeCookie, setCookie } from '@hooks/sign-in/useSignIn'; +import { REFRESH_API } from './refresh'; import { message } from 'antd'; -import { TextBox } from '@components/text-box'; import { ROUTES } from '@/constants/routes'; -import { REFRESH_API } from './refresh'; +import { isAccessTokenExpired } from '@/utils/refresh'; export const instance = axios.create({ // baseURL: '', baseURL: process.env.REACT_APP_SERVER_URL, + timeout: 5000, headers: { 'Content-Type': 'application/json', - timeout: 5000, }, }); +const handleUnauthorized = () => { + removeCookie('accessToken'); + removeCookie('refreshToken'); + localStorage.clear(); + message.error('로그인 만료 입니다.'); + window.location.replace('/signin'); +}; + instance.interceptors.request.use( - (config) => { + async (config) => { const accessToken = getCookie('accessToken'); if (accessToken) { - config.headers['Authorization'] = `Bearer ${accessToken}`; + const isTokenExpired = isAccessTokenExpired(accessToken); + if (isTokenExpired) { + try { + const res = await REFRESH_API.postRefresh({ + accessToken: accessToken, + refreshToken: getCookie('refreshToken') as string, + }); + config.headers['Authorization'] = `Bearer ${res.data.accessToken}`; + setCookie('accessToken', res.data.accessToken); + setCookie('refreshToken', res.data.refreshToken); + } catch (refreshError) { + console.log('재발급 실패'); + } + } else { + config.headers['Authorization'] = `Bearer ${accessToken}`; + } } return config; }, (error) => { + // 여기 뺐습니다. return Promise.reject(error); }, ); instance.interceptors.response.use( - (response) => { - return response; - }, - async (error) => { - const accessToken = getCookie('accessToken'); - const refreshToken = getCookie('refreshToken'); - if (error.response.status === HTTP_STATUS_CODE.NOTFOUND) { - console.log('404페이지로 이동'); - } else if ( + (response) => response, + (error) => { + if ( + error.response?.status === HTTP_STATUS_CODE.UNAUTHORIZED && window.location.pathname !== ROUTES.SIGNIN && - window.location.pathname !== ROUTES.SIGNUP && window.location.pathname !== ROUTES.SIGNIN_AGREEMENT && - window.location.pathname !== ROUTES.SIGNUP_SUCCESS && - error.response.status === HTTP_STATUS_CODE.UNAUTHORIZED && - accessToken - ) { - try { - const response = await REFRESH_API.postRefresh({ - accessToken: accessToken, - refreshToken: refreshToken as string, - }); - removeCookie('accessToken'); - removeCookie('refreshToken'); - removeCookie('accommodationId'); - const newAccessToken = response.data.data.accessToken; - const newRefreshToken = response.data.data.refreshToken; - setCookie('accessToken', newAccessToken); - setCookie('refreshToken', newRefreshToken); - } catch (error) { - removeCookie('accessToken'); - removeCookie('refreshToken'); - removeCookie('accommodationId'); - message.error({ - content: ( - - 로그인 만료입니다. - - ), - duration: 2, - }); - setTimeout(() => { - window.location.href = ROUTES.SIGNIN; - }, 1000); - } - return axios(error.config); - } else if ( - !accessToken && - window.location.pathname !== ROUTES.SIGNIN && window.location.pathname !== ROUTES.SIGNUP && - window.location.pathname !== ROUTES.SIGNIN_AGREEMENT && window.location.pathname !== ROUTES.SIGNUP_SUCCESS ) { - removeCookie('accessToken'); - removeCookie('refreshToken'); - removeCookie('accommodationId'); - localStorage.clear(); - setTimeout(() => { - window.location.href = ROUTES.SIGNIN; - }, 1000); + handleUnauthorized(); + } else if (error.response?.status === HTTP_STATUS_CODE.NOTFOUND) { + console.log('여기 404에러 핸들링 필요해요~'); } return Promise.reject(error); }, ); - -export default instance; diff --git a/src/api/refresh/index.ts b/src/api/refresh/index.ts index 19cc30c4..3e0f084a 100644 --- a/src/api/refresh/index.ts +++ b/src/api/refresh/index.ts @@ -1,8 +1,7 @@ import { instance } from '..'; import { PostRefreshData, RefreshData } from './type'; -import { Response } from '@/types/api'; export const REFRESH_API = { postRefresh: (data: RefreshData) => - instance.post>('/api/auth/refresh', data), + instance.post('/api/auth/refresh', data), }; diff --git a/src/api/sign-up/index.ts b/src/api/sign-up/index.ts index 0c3045a8..5152b61e 100644 --- a/src/api/sign-up/index.ts +++ b/src/api/sign-up/index.ts @@ -7,18 +7,17 @@ import { VerificationData, } from './type'; import { instance } from '..'; -import { Response } from '@/types/api'; export const SIGN_UP_API = { postSignUp: (data: SignUpData) => - instance.post>('/api/auth/owners/signup', data), + instance.post('/api/auth/owners/signup', data), postAuthentication: (data: AuthenticationData) => - instance.post>( + instance.post( '/api/auth/owners/request-email', data, ), getVerify: (data: VerificationData) => - instance.get>('/api/auth/owners/verify', { + instance.get('/api/auth/owners/verify', { params: { email: data.email, 'verification-code': data.verificationCode, diff --git a/src/assets/data/signInData.json b/src/assets/data/signInData.json index 7fedf1f3..9ab4b30d 100644 --- a/src/assets/data/signInData.json +++ b/src/assets/data/signInData.json @@ -1,12 +1,9 @@ { - "message": "성공적으로 로그인 했습니다.", - "data": { - "accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0MTIzNDVAbmF2ZXIuY29tIiwiaWF0IjoxNzAxMzMwOTE0LCJleHAiOjE3MDE0MTczMTR9._G-tEQZpJdCgT_Uo4ipDByBwjP-0HclSq35G3MLvNvIiPILbT0Y9MZR19gTWDbzLuU0Gp1phFMbSGNYuAD1cSg", - "refreshToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MDEzMzA5MTQsImV4cCI6MTcwMTkzNTcxNH0.8BZt06CkTEWx7FyT4OH4c7ZNNplz9un2TCmyxf-nM5aCJe-MXJVdJAYnHW00j3ZytR_aYWp-R78tBSSF3pfAkw", - "memberResponse": { - "id": 1, - "email": "test123@mail.com", - "name": "홍길동" - } + "accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0MTIzNDVAbmF2ZXIuY29tIiwiaWF0IjoxNzAxMzMwOTE0LCJleHAiOjE3MDE0MTczMTR9._G-tEQZpJdCgT_Uo4ipDByBwjP-0HclSq35G3MLvNvIiPILbT0Y9MZR19gTWDbzLuU0Gp1phFMbSGNYuAD1cSg", + "refreshToken": "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3MDEzMzA5MTQsImV4cCI6MTcwMTkzNTcxNH0.8BZt06CkTEWx7FyT4OH4c7ZNNplz9un2TCmyxf-nM5aCJe-MXJVdJAYnHW00j3ZytR_aYWp-R78tBSSF3pfAkw", + "memberResponse": { + "id": 1, + "email": "test123@mail.com", + "name": "홍길동" } } diff --git a/src/components/layout/side-bar/signout-btn/index.tsx b/src/components/layout/side-bar/signout-btn/index.tsx index 326c600b..fa452d3b 100644 --- a/src/components/layout/side-bar/signout-btn/index.tsx +++ b/src/components/layout/side-bar/signout-btn/index.tsx @@ -1,12 +1,23 @@ +import { ROUTES } from '@/constants/routes'; import { LogoutOutlined } from '@ant-design/icons'; import { TextBox } from '@components/text-box'; +import { removeCookie } from '@hooks/sign-in/useSignIn'; import styled from 'styled-components'; export const SignOutBtn = () => { + const handleSignOut = () => { + // 여기에 로그아웃 api 연결할 예정 + // 밑에는 onSuccess 시 할 일 + removeCookie('accessToken'); + removeCookie('refreshToken'); + localStorage.clear(); + window.location.href = ROUTES.SIGNIN; + // 여기 밑에는 onError 시 할 일 추가할 예정 + }; return ( - + 로그아웃 diff --git a/src/hooks/sign-in/useSignIn.ts b/src/hooks/sign-in/useSignIn.ts index d2e85a93..121ed2e3 100644 --- a/src/hooks/sign-in/useSignIn.ts +++ b/src/hooks/sign-in/useSignIn.ts @@ -1,7 +1,7 @@ export const setCookie = (name: string, value: string | number) => { try { if (name === 'accessToken') { - document.cookie = `${name}=${value};max-age=7200;path=/;`; + document.cookie = `${name}=${value};max-age=3600;path=/;`; } else if (name === 'refreshToken') { const expirationDate = new Date(); expirationDate.setDate(expirationDate.getDate() + 7); diff --git a/src/pages/sign-in/index.tsx b/src/pages/sign-in/index.tsx index 8be5ce5c..6afb50b2 100644 --- a/src/pages/sign-in/index.tsx +++ b/src/pages/sign-in/index.tsx @@ -24,6 +24,8 @@ export const SignIn = () => { setCookie('accessToken', response.data.accessToken); setCookie('refreshToken', response.data.refreshToken); const memberResponse = response.data.memberResponse; + setCookie('accessToken', response.data.accessToken); + setCookie('refreshToken', response.data.refreshToken); const memberData = JSON.stringify(memberResponse); localStorage.setItem('member', memberData); if (accommodationListData?.accommodations[0]?.id) { @@ -32,74 +34,35 @@ export const SignIn = () => { accommodationListData?.accommodations[0]?.id, ); } - try { - const res = isAccomodationList(); - if (res === true) { - const accomodationId = getCookie('accomodationId'); - setTimeout(() => { - handleChangeUrl(`/${accomodationId}/main`); - }, 1000); - } else { - setTimeout(() => { - handleChangeUrl('/init'); - }, 1000); - } - } catch (e) { - message.error({ - content: ( - - 요청에 실패했습니다. 잠시 후 다시 시도해 주세요. - - ), - duration: 2, - style: { - width: '346px', - height: '41px', - }, - }); + const res = isAccommodationList(); + if (res === true) { + const accommodationId = getCookie('accommodationId'); + setTimeout(() => { + handleChangeUrl(`/${accommodationId}/main`); + }, 1000); + } else { + setTimeout(() => { + handleChangeUrl('/init'); + }, 1000); } }, - onError(error) { - if (error instanceof AxiosError && error.response) { - if (error.response.status === HTTP_STATUS_CODE.BAD_GATEWAY) { - message.error({ - content: ( - - 요청에 실패했습니다. 잠시 후 다시 시도해 주세요. - - ), - duration: 2, - style: { - width: '346px', - height: '41px', - }, - }); - } else { - message.error({ - content: ( - - 이메일과 비밀번호를 확인해 주세요. - - ), - duration: 2, - style: { - width: '346px', - height: '41px', - }, - }); - } - } + onError() { + message.error({ + content: ( + + 이메일과 비밀번호를 확인해 주세요. + + ), + duration: 2, + }); }, }); - const isAccomodationList = () => { - if ( + + const isAccommodationList = () => { + return ( accommodationListData?.accommodations && accommodationListData.accommodations.length > 0 - ) { - return true; - } else { - return false; - } + ); }; const handleOnclick = () => { @@ -123,6 +86,7 @@ export const SignIn = () => { }); } }; + const formik = useFormik({ initialValues: { email: '', @@ -130,11 +94,43 @@ export const SignIn = () => { }, validationSchema: ValidateSchema, onSubmit: async (values) => { - const signInData: SignInData = { - email: values.email, - password: values.password, - }; - mutate(signInData); + try { + const signInData: SignInData = { + email: values.email, + password: values.password, + }; + await mutate(signInData); + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status === HTTP_STATUS_CODE.BAD_GATEWAY) { + message.error({ + content: ( + + 요청에 실패했습니다. 잠시 후 다시 시도해 주세요. + + ), + duration: 2, + style: { + width: '346px', + height: '41px', + }, + }); + } else { + message.error({ + content: ( + + 이메일과 비밀번호를 확인해 주세요. + + ), + duration: 2, + style: { + width: '346px', + height: '41px', + }, + }); + } + } + } }, }); @@ -155,7 +151,7 @@ export const SignIn = () => { value={values.email} onChange={handleChange} onBlur={handleBlur} - > + /> {touched.email && errors.email && ( {errors.email} @@ -171,7 +167,7 @@ export const SignIn = () => { value={values.password} onChange={handleChange} onBlur={handleBlur} - > + /> {touched.password && errors.password && ( {errors.password} diff --git a/src/queries/sign-up/index.ts b/src/queries/sign-up/index.ts index cc7b67dd..6ec44124 100644 --- a/src/queries/sign-up/index.ts +++ b/src/queries/sign-up/index.ts @@ -6,34 +6,32 @@ import { PostSignUpResData, SignUpData, } from '@api/sign-up/type'; -import { Response } from '@/types/api'; import { SIGN_UP_API } from '@api/sign-up'; export const usePostSignUp = ( options?: UseMutationOptions< - AxiosResponse>, + AxiosResponse, AxiosError, SignUpData >, ) => { - return useMutation< - AxiosResponse>, - AxiosError, - SignUpData - >((data: SignUpData) => SIGN_UP_API.postSignUp(data), { - ...options, - }); + return useMutation, AxiosError, SignUpData>( + (data: SignUpData) => SIGN_UP_API.postSignUp(data), + { + ...options, + }, + ); }; export const usePostAuthentication = ( options?: UseMutationOptions< - AxiosResponse>, + AxiosResponse, AxiosError, AuthenticationData >, ) => { return useMutation< - AxiosResponse>, + AxiosResponse, AxiosError, AuthenticationData >((data: AuthenticationData) => SIGN_UP_API.postAuthentication(data), { diff --git a/src/utils/refresh/index.ts b/src/utils/refresh/index.ts new file mode 100644 index 00000000..f4ca32ea --- /dev/null +++ b/src/utils/refresh/index.ts @@ -0,0 +1,18 @@ +export function isAccessTokenExpired(accessToken: string) { + if (!accessToken) { + return true; + } + + const decodedToken = decodeAccessToken(accessToken); + const currentTime = Math.floor(Date.now() / 1000); + + return decodedToken.exp < currentTime; +} + +function decodeAccessToken(accessToken: string) { + const tokenParts = accessToken.split('.'); + const encodedPayload = tokenParts[1]; + const decodedPayload = atob(encodedPayload); + + return JSON.parse(decodedPayload); +}