1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

Feat/auth hosted section (#280)

This commit is contained in:
Ivar Conradi Østhus 2021-04-29 21:55:48 +02:00 committed by GitHub
parent d7e6219070
commit 4b826715a7
6 changed files with 324 additions and 1 deletions

View File

@ -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 (
<div>
<br />
<div>
{options.map(o => (
<div
key={o.type}
className={classnames(
styles.contentContainer,
commonStyles.contentSpacingY
)}
>
<Button color="primary" variant="outlined" href={o.path} startIcon={o.type === 'google' ? <GoogleSvg /> : <LockRounded />}>
{o.message}
</Button>
</div>
))}
</div>
<p className={styles.fancyLine}>or</p>
<form onSubmit={handleSubmit} action={authDetails.path}>
<Typography variant="subtitle2" className={styles.apiError}>
{apiError}
</Typography>
<div
className={classnames(
styles.contentContainer,
commonStyles.contentSpacingY
)}
>
<TextField
label="Username or email"
name="username"
type="string"
onChange={evt => setUsername(evt.target.value)}
value={username}
error={!!usernameError}
helperText={usernameError}
variant="outlined"
size="small"
/>
<TextField
label="Password"
onChange={evt => setPassword(evt.target.value)}
name="password"
type="password"
value={password}
error={!!passwordError}
helperText={passwordError}
variant="outlined"
size="small"
/>
<Grid container spacing={3}>
<Grid item xs={6}>
<Button
variant="contained"
color="primary"
type="submit"
style={{ maxWidth: '150px' }}
>
Sign in
</Button>
</Grid>
<Grid item xs={6}>
<Link to="/forgotten-password">
<Typography variant="body2" align="right">
Forgot your password?
</Typography>
</Link>
</Grid>
</Grid>
<br />
<br />
<Typography variant="body2" align="center">Don't have an account? <br /> <a href="https://www.unleash-hosted.com/pricing">Sign up</a></Typography>
</div>
</form>
</div>
);
};
PasswordAuth.propTypes = {
authDetails: PropTypes.object.isRequired,
passwordLogin: PropTypes.func.isRequired,
loadInitialData: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
};
export default PasswordAuth;

View File

@ -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',
}
}
}));

View File

@ -0,0 +1,102 @@
export const GoogleSvg = () => (
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width="46px"
height="46px"
viewBox="0 0 46 46"
style={{display: 'block', width: '36px', height: '36px'}}
>
<defs>
<filter
x="-50%"
y="-50%"
width="200%"
height="200%"
filterUnits="objectBoundingBox"
id="filter-1"
>
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur
stdDeviation="0.5"
in="shadowOffsetOuter1"
result="shadowBlurOuter1"
/>
<feColorMatrix
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.168 0"
in="shadowBlurOuter1"
type="matrix"
result="shadowMatrixOuter1"
/>
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter2" />
<feGaussianBlur
stdDeviation="0.5"
in="shadowOffsetOuter2"
result="shadowBlurOuter2"
/>
<feColorMatrix
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.084 0"
in="shadowBlurOuter2"
type="matrix"
result="shadowMatrixOuter2"
/>
<feMerge>
<feMergeNode in="shadowMatrixOuter1" />
<feMergeNode in="shadowMatrixOuter2" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="40" height="40" rx="2" />
</defs>
<g
id="Google-Button"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="9-PATCH" transform="translate(-608.000000, -160.000000)" />
<g
id="btn_google_light_normal"
transform="translate(-1.000000, -1.000000)"
>
<g
id="button"
transform="translate(4.000000, 4.000000)"
filter="url(#filter-1)"
>
<g id="button-bg">
<use fill="#FFFFFF" fillRule="evenodd" />
<use fill="none" />
<use fill="none" />
<use fill="none" />
</g>
</g>
<g id="logo_googleg_48dp" transform="translate(15.000000, 15.000000)">
<path
d="M17.64,9.20454545 C17.64,8.56636364 17.5827273,7.95272727 17.4763636,7.36363636 L9,7.36363636 L9,10.845 L13.8436364,10.845 C13.635,11.97 13.0009091,12.9231818 12.0477273,13.5613636 L12.0477273,15.8195455 L14.9563636,15.8195455 C16.6581818,14.2527273 17.64,11.9454545 17.64,9.20454545 L17.64,9.20454545 Z"
id="Shape"
fill="#4285F4"
/>
<path
d="M9,18 C11.43,18 13.4672727,17.1940909 14.9563636,15.8195455 L12.0477273,13.5613636 C11.2418182,14.1013636 10.2109091,14.4204545 9,14.4204545 C6.65590909,14.4204545 4.67181818,12.8372727 3.96409091,10.71 L0.957272727,10.71 L0.957272727,13.0418182 C2.43818182,15.9831818 5.48181818,18 9,18 L9,18 Z"
id="Shape"
fill="#34A853"
/>
<path
d="M3.96409091,10.71 C3.78409091,10.17 3.68181818,9.59318182 3.68181818,9 C3.68181818,8.40681818 3.78409091,7.83 3.96409091,7.29 L3.96409091,4.95818182 L0.957272727,4.95818182 C0.347727273,6.17318182 0,7.54772727 0,9 C0,10.4522727 0.347727273,11.8268182 0.957272727,13.0418182 L3.96409091,10.71 L3.96409091,10.71 Z"
id="Shape"
fill="#FBBC05"
/>
<path
d="M9,3.57954545 C10.3213636,3.57954545 11.5077273,4.03363636 12.4404545,4.92545455 L15.0218182,2.34409091 C13.4631818,0.891818182 11.4259091,0 9,0 C5.48181818,0 2.43818182,2.01681818 0.957272727,4.95818182 L3.96409091,7.29 C4.67181818,5.16272727 6.65590909,3.57954545 9,3.57954545 L9,3.57954545 Z"
id="Shape"
fill="#EA4335"
/>
<path d="M0,0 L18,0 L18,18 L0,18 L0,0 Z" id="Shape" />
</g>
<g id="handles_square" />
</g>
</g>
</svg>
)

View File

@ -134,7 +134,7 @@ const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => {
)}
>
<Button color="primary" variant="contained" href={o.path}>
{o.value}
{o.message || o.value}
</Button>
</div>
))}

View File

@ -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 = (
<HostedAuth
passwordLogin={this.props.passwordLogin}
authDetails={authDetails}
loadInitialData={this.props.loadInitialData}
history={this.props.history}
/>
);
} else {
content = (
<AuthenticationCustomComponent authDetails={authDetails} />

View File

@ -36,12 +36,24 @@ function AdminAuthPage({ authenticationType, history }) {
in order configure Single Sign-on.</Alert>
}
/>
<ConditionallyRender condition={authenticationType === 'demo'}
show={
<Alert severity="warning">
You are running Unleash in demo mode. You have to use the Enterprise edition
in order configure Single Sign-on.</Alert>
}
/>
<ConditionallyRender condition={authenticationType === 'custom'}
show={
<Alert severity="warning">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.</Alert>
}
/>
<ConditionallyRender condition={authenticationType === 'hosted'}
show={
<Alert severity="info">You Unleash instance is managed by the Unleash-hosted team.</Alert>
}
/>
</PageContent>
</div>
);