mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-02-28 13:12:56 +05:30
#186: Decouple user middlewares from user actions
This commit is contained in:
parent
322325d4ad
commit
32496e99d9
@ -1,6 +1,5 @@
|
|||||||
import { routeActions } from 'react-router-redux';
|
import { routeActions } from 'react-router-redux';
|
||||||
|
|
||||||
import request from 'services/request';
|
|
||||||
import captcha from 'services/captcha';
|
import captcha from 'services/captcha';
|
||||||
import accounts from 'services/api/accounts';
|
import accounts from 'services/api/accounts';
|
||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
@ -92,15 +91,8 @@ export function acceptRules() {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
let middlewareAdded = false;
|
|
||||||
export function authenticate(token, refreshToken) { // TODO: this action, probably, belongs to components/auth
|
export function authenticate(token, refreshToken) { // TODO: this action, probably, belongs to components/auth
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
if (!middlewareAdded) {
|
|
||||||
request.addMiddleware(tokenCheckMiddleware(dispatch, getState));
|
|
||||||
request.addMiddleware(tokenApplyMiddleware(dispatch, getState));
|
|
||||||
middlewareAdded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshToken = refreshToken || getState().user.refreshToken;
|
refreshToken = refreshToken || getState().user.refreshToken;
|
||||||
dispatch(updateUser({
|
dispatch(updateUser({
|
||||||
token,
|
token,
|
||||||
@ -116,113 +108,3 @@ export function authenticate(token, refreshToken) { // TODO: this action, probab
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestAccessToken(refreshToken, dispatch) {
|
|
||||||
let promise;
|
|
||||||
if (refreshToken) {
|
|
||||||
promise = authentication.refreshToken(refreshToken);
|
|
||||||
} else {
|
|
||||||
promise = Promise.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise
|
|
||||||
.then((resp) => dispatch(updateUser({
|
|
||||||
token: resp.access_token
|
|
||||||
})))
|
|
||||||
.catch(() => dispatch(logout()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures, that all user's requests have fresh access token
|
|
||||||
*
|
|
||||||
* @param {function} dispatch
|
|
||||||
* @param {function} getState
|
|
||||||
*
|
|
||||||
* @return {object} middleware
|
|
||||||
*/
|
|
||||||
function tokenCheckMiddleware(dispatch, getState) {
|
|
||||||
return {
|
|
||||||
before(data) {
|
|
||||||
const {isGuest, refreshToken, token} = getState().user;
|
|
||||||
const isRefreshTokenRequest = data.url.includes('refresh-token');
|
|
||||||
|
|
||||||
if (isGuest || isRefreshTokenRequest || !token) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SAFETY_FACTOR = 60; // ask new token earlier to overcome time dissynchronization problem
|
|
||||||
const jwt = getJWTPayload(token);
|
|
||||||
|
|
||||||
if (jwt.exp - SAFETY_FACTOR < Date.now() / 1000) {
|
|
||||||
return requestAccessToken(refreshToken, dispatch).then(() => data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
catch(resp, restart) {
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"name": "Unauthorized",
|
|
||||||
"message": "You are requesting with an invalid credential.",
|
|
||||||
"code": 0,
|
|
||||||
"status": 401,
|
|
||||||
"type": "yii\\web\\UnauthorizedHttpException"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"name": "Unauthorized",
|
|
||||||
"message": "Token expired",
|
|
||||||
"code": 0,
|
|
||||||
"status": 401,
|
|
||||||
"type": "yii\\web\\UnauthorizedHttpException"
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if (resp && resp.status === 401) {
|
|
||||||
const {refreshToken} = getState().user;
|
|
||||||
if (resp.message === 'Token expired' && refreshToken) {
|
|
||||||
// request token and retry
|
|
||||||
return requestAccessToken(refreshToken, dispatch).then(restart);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(logout());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(resp);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies Bearer header for all requests
|
|
||||||
*
|
|
||||||
* @param {function} dispatch
|
|
||||||
* @param {function} getState
|
|
||||||
*
|
|
||||||
* @return {object} middleware
|
|
||||||
*/
|
|
||||||
function tokenApplyMiddleware(dispatch, getState) {
|
|
||||||
return {
|
|
||||||
before(data) {
|
|
||||||
const {token} = getState().user;
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
data.options.headers.Authorization = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getJWTPayload(jwt) {
|
|
||||||
const parts = (jwt || '').split('.');
|
|
||||||
|
|
||||||
if (parts.length !== 3) {
|
|
||||||
throw new Error('Invalid jwt token');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(atob(parts[1]));
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error('Can not decode jwt token');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
import { authenticate, changeLang } from 'components/user/actions';
|
import { authenticate, changeLang } from 'components/user/actions';
|
||||||
|
|
||||||
|
import request from 'services/request';
|
||||||
|
import bearerHeaderMiddleware from './middlewares/bearerHeaderMiddleware';
|
||||||
|
import refreshTokenMiddleware from './middlewares/refreshTokenMiddleware';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes User state with the fresh data
|
* Initializes User state with the fresh data
|
||||||
*
|
*
|
||||||
* @param {object} store - redux store
|
* @param {object} store - redux store
|
||||||
*
|
*
|
||||||
* @return {Promise} a promise, that resolves in User state
|
* @return {Promise} - a promise, that resolves in User state
|
||||||
*/
|
*/
|
||||||
export function factory(store) {
|
export function factory(store) {
|
||||||
|
request.addMiddleware(refreshTokenMiddleware(store));
|
||||||
|
request.addMiddleware(bearerHeaderMiddleware(store));
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const {user} = store.getState();
|
const {user} = store.getState();
|
||||||
|
|
||||||
|
21
src/components/user/middlewares/bearerHeaderMiddleware.js
Normal file
21
src/components/user/middlewares/bearerHeaderMiddleware.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Applies Bearer header for all requests
|
||||||
|
*
|
||||||
|
* @param {object} store - redux store
|
||||||
|
* @param {function} store.getState
|
||||||
|
*
|
||||||
|
* @return {object} - request middleware
|
||||||
|
*/
|
||||||
|
export default function bearerHeaderMiddleware({getState}) {
|
||||||
|
return {
|
||||||
|
before(data) {
|
||||||
|
const {token} = getState().user;
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
data.options.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
93
src/components/user/middlewares/refreshTokenMiddleware.js
Normal file
93
src/components/user/middlewares/refreshTokenMiddleware.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import authentication from 'services/api/authentication';
|
||||||
|
import {updateUser, logout} from '../actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures, that all user's requests have fresh access token
|
||||||
|
*
|
||||||
|
* @param {object} store - redux store
|
||||||
|
* @param {function} store.getState
|
||||||
|
* @param {function} store.dispatch
|
||||||
|
*
|
||||||
|
* @return {object} - request middleware
|
||||||
|
*/
|
||||||
|
export default function refreshTokenMiddleware({dispatch, getState}) {
|
||||||
|
return {
|
||||||
|
before(data) {
|
||||||
|
const {isGuest, refreshToken, token} = getState().user;
|
||||||
|
const isRefreshTokenRequest = data.url.includes('refresh-token');
|
||||||
|
|
||||||
|
if (isGuest || isRefreshTokenRequest || !token) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SAFETY_FACTOR = 60; // ask new token earlier to overcome time dissynchronization problem
|
||||||
|
const jwt = getJWTPayload(token);
|
||||||
|
|
||||||
|
if (jwt.exp - SAFETY_FACTOR < Date.now() / 1000) {
|
||||||
|
return requestAccessToken(refreshToken, dispatch).then(() => data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
catch(resp, restart) {
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"name": "Unauthorized",
|
||||||
|
"message": "You are requesting with an invalid credential.",
|
||||||
|
"code": 0,
|
||||||
|
"status": 401,
|
||||||
|
"type": "yii\\web\\UnauthorizedHttpException"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"name": "Unauthorized",
|
||||||
|
"message": "Token expired",
|
||||||
|
"code": 0,
|
||||||
|
"status": 401,
|
||||||
|
"type": "yii\\web\\UnauthorizedHttpException"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (resp && resp.status === 401) {
|
||||||
|
const {refreshToken} = getState().user;
|
||||||
|
if (resp.message === 'Token expired' && refreshToken) {
|
||||||
|
// request token and retry
|
||||||
|
return requestAccessToken(refreshToken, dispatch).then(restart);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(logout());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(resp);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestAccessToken(refreshToken, dispatch) {
|
||||||
|
let promise;
|
||||||
|
if (refreshToken) {
|
||||||
|
promise = authentication.refreshToken(refreshToken);
|
||||||
|
} else {
|
||||||
|
promise = Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.then((resp) => dispatch(updateUser({
|
||||||
|
token: resp.access_token
|
||||||
|
})))
|
||||||
|
.catch(() => dispatch(logout()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getJWTPayload(jwt) {
|
||||||
|
const parts = (jwt || '').split('.');
|
||||||
|
|
||||||
|
if (parts.length !== 3) {
|
||||||
|
throw new Error('Invalid jwt token');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(atob(parts[1]));
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error('Can not decode jwt token');
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user