Skip to content

Commit 6da8353

Browse files
committed
feat: 로그인 기능 임시 구현
- API 완성 되지 않음 : 논의된 부분을 바탕으로 임시로 제작함(작동 안함) - 토큰을 전달 받기 위해 권한 상태 페이지 추가(보여주기 위한 용도가 아니라 쿼리문으로 토큰 전달을 위한 목적)
1 parent 30bc2f2 commit 6da8353

File tree

8 files changed

+180
-21
lines changed

8 files changed

+180
-21
lines changed

src/App.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Route, Routes } from 'react-router';
22

3+
import { useAxiosIntercepter } from './apis/client';
34
import useViewport from './hooks/useViewport';
45
import Layout from './layouts/Layout';
56
import MobileLayout from './layouts/MobileLayout';
67
import AdminPage from './pages/Admin';
78
import FilteredLetterManage from './pages/Admin/FilteredLetter';
89
import FilteringManage from './pages/Admin/Filtering';
910
import ReportManage from './pages/Admin/Report';
11+
import AuthCallbackPage from './pages/Auth';
1012
import Home from './pages/Home';
1113
import Landing from './pages/Landing';
1214
import LetterBoardPage from './pages/LetterBoard';
@@ -25,6 +27,7 @@ import WritePage from './pages/Write';
2527

2628
const App = () => {
2729
useViewport();
30+
useAxiosIntercepter();
2831

2932
return (
3033
<Routes>
@@ -54,7 +57,8 @@ const App = () => {
5457
<Route path="board" element={<LetterBoardPage />} />
5558
<Route path="notifications" element={<NotificationsPage />} />
5659
</Route>
57-
<Route path="*" element={<NotFoundPage />}></Route>
60+
<Route path="*" element={<NotFoundPage />} />
61+
<Route path="auth-callback" element={<AuthCallbackPage />} />
5862
</Route>
5963

6064
<Route path="admin" element={<AdminPage />}>

src/apis/auth.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
1-
// import useAuthStore from '@/stores/authStore';
2-
3-
// import client from './client';
1+
import client from './client';
42

53
type LoginType = 'kakao' | 'naver' | 'google';
64
export const socialLogin = (loginType: LoginType) => {
75
// eslint-disable-next-line react-hooks/rules-of-hooks
86
// const { setUserId, setZipCode, login } = useAuthStore.getState();
9-
window.location.href = `http://13.209.132.150:8081/oauth2/authorization/${loginType}`;
7+
8+
window.location.href = `http://13.209.132.150:8081/oauth2/authorization/${loginType}`;
9+
};
10+
11+
//임시 코드
12+
export const getUserToken = async (stateToken: string) => {
13+
try {
14+
const response = await client.get(`/api/auth/token?state=${stateToken}`);
15+
if (!response) throw new Error('getUserToken: Error while fetching user token');
16+
const userInfo = response.headers['authorization'];
17+
18+
if (userInfo) {
19+
return userInfo;
20+
}
21+
} catch (error) {
22+
console.error(error);
23+
}
24+
};
25+
26+
export const getZipCode = async (accessToken: string) => {
27+
try {
28+
const response = await client.get(`/members/zipCode`, {
29+
headers: {
30+
Authorization: `Bearer ${accessToken}`,
31+
},
32+
});
33+
if (!response) throw new Error('getZipCode: no response data');
34+
console.log(response);
35+
return response;
36+
} catch (error) {
37+
console.error(error);
38+
}
1039
};
1140

41+
export const getNewToken = async () => {
42+
try {
43+
const response = await client.get('/api/reissue', { withCredentials: true });
44+
if (!response) throw new Error('getNewTOken: no response data');
45+
return response;
46+
} catch (error) {
47+
console.error(error);
48+
}
49+
};

src/apis/client.ts

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,67 @@
11
import axios from 'axios';
2+
import { useLayoutEffect } from 'react';
3+
4+
import useAuthStore from '@/stores/authStore';
5+
6+
import { getNewToken } from './auth';
27

38
const client = axios.create({
49
baseURL: import.meta.env.VITE_API_URL,
510
});
611

7-
client.interceptors.request.use(
8-
(config) => {
9-
const token = localStorage.getItem('authToken');
10-
if (token) {
11-
config.headers['Authorization'] = `Bearer ${token}`;
12-
}
13-
return config;
14-
},
15-
(error) => {
16-
//TODO: 에러처리
17-
return Promise.reject(error);
18-
},
19-
);
12+
// eslint-disable-next-line react-hooks/rules-of-hooks
13+
14+
export const useAxiosIntercepter = () => {
15+
const { accessToken, setAccessToken } = useAuthStore();
16+
const authIntercepter = client.interceptors.request.use(
17+
(config) => {
18+
config.headers.Authorization =
19+
accessToken && !config.headers['x-retry']
20+
? `Bearer ${accessToken}`
21+
: config.headers.Authorization;
22+
return config;
23+
},
24+
(error) => {
25+
return Promise.reject(error);
26+
},
27+
);
28+
29+
const refreshInterceptor = client.interceptors.response.use(
30+
(response) => response,
31+
async (error) => {
32+
const originalRequest = error.config;
33+
34+
if (
35+
(error.response.status === 401 || error.response.status === 403) &&
36+
error.response.data.message === 'Unauthorized'
37+
) {
38+
try {
39+
const response = await getNewToken();
40+
setAccessToken(response?.data.accessToken);
41+
42+
originalRequest.headers.Authorization = `Bearer ${response?.data.accessToken}`;
43+
originalRequest.headers['x-retry'] = true;
44+
45+
return client(originalRequest);
46+
} catch {
47+
setAccessToken('');
48+
}
49+
}
50+
return Promise.reject(error);
51+
},
52+
);
53+
54+
useLayoutEffect(() => {
55+
return () => {
56+
client.interceptors.request.eject(authIntercepter);
57+
};
58+
}, [accessToken]);
59+
60+
useLayoutEffect(() => {
61+
return () => {
62+
client.interceptors.response.eject(refreshInterceptor);
63+
};
64+
}, [accessToken]);
65+
};
2066

2167
export default client;

src/pages/Auth/index.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { useEffect, useState } from 'react';
2+
import { useNavigate } from 'react-router';
3+
4+
import { getUserToken } from '@/apis/auth';
5+
import useAuthStore from '@/stores/authStore';
6+
7+
const AuthCallbackPage = () => {
8+
const stateToken = new URLSearchParams(window.location.search).get('state');
9+
const { setUserId, setZipCode, setAccessToken, zipCode } = useAuthStore.getState();
10+
const [role, setRole] = useState<string>('');
11+
12+
const navigate = useNavigate();
13+
14+
const setUserInfo = async (stateToken: string) => {
15+
try {
16+
const userInfo = await getUserToken(stateToken);
17+
if (!userInfo) throw new Error('Error Fetching userInfo');
18+
19+
const accessToken = userInfo.match(/Bearer\s+(\S+)/);
20+
if (accessToken) setAccessToken(accessToken[1]);
21+
22+
const role = userInfo.match(/Role=([\w-]+)/);
23+
if (role) setRole(role);
24+
25+
const userId = userInfo.match(/UserId=(\d+)/);
26+
if (userId) setUserId(userId[1]);
27+
28+
const zipCode = userInfo.match(/ZipCode=([A-Za-z0-9]+)/);
29+
if (zipCode) setZipCode(zipCode[1]);
30+
} catch (error) {
31+
console.error(error);
32+
}
33+
};
34+
35+
const redirection = () => {
36+
if (role === 'admin') navigate('/report');
37+
else {
38+
if (!zipCode) navigate('/onboarding');
39+
else navigate('/');
40+
}
41+
};
42+
43+
useEffect(() => {
44+
if (stateToken) {
45+
setUserInfo(stateToken as string);
46+
redirection();
47+
} else navigate('/notFound');
48+
}, []);
49+
return <div></div>;
50+
};
51+
52+
export default AuthCallbackPage;

src/pages/Onboarding/SetZipCode.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
import { useEffect, useState } from 'react';
22

3+
import { getZipCode } from '@/apis/auth';
4+
import useAuthStore from '@/stores/authStore';
5+
36
import Spinner from './components/Spinner';
47

58
const SetZipCode = ({
69
setIsZipCodeSet,
710
}: {
811
setIsZipCodeSet: React.Dispatch<React.SetStateAction<boolean>>;
912
}) => {
10-
const DUMMY_ZIPCODE = '122A2';
13+
const [zipCode, setZipCode] = useState<string>('');
1114
const [isBtnActive, setIsBtnActive] = useState<boolean>(false);
15+
const { accessToken } = useAuthStore.getState();
1216

17+
const fetchZipCode = async () => {
18+
try {
19+
const response = await getZipCode(accessToken as string);
20+
if (!response) throw new Error('fetchZipCode: no response');
21+
console.log(response.data.zipCode);
22+
setZipCode(response.data.zipCode);
23+
} catch (error) {
24+
console.error(error);
25+
}
26+
};
1327
useEffect(() => {
28+
fetchZipCode();
1429
setTimeout(() => {
1530
setIsBtnActive(true);
1631
}, 6300);
@@ -23,7 +38,7 @@ const SetZipCode = ({
2338
<p className="caption-sb text-gray-60">사용자님이 편지를 주고 받는 주소입니다.</p>
2439
</header>
2540
<section className="flex gap-2">
26-
{DUMMY_ZIPCODE.split('').map((char, index) => (
41+
{zipCode.split('').map((char, index) => (
2742
<Spinner key={index} target={char} index={index}></Spinner>
2843
))}
2944
</section>

src/pages/Onboarding/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
22

33
import SetZipCode from './SetZipCode';
44
import UserInteraction from './UserInteraction';
5-
import WelcomeLetter from './welcomeLetter';
5+
import WelcomeLetter from './WelcomeLetter';
66

77
const OnboardingPage = () => {
88
const [isZipCodeSet, setIsZipCodeSet] = useState<boolean>(false);

src/stores/authStore.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,25 @@ interface AuthStore {
55
isLoggedIn: boolean;
66
userId: number | null;
77
zipCode: string;
8+
accessToken: string;
89
login: () => void;
910
logout: () => void;
1011
setUserId: (userId: number) => void;
1112
setZipCode: (zipCode: string) => void;
13+
setAccessToken: (accessToken: string) => void;
1214
}
1315
const useAuthStore = create(
1416
persist<AuthStore>(
1517
(set) => ({
1618
isLoggedIn: false,
19+
accessToken: '',
1720
userId: null,
1821
zipCode: '',
1922
login: () => set({ isLoggedIn: true }),
2023
logout: () => set({ isLoggedIn: false, userId: null, zipCode: '' }),
2124
setUserId: (userId) => set({ userId: userId }),
2225
setZipCode: (zipCode) => set({ zipCode: zipCode }),
26+
setAccessToken: (accessToken) => set({ accessToken: accessToken }),
2327
}),
2428
{
2529
name: 'userInfo',

src/utils/refreshTokenRotation.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)