Skip to content
Open
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
24 changes: 20 additions & 4 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import logging
import threading
import time
import hmac
import secrets
from dotenv import load_dotenv

# Load environment variables
Expand Down Expand Up @@ -47,15 +49,27 @@ def clear(self):

# Initialize Flask app
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv("FLASK_SECRET_KEY", "default-dev-key")
app.config['SECRET_KEY'] = os.getenv("FLASK_SECRET_KEY") or secrets.token_hex(32)

# Basic Auth
# Basic Auth. No hardcoded fallback password: anyone can read this repo, so a
# default here is a public credential for every deployment that forgets to set
# one. When ADMIN_PASS is unset, generate a random one for this run and print
# it once in the startup log so the operator can still log in locally.
ADMIN_USER = os.getenv("ADMIN_USER", "admin")
ADMIN_PASS = os.getenv("ADMIN_PASS", "polymarket2024")
ADMIN_PASS = os.getenv("ADMIN_PASS")
if not ADMIN_PASS:
ADMIN_PASS = secrets.token_urlsafe(18)
logger.warning(
"ADMIN_PASS is not set. Generated a random dashboard password for "
f"this run: {ADMIN_PASS} (set ADMIN_PASS in .env to choose your own)"
)


def check_auth(username, password):
return username == ADMIN_USER and password == ADMIN_PASS
return (
hmac.compare_digest(username or "", ADMIN_USER)
and hmac.compare_digest(password or "", ADMIN_PASS)
)


def authenticate():
Expand Down Expand Up @@ -560,6 +574,7 @@ def api_status():


@app.route('/api/analyze')
@requires_auth
def api_analyze():
"""API endpoint to run AI analysis"""
if not analyzer:
Expand Down Expand Up @@ -702,6 +717,7 @@ def api_positions():


@app.route('/api/add_order', methods=['POST'])
@requires_auth
def api_add_order():
"""Manually add an order to track (for orders placed before persistence was added)"""
try:
Expand Down
13 changes: 11 additions & 2 deletions deploy-tokyo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,18 @@ echo "Deploying to Cloud Run..."
# Build env vars string
ENV_VARS="BLOCKRUN_WALLET_KEY=$BLOCKRUN_WALLET_KEY"
ENV_VARS="$ENV_VARS,POLYGON_WALLET_PRIVATE_KEY=$POLYGON_WALLET_PRIVATE_KEY"
ENV_VARS="$ENV_VARS,FLASK_SECRET_KEY=${FLASK_SECRET_KEY:-polymarket-agent-secret}"
if [ -z "$FLASK_SECRET_KEY" ]; then
FLASK_SECRET_KEY=$(openssl rand -hex 32)
echo "FLASK_SECRET_KEY not set, generated one for this deploy."
fi
if [ -z "$ADMIN_PASS" ]; then
ADMIN_PASS=$(openssl rand -hex 12)
echo "ADMIN_PASS not set, generated dashboard password: $ADMIN_PASS"
echo "Save it now. Set ADMIN_PASS in .env to choose your own."
fi
ENV_VARS="$ENV_VARS,FLASK_SECRET_KEY=$FLASK_SECRET_KEY"
ENV_VARS="$ENV_VARS,ADMIN_USER=${ADMIN_USER:-admin}"
ENV_VARS="$ENV_VARS,ADMIN_PASS=${ADMIN_PASS:-polymarket2024}"
ENV_VARS="$ENV_VARS,ADMIN_PASS=$ADMIN_PASS"

# Add Polymarket API credentials if available (recommended for production)
if [ -n "$POLYMARKET_API_KEY" ]; then
Expand Down
62 changes: 62 additions & 0 deletions tests/test_no_default_credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Regression test: the dashboard must not fall back to known credentials.

Run: python -m pytest tests/test_no_default_credentials.py
Needs: flask, python-dotenv (already in requirements.txt).

The app module is imported in a fresh subprocess with ADMIN_PASS and
FLASK_SECRET_KEY removed from the environment. The trading/AI modules are
stubbed so the import needs no wallet keys and no network.

On the old code this fails: ADMIN_PASS came out as "polymarket2024" and
check_auth("admin", "polymarket2024") returned True.
"""
import os
import subprocess
import sys
import textwrap

CHILD = textwrap.dedent(
"""
import sys, types
from unittest.mock import MagicMock

for name in [
"src", "src.market", "src.market.polymarket",
"src.analysis", "src.analysis.ai_analyzer",
"src.trading", "src.trading.wallet", "src.trading.executor",
"src.storage",
]:
sys.modules.setdefault(name, types.ModuleType(name))
sys.modules["src.market.polymarket"].fetch_active_markets = lambda **kw: []
sys.modules["src.analysis.ai_analyzer"].get_analyzer = lambda: None
sys.modules["src.trading.wallet"].get_wallet = lambda: None
sys.modules["src.trading.executor"].get_executor = lambda: None
sys.modules["src.storage"].get_storage = lambda: None

import app

assert app.ADMIN_PASS not in ("", "polymarket2024"), app.ADMIN_PASS
assert app.app.config["SECRET_KEY"] != "default-dev-key"
assert app.check_auth("admin", "polymarket2024") is False
assert app.check_auth(app.ADMIN_USER, app.ADMIN_PASS) is True
assert app.check_auth(app.ADMIN_USER, "wrong-password") is False

client = app.app.test_client()
# Paid/write endpoints must reject requests with no credentials.
assert client.post("/api/add_order", json={}).status_code == 401
assert client.get("/api/analyze").status_code == 401
print("ok")
"""
)


def test_no_default_credentials():
env = {k: v for k, v in os.environ.items()
if k not in ("ADMIN_PASS", "FLASK_SECRET_KEY", "ADMIN_USER")}
result = subprocess.run(
[sys.executable, "-c", CHILD],
capture_output=True, text=True, env=env,
cwd=os.path.join(os.path.dirname(__file__), ".."),
)
assert result.returncode == 0, result.stderr
assert "ok" in result.stdout