mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
Feat/auth hosted section (#280)
This commit is contained in:
parent
d7e6219070
commit
4b826715a7
158
frontend/src/component/user/HostedAuth/HostedAuth.jsx
Normal file
158
frontend/src/component/user/HostedAuth/HostedAuth.jsx
Normal 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;
|
40
frontend/src/component/user/HostedAuth/HostedAuth.styles.js
Normal file
40
frontend/src/component/user/HostedAuth/HostedAuth.styles.js
Normal 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',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
102
frontend/src/component/user/HostedAuth/Icons.jsx
Normal file
102
frontend/src/component/user/HostedAuth/Icons.jsx
Normal 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>
|
||||||
|
)
|
@ -134,7 +134,7 @@ const PasswordAuth = ({ authDetails, passwordLogin, loadInitialData }) => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Button color="primary" variant="contained" href={o.path}>
|
<Button color="primary" variant="contained" href={o.path}>
|
||||||
{o.value}
|
{o.message || o.value}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -3,11 +3,13 @@ import PropTypes from 'prop-types';
|
|||||||
import SimpleAuth from './SimpleAuth/SimpleAuth';
|
import SimpleAuth from './SimpleAuth/SimpleAuth';
|
||||||
import AuthenticationCustomComponent from './authentication-custom-component';
|
import AuthenticationCustomComponent from './authentication-custom-component';
|
||||||
import PasswordAuth from './PasswordAuth/PasswordAuth';
|
import PasswordAuth from './PasswordAuth/PasswordAuth';
|
||||||
|
import HostedAuth from './HostedAuth/HostedAuth';
|
||||||
import DemoAuth from './DemoAuth';
|
import DemoAuth from './DemoAuth';
|
||||||
|
|
||||||
const SIMPLE_TYPE = 'unsecure';
|
const SIMPLE_TYPE = 'unsecure';
|
||||||
const DEMO_TYPE = 'demo';
|
const DEMO_TYPE = 'demo';
|
||||||
const PASSWORD_TYPE = 'password';
|
const PASSWORD_TYPE = 'password';
|
||||||
|
const HOSTED_TYPE = 'enterprise-hosted';
|
||||||
|
|
||||||
class AuthComponent extends React.Component {
|
class AuthComponent extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -51,6 +53,15 @@ class AuthComponent extends React.Component {
|
|||||||
history={this.props.history}
|
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 {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<AuthenticationCustomComponent authDetails={authDetails} />
|
<AuthenticationCustomComponent authDetails={authDetails} />
|
||||||
|
@ -36,12 +36,24 @@ function AdminAuthPage({ authenticationType, history }) {
|
|||||||
in order configure Single Sign-on.</Alert>
|
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'}
|
<ConditionallyRender condition={authenticationType === 'custom'}
|
||||||
show={
|
show={
|
||||||
<Alert severity="warning">You have decided to use custom authentication type. You have to use the Enterprise edition
|
<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>
|
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>
|
</PageContent>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user