snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
This commit is contained in:
109
apps/api/plane/authentication/rate_limit.py
Normal file
109
apps/api/plane/authentication/rate_limit.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# See the LICENSE file for details.
|
||||
|
||||
# Python imports
|
||||
import os
|
||||
from functools import wraps
|
||||
|
||||
# Third party imports
|
||||
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
# Django imports
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
# Module imports
|
||||
from plane.authentication.adapter.error import (
|
||||
AuthenticationException,
|
||||
AUTHENTICATION_ERROR_CODES,
|
||||
)
|
||||
from plane.authentication.utils.host import base_host
|
||||
from plane.utils.path_validator import get_safe_redirect_url
|
||||
|
||||
|
||||
class AuthenticationThrottle(AnonRateThrottle):
|
||||
# Rate is configurable per-deployment via the AUTHENTICATION_RATE_LIMIT
|
||||
# env var (DRF format: "<num>/<period>" where period is second/minute/hour/day).
|
||||
rate = os.environ.get("AUTHENTICATION_RATE_LIMIT", "10/minute")
|
||||
scope = "authentication"
|
||||
|
||||
def throttle_failure_view(self, request, *args, **kwargs):
|
||||
try:
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["RATE_LIMIT_EXCEEDED"],
|
||||
error_message="RATE_LIMIT_EXCEEDED",
|
||||
)
|
||||
except AuthenticationException as e:
|
||||
return Response(e.get_error_dict(), status=status.HTTP_429_TOO_MANY_REQUESTS)
|
||||
|
||||
|
||||
def authentication_throttle_allows(request):
|
||||
"""
|
||||
Apply AuthenticationThrottle to a plain django.views.View request.
|
||||
|
||||
DRF's throttle_classes only run inside APIView.initial(); the magic
|
||||
sign-in / sign-up endpoints extend django.views.View to return
|
||||
HttpResponseRedirect from a form POST flow, so they need a manual
|
||||
throttle check. Returns True if the request is allowed through,
|
||||
False if it should be rejected with a RATE_LIMIT_EXCEEDED error.
|
||||
"""
|
||||
throttle = AuthenticationThrottle()
|
||||
# SimpleRateThrottle.allow_request only reads request.META and
|
||||
# request.user, both available on a plain Django HttpRequest.
|
||||
return throttle.allow_request(request, None)
|
||||
|
||||
|
||||
def throttle_auth_redirect(*, is_app=False, is_space=False):
|
||||
"""
|
||||
Decorator for redirect-flow ``django.views.View`` POST handlers.
|
||||
|
||||
Applies AuthenticationThrottle before the wrapped handler runs; when the
|
||||
per-IP budget is exceeded it short-circuits with a RATE_LIMIT_EXCEEDED
|
||||
redirect to the auth error page (the same behaviour every throttled auth
|
||||
endpoint uses). The throttle runs before any DB access in the handler, so
|
||||
brute-force traffic is rejected without touching the database.
|
||||
|
||||
Pass ``is_app=True`` for /app auth views and ``is_space=True`` for /spaces
|
||||
auth views so the redirect targets the correct base host.
|
||||
"""
|
||||
|
||||
def decorator(post_method):
|
||||
@wraps(post_method)
|
||||
def wrapper(self, request, *args, **kwargs):
|
||||
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=is_app, is_space=is_space),
|
||||
next_path=request.POST.get("next_path"),
|
||||
params=exc.get_error_dict(),
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
return post_method(self, request, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class EmailVerificationThrottle(UserRateThrottle):
|
||||
"""
|
||||
Throttle for email verification code generation.
|
||||
Limits to 3 requests per hour per user to prevent abuse.
|
||||
"""
|
||||
|
||||
rate = "3/hour"
|
||||
scope = "email_verification"
|
||||
|
||||
def throttle_failure_view(self, request, *args, **kwargs):
|
||||
try:
|
||||
raise AuthenticationException(
|
||||
error_code=AUTHENTICATION_ERROR_CODES["RATE_LIMIT_EXCEEDED"],
|
||||
error_message="RATE_LIMIT_EXCEEDED",
|
||||
)
|
||||
except AuthenticationException as e:
|
||||
return Response(e.get_error_dict(), status=status.HTTP_429_TOO_MANY_REQUESTS)
|
||||
Reference in New Issue
Block a user