Skip to content

Commit 6837c24

Browse files
committed
fix(teams): skip email verification for admin-created users
Admin-created users are implicitly verified since an admin vouched for them. Only enforce require_email_verification_for_invites for self-registered users (auth_provider == "local") at both invitation creation and acceptance time. This prevents false rejections when inviting users provisioned via admin API or SSO. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
1 parent 2d4e8d6 commit 6837c24

5 files changed

Lines changed: 25 additions & 21 deletions

File tree

docs/config.schema.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,7 +1339,7 @@
13391339
},
13401340
"require_email_verification_for_invites": {
13411341
"default": true,
1342-
"description": "Require email verification for team invitations",
1342+
"description": "Require email verification for team invitations (only applies to self-registered users; admin-created users are implicitly verified)",
13431343
"title": "Require Email Verification For Invites",
13441344
"type": "boolean"
13451345
},
@@ -1770,8 +1770,8 @@
17701770
},
17711771
"allowed_origins": {
17721772
"default": [
1773-
"http://localhost",
1774-
"http://localhost:4444"
1773+
"http://localhost:4444",
1774+
"http://localhost"
17751775
],
17761776
"items": {
17771777
"type": "string"
@@ -2883,14 +2883,14 @@
28832883
},
28842884
"allowed_mime_types": {
28852885
"default": [
2886-
"image/jpeg",
2887-
"application/xml",
2888-
"text/markdown",
28892886
"text/plain",
2890-
"text/html",
2887+
"text/markdown",
28912888
"image/png",
2889+
"text/html",
2890+
"image/jpeg",
28922891
"image/gif",
2893-
"application/json"
2892+
"application/json",
2893+
"application/xml"
28942894
],
28952895
"items": {
28962896
"type": "string"

docs/docs/config.schema.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,7 +1339,7 @@
13391339
},
13401340
"require_email_verification_for_invites": {
13411341
"default": true,
1342-
"description": "Require email verification for team invitations",
1342+
"description": "Require email verification for team invitations (only applies to self-registered users; admin-created users are implicitly verified)",
13431343
"title": "Require Email Verification For Invites",
13441344
"type": "boolean"
13451345
},
@@ -1770,8 +1770,8 @@
17701770
},
17711771
"allowed_origins": {
17721772
"default": [
1773-
"http://localhost",
1774-
"http://localhost:4444"
1773+
"http://localhost:4444",
1774+
"http://localhost"
17751775
],
17761776
"items": {
17771777
"type": "string"
@@ -2883,14 +2883,14 @@
28832883
},
28842884
"allowed_mime_types": {
28852885
"default": [
2886-
"image/jpeg",
2887-
"application/xml",
2888-
"text/markdown",
28892886
"text/plain",
2890-
"text/html",
2887+
"text/markdown",
28912888
"image/png",
2889+
"text/html",
2890+
"image/jpeg",
28922891
"image/gif",
2893-
"application/json"
2892+
"application/json",
2893+
"application/xml"
28942894
],
28952895
"items": {
28962896
"type": "string"

mcpgateway/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ class Settings(BaseSettings):
570570
max_teams_per_user: int = Field(default=50, description="Maximum number of teams a user can belong to")
571571
max_members_per_team: int = Field(default=100, description="Maximum number of members per team")
572572
invitation_expiry_days: int = Field(default=7, description="Number of days before team invitations expire")
573-
require_email_verification_for_invites: bool = Field(default=True, description="Require email verification for team invitations")
573+
require_email_verification_for_invites: bool = Field(default=True, description="Require email verification for team invitations (only applies to self-registered users; admin-created users are implicitly verified)")
574574

575575
# Team Governance
576576
allow_team_creation: bool = Field(default=True, description="Allow users to create organizational teams. Admins can always create teams.")

mcpgateway/services/team_invitation_service.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,11 @@ async def create_invitation(self, team_id: str, email: str, role: str, invited_b
195195
logger.warning(f"Inviter {invited_by} not found")
196196
return None
197197

198-
# Check email verification requirement for invitee
198+
# Check email verification requirement for invitee (only for self-registered users;
199+
# admin-created users are implicitly verified since an admin vouched for them)
199200
if getattr(settings, "require_email_verification_for_invites", True):
200201
invitee = self.db.query(EmailUser).filter(EmailUser.email == email).first()
201-
if invitee and not invitee.email_verified_at:
202+
if invitee and invitee.auth_provider == "local" and not invitee.email_verified_at:
202203
raise ValueError("Invitee email address has not been verified")
203204

204205
# Check if inviter is a member of the team with appropriate permissions
@@ -324,9 +325,10 @@ async def accept_invitation(self, token: str, accepting_user_email: Optional[str
324325
logger.warning(f"User {accepting_user_email} not found")
325326
raise ValueError("User account not found")
326327

327-
# Check email verification at accept-time
328+
# Check email verification at accept-time (only for self-registered users;
329+
# admin-created users are implicitly verified since an admin vouched for them)
328330
if getattr(settings, "require_email_verification_for_invites", True):
329-
if not user.email_verified_at:
331+
if user.auth_provider == "local" and not user.email_verified_at:
330332
raise ValueError("Email address has not been verified")
331333

332334
# Check if team still exists

tests/unit/mcpgateway/test_team_governance_flags.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ async def test_require_email_verification_for_invites(self):
353353
mock_invitee = MagicMock(spec=EmailUser)
354354
mock_invitee.email = "invitee@example.com"
355355
mock_invitee.email_verified_at = None # Not verified
356+
mock_invitee.auth_provider = "local" # Self-registered user
356357

357358
mock_inviter_membership = MagicMock(spec=EmailTeamMember)
358359
mock_inviter_membership.role = "owner"
@@ -488,6 +489,7 @@ async def test_accept_invitation_unverified_email_rejected(self):
488489
mock_user = MagicMock(spec=EmailUser)
489490
mock_user.email = "user@example.com"
490491
mock_user.email_verified_at = None # Not verified
492+
mock_user.auth_provider = "local" # Self-registered user
491493
db.query.return_value.filter.return_value.first.return_value = mock_user
492494

493495
with patch("mcpgateway.services.team_invitation_service.settings") as mock_settings:

0 commit comments

Comments
 (0)