21720: replaced x8 spacing with indexed values
[arvados.git] / services / workbench2 / src / views / login-panel / login-panel.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { connect, DispatchProp } from 'react-redux';
7 import { Grid, Typography, Button, Select } from '@material-ui/core';
8 import { CustomStyleRulesCallback } from 'common/custom-theme';
9 import { WithStyles, withStyles } from '@material-ui/core/styles';
10 import { login, authActions } from 'store/auth/auth-action';
11 import { ArvadosTheme } from 'common/custom-theme';
12 import { RootState } from 'store/store';
13 import { LoginForm } from 'views-components/login-form/login-form';
14 import Axios, { AxiosResponse } from 'axios';
15 import { Config } from 'common/config';
16 import { sanitizeHTML } from 'common/html-sanitize';
17
18 type CssRules = 'root' | 'container' | 'title' | 'content' | 'content__bolder' | 'button';
19
20 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
21     root: {
22         position: 'relative',
23         backgroundColor: theme.palette.grey["200"],
24         '&::after': {
25             content: `''`,
26             position: 'absolute',
27             top: 0,
28             left: 0,
29             bottom: 0,
30             right: 0,
31             opacity: 0.2,
32         }
33     },
34     container: {
35         width: '560px',
36         zIndex: 10
37     },
38     title: {
39         marginBottom: theme.spacing(6),
40         color: theme.palette.grey["800"]
41     },
42     content: {
43         marginBottom: theme.spacing(3),
44         lineHeight: '1.2rem',
45         color: theme.palette.grey["800"]
46     },
47     'content__bolder': {
48         fontWeight: 'bolder'
49     },
50     button: {
51         boxShadow: 'none'
52     }
53 });
54
55 export type PasswordLoginResponse = {
56     uuid?: string;
57     api_token?: string;
58     message?: string;
59 };
60
61 const doPasswordLogin = (url: string) => (username: string, password: string) => {
62     const formData: string[] = [];
63     formData.push('username='+encodeURIComponent(username));
64     formData.push('password='+encodeURIComponent(password));
65     return Axios.post<string, AxiosResponse<PasswordLoginResponse>>(`${url}/arvados/v1/users/authenticate`, formData.join('&'), {
66         headers: {
67             'Content-Type': 'application/x-www-form-urlencoded'
68         },
69     });
70 };
71
72 type LoginPanelProps = DispatchProp<any> & WithStyles<CssRules> & {
73     remoteHosts: { [key: string]: string },
74     homeCluster: string,
75     localCluster: string,
76     loginCluster: string,
77     welcomePage: string,
78     passwordLogin: boolean,
79 };
80
81 const loginOptions = ['LDAP', 'PAM', 'Test'];
82
83 export const requirePasswordLogin = (config: Config): boolean => {
84     if (config && config.clusterConfig && config.clusterConfig.Login) {
85         return loginOptions
86             .filter(loginOption => !!config.clusterConfig.Login[loginOption])
87             .map(loginOption => config.clusterConfig.Login[loginOption].Enable)
88             .find(enabled => enabled === true) || false;
89     }
90     return false;
91 };
92
93 export const LoginPanel = withStyles(styles)(
94     connect((state: RootState) => ({
95         remoteHosts: state.auth.remoteHosts,
96         homeCluster: state.auth.homeCluster,
97         localCluster: state.auth.localCluster,
98         loginCluster: state.auth.loginCluster,
99         welcomePage: state.auth.config.clusterConfig.Workbench.WelcomePageHTML,
100         passwordLogin: requirePasswordLogin(state.auth.remoteHostsConfig[state.auth.loginCluster || state.auth.homeCluster]),
101         }))(({ classes, dispatch, remoteHosts, homeCluster, localCluster, loginCluster, welcomePage, passwordLogin }: LoginPanelProps) => {
102         const loginBtnLabel = `Log in${(localCluster !== homeCluster && loginCluster !== homeCluster) ? " to "+localCluster+" with user from "+homeCluster : ''}`;
103
104         return (<Grid container justify="center" alignItems="center"
105             className={classes.root}
106             style={{ marginTop: 56, overflowY: "auto", height: "100%" }}>
107             <Grid item className={classes.container}>
108                 <Typography component="div">
109                     <div dangerouslySetInnerHTML={{ __html: sanitizeHTML(welcomePage) }} style={{ margin: "1em" }} />
110                 </Typography>
111                 {Object.keys(remoteHosts).length > 1 && loginCluster === "" &&
112
113                     <Typography component="div" align="right">
114                         <label>Please select the cluster that hosts your user account:</label>
115                         <Select native value={homeCluster} style={{ margin: "1em" }}
116                             onChange={(event) => dispatch(authActions.SET_HOME_CLUSTER(event.target.value))}>
117                             {Object.keys(remoteHosts).map((k) => <option key={k} value={k}>{k}</option>)}
118                         </Select>
119                     </Typography>}
120
121                 {passwordLogin
122                 ? <Typography component="div">
123                     <LoginForm dispatch={dispatch}
124                         loginLabel={loginBtnLabel}
125                         handleSubmit={doPasswordLogin(`https://${remoteHosts[loginCluster || homeCluster]}`)}/>
126                 </Typography>
127                 : <Typography component="div" align="right">
128                     <Button variant="contained" color="primary" style={{ margin: "1em" }}
129                         className={classes.button}
130                         onClick={() => dispatch(login(localCluster, homeCluster, loginCluster, remoteHosts))}>
131                         {loginBtnLabel}
132                     </Button>
133                 </Typography>}
134             </Grid>
135         </Grid >);}
136     ));