Skip to content

Commit

Permalink
refactor: refresh token cookie로 변경, 로그인 버그 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
ptyoiy committed Jul 17, 2024
1 parent 8e0b70d commit 0afb486
Show file tree
Hide file tree
Showing 14 changed files with 147 additions and 137 deletions.
22 changes: 20 additions & 2 deletions src/api/axios/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
import axios from 'axios';
import axios, { AxiosError } from 'axios';
import { getAccessToken } from './user';

axios.defaults.baseURL = '/api';
axios.defaults.withCredentials = true;

axios.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
const originalRequest = error.config;
switch (error.response.status) {
// access 없음
// access 만료시 갱신
case 401:
case 419: {
await getAccessToken();
return axios(originalRequest);
}
// skip default
}
return Promise.reject(error);
}
);
export * from './alarm';
export { default as getAlerts } from './alert';
export * from './event';
export { default as getLinks } from './link';
export * from './notice';
export * from './types.d';
export * from './user';

2 changes: 0 additions & 2 deletions src/api/axios/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,5 @@ export interface ResponseLinkType {

export type ResponseLoginType = {
user: IUser;
accessToken: string;
refreshToken: string;
create: boolean;
};
10 changes: 3 additions & 7 deletions src/api/axios/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ export function login(id: number, kakao_accessToken: string) {
// eslint-disable-next-line max-len
export const kakaoLogout = async (client: QueryClient, navigate: NavigateFunction, dispatch: Dispatch<Action>) => {
console.log('logout called');
return axios.post('/logout', {
refreshToken: localStorage.getItem('refreshToken'),
}).then((d) => {
return axios.post('/logout').then((d) => {
if (d.data.status !== 1) return;
client.removeQueries({ queryKey: ['login'] });
client.removeQueries({ queryKey: ['userInfo'] });
Expand All @@ -37,9 +35,7 @@ export const getUserInfo = async (id: number) => {
return axios.get(`/users/info/${id}`).then((res) => res.data);
};

export const getAccessToken = async (refreshToken: string) => {
export const getAccessToken = async () => {
console.log('get accessToken called');
return axios.post('/getToken', {
refreshToken,
}).then((res) => res.data);
return axios.post('/getToken').then((res) => res.data);
};
8 changes: 4 additions & 4 deletions src/api/query/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ const useAlertsQuery = () => {
queryKey: ['alerts'],
queryFn: getAlerts,
select(data) {
return data.map((d) => ({
...d,
date: new Date(d.date)
}));
data.forEach((d) => {
d.date = new Date(d.date);
});
return data;
},
// refetchInterval: 15000,
// refetchIntervalInBackground: true
Expand Down
22 changes: 16 additions & 6 deletions src/api/query/notice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,36 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { User } from '../../context';
import { EventLikeRequest, getNoticeById, getNotices, ResponseNoticeType, setNoticeLike, setNoticeRead } from '../axios';
import { NoticeType } from './types';

export const useNoticesQuery = () => {
// const queryClient = useQueryClient(); // 캐싱데이터 저장공간
const noticesQuery = useQuery<ResponseNoticeType[]>({
const noticesQuery = useQuery<NoticeType[]>({
queryKey: ['notices'],
queryFn: getNotices,
select(data) {
data.forEach((d) => {
d.date = new Date(d.date);
});
return data;
},
// refetchInterval: 15000,
// refetchIntervalInBackground: true
});

console.log(noticesQuery.data);
return noticesQuery;
};

export const useNoticeByIdQuery = (targetId) => {
const noticeByIdQuery = useQuery({
const noticeByIdQuery = useQuery<NoticeType>({
queryKey: ['notices', targetId],
queryFn: () => getNoticeById(targetId),
select(data) {
return {
...data,
date: new Date(data.date)
};
},
// refetchInterval: 15000,
// refetchIntervalInBackground: true,
// select(data) {
Expand All @@ -35,9 +47,7 @@ export const useNoticeByIdQuery = (targetId) => {

export const useNoticeLikeMutation = () => {
const queryClient = useQueryClient();
const setLikeMutation = useMutation<any, Error, EventLikeRequest, {
prevData: any;
}>({
const setLikeMutation = useMutation<any, Error, EventLikeRequest, { prevData: any; }>({
mutationFn: setNoticeLike,

// Optimistic Update 적용
Expand Down
16 changes: 1 addition & 15 deletions src/api/query/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useQuery } from '@tanstack/react-query';
import axios, { AxiosError } from 'axios';
import { User, UserData } from '../../context';
import { getUserInfo, login } from '../axios';

Expand All @@ -20,25 +19,12 @@ export const useUserInfoQuery = (userData: UserData) => {
const userInfo = useQuery({
queryKey: ['userInfo'],
queryFn: () => getUserInfo(userId),
enabled: isAuthenticated && Boolean((axios.interceptors.response as any).handlers.length),
enabled: isAuthenticated,
staleTime: Infinity,
select(data) {
const { bookmark, eventLike, noticeLike, noticeRead } = data;
return new User(userData, bookmark, noticeRead, noticeLike, eventLike);
},
retry(_failureCount, error: AxiosError) {
// 401 == accessToken이 없음
// 403 == refreshToken이 없음
// 419 == accessToken 만료됨
// 기본값 3회 시도 후 종료
// return true -> 재시도
// return false -> 재시도 종료 후 error 플래그on
const maxRetry = _failureCount === 3;
const retryStatus = ![401, 419].some((errorCode) => errorCode === error?.response?.status);
console.count('retry');
console.log({ retryStatus });
return maxRetry || retryStatus;
},
});
return userInfo;
};
5 changes: 2 additions & 3 deletions src/components/Modal/MyModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { forwardRef, useContext } from "react";
import { useNavigate } from "react-router-dom";
import { kakaoLogout } from "../../../api/axios";
import { ModalContext, MyModalProps, userContext } from "../../../context";
import * as s from "./styles";
import KakaoLogin from "react-kakao-login";
import useKakaoLogin from "../../../hook/useKakaoLogin";
import * as s from "./styles";

const Contents = {
login: {
Expand Down Expand Up @@ -37,7 +36,7 @@ const MyModal = forwardRef(() => {
if (kakaoLoginRef.current) {
kakaoLoginRef?.current.onButtonClick();
}
break;
return;
case "logout":
kakaoLogout(client, navigate, dispatch);
break;
Expand Down
1 change: 0 additions & 1 deletion src/context/user/user.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ interface SetLoginInfoAction {
payload: 'authenticate';
data: {
info: Omit<UserData, 'isAuthenticated'>;
refreshToken: string;
};
}
interface SetMajorAction {
Expand Down
35 changes: 9 additions & 26 deletions src/context/user/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios, { AxiosError } from 'axios';
import { Dispatch, ReactNode, createContext, useEffect, useMemo, useReducer } from 'react';
import { useNavigate } from 'react-router-dom';
import { Action, UserData } from '.';
import { getAccessToken, retireToken } from '../../api/axios';
import { retireToken } from '../../api/axios';
import { useUserInfoQuery } from '../../api/query';
import { requestFCMToken } from '../../util/firebaseCloudMessage/requestFCMToken';

Expand Down Expand Up @@ -37,35 +37,18 @@ export function UserProvider({ children }: { children: ReactNode }) {
fcmToken,
});
const userInfo = useUserInfoQuery(userData);

const navigate = useNavigate();
const value = useMemo(() => ({ userData, dispatch }), [userData]);

// token 갱신
// refresh token 확인
useEffect(() => {
axios.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
const originalRequest = error.config;
switch (error.response.status) {
// access 없음
// access 만료시 갱신
case 401:
case 419: {
const refreshToken = localStorage.getItem('refreshToken');
if (refreshToken) {
await getAccessToken(refreshToken);
return axios(originalRequest);
}
dispatch({ payload: 'logout' });
break;
}
// refresh 없음
case 403: {
dispatch({ payload: 'logout' });
break;
}
// skip default
// refresh 만료
if (error.response.status === 403) {
dispatch({ payload: 'logout' });
// TODO: 세션이 만료됐다는 메세지 표시, 로그인 창 띄우기?
}
return Promise.reject(error);
}
Expand All @@ -81,6 +64,7 @@ export function UserProvider({ children }: { children: ReactNode }) {
console.warn('request FCM Token failed');
return;
}
console.log({ prevToken, currentToken });
if (prevToken !== currentToken) {
if (prevToken) await retireToken(prevToken);
dispatch({
Expand Down Expand Up @@ -111,9 +95,8 @@ export function UserProvider({ children }: { children: ReactNode }) {
function reducer(state: UserData, { payload, data }: Action): UserData {
switch (payload) {
case 'authenticate': {
const { info, refreshToken } = data;
const { info } = data;
localStorage.setItem('info', JSON.stringify(info));
localStorage.setItem('refreshToken', refreshToken);
return {
isAuthenticated: true,
...info,
Expand All @@ -125,9 +108,9 @@ function reducer(state: UserData, { payload, data }: Action): UserData {
return { ...state, major: data };
}
case 'logout': {
console.log('log out dispatch');
const info = JSON.parse(localStorage.getItem('info'));
localStorage.setItem('info', JSON.stringify({ major: info.major }));
localStorage.removeItem('refreshToken');
return { ...state, userId: undefined, name: undefined, isAuthenticated: false };
}
case 'setFcmToken': {
Expand Down
30 changes: 18 additions & 12 deletions src/hook/useKakaoLogin.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useContext, useEffect, useRef, useState } from "react";
import KakaoLogin from "react-kakao-login";
import { Props } from "react-kakao-login/lib/types";
import { useLoginQuery } from "../api/query";
import { userContext } from "../context";
import { useContext, useEffect, useRef, useState } from 'react';
import KakaoLogin from 'react-kakao-login';
import { Props } from 'react-kakao-login/lib/types';
import { useLoginQuery } from '../api/query';
import { ModalContext, userContext } from '../context';

const useKakaoLogin = () => {
const { userData, dispatch } = useContext(userContext);
const { modalDispatch } = useContext(ModalContext);

const kakaoLoginRef = useRef<any>();
const [{ id, accessToken }, setLoginData] = useState({
Expand All @@ -14,27 +15,32 @@ const useKakaoLogin = () => {
});
const { data } = useLoginQuery(id, accessToken);

const onSuccess: Props["onSuccess"] = async ({ response, profile }) => {
console.log("kakao login success", { response, profile });
const onSuccess: Props['onSuccess'] = async ({ response, profile }) => {
console.log('kakao login success', { response, profile });
const { id } = profile;
const { access_token: accessToken } = response;
setLoginData({ id, accessToken });
};

useEffect(() => {
console.log({ ref: kakaoLoginRef });
}, []);
// useEffect(() => {
// console.log({ ref: kakaoLoginRef });
// return () => {
// console.log('unmount');
// };
// }, []);

useEffect(() => {
if (!userData.isAuthenticated && data?.user) {
const { id, name, major } = data.user;
dispatch({
payload: "authenticate",
payload: 'authenticate',
data: {
info: { userId: id, name, major, fcmToken: localStorage.getItem('fcmToken') },
refreshToken: data.refreshToken,
},
});
modalDispatch({
payload: "close",
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data?.user]);
Expand Down
27 changes: 22 additions & 5 deletions src/pages/MyPage/GetSettingData/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,26 @@ import { UserData } from '../../../context';
import { ModalActions } from '../../../context/modal';

// UserInfo section component with useMemo
const UserInfoSection = (userData: UserData) => useMemo(() => ({
header: { title: userData?.name || '로그인 해주세요', icon: User },
}), [userData]);
const UserInfoSection = (userData: UserData, modalDispatch: Dispatch<ModalActions>) => useMemo(
() => ({
header: { title: userData?.name || '로그인 해주세요', icon: User },
contents: [
{
meta: '카카오 로그인',
description: '',
eventHandler: () => {
modalDispatch({
payload: 'myModal',
props: {
variant: 'login'
}
});
}
}
]
}),
[modalDispatch, userData?.name]
);

// Account section component with useMemo
const AccountSection = (
Expand All @@ -28,7 +45,7 @@ const AccountSection = (
props: {
variant: 'login'
}
})
});
}
},
{
Expand Down Expand Up @@ -95,7 +112,7 @@ export default function getSettingData(
dispatch,
modalDispatch: Dispatch<ModalActions>
) {
const userInfoSection = UserInfoSection(userData);
const userInfoSection = UserInfoSection(userData, modalDispatch);
const accountSection = AccountSection(userData, client, navigate, dispatch, modalDispatch);
const settingsSection = SettingsSection(userData, navigate);
const guideSection = GuideSection();
Expand Down
5 changes: 4 additions & 1 deletion src/pages/MyPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export function MyPage() {
);
return (
<Wrapper>
<PaperBox header={userInfoSection.header} />
<PaperBox header={userInfoSection.header}>
{userData.isAuthenticated || <SettingList content={userInfoSection.contents} />}
</PaperBox>
{accountSection && (
<PaperBox header={accountSection.header}>
<SettingList content={accountSection.contents} />
Expand Down Expand Up @@ -248,3 +250,4 @@ function ContentChild({ eventHandler, meta, description }: AlarmSettingCardConte
}

export * from './types.d';

Loading

0 comments on commit 0afb486

Please sign in to comment.