"""Collector (machine) authentication. Dashboard *users* are authenticated by nginx in front of the app (Microsoft 365 sign-in via oauth2-proxy), so nothing here deals with people. """ import hashlib import secrets import sqlite3 from fastapi import Depends, Header, HTTPException from .db import get_db from .timeutil import to_db, utcnow KEY_PREFIX = "bdk_" def generate_key() -> str: return KEY_PREFIX + secrets.token_urlsafe(32) def hash_key(key: str) -> str: # Keys are 256-bit random, so a fast unsalted hash is fine (no dictionary to attack). return hashlib.sha256(key.encode()).hexdigest() def require_ingest_key( authorization: str | None = Header(None), conn: sqlite3.Connection = Depends(get_db), ) -> str: scheme, _, token = (authorization or "").partition(" ") token = token.strip() if scheme.lower() != "bearer" or not token: raise HTTPException(401, "Missing bearer token", headers={"WWW-Authenticate": "Bearer"}) row = conn.execute( "SELECT id, name FROM api_keys WHERE key_hash = ? AND revoked_at IS NULL", (hash_key(token),) ).fetchone() if row is None: raise HTTPException(401, "Invalid or revoked API key", headers={"WWW-Authenticate": "Bearer"}) conn.execute("UPDATE api_keys SET last_used_at = ? WHERE id = ?", (to_db(utcnow()), row["id"])) return row["name"]