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,15 @@ 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+ } from './shared/IdentityProviderConfigurationForm' ;
20+ import {
21+ IdentityProviderConfigurationModes ,
22+ type IdpConfigurationMode ,
23+ } from './shared/IdentityProviderConfigurationModes' ;
1724
1825export 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+
255264const 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