Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…ient into feat/#131
  • Loading branch information
cjy3458 committed Jul 22, 2024
2 parents 80b370a + 2020c1a commit c767dbc
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 148 deletions.
101 changes: 101 additions & 0 deletions src/apis/authAxois.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import axios from 'axios';
import { getNewToken } from './login';
import LocalStorage from '@/utils/localStorage';

const baseURL = process.env.NEXT_PUBLIC_SERVER_URL;

const parseJwt = (token: string | null) => {
if (token) return JSON.parse(atob(token.split('.')[1]));
};

// 토큰 유효성 검사 및 재발급
export const checkAndRefreshToken = async () => {
const accessToken = LocalStorage.getItem('access');
const refreshToken = LocalStorage.getItem('refresh');

if (!accessToken || !refreshToken) {
// console.log('필요 토큰 미존재');
return null;
}

const decodedAccess = parseJwt(accessToken);
const decodedRefresh = parseJwt(refreshToken);

if (decodedAccess && decodedAccess.exp * 1000 < Date.now()) {
// console.log('access 토큰 만료');
if (decodedRefresh && decodedRefresh.exp * 1000 >= Date.now()) {
try {
// console.log('자동 재연장')
const { accessToken: newAccessToken } = await getNewToken();
return newAccessToken;
} catch (error) {
LocalStorage.removeItem('access');
LocalStorage.removeItem('refresh');
return null;
}
} else {
// console.log('Refresh 토큰 만료');
alert('토큰 만료로 자동 로그아웃되었습니다. 다시 로그인해주세요.');
LocalStorage.removeItem('access');
LocalStorage.removeItem('refresh');
return null;
}
}

return accessToken;
};

// 토큰 유효성 검사 함수
export const AuthVerify = async () => {
const accessToken = await checkAndRefreshToken();

if (!accessToken) {
return false;
}
return true;
};

// 주어진 토큰을 사용하여 API 요청에 인증된 Axios 인스턴스를 초기화
export const getAuthAxios = (token: string | null) => {
// Axios 인스턴스 생성
const authAxios = axios.create({
baseURL,
timeout: 8000,
headers: {
Authorization: `Bearer ${token}`,
},
});

// 응답 인터셉터 설정
authAxios.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;

try {
// 토큰 갱신 시도
const { accessToken } = await getNewToken();

if (!accessToken) {
throw new Error('토큰 갱신 실패');
}

// 갱신된 토큰으로 원래 요청 재시도
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
LocalStorage.setItem('access', accessToken);

return authAxios(originalRequest); // 재시도된 요청 반환
} catch (refreshError) {
alert('토큰이 만료되었습니다. 재로그인 후 시도해주세요!');
LocalStorage.removeItem('access');
LocalStorage.removeItem('refresh');
window.location.href = '/login'; // 로그인 페이지로 리다이렉트
return Promise.reject(refreshError); // 에러 전달
}
},
);

return authAxios;
};
15 changes: 7 additions & 8 deletions src/apis/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import axios, { AxiosResponse } from 'axios';
import { Server } from './settings';
import LocalStorage from '@/utils/localStorage';
import { ISearchResult, ISearchResultList, SearchResult } from '@/types/search';
import { getAuthAxios } from './authAxois';

const baseURL = process.env.NEXT_PUBLIC_SERVER_URL;

Expand All @@ -22,15 +23,13 @@ export const getSearchResult = async (keyword: string): Promise<ISearchResult |

export const newDocs = async (body: CreateDocs) => {
try {
const token = LocalStorage.getItem('access');
const result = await axios.post(`${baseURL}docs/`, body, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return result.data;
const access = LocalStorage.getItem('access');

const authAxios = getAuthAxios(access);
const response = await authAxios.post(`${baseURL}docs/`, body);

return response.data;
} catch (error) {
alert('로그아웃 후 다시 시도해주세요!');
throw error;
}
};
Expand Down
81 changes: 23 additions & 58 deletions src/apis/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,71 +18,36 @@ export const postCode = async (body: postCodeBody) => {
}
return response.data;
} catch (error) {
console.error('에러 발생', error);
// console.error('에러 발생', error);
throw error;
}
};

// 토큰 재발급
// export const getNewRefreshToken = async () => {
// const accessToken = localStorage.getItem("access");
// const refreshToken = localStorage.getItem("refresh");
// const result = await axios.post(
// "http://front.cau-likelion.org/refresh",
// {
// // 요청 body
// refreshToken,
// },
// {
// // headers.Authorization 필수
// headers: {
// Authorization: accessToken,
// },
// }
// );
// console.log(result)
// return result.data;
// };

// 회원가입
export const signUp = async (emailInput: string, nameInput: string) => {
// 구글 로그인 후 context에 이메일 저장하고 있음
export const getNewToken = async () => {
const refreshToken = localStorage.getItem('refresh');
try {
const response = await axios.post(`${baseURL}users/signup/`, { email: emailInput, name: nameInput });
if (response.data.status === '201') {
const accessToken = response.data.data.token.access_token;
const refreshToken = response.data.data.token.refresh_token;

LocalStorage.setItem('access', accessToken);
LocalStorage.setItem('refresh', refreshToken);
const response = await axios.post(
`${baseURL}users/token/refresh/`,
{
refresh: refreshToken,
},
{
headers: {
Authorization: `Bearer ${refreshToken}`,
},
},
);

if (response.status === 200) {
const newAccessToken = response.data.access;
return {
accessToken: newAccessToken,
};
} else {
throw new Error('토큰 갱신 실패');
}
return response.data;
} catch (error) {
console.log(error);
return false;
}
};

// 랜덤 닉네임 부여
export const getRandomNickname = async () => {
try {
const response = await axios.get(`${baseURL}users/rand-name/`);
return response.data;
} catch (error) {
console.log(error);
return false;
}
};

// 닉네임 중복여부 확인
export const checkNickName = async (name: string) => {
var resultString = name.replace(/\s+/g, '+');
try {
const response = await axios.get(`${baseURL}users/check-name/?name=${resultString}`);
return response.data;
} catch (error) {
// 사용할 수 없는 닉네임
console.log(error);
return false;
throw new Error('토큰 갱신 요청 실패');
}
};
47 changes: 47 additions & 0 deletions src/apis/signup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import axios from 'axios';
import LocalStorage from '@/utils/localStorage';

const baseURL = process.env.NEXT_PUBLIC_SERVER_URL;

// 회원가입
export const signUp = async (emailInput: string, nameInput: string) => {
// 구글 로그인 후 context에 이메일 저장하고 있음
try {
const response = await axios.post(`${baseURL}users/signup/`, { email: emailInput, name: nameInput });
if (response.data.status === '201') {
const accessToken = response.data.data.token.access_token;
const refreshToken = response.data.data.token.refresh_token;

LocalStorage.setItem('access', accessToken);
LocalStorage.setItem('refresh', refreshToken);
}
return response.data;
} catch (error) {
console.log(error);
return false;
}
};

// 랜덤 닉네임 부여
export const getRandomNickname = async () => {
try {
const response = await axios.get(`${baseURL}users/rand-name/`);
return response.data;
} catch (error) {
console.log(error);
return false;
}
};

// 닉네임 중복여부 확인
export const checkNickName = async (name: string) => {
let resultString = name.replace(/\s+/g, '+');
try {
const response = await axios.get(`${baseURL}users/check-name/?name=${resultString}`);
return response.data;
} catch (error) {
// 사용할 수 없는 닉네임
console.log(error);
return false;
}
};
46 changes: 26 additions & 20 deletions src/components/common/navbar/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
'use client';

import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { token } from '@/app/recoilContextProvider';
import { useRecoilValue } from 'recoil';
import { usePathname, useRouter } from 'next/navigation';
import { getRandomDoc } from '@/apis/viewer';
import { useEffect, useState } from 'react';
import { useMediaQuery } from 'react-responsive';
import SearchHeaderInput from '@/components/search/searchHeaderInput/searchHeaderInput';
import { AuthVerify } from '@/apis/authAxois';
import * as S from './NavBar.styled';

export interface IMenu {
Expand All @@ -18,8 +17,7 @@ export interface IMenu {
const NavBar = () => {
const isMobile = useMediaQuery({ query: '(max-width: 540px)' });
const router = useRouter();

const { access: tokenState } = useRecoilValue(token);
const pathname = usePathname();
const [isLogin, setIsLogin] = useState(false);

const gotoRandomDoc = async () => {
Expand All @@ -29,9 +27,24 @@ const NavBar = () => {
router.push(`/viewer?title=${encodedTitle}`);
};

const handlePostClick = () => {
if (!isLogin) {
alert('🦁 로그인을 먼저 해주세요 🦁');
router.push('/login');
} else {
router.push('/post');
}
};

// 유효한 토큰을 가진 경우에만 상태 변경
useEffect(() => {
if (tokenState) setIsLogin(true);
}, [tokenState]);
const checkLoginStatus = async () => {
const loginStatus = await AuthVerify();
setIsLogin(loginStatus === true);
};

checkLoginStatus();
}, [pathname]);

return (
<>
Expand Down Expand Up @@ -79,14 +92,11 @@ const NavBar = () => {
<Image
onClick={() => {
if (!isLogin) {
alert('🦁 로그인을 먼저 해주세요 🦁');
router.push('/login');
} else {
router.push('/post');
}
}}
src="/img/newPost.png"
alt={'newPost'}
src={isLogin ? '/img/welcome.png' : '/img/login.png'}
alt={isLogin ? '로그인버튼' : '로그인'}
width={44}
height={44}
style={{ cursor: 'pointer' }}
Expand All @@ -100,17 +110,13 @@ const NavBar = () => {
height={44}
style={{ cursor: 'pointer' }}
/>
{isLogin ? (
<Image src="/img/welcome.png" width={44} height={44} alt={'로그인버튼'} />
) : (
{!isMobile && (
<Image
src="/img/login.png"
onClick={() => {
router.push(`/login`);
}}
onClick={handlePostClick}
src="/img/newPost.png"
alt={'newPost'}
width={44}
height={44}
alt={'로그인버튼'}
style={{ cursor: 'pointer' }}
/>
)}
Expand Down
Loading

0 comments on commit c767dbc

Please sign in to comment.