diff --git a/bincio/serve/cli.py b/bincio/serve/cli.py index 085ce0b..ae07a44 100644 --- a/bincio/serve/cli.py +++ b/bincio/serve/cli.py @@ -19,9 +19,10 @@ console = Console() @click.option("--strava-client-id", default=None, envvar="STRAVA_CLIENT_ID", help="Strava OAuth client ID (enables per-user Strava sync)") @click.option("--strava-client-secret", default=None, envvar="STRAVA_CLIENT_SECRET", help="Strava OAuth client secret") @click.option("--max-users", default=None, type=int, help="Override max users for this instance (0 = unlimited; updates the DB setting)") +@click.option("--public-url", default=None, envvar="PUBLIC_URL", help="Public base URL (e.g. https://yourdomain.com). Required for Strava OAuth to work behind a reverse proxy.") 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]) -> None: + max_users: Optional[int], public_url: Optional[str]) -> None: """Start the bincio multi-user application server. Handles auth, user management, and write operations. @@ -51,6 +52,8 @@ def serve(data_dir: str, site_dir: Optional[str], host: str, port: int, srv.strava_client_id = strava_client_id if strava_client_secret: srv.strava_client_secret = strava_client_secret + if public_url: + srv.public_url = public_url db = open_db(dd) current_limit = get_setting(db, "max_users") diff --git a/bincio/serve/server.py b/bincio/serve/server.py index 0b23d83..9b8ab9f 100644 --- a/bincio/serve/server.py +++ b/bincio/serve/server.py @@ -48,6 +48,7 @@ data_dir: Path | None = None site_dir: Path | None = None # for post-write rebuild trigger strava_client_id: str = "" strava_client_secret: str = "" +public_url: str = "" # e.g. "https://yourdomain.com" — used for OAuth redirect URIs _db = None # sqlite3.Connection, opened lazily @@ -674,7 +675,10 @@ async def strava_auth_url(request: Request, bincio_session: Optional[str] = Cook raise HTTPException(400, "Strava client ID not configured on this server") state = secrets.token_urlsafe(16) _strava_oauth_states.add(state) - redirect_uri = str(request.url_for("strava_callback")) + if public_url: + redirect_uri = public_url.rstrip("/") + "/api/strava/callback" + else: + redirect_uri = str(request.url_for("strava_callback")) from bincio.extract.strava_api import auth_url return JSONResponse({"url": auth_url(strava_client_id, redirect_uri, state=state)}) @@ -687,7 +691,7 @@ async def strava_callback( state: str = "", bincio_session: Optional[str] = Cookie(default=None), ) -> RedirectResponse: - site_origin = str(request.base_url).rstrip("/") + site_origin = public_url.rstrip("/") if public_url else str(request.base_url).rstrip("/") if error or not code: return RedirectResponse(f"{site_origin}/?strava=error") if state not in _strava_oauth_states: diff --git a/docs/deployment/vps.md b/docs/deployment/vps.md index 5b79f92..4c1e905 100644 --- a/docs/deployment/vps.md +++ b/docs/deployment/vps.md @@ -114,7 +114,8 @@ ExecStart=/root/.local/bin/uv run bincio serve \ --data-dir /var/bincio/data \ --site-dir /opt/bincio/site \ --host 127.0.0.1 \ - --port 4041 + --port 4041 \ + --public-url https://yourdomain.com EnvironmentFile=/etc/bincio/secrets.env Restart=always RestartSec=5