diff --git a/backend/app/api/enterprise.py b/backend/app/api/enterprise.py index 715adf25f..b886ba81c 100644 --- a/backend/app/api/enterprise.py +++ b/backend/app/api/enterprise.py @@ -1590,6 +1590,23 @@ def _require_tenant_admin(current_user: User) -> None: raise HTTPException(status_code=400, detail="No company assigned") +async def _ensure_invitation_email_enabled(db: AsyncSession) -> None: + """Require enabled system email before accepting email invitations.""" + from app.services.system_email_service import resolve_email_config_async + + if await resolve_email_config_async(db): + return + if await resolve_email_config_async(db, include_disabled=True): + raise HTTPException( + status_code=400, + detail="System email SMTP is configured but disabled. Enable system email before sending invitations.", + ) + raise HTTPException( + status_code=400, + detail="System email SMTP settings are not configured. Configure system email before sending invitations.", + ) + + @router.post("/invitation-codes") async def create_invitation_codes( data: InvitationCodeCreate, @@ -1641,6 +1658,8 @@ async def invite_users( if not tenant: raise HTTPException(status_code=404, detail="Company not found") + await _ensure_invitation_email_enabled(db) + base_url = await platform_service.get_public_base_url(db, request=request) invited_count = 0 diff --git a/backend/tests/test_enterprise_invites.py b/backend/tests/test_enterprise_invites.py new file mode 100644 index 000000000..99e069fb8 --- /dev/null +++ b/backend/tests/test_enterprise_invites.py @@ -0,0 +1,59 @@ +import pytest +from fastapi import HTTPException + +from app.api import enterprise as enterprise_api +from app.services.system_email_service import SystemEmailConfig + + +@pytest.mark.asyncio +async def test_invitation_email_preflight_rejects_disabled_system_email(monkeypatch): + calls = [] + + async def fake_resolve_email_config_async(_db, *, include_disabled: bool = False): + calls.append(include_disabled) + if include_disabled: + return SystemEmailConfig( + from_address="bot@example.com", + from_name="Clawith", + smtp_host="smtp.example.com", + smtp_port=465, + smtp_username="bot@example.com", + smtp_password="secret", + smtp_ssl=True, + smtp_timeout_seconds=15, + ) + return None + + monkeypatch.setattr( + "app.services.system_email_service.resolve_email_config_async", + fake_resolve_email_config_async, + ) + + with pytest.raises(HTTPException) as excinfo: + await enterprise_api._ensure_invitation_email_enabled(object()) + + assert excinfo.value.status_code == 400 + assert "disabled" in excinfo.value.detail + assert calls == [False, True] + + +@pytest.mark.asyncio +async def test_invitation_email_preflight_accepts_enabled_system_email(monkeypatch): + async def fake_resolve_email_config_async(_db, *, include_disabled: bool = False): + return SystemEmailConfig( + from_address="bot@example.com", + from_name="Clawith", + smtp_host="smtp.example.com", + smtp_port=465, + smtp_username="bot@example.com", + smtp_password="secret", + smtp_ssl=True, + smtp_timeout_seconds=15, + ) + + monkeypatch.setattr( + "app.services.system_email_service.resolve_email_config_async", + fake_resolve_email_config_async, + ) + + await enterprise_api._ensure_invitation_email_enabled(object())