mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-01-15 08:12:21 +05:30
#126: middleware feature for request service. Created middlewares for token headers and token refreshing
This commit is contained in:
parent
3bf02f16dc
commit
35d430e13c
@ -54,38 +54,12 @@ export function logout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fetchUserData() {
|
export function fetchUserData() {
|
||||||
return (dispatch, getState) =>
|
return (dispatch) =>
|
||||||
accounts.current()
|
accounts.current()
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
dispatch(updateUser(resp));
|
dispatch(updateUser(resp));
|
||||||
|
|
||||||
return dispatch(changeLang(resp.lang));
|
return dispatch(changeLang(resp.lang));
|
||||||
})
|
|
||||||
.catch((resp) => {
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"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 {token} = getState().user;
|
|
||||||
if (resp.message === 'Token expired' && token) {
|
|
||||||
return dispatch(authenticate(token));
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(logout());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,32 +83,128 @@ export function changePassword({
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let middlewareAdded = false;
|
||||||
import authentication from 'services/api/authentication';
|
|
||||||
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
|
||||||
const jwt = getJWTPayload(token);
|
|
||||||
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
refreshToken = refreshToken || getState().user.refreshToken;
|
if (!middlewareAdded) {
|
||||||
|
request.addMiddleware(tokenCheckMiddleware(dispatch, getState));
|
||||||
if (jwt.exp < Date.now() / 1000) {
|
request.addMiddleware(tokenApplyMiddleware(dispatch, getState));
|
||||||
return authentication.refreshToken(refreshToken)
|
middlewareAdded = true;
|
||||||
.then((resp) => dispatch(authenticate(resp.access_token)))
|
|
||||||
.catch(() => dispatch(logout()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request.setAuthToken(token);
|
refreshToken = refreshToken || getState().user.refreshToken;
|
||||||
|
dispatch(updateUser({
|
||||||
|
token,
|
||||||
|
refreshToken
|
||||||
|
}));
|
||||||
|
|
||||||
return dispatch(fetchUserData()).then((resp) => {
|
return dispatch(fetchUserData()).then((resp) => {
|
||||||
dispatch(updateUser({
|
dispatch(updateUser({
|
||||||
isGuest: false,
|
isGuest: false
|
||||||
token,
|
|
||||||
refreshToken
|
|
||||||
}));
|
}));
|
||||||
return resp;
|
return resp;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import authentication from 'services/api/authentication';
|
||||||
|
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) {
|
function getJWTPayload(jwt) {
|
||||||
const parts = (jwt || '').split('.');
|
const parts = (jwt || '').split('.');
|
||||||
|
|
||||||
|
@ -26,43 +26,55 @@ function buildQuery(data = {}) {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
function doFetch(...args) {
|
const middlewares = [];
|
||||||
// NOTE: we are wrapping fetch, because it is returning
|
|
||||||
// Promise instance that can not be pollyfilled with Promise.prototype.finally
|
|
||||||
return new Promise((resolve, reject) => fetch(...args).then(resolve, reject));
|
|
||||||
}
|
|
||||||
|
|
||||||
let authToken;
|
|
||||||
|
|
||||||
const checkStatus = (resp) => Promise[resp.status >= 200 && resp.status < 300 ? 'resolve' : 'reject'](resp);
|
const checkStatus = (resp) => Promise[resp.status >= 200 && resp.status < 300 ? 'resolve' : 'reject'](resp);
|
||||||
const toJSON = (resp) => resp.json();
|
const toJSON = (resp) => resp.json();
|
||||||
const rejectWithJSON = (resp) => toJSON(resp).then((resp) => {throw resp;});
|
const rejectWithJSON = (resp) => toJSON(resp).then((resp) => {throw resp;});
|
||||||
const handleResponse = (resp) => Promise[resp.success || typeof resp.success === 'undefined' ? 'resolve' : 'reject'](resp);
|
const handleResponseSuccess = (resp) => Promise[resp.success || typeof resp.success === 'undefined' ? 'resolve' : 'reject'](resp);
|
||||||
|
|
||||||
const getDefaultHeaders = () => {
|
function doFetch(url, options = {}) {
|
||||||
const header = {Accept: 'application/json'};
|
// NOTE: we are wrapping fetch, because it is returning
|
||||||
|
// Promise instance that can not be pollyfilled with Promise.prototype.finally
|
||||||
|
|
||||||
if (authToken) {
|
options.headers = options.headers || {};
|
||||||
header.Authorization = `Bearer ${authToken}`;
|
options.headers.Accept = 'application/json';
|
||||||
}
|
|
||||||
|
|
||||||
return header;
|
return runMiddlewares('before', {url, options})
|
||||||
};
|
.then(({url, options}) => fetch(url, options))
|
||||||
|
.then(checkStatus)
|
||||||
|
.then(toJSON, rejectWithJSON)
|
||||||
|
.then(handleResponseSuccess)
|
||||||
|
.then((resp) => runMiddlewares('then', resp))
|
||||||
|
.catch((resp) => runMiddlewares('catch', resp, () => doFetch(url, options)))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} action - the name of middleware's hook (before|then|catch)
|
||||||
|
* @param {Object} data - the initial data to pass through middlewares chain
|
||||||
|
* @param {Function} restart - a function to restart current request (for `catch` hook)
|
||||||
|
*
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
function runMiddlewares(action, data, restart) {
|
||||||
|
return middlewares
|
||||||
|
.filter((middleware) => middleware[action])
|
||||||
|
.reduce(
|
||||||
|
(promise, middleware) => promise.then((resp) => middleware[action](resp, restart)),
|
||||||
|
Promise[action === 'catch' ? 'reject' : 'resolve'](data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
post(url, data) {
|
post(url, data) {
|
||||||
return doFetch(url, {
|
return doFetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
...getDefaultHeaders(),
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||||
},
|
},
|
||||||
body: buildQuery(data)
|
body: buildQuery(data)
|
||||||
})
|
});
|
||||||
.then(checkStatus)
|
|
||||||
.then(toJSON, rejectWithJSON)
|
|
||||||
.then(handleResponse)
|
|
||||||
;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
get(url, data) {
|
get(url, data) {
|
||||||
@ -71,18 +83,14 @@ export default {
|
|||||||
url += separator + buildQuery(data);
|
url += separator + buildQuery(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return doFetch(url, {
|
return doFetch(url);
|
||||||
headers: getDefaultHeaders()
|
|
||||||
})
|
|
||||||
.then(checkStatus)
|
|
||||||
.then(toJSON, rejectWithJSON)
|
|
||||||
.then(handleResponse)
|
|
||||||
;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
buildQuery,
|
buildQuery,
|
||||||
|
|
||||||
setAuthToken(tkn) {
|
addMiddleware(middleware) {
|
||||||
authToken = tkn;
|
if (!middlewares.find((mdware) => mdware === middleware)) {
|
||||||
|
middlewares.push(middleware);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user