Skip to content

Commit 3fe1741

Browse files
committed
Refactor identity provider form to declarative approach
1 parent 5fad0dd commit 3fe1741

8 files changed

Lines changed: 644 additions & 526 deletions

File tree

packages/localizations/src/en-US.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -535,13 +535,16 @@ export const enUS: LocalizationResource = {
535535
modes: {
536536
title: 'Fill in your Google Workspace application details',
537537
ariaLabel: 'Configuration ',
538-
metadataUpload: 'Add via metadata',
538+
metadataFile: 'Add via metadata',
539539
manual: 'Configure manually',
540540
},
541-
metadataUpload: {
541+
metadataFile: {
542542
label: 'IdP metadata',
543-
uploadFile: 'Upload file',
544543
description: 'In your Google Workspace app, download the IdP metadata and upload it below.',
544+
uploadFile: 'Upload file',
545+
replaceFile: 'Replace file',
546+
removeFile: 'Remove file',
547+
fileUploaded: 'File uploaded',
545548
},
546549
manual: {
547550
description: 'In your Google Workspace app, retrieve these values.',

packages/shared/src/types/localization.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,13 +1589,16 @@ export type __internal_LocalizationResource = {
15891589
modes: {
15901590
title: LocalizationValue;
15911591
ariaLabel: LocalizationValue;
1592-
metadataUpload: LocalizationValue;
1592+
metadataFile: LocalizationValue;
15931593
manual: LocalizationValue;
15941594
};
1595-
metadataUpload: {
1595+
metadataFile: {
15961596
label: LocalizationValue;
1597-
uploadFile: LocalizationValue;
15981597
description: LocalizationValue;
1598+
uploadFile: LocalizationValue;
1599+
replaceFile: LocalizationValue;
1600+
removeFile: LocalizationValue;
1601+
fileUploaded: LocalizationValue;
15991602
};
16001603
manual: {
16011604
description: LocalizationValue;

packages/ui/src/components/ConfigureSSO/steps/ConfigureStep/saml/SamlCustomConfigureSteps.tsx

Lines changed: 113 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type JSX } from 'react';
1+
import React, { type JSX } from 'react';
22

33
import { Col, descriptors, Heading, localizationKeys, Text } from '@/customizables';
44
import { ClipboardInput } from '@/elements/ClipboardInput';
@@ -12,8 +12,15 @@ import { Step } from '../../../elements/Step';
1212
import { useWizard, Wizard } from '../../../elements/Wizard';
1313
import { InnerStepCounter } from '../../../elements/Wizard/InnerStepCounter';
1414
import { AttributeMappingTable, type AttributeMappingTableConfig } from './shared/AttributeMappingTable';
15-
import { IdentityProviderMetadataForm } from './shared/IdentityProviderMetadataForm';
16-
import { useIdentityProviderMetadataForm } from './shared/useIdentityProviderMetadataForm';
15+
import {
16+
applySamlSubmitError,
17+
buildSamlConfigurationPayload,
18+
IdentityProviderConfigurationForm,
19+
} from './shared/IdentityProviderConfigurationForm';
20+
import {
21+
IdentityProviderConfigurationModes,
22+
type IdpConfigurationMode,
23+
} from './shared/IdentityProviderConfigurationModes';
1724

1825
export const SamlCustomConfigureSteps = (): JSX.Element => {
1926
return (
@@ -252,42 +259,73 @@ const SamlCustomAssignUsersStep = (): JSX.Element => {
252259
);
253260
};
254261

262+
const CUSTOM_SAML_IDP_MODES = ['metadataUrl', 'manual'] as const satisfies readonly IdpConfigurationMode[];
263+
255264
const SamlCustomIdentityProviderMetadataStep = (): JSX.Element => {
256265
const card = useCardState();
257266
const { goNext, goPrev, isFirstStep } = useWizard();
258267
const { enterpriseConnection, updateEnterpriseConnection } = useConfigureSSO();
259268

260-
const controller = useIdentityProviderMetadataForm({
261-
metadataUrl: {
262-
label: localizationKeys('configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.label'),
263-
placeholder: localizationKeys(
264-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.placeholder',
265-
),
266-
},
267-
manual: {
268-
signOnUrl: {
269-
label: localizationKeys(
270-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signOnUrl.label',
271-
),
272-
placeholder: localizationKeys(
273-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signOnUrl.placeholder',
274-
),
275-
},
276-
issuer: {
277-
label: localizationKeys(
278-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.issuer.label',
279-
),
280-
placeholder: localizationKeys(
281-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.issuer.placeholder',
282-
),
283-
},
284-
signingCertificateLabel: localizationKeys(
285-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.label',
286-
),
287-
},
269+
const samlConnection = enterpriseConnection?.samlConnection;
270+
const hasExistingConfig = Boolean(
271+
samlConnection?.idpSsoUrl ||
272+
samlConnection?.idpEntityId ||
273+
samlConnection?.idpCertificate ||
274+
samlConnection?.idpMetadataUrl,
275+
);
276+
const existingCertPresent = Boolean(samlConnection?.idpCertificate);
277+
278+
const [mode, setMode] = React.useState<IdpConfigurationMode>(hasExistingConfig ? 'manual' : 'metadataUrl');
279+
const [certFile, setCertFile] = React.useState<File | null>(null);
280+
281+
const metadataUrlField = useFormControl('idpMetadataUrl', samlConnection?.idpMetadataUrl ?? '', {
282+
type: 'text',
283+
label: localizationKeys('configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.label'),
284+
placeholder: localizationKeys(
285+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.placeholder',
286+
),
287+
isRequired: true,
288+
});
289+
290+
const signOnUrlField = useFormControl('idpSsoUrl', samlConnection?.idpSsoUrl ?? '', {
291+
type: 'text',
292+
label: localizationKeys(
293+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signOnUrl.label',
294+
),
295+
placeholder: localizationKeys(
296+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signOnUrl.placeholder',
297+
),
298+
isRequired: true,
299+
});
300+
301+
const issuerField = useFormControl('idpEntityId', samlConnection?.idpEntityId ?? '', {
302+
type: 'text',
303+
label: localizationKeys('configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.issuer.label'),
304+
placeholder: localizationKeys(
305+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.issuer.placeholder',
306+
),
307+
isRequired: true,
288308
});
289309

290-
const canSubmit = !card.isLoading && controller.isValid;
310+
const certificateField = useFormControl('idpCertificate', '', {
311+
type: 'text',
312+
label: localizationKeys(
313+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.label',
314+
),
315+
isRequired: true,
316+
});
317+
318+
const trimmedMetadataUrl = metadataUrlField.value.trim();
319+
const trimmedSignOnUrl = signOnUrlField.value.trim();
320+
const trimmedIssuer = issuerField.value.trim();
321+
const hasCert = certFile !== null || existingCertPresent;
322+
323+
const isValid =
324+
mode === 'metadataUrl'
325+
? trimmedMetadataUrl.length > 0
326+
: trimmedSignOnUrl.length > 0 && trimmedIssuer.length > 0 && hasCert;
327+
328+
const canSubmit = isValid && !card.isLoading;
291329

292330
const handleContinue = async (): Promise<void> => {
293331
if (!enterpriseConnection || !canSubmit) {
@@ -298,11 +336,20 @@ const SamlCustomIdentityProviderMetadataStep = (): JSX.Element => {
298336
card.setLoading();
299337

300338
try {
301-
const saml = await controller.buildSamlPayload();
339+
const saml = await buildSamlConfigurationPayload({
340+
mode,
341+
metadataUrl: { value: metadataUrlField.value },
342+
manual: { signOnUrl: signOnUrlField.value, issuer: issuerField.value, certFile },
343+
});
344+
302345
await updateEnterpriseConnection(enterpriseConnection.id, { saml });
303346
void goNext();
304347
} catch (err) {
305-
controller.applySubmitError(err, card);
348+
if (mode === 'metadataUrl') {
349+
applySamlSubmitError(err, card, metadataUrlField);
350+
} else {
351+
applySamlSubmitError(err, card, signOnUrlField, [issuerField, certificateField]);
352+
}
306353
} finally {
307354
card.setIdle();
308355
}
@@ -323,20 +370,29 @@ const SamlCustomIdentityProviderMetadataStep = (): JSX.Element => {
323370
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.title',
324371
)}
325372
/>
326-
<IdentityProviderMetadataForm
327-
controller={controller}
328-
modes={{
373+
<IdentityProviderConfigurationModes
374+
modes={CUSTOM_SAML_IDP_MODES}
375+
value={mode}
376+
onChange={next => {
377+
card.setError(undefined);
378+
setMode(next);
379+
}}
380+
labels={{
329381
ariaLabel: localizationKeys(
330382
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.ariaLabel',
331383
),
332-
metadataUrlLabel: localizationKeys(
384+
metadataUrl: localizationKeys(
333385
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.metadataUrl',
334386
),
335-
manualLabel: localizationKeys(
387+
manual: localizationKeys(
336388
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.manual',
337389
),
338390
}}
391+
/>
392+
<IdentityProviderConfigurationForm
393+
mode={mode}
339394
metadataUrl={{
395+
field: metadataUrlField,
340396
description: localizationKeys(
341397
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.description',
342398
),
@@ -345,23 +401,24 @@ const SamlCustomIdentityProviderMetadataStep = (): JSX.Element => {
345401
description: localizationKeys(
346402
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.description',
347403
),
348-
signingCertificate: {
349-
label: localizationKeys(
350-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.label',
351-
),
352-
uploadFile: localizationKeys(
353-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.uploadFile',
354-
),
355-
replaceFile: localizationKeys(
356-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.replaceFile',
357-
),
358-
removeFile: localizationKeys(
359-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.removeFile',
360-
),
361-
fileUploaded: localizationKeys(
362-
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.fileUploaded',
363-
),
364-
},
404+
signOnUrlField,
405+
issuerField,
406+
certificateField,
407+
certFile,
408+
onCertFileChange: setCertFile,
409+
existingCertPresent,
410+
uploadFile: localizationKeys(
411+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.uploadFile',
412+
),
413+
replaceFile: localizationKeys(
414+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.replaceFile',
415+
),
416+
removeFile: localizationKeys(
417+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.removeFile',
418+
),
419+
fileUploaded: localizationKeys(
420+
'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.fileUploaded',
421+
),
365422
}}
366423
/>
367424
</Step.Section>

0 commit comments

Comments
 (0)