mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-16 18:19:55 +05:30
Auth flow. The next
This commit is contained in:
parent
a317bfd3d4
commit
57f0cf30e6
@ -13,7 +13,6 @@ import messages from './Activation.messages';
|
|||||||
class Body extends BaseAuthBody {
|
class Body extends BaseAuthBody {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
...BaseAuthBody.propTypes,
|
...BaseAuthBody.propTypes,
|
||||||
activate: PropTypes.func.isRequired,
|
|
||||||
auth: PropTypes.shape({
|
auth: PropTypes.shape({
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
login: PropTypes.shape({
|
login: PropTypes.shape({
|
||||||
@ -48,10 +47,6 @@ class Body extends BaseAuthBody {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormSubmit() {
|
|
||||||
this.props.activate(this.serialize());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Activation() {
|
export default function Activation() {
|
||||||
|
@ -22,7 +22,7 @@ export default class AppInfo extends Component {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.appInfo}>
|
<div className={styles.appInfo}>
|
||||||
<div className={styles.logoContainer}>
|
<div className={styles.logoContainer}>
|
||||||
<h2 className={styles.logo}>{name}</h2>
|
<h2 className={styles.logo}>{name || 'Ely Accounts'}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.descriptionContainer}>
|
<div className={styles.descriptionContainer}>
|
||||||
<p className={styles.description}>
|
<p className={styles.description}>
|
||||||
|
@ -8,6 +8,8 @@ import AuthError from './AuthError';
|
|||||||
export default class BaseAuthBody extends Component {
|
export default class BaseAuthBody extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
clearErrors: PropTypes.func.isRequired,
|
clearErrors: PropTypes.func.isRequired,
|
||||||
|
resolve: PropTypes.func.isRequired,
|
||||||
|
reject: PropTypes.func.isRequired,
|
||||||
auth: PropTypes.shape({
|
auth: PropTypes.shape({
|
||||||
error: PropTypes.string
|
error: PropTypes.string
|
||||||
})
|
})
|
||||||
@ -20,6 +22,10 @@ export default class BaseAuthBody extends Component {
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onFormSubmit() {
|
||||||
|
this.props.resolve(this.serialize());
|
||||||
|
}
|
||||||
|
|
||||||
onClearErrors = this.props.clearErrors;
|
onClearErrors = this.props.clearErrors;
|
||||||
|
|
||||||
form = {};
|
form = {};
|
||||||
|
@ -14,7 +14,6 @@ import passwordMessages from './Password.messages';
|
|||||||
class Body extends BaseAuthBody {
|
class Body extends BaseAuthBody {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
...BaseAuthBody.propTypes,
|
...BaseAuthBody.propTypes,
|
||||||
login: PropTypes.func.isRequired,
|
|
||||||
auth: PropTypes.shape({
|
auth: PropTypes.shape({
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
login: PropTypes.shape({
|
login: PropTypes.shape({
|
||||||
@ -37,10 +36,6 @@ class Body extends BaseAuthBody {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormSubmit() {
|
|
||||||
this.props.login(this.serialize());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
|
@ -1,25 +1,9 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
export class Logout extends Component {
|
||||||
|
|
||||||
import { logout } from 'components/auth/actions';
|
|
||||||
|
|
||||||
class Logout extends Component {
|
|
||||||
static displayName = 'Logout';
|
static displayName = 'Logout';
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
logout: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.props.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <span />;
|
return <span/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(null, {
|
|
||||||
logout
|
|
||||||
})(Logout);
|
|
||||||
|
@ -1,43 +1,9 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
export default class OAuthInit extends Component {
|
||||||
|
|
||||||
import { oAuthValidate, oAuthComplete } from 'components/auth/actions';
|
|
||||||
|
|
||||||
class OAuthInit extends Component {
|
|
||||||
static displayName = 'OAuthInit';
|
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
|
|
||||||
}).then(this.props.complete);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <span />;
|
return <span />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((state) => ({
|
|
||||||
query: state.routing.location.query
|
|
||||||
}), {
|
|
||||||
validate: oAuthValidate,
|
|
||||||
complete: oAuthComplete
|
|
||||||
})(OAuthInit);
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { routeActions } from 'react-router-redux';
|
|
||||||
import { TransitionMotion, spring } from 'react-motion';
|
import { TransitionMotion, spring } from 'react-motion';
|
||||||
import ReactHeight from 'react-height';
|
import ReactHeight from 'react-height';
|
||||||
|
|
||||||
@ -10,6 +9,7 @@ import { Form } from 'components/ui/Form';
|
|||||||
import {helpLinks as helpLinksStyles} from 'components/auth/helpLinks.scss';
|
import {helpLinks as helpLinksStyles} from 'components/auth/helpLinks.scss';
|
||||||
import panelStyles from 'components/ui/panel.scss';
|
import panelStyles from 'components/ui/panel.scss';
|
||||||
import icons from 'components/ui/icons.scss';
|
import icons from 'components/ui/icons.scss';
|
||||||
|
import authFlow from 'services/authFlow';
|
||||||
|
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
@ -28,7 +28,6 @@ class PanelTransition extends Component {
|
|||||||
password: PropTypes.string
|
password: PropTypes.string
|
||||||
})
|
})
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
goBack: React.PropTypes.func.isRequired,
|
|
||||||
setError: React.PropTypes.func.isRequired,
|
setError: React.PropTypes.func.isRequired,
|
||||||
clearErrors: React.PropTypes.func.isRequired,
|
clearErrors: React.PropTypes.func.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
@ -211,8 +210,7 @@ class PanelTransition extends Component {
|
|||||||
onGoBack = (event) => {
|
onGoBack = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.body.onGoBack && this.body.onGoBack();
|
authFlow.goBack();
|
||||||
this.props.goBack();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getHeader(key, props) {
|
getHeader(key, props) {
|
||||||
@ -341,14 +339,10 @@ class PanelTransition extends Component {
|
|||||||
export default connect((state) => ({
|
export default connect((state) => ({
|
||||||
user: state.user,
|
user: state.user,
|
||||||
auth: state.auth,
|
auth: state.auth,
|
||||||
path: state.routing.location.pathname
|
path: state.routing.location.pathname,
|
||||||
|
resolve: authFlow.resolve.bind(authFlow),
|
||||||
|
reject: authFlow.reject.bind(authFlow)
|
||||||
}), {
|
}), {
|
||||||
goBack: routeActions.goBack,
|
|
||||||
login: actions.login,
|
|
||||||
logout: actions.logout,
|
|
||||||
register: actions.register,
|
|
||||||
activate: actions.activate,
|
|
||||||
clearErrors: actions.clearErrors,
|
clearErrors: actions.clearErrors,
|
||||||
oAuthComplete: actions.oAuthComplete,
|
|
||||||
setError: actions.setError
|
setError: actions.setError
|
||||||
})(PanelTransition);
|
})(PanelTransition);
|
||||||
|
@ -15,8 +15,6 @@ import messages from './Password.messages';
|
|||||||
class Body extends BaseAuthBody {
|
class Body extends BaseAuthBody {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
...BaseAuthBody.propTypes,
|
...BaseAuthBody.propTypes,
|
||||||
login: PropTypes.func.isRequired,
|
|
||||||
logout: PropTypes.func.isRequired,
|
|
||||||
auth: PropTypes.shape({
|
auth: PropTypes.shape({
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
login: PropTypes.shape({
|
login: PropTypes.shape({
|
||||||
@ -56,17 +54,6 @@ class Body extends BaseAuthBody {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormSubmit() {
|
|
||||||
this.props.login({
|
|
||||||
...this.serialize(),
|
|
||||||
login: this.props.user.email || this.props.user.username
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onGoBack() {
|
|
||||||
this.props.logout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Password() {
|
export default function Password() {
|
||||||
|
@ -15,15 +15,7 @@ import styles from './passwordChange.scss';
|
|||||||
|
|
||||||
class Body extends BaseAuthBody {
|
class Body extends BaseAuthBody {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
...BaseAuthBody.propTypes/*,
|
...BaseAuthBody.propTypes
|
||||||
// Я так полагаю, это правила валидации?
|
|
||||||
login: PropTypes.func.isRequired,
|
|
||||||
auth: PropTypes.shape({
|
|
||||||
error: PropTypes.string,
|
|
||||||
login: PropTypes.shape({
|
|
||||||
login: PropTypes.stirng
|
|
||||||
})
|
|
||||||
})*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -56,10 +48,6 @@ class Body extends BaseAuthBody {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormSubmit() {
|
|
||||||
this.props.login(this.serialize());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PasswordChange() {
|
export default function PasswordChange() {
|
||||||
@ -75,10 +63,14 @@ export default function PasswordChange() {
|
|||||||
<Message {...passwordChangedMessages.change} />
|
<Message {...passwordChangedMessages.change} />
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
Links: () => (
|
Links: (props) => (
|
||||||
<Link to="/oauth/permissions">
|
<a href="#" onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
props.reject();
|
||||||
|
}}>
|
||||||
<Message {...passwordChangedMessages.skipThisStep} />
|
<Message {...passwordChangedMessages.skipThisStep} />
|
||||||
</Link>
|
</a>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,6 @@ import messages from './Permissions.messages';
|
|||||||
class Body extends BaseAuthBody {
|
class Body extends BaseAuthBody {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
...BaseAuthBody.propTypes,
|
...BaseAuthBody.propTypes,
|
||||||
login: PropTypes.func.isRequired,
|
|
||||||
oAuthComplete: PropTypes.func.isRequired,
|
|
||||||
auth: PropTypes.shape({
|
auth: PropTypes.shape({
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
scopes: PropTypes.array.isRequired
|
scopes: PropTypes.array.isRequired
|
||||||
@ -52,20 +50,14 @@ class Body extends BaseAuthBody {
|
|||||||
<Message {...messages.theAppNeedsAccess2} />
|
<Message {...messages.theAppNeedsAccess2} />
|
||||||
</div>
|
</div>
|
||||||
<ul className={styles.permissionsList}>
|
<ul className={styles.permissionsList}>
|
||||||
{scopes.map((scope) => (
|
{scopes.map((scope, key) => (
|
||||||
<li>{<Message {...messages[`scope_${scope}`]} />}</li>
|
<li key={key}>{<Message {...messages[`scope_${scope}`]} />}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormSubmit() {
|
|
||||||
this.props.oAuthComplete({
|
|
||||||
accept: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Permissions() {
|
export default function Permissions() {
|
||||||
@ -85,9 +77,7 @@ export default function Permissions() {
|
|||||||
<a href="#" onClick={(event) => {
|
<a href="#" onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
props.onAuthComplete({
|
props.reject();
|
||||||
accept: false
|
|
||||||
});
|
|
||||||
}}>
|
}}>
|
||||||
<Message {...messages.decline} />
|
<Message {...messages.decline} />
|
||||||
</a>
|
</a>
|
||||||
|
@ -82,10 +82,6 @@ class Body extends BaseAuthBody {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormSubmit() {
|
|
||||||
this.props.register(this.serialize());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Register() {
|
export default function Register() {
|
||||||
|
@ -19,30 +19,26 @@ export function login({login = '', password = '', rememberMe = false}) {
|
|||||||
token: resp.jwt
|
token: resp.jwt
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dispatch(authenticate(resp.jwt));
|
return dispatch(authenticate(resp.jwt));
|
||||||
|
|
||||||
dispatch(redirectToGoal());
|
|
||||||
})
|
})
|
||||||
.catch((resp) => {
|
.catch((resp) => {
|
||||||
if (resp.errors.login === ACTIVATION_REQUIRED) {
|
if (resp.errors.login === ACTIVATION_REQUIRED) {
|
||||||
dispatch(updateUser({
|
return dispatch(updateUser({
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isGuest: false
|
isGuest: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dispatch(redirectToGoal());
|
|
||||||
} else if (resp.errors.password === PASSWORD_REQUIRED) {
|
} else if (resp.errors.password === PASSWORD_REQUIRED) {
|
||||||
dispatch(updateUser({
|
return dispatch(updateUser({
|
||||||
username: login,
|
username: login,
|
||||||
email: login
|
email: login
|
||||||
}));
|
}));
|
||||||
dispatch(routeActions.push('/password'));
|
|
||||||
} else {
|
} else {
|
||||||
if (resp.errors.login === LOGIN_REQUIRED && password) {
|
if (resp.errors.login === LOGIN_REQUIRED && password) {
|
||||||
dispatch(logout());
|
dispatch(logout());
|
||||||
}
|
}
|
||||||
const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
|
const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
|
||||||
dispatch(setError(errorMessage));
|
dispatch(setError(errorMessage));
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: log unexpected errors
|
// TODO: log unexpected errors
|
||||||
@ -73,6 +69,7 @@ export function register({
|
|||||||
.catch((resp) => {
|
.catch((resp) => {
|
||||||
const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
|
const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
|
||||||
dispatch(setError(errorMessage));
|
dispatch(setError(errorMessage));
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
|
||||||
// TODO: log unexpected errors
|
// TODO: log unexpected errors
|
||||||
})
|
})
|
||||||
@ -87,43 +84,22 @@ export function activate({key = ''}) {
|
|||||||
)
|
)
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
dispatch(updateUser({
|
dispatch(updateUser({
|
||||||
|
isGuest: false,
|
||||||
isActive: true
|
isActive: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dispatch(authenticate(resp.jwt));
|
return dispatch(authenticate(resp.jwt));
|
||||||
|
|
||||||
dispatch(redirectToGoal());
|
|
||||||
})
|
})
|
||||||
.catch((resp) => {
|
.catch((resp) => {
|
||||||
const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
|
const errorMessage = resp.errors[Object.keys(resp.errors)[0]];
|
||||||
dispatch(setError(errorMessage));
|
dispatch(setError(errorMessage));
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
|
||||||
// TODO: log unexpected errors
|
// TODO: log unexpected errors
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirectToGoal() {
|
|
||||||
return (dispatch, getState) => {
|
|
||||||
const {user} = getState();
|
|
||||||
|
|
||||||
switch (user.goal) {
|
|
||||||
case 'oauth':
|
|
||||||
dispatch(routeActions.push('/oauth/permissions'));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'account':
|
|
||||||
default:
|
|
||||||
dispatch(routeActions.push('/'));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispatch(updateUser({ // TODO: mb create action resetGoal?
|
|
||||||
// goal: null
|
|
||||||
// }));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ERROR = 'error';
|
export const ERROR = 'error';
|
||||||
export function setError(error) {
|
export function setError(error) {
|
||||||
return {
|
return {
|
||||||
@ -138,10 +114,7 @@ export function clearErrors() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
return (dispatch) => {
|
return logoutUser();
|
||||||
dispatch(logoutUser());
|
|
||||||
dispatch(routeActions.push('/login'));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to oAuth actions?
|
// TODO: move to oAuth actions?
|
||||||
@ -174,28 +147,26 @@ export function oAuthComplete(params = {}) {
|
|||||||
`/api/oauth/complete?${query}`,
|
`/api/oauth/complete?${query}`,
|
||||||
typeof params.accept === 'undefined' ? {} : {accept: params.accept}
|
typeof params.accept === 'undefined' ? {} : {accept: params.accept}
|
||||||
)
|
)
|
||||||
.then((resp) => {
|
|
||||||
if (resp.status === 401 && resp.name === 'Unauthorized') {
|
|
||||||
// TODO: temporary solution for oauth init by guest
|
|
||||||
// TODO: request serivce should handle http status codes
|
|
||||||
dispatch(routeActions.push('/oauth/permissions'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resp.redirectUri) {
|
|
||||||
location.href = resp.redirectUri;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((resp = {}) => { // TODO
|
.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') {
|
if (resp.statusCode === 401 && resp.error === 'access_denied') {
|
||||||
// user declined permissions
|
// user declined permissions
|
||||||
location.href = resp.redirectUri;
|
return {
|
||||||
|
redirectUri: resp.redirectUri
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOauthParamsValidation(resp);
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -212,17 +183,22 @@ function getOAuthRequest(oauth) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOauthParamsValidation(resp = {}) {
|
function handleOauthParamsValidation(resp = {}) {
|
||||||
|
const error = new Error('Error completing request');
|
||||||
if (resp.statusCode === 400 && resp.error === 'invalid_request') {
|
if (resp.statusCode === 400 && resp.error === 'invalid_request') {
|
||||||
alert(`Invalid request (${resp.parameter} required).`);
|
alert(`Invalid request (${resp.parameter} required).`);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
if (resp.statusCode === 400 && resp.error === 'unsupported_response_type') {
|
if (resp.statusCode === 400 && resp.error === 'unsupported_response_type') {
|
||||||
alert(`Invalid response type '${resp.parameter}'.`);
|
alert(`Invalid response type '${resp.parameter}'.`);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
if (resp.statusCode === 400 && resp.error === 'invalid_scope') {
|
if (resp.statusCode === 400 && resp.error === 'invalid_scope') {
|
||||||
alert(`Invalid scope '${resp.parameter}'.`);
|
alert(`Invalid scope '${resp.parameter}'.`);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
if (resp.statusCode === 401 && resp.error === 'invalid_client') {
|
if (resp.statusCode === 401 && resp.error === 'invalid_client') {
|
||||||
alert('Can not find application you are trying to authorize.');
|
alert('Can not find application you are trying to authorize.');
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,6 @@ export function authenticate(token) {
|
|||||||
|
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
request.setAuthToken(token);
|
request.setAuthToken(token);
|
||||||
dispatch(fetchUserData());
|
return dispatch(fetchUserData());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,32 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import AuthPage from 'pages/auth/AuthPage';
|
import { connect } from 'react-redux';
|
||||||
import Login from 'components/auth/Login';
|
|
||||||
|
|
||||||
export default class IndexPage extends Component {
|
import authFlow from 'services/authFlow';
|
||||||
|
|
||||||
|
class IndexPage extends Component {
|
||||||
displayName = 'IndexPage';
|
displayName = 'IndexPage';
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
if (this.props.user.isGuest) {
|
||||||
|
authFlow.login();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {user, children} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<div>
|
||||||
<Login />
|
<h1>
|
||||||
</AuthPage>
|
Hello {user.username}!
|
||||||
|
</h1>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect((state) => ({
|
||||||
|
user: state.user
|
||||||
|
}))(IndexPage);
|
||||||
|
@ -5,7 +5,7 @@ import RootPage from 'pages/root/RootPage';
|
|||||||
import IndexPage from 'pages/index/IndexPage';
|
import IndexPage from 'pages/index/IndexPage';
|
||||||
import AuthPage from 'pages/auth/AuthPage';
|
import AuthPage from 'pages/auth/AuthPage';
|
||||||
|
|
||||||
import { authenticate, updateUser } from 'components/user/actions';
|
import { authenticate } from 'components/user/actions';
|
||||||
|
|
||||||
import OAuthInit from 'components/auth/OAuthInit';
|
import OAuthInit from 'components/auth/OAuthInit';
|
||||||
import Register from 'components/auth/Register';
|
import Register from 'components/auth/Register';
|
||||||
@ -17,72 +17,37 @@ import Logout from 'components/auth/Logout';
|
|||||||
import PasswordChange from 'components/auth/PasswordChange';
|
import PasswordChange from 'components/auth/PasswordChange';
|
||||||
import ForgotPassword from 'components/auth/ForgotPassword';
|
import ForgotPassword from 'components/auth/ForgotPassword';
|
||||||
|
|
||||||
|
import authFlow from 'services/authFlow';
|
||||||
|
|
||||||
export default function routesFactory(store) {
|
export default function routesFactory(store) {
|
||||||
function checkAuth(nextState, replace) {
|
|
||||||
const state = store.getState();
|
|
||||||
const pathname = state.routing.location.pathname;
|
|
||||||
|
|
||||||
let forcePath;
|
|
||||||
let goal;
|
|
||||||
if (!state.user.isGuest) {
|
|
||||||
if (!state.user.isActive) {
|
|
||||||
forcePath = '/activation';
|
|
||||||
} else if (!state.user.shouldChangePassword) {
|
|
||||||
forcePath = '/password-change';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (state.user.email || state.user.username) {
|
|
||||||
forcePath = '/password';
|
|
||||||
} else {
|
|
||||||
forcePath = '/login';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: validate that we have all required data on premissions page
|
|
||||||
|
|
||||||
if (forcePath && pathname !== forcePath) {
|
|
||||||
switch (pathname) {
|
|
||||||
case '/':
|
|
||||||
goal = 'account';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '/oauth/permissions':
|
|
||||||
goal = 'oauth';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (goal) {
|
|
||||||
store.dispatch(updateUser({ // TODO: mb create action resetGoal?
|
|
||||||
goal
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
replace({pathname: forcePath});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
if (state.user.token) {
|
if (state.user.token) {
|
||||||
// authorizing user if it is possible
|
// authorizing user if it is possible
|
||||||
store.dispatch(authenticate(state.user.token));
|
store.dispatch(authenticate(state.user.token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authFlow.setStore(store);
|
||||||
|
|
||||||
|
const onEnter = {
|
||||||
|
onEnter: ({location}, replace) => authFlow.handleRequest(location.pathname, replace)
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Route path="/" component={RootPage}>
|
<Route path="/" component={RootPage}>
|
||||||
<IndexRoute component={IndexPage} onEnter={checkAuth} />
|
<IndexRoute component={IndexPage} />
|
||||||
|
|
||||||
|
<Route path="oauth" component={OAuthInit} {...onEnter} />
|
||||||
|
<Route path="logout" component={Logout} {...onEnter} />
|
||||||
|
|
||||||
<Route path="auth" component={AuthPage}>
|
<Route path="auth" component={AuthPage}>
|
||||||
<Route path="/login" components={new Login()} onEnter={checkAuth} />
|
<Route path="/login" components={new Login()} {...onEnter} />
|
||||||
<Route path="/password" components={new Password()} onEnter={checkAuth} />
|
<Route path="/password" components={new Password()} {...onEnter} />
|
||||||
<Route path="/register" components={new Register()} />
|
<Route path="/register" components={new Register()} {...onEnter} />
|
||||||
<Route path="/activation" components={new Activation()} />
|
<Route path="/activation" components={new Activation()} {...onEnter} />
|
||||||
<Route path="/oauth/permissions" components={new Permissions()} onEnter={checkAuth} />
|
<Route path="/oauth/permissions" components={new Permissions()} {...onEnter} />
|
||||||
<Route path="/password-change" components={new PasswordChange()} />
|
<Route path="/password-change" components={new PasswordChange()} {...onEnter} />
|
||||||
<Route path="/forgot-password" components={new ForgotPassword()} />
|
<Route path="/forgot-password" components={new ForgotPassword()} {...onEnter} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="oauth" component={OAuthInit} />
|
|
||||||
<Route path="logout" component={Logout} />
|
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
5
src/services/authFlow.js
Normal file
5
src/services/authFlow.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import AuthFlow from './authFlow/AuthFlow';
|
||||||
|
|
||||||
|
// TODO: a way to unload service (when we are on account page)
|
||||||
|
|
||||||
|
export default new AuthFlow();
|
9
src/services/authFlow/AbstractState.js
Normal file
9
src/services/authFlow/AbstractState.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export default class AbstractState {
|
||||||
|
resolve() {}
|
||||||
|
goBack() {
|
||||||
|
throw new Error('There is no way back');
|
||||||
|
}
|
||||||
|
reject() {}
|
||||||
|
enter() {}
|
||||||
|
leave() {}
|
||||||
|
}
|
19
src/services/authFlow/ActivationState.js
Normal file
19
src/services/authFlow/ActivationState.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
import CompleteState from './CompleteState';
|
||||||
|
|
||||||
|
export default class ActivationState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
const {user} = context.getState();
|
||||||
|
|
||||||
|
if (user.isActive) {
|
||||||
|
context.setState(new CompleteState());
|
||||||
|
} else {
|
||||||
|
context.navigate('/activation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(context, payload) {
|
||||||
|
context.run('activate', payload)
|
||||||
|
.then(() => context.setState(new CompleteState()));
|
||||||
|
}
|
||||||
|
}
|
116
src/services/authFlow/AuthFlow.js
Normal file
116
src/services/authFlow/AuthFlow.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { routeActions } from 'react-router-redux';
|
||||||
|
|
||||||
|
import * as actions from 'components/auth/actions';
|
||||||
|
import {updateUser} from 'components/user/actions';
|
||||||
|
|
||||||
|
import RegisterState from './RegisterState';
|
||||||
|
import LoginState from './LoginState';
|
||||||
|
import OAuthState from './OAuthState';
|
||||||
|
import ForgotPasswordState from './ForgotPasswordState';
|
||||||
|
|
||||||
|
const availableActions = {
|
||||||
|
...actions,
|
||||||
|
updateUser
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class AuthFlow {
|
||||||
|
constructor(states) {
|
||||||
|
this.states = states;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStore(store) {
|
||||||
|
this.navigate = (route) => {
|
||||||
|
const {routing} = this.getState();
|
||||||
|
|
||||||
|
if (routing.location.pathname !== route) {
|
||||||
|
this.ignoreRequest = true; // TODO: remove me
|
||||||
|
if (this.replace) {
|
||||||
|
this.replace(route);
|
||||||
|
}
|
||||||
|
store.dispatch(routeActions.push(route));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.replace = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getState = store.getState.bind(store);
|
||||||
|
this.dispatch = store.dispatch.bind(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(payload = {}) {
|
||||||
|
this.state.resolve(this, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(payload = {}) {
|
||||||
|
this.state.reject(this, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack() {
|
||||||
|
this.state.goBack(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
run(actionId, payload) {
|
||||||
|
if (!availableActions[actionId]) {
|
||||||
|
throw new Error(`Action ${actionId} does not exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dispatch(availableActions[actionId](payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(state) {
|
||||||
|
if (!state) {
|
||||||
|
throw new Error('State is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state instanceof state.constructor) {
|
||||||
|
// already in this state
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state && this.state.leave(this);
|
||||||
|
this.state = state;
|
||||||
|
this.state.enter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRequest(path, replace) {
|
||||||
|
this.replace = replace;
|
||||||
|
if (this.ignoreRequest) {
|
||||||
|
this.ignoreRequest = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (path) {
|
||||||
|
case '/oauth':
|
||||||
|
this.setState(new OAuthState());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/register':
|
||||||
|
this.setState(new RegisterState());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/forgot-password':
|
||||||
|
this.setState(new ForgotPasswordState());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/login':
|
||||||
|
case '/password':
|
||||||
|
case '/activation':
|
||||||
|
case '/password-change':
|
||||||
|
case '/oauth/permissions':
|
||||||
|
this.setState(new LoginState());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/logout':
|
||||||
|
this.run('logout');
|
||||||
|
this.setState(new LoginState());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported request: ${path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
this.setState(new LoginState());
|
||||||
|
}
|
||||||
|
}
|
15
src/services/authFlow/ChangePasswordState.js
Normal file
15
src/services/authFlow/ChangePasswordState.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
import CompleteState from './CompleteState';
|
||||||
|
|
||||||
|
export default class ChangePasswordState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
context.navigate('/password-change');
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(context) {
|
||||||
|
context.run('updateUser', {
|
||||||
|
shouldChangePassword: false
|
||||||
|
});
|
||||||
|
context.setState(new CompleteState());
|
||||||
|
}
|
||||||
|
}
|
32
src/services/authFlow/CompleteState.js
Normal file
32
src/services/authFlow/CompleteState.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
import LoginState from './LoginState';
|
||||||
|
import PermissionsState from './PermissionsState';
|
||||||
|
import ActivationState from './ActivationState';
|
||||||
|
import ChangePasswordState from './ChangePasswordState';
|
||||||
|
|
||||||
|
export default class CompleteState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
const {auth, user} = context.getState();
|
||||||
|
|
||||||
|
if (user.isGuest) {
|
||||||
|
context.setState(new LoginState());
|
||||||
|
} else if (!user.isActive) {
|
||||||
|
context.setState(new ActivationState());
|
||||||
|
} else if (user.shouldChangePassword) {
|
||||||
|
context.setState(new ChangePasswordState());
|
||||||
|
} else if (auth.oauth) {
|
||||||
|
context.run('oAuthComplete').then((resp) => {
|
||||||
|
location.href = resp.redirectUri;
|
||||||
|
}, (resp) => {
|
||||||
|
// TODO
|
||||||
|
if (resp.unauthorized) {
|
||||||
|
context.setState(new LoginState());
|
||||||
|
} else if (resp.acceptRequired) {
|
||||||
|
context.setState(new PermissionsState());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
context.navigate('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/services/authFlow/ForgotPasswordState.js
Normal file
16
src/services/authFlow/ForgotPasswordState.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
import LoginState from './LoginState';
|
||||||
|
|
||||||
|
export default class ForgotPasswordState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
context.navigate('/forgot-password');
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(context) {
|
||||||
|
context.setState(new LoginState());
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(context) {
|
||||||
|
context.navigate('/send-message');
|
||||||
|
}
|
||||||
|
}
|
24
src/services/authFlow/LoginState.js
Normal file
24
src/services/authFlow/LoginState.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
import PasswordState from './PasswordState';
|
||||||
|
import ForgotPasswordState from './ForgotPasswordState';
|
||||||
|
|
||||||
|
export default class LoginState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
const {user} = context.getState();
|
||||||
|
|
||||||
|
if (user.email || user.username) {
|
||||||
|
context.setState(new PasswordState());
|
||||||
|
} else {
|
||||||
|
context.navigate('/login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(context, payload) {
|
||||||
|
context.run('login', payload)
|
||||||
|
.then(() => context.setState(new PasswordState()));
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(context) {
|
||||||
|
context.setState(new ForgotPasswordState());
|
||||||
|
}
|
||||||
|
}
|
16
src/services/authFlow/OAuthState.js
Normal file
16
src/services/authFlow/OAuthState.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
import CompleteState from './CompleteState';
|
||||||
|
|
||||||
|
export default class OAuthState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
const query = context.getState().routing.location.query;
|
||||||
|
|
||||||
|
context.run('oAuthValidate', {
|
||||||
|
clientId: query.client_id,
|
||||||
|
redirectUrl: query.redirect_uri,
|
||||||
|
responseType: query.response_type,
|
||||||
|
scope: query.scope,
|
||||||
|
state: query.state
|
||||||
|
}).then(() => context.setState(new CompleteState()));
|
||||||
|
}
|
||||||
|
}
|
34
src/services/authFlow/PasswordState.js
Normal file
34
src/services/authFlow/PasswordState.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
import CompleteState from './CompleteState';
|
||||||
|
import ForgotPasswordState from './ForgotPasswordState';
|
||||||
|
|
||||||
|
export default class PasswordState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
const {user} = context.getState();
|
||||||
|
|
||||||
|
if (!user.isGuest) {
|
||||||
|
context.setState(new CompleteState());
|
||||||
|
} else {
|
||||||
|
context.navigate('/password');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(context, {password}) {
|
||||||
|
const {user} = context.getState();
|
||||||
|
|
||||||
|
context.run('login', {
|
||||||
|
password,
|
||||||
|
login: user.email || user.username
|
||||||
|
})
|
||||||
|
.then(() => context.setState(new CompleteState()));
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(context) {
|
||||||
|
context.setState(new ForgotPasswordState());
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(context) {
|
||||||
|
context.run('logout');
|
||||||
|
context.setState(new LoginState());
|
||||||
|
}
|
||||||
|
}
|
21
src/services/authFlow/PermissionsState.js
Normal file
21
src/services/authFlow/PermissionsState.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
|
||||||
|
export default class PermissionsState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
context.navigate('/oauth/permissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(context) {
|
||||||
|
this.process(context, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(context) {
|
||||||
|
this.process(context, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
process(context, accept) {
|
||||||
|
context.run('oAuthComplete', {
|
||||||
|
accept
|
||||||
|
}).then((resp) => location.href = resp.redirectUri);
|
||||||
|
}
|
||||||
|
}
|
22
src/services/authFlow/RegisterState.js
Normal file
22
src/services/authFlow/RegisterState.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
import CompleteState from './CompleteState';
|
||||||
|
|
||||||
|
export default class RegisterState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
const {user} = context.getState();
|
||||||
|
|
||||||
|
if (!user.isGuest) {
|
||||||
|
context.setState(new CompleteState());
|
||||||
|
} else {
|
||||||
|
context.navigate('/register');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(context, payload) {
|
||||||
|
context.run('register', payload)
|
||||||
|
.then(() => context.setState(new CompleteState()));
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(context) {
|
||||||
|
}
|
||||||
|
}
|
@ -28,9 +28,11 @@ function buildQuery(data) {
|
|||||||
|
|
||||||
let authToken;
|
let authToken;
|
||||||
|
|
||||||
|
const checkStatus = (resp) => Promise[resp.status >= 200 && resp.status < 300 ? 'resolve' : 'reject'](resp);
|
||||||
const toJSON = (resp) => resp.json();
|
const toJSON = (resp) => resp.json();
|
||||||
// if resp.success does not exist - degradating to HTTP status codes
|
const rejectWithJSON = (resp) => toJSON(resp).then((resp) => {throw resp;});
|
||||||
const handleResponse = (resp) => Promise[resp.success || typeof resp.success === 'undefined' ? 'resolve' : 'reject'](resp);
|
const handleResponse = (resp) => Promise[resp.success || typeof resp.success === 'undefined' ? 'resolve' : 'reject'](resp);
|
||||||
|
|
||||||
const getDefaultHeaders = () => {
|
const getDefaultHeaders = () => {
|
||||||
const header = {Accept: 'application/json'};
|
const header = {Accept: 'application/json'};
|
||||||
|
|
||||||
@ -51,7 +53,8 @@ export default {
|
|||||||
},
|
},
|
||||||
body: buildQuery(data)
|
body: buildQuery(data)
|
||||||
})
|
})
|
||||||
.then(toJSON)
|
.then(checkStatus)
|
||||||
|
.then(toJSON, rejectWithJSON)
|
||||||
.then(handleResponse)
|
.then(handleResponse)
|
||||||
;
|
;
|
||||||
},
|
},
|
||||||
@ -65,7 +68,8 @@ export default {
|
|||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
headers: getDefaultHeaders()
|
headers: getDefaultHeaders()
|
||||||
})
|
})
|
||||||
.then(toJSON)
|
.then(checkStatus)
|
||||||
|
.then(toJSON, rejectWithJSON)
|
||||||
.then(handleResponse)
|
.then(handleResponse)
|
||||||
;
|
;
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user