mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +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