Skip to content

Commit 9a25ec0

Browse files
gkorlandclaudeCopilot
authored
fix: require SECRET_TOKEN at startup to prevent auth bypass (#476)
* fix: require SECRET_TOKEN at startup to prevent auth bypass The original verify_token() allowed None == None when SECRET_TOKEN was unset, silently disabling authentication. The server now refuses to start without SECRET_TOKEN configured, and validate_user() accepts the static token via constant-time comparison (hmac.compare_digest) as an alternative to DB-backed OAuth tokens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add pylint disable for wrong-import-position in conftest.py The imports must come after os.environ.setdefault() for SECRET_TOKEN, which is intentionally non-standard. Suppress the C0413 warning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add SECRET_TOKEN to Playwright CI workflow env The 'Start FastAPI application' step was missing SECRET_TOKEN, causing the app to crash at startup with RuntimeError since the PR made it required. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1214b33 commit 9a25ec0

4 files changed

Lines changed: 38 additions & 9 deletions

File tree

.env.example

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ FALKORDB_URL=redis://localhost:6379/0 # REQUIRED - change to your FalkorDB URL
4242
# FALKORDB_PORT=6379
4343

4444
# -----------------------------
45-
# Optional API / secret tokens
45+
# API / secret tokens
4646
# -----------------------------
47-
# API token for internal API access (optional)
48-
# SECRET_TOKEN=your_secret_token
47+
# REQUIRED: static bearer token for internal / programmatic API access.
48+
# The server refuses to start when this is missing.
49+
SECRET_TOKEN=your_secret_token
4950
# SECRET_TOKEN_ERP=your_erp_token
5051

5152
# -----------------------------

.github/workflows/playwright.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ jobs:
136136
env:
137137
PYTHONUNBUFFERED: 1
138138
FASTAPI_SECRET_KEY: test-secret-key-for-ci
139+
SECRET_TOKEN: test-secret-token-for-ci
139140
APP_ENV: development
140141
FASTAPI_DEBUG: False
141142
FALKORDB_URL: redis://localhost:6379

api/auth/user_management.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""User management and authentication functions for text2sql API."""
22

33
import base64
4+
import hmac
45
import logging
56
import os
67
import secrets
@@ -19,6 +20,16 @@
1920
"FASTAPI_SECRET_KEY not set, using generated key. Set this in production!"
2021
)
2122

23+
# Static API token for internal / programmatic access.
24+
# REQUIRED: the server refuses to start when this is missing so that
25+
# an unconfigured deployment never silently disables authentication.
26+
SECRET_TOKEN: str = os.environ.get("SECRET_TOKEN", "")
27+
if not SECRET_TOKEN:
28+
raise RuntimeError(
29+
"SECRET_TOKEN environment variable is required but not set. "
30+
"Set it to a strong, random value before starting the server."
31+
)
32+
2233

2334
class IdentityInfo(BaseModel):
2435
"""
@@ -235,11 +246,22 @@ async def validate_user(request: Request) -> Tuple[Optional[Dict[str, Any]], boo
235246
try:
236247
api_token = get_token(request)
237248

238-
if api_token:
239-
db_info = await _get_user_info(api_token)
249+
if not api_token:
250+
return None, False
251+
252+
# Accept the static SECRET_TOKEN for internal / programmatic access.
253+
# Uses constant-time comparison to prevent timing attacks.
254+
if hmac.compare_digest(api_token, SECRET_TOKEN):
255+
return {
256+
"email": "api@internal",
257+
"name": "API",
258+
"picture": None,
259+
}, True
260+
261+
db_info = await _get_user_info(api_token)
240262

241-
if db_info:
242-
return db_info, True
263+
if db_info:
264+
return db_info, True
243265

244266
return None, False
245267

tests/conftest.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import subprocess
55
import time
66

7-
import pytest
8-
import requests
7+
# SECRET_TOKEN must be set before any test imports api.index (which triggers
8+
# app creation and the startup SECRET_TOKEN requirement check).
9+
os.environ.setdefault("SECRET_TOKEN", "test-secret-token")
10+
11+
import pytest # pylint: disable=wrong-import-position
12+
import requests # pylint: disable=wrong-import-position
913

1014

1115
def pytest_configure(config):
@@ -27,6 +31,7 @@ def fastapi_app():
2731
'GOOGLE_CLIENT_SECRET': 'test-google-client-secret',
2832
'GITHUB_CLIENT_ID': 'test-github-client-id',
2933
'GITHUB_CLIENT_SECRET': 'test-github-client-secret',
34+
'SECRET_TOKEN': 'test-secret-token',
3035
'ENABLE_TEST_AUTH': 'true', # Enable test auth bypass for E2E tests
3136
}
3237
for var, default in env_defaults.items():

0 commit comments

Comments
 (0)