mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Implement support for cutstom authentication.
This commit is contained in:
		
							parent
							
								
									077b607478
								
							
						
					
					
						commit
						da86b62509
					
				@ -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 (
 | 
			
		||||
            <div className={styles.container}>
 | 
			
		||||
                <UserContainer />
 | 
			
		||||
                <AuthenticationContainer />
 | 
			
		||||
                <Layout fixedHeader>
 | 
			
		||||
                    <Header title={this.getTitleWithLinks()}>
 | 
			
		||||
                        <Navigation>
 | 
			
		||||
 | 
			
		||||
@ -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 };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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 };
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										77
									
								
								frontend/src/component/user/authentication-component.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								frontend/src/component/user/authentication-component.jsx
									
									
									
									
									
										Normal file
									
								
							@ -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 = (
 | 
			
		||||
                <AuthenticationSimpleComponent
 | 
			
		||||
                    unsecureLogin={this.props.unsecureLogin}
 | 
			
		||||
                    authDetails={authDetails}
 | 
			
		||||
                    fetchFeatureToggles={this.props.fetchFeatureToggles}
 | 
			
		||||
                />
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            content = <AuthenticationCustomComponent authDetails={authDetails} />;
 | 
			
		||||
        }
 | 
			
		||||
        return (
 | 
			
		||||
            <div>
 | 
			
		||||
                <Modal isOpen={this.props.user.showDialog} contentLabel="test" style={customStyles}>
 | 
			
		||||
                    <Card shadow={0}>
 | 
			
		||||
                        <CardTitle
 | 
			
		||||
                            expand
 | 
			
		||||
                            style={{
 | 
			
		||||
                                color: '#fff',
 | 
			
		||||
                                background: '#000',
 | 
			
		||||
                            }}
 | 
			
		||||
                        >
 | 
			
		||||
                            Action required
 | 
			
		||||
                        </CardTitle>
 | 
			
		||||
                        <CardText>{content}</CardText>
 | 
			
		||||
                    </Card>
 | 
			
		||||
                </Modal>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default AuthComponent;
 | 
			
		||||
							
								
								
									
										15
									
								
								frontend/src/component/user/authentication-container.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								frontend/src/component/user/authentication-container.jsx
									
									
									
									
									
										Normal file
									
								
							@ -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);
 | 
			
		||||
@ -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 (
 | 
			
		||||
            <div>
 | 
			
		||||
                <p>{authDetails.message}</p>
 | 
			
		||||
                <CardActions style={{ textAlign: 'center' }}>
 | 
			
		||||
                    <a href={authDetails.path}>
 | 
			
		||||
                        <Button raised colored>
 | 
			
		||||
                            Click to Auhtenticate
 | 
			
		||||
                        </Button>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </CardActions>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default AuthenticationCustomComponent;
 | 
			
		||||
@ -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 (
 | 
			
		||||
            <form onSubmit={this.handleSubmit} action={authDetails.path}>
 | 
			
		||||
                <p>{authDetails.message}</p>
 | 
			
		||||
                <p>
 | 
			
		||||
                    This instance of Unleash is not set up with a secure authentication provider. You can read more
 | 
			
		||||
                    about{' '}
 | 
			
		||||
                    <a href="https://github.com/Unleash/unleash/blob/master/docs/securing-unleash.md" target="_blank">
 | 
			
		||||
                        securing unleash on GitHub
 | 
			
		||||
                    </a>
 | 
			
		||||
                </p>
 | 
			
		||||
                <Textfield label="Email" name="email" required type="email" ref="email" />
 | 
			
		||||
                <br />
 | 
			
		||||
 | 
			
		||||
                <CardActions style={{ textAlign: 'center' }}>
 | 
			
		||||
                    <Button raised accent colored>
 | 
			
		||||
                        Unsecure Login
 | 
			
		||||
                    </Button>
 | 
			
		||||
                </CardActions>
 | 
			
		||||
            </form>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default SimpleAuthenticationComponent;
 | 
			
		||||
@ -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 (
 | 
			
		||||
            <a className="mdl-navigation__link" href="#edit-user" onClick={this.openEdit}>
 | 
			
		||||
                <Tooltip label={this.props.user.userName || 'Unknown'} large>
 | 
			
		||||
                    <Icon name="account_circle" />
 | 
			
		||||
                </Tooltip>
 | 
			
		||||
            </a>
 | 
			
		||||
            <div className={styles.showUser}>
 | 
			
		||||
                <img src={imageUrl} title={email} alt={email} />
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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 (
 | 
			
		||||
            <div>
 | 
			
		||||
                <Modal isOpen={this.props.user.showDialog} contentLabel="test" style={customStyles}>
 | 
			
		||||
                    <h2>Action required</h2>
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <p>You have to specify a username to use Unleash. This will allow us to track your changes.</p>
 | 
			
		||||
                        <form onSubmit={this.handleSubmit}>
 | 
			
		||||
                            <Textfield
 | 
			
		||||
                                label="Username"
 | 
			
		||||
                                name="username"
 | 
			
		||||
                                required
 | 
			
		||||
                                value={this.props.user.userName}
 | 
			
		||||
                                onChange={e => this.props.updateUserName(e.target.value)}
 | 
			
		||||
                            />
 | 
			
		||||
                            <br />
 | 
			
		||||
                            <Button raised accent>
 | 
			
		||||
                                Save
 | 
			
		||||
                            </Button>
 | 
			
		||||
                        </form>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </Modal>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default EditUserComponent;
 | 
			
		||||
@ -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);
 | 
			
		||||
							
								
								
									
										5
									
								
								frontend/src/component/user/user.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								frontend/src/component/user/user.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
.showUser img {
 | 
			
		||||
    border-radius: 25px;
 | 
			
		||||
    height: 32px;
 | 
			
		||||
    border: 2px solid #ffffff;
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								frontend/src/data/user-api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/src/data/user-api.js
									
									
									
									
									
										Normal file
									
								
							@ -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,
 | 
			
		||||
};
 | 
			
		||||
@ -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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -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 => {
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ function receiveSeenApps(json) {
 | 
			
		||||
function dispatchAndThrow(dispatch, type) {
 | 
			
		||||
    return error => {
 | 
			
		||||
        dispatch({ type, error, receivedAt: Date.now() });
 | 
			
		||||
        throw error;
 | 
			
		||||
        // throw error;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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));
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								frontend/src/store/util.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/src/store/util.js
									
									
									
									
									
										Normal file
									
								
							@ -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() });
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user