serve: add JWT consumer shim for bincio-auth integration

When --jwt-secret / BINCIO_AUTH_JWT_SECRET is set, auth is validated
locally by decoding the bincio-auth-issued JWT — no DB session lookup.
Falls back to existing DB-based session lookup when the flag is absent,
so standalone deployments keep working without any config change.

Changes:
- deps.py: add jwt_secret global, _decode_jwt helper, wire into
  _current_user and _require_auth
- cli.py: add --jwt-secret option; log active auth mode on startup
- pyproject.toml: add PyJWT>=2.8 to serve and dev extras
This commit is contained in:
Davide Scaini
2026-06-02 14:54:43 +02:00
parent 0d6bf57932
commit 2af29a460b
3 changed files with 48 additions and 20 deletions
+16 -9
View File
@@ -3,7 +3,6 @@
from __future__ import annotations
from pathlib import Path
from typing import Optional
import click
from rich.console import Console
@@ -23,11 +22,12 @@ console = Console()
@click.option("--webroot", default=None, type=click.Path(), help="Nginx webroot (e.g. /var/www/bincio). When set, uploads trigger a full Astro build + rsync so new activity pages are immediately accessible without a git push.")
@click.option("--dem-url", default=None, envvar="DEM_URL", help="Base URL of an Open-Elevation-compatible API (default: https://api.open-elevation.com).")
@click.option("--sync-secret", default=None, envvar="BINCIO_SYNC_SECRET", help="Shared secret for POST /api/internal/rebuild (used by the sync-strava systemd timer).")
def serve(data_dir: str, site_dir: Optional[str], host: str, port: int,
strava_client_id: Optional[str], strava_client_secret: Optional[str],
max_users: Optional[int], public_url: Optional[str],
webroot: Optional[str], dem_url: Optional[str],
sync_secret: Optional[str]) -> None:
@click.option("--jwt-secret", default=None, envvar="BINCIO_AUTH_JWT_SECRET", help="Shared JWT secret from bincio-auth. When set, validates JWTs locally instead of DB session lookup.")
def serve(data_dir: str, site_dir: str | None, host: str, port: int,
strava_client_id: str | None, strava_client_secret: str | None,
max_users: int | None, public_url: str | None,
webroot: str | None, dem_url: str | None,
sync_secret: str | None, jwt_secret: str | None) -> None:
"""Start the bincio multi-user application server.
Handles auth, user management, and write operations.
@@ -36,9 +36,10 @@ def serve(data_dir: str, site_dir: Optional[str], host: str, port: int,
Requires a data directory initialised with `bincio init`.
"""
import uvicorn
import bincio.serve.server as srv
from bincio.serve import deps
from bincio.serve.db import open_db, set_setting, get_setting
from bincio.serve.db import get_setting, open_db, set_setting
dd = Path(data_dir).expanduser().resolve()
if not (dd / "instance.db").exists():
@@ -66,12 +67,14 @@ def serve(data_dir: str, site_dir: Optional[str], host: str, port: int,
deps.dem_url = dem_url
if sync_secret:
deps.sync_secret = sync_secret
if jwt_secret:
deps.jwt_secret = jwt_secret
db = open_db(dd)
current_limit = get_setting(db, "max_users")
db.close()
console.print(f"[bold]bincio serve[/bold]")
console.print("[bold]bincio serve[/bold]")
console.print(f" Data: [cyan]{dd}[/cyan]")
if deps.site_dir:
console.print(f" Site: [cyan]{deps.site_dir}[/cyan]")
@@ -81,8 +84,12 @@ def serve(data_dir: str, site_dir: Optional[str], host: str, port: int,
if current_limit and int(current_limit) > 0:
console.print(f" Users: [yellow]max {current_limit}[/yellow]")
else:
console.print(f" Users: [dim]unlimited[/dim]")
console.print(" Users: [dim]unlimited[/dim]")
console.print(f" DEM: [cyan]{deps.dem_url}[/cyan]")
if deps.jwt_secret:
console.print(" Auth: [green]JWT (bincio-auth)[/green]")
else:
console.print(" Auth: [dim]local DB sessions[/dim]")
console.print()
log_config = uvicorn.config.LOGGING_CONFIG.copy()