Skip to content
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

[hotfix/#102/UserInfo] 유저 정보 저장 로직 수정 #103

Merged
merged 9 commits into from
Mar 12, 2024
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ const queryClient = new QueryClient({
gcTime: 1000 * 60 * 3,
staleTime: 1000 * 60,
refetchOnWindowFocus: false,
retry: 0,
},
mutations: {
gcTime: 1000 * 60 * 3,
retry: 0,
},
},
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { getEmailAuthResponseType } from "api-models"
import { postEmailAuthResponseType } from "api-models"

import { ENDPOINTS } from "@constants/endPoints"

import { authInstance } from "../axiosInstance"

export const getEmailAuth = async () => {
export const postEmailAuth = async () => {
//TODO: accessToken을 소셜 로그인과 이메일 로그인으로 분리하기위해 접두어로 'email', 'github' 등을 붙여야해서 slice로 잘라내는 작업 필요
const { data } = await authInstance.get<getEmailAuthResponseType>(
const { data } = await authInstance.post<postEmailAuthResponseType>(
ENDPOINTS.EMAIL_AUTH,
)

Expand Down
17 changes: 6 additions & 11 deletions src/api/auth/postEmailRefresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@ import {
postEmailRefreshPayload,
postEmailRefreshResponseType,
} from "api-models"
import { AxiosRequestConfig } from "axios"

import { ENDPOINTS } from "@constants/endPoints"

import { baseInstance } from "../axiosInstance"

export const postEmailRefresh = async (
{ refreshToken }: postEmailRefreshPayload,
config: AxiosRequestConfig = {},
) => {
const {
data: { accessToken },
} = await baseInstance.post<postEmailRefreshResponseType>(
export const postEmailRefresh = async ({
refreshToken,
}: postEmailRefreshPayload) => {
const { data } = await baseInstance.post<postEmailRefreshResponseType>(
ENDPOINTS.EMAIL_REFRESH,
{},
{ ...config, headers: { Authorization: `Bearer ${refreshToken}` } },
{ refreshToken },
)

return accessToken
return data
}
79 changes: 58 additions & 21 deletions src/api/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import axios, { isAxiosError } from "axios"

import authToken from "@stores/authToken"

import { LogoutError, PermissionError } from "@constants/customError"

import { postEmailRefresh } from "./auth/postEmailRefresh"

const { VITE_BASE_URL } = import.meta.env
Expand All @@ -15,18 +17,41 @@ export const authInstance = axios.create({
//TODO: 소셜 로그인 로직 추가 예정
authInstance.interceptors.request.use(
async (config) => {
// const accessToken = authToken.getAccessToken()
// const refreshToken = authToken.getRefreshToken()

// if (!refreshToken) {
// throw new AxiosError("Login Required")
// }

// if (!accessToken) {
// const currentAccessToken = await postEmailRefresh({ refreshToken })
// authToken.setAccessToken(currentAccessToken)
// config.headers.Authorization = `Bearer ${currentAccessToken}`
// }
const accessToken = authToken.getAccessToken()
const refreshToken = authToken.getRefreshToken()

config.headers.Authorization = `Bearer ${accessToken}`

if (!refreshToken) {
const permission = new PermissionError()

if (import.meta.env.DEV) console.error(permission)

throw permission
}

if (!accessToken) {
try {
const data = await postEmailRefresh({ refreshToken })
authToken.setAccessToken(data.accessToken)
authToken.setRefreshToken(data.refreshToken)
config.headers.Authorization = `Bearer ${data.accessToken}`
} catch (refreshError) {
if (
isAxiosError(refreshError) &&
refreshError.response?.status === 401
) {
const logout = new LogoutError()

if (import.meta.env.DEV) console.error(logout)

throw logout
}

throw refreshError
}
}

return config
},
(error) => {
Expand All @@ -38,13 +63,14 @@ authInstance.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config
if (isAxiosError(error) && error.status === 401) {
if (isAxiosError(error) && error.response?.status === 401) {
const refreshToken = authToken.getRefreshToken()

try {
const currentAccessToken = await postEmailRefresh({ refreshToken })
authToken.setAccessToken(currentAccessToken)
originalRequest.headers.Authorization = `Bearer ${currentAccessToken}`
const data = await postEmailRefresh({ refreshToken })
authToken.setAccessToken(data.accessToken)
authToken.setRefreshToken(data.refreshToken)
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`

// 재발급된 엑세스 토큰으로 재요청
return baseInstance(originalRequest)
Expand All @@ -53,12 +79,23 @@ authInstance.interceptors.response.use(
1. 🟨 로그아웃 api 요청
2. 🟨 react-query의 유저 정보 캐싱 초기화
3. ✅ accessToken, refreshToken 초기화 */
authToken.removeAccessToken()
authToken.removeRefreshToken()
if (import.meta.env.DEV) {
console.error()
if (
isAxiosError(refreshError) &&
refreshError.response?.status === 401
) {
authToken.removeAccessToken()
authToken.removeRefreshToken()

if (import.meta.env.DEV) {
console.error("로그아웃 처리됩니다.")
}

const logout = new LogoutError()

throw logout
}
throw new Error("권한이 없습니다. 로그인 해주세요.")

throw refreshError
}
}
return Promise.reject(error)
Expand Down
9 changes: 9 additions & 0 deletions src/constants/customError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class LogoutError extends Error {
name = "Logout"
message = "로그아웃 처리됩니다."
}

export class PermissionError extends Error {
name = "PermissionError"
message = "권한이 없습니다."
}
4 changes: 2 additions & 2 deletions src/constants/endPoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ const VARIABLE_URL = "/api/v1"
export const ENDPOINTS = {
GITHUB_LOGIN: `${VARIABLE_URL}/auth/login/github`,
EMAIL_LOGIN: `${VARIABLE_URL}/auth/login`,
EMAIL_REFRESH: `${VARIABLE_URL}/auth/refresh`,
EMAIL_AUTH: `${VARIABLE_URL}/auth/login/me`,
EMAIL_REFRESH: `${VARIABLE_URL}/auth/reissue`,
EMAIL_AUTH: `${VARIABLE_URL}/auth/me`,
EMAIL_SIGNUP: `${VARIABLE_URL}/users/signup`,
GET_USER_NICKNAME: `${VARIABLE_URL}/users?keyword=`,
GET_USER_PROFILE: (userId: number) => `${VARIABLE_URL}/users/${userId}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getEmailAuthResponseType } from "api-models"
import { postEmailAuthResponseType } from "api-models"
import { rest } from "msw"

import { ENDPOINTS } from "@constants/endPoints"

export const getEmailAuth = rest.get(ENDPOINTS.EMAIL_AUTH, (_, res, ctx) => {
const response: getEmailAuthResponseType = {
export const postEmailAuth = rest.post(ENDPOINTS.EMAIL_AUTH, (_, res, ctx) => {
const response: postEmailAuthResponseType = {
id: 1,
nickname: "admin",
profileImageUrl: null,
Expand Down
6 changes: 6 additions & 0 deletions src/mocks/auth/postEmailRefresh.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export const postEmailRefresh = rest.post(
(_, res, ctx) => {
const response: postEmailRefreshResponseType = {
accessToken: "mock-accessToken",
refreshToken: "mock-refreshToken",
user: {
id: 0,
nickname: "admin",
profileImageUrl: null,
},
}
return res(ctx.status(200), ctx.json(response))
},
Expand Down
4 changes: 2 additions & 2 deletions src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import allProjectHandlers from "@pages/HomePage/mocks"
import { projectsHandlers, userInfoHandlers } from "@pages/ProfilePage/mocks"
import { projectDetailHandlers } from "@pages/ProjectDetailPage/mocks"

import { getEmailAuth } from "./auth/getEmailAuth.mock"
import { postEmailAuth } from "./auth/postEmailAuth.mock"
import { postEmailLogin } from "./auth/postEmailLogin.mock"
import { postEmailRefresh } from "./auth/postEmailRefresh.mock"

Expand All @@ -14,7 +14,7 @@ export const handlers = [
...allProjectHandlers,
postEmailRefresh,
postEmailLogin,
getEmailAuth,
postEmailAuth,
...userInfoHandlers,
...projectsHandlers,
]
6 changes: 3 additions & 3 deletions src/services/caches/useUserInfoData.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useQuery } from "@tanstack/react-query"
import { postEmailAuth } from "@api/auth/postEmailAuth"

import { getEmailAuth } from "@/api/auth/getEmailAuth"
import { useQuery } from "@tanstack/react-query"

import { QUERYKEY } from "@constants/queryKey"

export const useUserInfoData = () => {
return useQuery<Awaited<ReturnType<typeof getEmailAuth>>>({
return useQuery<Awaited<ReturnType<typeof postEmailAuth>>>({
queryKey: [QUERYKEY.USER_INFO],
}).data
}
8 changes: 4 additions & 4 deletions src/services/queries/userInfoOptions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { UseQueryOptions } from "@tanstack/react-query"
import { postEmailAuth } from "@api/auth/postEmailAuth"

import { getEmailAuth } from "@/api/auth/getEmailAuth"
import { UseQueryOptions } from "@tanstack/react-query"

import { QUERYKEY } from "@constants/queryKey"

export const userInfoOptions: UseQueryOptions<
Awaited<ReturnType<typeof getEmailAuth>>
Awaited<ReturnType<typeof postEmailAuth>>
> = {
queryKey: [QUERYKEY.USER_INFO],
queryFn: getEmailAuth,
queryFn: () => postEmailAuth(),
}
6 changes: 4 additions & 2 deletions src/stores/authToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ class AuthToken {
private REFRESH_KEY: string

constructor() {
this.accessToken = ""
this.refreshToken = ""
this.accessToken =
localStorage.getItem(VITE_AUTH_JWT_TOKEN_STORAGE_KEY) ?? ""
this.refreshToken =
localStorage.getItem(VITE_REFRESH_JWT_TOKEN_STORAGE_KEY) ?? ""
this.ACCESS_KEY = VITE_AUTH_JWT_TOKEN_STORAGE_KEY
this.REFRESH_KEY = VITE_REFRESH_JWT_TOKEN_STORAGE_KEY
}
Expand Down
4 changes: 3 additions & 1 deletion src/types/domain/apiDomain.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,16 @@ declare module "api-models" {
}

/* 인증 관련 */
export type getEmailAuthResponseType = UserSummary
export type postEmailAuthResponseType = UserSummary

export type postEmailRefreshPayload = {
refreshToken: string
}

export type postEmailRefreshResponseType = {
accessToken: string
refreshToken: string
user: UserSummary
}

export type postEmailLoginPayload = {
Expand Down
Loading