From 4b826715a7bdd2eb03ba29132efad2a5acbb2607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivar=20Conradi=20=C3=98sthus?= Date: Thu, 29 Apr 2021 21:55:48 +0200 Subject: [PATCH] Feat/auth hosted section (#280) --- .../component/user/HostedAuth/HostedAuth.jsx | 158 ++++++++++++++++++ .../user/HostedAuth/HostedAuth.styles.js | 40 +++++ .../src/component/user/HostedAuth/Icons.jsx | 102 +++++++++++ .../user/PasswordAuth/PasswordAuth.jsx | 2 +- .../user/authentication-component.jsx | 11 ++ .../src/page/admin/auth/authentication.jsx | 12 ++ 6 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 frontend/src/component/user/HostedAuth/HostedAuth.jsx create mode 100644 frontend/src/component/user/HostedAuth/HostedAuth.styles.js create mode 100644 frontend/src/component/user/HostedAuth/Icons.jsx diff --git a/frontend/src/component/user/HostedAuth/HostedAuth.jsx b/frontend/src/component/user/HostedAuth/HostedAuth.jsx new file mode 100644 index 0000000000..cce911a25d --- /dev/null +++ b/frontend/src/component/user/HostedAuth/HostedAuth.jsx @@ -0,0 +1,158 @@ +import React, { useState } from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; +import { Button, Grid, TextField, Typography } from '@material-ui/core'; +import LockRounded from '@material-ui/icons/LockRounded'; +import { useHistory } from 'react-router'; +import { useCommonStyles } from '../../../common.styles'; +import { useStyles } from './HostedAuth.styles'; +import { Link } from 'react-router-dom'; +import { GoogleSvg } from './Icons'; + +const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => { + const commonStyles = useCommonStyles(); + const styles = useStyles(); + const history = useHistory(); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [errors, setErrors] = useState({ + usernameError: '', + passwordError: '', + }); + + const handleSubmit = async evt => { + evt.preventDefault(); + + if (!username) { + setErrors(prev => ({ + ...prev, + usernameError: 'This is a required field', + })); + } + if (!password) { + setErrors(prev => ({ + ...prev, + passwordError: 'This is a required field', + })); + } + + if (!password || !username) { + return; + } + + const user = { username, password }; + const path = evt.target.action; + + try { + await passwordLogin(path, user); + await loadInitialData(); + history.push(`/`); + } catch (error) { + if (error.statusCode === 404 || error.statusCode === 400) { + setErrors(prev => ({ + ...prev, + apiError: 'Invalid login details', + })); + setPassword(''); + setUsername(''); + } else { + setErrors({ + apiError: 'Unknown error while trying to authenticate.', + }); + } + } + }; + + + const { usernameError, passwordError, apiError } = errors; + const { options = [] } = authDetails; + + return ( +
+
+
+ {options.map(o => ( +
+ +
+ ))} +
+

or

+
+ + {apiError} + +
+ setUsername(evt.target.value)} + value={username} + error={!!usernameError} + helperText={usernameError} + variant="outlined" + size="small" + /> + setPassword(evt.target.value)} + name="password" + type="password" + value={password} + error={!!passwordError} + helperText={passwordError} + variant="outlined" + size="small" + /> + + + + + + + + Forgot your password? + + + + +
+
+ Don't have an account?
Sign up
+ + +
+
+
+ ); +}; + +PasswordAuth.propTypes = { + authDetails: PropTypes.object.isRequired, + passwordLogin: PropTypes.func.isRequired, + loadInitialData: PropTypes.func.isRequired, + history: PropTypes.object.isRequired, +}; + +export default PasswordAuth; diff --git a/frontend/src/component/user/HostedAuth/HostedAuth.styles.js b/frontend/src/component/user/HostedAuth/HostedAuth.styles.js new file mode 100644 index 0000000000..c99473967b --- /dev/null +++ b/frontend/src/component/user/HostedAuth/HostedAuth.styles.js @@ -0,0 +1,40 @@ +import { makeStyles } from '@material-ui/styles'; + +export const useStyles = makeStyles(theme => ({ + loginContainer: { + minWidth: '350px', + [theme.breakpoints.down('xs')]: { + width: '100%', + minWidth: 'auto', + }, + }, + contentContainer: { + display: 'flex', + flexDirection: 'column', + }, + apiError: { + color: theme.palette.error.main, + }, + + fancyLine: { + display: 'flex', + width: '100%', + margin: '10px 0', + justifyContent: 'center', + alignItems: 'center', + textAlign: 'center', + color: 'gray', + '&::before': { + content: '""', + borderTop: '1px solid silver', + margin: '0 20px 0 0', + flex: '1 0 20px', + }, + '&::after': { + content: '""', + borderTop: '1px solid silver', + margin: '0 20px 0 0', + flex: '1 0 20px', + } + } +})); diff --git a/frontend/src/component/user/HostedAuth/Icons.jsx b/frontend/src/component/user/HostedAuth/Icons.jsx new file mode 100644 index 0000000000..b0c29d3fb1 --- /dev/null +++ b/frontend/src/component/user/HostedAuth/Icons.jsx @@ -0,0 +1,102 @@ +export const GoogleSvg = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) \ No newline at end of file diff --git a/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx b/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx index 2e043a5e39..d5be186716 100644 --- a/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx +++ b/frontend/src/component/user/PasswordAuth/PasswordAuth.jsx @@ -134,7 +134,7 @@ const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => { )} > ))} diff --git a/frontend/src/component/user/authentication-component.jsx b/frontend/src/component/user/authentication-component.jsx index 0235155b8c..452069b7fe 100644 --- a/frontend/src/component/user/authentication-component.jsx +++ b/frontend/src/component/user/authentication-component.jsx @@ -3,11 +3,13 @@ import PropTypes from 'prop-types'; import SimpleAuth from './SimpleAuth/SimpleAuth'; import AuthenticationCustomComponent from './authentication-custom-component'; import PasswordAuth from './PasswordAuth/PasswordAuth'; +import HostedAuth from './HostedAuth/HostedAuth'; import DemoAuth from './DemoAuth'; const SIMPLE_TYPE = 'unsecure'; const DEMO_TYPE = 'demo'; const PASSWORD_TYPE = 'password'; +const HOSTED_TYPE = 'enterprise-hosted'; class AuthComponent extends React.Component { static propTypes = { @@ -51,6 +53,15 @@ class AuthComponent extends React.Component { history={this.props.history} /> ); + } else if (authDetails.type === HOSTED_TYPE) { + content = ( + + ); } else { content = ( diff --git a/frontend/src/page/admin/auth/authentication.jsx b/frontend/src/page/admin/auth/authentication.jsx index cd2c065949..31f9dc34af 100644 --- a/frontend/src/page/admin/auth/authentication.jsx +++ b/frontend/src/page/admin/auth/authentication.jsx @@ -36,12 +36,24 @@ function AdminAuthPage({ authenticationType, history }) { in order configure Single Sign-on. } /> + + You are running Unleash in demo mode. You have to use the Enterprise edition + in order configure Single Sign-on. + } + /> You have decided to use custom authentication type. You have to use the Enterprise edition in order configure Single Sign-on from the user interface. } /> + You Unleash instance is managed by the Unleash-hosted team. + } + /> );