Fix distillation bugs: imports, auth security, and run configuration

- Fix bare imports in core/ modules to use fully-qualified paths (core.users, core.postgres)
- Fix scheduler/daemon.py importing os before use
- Fix verifyLoginToken returning truthy 401 on failure (security: invalid tokens were passing auth checks)
- Fix api/routes/example.py passing literal True as userUUID instead of decoded JWT sub
- Switch all services to python -m invocation so /app is always on sys.path
- Remove orphaned sys.path.insert hacks from bot.py, commands/example.py, routes/example.py
- Change API port mapping from 5000 to 8080
- Add config/.env and root .env for docker-compose variable substitution

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 21:26:36 -06:00
parent 11c4ff5cb7
commit f53104d947
11 changed files with 39 additions and 29 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
DB_PASS=y8Khu7pJQZq6ywFDIJiqpx4zYmclHGHw

View File

@@ -7,4 +7,4 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "api/main.py"]
CMD ["python", "-m", "api.main"]

View File

@@ -7,16 +7,22 @@ This module demonstrates:
3. Making database calls via postgres module
"""
import flask
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import flask
import jwt
import core.auth as auth
import core.postgres as postgres
def _get_user_uuid(token):
"""Decode JWT to extract user UUID. Returns None on failure."""
try:
payload = jwt.decode(token, os.getenv("JWT_SECRET"), algorithms=["HS256"])
return payload.get("sub")
except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
return None
def register(app):
"""Register routes with the Flask app."""
@@ -27,8 +33,8 @@ def register(app):
return flask.jsonify({"error": "missing token"}), 401
token = header[7:]
decoded = auth.verifyLoginToken(token, userUUID=True)
if decoded != True:
user_uuid = _get_user_uuid(token)
if not user_uuid or not auth.verifyLoginToken(token, userUUID=user_uuid):
return flask.jsonify({"error": "unauthorized"}), 401
items = postgres.select("examples")
@@ -41,8 +47,8 @@ def register(app):
return flask.jsonify({"error": "missing token"}), 401
token = header[7:]
decoded = auth.verifyLoginToken(token, userUUID=True)
if decoded != True:
user_uuid = _get_user_uuid(token)
if not user_uuid or not auth.verifyLoginToken(token, userUUID=user_uuid):
return flask.jsonify({"error": "unauthorized"}), 401
data = flask.request.get_json()

View File

@@ -18,8 +18,6 @@ import requests
import bcrypt
import pickle
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from bot.command_registry import get_handler, list_registered
import ai.parser as ai_parser

View File

@@ -7,11 +7,6 @@ This module demonstrates:
3. Making API calls
"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from bot.command_registry import register_module
import ai.parser as ai_parser

11
config/.env Normal file
View File

@@ -0,0 +1,11 @@
DISCORD_BOT_TOKEN=MTQ2NzYwMTc2ODM0NjE2MTE3Mw.G7BKQ-.kivCRj7mOl6aS5VyX4RW9hirqzm7qJ8nJOVMpE
API_URL=http://app:5000
DB_HOST=db
DB_PORT=5432
DB_NAME=app
DB_USER=app
DB_PASS=y8Khu7pJQZq6ywFDIJiqpx4zYmclHGHw
JWT_SECRET=bf773b4562221bef4d304ae5752a68931382ea3e98fe38394a098f73e0c776e1
OPENROUTER_API_KEY=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
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
AI_CONFIG_PATH=/app/ai/ai_config.json

View File

@@ -1,5 +1,5 @@
import users
import postgres
import core.users as users
import core.postgres as postgres
import bcrypt
import jwt
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
@@ -19,9 +19,9 @@ def verifyLoginToken(login_token, username=False, userUUID=False):
if decoded_token.get("sub") == str(userUUID):
return True
return False
except:
return 401
return 401
except (ExpiredSignatureError, InvalidTokenError):
return False
return False
def getUserpasswordHash(userUUID):

View File

@@ -4,7 +4,7 @@ notifications.py - Multi-channel notification routing
Supported channels: Discord webhook, ntfy
"""
import postgres
import core.postgres as postgres
import uuid
import requests
import time

View File

@@ -1,5 +1,5 @@
import uuid
import postgres
import core.postgres as postgres
import bcrypt

View File

@@ -19,7 +19,7 @@ services:
app:
build: .
ports:
- "5000:5000"
- "8080:5000"
env_file: config/.env
depends_on:
db:
@@ -27,7 +27,7 @@ services:
scheduler:
build: .
command: ["python", "scheduler/daemon.py"]
command: ["python", "-m", "scheduler.daemon"]
env_file: config/.env
depends_on:
db:
@@ -35,7 +35,7 @@ services:
bot:
build: .
command: ["python", "bot/bot.py"]
command: ["python", "-m", "bot.bot"]
env_file: config/.env
depends_on:
app:

View File

@@ -4,6 +4,7 @@ daemon.py - Background polling loop for scheduled tasks
Override poll_callback() with your domain-specific logic.
"""
import os
import time
import logging
@@ -32,6 +33,4 @@ def daemon_loop():
if __name__ == "__main__":
import os
daemon_loop()