diff --git a/src/utils/api.js b/src/utils/api.js index e7b443c..862d99f 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,7 +1,9 @@ /* eslint-disable no-console */ /* eslint-disable no-unused-vars */ import axios from 'axios'; +import { StatusCodes } from 'http-status-codes'; import usePopup from '@components/usePopup'; +import { clearCookie, setCookie } from './cookie'; const { API_URL_BASE } = process.env; @@ -75,6 +77,7 @@ axiosInstance.interceptors.request.use( if (token) { config.headers.Authorization = token; } + config.withCredentials = true; return config; }, error => { @@ -87,9 +90,38 @@ axiosInstance.interceptors.request.use( axiosInstance.interceptors.response.use( config => config, - error => Promise.resolve(error.response), + error => { + console.error(error.stack); + return Promise.reject(error.response); + }, ); +function handleStatus(error) { + const { status } = error; + + // 401 + if (status === StatusCodes.UNAUTHORIZED) { + // redirect to login page + removeToken(); + removeUserID(); + removeRefreshToken(); + window.location.href = '/login'; + } + + // TODO: Edit here for other status codes +} + +// if access_token expired, refresh the token +function checkAccessToken() { + // get new token from cookie + const match = document.cookie.match(/(^| )access_token=([^;]+)/); + if (!match) return; + + const token = decodeURIComponent(match[2]); + setToken(token); + clearCookie('access_token'); +} + export async function requestAPI(config) { try { if (config.needToken && !getToken()) { @@ -102,14 +134,17 @@ export async function requestAPI(config) { delete config.needToken; const response = await axiosInstance.request(config); + checkAccessToken(); + // for test console.log(response); - // includes 3xx, 4xx responses + // includes 2xx responses return response; } catch (error) { - console.error(error); - return null; + handleStatus(error); + + return error; } } @@ -125,6 +160,14 @@ export function removeToken() { localStorage.removeItem('token'); } +export function setRefreshToken(refreshToken, options = {}) { + setCookie('refresh_token', refreshToken, options); +} + +export function removeRefreshToken() { + clearCookie('refresh_token'); +} + export function setUserID(userID) { localStorage.setItem('userID', userID); } diff --git a/src/utils/cookie.js b/src/utils/cookie.js new file mode 100644 index 0000000..eac3d2b --- /dev/null +++ b/src/utils/cookie.js @@ -0,0 +1,12 @@ +export function setCookie(name, value, options = {}) { + const cookie = `${name}=${value}`; + const optionString = Object.entries(options).map(([key, value]) => ( + value === true ? `${key}` : `${key}=${value}` + )).join(';'); + + document.cookie = `${cookie};${optionString}`; +} + +export function clearCookie(name) { + setCookie(name, '', { 'max-age': -1 }); +} diff --git a/src/views/login/index.jsx b/src/views/login/index.jsx index e93e62e..a6795f7 100644 --- a/src/views/login/index.jsx +++ b/src/views/login/index.jsx @@ -6,7 +6,7 @@ import { Button, Box, makeStyles, Container, Typography, Link } from '@material- import Loading from '@components/Loading'; import UosInput from '@components/UosInput'; import { semesterState } from '@states/Semester'; -import { API_LOGIN, API_GET_SEMESTER, requestAPI, getToken, setToken, removeUserID, setUserID } from '@utils/api'; +import { API_LOGIN, API_GET_SEMESTER, requestAPI, getToken, setToken, setRefreshToken, removeUserID, setUserID } from '@utils/api'; import { foregroundColor } from '@utils/styles/Colors'; import useLogoLayoutStyles from '@utils/styles/login/LogoLayout'; import useLoginLabelStyles from '@utils/styles/login/LoginLabel'; @@ -85,6 +85,7 @@ export function LoginBox() { } setToken(response.data.token); + setRefreshToken(response.data.refresh); setUserID(response.data.userId); await callSemester(); getSocket();