Skip to content

Commit 707c675

Browse files
committed
fix(sso): clear the required-error when a secret replacement is backed out
1 parent 091d08a commit 707c675

2 files changed

Lines changed: 41 additions & 6 deletions

File tree

apps/sim/ee/sso/components/sso-settings.test.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,27 @@ describe('SSO client secret preservation', () => {
298298
expect(container).toHaveTextContent('Client Secret is required.')
299299
})
300300

301+
/**
302+
* Backing out has to revalidate as "keeping the saved secret". Validating against
303+
* the pre-toggle value would leave a required-error stranded on the masked row,
304+
* where there is no longer an input to fix it in.
305+
*/
306+
it('clears a stranded required-error when the replacement is backed out', async () => {
307+
renderSso('org-a')
308+
startEditing()
309+
act(() => findButton('Replace')?.click())
310+
typeSecret(' ')
311+
await act(async () => {
312+
findButton('Update')?.click()
313+
})
314+
expect(container).toHaveTextContent('Client Secret is required.')
315+
316+
act(() => findButton('Keep saved')?.click())
317+
318+
expect(container).not.toHaveTextContent('Client Secret is required.')
319+
expect(secretInput()?.value).toBe('••••••••••••4f2a')
320+
})
321+
301322
/**
302323
* The label is deliberately not "Cancel": the header already uses that to discard
303324
* the whole edit, and matching it here would make two very different actions

apps/sim/ee/sso/components/sso-settings.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,12 @@ function OrganizationSsoSettings({ organizationId }: SSOProps) {
331331
return out
332332
}
333333

334-
const validateAll = (data: typeof formData) => {
334+
/**
335+
* `isReplacingSecret` is a parameter rather than a closure read: callers that
336+
* validate in the same tick as toggling it would otherwise see the previous
337+
* value and leave a stale "required" error on a field that is no longer an input.
338+
*/
339+
const validateAll = (data: typeof formData, isReplacingSecret = isReplacingClientSecret) => {
335340
const newErrors: Record<string, string[]> = {
336341
providerType: [],
337342
providerId: validateProviderId(data.providerId),
@@ -354,7 +359,7 @@ function OrganizationSsoSettings({ organizationId }: SSOProps) {
354359
// clicked the field is a real input again, so a blank or whitespace-only
355360
// value has to fail rather than quietly overwrite a working secret.
356361
newErrors.clientSecret =
357-
hasStoredClientSecret && !isReplacingClientSecret
362+
hasStoredClientSecret && !isReplacingSecret
358363
? []
359364
: validateRequired('Client Secret', data.clientSecret)
360365
if (!data.scopes || !data.scopes.trim()) {
@@ -483,6 +488,18 @@ function OrganizationSsoSettings({ organizationId }: SSOProps) {
483488
validateAll(next)
484489
}
485490

491+
/**
492+
* Backs out of a replacement: drops what was typed and revalidates as "keeping
493+
* the saved secret", so a required-error from a failed submit does not linger on
494+
* a row that is no longer an input.
495+
*/
496+
const handleKeepSavedSecret = () => {
497+
setIsReplacingClientSecret(false)
498+
const next = { ...formData, clientSecret: '' }
499+
setFormData(next)
500+
validateAll(next, false)
501+
}
502+
486503
const isSaml = formData.providerType === 'saml'
487504
const mappingDefaults = isSaml ? SAML_DEFAULT_MAPPING : OIDC_DEFAULT_MAPPING
488505
const callbackUrl = `${getBaseUrl()}/api/auth/${isSaml ? 'sso/saml2/callback' : 'sso/callback'}/${formData.providerId || existingProvider?.providerId || 'provider-id'}`
@@ -822,10 +839,7 @@ function OrganizationSsoSettings({ organizationId }: SSOProps) {
822839
storedHint={storedClientSecretHint}
823840
isReplacing={isReplacingClientSecret}
824841
onReplace={() => setIsReplacingClientSecret(true)}
825-
onCancelReplace={() => {
826-
setIsReplacingClientSecret(false)
827-
handleInputChange('clientSecret', '')
828-
}}
842+
onCancelReplace={handleKeepSavedSecret}
829843
value={formData.clientSecret}
830844
onChange={(next) => handleInputChange('clientSecret', next)}
831845
hasError={showErrors && errors.clientSecret.length > 0}

0 commit comments

Comments
 (0)