1- import { type JSX } from 'react' ;
1+ import React , { type JSX } from 'react' ;
22
33import { Col , descriptors , Heading , localizationKeys , Text } from '@/customizables' ;
44import { ClipboardInput } from '@/elements/ClipboardInput' ;
@@ -12,8 +12,16 @@ import { Step } from '../../../elements/Step';
1212import { useWizard , Wizard } from '../../../elements/Wizard' ;
1313import { InnerStepCounter } from '../../../elements/Wizard/InnerStepCounter' ;
1414import { 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+ type IdentityProviderConfigurationFormProps ,
20+ } from './shared/IdentityProviderConfigurationForm' ;
21+ import {
22+ IdentityProviderConfigurationModes ,
23+ type IdpConfigurationMode ,
24+ } from './shared/IdentityProviderConfigurationModes' ;
1725
1826export const SamlCustomConfigureSteps = ( ) : JSX . Element => {
1927 return (
@@ -252,42 +260,113 @@ const SamlCustomAssignUsersStep = (): JSX.Element => {
252260 ) ;
253261} ;
254262
263+ const CUSTOM_SAML_IDP_MODES = [ 'metadataUrl' , 'manual' ] as const satisfies readonly IdpConfigurationMode [ ] ;
264+
255265const SamlCustomIdentityProviderMetadataStep = ( ) : JSX . Element => {
256266 const card = useCardState ( ) ;
257267 const { goNext, goPrev, isFirstStep } = useWizard ( ) ;
258268 const { enterpriseConnection, updateEnterpriseConnection } = useConfigureSSO ( ) ;
259269
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- } ,
270+ const samlConnection = enterpriseConnection ?. samlConnection ;
271+ const hasExistingConfig = Boolean (
272+ samlConnection ?. idpSsoUrl ||
273+ samlConnection ?. idpEntityId ||
274+ samlConnection ?. idpCertificate ||
275+ samlConnection ?. idpMetadataUrl ,
276+ ) ;
277+ const existingCertPresent = Boolean ( samlConnection ?. idpCertificate ) ;
278+
279+ const [ mode , setMode ] = React . useState < IdpConfigurationMode > ( hasExistingConfig ? 'manual' : 'metadataUrl' ) ;
280+ const [ certFile , setCertFile ] = React . useState < File | null > ( null ) ;
281+
282+ const metadataUrlField = useFormControl ( 'idpMetadataUrl' , samlConnection ?. idpMetadataUrl ?? '' , {
283+ type : 'text' ,
284+ label : localizationKeys ( 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.label' ) ,
285+ placeholder : localizationKeys (
286+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.placeholder' ,
287+ ) ,
288+ isRequired : true ,
289+ } ) ;
290+
291+ const signOnUrlField = useFormControl ( 'idpSsoUrl' , samlConnection ?. idpSsoUrl ?? '' , {
292+ type : 'text' ,
293+ label : localizationKeys (
294+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signOnUrl.label' ,
295+ ) ,
296+ placeholder : localizationKeys (
297+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signOnUrl.placeholder' ,
298+ ) ,
299+ isRequired : true ,
300+ } ) ;
301+
302+ const issuerField = useFormControl ( 'idpEntityId' , samlConnection ?. idpEntityId ?? '' , {
303+ type : 'text' ,
304+ label : localizationKeys ( 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.issuer.label' ) ,
305+ placeholder : localizationKeys (
306+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.issuer.placeholder' ,
307+ ) ,
308+ isRequired : true ,
288309 } ) ;
289310
290- const canSubmit = ! card . isLoading && controller . isValid ;
311+ const certificateField = useFormControl ( 'idpCertificate' , '' , {
312+ type : 'text' ,
313+ label : localizationKeys (
314+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.label' ,
315+ ) ,
316+ isRequired : true ,
317+ } ) ;
318+
319+ const trimmedMetadataUrl = metadataUrlField . value . trim ( ) ;
320+ const trimmedSignOnUrl = signOnUrlField . value . trim ( ) ;
321+ const trimmedIssuer = issuerField . value . trim ( ) ;
322+ const hasCert = certFile !== null || existingCertPresent ;
323+
324+ const isValid =
325+ mode === 'metadataUrl'
326+ ? trimmedMetadataUrl . length > 0
327+ : trimmedSignOnUrl . length > 0 && trimmedIssuer . length > 0 && hasCert ;
328+
329+ const canSubmit = isValid && ! card . isLoading ;
330+
331+ const formProps : IdentityProviderConfigurationFormProps =
332+ mode === 'metadataUrl'
333+ ? {
334+ mode : 'metadataUrl' ,
335+ form : { field : metadataUrlField } ,
336+ labels : {
337+ description : localizationKeys (
338+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.description' ,
339+ ) ,
340+ } ,
341+ }
342+ : {
343+ mode : 'manual' ,
344+ form : {
345+ signOnUrlField,
346+ issuerField,
347+ certificateField,
348+ certFile,
349+ onCertFileChange : setCertFile ,
350+ existingCertPresent,
351+ } ,
352+ labels : {
353+ description : localizationKeys (
354+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.description' ,
355+ ) ,
356+ uploadFile : localizationKeys (
357+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.uploadFile' ,
358+ ) ,
359+ replaceFile : localizationKeys (
360+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.replaceFile' ,
361+ ) ,
362+ removeFile : localizationKeys (
363+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.removeFile' ,
364+ ) ,
365+ fileUploaded : localizationKeys (
366+ 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.signingCertificate.fileUploaded' ,
367+ ) ,
368+ } ,
369+ } ;
291370
292371 const handleContinue = async ( ) : Promise < void > => {
293372 if ( ! enterpriseConnection || ! canSubmit ) {
@@ -298,11 +377,20 @@ const SamlCustomIdentityProviderMetadataStep = (): JSX.Element => {
298377 card . setLoading ( ) ;
299378
300379 try {
301- const saml = await controller . buildSamlPayload ( ) ;
380+ const saml = await buildSamlConfigurationPayload ( {
381+ mode,
382+ metadataUrl : { value : metadataUrlField . value } ,
383+ manual : { signOnUrl : signOnUrlField . value , issuer : issuerField . value , certFile } ,
384+ } ) ;
385+
302386 await updateEnterpriseConnection ( enterpriseConnection . id , { saml } ) ;
303387 void goNext ( ) ;
304388 } catch ( err ) {
305- controller . applySubmitError ( err , card ) ;
389+ if ( mode === 'metadataUrl' ) {
390+ applySamlSubmitError ( err , card , metadataUrlField ) ;
391+ } else {
392+ applySamlSubmitError ( err , card , signOnUrlField , [ issuerField , certificateField ] ) ;
393+ }
306394 } finally {
307395 card . setIdle ( ) ;
308396 }
@@ -323,47 +411,26 @@ const SamlCustomIdentityProviderMetadataStep = (): JSX.Element => {
323411 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.title' ,
324412 ) }
325413 />
326- < IdentityProviderMetadataForm
327- controller = { controller }
328- modes = { {
414+ < IdentityProviderConfigurationModes
415+ modes = { CUSTOM_SAML_IDP_MODES }
416+ value = { mode }
417+ onChange = { next => {
418+ card . setError ( undefined ) ;
419+ setMode ( next ) ;
420+ } }
421+ labels = { {
329422 ariaLabel : localizationKeys (
330423 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.ariaLabel' ,
331424 ) ,
332- metadataUrlLabel : localizationKeys (
425+ metadataUrl : localizationKeys (
333426 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.metadataUrl' ,
334427 ) ,
335- manualLabel : localizationKeys (
428+ manual : localizationKeys (
336429 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.modes.manual' ,
337430 ) ,
338431 } }
339- metadataUrl = { {
340- description : localizationKeys (
341- 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.metadataUrl.description' ,
342- ) ,
343- } }
344- manual = { {
345- description : localizationKeys (
346- 'configureSSO.configureStep.samlCustom.identityProviderMetadataStep.manual.description' ,
347- ) ,
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- } ,
365- } }
366432 />
433+ < IdentityProviderConfigurationForm { ...formProps } />
367434 </ Step . Section >
368435 </ Step . Body >
369436
0 commit comments