From da86b62509465b2af374fe220d647404ec284849 Mon Sep 17 00:00:00 2001 From: ivaosthu Date: Sat, 4 Nov 2017 14:22:31 +0100 Subject: [PATCH] Implement support for cutstom authentication. --- frontend/src/component/app.jsx | 4 +- .../component/feature/form-add-container.jsx | 2 +- .../src/component/feature/list-component.jsx | 1 + .../src/component/strategies/add-container.js | 2 +- .../user/authentication-component.jsx | 77 +++++++++++++++++++ .../user/authentication-container.jsx | 15 ++++ .../user/authentication-custom-component.jsx | 27 +++++++ .../user/authentication-simple-component.jsx | 52 +++++++++++++ .../component/user/show-user-component.jsx | 22 +++--- .../component/user/show-user-container.jsx | 6 +- .../src/component/user/user-component.jsx | 66 ---------------- .../src/component/user/user-container.jsx | 14 ---- frontend/src/component/user/user.scss | 5 ++ frontend/src/data/helper.js | 24 +++++- frontend/src/data/user-api.js | 20 +++++ frontend/src/store/application/actions.js | 12 +-- frontend/src/store/archive-actions.js | 10 +-- frontend/src/store/error-store.js | 4 + frontend/src/store/feature-actions.js | 8 +- frontend/src/store/feature-metrics-actions.js | 2 +- frontend/src/store/history-actions.js | 10 +-- frontend/src/store/strategy/actions.js | 15 +--- frontend/src/store/user/actions.js | 44 ++++++++--- frontend/src/store/user/index.js | 64 ++++----------- frontend/src/store/util.js | 14 ++++ 25 files changed, 314 insertions(+), 206 deletions(-) create mode 100644 frontend/src/component/user/authentication-component.jsx create mode 100644 frontend/src/component/user/authentication-container.jsx create mode 100644 frontend/src/component/user/authentication-custom-component.jsx create mode 100644 frontend/src/component/user/authentication-simple-component.jsx delete mode 100644 frontend/src/component/user/user-component.jsx delete mode 100644 frontend/src/component/user/user-container.jsx create mode 100644 frontend/src/component/user/user.scss create mode 100644 frontend/src/data/user-api.js create mode 100644 frontend/src/store/util.js diff --git a/frontend/src/component/app.jsx b/frontend/src/component/app.jsx index a573d253d2..600fc6e60a 100644 --- a/frontend/src/component/app.jsx +++ b/frontend/src/component/app.jsx @@ -18,7 +18,7 @@ import { Link } from 'react-router'; import styles from './styles.scss'; import ErrorContainer from './error/error-container'; -import UserContainer from './user/user-container'; +import AuthenticationContainer from './user/authentication-container'; import ShowUserContainer from './user/show-user-container'; import ShowApiDetailsContainer from './api/show-api-details-container'; import { ScrollContainer } from 'react-router-scroll'; @@ -136,7 +136,7 @@ export default class App extends Component { return (
- +
diff --git a/frontend/src/component/feature/form-add-container.jsx b/frontend/src/component/feature/form-add-container.jsx index b169599ef2..5ebe4ed535 100644 --- a/frontend/src/component/feature/form-add-container.jsx +++ b/frontend/src/component/feature/form-add-container.jsx @@ -12,7 +12,7 @@ const mapStateToProps = createMapper({ try { [, name] = document.location.hash.match(/name=([a-z0-9-_.]+)/i); } catch (e) { - // nothing + // hide error } return { name }; }, diff --git a/frontend/src/component/feature/list-component.jsx b/frontend/src/component/feature/list-component.jsx index af04fb57a7..e2a589fb4b 100644 --- a/frontend/src/component/feature/list-component.jsx +++ b/frontend/src/component/feature/list-component.jsx @@ -25,6 +25,7 @@ export default class FeatureListComponent extends React.PureComponent { this.props.fetchFeatureToggles(); this.props.fetchFeatureMetrics(); this.timer = setInterval(() => { + // this.props.fetchFeatureToggles(); this.props.fetchFeatureMetrics(); }, 5000); } diff --git a/frontend/src/component/strategies/add-container.js b/frontend/src/component/strategies/add-container.js index b63e8b110a..e7d5a0e8fc 100644 --- a/frontend/src/component/strategies/add-container.js +++ b/frontend/src/component/strategies/add-container.js @@ -53,7 +53,7 @@ export default connect( try { [, name] = document.location.hash.match(/name=([a-z0-9-_.]+)/i); } catch (e) { - // nothing + // hide error } return { name }; }, diff --git a/frontend/src/component/user/authentication-component.jsx b/frontend/src/component/user/authentication-component.jsx new file mode 100644 index 0000000000..dd93373c61 --- /dev/null +++ b/frontend/src/component/user/authentication-component.jsx @@ -0,0 +1,77 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Card, CardTitle, CardText } from 'react-mdl'; +import Modal from 'react-modal'; +import AuthenticationSimpleComponent from './authentication-simple-component'; +import AuthenticationCustomComponent from './authentication-custom-component'; + +const SIMPLE_TYPE = 'unsecure'; + +const customStyles = { + overlay: { + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(0, 0, 0, 0.75)', + zIndex: 99999, + }, + content: { + top: '50%', + left: '50%', + right: 'auto', + bottom: 'auto', + marginRight: '-50%', + transform: 'translate(-50%, -50%)', + backgroundColor: 'transparent', + padding: 0, + overflow: 'none', + }, +}; + +class AuthComponent extends React.Component { + static propTypes = { + user: PropTypes.object.isRequired, + unsecureLogin: PropTypes.func.isRequired, + fetchFeatureToggles: PropTypes.func.isRequired, + }; + + render() { + const authDetails = this.props.user.authDetails; + if (!authDetails) return null; + + let content; + if (authDetails.type === SIMPLE_TYPE) { + content = ( + + ); + } else { + content = ; + } + return ( +
+ + + + Action required + + {content} + + +
+ ); + } +} + +export default AuthComponent; diff --git a/frontend/src/component/user/authentication-container.jsx b/frontend/src/component/user/authentication-container.jsx new file mode 100644 index 0000000000..edc26c530e --- /dev/null +++ b/frontend/src/component/user/authentication-container.jsx @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import AuthenticationComponent from './authentication-component'; +import { unsecureLogin } from '../../store/user/actions'; +import { fetchFeatureToggles } from '../../store/feature-actions'; + +const mapDispatchToProps = { + unsecureLogin, + fetchFeatureToggles, +}; + +const mapStateToProps = state => ({ + user: state.user.toJS(), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(AuthenticationComponent); diff --git a/frontend/src/component/user/authentication-custom-component.jsx b/frontend/src/component/user/authentication-custom-component.jsx new file mode 100644 index 0000000000..1169227ebb --- /dev/null +++ b/frontend/src/component/user/authentication-custom-component.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { CardActions, Button } from 'react-mdl'; + +class AuthenticationCustomComponent extends React.Component { + static propTypes = { + authDetails: PropTypes.object.isRequired, + }; + + render() { + const authDetails = this.props.authDetails; + return ( +
+

{authDetails.message}

+ + + + + +
+ ); + } +} + +export default AuthenticationCustomComponent; diff --git a/frontend/src/component/user/authentication-simple-component.jsx b/frontend/src/component/user/authentication-simple-component.jsx new file mode 100644 index 0000000000..b1c7f2cbd5 --- /dev/null +++ b/frontend/src/component/user/authentication-simple-component.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { hashHistory } from 'react-router'; +import { CardActions, Button, Textfield } from 'react-mdl'; + +class SimpleAuthenticationComponent extends React.Component { + static propTypes = { + authDetails: PropTypes.object.isRequired, + unsecureLogin: PropTypes.func.isRequired, + fetchFeatureToggles: PropTypes.func.isRequired, + }; + + handleSubmit = evt => { + evt.preventDefault(); + const email = this.refs.email.inputRef.value; + const user = { email }; + const path = evt.target.action; + + this.props + .unsecureLogin(path, user) + .then(this.props.fetchFeatureToggles) + .then(() => { + hashHistory.push('/features'); + }); + }; + + render() { + const authDetails = this.props.authDetails; + return ( +
+

{authDetails.message}

+

+ This instance of Unleash is not set up with a secure authentication provider. You can read more + about{' '} + + securing unleash on GitHub + +

+ +
+ + + + + + ); + } +} + +export default SimpleAuthenticationComponent; diff --git a/frontend/src/component/user/show-user-component.jsx b/frontend/src/component/user/show-user-component.jsx index 6223f37e30..ef55e294ba 100644 --- a/frontend/src/component/user/show-user-component.jsx +++ b/frontend/src/component/user/show-user-component.jsx @@ -1,25 +1,23 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Icon, Tooltip } from 'react-mdl'; +import styles from './user.scss'; export default class ShowUserComponent extends React.Component { static propTypes = { - user: PropTypes.object.isRequired, - openEdit: PropTypes.func.isRequired, + profile: PropTypes.object, }; - openEdit = evt => { - evt.preventDefault(); - this.props.openEdit(); - }; + componentDidMount() { + this.props.fetchUser(); + } render() { + const email = this.props.profile ? this.props.profile.email : ''; + const imageUrl = email ? this.props.profile.imageUrl : ''; return ( - - - - - +
+ {email} +
); } } diff --git a/frontend/src/component/user/show-user-container.jsx b/frontend/src/component/user/show-user-container.jsx index dfbf283601..db4b061518 100644 --- a/frontend/src/component/user/show-user-container.jsx +++ b/frontend/src/component/user/show-user-container.jsx @@ -1,13 +1,13 @@ import { connect } from 'react-redux'; import ShowUserComponent from './show-user-component'; -import { openEdit } from '../../store/user/actions'; +import { fetchUser } from '../../store/user/actions'; const mapDispatchToProps = { - openEdit, + fetchUser, }; const mapStateToProps = state => ({ - user: state.user.toJS(), + profile: state.user.get('profile'), }); export default connect(mapStateToProps, mapDispatchToProps)(ShowUserComponent); diff --git a/frontend/src/component/user/user-component.jsx b/frontend/src/component/user/user-component.jsx deleted file mode 100644 index b2dfd6639a..0000000000 --- a/frontend/src/component/user/user-component.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Textfield, Button } from 'react-mdl'; -import Modal from 'react-modal'; - -const customStyles = { - overlay: { - position: 'fixed', - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: 'rgba(0, 0, 0, 0.75)', - zIndex: 99999, - }, - content: { - top: '50%', - left: '50%', - right: 'auto', - bottom: 'auto', - marginRight: '-50%', - transform: 'translate(-50%, -50%)', - backgroundColor: '#FFFFFF', - }, -}; - -class EditUserComponent extends React.Component { - static propTypes = { - user: PropTypes.object.isRequired, - updateUserName: PropTypes.func.isRequired, - save: PropTypes.func.isRequired, - }; - - handleSubmit = evt => { - evt.preventDefault(); - this.props.save(); - }; - - render() { - return ( -
- -

Action required

-
-

You have to specify a username to use Unleash. This will allow us to track your changes.

-
- this.props.updateUserName(e.target.value)} - /> -
- - -
-
-
- ); - } -} - -export default EditUserComponent; diff --git a/frontend/src/component/user/user-container.jsx b/frontend/src/component/user/user-container.jsx deleted file mode 100644 index ead0259620..0000000000 --- a/frontend/src/component/user/user-container.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { connect } from 'react-redux'; -import UserComponent from './user-component'; -import { updateUserName, save } from '../../store/user/actions'; - -const mapDispatchToProps = { - updateUserName, - save, -}; - -const mapStateToProps = state => ({ - user: state.user.toJS(), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(UserComponent); diff --git a/frontend/src/component/user/user.scss b/frontend/src/component/user/user.scss new file mode 100644 index 0000000000..d8a430fc46 --- /dev/null +++ b/frontend/src/component/user/user.scss @@ -0,0 +1,5 @@ +.showUser img { + border-radius: 25px; + height: 32px; + border: 2px solid #ffffff; +} \ No newline at end of file diff --git a/frontend/src/data/helper.js b/frontend/src/data/helper.js index 59e3ed9dd3..f1132843fa 100644 --- a/frontend/src/data/helper.js +++ b/frontend/src/data/helper.js @@ -7,9 +7,29 @@ function extractLegacyMsg(body) { return body && body.length > 0 ? body[0].msg : defaultErrorMessage; } +class ServiceError extends Error { + constructor() { + super(defaultErrorMessage); + this.name = 'ServiceError'; + } +} + +export class AuthenticationError extends Error { + constructor(statusCode, body) { + super('Authentication required'); + this.name = 'AuthenticationError'; + this.statusCode = statusCode; + this.body = body; + } +} + export function throwIfNotSuccess(response) { if (!response.ok) { - if (response.status > 399 && response.status < 404) { + if (response.status === 401) { + return new Promise((resolve, reject) => { + response.json().then(body => reject(new AuthenticationError(response.status, body))); + }); + } else if (response.status > 399 && response.status < 404) { return new Promise((resolve, reject) => { response.json().then(body => { const errorMsg = body && body.isJoi ? extractJoiMsg(body) : extractLegacyMsg(body); @@ -19,7 +39,7 @@ export function throwIfNotSuccess(response) { }); }); } else { - return Promise.reject(new Error(defaultErrorMessage)); + return Promise.reject(new ServiceError()); } } return Promise.resolve(response); diff --git a/frontend/src/data/user-api.js b/frontend/src/data/user-api.js new file mode 100644 index 0000000000..e2671ce8dc --- /dev/null +++ b/frontend/src/data/user-api.js @@ -0,0 +1,20 @@ +import { throwIfNotSuccess, headers } from './helper'; + +const URI = 'api/admin/user'; + +function fetchUser() { + return fetch(URI, { credentials: 'include' }) + .then(throwIfNotSuccess) + .then(response => response.json()); +} + +function unsecureLogin(path, user) { + return fetch(path, { method: 'POST', credentials: 'include', headers, body: JSON.stringify(user) }) + .then(throwIfNotSuccess) + .then(response => response.json()); +} + +export default { + fetchUser, + unsecureLogin, +}; diff --git a/frontend/src/store/application/actions.js b/frontend/src/store/application/actions.js index 34d6181af3..505ec1e120 100644 --- a/frontend/src/store/application/actions.js +++ b/frontend/src/store/application/actions.js @@ -1,4 +1,5 @@ import api from '../../data/applications-api'; +import { dispatchAndThrow } from '../util'; export const RECEIVE_ALL_APPLICATIONS = 'RECEIVE_ALL_APPLICATIONS'; export const ERROR_RECEIVE_ALL_APPLICATIONS = 'ERROR_RECEIVE_ALL_APPLICATIONS'; @@ -16,24 +17,19 @@ const recieveApplication = json => ({ value: json, }); -const errorReceiveApplications = (statusCode, type = ERROR_RECEIVE_ALL_APPLICATIONS) => ({ - type, - statusCode, -}); - export function fetchAll() { return dispatch => api .fetchAll() .then(json => dispatch(recieveAllApplications(json))) - .catch(error => dispatch(errorReceiveApplications(error))); + .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_ALL_APPLICATIONS)); } export function storeApplicationMetaData(appName, key, value) { return dispatch => api .storeApplicationMetaData(appName, key, value) - .catch(error => dispatch(errorReceiveApplications(error, ERROR_UPDATING_APPLICATION_DATA))); + .catch(dispatchAndThrow(dispatch, ERROR_UPDATING_APPLICATION_DATA)); } export function fetchApplication(appName) { @@ -41,5 +37,5 @@ export function fetchApplication(appName) { api .fetchApplication(appName) .then(json => dispatch(recieveApplication(json))) - .catch(error => dispatch(errorReceiveApplications(error))); + .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_ALL_APPLICATIONS)); } diff --git a/frontend/src/store/archive-actions.js b/frontend/src/store/archive-actions.js index b787c04e49..ff9dce272a 100644 --- a/frontend/src/store/archive-actions.js +++ b/frontend/src/store/archive-actions.js @@ -1,4 +1,5 @@ import api from '../data/archive-api'; +import { dispatchAndThrow } from './util'; export const REVIVE_TOGGLE = 'REVIVE_TOGGLE'; export const RECEIVE_ARCHIVE = 'RECEIVE_ARCHIVE'; @@ -14,17 +15,12 @@ const reviveToggle = archiveFeatureToggle => ({ value: archiveFeatureToggle, }); -const errorReceiveArchive = statusCode => ({ - type: ERROR_RECEIVE_ARCHIVE, - statusCode, -}); - export function revive(featureToggle) { return dispatch => api .revive(featureToggle) .then(() => dispatch(reviveToggle(featureToggle))) - .catch(error => dispatch(errorReceiveArchive(error))); + .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_ARCHIVE)); } export function fetchArchive() { @@ -32,5 +28,5 @@ export function fetchArchive() { api .fetchAll() .then(json => dispatch(receiveArchive(json))) - .catch(error => dispatch(errorReceiveArchive(error))); + .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_ARCHIVE)); } diff --git a/frontend/src/store/error-store.js b/frontend/src/store/error-store.js index f4cf4d1964..53a95e778a 100644 --- a/frontend/src/store/error-store.js +++ b/frontend/src/store/error-store.js @@ -9,6 +9,8 @@ import { import { ERROR_UPDATING_STRATEGY, ERROR_CREATING_STRATEGY, ERROR_RECEIVE_STRATEGIES } from './strategy/actions'; +import { FORBIDDEN } from './util'; + const debug = require('debug')('unleash:error-store'); function getInitState() { @@ -35,6 +37,8 @@ const strategies = (state = getInitState(), action) => { case ERROR_CREATING_STRATEGY: case ERROR_RECEIVE_STRATEGIES: return addErrorIfNotAlreadyInList(state, action.error.message); + case FORBIDDEN: + return addErrorIfNotAlreadyInList(state, '403 Forbidden'); case MUTE_ERROR: return state.update('list', list => list.remove(list.indexOf(action.error))); default: diff --git a/frontend/src/store/feature-actions.js b/frontend/src/store/feature-actions.js index 487f6e8afc..23f29f9a29 100644 --- a/frontend/src/store/feature-actions.js +++ b/frontend/src/store/feature-actions.js @@ -1,5 +1,6 @@ import api from '../data/feature-api'; const debug = require('debug')('unleash:feature-actions'); +import { dispatchAndThrow } from './util'; export const ADD_FEATURE_TOGGLE = 'ADD_FEATURE_TOGGLE'; export const REMOVE_FEATURE_TOGGLE = 'REMOVE_FEATURE_TOGGLE'; @@ -38,13 +39,6 @@ function receiveFeatureToggles(json) { }; } -function dispatchAndThrow(dispatch, type) { - return error => { - dispatch({ type, error, receivedAt: Date.now() }); - throw error; - }; -} - export function fetchFeatureToggles() { debug('Start fetching feature toggles'); return dispatch => { diff --git a/frontend/src/store/feature-metrics-actions.js b/frontend/src/store/feature-metrics-actions.js index 2c2fb409e4..7b4c79889a 100644 --- a/frontend/src/store/feature-metrics-actions.js +++ b/frontend/src/store/feature-metrics-actions.js @@ -27,7 +27,7 @@ function receiveSeenApps(json) { function dispatchAndThrow(dispatch, type) { return error => { dispatch({ type, error, receivedAt: Date.now() }); - throw error; + // throw error; }; } diff --git a/frontend/src/store/history-actions.js b/frontend/src/store/history-actions.js index 3367123358..46699dc258 100644 --- a/frontend/src/store/history-actions.js +++ b/frontend/src/store/history-actions.js @@ -1,4 +1,5 @@ import api from '../data/history-api'; +import { dispatchAndThrow } from './util'; export const RECEIVE_HISTORY = 'RECEIVE_HISTORY'; export const ERROR_RECEIVE_HISTORY = 'ERROR_RECEIVE_HISTORY'; @@ -15,17 +16,12 @@ const receiveHistoryforToggle = json => ({ value: json, }); -const errorReceiveHistory = statusCode => ({ - type: ERROR_RECEIVE_HISTORY, - statusCode, -}); - export function fetchHistory() { return dispatch => api .fetchAll() .then(json => dispatch(receiveHistory(json))) - .catch(error => dispatch(errorReceiveHistory(error))); + .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_HISTORY)); } export function fetchHistoryForToggle(toggleName) { @@ -33,5 +29,5 @@ export function fetchHistoryForToggle(toggleName) { api .fetchHistoryForToggle(toggleName) .then(json => dispatch(receiveHistoryforToggle(json))) - .catch(error => dispatch(errorReceiveHistory(error))); + .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_HISTORY)); } diff --git a/frontend/src/store/strategy/actions.js b/frontend/src/store/strategy/actions.js index ef35480b5d..cc7a97e5f2 100644 --- a/frontend/src/store/strategy/actions.js +++ b/frontend/src/store/strategy/actions.js @@ -1,5 +1,6 @@ import api from '../../data/strategy-api'; import applicationApi from '../../data/applications-api'; +import { dispatchAndThrow } from '../util'; export const ADD_STRATEGY = 'ADD_STRATEGY'; export const UPDATE_STRATEGY = 'UPDATE_STRATEGY'; @@ -26,20 +27,8 @@ const receiveStrategies = json => ({ const startCreate = () => ({ type: START_CREATE_STRATEGY }); -const errorReceiveStrategies = statusCode => ({ - type: ERROR_RECEIVE_STRATEGIES, - statusCode, -}); - const startUpdate = () => ({ type: START_UPDATE_STRATEGY }); -function dispatchAndThrow(dispatch, type) { - return error => { - dispatch({ type, error, receivedAt: Date.now() }); - throw error; - }; -} - export function fetchStrategies() { return dispatch => { dispatch(startRequest()); @@ -47,7 +36,7 @@ export function fetchStrategies() { return api .fetchAll() .then(json => dispatch(receiveStrategies(json))) - .catch(error => dispatch(errorReceiveStrategies(error))); + .catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_STRATEGIES)); }; } diff --git a/frontend/src/store/user/actions.js b/frontend/src/store/user/actions.js index 502590ec32..c52ca50c27 100644 --- a/frontend/src/store/user/actions.js +++ b/frontend/src/store/user/actions.js @@ -1,16 +1,38 @@ -export const USER_UPDATE_USERNAME = 'USER_UPDATE_USERNAME'; -export const USER_SAVE = 'USER_SAVE'; -export const USER_EDIT = 'USER_EDIT'; +import api from '../../data/user-api'; +import { dispatchAndThrow } from '../util'; +export const UPDATE_USER = 'UPDATE_USER'; +export const START_FETCH_USER = 'START_FETCH_USER'; +export const ERROR_FETCH_USER = 'ERROR_FETCH_USER'; +const debug = require('debug')('unleash:user-actions'); -export const updateUserName = value => ({ - type: USER_UPDATE_USERNAME, +const updateUser = value => ({ + type: UPDATE_USER, value, }); -export const save = () => ({ - type: USER_SAVE, -}); +function handleError(error) { + debug(error); +} -export const openEdit = () => ({ - type: USER_EDIT, -}); +export function fetchUser() { + debug('Start fetching user'); + return dispatch => { + dispatch({ type: START_FETCH_USER }); + + return api + .fetchUser() + .then(json => dispatch(updateUser(json))) + .catch(dispatchAndThrow(dispatch, ERROR_FETCH_USER)); + }; +} + +export function unsecureLogin(path, user) { + return dispatch => { + dispatch({ type: START_FETCH_USER }); + + return api + .unsecureLogin(path, user) + .then(json => dispatch(updateUser(json))) + .catch(handleError); + }; +} diff --git a/frontend/src/store/user/index.js b/frontend/src/store/user/index.js index 11e31807d8..91a8b332fa 100644 --- a/frontend/src/store/user/index.js +++ b/frontend/src/store/user/index.js @@ -1,59 +1,21 @@ import { Map as $Map } from 'immutable'; -import { USER_UPDATE_USERNAME, USER_SAVE, USER_EDIT } from './actions'; +import { UPDATE_USER } from './actions'; +import { AUTH_REQUIRED } from '../util'; -const COOKIE_NAME = 'username'; - -// Ref: http://stackoverflow.com/questions/10730362/get-cookie-by-name -function readCookie() { - const nameEQ = `${COOKIE_NAME}=`; - const ca = document.cookie.split(';'); - for (let i = 0; i < ca.length; i++) { - let c = ca[i]; - // eslint-disable-next-line eqeqeq - while (c.charAt(0) == ' ') { - c = c.substring(1, c.length); - } - if (c.indexOf(nameEQ) === 0) { - return c.substring(nameEQ.length, c.length); - } - } -} - -function writeCookie(userName) { - document.cookie = `${COOKIE_NAME}=${encodeURIComponent(userName)}; expires=Thu, 18 Dec 2099 12:00:00 UTC`; -} - -function getInitState() { - const userName = decodeURIComponent(readCookie(COOKIE_NAME)); - const showDialog = !userName; - return new $Map({ userName, showDialog }); -} - -function updateUserName(state, action) { - return state.set('userName', action.value); -} - -function save(state) { - const userName = state.get('userName'); - if (userName) { - writeCookie(userName); - return state.set('showDialog', false); - } else { - return state; - } -} - -const settingStore = (state = getInitState(), action) => { +const userStore = (state = new $Map(), action) => { switch (action.type) { - case USER_UPDATE_USERNAME: - return updateUserName(state, action); - case USER_SAVE: - return save(state); - case USER_EDIT: - return state.set('showDialog', true); + case UPDATE_USER: + state = state + .set('profile', action.value) + .set('showDialog', false) + .set('authDetails', undefined); + return state; + case AUTH_REQUIRED: + state = state.set('authDetails', action.error.body).set('showDialog', true); + return state; default: return state; } }; -export default settingStore; +export default userStore; diff --git a/frontend/src/store/util.js b/frontend/src/store/util.js new file mode 100644 index 0000000000..72b350911c --- /dev/null +++ b/frontend/src/store/util.js @@ -0,0 +1,14 @@ +export const AUTH_REQUIRED = 'AUTH_REQUIRED'; +export const FORBIDDEN = 'FORBIDDEN'; + +export function dispatchAndThrow(dispatch, type) { + return error => { + if (error.statusCode === 401) { + dispatch({ type: AUTH_REQUIRED, error, receivedAt: Date.now() }); + } else if (error.statusCode === 403) { + dispatch({ type: FORBIDDEN, error, receivedAt: Date.now() }); + } else { + dispatch({ type, error, receivedAt: Date.now() }); + } + }; +}