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