RandomCatLover Claude Sonnet 5 commited on
Commit
2f83d3e
·
1 Parent(s): 68e5370

Wire app.py/secrets/requirements to the new cookie-based token store

Browse files

Completes the previous commit -- these didn't get staged there because
git add aborted when one of the paths in that batch (the deleted
token_store.py) no longer existed. app.py now imports
cookie_token_store instead of token_store; secrets.toml/bootstrap_secrets.py
use a [cookies] section instead of [supabase]; requirements.txt swaps
psycopg2-binary for extra-streamlit-components.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Files changed (4) hide show
  1. README.md +12 -12
  2. app.py +6 -6
  3. requirements.txt +1 -1
  4. src/bootstrap_secrets.py +2 -3
README.md CHANGED
@@ -43,11 +43,16 @@ button) only ever handles identity -- it deliberately never exposes a
43
  logged in, a separate one-time **Connect Google Drive** button appears in the
44
  sidebar; clicking it runs its own OAuth exchange (`access_type=offline` +
45
  `prompt=consent`) to force Google to hand back a `refresh_token`, which gets
46
- encrypted and stored in a Supabase Postgres table keyed by email. From then
47
- on -- even after closing the browser, or logging in from a different device
48
- -- the app silently mints a fresh access token from that stored refresh_token
49
- on every login; the Connect Drive button only reappears if that stored token
50
- is ever revoked (e.g. you remove the app's access in your Google account).
 
 
 
 
 
51
 
52
  ### One-time Google Cloud setup
53
  1. Create an OAuth 2.0 **Web application** Client ID; add your app's URL(s)
@@ -73,14 +78,9 @@ client_id = "<oauth client id>"
73
  client_secret = "<oauth client secret>"
74
  server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration"
75
 
76
- [supabase]
77
- db_url = "postgresql://..." # Supabase connection string
78
  encryption_key = "<Fernet.generate_key() output>"
79
  ```
80
- The `[supabase]` table (`user_refresh_tokens`) is created automatically on
81
- first use -- no manual migration needed. Rotate the DB password in Supabase's
82
- dashboard if it's ever been pasted somewhere outside a secrets file (e.g.
83
- chat, a ticket, a terminal history you don't control).
84
 
85
  ### Hugging Face Space deploy
86
  `st.login()` only reads `st.secrets`, so `src/bootstrap_secrets.py` assembles
@@ -89,4 +89,4 @@ Variables and secrets), so nothing sensitive needs to live in the repo or the
89
  image: `AUTH_CLIENT_ID`, `AUTH_CLIENT_SECRET`, `AUTH_COOKIE_SECRET`,
90
  `AUTH_REDIRECT_URI` (your Space's public URL **with `/oauth2callback`
91
  appended**, e.g. `https://randomcatlover-boardgames-tracker.hf.space/oauth2callback`),
92
- `SUPABASE_DB_URL`, `SUPABASE_ENCRYPTION_KEY`.
 
43
  logged in, a separate one-time **Connect Google Drive** button appears in the
44
  sidebar; clicking it runs its own OAuth exchange (`access_type=offline` +
45
  `prompt=consent`) to force Google to hand back a `refresh_token`, which gets
46
+ encrypted and stored in a long-lived browser cookie (via
47
+ `extra-streamlit-components`' `CookieManager`, since Streamlit page code has
48
+ no direct access to set real HttpOnly cookies). From then on, on that same
49
+ browser, the app silently mints a fresh access token from the stored
50
+ refresh_token -- no repeat consent screen -- until it's revoked (e.g. you
51
+ remove the app's access in your Google account) or the browser's cookies are
52
+ cleared. This is per-browser/device, not per-account centrally: a new device
53
+ or browser needs to click Connect Drive once, same as the very first time.
54
+ The cookie only ever holds the encrypted blob; the decryption key lives
55
+ purely in server-side secrets and never reaches the browser.
56
 
57
  ### One-time Google Cloud setup
58
  1. Create an OAuth 2.0 **Web application** Client ID; add your app's URL(s)
 
78
  client_secret = "<oauth client secret>"
79
  server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration"
80
 
81
+ [cookies]
 
82
  encryption_key = "<Fernet.generate_key() output>"
83
  ```
 
 
 
 
84
 
85
  ### Hugging Face Space deploy
86
  `st.login()` only reads `st.secrets`, so `src/bootstrap_secrets.py` assembles
 
89
  image: `AUTH_CLIENT_ID`, `AUTH_CLIENT_SECRET`, `AUTH_COOKIE_SECRET`,
90
  `AUTH_REDIRECT_URI` (your Space's public URL **with `/oauth2callback`
91
  appended**, e.g. `https://randomcatlover-boardgames-tracker.hf.space/oauth2callback`),
92
+ `COOKIE_ENCRYPTION_KEY`.
app.py CHANGED
@@ -11,8 +11,8 @@ ensure_secrets_file()
11
 
12
  import streamlit as st
13
 
 
14
  import google_oauth
15
- import token_store
16
  from analytics import inject_umami
17
 
18
  st.set_page_config(page_title="Board Game Tracker", layout="wide")
@@ -64,8 +64,8 @@ def _handle_drive_oauth_callback() -> None:
64
  return
65
 
66
  refresh_token = tokens.get("refresh_token")
67
- if refresh_token and "supabase" in st.secrets:
68
- token_store.save_refresh_token(st.user.email, refresh_token)
69
  _store_access_token(tokens)
70
  st.query_params.clear()
71
  st.rerun()
@@ -79,8 +79,8 @@ def _ensure_drive_access() -> None:
79
  if not st.user.is_logged_in or _access_token_valid():
80
  return
81
 
82
- if "supabase" in st.secrets:
83
- refresh_token = token_store.get_refresh_token(st.user.email)
84
  if refresh_token:
85
  try:
86
  tokens = google_oauth.refresh_access_token(
@@ -91,7 +91,7 @@ def _ensure_drive_access() -> None:
91
  _store_access_token(tokens)
92
  return
93
  except Exception:
94
- token_store.delete_refresh_token(st.user.email)
95
 
96
  state = uuid.uuid4().hex
97
  st.session_state["drive_oauth_state"] = state
 
11
 
12
  import streamlit as st
13
 
14
+ import cookie_token_store
15
  import google_oauth
 
16
  from analytics import inject_umami
17
 
18
  st.set_page_config(page_title="Board Game Tracker", layout="wide")
 
64
  return
65
 
66
  refresh_token = tokens.get("refresh_token")
67
+ if refresh_token and "cookies" in st.secrets:
68
+ cookie_token_store.save_refresh_token(st.user.email, refresh_token)
69
  _store_access_token(tokens)
70
  st.query_params.clear()
71
  st.rerun()
 
79
  if not st.user.is_logged_in or _access_token_valid():
80
  return
81
 
82
+ if "cookies" in st.secrets:
83
+ refresh_token = cookie_token_store.get_refresh_token(st.user.email)
84
  if refresh_token:
85
  try:
86
  tokens = google_oauth.refresh_access_token(
 
91
  _store_access_token(tokens)
92
  return
93
  except Exception:
94
+ cookie_token_store.delete_refresh_token(st.user.email)
95
 
96
  state = uuid.uuid4().hex
97
  st.session_state["drive_oauth_state"] = state
requirements.txt CHANGED
@@ -6,6 +6,6 @@ google-auth
6
  Authlib
7
  httpx
8
  tomli_w
9
- psycopg2-binary
10
  cryptography
11
  requests
 
 
6
  Authlib
7
  httpx
8
  tomli_w
 
9
  cryptography
10
  requests
11
+ extra-streamlit-components
src/bootstrap_secrets.py CHANGED
@@ -19,9 +19,8 @@ def ensure_secrets_file() -> None:
19
  "client_secret": os.environ["AUTH_CLIENT_SECRET"],
20
  "server_metadata_url": "https://accounts.google.com/.well-known/openid-configuration",
21
  },
22
- "supabase": {
23
- "db_url": os.environ["SUPABASE_DB_URL"],
24
- "encryption_key": os.environ["SUPABASE_ENCRYPTION_KEY"],
25
  },
26
  }
27
  _SECRETS_PATH.write_text(tomli_w.dumps(data))
 
19
  "client_secret": os.environ["AUTH_CLIENT_SECRET"],
20
  "server_metadata_url": "https://accounts.google.com/.well-known/openid-configuration",
21
  },
22
+ "cookies": {
23
+ "encryption_key": os.environ["COOKIE_ENCRYPTION_KEY"],
 
24
  },
25
  }
26
  _SECRETS_PATH.write_text(tomli_w.dumps(data))