Skip to content

Commit

Permalink
[#36] axios interceptors for appending token or for obtaining refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
liamstevens111 committed Mar 8, 2023
1 parent 71150ac commit 90c772d
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/adapters/authAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Expand Down
15 changes: 14 additions & 1 deletion src/adapters/authAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type LoginAuthType = {
};

class AuthAdapter extends BaseAdapter {
static login(params: LoginAuthType) {
static loginWithEmailPassword(params: LoginAuthType) {
/* eslint-disable camelcase */
const requestParams = {
...params,
Expand All @@ -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;
2 changes: 2 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const PASSWORD_MIN_LENGTH = 5;

export const LOGIN_URL = '/login';
15 changes: 6 additions & 9 deletions src/helpers/userToken.ts
Original file line number Diff line number Diff line change
@@ -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');
};
55 changes: 55 additions & 0 deletions src/lib/requestManager.ts
Original file line number Diff line number Diff line change
@@ -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',
};
Expand Down Expand Up @@ -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;
});
Expand Down
4 changes: 2 additions & 2 deletions src/screens/Login/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<LoginScreen />, { wrapper: BrowserRouter });

Expand Down Expand Up @@ -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(<LoginScreen />, { wrapper: BrowserRouter });

Expand Down
2 changes: 1 addition & 1 deletion src/screens/Login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down

0 comments on commit 90c772d

Please sign in to comment.