mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-05-31 14:11:58 +05:30
Decouple oauth api calls into separate module. Simple tests for oauth
actions
This commit is contained in:
@@ -3,6 +3,7 @@ import { routeActions } from 'react-router-redux';
|
|||||||
import { updateUser, logout as logoutUser, changePassword as changeUserPassword, authenticate } from 'components/user/actions';
|
import { updateUser, logout as logoutUser, changePassword as changeUserPassword, authenticate } from 'components/user/actions';
|
||||||
import request from 'services/request';
|
import request from 'services/request';
|
||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
|
import oauth from 'services/api/oauth';
|
||||||
|
|
||||||
export function login({login = '', password = '', rememberMe = false}) {
|
export function login({login = '', password = '', rememberMe = false}) {
|
||||||
const PASSWORD_REQUIRED = 'error.password_required';
|
const PASSWORD_REQUIRED = 'error.password_required';
|
||||||
@@ -146,100 +147,50 @@ export function logout() {
|
|||||||
|
|
||||||
// TODO: move to oAuth actions?
|
// TODO: move to oAuth actions?
|
||||||
// test request: /oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session
|
// test request: /oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session
|
||||||
export function oAuthValidate(oauth) {
|
export function oAuthValidate(oauthData) {
|
||||||
return wrapInLoader((dispatch) =>
|
return wrapInLoader((dispatch) =>
|
||||||
request.get(
|
oauth.validate(oauthData)
|
||||||
'/api/oauth/validate',
|
.then((resp) => {
|
||||||
getOAuthRequest(oauth)
|
dispatch(setClient(resp.client));
|
||||||
)
|
dispatch(setOAuthRequest(resp.oAuth));
|
||||||
.then((resp) => {
|
dispatch(setScopes(resp.session.scopes));
|
||||||
dispatch(setClient(resp.client));
|
})
|
||||||
dispatch(setOAuthRequest(resp.oAuth));
|
.catch(handleOauthParamsValidation)
|
||||||
dispatch(setScopes(resp.session.scopes));
|
|
||||||
})
|
|
||||||
.catch((resp = {}) => { // TODO
|
|
||||||
handleOauthParamsValidation(resp);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function oAuthComplete(params = {}) {
|
export function oAuthComplete(params = {}) {
|
||||||
return wrapInLoader((dispatch, getState) => {
|
return wrapInLoader((dispatch, getState) =>
|
||||||
const oauth = getState().auth.oauth;
|
oauth.complete(getState().auth.oauth, params)
|
||||||
const query = request.buildQuery(getOAuthRequest(oauth));
|
.then((resp) => {
|
||||||
|
if (resp.redirectUri.startsWith('static_page')) {
|
||||||
|
resp.code = resp.redirectUri.match(/code=(.+)&/)[1];
|
||||||
|
resp.redirectUri = resp.redirectUri.match(/^(.+)\?/)[1];
|
||||||
|
resp.displayCode = resp.redirectUri === 'static_page_with_code';
|
||||||
|
|
||||||
return request.post(
|
dispatch(setOAuthCode({
|
||||||
`/api/oauth/complete?${query}`,
|
success: resp.success,
|
||||||
typeof params.accept === 'undefined' ? {} : {accept: params.accept}
|
code: resp.code,
|
||||||
)
|
displayCode: resp.displayCode
|
||||||
.catch((resp = {}) => { // TODO
|
}));
|
||||||
if (resp.statusCode === 401 && resp.error === 'access_denied') {
|
}
|
||||||
// user declined permissions
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
redirectUri: resp.redirectUri
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOauthParamsValidation(resp);
|
return resp;
|
||||||
|
}, (resp) => {
|
||||||
|
if (resp.acceptRequired) {
|
||||||
|
dispatch(requirePermissionsAccept());
|
||||||
|
}
|
||||||
|
|
||||||
if (resp.status === 401 && resp.name === 'Unauthorized') {
|
return handleOauthParamsValidation(resp);
|
||||||
const error = new Error('Unauthorized');
|
})
|
||||||
error.unauthorized = true;
|
);
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resp.statusCode === 401 && resp.error === 'accept_required') {
|
|
||||||
const error = new Error('Permissions accept required');
|
|
||||||
error.acceptRequired = true;
|
|
||||||
dispatch(requirePermissionsAccept());
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((resp) => {
|
|
||||||
if (resp.redirectUri.startsWith('static_page')) {
|
|
||||||
resp.code = resp.redirectUri.match(/code=(.+)&/)[1];
|
|
||||||
resp.redirectUri = resp.redirectUri.match(/^(.+)\?/)[1];
|
|
||||||
resp.displayCode = resp.redirectUri === 'static_page_with_code';
|
|
||||||
dispatch(setOAuthCode({
|
|
||||||
success: resp.success,
|
|
||||||
code: resp.code,
|
|
||||||
displayCode: resp.displayCode
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOAuthRequest(oauth) {
|
|
||||||
return {
|
|
||||||
client_id: oauth.clientId,
|
|
||||||
redirect_uri: oauth.redirectUrl,
|
|
||||||
response_type: oauth.responseType,
|
|
||||||
scope: oauth.scope,
|
|
||||||
state: oauth.state
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleOauthParamsValidation(resp = {}) {
|
function handleOauthParamsValidation(resp = {}) {
|
||||||
let userMessage;
|
|
||||||
if (resp.statusCode === 400 && resp.error === 'invalid_request') {
|
|
||||||
userMessage = `Invalid request (${resp.parameter} required).`;
|
|
||||||
} else if (resp.statusCode === 400 && resp.error === 'unsupported_response_type') {
|
|
||||||
userMessage = `Invalid response type '${resp.parameter}'.`;
|
|
||||||
} else if (resp.statusCode === 400 && resp.error === 'invalid_scope') {
|
|
||||||
userMessage = `Invalid scope '${resp.parameter}'.`;
|
|
||||||
} else if (resp.statusCode === 401 && resp.error === 'invalid_client') {
|
|
||||||
userMessage = 'Can not find application you are trying to authorize.';
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint no-alert: "off" */
|
/* eslint no-alert: "off" */
|
||||||
alert(userMessage);
|
resp.userMessage && alert(resp.userMessage);
|
||||||
throw new Error('Error completing request');
|
|
||||||
|
return Promise.reject(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SET_CLIENT = 'set_client';
|
export const SET_CLIENT = 'set_client';
|
||||||
|
69
src/services/api/oauth.js
Normal file
69
src/services/api/oauth.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import request from 'services/request';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
validate(oauthData) {
|
||||||
|
return request.get(
|
||||||
|
'/api/oauth/validate',
|
||||||
|
getOAuthRequest(oauthData)
|
||||||
|
).catch(handleOauthParamsValidation);
|
||||||
|
},
|
||||||
|
|
||||||
|
complete(oauthData, params = {}) {
|
||||||
|
const query = request.buildQuery(getOAuthRequest(oauthData));
|
||||||
|
|
||||||
|
return request.post(
|
||||||
|
`/api/oauth/complete?${query}`,
|
||||||
|
typeof params.accept === 'undefined' ? {} : {accept: params.accept}
|
||||||
|
).catch((resp = {}) => {
|
||||||
|
if (resp.statusCode === 401 && resp.error === 'access_denied') {
|
||||||
|
// user declined permissions
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
redirectUri: resp.redirectUri
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.status === 401 && resp.name === 'Unauthorized') {
|
||||||
|
const error = new Error('Unauthorized');
|
||||||
|
error.unauthorized = true;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.statusCode === 401 && resp.error === 'accept_required') {
|
||||||
|
const error = new Error('Permissions accept required');
|
||||||
|
error.acceptRequired = true;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleOauthParamsValidation(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getOAuthRequest(oauthData) {
|
||||||
|
return {
|
||||||
|
client_id: oauthData.clientId,
|
||||||
|
redirect_uri: oauthData.redirectUrl,
|
||||||
|
response_type: oauthData.responseType,
|
||||||
|
scope: oauthData.scope,
|
||||||
|
state: oauthData.state
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOauthParamsValidation(resp = {}) {
|
||||||
|
let userMessage;
|
||||||
|
|
||||||
|
if (resp.statusCode === 400 && resp.error === 'invalid_request') {
|
||||||
|
userMessage = `Invalid request (${resp.parameter} required).`;
|
||||||
|
} else if (resp.statusCode === 400 && resp.error === 'unsupported_response_type') {
|
||||||
|
userMessage = `Invalid response type '${resp.parameter}'.`;
|
||||||
|
} else if (resp.statusCode === 400 && resp.error === 'invalid_scope') {
|
||||||
|
userMessage = `Invalid scope '${resp.parameter}'.`;
|
||||||
|
} else if (resp.statusCode === 401 && resp.error === 'invalid_client') {
|
||||||
|
userMessage = 'Can not find application you are trying to authorize.';
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(userMessage);
|
||||||
|
}
|
126
tests/components/auth/actions.test.js
Normal file
126
tests/components/auth/actions.test.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import request from 'services/request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
oAuthValidate,
|
||||||
|
oAuthComplete,
|
||||||
|
setClient,
|
||||||
|
setOAuthRequest,
|
||||||
|
setScopes,
|
||||||
|
setOAuthCode,
|
||||||
|
requirePermissionsAccept
|
||||||
|
} from 'components/auth/actions';
|
||||||
|
|
||||||
|
const oauthData = {
|
||||||
|
clientId: '',
|
||||||
|
redirectUrl: '',
|
||||||
|
responseType: '',
|
||||||
|
scope: '',
|
||||||
|
state: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('components/auth/actions', () => {
|
||||||
|
const dispatch = sinon.stub();
|
||||||
|
const getState = sinon.stub();
|
||||||
|
|
||||||
|
const callThunk = function(fn, ...args) {
|
||||||
|
const thunk = fn(...args);
|
||||||
|
|
||||||
|
return thunk(dispatch, getState);
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dispatch.reset();
|
||||||
|
getState.reset();
|
||||||
|
getState.returns({});
|
||||||
|
sinon.stub(request, 'get');
|
||||||
|
sinon.stub(request, 'post');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
request.get.restore();
|
||||||
|
request.post.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#oAuthValidate()', () => {
|
||||||
|
it('should dispatch setClient, setOAuthRequest and setScopes', () => {
|
||||||
|
// TODO: the assertions may be splitted up to one per test
|
||||||
|
|
||||||
|
const resp = {
|
||||||
|
client: {id: 123},
|
||||||
|
oAuth: {state: 123},
|
||||||
|
session: {
|
||||||
|
scopes: ['scopes']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.get.returns(Promise.resolve(resp));
|
||||||
|
|
||||||
|
return callThunk(oAuthValidate, oauthData).then(() => {
|
||||||
|
sinon.assert.calledWith(request.get, '/api/oauth/validate');
|
||||||
|
sinon.assert.calledWith(dispatch, setClient(resp.client));
|
||||||
|
sinon.assert.calledWith(dispatch, setOAuthRequest(resp.oAuth));
|
||||||
|
sinon.assert.calledWith(dispatch, setScopes(resp.session.scopes));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#oAuthComplete()', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
getState.returns({
|
||||||
|
auth: {
|
||||||
|
oauth: oauthData
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch setOAuthCode for static_page redirect', () => {
|
||||||
|
// TODO: it may be split on separate url and dispatch tests
|
||||||
|
const resp = {
|
||||||
|
success: true,
|
||||||
|
redirectUri: 'static_page?code=123&state='
|
||||||
|
};
|
||||||
|
|
||||||
|
request.post.returns(Promise.resolve(resp));
|
||||||
|
|
||||||
|
return callThunk(oAuthComplete).then(() => {
|
||||||
|
sinon.assert.calledWithMatch(request.post, /\/api\/oauth\/complete/);
|
||||||
|
sinon.assert.calledWith(dispatch, setOAuthCode({
|
||||||
|
success: true,
|
||||||
|
code: '123',
|
||||||
|
displayCode: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve to with success false and redirectUri for access_denied', () => {
|
||||||
|
const resp = {
|
||||||
|
statusCode: 401,
|
||||||
|
error: 'access_denied',
|
||||||
|
redirectUri: 'redirectUri'
|
||||||
|
};
|
||||||
|
|
||||||
|
request.post.returns(Promise.reject(resp));
|
||||||
|
|
||||||
|
return callThunk(oAuthComplete).then((resp) => {
|
||||||
|
expect(resp).to.be.deep.equal({
|
||||||
|
success: false,
|
||||||
|
redirectUri: 'redirectUri'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch requirePermissionsAccept if accept_required', () => {
|
||||||
|
const resp = {
|
||||||
|
statusCode: 401,
|
||||||
|
error: 'accept_required'
|
||||||
|
};
|
||||||
|
|
||||||
|
request.post.returns(Promise.reject(resp));
|
||||||
|
|
||||||
|
return callThunk(oAuthComplete).catch((resp) => {
|
||||||
|
expect(resp.acceptRequired).to.be.true;
|
||||||
|
sinon.assert.calledWith(dispatch, requirePermissionsAccept());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user