-
-
Notifications
You must be signed in to change notification settings - Fork 730
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
325 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
144 changes: 144 additions & 0 deletions
144
frontend/src/component/admin/auth/ScimSettings/ScimSettings.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import type React from 'react'; | ||
import { useEffect, useState } from 'react'; | ||
import { Button, FormControlLabel, Grid, Switch } from '@mui/material'; | ||
import { Alert } from '@mui/material'; | ||
import useToast from 'hooks/useToast'; | ||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||
import { useScimSettings } from 'hooks/api/getters/useScimSettings/useScimSettings'; | ||
import { useScimSettingsApi } from 'hooks/api/actions/useScimSettingsApi/useScimSettingsApi'; | ||
import { formatUnknownError } from 'utils/formatUnknownError'; | ||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||
import { ScimTokenGenerationDialog } from './ScimTokenGenerationDialog'; | ||
import { ScimTokenDialog } from './ScimTokenDialog'; | ||
|
||
export const ScimSettings = () => { | ||
const { setToastData, setToastApiError } = useToast(); | ||
const { uiConfig } = useUiConfig(); | ||
const { settings, refetch } = useScimSettings(); | ||
const { saveSettings, generateNewToken, errors, loading } = | ||
useScimSettingsApi(); | ||
|
||
const [enabled, setEnabled] = useState(false); | ||
|
||
const [tokenGenerationDialog, setTokenGenerationDialog] = useState(false); | ||
const [tokenDialog, setTokenDialog] = useState(false); | ||
const [newToken, setNewToken] = useState(''); | ||
|
||
useEffect(() => { | ||
setEnabled(settings.enabled ?? false); | ||
}, [settings]); | ||
|
||
const onSubmit = async (event: React.SyntheticEvent) => { | ||
event.preventDefault(); | ||
|
||
try { | ||
await saveSettings({ enabled }); | ||
if (enabled && !settings.hasToken) { | ||
const token = await generateNewToken(); | ||
setNewToken(token); | ||
setTokenDialog(true); | ||
} | ||
|
||
setToastData({ | ||
title: 'Settings stored', | ||
type: 'success', | ||
}); | ||
refetch(); | ||
} catch (error: unknown) { | ||
setToastApiError(formatUnknownError(error)); | ||
} | ||
}; | ||
|
||
const onGenerateNewToken = async () => { | ||
setTokenGenerationDialog(true); | ||
}; | ||
|
||
const onGenerateNewTokenConfirm = async () => { | ||
setTokenGenerationDialog(false); | ||
const token = await generateNewToken(); | ||
setNewToken(token); | ||
setTokenDialog(true); | ||
}; | ||
|
||
return ( | ||
<> | ||
<Grid container sx={{ mb: 3 }}> | ||
<Grid item md={12}> | ||
<Alert severity='info'> | ||
Please read the{' '} | ||
<a | ||
href='https://docs.getunleash.io/reference/scim' | ||
target='_blank' | ||
rel='noreferrer' | ||
> | ||
documentation | ||
</a>{' '} | ||
to learn how to integrate with specific SCIM clients | ||
(Microsoft Entra, Okta, etc). <br /> | ||
SCIM API URL: <code>{uiConfig.unleashUrl}/scim</code> | ||
</Alert> | ||
</Grid> | ||
</Grid> | ||
<form onSubmit={onSubmit}> | ||
<Grid container spacing={3}> | ||
<Grid item md={5} mb={2}> | ||
<strong>Enable</strong> | ||
<p>Enable SCIM provisioning.</p> | ||
</Grid> | ||
<Grid item md={6}> | ||
<FormControlLabel | ||
control={ | ||
<Switch | ||
onChange={(_, enabled) => | ||
setEnabled(enabled) | ||
} | ||
value={enabled} | ||
name='enabled' | ||
checked={enabled} | ||
/> | ||
} | ||
label={enabled ? 'Enabled' : 'Disabled'} | ||
/> | ||
</Grid> | ||
</Grid> | ||
|
||
<Grid container spacing={3}> | ||
<Grid item md={5}> | ||
<Button | ||
variant='contained' | ||
color='primary' | ||
type='submit' | ||
disabled={loading} | ||
> | ||
Save | ||
</Button> | ||
<ConditionallyRender | ||
condition={Boolean(settings.hasToken)} | ||
show={ | ||
<Button | ||
variant='outlined' | ||
color='error' | ||
disabled={loading} | ||
onClick={onGenerateNewToken} | ||
sx={{ ml: 1 }} | ||
> | ||
Generate new token | ||
</Button> | ||
} | ||
/> | ||
</Grid> | ||
</Grid> | ||
</form> | ||
<ScimTokenGenerationDialog | ||
open={tokenGenerationDialog} | ||
setOpen={setTokenGenerationDialog} | ||
onConfirm={onGenerateNewTokenConfirm} | ||
/> | ||
<ScimTokenDialog | ||
open={tokenDialog} | ||
setOpen={setTokenDialog} | ||
token={newToken} | ||
/> | ||
</> | ||
); | ||
}; |
37 changes: 37 additions & 0 deletions
37
frontend/src/component/admin/auth/ScimSettings/ScimTokenDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Alert, styled, Typography } from '@mui/material'; | ||
import { UserToken } from 'component/admin/apiToken/ConfirmToken/UserToken/UserToken'; | ||
import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||
|
||
const StyledAlert = styled(Alert)(({ theme }) => ({ | ||
marginBottom: theme.spacing(3), | ||
})); | ||
|
||
interface IScimTokenDialogProps { | ||
open: boolean; | ||
setOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||
token?: string; | ||
} | ||
|
||
export const ScimTokenDialog = ({ | ||
open, | ||
setOpen, | ||
token, | ||
}: IScimTokenDialogProps) => ( | ||
<Dialogue | ||
open={open} | ||
secondaryButtonText='Close' | ||
onClose={(_, muiCloseReason?: string) => { | ||
if (!muiCloseReason) { | ||
setOpen(false); | ||
} | ||
}} | ||
title='SCIM API token created' | ||
> | ||
<StyledAlert severity='info'> | ||
Make sure to copy your SCIM API token now. You won't be able to see | ||
it again! | ||
</StyledAlert> | ||
<Typography variant='body1'>Your token:</Typography> | ||
<UserToken token={token || ''} /> | ||
</Dialogue> | ||
); |
37 changes: 37 additions & 0 deletions
37
frontend/src/component/admin/auth/ScimSettings/ScimTokenGenerationDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Alert, styled } from '@mui/material'; | ||
import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||
|
||
const StyledAlert = styled(Alert)(({ theme }) => ({ | ||
marginBottom: theme.spacing(3), | ||
})); | ||
|
||
interface IScimTokenGenerationDialogProps { | ||
open: boolean; | ||
setOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||
onConfirm: () => void; | ||
} | ||
|
||
export const ScimTokenGenerationDialog = ({ | ||
open, | ||
setOpen, | ||
onConfirm, | ||
}: IScimTokenGenerationDialogProps) => ( | ||
<Dialogue | ||
open={open} | ||
secondaryButtonText='Close' | ||
onClose={(_, muiCloseReason?: string) => { | ||
if (!muiCloseReason) { | ||
setOpen(false); | ||
} | ||
}} | ||
primaryButtonText='Generate new token' | ||
onClick={onConfirm} | ||
title='Generate new SCIM API token?' | ||
> | ||
<StyledAlert severity='error'> | ||
Generating a new token will <strong>immediately revoke</strong> the | ||
current one, which may break any existing provision integrations | ||
currently using it. | ||
</StyledAlert> | ||
</Dialogue> | ||
); |
48 changes: 48 additions & 0 deletions
48
frontend/src/hooks/api/actions/useScimSettingsApi/useScimSettingsApi.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import useAPI from '../useApi/useApi'; | ||
import type { ScimSettings } from 'hooks/api/getters/useScimSettings/useScimSettings'; | ||
|
||
const ENDPOINT = 'api/admin/scim-settings'; | ||
|
||
export type ScimSettingsPayload = Omit<ScimSettings, 'hasToken'>; | ||
|
||
export const useScimSettingsApi = () => { | ||
const { loading, makeRequest, createRequest, errors } = useAPI({ | ||
propagateErrors: true, | ||
}); | ||
|
||
const saveSettings = async (scimSettings: ScimSettingsPayload) => { | ||
const requestId = 'saveSettings'; | ||
const req = createRequest( | ||
ENDPOINT, | ||
{ | ||
method: 'POST', | ||
body: JSON.stringify(scimSettings), | ||
}, | ||
requestId, | ||
); | ||
|
||
await makeRequest(req.caller, req.id); | ||
}; | ||
|
||
const generateNewToken = async (): Promise<string> => { | ||
const requestId = 'generateNewToken'; | ||
const req = createRequest( | ||
`${ENDPOINT}/generate-new-token`, | ||
{ | ||
method: 'POST', | ||
}, | ||
requestId, | ||
); | ||
|
||
const response = await makeRequest(req.caller, req.id); | ||
const { token } = await response.json(); | ||
return token; | ||
}; | ||
|
||
return { | ||
saveSettings, | ||
generateNewToken, | ||
errors, | ||
loading, | ||
}; | ||
}; |
46 changes: 46 additions & 0 deletions
46
frontend/src/hooks/api/getters/useScimSettings/useScimSettings.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { useMemo } from 'react'; | ||
import { formatApiPath } from 'utils/formatPath'; | ||
import handleErrorResponses from '../httpErrorResponseHandler'; | ||
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR'; | ||
import useUiConfig from '../useUiConfig/useUiConfig'; | ||
import { useUiFlag } from 'hooks/useUiFlag'; | ||
|
||
const ENDPOINT = 'api/admin/scim-settings'; | ||
|
||
export type ScimSettings = { | ||
enabled: boolean; | ||
hasToken: boolean; | ||
}; | ||
|
||
const DEFAULT_DATA: ScimSettings = { | ||
enabled: false, | ||
hasToken: false, | ||
}; | ||
|
||
export const useScimSettings = () => { | ||
const { isEnterprise } = useUiConfig(); | ||
const scimEnabled = useUiFlag('scimApi'); | ||
|
||
const { data, error, mutate } = useConditionalSWR<ScimSettings>( | ||
isEnterprise() && scimEnabled, | ||
DEFAULT_DATA, | ||
formatApiPath(ENDPOINT), | ||
fetcher, | ||
); | ||
|
||
return useMemo( | ||
() => ({ | ||
settings: data ?? DEFAULT_DATA, | ||
loading: !error && !data, | ||
refetch: () => mutate(), | ||
error, | ||
}), | ||
[data, error, mutate], | ||
); | ||
}; | ||
|
||
const fetcher = (path: string) => { | ||
return fetch(path) | ||
.then(handleErrorResponses('SCIM settings')) | ||
.then((res) => res.json()); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters