diff --git a/api/controllers/console/error.py b/api/controllers/console/error.py index ee87138a44..ec553cf147 100644 --- a/api/controllers/console/error.py +++ b/api/controllers/console/error.py @@ -101,3 +101,15 @@ class AccountInFreezeError(BaseHTTPException): "This email account has been deleted within the past 30 days" "and is temporarily unavailable for new account registration." ) + + +class EducationVerifyLimitError(BaseHTTPException): + error_code = "education_verify_limit" + description = "Rate limit exceeded" + code = 429 + + +class EducationActivateLimitError(BaseHTTPException): + error_code = "education_activate_limit" + description = "Rate limit exceeded" + code = 429 diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index 4aa40a733a..c8fc8b52e9 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -312,7 +312,7 @@ class EducationVerifyApi(Resource): def get(self): account = current_user - return BillingService.EducationIdentity.verify(account.id) + return BillingService.EducationIdentity.verify(account.id, account.email) class EducationApi(Resource): diff --git a/api/services/billing_service.py b/api/services/billing_service.py index 4cb514616a..e2120bf7fe 100644 --- a/api/services/billing_service.py +++ b/api/services/billing_service.py @@ -5,6 +5,7 @@ import httpx from tenacity import retry, retry_if_exception_type, stop_before_delay, wait_fixed from extensions.ext_database import db +from libs.helper import RateLimiter from models.account import Account, TenantAccountJoin, TenantAccountRole @@ -93,8 +94,18 @@ class BillingService: return cls._send_request("POST", "/account/delete-feedback", json=json) class EducationIdentity: + verification_rate_limit = RateLimiter(prefix="edu_verification_rate_limit", limit=10, period=60) + activation_rate_limit = RateLimiter(prefix="edu_activation_rate_limit", limit=10, period=60) + @classmethod - def verify(cls, account_id: str): + def verify(cls, account_id: str, account_email: str): + if cls.verification_rate_limit.is_rate_limited(account_email): + from controllers.console.error import EducationVerifyLimitError + + raise EducationVerifyLimitError() + + cls.verification_rate_limit.increment_rate_limit(account_email) + params = {"account_id": account_id} return BillingService._send_request("GET", "/education/verify", params=params) @@ -105,6 +116,12 @@ class BillingService: @classmethod def activate(cls, account: Account, token: str): + if cls.activation_rate_limit.is_rate_limited(account.email): + from controllers.console.error import EducationActivateLimitError + + raise EducationActivateLimitError() + + cls.activation_rate_limit.increment_rate_limit(account.email) json = { "account_id": account.id, "email": account.email,