@@ -32,6 +32,7 @@ type SsoFormState = {
3232 allowedEmailDomains : string [ ] ;
3333 allowedGroups : string [ ] ;
3434 trustUnverifiedEmail : boolean ;
35+ manualUserValidation : boolean ;
3536} ;
3637
3738const DEFAULT_SCOPES = 'openid profile email' ;
@@ -47,6 +48,7 @@ const emptyForm: SsoFormState = {
4748 allowedEmailDomains : [ ] ,
4849 allowedGroups : [ ] ,
4950 trustUnverifiedEmail : false ,
51+ manualUserValidation : false ,
5052} ;
5153
5254const formFromSettings = ( settings : SsoSettings ) : SsoFormState => ( {
@@ -61,6 +63,7 @@ const formFromSettings = (settings: SsoSettings): SsoFormState => ({
6163 allowedEmailDomains : settings . allowedEmailDomains ,
6264 allowedGroups : settings . allowedGroups ,
6365 trustUnverifiedEmail : settings . trustUnverifiedEmail ,
66+ manualUserValidation : settings . manualUserValidation ,
6467} ) ;
6568
6669// Signature of the stored values the form is populated from. The live enabled
@@ -78,6 +81,7 @@ const settingsFormSignature = (settings: SsoSettings | null): string | null =>
7881 settings . allowedEmailDomains ,
7982 settings . allowedGroups ,
8083 settings . trustUnverifiedEmail ,
84+ settings . manualUserValidation ,
8185 ] )
8286 : null ;
8387
@@ -241,6 +245,7 @@ export const SsoConfigCard = () => {
241245 allowedGroups : form . allowedGroups ,
242246 groupsClaim : form . groupsClaim . trim ( ) ,
243247 trustUnverifiedEmail : form . trustUnverifiedEmail ,
248+ manualUserValidation : form . manualUserValidation ,
244249 } ) ;
245250 queryClient . setQueryData ( [ 'ssoSettings' ] , saved ) ;
246251 queryClient . invalidateQueries ( { queryKey : [ 'ssoPublicConfig' ] } ) ;
@@ -278,6 +283,7 @@ export const SsoConfigCard = () => {
278283 allowedGroups : settings . allowedGroups ,
279284 groupsClaim : settings . groupsClaim ,
280285 trustUnverifiedEmail : settings . trustUnverifiedEmail ,
286+ manualUserValidation : settings . manualUserValidation ,
281287 } ) ;
282288 queryClient . setQueryData ( [ 'ssoSettings' ] , saved ) ;
283289 queryClient . invalidateQueries ( { queryKey : [ 'ssoPublicConfig' ] } ) ;
@@ -364,7 +370,10 @@ export const SsoConfigCard = () => {
364370 < AlertTitle > Could not load the SSO configuration</ AlertTitle >
365371 < AlertDescription className = "flex flex-col items-start gap-3" >
366372 < span className = "break-words" >
367- { describeApiError ( settingsQuery . error , 'The server could not be reached.' ) . description }
373+ {
374+ describeApiError ( settingsQuery . error , 'The server could not be reached.' )
375+ . description
376+ }
368377 </ span >
369378 < Button
370379 variant = "outline"
@@ -428,12 +437,16 @@ export const SsoConfigCard = () => {
428437 size = "icon"
429438 onClick = { handleCopyRedirectUri }
430439 title = "Copy the redirect URI" >
431- { copied ? < Check className = "h-4 w-4 text-emerald-600" /> : < Copy className = "h-4 w-4" /> }
440+ { copied ? (
441+ < Check className = "h-4 w-4 text-emerald-600" />
442+ ) : (
443+ < Copy className = "h-4 w-4" />
444+ ) }
432445 </ Button >
433446 </ div >
434447 < FieldHint >
435- Derived from BASE_URL. Register it as a web redirect URI in your identity
436- provider's app registration before saving.
448+ Derived from BASE_URL. Register it as a web redirect URI in your identity provider's
449+ app registration before saving.
437450 </ FieldHint >
438451 </ div >
439452
@@ -448,8 +461,10 @@ export const SsoConfigCard = () => {
448461 spellCheck = { false }
449462 />
450463 < FieldHint >
451- The OpenID Connect issuer of your provider. Saving fetches
452- { ' ' } < code className = "rounded bg-muted px-1 py-0.5 text-[11px]" > /.well-known/openid-configuration</ code > { ' ' }
464+ The OpenID Connect issuer of your provider. Saving fetches{ ' ' }
465+ < code className = "rounded bg-muted px-1 py-0.5 text-[11px]" >
466+ /.well-known/openid-configuration
467+ </ code > { ' ' }
453468 from it and reports any error right here.
454469 </ FieldHint >
455470 </ div >
@@ -547,6 +562,24 @@ export const SsoConfigCard = () => {
547562 onCheckedChange = { checked => setField ( 'trustUnverifiedEmail' , checked ) }
548563 />
549564 </ div >
565+
566+ < div className = "flex items-start justify-between gap-4 border-t pt-4" >
567+ < div className = "space-y-0.5" >
568+ < Label htmlFor = "sso-manual-validation" > Require admin approval</ Label >
569+ < FieldHint >
570+ New accounts are created on their first sign-in but cannot enter until an admin
571+ approves them on the Users page, where they show up as pending. Use this when
572+ your provider authenticates more people than should reach this dashboard and
573+ does not send group claims to filter on, which is the case for Google Workspace.
574+ Accounts that already exist keep their access.
575+ </ FieldHint >
576+ </ div >
577+ < Switch
578+ aria-label = "Require admin approval"
579+ checked = { form . manualUserValidation }
580+ onCheckedChange = { checked => setField ( 'manualUserValidation' , checked ) }
581+ />
582+ </ div >
550583 </ div >
551584
552585 < div className = "space-y-4" >
0 commit comments