Show 404 for unknown authFlow requests. Pass {path, query, params} as first argument to authFlow#handleRequest

This commit is contained in:
SleepWalker 2016-08-07 16:54:59 +03:00
parent 97a0e448ed
commit 0149fc59d6
11 changed files with 70 additions and 40 deletions

View File

@ -31,7 +31,8 @@ export default function routesFactory(store) {
authFlow.setStore(store);
const startAuthFlow = {
onEnter: ({location}, replace, callback) => authFlow.handleRequest(location.pathname, replace, callback)
onEnter: ({location: {query, pathname: path}, params}, replace, callback) =>
authFlow.handleRequest({path, params, query}, replace, callback)
};
const userOnly = {

View File

@ -9,8 +9,8 @@ export default class ActivationState extends AbstractState {
if (user.isActive) {
context.setState(new CompleteState());
} else {
const url = context.getCurrentPath().includes('/activation')
? context.getCurrentPath()
const url = context.getRequest().path.includes('/activation')
? context.getRequest().path
: '/activation';
context.navigate(url);
}

View File

@ -25,8 +25,10 @@ export default class AuthFlow {
setStore(store) {
this.navigate = (route) => {
if (this.currentPath !== route) {
this.currentPath = route;
if (this.getRequest().path !== route) {
this.currentRequest = {
path: route
};
if (this.replace) {
this.replace(route);
@ -89,33 +91,40 @@ export default class AuthFlow {
}
}
getCurrentPath() {
return this.currentPath;
}
getQuery() {
return this.getState().routing.location.query;
/**
* @return {object} - current request object
*/
getRequest() {
return this.currentRequest ? {...this.currentRequest} : {};
}
/**
* This should be called from onEnter prop of react-router Route component
*
* @param {string} path
* @param {object} request
* @param {string} request.path
* @param {object} request.params
* @param {object} request.query
* @param {function} replace
* @param {function} [callback = function() {}] - an optional callback function to be called, when state will be stabilized
* (state's enter function's promise resolved)
*/
handleRequest(path, replace, callback = function() {}) {
handleRequest(request, replace, callback = function() {}) {
const {path, params = {}, query = {}} = request;
this.replace = replace;
this.onReady = callback;
if (this.currentPath === path) {
if (!path) {
throw new Error('The request.path is required');
}
if (this.getRequest().path === path) {
// we are already handling this path
this.onReady();
return;
}
this.currentPath = path;
this.currentRequest = request;
if (path === '/') {
// reset oauth data if user tried to navigate to index route
@ -159,7 +168,9 @@ export default class AuthFlow {
break;
default:
throw new Error(`Unsupported request: ${path}`);
console.log('Unsupported request', {path, query, params});
replace('/404');
break;
}
}

View File

@ -3,7 +3,7 @@ import CompleteState from './CompleteState';
export default class OAuthState extends AbstractState {
enter(context) {
const query = context.getQuery();
const {query} = context.getRequest();
return context.run('oAuthValidate', {
clientId: query.client_id,

View File

@ -7,8 +7,8 @@ export default class RecoverPasswordState extends AbstractState {
const {user} = context.getState();
if (user.isGuest) {
const url = context.getCurrentPath().includes('/recover-password')
? context.getCurrentPath()
const url = context.getRequest().path.includes('/recover-password')
? context.getRequest().path
: '/recover-password';
context.navigate(url);
} else {

View File

@ -30,7 +30,7 @@ describe('ActivationState', () => {
}
});
context.getCurrentPath.returns(expectedPath);
context.getRequest.returns({path: expectedPath});
expectNavigate(mock, '/activation');
@ -45,7 +45,7 @@ describe('ActivationState', () => {
}
});
context.getCurrentPath.returns(expectedPath);
context.getRequest.returns({path: expectedPath});
expectNavigate(mock, expectedPath);

View File

@ -30,10 +30,10 @@ describe('AuthFlow.functional', () => {
flow = new AuthFlow(actions);
flow.setStore(store);
navigate = function navigate(url) { // emulates router behaviour
if (navigate.lastUrl !== url) {
navigate.lastUrl = url;
flow.handleRequest(url, navigate);
navigate = function navigate(path, extra = {}) { // emulates router behaviour
if (navigate.lastUrl !== path) {
navigate.lastUrl = path;
flow.handleRequest({path, ...extra}, navigate);
}
};
@ -97,7 +97,7 @@ describe('AuthFlow.functional', () => {
redirectUri: expectedRedirect
})});
navigate('/oauth2');
navigate('/oauth2', {query: {}});
expect(flow.run, 'to have calls satisfying', [
['oAuthValidate', {}],

View File

@ -5,7 +5,6 @@ import AbstractState from 'services/authFlow/AbstractState';
import OAuthState from 'services/authFlow/OAuthState';
import RegisterState from 'services/authFlow/RegisterState';
import AcceptRulesState from 'services/authFlow/AcceptRulesState';
import RecoverPasswordState from 'services/authFlow/RecoverPasswordState';
import ForgotPasswordState from 'services/authFlow/ForgotPasswordState';
import ActivationState from 'services/authFlow/ActivationState';
@ -189,7 +188,7 @@ describe('AuthFlow', () => {
'/resend-activation': ResendActivationState
}).forEach(([path, type]) => {
it(`should transition to ${type.name} if ${path}`, () => {
flow.handleRequest(path);
flow.handleRequest({path});
expect(flow.setState, 'was called once');
expect(flow.setState, 'to have a call satisfying', [
@ -199,7 +198,7 @@ describe('AuthFlow', () => {
});
it('should run setOAuthRequest if /', () => {
flow.handleRequest('/');
flow.handleRequest({path: '/'});
expect(flow.run, 'was called once');
expect(flow.run, 'to have a call satisfying', ['setOAuthRequest', {}]);
@ -208,7 +207,7 @@ describe('AuthFlow', () => {
it('should call callback', () => {
const callback = sinon.stub().named('callback');
flow.handleRequest('/', () => {}, callback);
flow.handleRequest({path: '/'}, () => {}, callback);
expect(callback, 'was called once');
});
@ -223,7 +222,7 @@ describe('AuthFlow', () => {
flow.setState = AuthFlow.prototype.setState.bind(flow, state);
flow.handleRequest('/', () => {}, callback);
flow.handleRequest({path: '/'}, () => {}, callback);
expect(resolve, 'to be', callback);
@ -236,8 +235,8 @@ describe('AuthFlow', () => {
const path = '/oauth2';
const callback = sinon.stub();
flow.handleRequest(path, () => {}, callback);
flow.handleRequest(path, () => {}, callback);
flow.handleRequest({path}, () => {}, callback);
flow.handleRequest({path}, () => {}, callback);
expect(flow.setState, 'was called once');
expect(flow.setState, 'to have a call satisfying', [
@ -247,7 +246,27 @@ describe('AuthFlow', () => {
});
it('throws if unsupported request', () => {
expect(() => flow.handleRequest('/foo/bar'), 'to throw', 'Unsupported request: /foo/bar');
const replace = sinon.stub().named('replace');
flow.handleRequest({path: '/foo/bar'}, replace);
expect(replace, 'to have a call satisfying', ['/404']);
});
});
describe('#getRequest()', () => {
beforeEach(() => {
sinon.stub(flow, 'setState').named('flow.setState');
sinon.stub(flow, 'run').named('flow.run');
});
it('should return a copy of current request', () => {
const request = {path: '/', query: {foo: 'bar'}, params: {baz: 'bud'}};
flow.handleRequest(request);
expect(flow.getRequest(), 'to equal', request);
expect(flow.getRequest(), 'not to be', request);
});
});
});

View File

@ -30,7 +30,7 @@ describe('OAuthState', () => {
state: 'state'
};
context.getQuery.returns(query);
context.getRequest.returns({query});
expectRun(
mock,
@ -50,7 +50,7 @@ describe('OAuthState', () => {
it('should transition to complete state on success', () => {
const promise = Promise.resolve();
context.getQuery.returns({});
context.getRequest.returns({query: {}});
mock.expects('run').returns(promise);
expectState(mock, CompleteState);

View File

@ -28,7 +28,7 @@ describe('RecoverPasswordState', () => {
user: {isGuest: true}
});
context.getCurrentPath.returns(expectedPath);
context.getRequest.returns({path: expectedPath});
expectNavigate(mock, expectedPath);
@ -41,7 +41,7 @@ describe('RecoverPasswordState', () => {
user: {isGuest: true}
});
context.getCurrentPath.returns(expectedPath);
context.getRequest.returns({path: expectedPath});
expectNavigate(mock, expectedPath);

View File

@ -7,8 +7,7 @@ export function bootstrap() {
getState: sinon.stub(),
run() {},
setState() {},
getCurrentPath: sinon.stub(),
getQuery: sinon.stub(),
getRequest: sinon.stub(),
navigate() {}
};