mirror of
				https://github.com/elyby/accounts-frontend.git
				synced 2025-05-31 14:11:58 +05:30 
			
		
		
		
	#365: improve token api errors handling
This commit is contained in:
		@@ -8,7 +8,7 @@
 | 
			
		||||
  "license": "UNLICENSED",
 | 
			
		||||
  "repository": "git@gitlab.ely.by:elyby/accounts.git",
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": "9"
 | 
			
		||||
    "node": ">=8.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "yarn run clean && yarn run build:dll && webpack-dev-server --progress --colors",
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@ import { updateUser, setGuest } from 'components/user/actions';
 | 
			
		||||
import { setLocale } from 'components/i18n/actions';
 | 
			
		||||
import { setAccountSwitcher } from 'components/auth/actions';
 | 
			
		||||
import logger from 'services/logger';
 | 
			
		||||
import { InternalServerError } from 'services/request';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    add,
 | 
			
		||||
@@ -38,15 +37,9 @@ export function authenticate({token, refreshToken}) {
 | 
			
		||||
    return (dispatch, getState) =>
 | 
			
		||||
        authentication.validateToken({token, refreshToken})
 | 
			
		||||
            .catch((resp = {}) => {
 | 
			
		||||
                if (resp instanceof InternalServerError) {
 | 
			
		||||
                    // delegate error recovering to the later logic
 | 
			
		||||
                    return Promise.reject(resp);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                logger.warn('Error validating token during auth', {
 | 
			
		||||
                    resp
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // all the logic to get the valid token was failed,
 | 
			
		||||
                // we must forget current token, but leave other user's accounts
 | 
			
		||||
                return dispatch(logoutAll())
 | 
			
		||||
                    .then(() => Promise.reject(resp));
 | 
			
		||||
            })
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ import sinon from 'sinon';
 | 
			
		||||
 | 
			
		||||
import { browserHistory } from 'services/history';
 | 
			
		||||
 | 
			
		||||
import logger from 'services/logger';
 | 
			
		||||
import { InternalServerError } from 'services/request';
 | 
			
		||||
import { sessionStorage } from 'services/localStorage';
 | 
			
		||||
import authentication from 'services/api/authentication';
 | 
			
		||||
@@ -58,7 +57,6 @@ describe('components/accounts/actions', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        sinon.stub(authentication, 'validateToken').named('authentication.validateToken');
 | 
			
		||||
        sinon.stub(logger, 'warn').named('logger.warn');
 | 
			
		||||
        authentication.validateToken.returns(Promise.resolve({
 | 
			
		||||
            token: account.token,
 | 
			
		||||
            refreshToken: account.refreshToken,
 | 
			
		||||
@@ -68,7 +66,6 @@ describe('components/accounts/actions', () => {
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        authentication.validateToken.restore();
 | 
			
		||||
        logger.warn.restore();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('#authenticate()', () => {
 | 
			
		||||
@@ -122,9 +119,6 @@ describe('components/accounts/actions', () => {
 | 
			
		||||
            authentication.validateToken.returns(Promise.reject({}));
 | 
			
		||||
 | 
			
		||||
            return expect(authenticate(account)(dispatch, getState), 'to be rejected').then(() => {
 | 
			
		||||
                expect(logger.warn, 'to have a call satisfying', [
 | 
			
		||||
                    'Error validating token during auth', {}
 | 
			
		||||
                ]);
 | 
			
		||||
                expect(dispatch, 'to have a call satisfying', [
 | 
			
		||||
                    {payload: {isGuest: true}},
 | 
			
		||||
                ]);
 | 
			
		||||
@@ -139,13 +133,10 @@ describe('components/accounts/actions', () => {
 | 
			
		||||
 | 
			
		||||
            authentication.validateToken.returns(Promise.reject(resp));
 | 
			
		||||
 | 
			
		||||
            return expect(authenticate(account)(dispatch, getState), 'to be rejected with', resp).then(() => {
 | 
			
		||||
                expect(dispatch, 'to have no calls satisfying', [
 | 
			
		||||
            return expect(authenticate(account)(dispatch, getState), 'to be rejected with', resp)
 | 
			
		||||
                .then(() => expect(dispatch, 'to have no calls satisfying', [
 | 
			
		||||
                    {payload: {isGuest: true}},
 | 
			
		||||
                ]);
 | 
			
		||||
 | 
			
		||||
                expect(logger.warn, 'was not called');
 | 
			
		||||
            });
 | 
			
		||||
                ]));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('marks user as stranger, if there is no refreshToken', () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,30 @@
 | 
			
		||||
// @flow
 | 
			
		||||
import request from 'services/request';
 | 
			
		||||
 | 
			
		||||
type UserResponse = {
 | 
			
		||||
    elyProfileLink: string,
 | 
			
		||||
    email: string,
 | 
			
		||||
    hasMojangUsernameCollision: bool,
 | 
			
		||||
    id: number,
 | 
			
		||||
    isActive: bool,
 | 
			
		||||
    isOtpEnabled: bool,
 | 
			
		||||
    lang: string,
 | 
			
		||||
    passwordChangedAt: number, // timestamp
 | 
			
		||||
    registeredAt: number, // timestamp
 | 
			
		||||
    shouldAcceptRules: bool,
 | 
			
		||||
    username: string,
 | 
			
		||||
    uuid: string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {object} options
 | 
			
		||||
     * @param {object} [options.token] - an optional token to overwrite headers
 | 
			
		||||
     *                                   in middleware and disable token auto-refresh
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Promise<User>}
 | 
			
		||||
     * @return {Promise<UserResponse>}
 | 
			
		||||
     */
 | 
			
		||||
    current(options = {}) {
 | 
			
		||||
    current(options: { token?: ?string } = {}): Promise<UserResponse> {
 | 
			
		||||
        return request.get('/api/accounts/current', {}, {
 | 
			
		||||
            token: options.token
 | 
			
		||||
        });
 | 
			
		||||
@@ -19,6 +35,11 @@ export default {
 | 
			
		||||
        newPassword = '',
 | 
			
		||||
        newRePassword = '',
 | 
			
		||||
        logoutAll = true
 | 
			
		||||
    }: {
 | 
			
		||||
        password?: string,
 | 
			
		||||
        newPassword?: string,
 | 
			
		||||
        newRePassword?: string,
 | 
			
		||||
        logoutAll?: bool,
 | 
			
		||||
    }) {
 | 
			
		||||
        return request.post(
 | 
			
		||||
            '/api/accounts/change-password',
 | 
			
		||||
@@ -33,6 +54,9 @@ export default {
 | 
			
		||||
    changeUsername({
 | 
			
		||||
        username = '',
 | 
			
		||||
        password = ''
 | 
			
		||||
    }: {
 | 
			
		||||
        username?: string,
 | 
			
		||||
        password?: string,
 | 
			
		||||
    }) {
 | 
			
		||||
        return request.post(
 | 
			
		||||
            '/api/accounts/change-username',
 | 
			
		||||
@@ -40,14 +64,14 @@ export default {
 | 
			
		||||
        );
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    changeLang(lang) {
 | 
			
		||||
    changeLang(lang: string) {
 | 
			
		||||
        return request.post(
 | 
			
		||||
            '/api/accounts/change-lang',
 | 
			
		||||
            {lang}
 | 
			
		||||
        );
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    requestEmailChange({password = ''}) {
 | 
			
		||||
    requestEmailChange({password = ''}: { password?: string }) {
 | 
			
		||||
        return request.post(
 | 
			
		||||
            '/api/accounts/change-email/initialize',
 | 
			
		||||
            {password}
 | 
			
		||||
@@ -57,6 +81,9 @@ export default {
 | 
			
		||||
    setNewEmail({
 | 
			
		||||
        email = '',
 | 
			
		||||
        key = ''
 | 
			
		||||
    }: {
 | 
			
		||||
        email?: string,
 | 
			
		||||
        key?: string,
 | 
			
		||||
    }) {
 | 
			
		||||
        return request.post(
 | 
			
		||||
            '/api/accounts/change-email/submit-new-email',
 | 
			
		||||
@@ -64,7 +91,7 @@ export default {
 | 
			
		||||
        );
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    confirmNewEmail({key}) {
 | 
			
		||||
    confirmNewEmail({key}: { key: string }) {
 | 
			
		||||
        return request.post(
 | 
			
		||||
            '/api/accounts/change-email/confirm-new-email',
 | 
			
		||||
            {key}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
// @flow
 | 
			
		||||
import request from 'services/request';
 | 
			
		||||
import logger from 'services/logger';
 | 
			
		||||
import request, { InternalServerError } from 'services/request';
 | 
			
		||||
import accounts from 'services/api/accounts';
 | 
			
		||||
 | 
			
		||||
const authentication = {
 | 
			
		||||
@@ -91,12 +92,31 @@ const authentication = {
 | 
			
		||||
            .then(() => accounts.current({token}))
 | 
			
		||||
            .then((user) => ({token, refreshToken, user}))
 | 
			
		||||
            .catch((resp) => {
 | 
			
		||||
                if (resp.message === 'Token expired') {
 | 
			
		||||
                if (resp instanceof InternalServerError) {
 | 
			
		||||
                    // delegate error recovering to the bsod middleware
 | 
			
		||||
                    return new Promise(() => {});
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (['Token expired', 'Incorrect token'].includes(resp.message)) {
 | 
			
		||||
                    return authentication.requestToken(refreshToken)
 | 
			
		||||
                        .then(({token}) =>
 | 
			
		||||
                            accounts.current({token})
 | 
			
		||||
                                .then((user) => ({token, refreshToken, user}))
 | 
			
		||||
                        );
 | 
			
		||||
                        )
 | 
			
		||||
                        .catch((error) => {
 | 
			
		||||
                            logger.error('Failed refreshing token during token validation', {
 | 
			
		||||
                                error
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            return Promise.reject(error);
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const errors = resp.errors || {};
 | 
			
		||||
                if (errors.refresh_token !== 'error.refresh_token_not_exist') {
 | 
			
		||||
                    logger.error('Unexpected error during token validation', {
 | 
			
		||||
                        resp
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Promise.reject(resp);
 | 
			
		||||
 
 | 
			
		||||
@@ -125,6 +125,43 @@ describe('authentication api', () => {
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe('when token is incorrect', () => {
 | 
			
		||||
            const expiredResponse = {
 | 
			
		||||
                name: 'Unauthorized',
 | 
			
		||||
                message: 'Incorrect token',
 | 
			
		||||
                code: 0,
 | 
			
		||||
                status: 401,
 | 
			
		||||
                type: 'yii\\web\\UnauthorizedHttpException'
 | 
			
		||||
            };
 | 
			
		||||
            const newToken = 'baz';
 | 
			
		||||
 | 
			
		||||
            beforeEach(() => {
 | 
			
		||||
                sinon.stub(authentication, 'requestToken');
 | 
			
		||||
 | 
			
		||||
                accounts.current.onCall(0).returns(Promise.reject(expiredResponse));
 | 
			
		||||
                authentication.requestToken.returns(Promise.resolve({token: newToken}));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            afterEach(() => {
 | 
			
		||||
                authentication.requestToken.restore();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('resolves with new token and user object', () =>
 | 
			
		||||
                expect(authentication.validateToken(validTokens),
 | 
			
		||||
                    'to be fulfilled with', {...validTokens, token: newToken, user}
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            it('rejects if token request failed', () => {
 | 
			
		||||
                const error = 'Something wrong';
 | 
			
		||||
                authentication.requestToken.returns(Promise.reject(error));
 | 
			
		||||
 | 
			
		||||
                return expect(authentication.validateToken(validTokens),
 | 
			
		||||
                    'to be rejected with', error
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('#logout', () => {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user