wg-easy + Traefik docker-compose stack (Phase 1) plus a stdlib-only control-plane API for box registration, WireGuard peer provisioning via wg-easy, and per-box route publish/unpublish backed by Traefik's file provider (Phase 2, single-tenant mode). SQLite holds accounts/boxes/routes so a later multi-tenant shared instance is the same schema with more rows, not a reshape. wg-easy's actual REST API was verified against its source rather than assumed: it has no bearer-token auth (session-cookie login via POST /api/auth/password) and no way to accept an externally-generated public key (it always mints the keypair itself, private key included) — both corrected from the original plan during implementation.
27 lines
795 B
Python
27 lines
795 B
Python
"""Bearer-token helpers shared by accounts.py/boxes.py.
|
|
|
|
Tokens are minted as ``"<row-id>.<secret>"`` so authenticating one is an
|
|
O(1) primary-key lookup followed by a single hash comparison, rather than
|
|
scanning every row and hashing each one looking for a match.
|
|
"""
|
|
|
|
import secrets
|
|
|
|
|
|
def generate_secret() -> str:
|
|
return secrets.token_urlsafe(32)
|
|
|
|
|
|
def issue(row_id: str) -> tuple[str, str]:
|
|
"""Return ``(bearer_token_to_hand_out, secret_to_hash_and_store)``."""
|
|
secret = generate_secret()
|
|
return f"{row_id}.{secret}", secret
|
|
|
|
|
|
def split(token: str) -> tuple[str, str] | None:
|
|
if not isinstance(token, str) or "." not in token:
|
|
return None
|
|
row_id, _, secret = token.partition(".")
|
|
if not row_id or not secret:
|
|
return None
|
|
return row_id, secret
|