Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
INSTALL_FOLDER = '/path/to/installation'
SAMPLE_REPOSITORY = '/path/to/samples'
SESSION_COOKIE_PATH = '/'
# Where the web console is served, if one is. Password reset links point
# there when it is set, and at these pages when it is not.
CONSOLE_URL = ''
FTP_PORT = 21
MAX_CONTENT_LENGTH = 512 * 1024 * 1024
MIN_PWD_LEN = 10
Expand Down
1 change: 1 addition & 0 deletions mod_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@
from mod_api.routes import runs as runs_routes # noqa: E402, F401
from mod_api.routes import samples as samples_routes # noqa: E402, F401
from mod_api.routes import system as system_routes # noqa: E402, F401
from mod_api.routes import uploads as uploads_routes # noqa: E402, F401
5 changes: 5 additions & 0 deletions mod_api/middleware/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
_PUBLIC_ENDPOINTS = frozenset([
'api.create_token', # POST /auth/tokens (uses email/password body)
'api.system_health', # GET /system/health (uptime monitoring)
# Account recovery: nobody holds a token at this point, which is the
# whole reason for asking. These are limited per IP instead.
'api.signup',
'api.request_password_reset',
'api.complete_password_reset',
])


Expand Down
15 changes: 13 additions & 2 deletions mod_api/middleware/rate_limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,20 @@ def _get_client_ip():
return request.remote_addr


# Endpoints reached without a token, so there is nothing to key on but the
# address. All of them either hand out credentials or send mail to an address
# the caller names, which is worth rationing tightly.
_UNAUTHENTICATED_ENDPOINTS = frozenset([
'api.create_token',
'api.signup',
'api.request_password_reset',
'api.complete_password_reset',
])


def _get_rate_limit_key():
"""Build the rate-limit bucket key for this request."""
if request.endpoint == 'api.create_token':
if request.endpoint in _UNAUTHENTICATED_ENDPOINTS:
return f'ip:{_get_client_ip()}'
token = getattr(g, 'api_token', None)
if token:
Expand All @@ -72,7 +83,7 @@ def _get_rate_limit_key():

def _get_limits():
"""Return (max_requests, window_seconds) for the current endpoint."""
if request.endpoint == 'api.create_token':
if request.endpoint in _UNAUTHENTICATED_ENDPOINTS:
return 5, 900
if request.method in ('POST', 'DELETE', 'PUT', 'PATCH'):
return 20, 60
Expand Down
Loading
Loading