diff --git a/src/adapters/authAdapter.test.ts b/src/adapters/authAdapter.test.ts index a863c21..8ae0953 100644 --- a/src/adapters/authAdapter.test.ts +++ b/src/adapters/authAdapter.test.ts @@ -35,7 +35,7 @@ describe('AuthAdapter', () => { .reply(200); expect(scope.isDone()).toBe(false); - await AuthAdapter.login({ ...testCredentials }); + await AuthAdapter.loginWithEmailPassword({ ...testCredentials }); expect(scope.isDone()).toBe(true); }); }); diff --git a/src/adapters/authAdapter.ts b/src/adapters/authAdapter.ts index cee734a..447d936 100644 --- a/src/adapters/authAdapter.ts +++ b/src/adapters/authAdapter.ts @@ -6,7 +6,7 @@ type LoginAuthType = { }; class AuthAdapter extends BaseAdapter { - static login(params: LoginAuthType) { + static loginWithEmailPassword(params: LoginAuthType) { /* eslint-disable camelcase */ const requestParams = { ...params, @@ -18,6 +18,19 @@ class AuthAdapter extends BaseAdapter { return this.prototype.postRequest('oauth/token', { data: requestParams }); } + + static loginWithRefreshToken(refreshToken: string) { + /* eslint-disable camelcase */ + const requestParams = { + refresh_token: refreshToken, + grant_type: 'refresh_token', + client_id: process.env.REACT_APP_API_CLIENT_ID, + client_secret: process.env.REACT_APP_API_CLIENT_SECRET, + }; + /* eslint-enable camelcase */ + + return this.prototype.postRequest('oauth/token', { data: requestParams }); + } } export default AuthAdapter; diff --git a/src/constants/index.ts b/src/constants/index.ts index 3904c7f..2ca62cf 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1 +1,3 @@ export const PASSWORD_MIN_LENGTH = 5; + +export const LOGIN_URL = '/login'; diff --git a/src/helpers/userToken.ts b/src/helpers/userToken.ts index 44893ec..a3abd6c 100644 --- a/src/helpers/userToken.ts +++ b/src/helpers/userToken.ts @@ -1,14 +1,11 @@ -type UserToken = - | { - access_token: string; - refresh_token: string; - } - | '{}'; - -export const getToken = (): UserToken => { +export const getToken = () => { return JSON.parse(localStorage.getItem('UserToken') || '{}'); }; -export const setToken = (token: UserToken) => { +export const setToken = (token: { access_token: string; refresh_token: string }) => { localStorage.setItem('UserToken', JSON.stringify(token)); }; + +export const clearToken = () => { + localStorage.removeItem('UserToken'); +}; diff --git a/src/lib/requestManager.ts b/src/lib/requestManager.ts index 81f9b8d..fbecdde 100644 --- a/src/lib/requestManager.ts +++ b/src/lib/requestManager.ts @@ -1,5 +1,10 @@ import axios, { Method as HTTPMethod, ResponseType, AxiosRequestConfig, AxiosResponse } from 'axios'; +import AuthAdapter from 'adapters/authAdapter'; +import { setToken, getToken, clearToken } from 'helpers/userToken'; + +import { LOGIN_URL } from '../constants'; + export const defaultOptions: { responseType: ResponseType } = { responseType: 'json', }; @@ -28,6 +33,56 @@ const requestManager = ( ...requestOptions, }; + axios.interceptors.request.use( + function (config) { + const userToken = getToken(); + + if (userToken?.access_token) { + config.headers.Authorization = `Bearer ${userToken.access_token}`; + } + return config; + }, + function (error) { + return Promise.reject(error); + } + ); + + axios.interceptors.response.use( + function (response) { + return response; + }, + async function (error) { + if (error.response?.status === 401) { + const userToken = getToken(); + + clearToken(); + + if (userToken?.refresh_token) { + try { + const response = await AuthAdapter.loginWithRefreshToken(userToken.refresh_token); + + const { + attributes: { access_token: accessToken, refresh_token: refreshToken }, + } = await response.data; + + /* eslint-disable camelcase */ + setToken({ access_token: accessToken, refresh_token: refreshToken }); + /* eslint-enable camelcase */ + + error.config.headers.Authorization = `Bearer ${accessToken}`; + return axios.request(error.config); + } catch { + window.location.href = LOGIN_URL; + } + } + + window.location.href = LOGIN_URL; + } + + return Promise.reject(error); + } + ); + return axios.request(requestParams).then((response: AxiosResponse) => { return response.data; }); diff --git a/src/screens/Login/index.test.tsx b/src/screens/Login/index.test.tsx index 12d07fc..f312d59 100644 --- a/src/screens/Login/index.test.tsx +++ b/src/screens/Login/index.test.tsx @@ -59,7 +59,7 @@ describe('LoginScreen', () => { }); test('given an empty email and password in the login form, displays both errors', async () => { - const mockLogin = jest.spyOn(AuthAdapter, 'login'); + const mockLogin = jest.spyOn(AuthAdapter, 'loginWithEmailPassword'); render(, { wrapper: BrowserRouter }); @@ -114,7 +114,7 @@ describe('LoginScreen', () => { }); test('given INCORRECT credentials, displays the error from the API response', async () => { - const mockLogin = jest.spyOn(AuthAdapter, 'login'); + const mockLogin = jest.spyOn(AuthAdapter, 'loginWithEmailPassword'); render(, { wrapper: BrowserRouter }); diff --git a/src/screens/Login/index.tsx b/src/screens/Login/index.tsx index 13c98c0..4bbce98 100644 --- a/src/screens/Login/index.tsx +++ b/src/screens/Login/index.tsx @@ -32,7 +32,7 @@ function LoginScreen() { const performLogin = async () => { try { - const response = await AuthAdapter.login({ email: email, password: password }); + const response = await AuthAdapter.loginWithEmailPassword({ email: email, password: password }); const { attributes: { access_token: accessToken, refresh_token: refreshToken },