@@ -88,6 +88,7 @@ const OIDC_BODY = {
8888 providerId : 'acme-oidc' ,
8989 issuer : 'https://idp.acme.com' ,
9090 domain : 'acme.com' ,
91+ orgId : 'org1' ,
9192 clientId : 'client-id' ,
9293 clientSecret : 'client-secret' ,
9394 authorizationEndpoint : 'https://idp.acme.com/authorize' ,
@@ -137,21 +138,21 @@ describe('POST /api/auth/sso/register', () => {
137138
138139 it ( 'rejects callers without an Enterprise plan' , async ( ) => {
139140 mockHasSSOAccess . mockResolvedValue ( false )
140- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
141+ const res = await POST ( request ( OIDC_BODY ) )
141142 expect ( res . status ) . toBe ( 403 )
142143 expect ( mockRegisterSSOProvider ) . not . toHaveBeenCalled ( )
143144 } )
144145
145146 it ( 'rejects callers who are not an admin/owner of the target org' , async ( ) => {
146147 queueMembers ( [ { organizationId : 'org1' , role : 'member' } ] )
147- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
148+ const res = await POST ( request ( OIDC_BODY ) )
148149 expect ( res . status ) . toBe ( 403 )
149150 expect ( mockRegisterSSOProvider ) . not . toHaveBeenCalled ( )
150151 } )
151152
152153 it ( 'rejects an invalid domain' , async ( ) => {
153154 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
154- const res = await POST ( request ( { ...OIDC_BODY , domain : 'not-a-domain' , orgId : 'org1' } ) )
155+ const res = await POST ( request ( { ...OIDC_BODY , domain : 'not-a-domain' } ) )
155156 expect ( res . status ) . toBe ( 400 )
156157 expect ( mockRegisterSSOProvider ) . not . toHaveBeenCalled ( )
157158 } )
@@ -160,7 +161,7 @@ describe('POST /api/auth/sso/register', () => {
160161 resetDbChainMock ( )
161162 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
162163 queueTableRows ( schemaMock . ssoDomain , [ ] ) // no verified sso_domain row
163- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
164+ const res = await POST ( request ( OIDC_BODY ) )
164165 const json = await res . json ( )
165166 expect ( res . status ) . toBe ( 403 )
166167 expect ( json . code ) . toBe ( 'SSO_DOMAIN_NOT_VERIFIED' )
@@ -172,7 +173,7 @@ describe('POST /api/auth/sso/register', () => {
172173 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
173174 queueTableRows ( schemaMock . ssoDomain , [ { id : 'v' } ] ) // entry gate: verified
174175 queueTableRows ( schemaMock . ssoDomain , [ ] ) // re-check before write: revoked
175- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
176+ const res = await POST ( request ( OIDC_BODY ) )
176177 const json = await res . json ( )
177178 expect ( res . status ) . toBe ( 403 )
178179 expect ( json . code ) . toBe ( 'SSO_DOMAIN_NOT_VERIFIED' )
@@ -185,7 +186,7 @@ describe('POST /api/auth/sso/register', () => {
185186 queueTableRows ( schemaMock . ssoDomain , [ { id : 'v' } ] ) // entry gate: verified
186187 queueTableRows ( schemaMock . ssoDomain , [ { id : 'v' } ] ) // pre-write re-check: verified
187188 queueTableRows ( schemaMock . ssoDomain , [ ] ) // locking read in the grant: proof gone
188- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
189+ const res = await POST ( request ( OIDC_BODY ) )
189190 const json = await res . json ( )
190191 expect ( res . status ) . toBe ( 403 )
191192 expect ( json . code ) . toBe ( 'SSO_DOMAIN_NOT_VERIFIED' )
@@ -221,7 +222,7 @@ describe('POST /api/auth/sso/register', () => {
221222 queueProviders ( [
222223 { domain : 'acme.com' , userId : 'u1' , organizationId : 'org1' , providerId : 'acme-saml' } ,
223224 ] )
224- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
225+ const res = await POST ( request ( OIDC_BODY ) )
225226 const json = await res . json ( )
226227 expect ( res . status ) . toBe ( 409 )
227228 expect ( json . code ) . toBe ( 'SSO_DOMAIN_ALREADY_ROUTED' )
@@ -238,7 +239,7 @@ describe('POST /api/auth/sso/register', () => {
238239 constraint_name : 'sso_provider_org_domain_unique' ,
239240 } )
240241 )
241- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
242+ const res = await POST ( request ( OIDC_BODY ) )
242243 const json = await res . json ( )
243244 expect ( res . status ) . toBe ( 409 )
244245 expect ( json . code ) . toBe ( 'SSO_DOMAIN_ALREADY_ROUTED' )
@@ -248,14 +249,14 @@ describe('POST /api/auth/sso/register', () => {
248249 it ( 'lets the organization add a provider for a different verified domain' , async ( ) => {
249250 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
250251 queueProviders ( [ ] )
251- const res = await POST ( request ( { ...OIDC_BODY , orgId : 'org1' , domain : 'eng.acme.com' } ) )
252+ const res = await POST ( request ( { ...OIDC_BODY , domain : 'eng.acme.com' } ) )
252253 expect ( res . status ) . toBe ( 200 )
253254 expect ( mockRegisterSSOProvider ) . toHaveBeenCalledTimes ( 1 )
254255 } )
255256
256257 it ( 'registers when the domain is unclaimed' , async ( ) => {
257258 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
258- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
259+ const res = await POST ( request ( OIDC_BODY ) )
259260 expect ( res . status ) . toBe ( 200 )
260261 expect ( mockRegisterSSOProvider ) . toHaveBeenCalledTimes ( 1 )
261262 } )
@@ -286,7 +287,7 @@ describe('POST /api/auth/sso/register', () => {
286287 it ( 'does not treat the caller’s own provider as a providerId conflict' , async ( ) => {
287288 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
288289 queueProviders ( [ ] , [ { domain : 'acme.com' , userId : 'u1' , organizationId : 'org1' } ] )
289- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
290+ const res = await POST ( request ( OIDC_BODY ) )
290291 expect ( res . status ) . toBe ( 200 )
291292 } )
292293
@@ -298,7 +299,7 @@ describe('POST /api/auth/sso/register', () => {
298299 */
299300 it ( 'marks the provider domain-verified after registering' , async ( ) => {
300301 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
301- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
302+ const res = await POST ( request ( OIDC_BODY ) )
302303 expect ( res . status ) . toBe ( 200 )
303304 expect ( dbChainMockFns . set ) . toHaveBeenCalledWith ( {
304305 domainVerified : true ,
@@ -311,7 +312,7 @@ describe('POST /api/auth/sso/register', () => {
311312 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
312313 queueProviders ( [ ] )
313314 queueTableRows ( schemaMock . ssoProvider , [ { id : 'p1' } ] )
314- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
315+ const res = await POST ( request ( OIDC_BODY ) )
315316 expect ( res . status ) . toBe ( 200 )
316317 expect ( mockUpdateSSOProvider ) . toHaveBeenCalledTimes ( 1 )
317318 expect ( dbChainMockFns . set ) . toHaveBeenCalledWith ( {
@@ -346,7 +347,7 @@ describe('POST /api/auth/sso/register', () => {
346347 } ,
347348 ] ) // provider already owned → update path
348349
349- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
350+ const res = await POST ( request ( OIDC_BODY ) )
350351 expect ( res . status ) . toBe ( 403 )
351352 expect ( mockUpdateSSOProvider ) . toHaveBeenCalledTimes ( 1 )
352353 // The conditional grant UPDATE is still issued — it simply matches no rows once
@@ -376,7 +377,7 @@ describe('POST /api/auth/sso/register', () => {
376377 ] )
377378 dbChainMockFns . returning . mockRejectedValueOnce ( new Error ( 'trust write failed' ) )
378379
379- const res = await POST ( request ( { ...OIDC_BODY , orgId : 'org1' , jitProvisioningEnabled : false } ) )
380+ const res = await POST ( request ( { ...OIDC_BODY , jitProvisioningEnabled : false } ) )
380381
381382 expect ( res . status ) . toBe ( 500 )
382383 expect ( mockUpdateSSOProvider ) . toHaveBeenCalledTimes ( 1 )
@@ -397,41 +398,28 @@ describe('POST /api/auth/sso/register', () => {
397398 queueTableRows ( schemaMock . ssoDomain , [ { id : 'verified-domain' } ] )
398399 queueTableRows ( schemaMock . ssoDomain , [ { id : 'verified-domain' } ] )
399400 queueTableRows ( schemaMock . ssoDomain , [ ] ) // locking read in the grant: proof gone
400- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
401+ const res = await POST ( request ( OIDC_BODY ) )
401402 expect ( res . status ) . toBe ( 403 )
402403 expect ( mockRegisterSSOProvider ) . toHaveBeenCalledTimes ( 1 ) // it was created…
403404 expect ( dbChainMockFns . delete ) . toHaveBeenCalled ( ) // …then rolled back
404405 } )
405406
406407 /**
407- * A personal provider has no verified domain behind it. On the hosted
408- * multi-tenant deployment that must grant no linking authority, or anyone able
409- * to register one could claim a domain they do not own and have their own IdP
410- * auto-link to existing accounts on it.
408+ * An org-less provider has no `sso_domain` proof behind its domain, and domain
409+ * trust is what auto-links an SSO sign-in into an existing same-email account,
410+ * so the route refuses one before any write.
411411 */
412- it ( 'does not grant domain trust to a personal provider when hosted' , async ( ) => {
413- setEnvFlags ( { isSsoEnabled : true , isHosted : true } )
414- const res = await POST ( request ( OIDC_BODY ) )
415- expect ( res . status ) . toBe ( 200 )
416- expect ( dbChainMockFns . set ) . toHaveBeenCalledWith ( {
417- domainVerified : false ,
418- jitProvisioningEnabled : true ,
419- } )
420- } )
421-
422- it ( 'grants domain trust to a personal provider when self-hosted' , async ( ) => {
423- setEnvFlags ( { isSsoEnabled : true , isHosted : false } )
424- const res = await POST ( request ( OIDC_BODY ) )
425- expect ( res . status ) . toBe ( 200 )
426- expect ( dbChainMockFns . set ) . toHaveBeenCalledWith ( {
427- domainVerified : true ,
428- jitProvisioningEnabled : true ,
429- } )
412+ it ( 'refuses a provider without an organization' , async ( ) => {
413+ const { orgId : _orgId , ...orgLessBody } = OIDC_BODY
414+ const res = await POST ( request ( orgLessBody ) )
415+ expect ( res . status ) . toBe ( 400 )
416+ expect ( ( await res . json ( ) ) . error ) . toContain ( 'Organization ID is required' )
417+ expect ( mockRegisterSSOProvider ) . not . toHaveBeenCalled ( )
430418 } )
431419
432420 it ( 'persists invite-only provisioning without changing Better Auth provider config' , async ( ) => {
433421 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
434- const res = await POST ( request ( { ...OIDC_BODY , orgId : 'org1' , jitProvisioningEnabled : false } ) )
422+ const res = await POST ( request ( { ...OIDC_BODY , jitProvisioningEnabled : false } ) )
435423 expect ( res . status ) . toBe ( 200 )
436424 expect ( dbChainMockFns . set ) . toHaveBeenCalledWith ( {
437425 domainVerified : true ,
@@ -516,9 +504,7 @@ describe('POST /api/auth/sso/register', () => {
516504
517505 it ( 'nests the attribute mapping inside oidcConfig (Better Auth reads it there)' , async ( ) => {
518506 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
519- await POST (
520- request ( { ...OIDC_BODY , orgId : 'org1' , mapping : { id : 'oid' , email : 'upn' , name : 'name' } } )
521- )
507+ await POST ( request ( { ...OIDC_BODY , mapping : { id : 'oid' , email : 'upn' , name : 'name' } } ) )
522508 expect ( mockRegisterSSOProvider ) . toHaveBeenCalledTimes ( 1 )
523509 const sent = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
524510 expect ( sent . mapping ) . toBeUndefined ( ) // not passed at the top level (silently ignored there)
@@ -529,7 +515,7 @@ describe('POST /api/auth/sso/register', () => {
529515 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
530516 queueProviders ( [ ] ) // no providerId or domain conflicts on either pass
531517 queueTableRows ( schemaMock . ssoProvider , [ { id : 'p1' } ] ) // provider already owned → edit
532- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
518+ const res = await POST ( request ( OIDC_BODY ) )
533519 expect ( res . status ) . toBe ( 200 )
534520 const body = await res . json ( )
535521 expect ( body . message ) . toContain ( 'updated' )
@@ -540,30 +526,30 @@ describe('POST /api/auth/sso/register', () => {
540526 it ( 'allows the owning tenant to update its own provider for the same domain' , async ( ) => {
541527 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
542528 queueProviders ( [ { domain : 'acme.com' , userId : 'u1' , organizationId : 'org1' } ] )
543- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
529+ const res = await POST ( request ( OIDC_BODY ) )
544530 expect ( res . status ) . toBe ( 200 )
545531 expect ( mockRegisterSSOProvider ) . toHaveBeenCalledTimes ( 1 )
546532 } )
547533
548- it ( 'lets an org admin adopt their own user-scoped provider for the same domain' , async ( ) => {
534+ it ( "does not report the caller's own org-less provider as another tenant's claim" , async ( ) => {
549535 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
550536 queueProviders ( [ { domain : 'acme.com' , userId : 'u1' , organizationId : null } ] )
551- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
537+ const res = await POST ( request ( OIDC_BODY ) )
552538 expect ( res . status ) . toBe ( 200 )
553539 expect ( mockRegisterSSOProvider ) . toHaveBeenCalledTimes ( 1 )
554540 } )
555541
556542 it ( "still blocks an org admin from claiming another user's user-scoped domain" , async ( ) => {
557543 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
558544 queueProviders ( [ { domain : 'acme.com' , userId : 'someone-else' , organizationId : null } ] )
559- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
545+ const res = await POST ( request ( OIDC_BODY ) )
560546 expect ( res . status ) . toBe ( 409 )
561547 expect ( mockRegisterSSOProvider ) . not . toHaveBeenCalled ( )
562548 } )
563549
564550 it ( 'normalizes the domain before persisting it' , async ( ) => {
565551 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
566- const res = await POST ( request ( { ...OIDC_BODY , domain : 'ACME.com' , orgId : 'org1' } ) )
552+ const res = await POST ( request ( { ...OIDC_BODY , domain : 'ACME.com' } ) )
567553 expect ( res . status ) . toBe ( 200 )
568554 expect ( mockRegisterSSOProvider ) . toHaveBeenCalledTimes ( 1 )
569555 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
@@ -572,15 +558,15 @@ describe('POST /api/auth/sso/register', () => {
572558
573559 it ( 'passes skipDiscovery since Sim already resolved and validated the OIDC endpoints' , async ( ) => {
574560 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
575- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
561+ const res = await POST ( request ( OIDC_BODY ) )
576562 expect ( res . status ) . toBe ( 200 )
577563 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
578564 expect ( config . oidcConfig . skipDiscovery ) . toBe ( true )
579565 } )
580566
581567 it ( 'omits userInfoEndpoint when skipUserInfoEndpoint is requested, forcing ID token claims' , async ( ) => {
582568 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
583- const res = await POST ( request ( { ...OIDC_BODY , skipUserInfoEndpoint : true , orgId : 'org1' } ) )
569+ const res = await POST ( request ( { ...OIDC_BODY , skipUserInfoEndpoint : true } ) )
584570 expect ( res . status ) . toBe ( 200 )
585571 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
586572 expect ( config . oidcConfig . userInfoEndpoint ) . toBeUndefined ( )
@@ -594,7 +580,7 @@ describe('POST /api/auth/sso/register', () => {
594580 }
595581 return { isValid : true , resolvedIP : '1.2.3.4' }
596582 } )
597- const res = await POST ( request ( { ...OIDC_BODY , skipUserInfoEndpoint : true , orgId : 'org1' } ) )
583+ const res = await POST ( request ( { ...OIDC_BODY , skipUserInfoEndpoint : true } ) )
598584 expect ( res . status ) . toBe ( 200 )
599585 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
600586 expect ( config . oidcConfig . userInfoEndpoint ) . toBeUndefined ( )
@@ -624,15 +610,15 @@ describe('POST /api/auth/sso/register', () => {
624610 jwksEndpoint : undefined ,
625611 skipUserInfoEndpoint : true ,
626612 }
627- const res = await POST ( request ( { ... discoveredBody , orgId : 'org1' } ) )
613+ const res = await POST ( request ( discoveredBody ) )
628614 expect ( res . status ) . toBe ( 200 )
629615 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
630616 expect ( config . oidcConfig . userInfoEndpoint ) . toBeUndefined ( )
631617 } )
632618
633619 it ( 'keeps userInfoEndpoint when skipUserInfoEndpoint is not requested' , async ( ) => {
634620 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
635- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
621+ const res = await POST ( request ( OIDC_BODY ) )
636622 expect ( res . status ) . toBe ( 200 )
637623 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
638624 expect ( config . oidcConfig . userInfoEndpoint ) . toBe ( 'https://idp.acme.com/userinfo' )
@@ -656,7 +642,7 @@ describe('POST /api/auth/sso/register', () => {
656642 tokenEndpoint : undefined ,
657643 jwksEndpoint : undefined ,
658644 }
659- const res = await POST ( request ( { ... discoveredBody , orgId : 'org1' } ) )
645+ const res = await POST ( request ( discoveredBody ) )
660646 expect ( res . status ) . toBe ( 200 )
661647 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
662648 expect ( config . oidcConfig . tokenEndpointAuthentication ) . toBe ( 'client_secret_post' )
@@ -670,7 +656,7 @@ describe('POST /api/auth/sso/register', () => {
670656 token_endpoint_auth_methods_supported : [ 'client_secret_post' ] ,
671657 } ) ,
672658 } )
673- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
659+ const res = await POST ( request ( OIDC_BODY ) )
674660 expect ( res . status ) . toBe ( 200 )
675661 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
676662 expect ( config . oidcConfig . tokenEndpointAuthentication ) . toBe ( 'client_secret_post' )
@@ -680,7 +666,7 @@ describe('POST /api/auth/sso/register', () => {
680666 it ( 'registers successfully when discovery is unreachable and all endpoints are explicit' , async ( ) => {
681667 queueMembers ( [ { organizationId : 'org1' , role : 'owner' } ] )
682668 mockSecureFetchWithPinnedIP . mockRejectedValue ( new Error ( 'ECONNREFUSED' ) )
683- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
669+ const res = await POST ( request ( OIDC_BODY ) )
684670 expect ( res . status ) . toBe ( 200 )
685671 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
686672 expect ( config . oidcConfig . skipDiscovery ) . toBe ( true )
@@ -696,7 +682,7 @@ describe('POST /api/auth/sso/register', () => {
696682 token_endpoint_auth_methods_supported : [ 'client_secret_basic' , 'client_secret_post' ] ,
697683 } ) ,
698684 } )
699- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
685+ const res = await POST ( request ( OIDC_BODY ) )
700686 expect ( res . status ) . toBe ( 200 )
701687 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
702688 expect ( config . oidcConfig . tokenEndpointAuthentication ) . toBe ( 'client_secret_post' )
@@ -708,7 +694,7 @@ describe('POST /api/auth/sso/register', () => {
708694 ok : true ,
709695 json : async ( ) => ( { } ) ,
710696 } )
711- const res = await POST ( request ( { ... OIDC_BODY , orgId : 'org1' } ) )
697+ const res = await POST ( request ( OIDC_BODY ) )
712698 expect ( res . status ) . toBe ( 200 )
713699 const config = mockRegisterSSOProvider . mock . calls [ 0 ] [ 0 ] . body
714700 expect ( config . oidcConfig . tokenEndpointAuthentication ) . toBe ( 'client_secret_post' )
@@ -728,7 +714,7 @@ describe('POST /api/auth/sso/register', () => {
728714 tokenEndpoint : undefined ,
729715 jwksEndpoint : undefined ,
730716 }
731- const res = await POST ( request ( { ... discoveredBody , orgId : 'org1' } ) )
717+ const res = await POST ( request ( discoveredBody ) )
732718 const json = await res . json ( )
733719 expect ( res . status ) . toBe ( 400 )
734720 expect ( json . error ) . toContain ( 'resolves to a private IP address' )
0 commit comments