@@ -127,7 +127,7 @@ func TestPostTeamsTeamIDMembers_DuplicateMemberReturnsBadRequest(t *testing.T) {
127127 store := & APIStore {
128128 db : testDB .SqlcClient ,
129129 authDB : testDB .AuthDB ,
130- idp : newHandlerTestIdentityProvider () ,
130+ idp : handlerTestIdentityProvider {} ,
131131 }
132132 store .PostTeamsTeamIDMembers (ginCtx , teamID )
133133
@@ -166,7 +166,7 @@ func TestPostTeamsTeamIDMembers_CreatesPublicUserAnchorForInvitee(t *testing.T)
166166 db : testDB .SqlcClient ,
167167 authDB : testDB .AuthDB ,
168168 authService : noopAuthService {},
169- idp : newHandlerTestIdentityProvider () ,
169+ idp : handlerTestIdentityProvider {} ,
170170 }
171171 store .PostTeamsTeamIDMembers (ginCtx , teamID )
172172
@@ -186,6 +186,92 @@ func TestPostTeamsTeamIDMembers_CreatesPublicUserAnchorForInvitee(t *testing.T)
186186 }
187187}
188188
189+ // ssoIdentityProvider is a Provider whose organization lookups are configurable, so
190+ // tests can simulate identities that belong to an Ory organization.
191+ type ssoIdentityProvider struct {
192+ handlerTestIdentityProvider
193+
194+ orgByUser map [uuid.UUID ]uuid.UUID
195+ }
196+
197+ func (p ssoIdentityProvider ) GetUserOrganizationID (_ context.Context , userID uuid.UUID ) (uuid.UUID , error ) {
198+ return p .orgByUser [userID ], nil
199+ }
200+
201+ func TestPostTeamsTeamIDMembers_RejectsInviteOutsideSSOOrg (t * testing.T ) {
202+ t .Parallel ()
203+
204+ ctx := t .Context ()
205+ teamID := uuid .New ()
206+ orgID := uuid .New ()
207+ actingUserID := uuid .New ()
208+ inviteeID := uuid .New ()
209+
210+ recorder := httptest .NewRecorder ()
211+ ginCtx , _ := gin .CreateTestContext (recorder )
212+ auth .SetUserIDForTest (t , ginCtx , actingUserID )
213+ auth .SetTeamInfoForTest (t , ginCtx , & authtypes.Team {
214+ Team : & authqueries.Team {ID : teamID , SsoOrganizationID : & orgID },
215+ })
216+ ginCtx .Request = httptest .NewRequestWithContext (ctx , http .MethodPost , "/" , strings .NewReader (`{"email":"` + handlerTestUserEmail (inviteeID )+ `"}` ))
217+ ginCtx .Request .Header .Set ("Content-Type" , "application/json" )
218+
219+ // invitee belongs to no org (orgByUser empty) → outside the team's org.
220+ store := & APIStore {
221+ idp : ssoIdentityProvider {},
222+ provisioning : provisioning .New (nil , ssoIdentityProvider {}, nil , "" ),
223+ }
224+ store .PostTeamsTeamIDMembers (ginCtx , teamID )
225+
226+ if recorder .Code != http .StatusForbidden {
227+ t .Fatalf ("expected 403 for an invite outside the SSO org, got %d: %s" , recorder .Code , recorder .Body .String ())
228+ }
229+ }
230+
231+ func TestPostTeamsTeamIDMembers_AllowsInviteFromSSOOrg (t * testing.T ) {
232+ t .Parallel ()
233+
234+ testDB := testutils .SetupDatabase (t )
235+ ctx := t .Context ()
236+
237+ orgID := uuid .New ()
238+ teamID := testutils .CreateTestTeam (t , testDB )
239+ actingUserID := createHandlerTestUser (t , testDB )
240+ inviteeID := uuid .New ()
241+
242+ recorder := httptest .NewRecorder ()
243+ ginCtx , _ := gin .CreateTestContext (recorder )
244+ auth .SetUserIDForTest (t , ginCtx , actingUserID )
245+ auth .SetTeamInfoForTest (t , ginCtx , & authtypes.Team {
246+ Team : & authqueries.Team {ID : teamID , SsoOrganizationID : & orgID },
247+ })
248+ ginCtx .Request = httptest .NewRequestWithContext (ctx , http .MethodPost , "/" , strings .NewReader (`{"email":"` + handlerTestUserEmail (inviteeID )+ `"}` ))
249+ ginCtx .Request .Header .Set ("Content-Type" , "application/json" )
250+
251+ // invitee belongs to the same org as the team → allowed.
252+ profiles := ssoIdentityProvider {orgByUser : map [uuid.UUID ]uuid.UUID {inviteeID : orgID }}
253+ store := & APIStore {
254+ db : testDB .SqlcClient ,
255+ authDB : testDB .AuthDB ,
256+ authService : noopAuthService {},
257+ idp : profiles ,
258+ provisioning : provisioning .New (testDB .AuthDB , profiles , nil , "" ),
259+ }
260+ store .PostTeamsTeamIDMembers (ginCtx , teamID )
261+
262+ if ginCtx .Writer .Status () != http .StatusCreated {
263+ t .Fatalf ("expected 201 for an in-org invite, got %d: %s" , ginCtx .Writer .Status (), recorder .Body .String ())
264+ }
265+
266+ memberships , err := testDB .AuthDB .Read .GetTeamsWithUsersTeams (ctx , inviteeID )
267+ if err != nil {
268+ t .Fatalf ("failed to read memberships: %v" , err )
269+ }
270+ if len (memberships ) != 1 || memberships [0 ].Team .ID != teamID {
271+ t .Fatalf ("expected invitee to be a member of %s, got %v" , teamID , memberships )
272+ }
273+ }
274+
189275func TestDeleteTeamsTeamIDMembersUserId_NonMemberReturnsBadRequest (t * testing.T ) {
190276 t .Parallel ()
191277
@@ -357,10 +443,6 @@ func handlerTestUserEmail(userID uuid.UUID) string {
357443
358444type handlerTestIdentityProvider struct {}
359445
360- func newHandlerTestIdentityProvider () identity.Provider {
361- return handlerTestIdentityProvider {}
362- }
363-
364446func (handlerTestIdentityProvider ) GetProfilesByUserID (_ context.Context , userIDs []uuid.UUID ) (map [uuid.UUID ]identity.Profile , error ) {
365447 profiles := make (map [uuid.UUID ]identity.Profile , len (userIDs ))
366448 for _ , userID := range userIDs {
@@ -483,7 +565,7 @@ func TestPostTeams_LocalPolicyDeniedReturnsBadRequestWithoutCreatingTeam(t *test
483565 store := & APIStore {
484566 db : testDB .SqlcClient ,
485567 authDB : testDB .AuthDB ,
486- provisioning : provisioning .New (testDB .AuthDB , newHandlerTestIdentityProvider () , sink , "" ),
568+ provisioning : provisioning .New (testDB .AuthDB , handlerTestIdentityProvider {} , sink , "" ),
487569 }
488570 store .PostTeams (ginCtx )
489571
@@ -521,7 +603,7 @@ func TestPostTeams_InvalidNameReturnsBadRequest(t *testing.T) {
521603 store := & APIStore {
522604 db : testDB .SqlcClient ,
523605 authDB : testDB .AuthDB ,
524- provisioning : provisioning .New (testDB .AuthDB , newHandlerTestIdentityProvider () , sink , "" ),
606+ provisioning : provisioning .New (testDB .AuthDB , handlerTestIdentityProvider {} , sink , "" ),
525607 }
526608 store .PostTeams (ginCtx )
527609
@@ -551,7 +633,7 @@ func TestPostTeams_InvalidRequestBodyReturnsBadRequest(t *testing.T) {
551633 store := & APIStore {
552634 db : testDB .SqlcClient ,
553635 authDB : testDB .AuthDB ,
554- provisioning : provisioning .New (testDB .AuthDB , newHandlerTestIdentityProvider () , sink , "" ),
636+ provisioning : provisioning .New (testDB .AuthDB , handlerTestIdentityProvider {} , sink , "" ),
555637 }
556638 store .PostTeams (ginCtx )
557639
@@ -583,7 +665,7 @@ func TestPostTeams_TrimsNameBeforeCreate(t *testing.T) {
583665 store := & APIStore {
584666 db : testDB .SqlcClient ,
585667 authDB : testDB .AuthDB ,
586- provisioning : provisioning .New (testDB .AuthDB , newHandlerTestIdentityProvider () , sink , "" ),
668+ provisioning : provisioning .New (testDB .AuthDB , handlerTestIdentityProvider {} , sink , "" ),
587669 }
588670 store .PostTeams (ginCtx )
589671
@@ -634,7 +716,7 @@ func TestPostTeams_ProvisioningFailureRollsBackCreatedTeam(t *testing.T) {
634716 store := & APIStore {
635717 db : testDB .SqlcClient ,
636718 authDB : testDB .AuthDB ,
637- provisioning : provisioning .New (testDB .AuthDB , newHandlerTestIdentityProvider () , sink , "" ),
719+ provisioning : provisioning .New (testDB .AuthDB , handlerTestIdentityProvider {} , sink , "" ),
638720 }
639721 store .PostTeams (ginCtx )
640722
@@ -692,7 +774,7 @@ func TestPostTeams_ProvisioningFailurePreservesProvisionErrorStatus(t *testing.T
692774 store := & APIStore {
693775 db : testDB .SqlcClient ,
694776 authDB : testDB .AuthDB ,
695- provisioning : provisioning .New (testDB .AuthDB , newHandlerTestIdentityProvider () , sink , "" ),
777+ provisioning : provisioning .New (testDB .AuthDB , handlerTestIdentityProvider {} , sink , "" ),
696778 }
697779 store .PostTeams (ginCtx )
698780
0 commit comments