feat(org): add organization limit per user

Add 10 organization limit per user to prevent abuse. Includes
graceful fallback if count service is unavailable.

- Add get_user_org_count method to OrganizationService
- Check org count before allowing new organization creation
- Improve invite email mismatch error message for logged-in users
This commit is contained in:
2026-04-20 15:04:44 +09:30
parent 69f39dfa04
commit b2c2acc84f
3 changed files with 27 additions and 1 deletions
@@ -1,4 +1,5 @@
"""Organization core CRUD endpoints."""
import logging
from flask import g, request
from marshmallow import ValidationError
from gatehouse_app.api.v1 import api_v1_bp
@@ -12,6 +13,15 @@ from gatehouse_app.services.organization_service import OrganizationService
@login_required
@full_access_required
def create_organization():
try:
org_count = OrganizationService.get_user_org_count(g.current_user.id)
if org_count is not None and org_count >= 10:
return api_response(success=False, message="You cannot belong to more than 10 organizations", status=400, error_type="ORG_LIMIT_REACHED")
except Exception as e:
logger = logging.getLogger(__name__)
logger.warning(f"[Org] Failed to check org count for user {g.current_user.id}: {e}")
# Fail open to avoid blocking legitimate users when the count service is unavailable
try:
schema = OrganizationCreateSchema()
data = schema.load(request.json)
@@ -173,7 +173,7 @@ def accept_invite(token):
if session_user.email.lower() != invite.email.lower():
return api_response(
success=False,
message="This invite was sent to a different email address.",
message="You are already logged in and this invite was sent to a different email address.",
status=403,
error_type="EMAIL_MISMATCH",
)
@@ -70,6 +70,22 @@ class OrganizationService:
return org
@staticmethod
def get_user_org_count(user_id):
"""
Get the count of organizations a user belongs to.
Args:
user_id: User ID
Returns:
Count of active memberships (deleted_at is NULL)
"""
return OrganizationMember.query.filter_by(
user_id=user_id,
deleted_at=None,
).count()
@staticmethod
def get_organization_by_id(org_id):
"""