From 404684b8d953858cc990948fdcd3659c59321b99 Mon Sep 17 00:00:00 2001 From: SleepWalker Date: Tue, 23 Feb 2016 07:57:16 +0200 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20appInfo=20=D1=81=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/auth/OAuthInit.jsx | 42 +++++++++++++++++++++++++++++++ src/components/auth/actions.js | 42 +++++++++++++++++++++++++++++++ src/components/auth/reducer.js | 22 ++++++++++++++-- src/pages/auth/AuthPage.jsx | 28 ++++++++++++++------- src/routes.js | 3 ++- src/services/request.js | 24 +++++++++++++++--- 6 files changed, 146 insertions(+), 15 deletions(-) create mode 100644 src/components/auth/OAuthInit.jsx diff --git a/src/components/auth/OAuthInit.jsx b/src/components/auth/OAuthInit.jsx new file mode 100644 index 0000000..48e9e07 --- /dev/null +++ b/src/components/auth/OAuthInit.jsx @@ -0,0 +1,42 @@ +import React, { Component, PropTypes } from 'react'; + +import { connect } from 'react-redux'; + +import { oAuthValidate } from 'components/auth/actions'; + +class OAuthInit extends Component { + static displayName = 'OAuthInit'; + + static propTypes = { + query: PropTypes.shape({ + client_id: PropTypes.string.isRequired, + redirect_uri: PropTypes.string.isRequired, + response_type: PropTypes.string.isRequired, + scope: PropTypes.string.isRequired, + state: PropTypes.string + }), + validate: PropTypes.func.isRequired + }; + + componentWillMount() { + const {query} = this.props; + + this.props.validate({ + clientId: query.client_id, + redirectUrl: query.redirect_uri, + responseType: query.response_type, + scope: query.scope, + state: query.state + }); + } + + render() { + return ; + } +} + +export default connect((state) => ({ + query: state.routing.location.query +}), { + validate: oAuthValidate +})(OAuthInit); diff --git a/src/components/auth/actions.js b/src/components/auth/actions.js index 6bcd97e..3cb1da9 100644 --- a/src/components/auth/actions.js +++ b/src/components/auth/actions.js @@ -107,3 +107,45 @@ export function logout() { dispatch(routeActions.push('/login')); }; } + +// 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}) { + return (dispatch) => + request.get( + '/api/oauth/validate', + { + client_id: clientId, + redirect_uri: redirectUrl, + response_type: responseType, + scope, + state + } + ) + .then((resp) => { + dispatch(setClient(resp.client)); + dispatch(routeActions.push('/oauth/permissions')); + }) + .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}'.`); + } + }); +} + +export const SET_CLIENT = 'set_client'; +export function setClient({id, name, description}) { + return { + type: SET_CLIENT, + payload: {id, name, description} + }; +} diff --git a/src/components/auth/reducer.js b/src/components/auth/reducer.js index b6c59b1..da79a42 100644 --- a/src/components/auth/reducer.js +++ b/src/components/auth/reducer.js @@ -1,9 +1,10 @@ import { combineReducers } from 'redux'; -import { ERROR } from './actions'; +import { ERROR, SET_CLIENT } from './actions'; export default combineReducers({ - error + error, + client }); function error( @@ -21,3 +22,20 @@ function error( return state; } } + +function client( + state = null, + {type, payload = {}} +) { + switch (type) { + case SET_CLIENT: + return { + id: payload.id, + name: payload.name, + description: payload.description + }; + + default: + return state; + } +} diff --git a/src/pages/auth/AuthPage.jsx b/src/pages/auth/AuthPage.jsx index ffb913a..d31bcc2 100644 --- a/src/pages/auth/AuthPage.jsx +++ b/src/pages/auth/AuthPage.jsx @@ -1,29 +1,34 @@ -import React, { Component } from 'react'; +import React, { Component, PropTypes } from 'react'; + +import { connect } from 'react-redux'; import AppInfo from 'components/auth/AppInfo'; import PanelTransition from 'components/auth/PanelTransition'; import styles from './auth.scss'; -export default class AuthPage extends Component { +class AuthPage extends Component { static displayName = 'AuthPage'; + static propTypes = { + client: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + description: PropTypes.string.isRequired + }) + }; state = { isSidebarHidden: false }; render() { - var {isSidebarHidden} = this.state; - - var appInfo = { - name: 'TLauncher', - description: `Лучший альтернативный лаунчер для Minecraft с большим количеством версий и их модификаций, а также возмоностью входа как с лицензионным аккаунтом, так и без него.` - }; + const {isSidebarHidden} = this.state; + const {client} = this.props; return (
- +
@@ -38,3 +43,8 @@ export default class AuthPage extends Component { }); }; } + + +export default connect((state) => ({ + client: state.auth.client +}))(AuthPage); diff --git a/src/routes.js b/src/routes.js index b2783c7..756f9a4 100644 --- a/src/routes.js +++ b/src/routes.js @@ -5,6 +5,7 @@ import RootPage from 'pages/root/RootPage'; import IndexPage from 'pages/index/IndexPage'; import AuthPage from 'pages/auth/AuthPage'; +import OAuthInit from 'components/auth/OAuthInit'; import Register from 'components/auth/Register'; import Login from 'components/auth/Login'; import Permissions from 'components/auth/Permissions'; @@ -47,10 +48,10 @@ export default function routesFactory(store) { - + ); diff --git a/src/services/request.js b/src/services/request.js index cb402f7..0222322 100644 --- a/src/services/request.js +++ b/src/services/request.js @@ -2,7 +2,7 @@ function serialize(data) { return Object.keys(data) .map( (keyName) => - [keyName, data[keyName]] + [keyName, typeof data[keyName] === 'undefined' ? '' : data[keyName]] .map(encodeURIComponent) .join('=') ) @@ -10,6 +10,9 @@ function serialize(data) { ; } +const toJSON = (resp) => resp.json(); +const handleResponse = (resp) => Promise[resp.success ? 'resolve' : 'reject'](resp); + export default { post(url, data) { return fetch(url, { @@ -20,8 +23,23 @@ export default { }, body: serialize(data) }) - .then((resp) => resp.json()) - .then((resp) => Promise[resp.success ? 'resolve' : 'reject'](resp)) + .then(toJSON) + .then(handleResponse) + ; + }, + get(url, data) { + if (typeof data === 'object') { + const separator = url.indexOf('?') === -1 ? '?' : '&'; + url += separator + serialize(data); + } + + return fetch(url, { + headers: { + Accept: 'application/json' + } + }) + .then(toJSON) + .then(handleResponse) ; } };