1 import type { ReactNode } from 'react';
2 import { useState } from 'react';
4 import type { Action, ThunkDispatch } from '@reduxjs/toolkit';
5 import { c } from 'ttag';
7 import type { SamlState } from '@proton/account';
8 import { disableSCIMAction, setupSCIMAction } from '@proton/account/samlSSO/actions';
9 import { Button } from '@proton/atoms';
10 import Icon from '@proton/components/components/icon/Icon';
11 import Info from '@proton/components/components/link/Info';
12 import useModalState from '@proton/components/components/modalTwo/useModalState';
13 import useLoading from '@proton/hooks/useLoading';
14 import { baseUseDispatch } from '@proton/react-redux-store';
15 import type { ProtonThunkArguments } from '@proton/redux-shared-store-types';
16 import { VPN_APP_NAME } from '@proton/shared/lib/constants';
17 import type { Domain } from '@proton/shared/lib/interfaces';
18 import { DOMAIN_STATE } from '@proton/shared/lib/interfaces';
20 import getBoldFormattedText from '../../../../helpers/getBoldFormattedText';
21 import { useApi, useNotifications } from '../../../../hooks';
28 } from '../../../account';
29 import SubSettingsSection from '../../../layout/SubSettingsSection';
30 import ReadonlyFieldWithCopy from '../ReadonlyFieldWithCopy';
31 import DisableSCIMModal from './DisableSCIMModal';
32 import RegenerateSCIMConfirmModal from './RegenerateSCIMConfirmModal';
33 import type { SCIMConfiguration } from './SetupSCIMModal';
34 import SetupSCIMModal from './SetupSCIMModal';
38 onConfigureSaml: () => void;
39 onShowVerifyDomain: () => void;
40 hasSsoConfig: boolean;
41 scimInfo: NonNullable<SamlState['sso']['value']>['scimInfo'];
44 const PreReq = ({ data, action }: { data: ReactNode; action: ReactNode }) => {
46 <div className="rounded border bg-weak p-4 flex justify-space-between gap-2 items-center lg:flex-nowrap">
47 <div className="flex gap-2 items-start flex-nowrap">
48 <Icon name="info-circle" className="shrink-0" />
49 <p className="m-0">{data}</p>
56 const SCIMSettingsSection = ({ domain, onConfigureSaml, onShowVerifyDomain, hasSsoConfig, scimInfo }: Props) => {
58 const dispatch = baseUseDispatch<ThunkDispatch<SamlState, ProtonThunkArguments, Action>>();
59 const { createNotification } = useNotifications();
61 const [setupSCIMModalProps, setSetupSCIMModalOpen, renderSetupSCIMModal] = useModalState();
62 const [regenerateSCIMModalProps, setRegenerateSCIMModalOpen, renderRegenerateSCIMModal] = useModalState();
63 const [disableSCIMModalProps, setDisableSCIMModalOpen, renderDisableSCIMModal] = useModalState();
64 const [localSCIMConfiguration, setLocalSCIMConfiguration] = useState<SCIMConfiguration>();
65 const [loadingSCIM, withLoadingSCIM] = useLoading();
70 id="scim-automatic-provisioning"
71 title={c('scim: Title').t`SCIM automatic provisioning`}
72 className="container-section-sticky-section"
76 <SettingsParagraph learnMoreUrl="">
78 .t`Simplifies user management across different services. Add, edit, and remove users in your identity provider, and those changes will automatically be applied to your ${VPN_APP_NAME} organization.`}
85 data={getBoldFormattedText(
87 .t`**Prerequisite:** SAML authentication must be configured to enable SCIM provisioning.`
95 onClick={onConfigureSaml}
97 {c('Action').t`Configure SAML`}
104 if (!scimInfo.state) {
105 const domainName = domain?.DomainName || '';
106 const isVerified = domain?.State !== DOMAIN_STATE.DOMAIN_STATE_DEFAULT;
112 data={getBoldFormattedText(
114 .t`**Prerequisite:** to enable SCIM provisioning, you must verify ownership of the domain **${domainName}**. This verification can take up to one hour.`
122 onClick={onShowVerifyDomain}
124 {c('Action').t`Verify domain`}
132 className="shrink-0 grow-0"
134 const run = async () => {
135 const result = await dispatch(
141 setLocalSCIMConfiguration({ ...result, type: 'setup' });
142 setSetupSCIMModalOpen(true);
143 createNotification({ text: c('Info').t`SCIM token active` });
145 void withLoadingSCIM(run());
147 loading={loadingSCIM}
149 {c('scim: Action').t`Configure SCIM`}
161 <label htmlFor="scimURL" className="text-semibold flex items-center gap-2">
162 <span>{c('scim: Label').t`SCIM base URL`}</span>
164 title={c('scim: Tooltip')
165 .t`URL of the SCIM connector to which your IdP Provisioning Agent forwards SCIM data`}
168 </SettingsLayoutLeft>
169 <SettingsLayoutRight className="w-full">
170 <ReadonlyFieldWithCopy id="scimURL" value={scimInfo.baseUrl} />
171 </SettingsLayoutRight>
175 <label htmlFor="scimToken" className="text-semibold flex items-center gap-2">
176 <span>{c('scim: Label').t`SCIM token`}</span>
178 title={c('scim: Tooltip')
179 .t`API key of the SCIM connector to which your IdP Provisioning Agent forwards SCIM data`}
182 </SettingsLayoutLeft>
183 <SettingsLayoutRight className="w-full">
184 <div className="flex items-center gap-2 mb-4 mt-2">
185 <Icon name="eye-slash" className="shrink-0" />
186 <p className="m-0">{c('scim: Info')
187 .t`For security reasons, the token is hidden`}</p>
190 <div className="border rounded p-4 flex items-start lg:flex-nowrap gap-2">
192 .t`If you've lost or forgotten the SCIM token, you can generate a new one, but be aware that your identity provider settings will need to be updated.`}
197 className="shrink-0 grow-0"
198 onClick={() => setRegenerateSCIMModalOpen(true)}
199 loading={loadingSCIM}
201 <Icon name="arrow-rotate-right" className="shrink-0 mr-1" />
202 {c('scim: Action').t`Generate new token`}
205 </SettingsLayoutRight>
208 <Button color="danger" shape="outline" onClick={() => setDisableSCIMModalOpen(true)}>
209 {c('scim: Action').t`Disable SCIM integration`}
214 </SettingsSectionWide>
215 </SubSettingsSection>
217 {renderSetupSCIMModal && localSCIMConfiguration && (
218 <SetupSCIMModal {...setupSCIMModalProps} {...localSCIMConfiguration} />
220 {renderRegenerateSCIMModal && (
221 <RegenerateSCIMConfirmModal
222 {...regenerateSCIMModalProps}
223 onConfirm={async () => {
224 const result = await dispatch(
230 setLocalSCIMConfiguration({ ...result, type: 'generated' });
231 setSetupSCIMModalOpen(true);
232 regenerateSCIMModalProps.onClose();
233 createNotification({ text: c('Info').t`SCIM token active` });
237 {renderDisableSCIMModal && (
239 {...disableSCIMModalProps}
240 onConfirm={async () => {
246 setLocalSCIMConfiguration(undefined);
247 disableSCIMModalProps.onClose();
248 createNotification({ text: c('Info').t`SCIM integration disabled` });
256 export default SCIMSettingsSection;