1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

feat: add support for username/password login

This commit is contained in:
Ivar Conradi Østhus 2020-05-12 22:57:33 +02:00
parent 91b8f15364
commit 63091184d3
7 changed files with 133 additions and 5 deletions

View File

@ -4,8 +4,10 @@ import { Card, CardTitle, CardText } from 'react-mdl';
import Modal from 'react-modal'; import Modal from 'react-modal';
import AuthenticationSimpleComponent from './authentication-simple-component'; import AuthenticationSimpleComponent from './authentication-simple-component';
import AuthenticationCustomComponent from './authentication-custom-component'; import AuthenticationCustomComponent from './authentication-custom-component';
import AuthenticationPasswordComponent from './authentication-password-component';
const SIMPLE_TYPE = 'unsecure'; const SIMPLE_TYPE = 'unsecure';
const PASSWORD_TYPE = 'password';
const customStyles = { const customStyles = {
overlay: { overlay: {
@ -34,7 +36,9 @@ class AuthComponent extends React.Component {
static propTypes = { static propTypes = {
user: PropTypes.object.isRequired, user: PropTypes.object.isRequired,
unsecureLogin: PropTypes.func.isRequired, unsecureLogin: PropTypes.func.isRequired,
passwordLogin: PropTypes.func.isRequired,
fetchFeatureToggles: PropTypes.func.isRequired, fetchFeatureToggles: PropTypes.func.isRequired,
fetchUIConfig: PropTypes.func.isRequired,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
}; };
@ -43,7 +47,17 @@ class AuthComponent extends React.Component {
if (!authDetails) return null; if (!authDetails) return null;
let content; let content;
if (authDetails.type === SIMPLE_TYPE) { if (authDetails.type === PASSWORD_TYPE) {
content = (
<AuthenticationPasswordComponent
passwordLogin={this.props.passwordLogin}
authDetails={authDetails}
fetchFeatureToggles={this.props.fetchFeatureToggles}
fetchUIConfig={this.props.fetchUIConfig}
history={this.props.history}
/>
);
} else if (authDetails.type === SIMPLE_TYPE) {
content = ( content = (
<AuthenticationSimpleComponent <AuthenticationSimpleComponent
unsecureLogin={this.props.unsecureLogin} unsecureLogin={this.props.unsecureLogin}
@ -66,7 +80,7 @@ class AuthComponent extends React.Component {
background: 'rgb(96, 125, 139)', background: 'rgb(96, 125, 139)',
}} }}
> >
Action required! Action Required
</CardTitle> </CardTitle>
<CardText>{content}</CardText> <CardText>{content}</CardText>
</Card> </Card>

View File

@ -1,11 +1,14 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import AuthenticationComponent from './authentication-component'; import AuthenticationComponent from './authentication-component';
import { unsecureLogin } from '../../store/user/actions'; import { unsecureLogin, passwordLogin } from '../../store/user/actions';
import { fetchFeatureToggles } from '../../store/feature-actions'; import { fetchFeatureToggles } from '../../store/feature-actions';
import { fetchUIConfig } from '../../store/ui-config/actions';
const mapDispatchToProps = { const mapDispatchToProps = {
unsecureLogin, unsecureLogin,
passwordLogin,
fetchFeatureToggles, fetchFeatureToggles,
fetchUIConfig,
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({

View File

@ -0,0 +1,87 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CardActions, Button, Textfield } from 'react-mdl';
class EnterpriseAuthenticationComponent extends React.Component {
static propTypes = {
authDetails: PropTypes.object.isRequired,
passwordLogin: PropTypes.func.isRequired,
fetchFeatureToggles: PropTypes.func.isRequired,
fetchUIConfig: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
};
constructor() {
super();
this.state = {};
}
handleSubmit = async evt => {
evt.preventDefault();
const { username, password } = this.state;
if (!username) {
this.setState({ usernameError: 'This is a required field' });
return;
}
if (!password) {
this.setState({ passwordError: 'This is a required field' });
return;
}
const user = { username, password };
const path = evt.target.action;
try {
await this.props.passwordLogin(path, user);
await this.props.fetchFeatureToggles();
await this.props.fetchUIConfig();
this.props.history.push(`/`);
} catch (error) {
if (error.statusCode === 404) {
this.setState({ error: 'User not found', password: '' });
} else if (error.statusCode === 400) {
this.setState({ error: 'Wrong password', password: '' });
} else {
this.setState({ error: 'Could not sign in at the moment.' });
}
}
};
render() {
const authDetails = this.props.authDetails;
const { username, usernameError, password, passwordError, error } = this.state;
return (
<form onSubmit={this.handleSubmit} action={authDetails.path}>
<p>{authDetails.message}</p>
<p style={{ color: 'red' }}>{error}</p>
<Textfield
label="Username or email"
floatingLabel
name="username"
type="string"
onChange={evt => this.setState({ username: evt.target.value })}
value={username}
error={usernameError}
/>
<Textfield
label="Password"
onChange={evt => this.setState({ password: evt.target.value })}
floatingLabel
name="password"
type="password"
value={password}
error={passwordError}
/>
<br />
<CardActions style={{ textAlign: 'center' }}>
<Button raised colored>
Sign in
</Button>
</CardActions>
</form>
);
}
}
export default EnterpriseAuthenticationComponent;

View File

@ -8,9 +8,10 @@ function extractLegacyMsg(body) {
} }
class ServiceError extends Error { class ServiceError extends Error {
constructor() { constructor(statusCode = 500) {
super(defaultErrorMessage); super(defaultErrorMessage);
this.name = 'ServiceError'; this.name = 'ServiceError';
this.statusCode = statusCode;
} }
} }
@ -52,7 +53,7 @@ export function throwIfNotSuccess(response) {
}); });
}); });
} else { } else {
return Promise.reject(new ServiceError()); return Promise.reject(new ServiceError(response.status));
} }
} }
return Promise.resolve(response); return Promise.resolve(response);

View File

@ -20,8 +20,18 @@ function unsecureLogin(path, user) {
.then(response => response.json()); .then(response => response.json());
} }
function passwordLogin(path, data) {
return fetch(path, {
method: 'POST',
credentials: 'include',
headers,
body: JSON.stringify(data),
}).then(throwIfNotSuccess);
}
export default { export default {
fetchUser, fetchUser,
unsecureLogin, unsecureLogin,
logoutUser, logoutUser,
passwordLogin,
}; };

View File

@ -38,6 +38,14 @@ export function unsecureLogin(path, user) {
}; };
} }
export function passwordLogin(path, user) {
return dispatch => {
dispatch({ type: START_FETCH_USER });
return api.passwordLogin(path, user).then(json => dispatch(updateUser(json)));
};
}
export function logoutUser() { export function logoutUser() {
return dispatch => { return dispatch => {
dispatch({ type: USER_LOGOUT }); dispatch({ type: USER_LOGOUT });

View File

@ -115,6 +115,11 @@ module.exports = {
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
}, },
'/auth': {
target: process.env.UNLEASH_API || 'http://localhost:4242',
changeOrigin: true,
secure: false,
},
}, },
port: process.env.PORT || 3000, port: process.env.PORT || 3000,
}, },