Skip to content

Commit

Permalink
Merge pull request #142 from Together42/141-TokenStorage_refactor
Browse files Browse the repository at this point in the history
Fix 로컬스토리지 관리 로직 수정
  • Loading branch information
seo-wo authored Dec 20, 2023
2 parents c285538 + e67bdbe commit 6ef0c1f
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 69 deletions.
28 changes: 0 additions & 28 deletions src/cert/AuthStorage.ts

This file was deleted.

101 changes: 85 additions & 16 deletions src/cert/TokenStorage.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,97 @@
const TOKEN = 'token';
const expireDays = 5;

const JWT_PART_LENGTH = 3;
const MS_PER_SEC = 1000;

enum JWTPart {
Header,
Payload,
Signature,
}
export interface JWTPayload {
id: string;
iat: number;
exp: number;
}

export function getJWTPart(token: string, part: JWTPart): string {
const parts = token.split('.');
if (parts.length !== JWT_PART_LENGTH) {
throw new Error('Invalid JWT token format.');
}
return parts[part];
}

export function isValidJWTPayload(object: any): object is JWTPayload {
return (
object !== null &&
typeof object === 'object' &&
'id' in object &&
typeof object.id === 'number' &&
'iat' in object &&
typeof object.iat === 'number' &&
'exp' in object &&
typeof object.exp === 'number'
);
}

export function parseJWTPayload(token: string): JWTPayload {
try {
const payload = decodeToken(token);
if (!isValidJWTPayload(payload)) {
throw new Error('Invalid JWTPayload');
}
return payload;
} catch (error) {
throw error;
}
}

export function isJWTExpired(token: string): boolean {
try {
const payload = parseJWTPayload(token);
return payload.exp < Date.now() / MS_PER_SEC;
} catch (error) {
console.error(error);
return true;
}
}

export function clearToken() {
localStorage.clear();
localStorage.removeItem(TOKEN);
}

export function saveToken(token: string) {
const tokenObj = {
value: token,
expire: Date.now() + expireDays * 24 * 60 * 60 * 1000,
};
const tokenObjString = JSON.stringify(tokenObj);
localStorage.setItem(TOKEN, tokenObjString);
localStorage.setItem(TOKEN, token);
}

export function getToken() {
const tokenObjString = localStorage.getItem(TOKEN);
if (!tokenObjString) {
return null;
}
const tokenObj = JSON.parse(tokenObjString);
if (Date.now() > tokenObj.expire) {
let token = localStorage.getItem(TOKEN);
if (token !== null && isJWTExpired(token)) {
clearToken();
return null;
token = null;
}
return tokenObj.value;
return token;
}

export function getDecodedToken(): { id: string; url: string } {
const token = localStorage.getItem(TOKEN);
if (!token) {
return { id: null, url: null };
}
const decoded = decodeToken(token);
return { id: decoded.username, url: decoded.imageUrl };
}

const decodeToken = (token: string) => {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join(''),
);

return JSON.parse(jsonPayload);
};
7 changes: 3 additions & 4 deletions src/components/Auth/AuthCallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { saveToken } from '@cert/TokenStorage';
import { useNavigate } from 'react-router-dom';
import { saveAuth, getAuth } from '@cert/AuthStorage';
import { getDecodedToken } from '@cert/TokenStorage';
import GlobalLoginState from '@recoil/GlobalLoginState';
import { useSetRecoilState } from 'recoil';

Expand All @@ -15,13 +15,12 @@ const AuthCallback = () => {

useEffect(() => {
saveToken(token);
saveAuth(token);
setLoginState(() => {
return {
id: getAuth().id,
id: getDecodedToken().id,
isLogin: true,
isAdmin: false,
profileUrl: getAuth().url,
profileUrl: getDecodedToken().url,
};
});
navigate('/');
Expand Down
2 changes: 0 additions & 2 deletions src/components/Auth/AuthSignUp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useSetRecoilState, useRecoilValue } from 'recoil';
import errorAlert from '@globalObj/function/errorAlert';
import apiClient from '@service/apiClient';
import { saveToken } from '@cert/TokenStorage';
import { saveAuth } from '@cert/AuthStorage';
import ProfileChangeModalShow from '@recoil/ProfileChangeModalShow';
import SignUpProfileState from '@recoil/SignUpProfileState';
import ProfileModal from '@auth/ProfileModal';
Expand Down Expand Up @@ -33,7 +32,6 @@ const AuthSignUp = () => {
})
.then((res) => {
saveToken(res.data.access_token);
saveAuth(res.data.access_token);
setLoginState(() => {
return {
id: res.data.id,
Expand Down
4 changes: 2 additions & 2 deletions src/components/Review/SelectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import useSWR from 'swr';
import getAddress from '@globalObj/function/getAddress';
import fetcher from '@globalObj/function/fetcher';
import EmptyEvent from '@globalObj/object/EmptyEvent';
import { getAuth } from '@cert/AuthStorage';
import { getDecodedToken } from '@cert/TokenStorage';

function SelectModal(prop: { mode: string }) {
const { data: eventList, mutate: mutateAllEvent } = useSWR<ReviewSelectedEventType[]>(
Expand Down Expand Up @@ -66,7 +66,7 @@ function SelectModal(prop: { mode: string }) {
setSelectedTeam({ [event]: memArr });
setEventListModalShow(false);
} else {
setSelectedTeam({ [event]: [{ intraId: getAuth().id, profile: getAuth().url, teamId: -1 }] });
setSelectedTeam({ [event]: [{ intraId: getDecodedToken().id, profile: getDecodedToken().url, teamId: -1 }] });
setEventListModalShow(false);
}
};
Expand Down
4 changes: 2 additions & 2 deletions src/components/Rotation/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction';
import { createEventId, getRotationArr } from './event_utils';
import '@css/Rotation/Calendar.scss';
import { getAuth } from '@cert/AuthStorage';
import { getDecodedToken } from '@cert/TokenStorage';
import { DAY_OF_SUNDAY } from './rotation_utils';
import apiClient from '@service/apiClient';

Expand All @@ -16,7 +16,7 @@ const COLOR = {

export default class Calendar extends React.Component {
state = {
auth: getAuth(),
auth: getDecodedToken(),
weekendsVisible: true,
currentEvents: [],
};
Expand Down
4 changes: 2 additions & 2 deletions src/components/Rotation/Rotation.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState, MouseEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { getAuth } from '@cert/AuthStorage';
import { getDecodedToken } from '@cert/TokenStorage';
import Calendar, { CalendarTileProperties } from 'react-calendar';
import getAddress from '@globalObj/function/getAddress';
import { getToken } from '@cert/TokenStorage';
Expand Down Expand Up @@ -330,7 +330,7 @@ export const Rotate = () => {
const navigate = useNavigate();
const currentDate = new Date();
const initialRecord = createInitialObject(currentDate);
const intraId = getAuth()?.id ?? null;
const intraId = getDecodedToken()?.id ?? null;
const isRotationApplicationPeriod = calculateIsRotationApplicationPeriod(currentDate);
const [record, setRecord] = useState(() => ({ ...initialRecord }));
const [isSubmit, setIsSubmit] = useState(false);
Expand Down
4 changes: 2 additions & 2 deletions src/components/Rotation/RotationResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import LoadingSpinner from './Loading';
import { RotateUserResult } from './RotateUserResult';
import { getRotationMonthArr } from './event_utils';
import { getAuth } from '@cert/AuthStorage';
import { getDecodedToken } from '@cert/TokenStorage';
import '@css/Rotation/Rotation.scss';

export const RotateResult = () => {
Expand All @@ -13,7 +13,7 @@ export const RotateResult = () => {
date < new Date(year, nextmonth, -1) ? (month = nextmonth - 1) : nextmonth;
const [Loading, setLoading] = useState(true);
const [arr, setArr] = useState([]);
const intraId = getAuth() ? getAuth().id : null;
const intraId = getDecodedToken() ? getDecodedToken().id : null;

const mainApi = async () => {
setLoading(true); // api 호출 전에 true로 변경하여 로딩화면 띄우기
Expand Down
4 changes: 2 additions & 2 deletions src/components/Rotation/event_utils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getAuth } from '@cert/AuthStorage';
import { getDecodedToken } from '@cert/TokenStorage';
import apiClient from '@service/apiClient';
import errorAlert from '@globalObj/function/errorAlert';

Expand All @@ -9,7 +9,7 @@ export function createEventId() {
}

function rotatedArrAllInfo(data) {
const intraId = getAuth() ? getAuth().id : null;
const intraId = getDecodedToken() ? getDecodedToken().id : null;
return data
.filter((el) => !!el.year && !!el.month && !!el.day)
.map((el) => ({
Expand Down
2 changes: 0 additions & 2 deletions src/components/utils/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useNavigate } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import GlobalLoginState from '@recoil/GlobalLoginState';
import { clearToken, getToken } from '@cert/TokenStorage';
import { removeAuth } from '@cert/AuthStorage';
import { Dropdown } from './Dropdown';
import getAddress from '@globalObj/function/getAddress';
import apiClient from '@service/apiClient';
Expand All @@ -18,7 +17,6 @@ function Navbar() {
apiClient
.post('/auth/logout')
.then((res) => {
removeAuth();
clearToken();
setLoginState(() => {
return {
Expand Down
4 changes: 2 additions & 2 deletions src/globalObj/object/EmptyEvent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getAuth } from '@cert/AuthStorage';
import { getDecodedToken } from '@cert/TokenStorage';
import { ReviewSelectedEventType } from './types';

function EmptyEvent(): ReviewSelectedEventType {
Expand All @@ -9,7 +9,7 @@ function EmptyEvent(): ReviewSelectedEventType {
createdId: 1,
intraId: 'tkim',
isMatching: 1,
teamList: { 사서: [{ intraId: getAuth().id, profile: getAuth().url, teamId: -1 }] },
teamList: { 사서: [{ intraId: getDecodedToken().id, profile: getDecodedToken().url, teamId: -1 }] },
};
}

Expand Down
10 changes: 5 additions & 5 deletions src/recoil/GlobalLoginState.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { atom } from 'recoil';
import { userData } from '@globalObj/object/types';
import { getAuth } from '@cert/AuthStorage';
import { getDecodedToken } from '@cert/TokenStorage';

const value = getAuth()
const value = getDecodedToken()
? {
isLogin: true,
isAdmin: getAuth()['id'] === 'tkim',
id: getAuth()['id'],
profileUrl: getAuth()['url'],
isAdmin: getDecodedToken()['id'] === 'tkim',
id: getDecodedToken()['id'],
profileUrl: getDecodedToken()['url'],
}
: {
isLogin: false,
Expand Down

0 comments on commit 6ef0c1f

Please sign in to comment.