diff --git a/frontend/src/component/admin/auth/PasswordAuthSettings.tsx b/frontend/src/component/admin/auth/PasswordAuthSettings.tsx new file mode 100644 index 0000000000..9eebaec6a1 --- /dev/null +++ b/frontend/src/component/admin/auth/PasswordAuthSettings.tsx @@ -0,0 +1,107 @@ +import React, { useState, useContext, useEffect } 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'; + +const PasswordAuthSettings = () => { + + const { setToastData } = useToast(); + const { config } = useAuthSettings('simple'); + const [disablePasswordAuth, setDisablePasswordAuth] = useState(false); + const { updateSettings, errors, loading } = useAuthSettingsApi('simple') + const { hasAccess } = useContext(AccessContext); + + + useEffect(() => { + setDisablePasswordAuth(!!config.disabled); + }, [ config.disabled ]); + + if (!hasAccess(ADMIN)) { + return ( + + You need to be a root admin to access this section. + + ); + } + + const updateDisabled = () => { + setDisablePasswordAuth(!disablePasswordAuth); + }; + + + const onSubmit = async evt => { + evt.preventDefault(); + + try { + const settings: ISimpleAuthSettings = { disabled: disablePasswordAuth }; + await updateSettings(settings); + setToastData({ + title: 'Successfully saved', + text: 'Password authentication settings stored.', + autoHideDuration: 4000, + 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) + } + + }; + return ( + +
+ + + Password based login +

Allow users to login with username & password

+
+ + + } + label={!disablePasswordAuth ? 'Enabled' : 'Disabled'} + /> + +
+ + + {' '} +

{errors?.message}

+
+
+
+
+ ); +} + +export default PasswordAuthSettings; diff --git a/frontend/src/component/admin/auth/authentication.jsx b/frontend/src/component/admin/auth/authentication.jsx index e2301abf93..e1233a2e73 100644 --- a/frontend/src/component/admin/auth/authentication.jsx +++ b/frontend/src/component/admin/auth/authentication.jsx @@ -5,6 +5,7 @@ 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'; @@ -19,6 +20,10 @@ function AdminAuthPage({ authenticationType, history }) { label: 'SAML 2.0', component: , }, + { + label: 'Password', + component: + }, { label: 'Google', component: , diff --git a/frontend/src/component/user/Authentication/Authentication.tsx b/frontend/src/component/user/Authentication/Authentication.tsx index 260f82a59f..68b1e68205 100644 --- a/frontend/src/component/user/Authentication/Authentication.tsx +++ b/frontend/src/component/user/Authentication/Authentication.tsx @@ -47,7 +47,7 @@ const Authentication = ({ history={history} /> } /> @@ -77,7 +77,7 @@ const Authentication = ({ history={history} /> } /> diff --git a/frontend/src/component/user/HostedAuth/HostedAuth.jsx b/frontend/src/component/user/HostedAuth/HostedAuth.jsx index b2e25ea70b..64893d72a5 100644 --- a/frontend/src/component/user/HostedAuth/HostedAuth.jsx +++ b/frontend/src/component/user/HostedAuth/HostedAuth.jsx @@ -84,7 +84,7 @@ const HostedAuth = ({ authDetails, passwordLogin }) => { /> { return ( { const renderWithOptions = options => ( <> - + } + /> {renderLoginForm()} ); diff --git a/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.jsx b/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.jsx index f11a105b6d..61119e4ea1 100644 --- a/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.jsx +++ b/frontend/src/component/user/UserProfile/UserProfileContent/UserProfileContent.jsx @@ -16,6 +16,7 @@ import { Alert } from '@material-ui/lab'; import EditProfile from '../EditProfile/EditProfile'; import legacyStyles from '../../user.module.scss'; import { getBasePath } from '../../../../utils/format-path'; +import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig'; const UserProfileContent = ({ showProfile, @@ -27,6 +28,7 @@ const UserProfileContent = ({ setCurrentLocale, }) => { const commonStyles = useCommonStyles(); + const { uiConfig } = useUiConfig(); const [updatedPassword, setUpdatedPassword] = useState(false); const [edititingProfile, setEditingProfile] = useState(false); const styles = useStyles(); @@ -81,12 +83,14 @@ const UserProfileContent = ({ condition={!edititingProfile} show={ <> - + setEditingProfile(true)} + > + Update password + + } />
>, + res?: Response, +) => { + if (!setErrors) return; + if (res) { + const data = await res.json(); + + setErrors({message: data.message}); + throw new Error(data.message); + } + + throw new Error(); +}; + +const useAuthSettingsApi = (id: string) => { + const { makeRequest, createRequest, errors, loading } = useAPI({ + propagateErrors: true, + handleBadRequest, + }); + + const updateSettings = async (settings: T): Promise => { + const path = `api/admin/auth/${id}/settings`; + const req = createRequest(path, { + method: 'POST', + body: JSON.stringify(settings), + }); + + try { + await makeRequest(req.caller, req.id); + } catch (e) { + throw e; + } + }; + + return { updateSettings, errors, loading }; +}; + +export default useAuthSettingsApi; diff --git a/frontend/src/hooks/api/getters/useAuthSettings/useAuthSettings.ts b/frontend/src/hooks/api/getters/useAuthSettings/useAuthSettings.ts new file mode 100644 index 0000000000..49adf03b4c --- /dev/null +++ b/frontend/src/hooks/api/getters/useAuthSettings/useAuthSettings.ts @@ -0,0 +1,36 @@ +import useSWR, { mutate, SWRConfiguration } from 'swr'; +import { useState, useEffect } from 'react'; +import { formatApiPath } from '../../../../utils/format-path'; +import handleErrorResponses from '../httpErrorResponseHandler'; + +const useAuthSettings = (id: string, options: SWRConfiguration = {}) => { + const fetcher = async () => { + const path = formatApiPath(`api/admin/auth/${id}/settings`); + const res = await fetch(path, { + method: 'GET', + }).then(handleErrorResponses('Auth settings')); + return res.json(); + }; + + const KEY = `api/admin/auth/${id}/settings`; + + const { data, error } = useSWR(KEY, fetcher, options); + const [loading, setLoading] = useState(!error && !data); + + const refetch = () => { + mutate(KEY); + }; + + useEffect(() => { + setLoading(!error && !data); + }, [data, error]); + + return { + config: data || {}, + error, + loading, + refetch, + }; +}; + +export default useAuthSettings;