diff --git a/apps/api/plane/authentication/views/app/email.py b/apps/api/plane/authentication/views/app/email.py index 3d1954875c4..95d2a6c3dce 100644 --- a/apps/api/plane/authentication/views/app/email.py +++ b/apps/api/plane/authentication/views/app/email.py @@ -11,6 +11,7 @@ # Module imports from plane.authentication.provider.credentials.email import EmailProvider from plane.authentication.utils.login import user_login +from plane.authentication.rate_limit import authentication_throttle_allows from plane.license.models import Instance from plane.authentication.utils.host import base_host from plane.authentication.utils.redirection_path import get_redirection_path @@ -26,6 +27,23 @@ class SignInAuthEndpoint(View): def post(self, request): next_path = request.POST.get("next_path") + + # Rate-limit all password auth attempts before any DB access. + # authentication_throttle_allows() applies the same AuthenticationThrottle + # that magic-code views use (default: 10/minute, configurable via + # AUTHENTICATION_RATE_LIMIT env var). + if not authentication_throttle_allows(request): + exc = AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["RATE_LIMIT_EXCEEDED"], + error_message="RATE_LIMIT_EXCEEDED", + ) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=exc.get_error_dict(), + ) + return HttpResponseRedirect(url) + # Check instance configuration instance = Instance.objects.first() if instance is None or not instance.is_setup_done: @@ -135,6 +153,20 @@ def post(self, request): class SignUpAuthEndpoint(View): def post(self, request): next_path = request.POST.get("next_path") + + # Rate-limit before any DB access (same throttle as sign-in / magic-code). + if not authentication_throttle_allows(request): + exc = AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["RATE_LIMIT_EXCEEDED"], + error_message="RATE_LIMIT_EXCEEDED", + ) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_app=True), + next_path=next_path, + params=exc.get_error_dict(), + ) + return HttpResponseRedirect(url) + # Check instance configuration instance = Instance.objects.first() if instance is None or not instance.is_setup_done: diff --git a/apps/api/plane/authentication/views/space/email.py b/apps/api/plane/authentication/views/space/email.py index 827348cef23..ee6b3de0e14 100644 --- a/apps/api/plane/authentication/views/space/email.py +++ b/apps/api/plane/authentication/views/space/email.py @@ -12,6 +12,7 @@ # Module imports from plane.authentication.provider.credentials.email import EmailProvider from plane.authentication.utils.login import user_login +from plane.authentication.rate_limit import authentication_throttle_allows from plane.license.models import Instance from plane.authentication.utils.host import base_host from plane.db.models import User @@ -25,6 +26,20 @@ class SignInAuthSpaceEndpoint(View): def post(self, request): next_path = request.POST.get("next_path") + + # Rate-limit before any DB access. + if not authentication_throttle_allows(request): + exc = AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["RATE_LIMIT_EXCEEDED"], + error_message="RATE_LIMIT_EXCEEDED", + ) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=exc.get_error_dict(), + ) + return HttpResponseRedirect(url) + # Check instance configuration instance = Instance.objects.first() if instance is None or not instance.is_setup_done: @@ -110,6 +125,20 @@ def post(self, request): class SignUpAuthSpaceEndpoint(View): def post(self, request): next_path = request.POST.get("next_path") + + # Rate-limit before any DB access. + if not authentication_throttle_allows(request): + exc = AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["RATE_LIMIT_EXCEEDED"], + error_message="RATE_LIMIT_EXCEEDED", + ) + url = get_safe_redirect_url( + base_url=base_host(request=request, is_space=True), + next_path=next_path, + params=exc.get_error_dict(), + ) + return HttpResponseRedirect(url) + # Check instance configuration instance = Instance.objects.first() if instance is None or not instance.is_setup_done: