oauth/complete

This commit is contained in:
SleepWalker 2016-02-27 12:53:58 +02:00
parent 31d94ae770
commit e44360a20b
7 changed files with 119 additions and 32 deletions

View File

@ -2,7 +2,7 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { oAuthValidate } from 'components/auth/actions';
import { oAuthValidate, oAuthComplete } from 'components/auth/actions';
class OAuthInit extends Component {
static displayName = 'OAuthInit';
@ -27,7 +27,7 @@ class OAuthInit extends Component {
responseType: query.response_type,
scope: query.scope,
state: query.state
});
}).then(this.props.complete);
}
render() {
@ -38,5 +38,6 @@ class OAuthInit extends Component {
export default connect((state) => ({
query: state.routing.location.query
}), {
validate: oAuthValidate
validate: oAuthValidate,
complete: oAuthComplete
})(OAuthInit);

View File

@ -345,5 +345,6 @@ export default connect((state) => ({
register: actions.register,
activate: actions.activate,
clearErrors: actions.clearErrors,
oAuthComplete: actions.oAuthComplete,
setError: actions.setError
})(PanelTransition);

View File

@ -15,6 +15,7 @@ class Body extends BaseAuthBody {
static propTypes = {
...BaseAuthBody.propTypes,
login: PropTypes.func.isRequired,
oAuthComplete: PropTypes.func.isRequired,
auth: PropTypes.shape({
error: PropTypes.string,
login: PropTypes.shape({
@ -24,6 +25,8 @@ class Body extends BaseAuthBody {
};
render() {
const {user} = this.props;
return (
<div>
{this.renderErrors()}
@ -31,14 +34,16 @@ class Body extends BaseAuthBody {
<PanelBodyHeader>
<div className={styles.authInfo}>
<div className={styles.authInfoAvatar}>
{/*<img src="//lorempixel.com/g/90/90" />*/}
<span className={icons.user} />
{user.avatar
? <img src={user.avatar} />
: <span className={icons.user} />
}
</div>
<div className={styles.authInfoTitle}>
<Message {...messages.youAuthorizedAs} />
</div>
<div className={styles.authInfoEmail}>
{'erickskrauch@yandex.ru'}
{user.email}
</div>
</div>
</PanelBodyHeader>
@ -59,7 +64,9 @@ class Body extends BaseAuthBody {
}
onFormSubmit() {
// TODO
this.props.oAuthComplete({
accept: true
});
}
}

View File

@ -138,38 +138,78 @@ export function logout() {
// 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
export function oAuthValidate({clientId, redirectUrl, responseType, scope, state}) {
export function oAuthValidate(oauth) {
return (dispatch) =>
request.get(
'/api/oauth/validate',
{
client_id: clientId,
redirect_uri: redirectUrl,
response_type: responseType,
scope,
state
}
getOAuthRequest(oauth)
)
.then((resp) => {
dispatch(setClient(resp.client));
dispatch(routeActions.push('/oauth/permissions'));
dispatch(setOAuthRequest(resp.oAuth));
})
.catch((resp = {}) => { // TODO
if (resp.statusCode === 400 && resp.error === 'invalid_request') {
alert(`Invalid request (${resp.parameter} required).`);
}
if (resp.statusCode === 401 && resp.error === 'invalid_client') {
alert('Can not find application you are trying to authorize.');
}
if (resp.statusCode === 400 && resp.error === 'unsupported_response_type') {
alert(`Invalid response type '${resp.parameter}'.`);
}
if (resp.statusCode === 400 && resp.error === 'invalid_scope') {
alert(`Invalid scope '${resp.parameter}'.`);
handleOauthParamsValidation(resp);
if (resp.statusCode === 401 && resp.error === 'accept_required') {
alert('Accept required.');
}
});
}
export function oAuthComplete(params = {}) {
return (dispatch, getState) => {
const oauth = getState().auth.oauth;
const query = request.buildQuery(getOAuthRequest(oauth));
return request.post(
`/api/oauth/complete?${query}`,
typeof params.accept === 'undefined' ? {} : {accept: params.accept}
)
.then((resp) => {
if (resp.redirectUri) {
location.href = resp.redirectUri;
}
})
.catch((resp = {}) => { // TODO
handleOauthParamsValidation(resp);
if (resp.statusCode === 401 && resp.error === 'accept_required') {
dispatch(routeActions.push('/oauth/permissions'));
}
if (resp.statusCode === 401 && resp.error === 'access_denied') {
// user declined permissions
location.href = resp.redirectUri;
}
});
};
}
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 = {}) {
if (resp.statusCode === 400 && resp.error === 'invalid_request') {
alert(`Invalid request (${resp.parameter} required).`);
}
if (resp.statusCode === 400 && resp.error === 'unsupported_response_type') {
alert(`Invalid response type '${resp.parameter}'.`);
}
if (resp.statusCode === 400 && resp.error === 'invalid_scope') {
alert(`Invalid scope '${resp.parameter}'.`);
}
if (resp.statusCode === 401 && resp.error === 'invalid_client') {
alert('Can not find application you are trying to authorize.');
}
}
export const SET_CLIENT = 'set_client';
export function setClient({id, name, description}) {
return {
@ -177,3 +217,17 @@ export function setClient({id, name, description}) {
payload: {id, name, description}
};
}
export const SET_OAUTH = 'set_oauth';
export function setOAuthRequest(oauth) {
return {
type: SET_OAUTH,
payload: {
clientId: oauth.client_id,
redirectUrl: oauth.redirect_uri,
responseType: oauth.response_type,
scope: oauth.scope,
state: oauth.state
}
};
}

View File

@ -1,10 +1,11 @@
import { combineReducers } from 'redux';
import { ERROR, SET_CLIENT } from './actions';
import { ERROR, SET_CLIENT, SET_OAUTH } from './actions';
export default combineReducers({
error,
client
client,
oauth
});
function error(
@ -39,3 +40,22 @@ function client(
return state;
}
}
function oauth(
state = null,
{type, payload = {}}
) {
switch (type) {
case SET_OAUTH:
return {
clientId: payload.clientId,
redirectUrl: payload.redirectUrl,
responseType: payload.responseType,
scope: payload.scope,
state: payload.state
};
default:
return state;
}
}

View File

@ -37,6 +37,8 @@ export default function routesFactory(store) {
}
}
// TODO: validate that we have all required data on premissions page
if (forcePath && pathname !== forcePath) {
switch (pathname) {
case '/':

View File

@ -1,4 +1,4 @@
function serialize(data) {
function buildQuery(data) {
return Object.keys(data)
.map(
(keyName) =>
@ -33,7 +33,7 @@ export default {
...getDefaultHeaders(),
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
body: serialize(data)
body: buildQuery(data)
})
.then(toJSON)
.then(handleResponse)
@ -43,7 +43,7 @@ export default {
get(url, data) {
if (typeof data === 'object') {
const separator = url.indexOf('?') === -1 ? '?' : '&';
url += separator + serialize(data);
url += separator + buildQuery(data);
}
return fetch(url, {
@ -54,6 +54,8 @@ export default {
;
},
buildQuery,
setAuthToken(tkn) {
authToken = tkn;
}