mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	refactor: port auth admin to TS/SWR (#675)
* refactor: format files * refactor: add missing RE UI config flag * refactor: port admin auth index to TS/SWR * refactor: port GoogleAuth to TS/SWR * refactor: port OidcAuth to TS/SWR * refactor: port SamlAuth to TS/SWR * refactor: remove unused e-admin-auth store * refactor: make AutoCreateForm an explicit export * refactor: improve auth settings dir structure * refactor: destructure authenticationType from uiConfig * refactor: use setToastApiError to show errors * refactor: format files * refactor: remove invalid string fields from requests Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
		
							parent
							
								
									234bab6cb4
								
							
						
					
					
						commit
						f4d5ed03aa
					
				| @ -1,16 +1,18 @@ | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import AdminMenu from '../menu/AdminMenu'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import GoogleAuth from './google-auth-container'; | ||||
| import SamlAuth from './saml-auth-container'; | ||||
| import OidcAuth from './oidc-auth-container'; | ||||
| import PasswordAuthSettings from './PasswordAuthSettings'; | ||||
| import TabNav from '../../common/TabNav/TabNav'; | ||||
| import PageContent from '../../common/PageContent/PageContent'; | ||||
| import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; | ||||
| import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { OidcAuth } from './OidcAuth/OidcAuth'; | ||||
| import { SamlAuth } from './SamlAuth/SamlAuth'; | ||||
| import { PasswordAuth } from './PasswordAuth/PasswordAuth'; | ||||
| import { GoogleAuth } from './GoogleAuth/GoogleAuth'; | ||||
| 
 | ||||
| export const AuthSettings = () => { | ||||
|     const { authenticationType } = useUiConfig().uiConfig; | ||||
| 
 | ||||
| function AdminAuthPage({ authenticationType, history }) { | ||||
|     const tabs = [ | ||||
|         { | ||||
|             label: 'OpenID Connect', | ||||
| @ -22,7 +24,7 @@ function AdminAuthPage({ authenticationType, history }) { | ||||
|         }, | ||||
|         { | ||||
|             label: 'Password', | ||||
|             component: <PasswordAuthSettings />, | ||||
|             component: <PasswordAuth />, | ||||
|         }, | ||||
|         { | ||||
|             label: 'Google', | ||||
| @ -32,7 +34,7 @@ function AdminAuthPage({ authenticationType, history }) { | ||||
| 
 | ||||
|     return ( | ||||
|         <div> | ||||
|             <AdminMenu history={history} /> | ||||
|             <AdminMenu /> | ||||
|             <PageContent headerContent="Single Sign-On"> | ||||
|                 <ConditionallyRender | ||||
|                     condition={authenticationType === 'enterprise'} | ||||
| @ -80,12 +82,4 @@ function AdminAuthPage({ authenticationType, history }) { | ||||
|             </PageContent> | ||||
|         </div> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| AdminAuthPage.propTypes = { | ||||
|     match: PropTypes.object.isRequired, | ||||
|     history: PropTypes.object.isRequired, | ||||
|     authenticationType: PropTypes.string, | ||||
| }; | ||||
| 
 | ||||
| export default AdminAuthPage; | ||||
| @ -1,102 +1,119 @@ | ||||
| import React, { ChangeEvent, Fragment } from 'react'; | ||||
| import { FormControl, Grid, MenuItem, Switch, TextField, Select, InputLabel, FormControlLabel } from '@material-ui/core'; | ||||
| import { | ||||
|     FormControl, | ||||
|     FormControlLabel, | ||||
|     Grid, | ||||
|     InputLabel, | ||||
|     MenuItem, | ||||
|     Select, | ||||
|     Switch, | ||||
|     TextField, | ||||
| } from '@material-ui/core'; | ||||
| 
 | ||||
| interface Props {  | ||||
| interface Props { | ||||
|     data?: { | ||||
|         enabled: boolean; | ||||
|         autoCreate: boolean; | ||||
|         defaultRootRole?: string; | ||||
|         emailDomains?: string; | ||||
| 
 | ||||
|     }; | ||||
|     setValue: (name: string, value: string | boolean) => void; | ||||
| } | ||||
| 
 | ||||
| function AutoCreateForm({ data = { enabled: false, autoCreate: false }, setValue }: Props) { | ||||
| export const AutoCreateForm = ({ | ||||
|     data = { enabled: false, autoCreate: false }, | ||||
|     setValue, | ||||
| }: Props) => { | ||||
|     const updateAutoCreate = () => { | ||||
|         setValue('autoCreate', !data.autoCreate); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     const updateDefaultRootRole = (evt: ChangeEvent<{ name?: string; value: unknown }>) => { | ||||
|     const updateDefaultRootRole = ( | ||||
|         evt: ChangeEvent<{ name?: string; value: unknown }> | ||||
|     ) => { | ||||
|         setValue('defaultRootRole', evt.target.value as string); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     const updateField = (e: ChangeEvent<HTMLInputElement>) => { | ||||
|         setValue(e.target.name, e.target.value); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
| return ( | ||||
|     <Fragment> | ||||
|         <Grid container spacing={3}> | ||||
|             <Grid item md={5}> | ||||
|                 <strong>Auto-create users</strong> | ||||
|                 <p> | ||||
|                     Enable automatic creation of new users when signing in. | ||||
|                 </p> | ||||
|     return ( | ||||
|         <Fragment> | ||||
|             <Grid container spacing={3}> | ||||
|                 <Grid item md={5}> | ||||
|                     <strong>Auto-create users</strong> | ||||
|                     <p> | ||||
|                         Enable automatic creation of new users when signing in. | ||||
|                     </p> | ||||
|                 </Grid> | ||||
|                 <Grid item md={6} style={{ padding: '20px' }}> | ||||
|                     <FormControlLabel | ||||
|                         control={ | ||||
|                             <Switch | ||||
|                                 onChange={updateAutoCreate} | ||||
|                                 name="enabled" | ||||
|                                 checked={data.autoCreate} | ||||
|                                 disabled={!data.enabled} | ||||
|                             /> | ||||
|                         } | ||||
|                         label="Auto-create users" | ||||
|                     /> | ||||
|                 </Grid> | ||||
|             </Grid> | ||||
|             <Grid item md={6} style={{ padding: '20px' }}> | ||||
| 
 | ||||
|             <FormControlLabel | ||||
|                 control={ <Switch | ||||
|                     onChange={updateAutoCreate} | ||||
|                     name="enabled" | ||||
|                     checked={data.autoCreate} | ||||
|                     disabled={!data.enabled} | ||||
|                 />} | ||||
|                 label="Auto-create users" | ||||
|             /> | ||||
|             <Grid container spacing={3}> | ||||
|                 <Grid item md={5}> | ||||
|                     <strong>Default Root Role</strong> | ||||
|                     <p> | ||||
|                         Choose which root role the user should get when no | ||||
|                         explicit role mapping exists. | ||||
|                     </p> | ||||
|                 </Grid> | ||||
|                 <Grid item md={6}> | ||||
|                     <FormControl style={{ minWidth: '200px' }}> | ||||
|                         <InputLabel id="defaultRootRole-label"> | ||||
|                             Default Role | ||||
|                         </InputLabel> | ||||
|                         <Select | ||||
|                             labelId="defaultRootRole-label" | ||||
|                             id="defaultRootRole" | ||||
|                             name="defaultRootRole" | ||||
|                             disabled={!data.autoCreate || !data.enabled} | ||||
|                             value={data.defaultRootRole || 'Editor'} | ||||
|                             onChange={updateDefaultRootRole} | ||||
|                         > | ||||
|                             {/*consider these from API or constants. */} | ||||
|                             <MenuItem value="Viewer">Viewer</MenuItem> | ||||
|                             <MenuItem value="Editor">Editor</MenuItem> | ||||
|                             <MenuItem value="Admin">Admin</MenuItem> | ||||
|                         </Select> | ||||
|                     </FormControl> | ||||
|                 </Grid> | ||||
|             </Grid> | ||||
|         </Grid> | ||||
|         <Grid container spacing={3}> | ||||
|             <Grid item md={5}> | ||||
|                 <strong>Default Root Role</strong> | ||||
|                 <p> | ||||
|                     Choose which root role the user should get when no explicit role mapping exists. | ||||
|                 </p> | ||||
|             <Grid container spacing={3}> | ||||
|                 <Grid item md={5}> | ||||
|                     <strong>Email domains</strong> | ||||
|                     <p> | ||||
|                         Comma separated list of email domains that should be | ||||
|                         allowed to sign in. | ||||
|                     </p> | ||||
|                 </Grid> | ||||
|                 <Grid item md={6}> | ||||
|                     <TextField | ||||
|                         onChange={updateField} | ||||
|                         label="Email domains" | ||||
|                         name="emailDomains" | ||||
|                         disabled={!data.autoCreate || !data.enabled} | ||||
|                         required={!!data.autoCreate} | ||||
|                         value={data.emailDomains || ''} | ||||
|                         placeholder="@company.com, @anotherCompany.com" | ||||
|                         style={{ width: '400px' }} | ||||
|                         rows={2} | ||||
|                         variant="outlined" | ||||
|                         size="small" | ||||
|                     /> | ||||
|                 </Grid> | ||||
|             </Grid> | ||||
|             <Grid item md={6}> | ||||
|             <FormControl style={{minWidth: '200px'}}> | ||||
|                 <InputLabel id="defaultRootRole-label">Default Role</InputLabel> | ||||
|                 <Select | ||||
|                     labelId="defaultRootRole-label" | ||||
|                     id="defaultRootRole" | ||||
|                     name="defaultRootRole" | ||||
|                     disabled={!data.autoCreate || !data.enabled} | ||||
|                     value={data.defaultRootRole || 'Editor'} | ||||
|                     onChange={updateDefaultRootRole} | ||||
|                     > | ||||
|                     {/*consider these from API or constants. */} | ||||
|                     <MenuItem value='Viewer'>Viewer</MenuItem> | ||||
|                     <MenuItem value='Editor'>Editor</MenuItem> | ||||
|                     <MenuItem value='Admin'>Admin</MenuItem> | ||||
|                 </Select> | ||||
|             </FormControl> | ||||
|             </Grid> | ||||
|         </Grid> | ||||
|         <Grid container spacing={3}> | ||||
|             <Grid item md={5}> | ||||
|                 <strong>Email domains</strong> | ||||
|                 <p> | ||||
|                     Comma separated list of email domains | ||||
|                     that should be allowed to sign in. | ||||
|                 </p> | ||||
|             </Grid> | ||||
|             <Grid item md={6}> | ||||
|                 <TextField | ||||
|                     onChange={updateField} | ||||
|                     label="Email domains" | ||||
|                     name="emailDomains" | ||||
|                     disabled={!data.autoCreate || !data.enabled} | ||||
|                     required={!!data.autoCreate} | ||||
|                     value={data.emailDomains || ''} | ||||
|                     placeholder="@company.com, @anotherCompany.com" | ||||
|                     style={{ width: '400px' }} | ||||
|                     rows={2} | ||||
|                     variant="outlined" | ||||
|                     size="small" | ||||
|                 /> | ||||
|             </Grid> | ||||
|         </Grid> | ||||
|     </Fragment>); | ||||
| } | ||||
| export default AutoCreateForm; | ||||
|         </Fragment> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| import React, { useState, useEffect, useContext } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import React, { useContext, useEffect, useState } from 'react'; | ||||
| import { | ||||
|     Button, | ||||
|     FormControlLabel, | ||||
| @ -8,30 +7,32 @@ import { | ||||
|     TextField, | ||||
| } from '@material-ui/core'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import PageContent from '../../common/PageContent/PageContent'; | ||||
| import AccessContext from '../../../contexts/AccessContext'; | ||||
| import { ADMIN } from '../../providers/AccessProvider/permissions'; | ||||
| import PageContent from '../../../common/PageContent/PageContent'; | ||||
| import AccessContext from '../../../../contexts/AccessContext'; | ||||
| import { ADMIN } from '../../../providers/AccessProvider/permissions'; | ||||
| import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useAuthSettings from '../../../../hooks/api/getters/useAuthSettings/useAuthSettings'; | ||||
| import useAuthSettingsApi from '../../../../hooks/api/actions/useAuthSettingsApi/useAuthSettingsApi'; | ||||
| import useToast from '../../../../hooks/useToast'; | ||||
| import { formatUnknownError } from '../../../../utils/format-unknown-error'; | ||||
| import { removeEmptyStringFields } from '../../../../utils/remove-empty-string-fields'; | ||||
| 
 | ||||
| const initialState = { | ||||
|     enabled: false, | ||||
|     autoCreate: false, | ||||
|     unleashHostname: location.hostname, | ||||
|     clientId: '', | ||||
|     clientSecret: '', | ||||
|     emailDomains: '', | ||||
| }; | ||||
| 
 | ||||
| function GoogleAuth({ | ||||
|     config, | ||||
|     getGoogleConfig, | ||||
|     updateGoogleConfig, | ||||
|     unleashUrl, | ||||
| }) { | ||||
| export const GoogleAuth = () => { | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const [data, setData] = useState(initialState); | ||||
|     const [info, setInfo] = useState(); | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         getGoogleConfig(); | ||||
|         // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     }, []); | ||||
|     const { config } = useAuthSettings('google'); | ||||
|     const { updateSettings, errors, loading } = useAuthSettingsApi('google'); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (config.clientId) { | ||||
| @ -43,10 +44,10 @@ function GoogleAuth({ | ||||
|         return <span>You need admin privileges to access this section.</span>; | ||||
|     } | ||||
| 
 | ||||
|     const updateField = e => { | ||||
|     const updateField = (event: React.ChangeEvent<HTMLInputElement>) => { | ||||
|         setData({ | ||||
|             ...data, | ||||
|             [e.target.name]: e.target.value, | ||||
|             [event.target.name]: event.target.value, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
| @ -58,19 +59,22 @@ function GoogleAuth({ | ||||
|         setData({ ...data, autoCreate: !data.autoCreate }); | ||||
|     }; | ||||
| 
 | ||||
|     const onSubmit = async e => { | ||||
|         e.preventDefault(); | ||||
|         setInfo('...saving'); | ||||
|     const onSubmit = async (event: React.SyntheticEvent) => { | ||||
|         event.preventDefault(); | ||||
| 
 | ||||
|         try { | ||||
|             await updateGoogleConfig(data); | ||||
|             setInfo('Settings stored'); | ||||
|             setTimeout(() => setInfo(''), 2000); | ||||
|         } catch (e) { | ||||
|             setInfo(e.message); | ||||
|             await updateSettings(removeEmptyStringFields(data)); | ||||
|             setToastData({ | ||||
|                 title: 'Settings stored', | ||||
|                 type: 'success', | ||||
|             }); | ||||
|         } catch (err) { | ||||
|             setToastApiError(formatUnknownError(err)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent> | ||||
|         <PageContent headerContent=""> | ||||
|             <Grid container style={{ marginBottom: '1rem' }}> | ||||
|                 <Grid item xs={12}> | ||||
|                     <Alert severity="info"> | ||||
| @ -84,7 +88,7 @@ function GoogleAuth({ | ||||
|                         </a>{' '} | ||||
|                         to learn how to integrate with Google OAuth 2.0. <br /> | ||||
|                         Callback URL:{' '} | ||||
|                         <code>{unleashUrl}/auth/google/callback</code> | ||||
|                         <code>{uiConfig.unleashUrl}/auth/google/callback</code> | ||||
|                     </Alert> | ||||
|                 </Grid> | ||||
|             </Grid> | ||||
| @ -125,7 +129,7 @@ function GoogleAuth({ | ||||
|                             label="Client ID" | ||||
|                             name="clientId" | ||||
|                             placeholder="" | ||||
|                             value={data.clientId || ''} | ||||
|                             value={data.clientId} | ||||
|                             style={{ width: '400px' }} | ||||
|                             variant="outlined" | ||||
|                             size="small" | ||||
| @ -146,7 +150,7 @@ function GoogleAuth({ | ||||
|                             onChange={updateField} | ||||
|                             label="Client Secret" | ||||
|                             name="clientSecret" | ||||
|                             value={data.clientSecret || ''} | ||||
|                             value={data.clientSecret} | ||||
|                             placeholder="" | ||||
|                             style={{ width: '400px' }} | ||||
|                             variant="outlined" | ||||
| @ -195,9 +199,7 @@ function GoogleAuth({ | ||||
|                             onChange={updateAutoCreate} | ||||
|                             name="enabled" | ||||
|                             checked={data.autoCreate} | ||||
|                         > | ||||
|                             Auto-create users | ||||
|                         </Switch> | ||||
|                         /> | ||||
|                     </Grid> | ||||
|                 </Grid> | ||||
|                 <Grid container spacing={3}> | ||||
| @ -229,22 +231,18 @@ function GoogleAuth({ | ||||
|                             variant="contained" | ||||
|                             color="primary" | ||||
|                             type="submit" | ||||
|                             disabled={loading} | ||||
|                         > | ||||
|                             Save | ||||
|                         </Button>{' '} | ||||
|                         <small>{info}</small> | ||||
|                         <p> | ||||
|                             <small style={{ color: 'red' }}> | ||||
|                                 {errors?.message} | ||||
|                             </small> | ||||
|                         </p> | ||||
|                     </Grid> | ||||
|                 </Grid> | ||||
|             </form> | ||||
|         </PageContent> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| GoogleAuth.propTypes = { | ||||
|     config: PropTypes.object, | ||||
|     unleashUrl: PropTypes.string, | ||||
|     getGoogleConfig: PropTypes.func.isRequired, | ||||
|     updateGoogleConfig: PropTypes.func.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default GoogleAuth; | ||||
| @ -1,5 +1,4 @@ | ||||
| import React, { useState, useEffect, useContext } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import React, { useContext, useEffect, useState } from 'react'; | ||||
| import { | ||||
|     Button, | ||||
|     FormControlLabel, | ||||
| @ -8,34 +7,40 @@ import { | ||||
|     TextField, | ||||
| } from '@material-ui/core'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import PageContent from '../../common/PageContent/PageContent'; | ||||
| import AccessContext from '../../../contexts/AccessContext'; | ||||
| import { ADMIN } from '../../providers/AccessProvider/permissions'; | ||||
| import AutoCreateForm from './AutoCreateForm/AutoCreateForm'; | ||||
| import PageContent from '../../../common/PageContent/PageContent'; | ||||
| import AccessContext from '../../../../contexts/AccessContext'; | ||||
| import { ADMIN } from '../../../providers/AccessProvider/permissions'; | ||||
| import { AutoCreateForm } from '../AutoCreateForm/AutoCreateForm'; | ||||
| import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useAuthSettingsApi from '../../../../hooks/api/actions/useAuthSettingsApi/useAuthSettingsApi'; | ||||
| import useAuthSettings from '../../../../hooks/api/getters/useAuthSettings/useAuthSettings'; | ||||
| import useToast from '../../../../hooks/useToast'; | ||||
| import { formatUnknownError } from '../../../../utils/format-unknown-error'; | ||||
| import { removeEmptyStringFields } from '../../../../utils/remove-empty-string-fields'; | ||||
| 
 | ||||
| const initialState = { | ||||
|     enabled: false, | ||||
|     enableSingleSignOut: false, | ||||
|     autoCreate: false, | ||||
|     unleashHostname: location.hostname, | ||||
|     clientId: '', | ||||
|     discoverUrl: '', | ||||
|     secret: '', | ||||
|     acrValues: '', | ||||
| }; | ||||
| 
 | ||||
| function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
| export const OidcAuth = () => { | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const [data, setData] = useState(initialState); | ||||
|     const [info, setInfo] = useState(); | ||||
|     const [error, setError] = useState(); | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         getOidcConfig(); | ||||
|         // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     }, []); | ||||
|     const { config } = useAuthSettings('oidc'); | ||||
|     const { updateSettings, errors, loading } = useAuthSettingsApi('oidc'); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (config.discoverUrl) { | ||||
|             setData(config); | ||||
|         } | ||||
|         // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     }, [config]); | ||||
| 
 | ||||
|     if (!hasAccess(ADMIN)) { | ||||
| @ -46,8 +51,8 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     const updateField = e => { | ||||
|         setValue(e.target.name, e.target.value); | ||||
|     const updateField = (event: React.ChangeEvent<HTMLInputElement>) => { | ||||
|         setValue(event.target.name, event.target.value); | ||||
|     }; | ||||
| 
 | ||||
|     const updateEnabled = () => { | ||||
| @ -58,28 +63,29 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
|         setData({ ...data, enableSingleSignOut: !data.enableSingleSignOut }); | ||||
|     }; | ||||
| 
 | ||||
|     const setValue = (field, value) => { | ||||
|     const setValue = (name: string, value: string | boolean) => { | ||||
|         setData({ | ||||
|             ...data, | ||||
|             [field]: value, | ||||
|             [name]: value, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     const onSubmit = async e => { | ||||
|         e.preventDefault(); | ||||
|         setInfo('...saving'); | ||||
|         setError(''); | ||||
|     const onSubmit = async (event: React.SyntheticEvent) => { | ||||
|         event.preventDefault(); | ||||
| 
 | ||||
|         try { | ||||
|             await updateOidcConfig(data); | ||||
|             setInfo('Settings stored'); | ||||
|             setTimeout(() => setInfo(''), 2000); | ||||
|         } catch (e) { | ||||
|             setInfo(''); | ||||
|             setError(e.message); | ||||
|             await updateSettings(removeEmptyStringFields(data)); | ||||
|             setToastData({ | ||||
|                 title: 'Settings stored', | ||||
|                 type: 'success', | ||||
|             }); | ||||
|         } catch (err) { | ||||
|             setToastApiError(formatUnknownError(err)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent> | ||||
|         <PageContent headerContent=""> | ||||
|             <Grid container style={{ marginBottom: '1rem' }}> | ||||
|                 <Grid item md={12}> | ||||
|                     <Alert severity="info"> | ||||
| @ -94,7 +100,7 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
|                         to learn how to integrate with specific Open Id Connect | ||||
|                         providers (Okta, Keycloak, Google, etc). <br /> | ||||
|                         Callback URL:{' '} | ||||
|                         <code>{unleashUrl}/auth/oidc/callback</code> | ||||
|                         <code>{uiConfig.unleashUrl}/auth/oidc/callback</code> | ||||
|                     </Alert> | ||||
|                 </Grid> | ||||
|             </Grid> | ||||
| @ -128,7 +134,7 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
|                             onChange={updateField} | ||||
|                             label="Discover URL" | ||||
|                             name="discoverUrl" | ||||
|                             value={data.discoverUrl || ''} | ||||
|                             value={data.discoverUrl} | ||||
|                             disabled={!data.enabled} | ||||
|                             style={{ width: '400px' }} | ||||
|                             variant="outlined" | ||||
| @ -146,7 +152,7 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
|                             onChange={updateField} | ||||
|                             label="Client ID" | ||||
|                             name="clientId" | ||||
|                             value={data.clientId || ''} | ||||
|                             value={data.clientId} | ||||
|                             disabled={!data.enabled} | ||||
|                             style={{ width: '400px' }} | ||||
|                             variant="outlined" | ||||
| @ -167,7 +173,7 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
|                             onChange={updateField} | ||||
|                             label="Client Secret" | ||||
|                             name="secret" | ||||
|                             value={data.secret || ''} | ||||
|                             value={data.secret} | ||||
|                             disabled={!data.enabled} | ||||
|                             style={{ width: '400px' }} | ||||
|                             variant="outlined" | ||||
| @ -180,7 +186,10 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
|                 <Grid container spacing={3}> | ||||
|                     <Grid item md={5}> | ||||
|                         <strong>Enable Single Sign-Out</strong> | ||||
|                         <p>If you enable Single Sign-Out Unleash will redirect the user to the IDP as part of the Sign-out process.</p> | ||||
|                         <p> | ||||
|                             If you enable Single Sign-Out Unleash will redirect | ||||
|                             the user to the IDP as part of the Sign-out process. | ||||
|                         </p> | ||||
|                     </Grid> | ||||
|                     <Grid item md={6} style={{ padding: '20px' }}> | ||||
|                         <FormControlLabel | ||||
| @ -204,15 +213,21 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
|                 <Grid container spacing={3}> | ||||
|                     <Grid item md={5}> | ||||
|                         <strong>ACR Values</strong> | ||||
|                         <p>Requested Authentication Context Class Reference values. If multiple values are specified they should be "space" separated. Will be sent as "acr_values" as  | ||||
|                             part of the authentication request. Unleash will validate the acr value in the id token claims against the list of acr values.</p> | ||||
|                         <p> | ||||
|                             Requested Authentication Context Class Reference | ||||
|                             values. If multiple values are specified they should | ||||
|                             be "space" separated. Will be sent as "acr_values" | ||||
|                             as part of the authentication request. Unleash will | ||||
|                             validate the acr value in the id token claims | ||||
|                             against the list of acr values. | ||||
|                         </p> | ||||
|                     </Grid> | ||||
|                     <Grid item md={6}> | ||||
|                         <TextField | ||||
|                             onChange={updateField} | ||||
|                             label="ACR Values" | ||||
|                             name="acrValues" | ||||
|                             value={data.acrValues || ''} | ||||
|                             value={data.acrValues} | ||||
|                             disabled={!data.enabled} | ||||
|                             style={{ width: '400px' }} | ||||
|                             variant="outlined" | ||||
| @ -229,23 +244,18 @@ function OidcAuth({ config, getOidcConfig, updateOidcConfig, unleashUrl }) { | ||||
|                             variant="contained" | ||||
|                             color="primary" | ||||
|                             type="submit" | ||||
|                             disabled={loading} | ||||
|                         > | ||||
|                             Save | ||||
|                         </Button>{' '} | ||||
|                         <small>{info}</small> | ||||
|                         <small style={{ color: 'red' }}>{error}</small> | ||||
|                         <p> | ||||
|                             <small style={{ color: 'red' }}> | ||||
|                                 {errors?.message} | ||||
|                             </small> | ||||
|                         </p> | ||||
|                     </Grid> | ||||
|                 </Grid> | ||||
|             </form> | ||||
|         </PageContent> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| OidcAuth.propTypes = { | ||||
|     config: PropTypes.object, | ||||
|     unleash: PropTypes.string, | ||||
|     getOidcConfig: PropTypes.func.isRequired, | ||||
|     updateOidcConfig: PropTypes.func.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default OidcAuth; | ||||
| @ -1,30 +1,28 @@ | ||||
| import React, { useState, useContext, useEffect } from 'react'; | ||||
| import { | ||||
|     Button, | ||||
|     FormControlLabel, | ||||
|     Grid, | ||||
|     Switch, | ||||
| } from '@material-ui/core'; | ||||
| import React, { useContext, useEffect, useState } from 'react'; | ||||
| import { Button, FormControlLabel, Grid, Switch } from '@material-ui/core'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import PageContent from '../../common/PageContent/PageContent'; | ||||
| import AccessContext from '../../../contexts/AccessContext'; | ||||
| import { ADMIN } from '../../providers/AccessProvider/permissions'; | ||||
| import useAuthSettings from '../../../hooks/api/getters/useAuthSettings/useAuthSettings'; | ||||
| import useAuthSettingsApi, {ISimpleAuthSettings } from '../../../hooks/api/actions/useAuthSettingsApi/useAuthSettingsApi'; | ||||
| import useToast from '../../../hooks/useToast'; | ||||
| import PageContent from '../../../common/PageContent/PageContent'; | ||||
| import AccessContext from '../../../../contexts/AccessContext'; | ||||
| import { ADMIN } from '../../../providers/AccessProvider/permissions'; | ||||
| import useAuthSettings from '../../../../hooks/api/getters/useAuthSettings/useAuthSettings'; | ||||
| import useAuthSettingsApi, { | ||||
|     ISimpleAuthSettings, | ||||
| } from '../../../../hooks/api/actions/useAuthSettingsApi/useAuthSettingsApi'; | ||||
| import useToast from '../../../../hooks/useToast'; | ||||
| import { formatUnknownError } from '../../../../utils/format-unknown-error'; | ||||
| 
 | ||||
| const PasswordAuthSettings = () => { | ||||
| 
 | ||||
|     const { setToastData } = useToast(); | ||||
| export const PasswordAuth = () => { | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { config } = useAuthSettings('simple'); | ||||
|     const [disablePasswordAuth, setDisablePasswordAuth] = useState<boolean>(false); | ||||
|     const { updateSettings, errors, loading } = useAuthSettingsApi<ISimpleAuthSettings>('simple') | ||||
|     const [disablePasswordAuth, setDisablePasswordAuth] = | ||||
|         useState<boolean>(false); | ||||
|     const { updateSettings, errors, loading } = | ||||
|         useAuthSettingsApi<ISimpleAuthSettings>('simple'); | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| 
 | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         setDisablePasswordAuth(!!config.disabled); | ||||
|     }, [ config.disabled ]); | ||||
|     }, [config.disabled]); | ||||
| 
 | ||||
|     if (!hasAccess(ADMIN)) { | ||||
|         return ( | ||||
| @ -38,12 +36,13 @@ const PasswordAuthSettings = () => { | ||||
|         setDisablePasswordAuth(!disablePasswordAuth); | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     const onSubmit = async evt => { | ||||
|         evt.preventDefault(); | ||||
|     const onSubmit = async (event: React.SyntheticEvent) => { | ||||
|         event.preventDefault(); | ||||
| 
 | ||||
|         try { | ||||
|             const settings: ISimpleAuthSettings = { disabled: disablePasswordAuth }; | ||||
|             const settings: ISimpleAuthSettings = { | ||||
|                 disabled: disablePasswordAuth, | ||||
|             }; | ||||
|             await updateSettings(settings); | ||||
|             setToastData({ | ||||
|                 title: 'Successfully saved', | ||||
| @ -52,20 +51,13 @@ const PasswordAuthSettings = () => { | ||||
|                 type: 'success', | ||||
|                 show: true, | ||||
|             }); | ||||
|         } catch (err: any) { | ||||
|             setToastData({ | ||||
|                 title: 'Could not store settings', | ||||
|                 text: err?.message, | ||||
|                 autoHideDuration: 4000, | ||||
|                 type: 'error', | ||||
|                 show: true, | ||||
|             }); | ||||
|             setDisablePasswordAuth(config.disabled) | ||||
|         } catch (err) { | ||||
|             setToastApiError(formatUnknownError(err)); | ||||
|             setDisablePasswordAuth(config.disabled); | ||||
|         } | ||||
| 
 | ||||
|     }; | ||||
|     return ( | ||||
|         <PageContent headerContent=''> | ||||
|         <PageContent headerContent=""> | ||||
|             <form onSubmit={onSubmit}> | ||||
|                 <Grid container spacing={3}> | ||||
|                     <Grid item md={5}> | ||||
| @ -82,7 +74,9 @@ const PasswordAuthSettings = () => { | ||||
|                                     checked={!disablePasswordAuth} | ||||
|                                 /> | ||||
|                             } | ||||
|                             label={!disablePasswordAuth ? 'Enabled' : 'Disabled'} | ||||
|                             label={ | ||||
|                                 !disablePasswordAuth ? 'Enabled' : 'Disabled' | ||||
|                             } | ||||
|                         /> | ||||
|                     </Grid> | ||||
|                 </Grid> | ||||
| @ -96,12 +90,14 @@ const PasswordAuthSettings = () => { | ||||
|                         > | ||||
|                             Save | ||||
|                         </Button>{' '} | ||||
|                         <p><small style={{ color: 'red' }}>{errors?.message}</small></p> | ||||
|                         <p> | ||||
|                             <small style={{ color: 'red' }}> | ||||
|                                 {errors?.message} | ||||
|                             </small> | ||||
|                         </p> | ||||
|                     </Grid> | ||||
|                 </Grid> | ||||
|             </form> | ||||
|         </PageContent> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| export default PasswordAuthSettings; | ||||
| }; | ||||
| @ -1,5 +1,4 @@ | ||||
| import React, { useState, useEffect, useContext } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import React, { useContext, useEffect, useState } from 'react'; | ||||
| import { | ||||
|     Button, | ||||
|     FormControlLabel, | ||||
| @ -8,32 +7,40 @@ import { | ||||
|     TextField, | ||||
| } from '@material-ui/core'; | ||||
| import { Alert } from '@material-ui/lab'; | ||||
| import PageContent from '../../common/PageContent/PageContent'; | ||||
| import AccessContext from '../../../contexts/AccessContext'; | ||||
| import { ADMIN } from '../../providers/AccessProvider/permissions'; | ||||
| import AutoCreateForm from './AutoCreateForm/AutoCreateForm'; | ||||
| import PageContent from '../../../common/PageContent/PageContent'; | ||||
| import AccessContext from '../../../../contexts/AccessContext'; | ||||
| import { ADMIN } from '../../../providers/AccessProvider/permissions'; | ||||
| import { AutoCreateForm } from '../AutoCreateForm/AutoCreateForm'; | ||||
| import useToast from '../../../../hooks/useToast'; | ||||
| import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import useAuthSettings from '../../../../hooks/api/getters/useAuthSettings/useAuthSettings'; | ||||
| import useAuthSettingsApi from '../../../../hooks/api/actions/useAuthSettingsApi/useAuthSettingsApi'; | ||||
| import { formatUnknownError } from '../../../../utils/format-unknown-error'; | ||||
| import { removeEmptyStringFields } from '../../../../utils/remove-empty-string-fields'; | ||||
| 
 | ||||
| const initialState = { | ||||
|     enabled: false, | ||||
|     autoCreate: false, | ||||
|     unleashHostname: location.hostname, | ||||
|     entityId: '', | ||||
|     signOnUrl: '', | ||||
|     certificate: '', | ||||
|     signOutUrl: '', | ||||
|     spCertificate: '', | ||||
| }; | ||||
| 
 | ||||
| function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { | ||||
| export const SamlAuth = () => { | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const [data, setData] = useState(initialState); | ||||
|     const [info, setInfo] = useState(); | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         getSamlConfig(); | ||||
|         // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     }, []); | ||||
|     const { config } = useAuthSettings('saml'); | ||||
|     const { updateSettings, errors, loading } = useAuthSettingsApi('saml'); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (config.entityId) { | ||||
|             setData(config); | ||||
|         } | ||||
|         // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     }, [config]); | ||||
| 
 | ||||
|     if (!hasAccess(ADMIN)) { | ||||
| @ -44,34 +51,37 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     const updateField = e => { | ||||
|         setValue(e.target.name, e.target.value); | ||||
|     const updateField = (event: React.ChangeEvent<HTMLInputElement>) => { | ||||
|         setValue(event.target.name, event.target.value); | ||||
|     }; | ||||
| 
 | ||||
|     const updateEnabled = () => { | ||||
|         setData({ ...data, enabled: !data.enabled }); | ||||
|     }; | ||||
| 
 | ||||
|     const setValue = (field, value) => { | ||||
|     const setValue = (name: string, value: string | boolean) => { | ||||
|         setData({ | ||||
|             ...data, | ||||
|             [field]: value, | ||||
|             [name]: value, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     const onSubmit = async e => { | ||||
|         e.preventDefault(); | ||||
|         setInfo('...saving'); | ||||
|     const onSubmit = async (event: React.SyntheticEvent) => { | ||||
|         event.preventDefault(); | ||||
| 
 | ||||
|         try { | ||||
|             await updateSamlConfig(data); | ||||
|             setInfo('Settings stored'); | ||||
|             setTimeout(() => setInfo(''), 2000); | ||||
|         } catch (e) { | ||||
|             setInfo(e.message); | ||||
|             await updateSettings(removeEmptyStringFields(data)); | ||||
|             setToastData({ | ||||
|                 title: 'Settings stored', | ||||
|                 type: 'success', | ||||
|             }); | ||||
|         } catch (err) { | ||||
|             setToastApiError(formatUnknownError(err)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent> | ||||
|         <PageContent headerContent=""> | ||||
|             <Grid container style={{ marginBottom: '1rem' }}> | ||||
|                 <Grid item md={12}> | ||||
|                     <Alert severity="info"> | ||||
| @ -86,7 +96,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { | ||||
|                         to learn how to integrate with specific SAML 2.0 | ||||
|                         providers (Okta, Keycloak, etc). <br /> | ||||
|                         Callback URL:{' '} | ||||
|                         <code>{unleashUrl}/auth/saml/callback</code> | ||||
|                         <code>{uiConfig.unleashUrl}/auth/saml/callback</code> | ||||
|                     </Alert> | ||||
|                 </Grid> | ||||
|             </Grid> | ||||
| @ -120,7 +130,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { | ||||
|                             onChange={updateField} | ||||
|                             label="Entity ID" | ||||
|                             name="entityId" | ||||
|                             value={data.entityId || ''} | ||||
|                             value={data.entityId} | ||||
|                             disabled={!data.enabled} | ||||
|                             style={{ width: '400px' }} | ||||
|                             variant="outlined" | ||||
| @ -142,7 +152,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { | ||||
|                             onChange={updateField} | ||||
|                             label="Single Sign-On URL" | ||||
|                             name="signOnUrl" | ||||
|                             value={data.signOnUrl || ''} | ||||
|                             value={data.signOnUrl} | ||||
|                             disabled={!data.enabled} | ||||
|                             style={{ width: '400px' }} | ||||
|                             variant="outlined" | ||||
| @ -164,7 +174,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { | ||||
|                             onChange={updateField} | ||||
|                             label="X.509 Certificate" | ||||
|                             name="certificate" | ||||
|                             value={data.certificate || ''} | ||||
|                             value={data.certificate} | ||||
|                             disabled={!data.enabled} | ||||
|                             style={{ width: '100%' }} | ||||
|                             InputProps={{ | ||||
| @ -196,7 +206,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { | ||||
|                             onChange={updateField} | ||||
|                             label="Single Sign-out URL" | ||||
|                             name="signOutUrl" | ||||
|                             value={data.signOutUrl || ''} | ||||
|                             value={data.signOutUrl} | ||||
|                             disabled={!data.enabled} | ||||
|                             style={{ width: '400px' }} | ||||
|                             variant="outlined" | ||||
| @ -219,7 +229,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { | ||||
|                             onChange={updateField} | ||||
|                             label="X.509 Certificate" | ||||
|                             name="spCertificate" | ||||
|                             value={data.spCertificate || ''} | ||||
|                             value={data.spCertificate} | ||||
|                             disabled={!data.enabled} | ||||
|                             style={{ width: '100%' }} | ||||
|                             InputProps={{ | ||||
| @ -243,22 +253,18 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, unleashUrl }) { | ||||
|                             variant="contained" | ||||
|                             color="primary" | ||||
|                             type="submit" | ||||
|                             disabled={loading} | ||||
|                         > | ||||
|                             Save | ||||
|                         </Button>{' '} | ||||
|                         <small>{info}</small> | ||||
|                         <p> | ||||
|                             <small style={{ color: 'red' }}> | ||||
|                                 {errors?.message} | ||||
|                             </small> | ||||
|                         </p> | ||||
|                     </Grid> | ||||
|                 </Grid> | ||||
|             </form> | ||||
|         </PageContent> | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| SamlAuth.propTypes = { | ||||
|     config: PropTypes.object, | ||||
|     unleash: PropTypes.string, | ||||
|     getSamlConfig: PropTypes.func.isRequired, | ||||
|     updateSamlConfig: PropTypes.func.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default SamlAuth; | ||||
| @ -1,12 +0,0 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import GoogleAuth from './google-auth'; | ||||
| import { getGoogleConfig, updateGoogleConfig } from '../../../store/e-admin-auth/actions'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|     config: state.authAdmin.get('google'), | ||||
|     unleashUrl: state.uiConfig.toJS().unleashUrl, | ||||
| }); | ||||
| 
 | ||||
| const Container = connect(mapStateToProps, { getGoogleConfig, updateGoogleConfig })(GoogleAuth); | ||||
| 
 | ||||
| export default Container; | ||||
| @ -1,10 +0,0 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import component from './authentication'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|     authenticationType: state.uiConfig.toJS().authenticationType, | ||||
| }); | ||||
| 
 | ||||
| const Container = connect(mapStateToProps, { })(component); | ||||
| 
 | ||||
| export default Container; | ||||
| @ -1,12 +0,0 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import OidcAuth from './oidc-auth'; | ||||
| import { getOidcConfig, updateOidcConfig } from '../../../store/e-admin-auth/actions'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|     config: state.authAdmin.get('oidc'), | ||||
|     unleashUrl: state.uiConfig.toJS().unleashUrl, | ||||
| }); | ||||
| 
 | ||||
| const OidcContainer = connect(mapStateToProps, { getOidcConfig, updateOidcConfig })(OidcAuth); | ||||
| 
 | ||||
| export default OidcContainer; | ||||
| @ -1,12 +0,0 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import SamlAuth from './saml-auth'; | ||||
| import { getSamlConfig, updateSamlConfig } from '../../../store/e-admin-auth/actions'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|     config: state.authAdmin.get('saml'), | ||||
|     unleashUrl: state.uiConfig.toJS().unleashUrl, | ||||
| }); | ||||
| 
 | ||||
| const Container = connect(mapStateToProps, { getSamlConfig, updateSamlConfig })(SamlAuth); | ||||
| 
 | ||||
| export default Container; | ||||
| @ -1,6 +1,6 @@ | ||||
| import React from 'react'; | ||||
| import { NavLink } from 'react-router-dom'; | ||||
| import { Paper, Tabs, Tab } from '@material-ui/core'; | ||||
| import { NavLink, useLocation } from 'react-router-dom'; | ||||
| import { Paper, Tab, Tabs } from '@material-ui/core'; | ||||
| import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| 
 | ||||
| const navLinkStyle = { | ||||
| @ -13,18 +13,17 @@ const navLinkStyle = { | ||||
|     padding: '0.8rem 1.5rem', | ||||
| }; | ||||
| 
 | ||||
| const activeNavLinkStyle = { | ||||
| const activeNavLinkStyle: React.CSSProperties = { | ||||
|     fontWeight: 'bold', | ||||
|     borderRadius: '3px', | ||||
|     padding: '0.8rem 1.5rem', | ||||
| }; | ||||
| 
 | ||||
| function AdminMenu({ history }) { | ||||
| function AdminMenu() { | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const { pathname } = useLocation(); | ||||
|     const { flags } = uiConfig; | ||||
| 
 | ||||
|     const { location } = history; | ||||
|     const { pathname } = location; | ||||
|     return ( | ||||
|         <Paper | ||||
|             style={{ | ||||
| @ -45,7 +44,7 @@ function AdminMenu({ history }) { | ||||
|                             <span>Users</span> | ||||
|                         </NavLink> | ||||
|                     } | ||||
|                 ></Tab> | ||||
|                 /> | ||||
|                 {flags.RE && ( | ||||
|                     <Tab | ||||
|                         value="/admin/roles" | ||||
| @ -58,7 +57,7 @@ function AdminMenu({ history }) { | ||||
|                                 <span>PROJECT ROLES</span> | ||||
|                             </NavLink> | ||||
|                         } | ||||
|                     ></Tab> | ||||
|                     /> | ||||
|                 )} | ||||
| 
 | ||||
|                 <Tab | ||||
| @ -72,7 +71,7 @@ function AdminMenu({ history }) { | ||||
|                             API Access | ||||
|                         </NavLink> | ||||
|                     } | ||||
|                 ></Tab> | ||||
|                 /> | ||||
|                 <Tab | ||||
|                     value="/admin/auth" | ||||
|                     label={ | ||||
| @ -84,7 +83,7 @@ function AdminMenu({ history }) { | ||||
|                             Single Sign-On | ||||
|                         </NavLink> | ||||
|                     } | ||||
|                 ></Tab> | ||||
|                 /> | ||||
|             </Tabs> | ||||
|         </Paper> | ||||
|     ); | ||||
|  | ||||
| @ -61,6 +61,7 @@ const TabNav = ({ tabData, className, navClass, startingTab = 0 }) => { | ||||
| 
 | ||||
| TabNav.propTypes = { | ||||
|     tabData: PropTypes.array.isRequired, | ||||
|     navClass: PropTypes.string, | ||||
|     className: PropTypes.string, | ||||
|     startingTab: PropTypes.number, | ||||
| }; | ||||
|  | ||||
| @ -398,12 +398,7 @@ Array [ | ||||
|     "type": "protected", | ||||
|   }, | ||||
|   Object { | ||||
|     "component": Object { | ||||
|       "$$typeof": Symbol(react.memo), | ||||
|       "WrappedComponent": [Function], | ||||
|       "compare": null, | ||||
|       "type": [Function], | ||||
|     }, | ||||
|     "component": [Function], | ||||
|     "layout": "main", | ||||
|     "menu": Object { | ||||
|       "adminSettings": true, | ||||
|  | ||||
| @ -16,7 +16,7 @@ import Admin from '../admin'; | ||||
| import AdminApi from '../admin/api'; | ||||
| import AdminInvoice from '../admin/invoice/InvoiceAdminPage'; | ||||
| import AdminUsers from '../admin/users/UsersAdmin'; | ||||
| import AdminAuth from '../admin/auth'; | ||||
| import { AuthSettings } from '../admin/auth/AuthSettings'; | ||||
| import Login from '../user/Login/Login'; | ||||
| import { P, C, E, EEA, RE } from '../common/flags'; | ||||
| import NewUser from '../user/NewUser'; | ||||
| @ -446,7 +446,7 @@ export const routes = [ | ||||
|         path: '/admin/auth', | ||||
|         parent: '/admin', | ||||
|         title: 'Single Sign-On', | ||||
|         component: AdminAuth, | ||||
|         component: AuthSettings, | ||||
|         type: 'protected', | ||||
|         layout: 'main', | ||||
|         menu: { adminSettings: true }, | ||||
|  | ||||
| @ -46,7 +46,7 @@ const useAPI = ({ | ||||
|     handleUnauthorized, | ||||
|     propagateErrors = false, | ||||
| }: IUseAPI) => { | ||||
|     const [errors, setErrors] = useState({}); | ||||
|     const [errors, setErrors] = useState<Record<string, string>>({}); | ||||
|     const [loading, setLoading] = useState(false); | ||||
| 
 | ||||
|     const defaultOptions: RequestInit = { | ||||
|  | ||||
| @ -12,7 +12,6 @@ export const handleBadRequest = async ( | ||||
|     if (!setErrors) return; | ||||
|     if (res) { | ||||
|         const data = await res.json(); | ||||
| 
 | ||||
|         setErrors({message: data.message}); | ||||
|         throw new Error(data.message); | ||||
|     } | ||||
|  | ||||
| @ -5,7 +5,7 @@ export const defaultValue = { | ||||
|     version: '3.x', | ||||
|     environment: '', | ||||
|     slogan: 'The enterprise ready feature toggle service.', | ||||
|     flags: { P: false, C: false, E: false }, | ||||
|     flags: { P: false, C: false, E: false, RE: false }, | ||||
|     links: [ | ||||
|         { | ||||
|             value: 'Documentation', | ||||
|  | ||||
| @ -15,6 +15,7 @@ export interface IFlags { | ||||
|     C: boolean; | ||||
|     P: boolean; | ||||
|     E: boolean; | ||||
|     RE: boolean; | ||||
| } | ||||
| 
 | ||||
| export interface IVersionInfo { | ||||
|  | ||||
| @ -1,91 +0,0 @@ | ||||
| import api from './api'; | ||||
| import { dispatchError } from '../util'; | ||||
| export const RECIEVE_GOOGLE_CONFIG = 'RECIEVE_GOOGLE_CONFIG'; | ||||
| export const RECIEVE_GOOGLE_CONFIG_ERROR = 'RECIEVE_GOOGLE_CONFIG_ERROR'; | ||||
| export const UPDATE_GOOGLE_AUTH = 'UPDATE_GOOGLE_AUTH'; | ||||
| export const UPDATE_GOOGLE_AUTH_ERROR = 'UPDATE_GOOGLE_AUTH_ERROR'; | ||||
| export const RECIEVE_SAML_CONFIG = 'RECIEVE_SAML_CONFIG'; | ||||
| export const RECIEVE_SAML_CONFIG_ERROR = 'RECIEVE_SAML_CONFIG_ERROR'; | ||||
| export const UPDATE_SAML_AUTH = 'UPDATE_SAML_AUTH'; | ||||
| export const UPDATE_SAML_AUTH_ERROR = 'UPDATE_SAML_AUTH_ERROR'; | ||||
| export const RECIEVE_OIDC_CONFIG = 'RECIEVE_OIDC_CONFIG'; | ||||
| export const RECIEVE_OIDC_CONFIG_ERROR = 'RECIEVE_OIDC_CONFIG_ERROR'; | ||||
| export const UPDATE_OIDC_AUTH = 'UPDATE_OIDC_AUTH'; | ||||
| export const UPDATE_OIDC_AUTH_ERROR = 'UPDATE_OIDC_AUTH_ERROR'; | ||||
| 
 | ||||
| const debug = require('debug')('unleash:e-admin-auth-actions'); | ||||
| 
 | ||||
| export function getGoogleConfig() { | ||||
|     debug('Start fetching google-auth config'); | ||||
|     return dispatch => | ||||
|         api | ||||
|             .getGoogleConfig() | ||||
|             .then(config => | ||||
|                 dispatch({ | ||||
|                     type: RECIEVE_GOOGLE_CONFIG, | ||||
|                     config, | ||||
|                 }) | ||||
|             ) | ||||
|             .catch(dispatchError(dispatch, RECIEVE_GOOGLE_CONFIG_ERROR)); | ||||
| } | ||||
| 
 | ||||
| export function updateGoogleConfig(data) { | ||||
|     return dispatch => | ||||
|         api | ||||
|             .updateGoogleConfig(data) | ||||
|             .then(config => dispatch({ type: UPDATE_GOOGLE_AUTH, config })) | ||||
|             .catch(e => { | ||||
|                 dispatchError(dispatch, UPDATE_GOOGLE_AUTH_ERROR)(e); | ||||
|                 throw e; | ||||
|             }); | ||||
| } | ||||
| 
 | ||||
| export function getSamlConfig() { | ||||
|     debug('Start fetching Saml-auth config'); | ||||
|     return dispatch => | ||||
|         api | ||||
|             .getSamlConfig() | ||||
|             .then(config => | ||||
|                 dispatch({ | ||||
|                     type: RECIEVE_SAML_CONFIG, | ||||
|                     config, | ||||
|                 }) | ||||
|             ) | ||||
|             .catch(dispatchError(dispatch, RECIEVE_SAML_CONFIG_ERROR)); | ||||
| } | ||||
| 
 | ||||
| export function updateSamlConfig(data) { | ||||
|     return dispatch => | ||||
|         api | ||||
|             .updateSamlConfig(data) | ||||
|             .then(config => dispatch({ type: UPDATE_SAML_AUTH, config })) | ||||
|             .catch(e => { | ||||
|                 dispatchError(dispatch, UPDATE_SAML_AUTH_ERROR)(e); | ||||
|                 throw e; | ||||
|             }); | ||||
| } | ||||
| 
 | ||||
| export function getOidcConfig() { | ||||
|     debug('Start fetching OIDC-auth config'); | ||||
|     return dispatch => | ||||
|         api | ||||
|             .getOidcConfig() | ||||
|             .then(config => | ||||
|                 dispatch({ | ||||
|                     type: RECIEVE_OIDC_CONFIG, | ||||
|                     config, | ||||
|                 }) | ||||
|             ) | ||||
|             .catch(dispatchError(dispatch, RECIEVE_OIDC_CONFIG_ERROR)); | ||||
| } | ||||
| 
 | ||||
| export function updateOidcConfig(data) { | ||||
|     return dispatch => | ||||
|         api | ||||
|             .updateOidcConfig(data) | ||||
|             .then(config => dispatch({ type: UPDATE_OIDC_AUTH, config })) | ||||
|             .catch(e => { | ||||
|                 dispatchError(dispatch, UPDATE_OIDC_AUTH_ERROR)(e); | ||||
|                 throw e; | ||||
|             }); | ||||
| } | ||||
| @ -1,66 +0,0 @@ | ||||
| import { throwIfNotSuccess, headers } from '../api-helper'; | ||||
| import { formatApiPath } from '../../utils/format-path'; | ||||
| 
 | ||||
| const GOOGLE_URI = formatApiPath('api/admin/auth/google/settings'); | ||||
| const SAML_URI = formatApiPath('api/admin/auth/saml/settings'); | ||||
| const OIDC_URI = formatApiPath('api/admin/auth/oidc/settings'); | ||||
| 
 | ||||
| function getGoogleConfig() { | ||||
|     return fetch(GOOGLE_URI, { headers, credentials: 'include' }) | ||||
|         .then(throwIfNotSuccess) | ||||
|         .then(response => response.json()); | ||||
| } | ||||
| 
 | ||||
| function updateGoogleConfig(data) { | ||||
|     return fetch(GOOGLE_URI, { | ||||
|         method: 'POST', | ||||
|         headers, | ||||
|         body: JSON.stringify(data), | ||||
|         credentials: 'include', | ||||
|     }) | ||||
|         .then(throwIfNotSuccess) | ||||
|         .then(response => response.json()); | ||||
| } | ||||
| 
 | ||||
| function getSamlConfig() { | ||||
|     return fetch(SAML_URI, { headers, credentials: 'include' }) | ||||
|         .then(throwIfNotSuccess) | ||||
|         .then(response => response.json()); | ||||
| } | ||||
| 
 | ||||
| function updateSamlConfig(data) { | ||||
|     return fetch(SAML_URI, { | ||||
|         method: 'POST', | ||||
|         headers, | ||||
|         body: JSON.stringify(data), | ||||
|         credentials: 'include', | ||||
|     }) | ||||
|         .then(throwIfNotSuccess) | ||||
|         .then(response => response.json()); | ||||
| } | ||||
| 
 | ||||
| function getOidcConfig() { | ||||
|     return fetch(OIDC_URI, { headers, credentials: 'include' }) | ||||
|         .then(throwIfNotSuccess) | ||||
|         .then(response => response.json()); | ||||
| } | ||||
| 
 | ||||
| function updateOidcConfig(data) { | ||||
|     return fetch(OIDC_URI, { | ||||
|         method: 'POST', | ||||
|         headers, | ||||
|         body: JSON.stringify(data), | ||||
|         credentials: 'include', | ||||
|     }) | ||||
|         .then(throwIfNotSuccess) | ||||
|         .then(response => response.json()); | ||||
| } | ||||
| 
 | ||||
| export default { | ||||
|     getGoogleConfig, | ||||
|     updateGoogleConfig, | ||||
|     getSamlConfig, | ||||
|     updateSamlConfig, | ||||
|     getOidcConfig, | ||||
|     updateOidcConfig, | ||||
| }; | ||||
| @ -1,20 +0,0 @@ | ||||
| import { Map as $Map } from 'immutable'; | ||||
| import { RECIEVE_GOOGLE_CONFIG, UPDATE_GOOGLE_AUTH, RECIEVE_SAML_CONFIG, UPDATE_SAML_AUTH, UPDATE_OIDC_AUTH, RECIEVE_OIDC_CONFIG } from './actions'; | ||||
| 
 | ||||
| const store = (state = new $Map({ google: {}, saml: {}, oidc: {} }), action) => { | ||||
|     switch (action.type) { | ||||
|         case UPDATE_GOOGLE_AUTH: | ||||
|         case RECIEVE_GOOGLE_CONFIG: | ||||
|             return state.set('google', action.config); | ||||
|         case UPDATE_SAML_AUTH: | ||||
|         case RECIEVE_SAML_CONFIG: | ||||
|             return state.set('saml', action.config); | ||||
|         case UPDATE_OIDC_AUTH: | ||||
|         case RECIEVE_OIDC_CONFIG: | ||||
|             return state.set('oidc', action.config); | ||||
|         default: | ||||
|             return state; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| export default store; | ||||
| @ -15,7 +15,6 @@ import uiConfig from './ui-config'; | ||||
| import context from './context'; | ||||
| import projects from './project'; | ||||
| import addons from './addons'; | ||||
| import authAdmin from './e-admin-auth'; | ||||
| import apiCalls from './api-calls'; | ||||
| import invoiceAdmin from './e-admin-invoice'; | ||||
| import feedback from './feedback'; | ||||
| @ -37,7 +36,6 @@ const unleashStore = combineReducers({ | ||||
|     context, | ||||
|     projects, | ||||
|     addons, | ||||
|     authAdmin, | ||||
|     apiCalls, | ||||
|     invoiceAdmin, | ||||
|     feedback, | ||||
|  | ||||
							
								
								
									
										8
									
								
								frontend/src/utils/format-unknown-error.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/src/utils/format-unknown-error.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| import { formatUnknownError } from './format-unknown-error'; | ||||
| 
 | ||||
| test('formatUnknownError', () => { | ||||
|     expect(formatUnknownError(1)).toEqual('Unknown error'); | ||||
|     expect(formatUnknownError('1')).toEqual('1'); | ||||
|     expect(formatUnknownError(new Error('1'))).toEqual('1'); | ||||
|     expect(formatUnknownError(new Error())).toEqual('Error'); | ||||
| }); | ||||
							
								
								
									
										10
									
								
								frontend/src/utils/format-unknown-error.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								frontend/src/utils/format-unknown-error.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| // Get a human-readable error message string from a caught value.
 | ||||
| export const formatUnknownError = (error: unknown): string => { | ||||
|     if (error instanceof Error) { | ||||
|         return error.message || error.toString(); | ||||
|     } else if (typeof error === 'string') { | ||||
|         return error; | ||||
|     } else { | ||||
|         return 'Unknown error'; | ||||
|     } | ||||
| }; | ||||
							
								
								
									
										11
									
								
								frontend/src/utils/remove-empty-string-fields.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								frontend/src/utils/remove-empty-string-fields.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| import { removeEmptyStringFields } from './remove-empty-string-fields'; | ||||
| 
 | ||||
| test('removeEmptyStringFields', () => { | ||||
|     expect(removeEmptyStringFields({})).toEqual({}); | ||||
|     expect(removeEmptyStringFields({ a: undefined })).toEqual({ a: undefined }); | ||||
|     expect(removeEmptyStringFields({ a: 0 })).toEqual({ a: 0 }); | ||||
|     expect(removeEmptyStringFields({ a: 1 })).toEqual({ a: 1 }); | ||||
|     expect(removeEmptyStringFields({ a: '1' })).toEqual({ a: '1' }); | ||||
|     expect(removeEmptyStringFields({ a: '' })).toEqual({}); | ||||
|     expect(removeEmptyStringFields({ a: '', b: '2' })).toEqual({ b: '2' }); | ||||
| }); | ||||
							
								
								
									
										9
									
								
								frontend/src/utils/remove-empty-string-fields.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								frontend/src/utils/remove-empty-string-fields.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| // Remove fields from an object if their value is the empty string.
 | ||||
| export const removeEmptyStringFields = (object: { | ||||
|     [key: string]: unknown; | ||||
| }): { [key: string]: unknown } => { | ||||
|     const entries = Object.entries(object); | ||||
|     const filtered = entries.filter(([, v]) => v !== ''); | ||||
| 
 | ||||
|     return Object.fromEntries(filtered); | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user