import hashlib import hmac import os import secrets from fastapi import Header, HTTPException, Depends from sqlalchemy import text from .db import get_db # Password hashing — stdlib PBKDF2 (no bcrypt/passlib in the image, no new dependency). def hash_password(pw: str) -> str: salt = secrets.token_bytes(16) dk = hashlib.pbkdf2_hmac("sha256", pw.encode(), salt, 200_000) return f"pbkdf2_sha256$200000${salt.hex()}${dk.hex()}" def verify_password(pw: str, stored: str | None) -> bool: if not stored: return False try: _algo, iters, salt_hex, hash_hex = stored.split("$") dk = hashlib.pbkdf2_hmac("sha256", pw.encode(), bytes.fromhex(salt_hex), int(iters)) return hmac.compare_digest(dk.hex(), hash_hex) except Exception: return False # Single-tenant v1: one store. The env token is the OWNER (always admin); staff get their own # tokens from the `staff` table with a role (admin | staff). Role gates the sensitive endpoints. STORE = os.getenv("RECORDGOD_STORE", "Monster Robot Party") PLAN = os.getenv("RECORDGOD_PLAN", "enterprise") TOKEN = os.getenv("RECORDGOD_TOKEN") # no default — unset means "deny everything", not "open" async def require_token(authorization: str | None = Header(None), db=Depends(get_db)): # Bearer per WOWPLATTER_BRIEF. Owner env-token first (no DB hit); then the staff table. if not authorization or not authorization.startswith("Bearer "): raise HTTPException(401, "missing bearer token") tok = authorization[7:].strip() base = {"store": STORE, "plan": PLAN, "store_id": 1} if TOKEN and tok == TOKEN: return {**base, "role": "admin", "name": "Owner", "staff_id": None} row = (await db.execute( text("SELECT id, name, role FROM staff WHERE token = :t AND active"), {"t": tok} )).mappings().first() if row: return {**base, "role": row["role"], "name": row["name"], "staff_id": row["id"]} raise HTTPException(401, "invalid token") async def require_admin(ident=Depends(require_token)): """Gate the sensitive surface (API keys / connections / staff admin) to admins only.""" if ident.get("role") != "admin": raise HTTPException(403, "admin only") return ident