Spaces:
Sleeping
Sleeping
| title: Boardgames Tracker | |
| emoji: π | |
| colorFrom: red | |
| colorTo: red | |
| sdk: docker | |
| app_port: 8501 | |
| tags: | |
| - streamlit | |
| pinned: false | |
| short_description: Streamlit template space | |
| license: mit | |
| # Welcome to Streamlit! | |
| Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart: | |
| If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community | |
| forums](https://discuss.streamlit.io). | |
| ## Persistence setup (Google login + Sheets) | |
| The app works fully anonymously with no setup. Logging in with Google (button | |
| in the sidebar) additionally persists your players and round history to a | |
| Google Sheet -- created directly in **your own Drive**, owned by you. | |
| There's no service account and no shared storage involved: Drive access uses | |
| the `drive.file` scope, so the app can only create/edit the one spreadsheet | |
| it creates for you (`BoardGameTracker - {your email}`), nothing else in your | |
| Drive. Because it's created by your own account, it uses your own storage -- | |
| there's no "service account has 0 storage quota" issue to work around. | |
| Trade-off: since the app stays in Google's "Testing" publishing status | |
| (no Google security review needed for a small friend group), each person who | |
| connects Drive must first be added as a **test user** in the OAuth consent | |
| screen, and will see an "unverified app" warning the first time they | |
| click through. | |
| **Login vs. Drive access are two separate steps.** `st.login()` (sidebar | |
| button) only ever handles identity -- it deliberately never exposes a | |
| `refresh_token` to app code, no matter what scope you ask for. So once | |
| logged in, a separate one-time **Connect Google Drive** button appears in the | |
| sidebar; clicking it runs its own OAuth exchange (`access_type=offline` + | |
| `prompt=consent`) to force Google to hand back a `refresh_token`, which gets | |
| encrypted and stored in a long-lived browser cookie (via | |
| `extra-streamlit-components`' `CookieManager`, since Streamlit page code has | |
| no direct access to set real HttpOnly cookies). From then on, on that same | |
| browser, the app silently mints a fresh access token from the stored | |
| refresh_token -- no repeat consent screen -- until it's revoked (e.g. you | |
| remove the app's access in your Google account) or the browser's cookies are | |
| cleared. This is per-browser/device, not per-account centrally: a new device | |
| or browser needs to click Connect Drive once, same as the very first time. | |
| The cookie only ever holds the encrypted blob; the decryption key lives | |
| purely in server-side secrets and never reaches the browser. | |
| ### One-time Google Cloud setup | |
| 1. Create an OAuth 2.0 **Web application** Client ID; add your app's URL(s) | |
| **with the `/oauth2callback` path** as authorized redirect URIs (e.g. | |
| `http://localhost:8501/oauth2callback` for local dev, and | |
| `https://<your-space>.hf.space/oauth2callback` for a deployed Space). | |
| Streamlit only processes the OAuth code exchange at that exact path -- | |
| a bare app URL without `/oauth2callback` will silently fail to complete | |
| login (no error, but `st.user.is_logged_in` never becomes `True`). | |
| 2. On the OAuth consent screen, add every player's Google account email | |
| under **Test users** (keep publishing status as "Testing" -- this avoids | |
| Google's verification review entirely for `drive.file`, a scope that | |
| only grants access to files the app itself creates). | |
| 3. No service account, no Sheets/Drive API project-level enablement, no | |
| index spreadsheet -- the app's own OAuth client handles everything. | |
| ### Local dev β `.streamlit/secrets.toml` (gitignored, never commit this) | |
| ```toml | |
| [auth] | |
| redirect_uri = "http://localhost:8501/oauth2callback" | |
| cookie_secret = "<random string>" | |
| client_id = "<oauth client id>" | |
| client_secret = "<oauth client secret>" | |
| server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration" | |
| [cookies] | |
| encryption_key = "<Fernet.generate_key() output>" | |
| ``` | |
| ### Hugging Face Space deploy | |
| `st.login()` only reads `st.secrets`, so `src/bootstrap_secrets.py` assembles | |
| `.streamlit/secrets.toml` at startup from individual Space secrets (Settings β | |
| Variables and secrets), so nothing sensitive needs to live in the repo or the | |
| image: `AUTH_CLIENT_ID`, `AUTH_CLIENT_SECRET`, `AUTH_COOKIE_SECRET`, | |
| `AUTH_REDIRECT_URI` (your Space's public URL **with `/oauth2callback` | |
| appended**, e.g. `https://randomcatlover-boardgames-tracker.hf.space/oauth2callback`), | |
| `COOKIE_ENCRYPTION_KEY`. | |