Spaces:
Sleeping
Sleeping
Commit ·
fe83ebb
1
Parent(s): 5be8e7e
Surface Drive-connection failures instead of swallowing them
Browse filesEvery failure in the Connect Drive / silent-refresh path was being
caught and turned into a generic re-show-the-button state, with no
way to tell what actually went wrong (missing refresh_token from
Google, expired/revoked token, cookie decrypt failure from a changed
encryption key). Now each failure mode shows a specific message, and
a 'Drive connection diagnostics' expander reports whether a token
cookie is present. Also switched the cookie's SameSite from this
library's default 'strict' to 'lax', matching Streamlit's own auth
cookie policy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- app.py +20 -4
- src/cookie_token_store.py +7 -1
app.py
CHANGED
|
@@ -58,13 +58,20 @@ def _handle_drive_oauth_callback(cookie_manager) -> None:
|
|
| 58 |
_drive_redirect_uri(),
|
| 59 |
code,
|
| 60 |
)
|
| 61 |
-
except Exception:
|
| 62 |
st.query_params.clear()
|
| 63 |
-
st.error("Failed to connect Google Drive
|
| 64 |
return
|
| 65 |
|
| 66 |
refresh_token = tokens.get("refresh_token")
|
| 67 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
cookie_token_store.save_refresh_token(cookie_manager, st.user.email, refresh_token)
|
| 69 |
_store_access_token(tokens)
|
| 70 |
st.query_params.clear()
|
|
@@ -90,7 +97,8 @@ def _ensure_drive_access(cookie_manager) -> None:
|
|
| 90 |
)
|
| 91 |
_store_access_token(tokens)
|
| 92 |
return
|
| 93 |
-
except Exception:
|
|
|
|
| 94 |
cookie_token_store.delete_refresh_token(cookie_manager, st.user.email)
|
| 95 |
|
| 96 |
state = uuid.uuid4().hex
|
|
@@ -100,6 +108,14 @@ def _ensure_drive_access(cookie_manager) -> None:
|
|
| 100 |
)
|
| 101 |
st.link_button("Connect Google Drive", authorize_url)
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
# Constructed at most once per script run -- see get_cookie_manager's docstring.
|
| 105 |
_cookie_manager = cookie_token_store.get_cookie_manager() if "cookies" in st.secrets else None
|
|
|
|
| 58 |
_drive_redirect_uri(),
|
| 59 |
code,
|
| 60 |
)
|
| 61 |
+
except Exception as e:
|
| 62 |
st.query_params.clear()
|
| 63 |
+
st.error(f"Failed to connect Google Drive: {e}")
|
| 64 |
return
|
| 65 |
|
| 66 |
refresh_token = tokens.get("refresh_token")
|
| 67 |
+
if not refresh_token:
|
| 68 |
+
st.warning(
|
| 69 |
+
"Google didn't return a refresh_token (this happens if the app was "
|
| 70 |
+
"already authorized without prompt=consent taking effect). "
|
| 71 |
+
"Drive access will work for this session only -- try disconnecting "
|
| 72 |
+
"the app at myaccount.google.com/permissions and reconnecting."
|
| 73 |
+
)
|
| 74 |
+
elif cookie_manager is not None:
|
| 75 |
cookie_token_store.save_refresh_token(cookie_manager, st.user.email, refresh_token)
|
| 76 |
_store_access_token(tokens)
|
| 77 |
st.query_params.clear()
|
|
|
|
| 97 |
)
|
| 98 |
_store_access_token(tokens)
|
| 99 |
return
|
| 100 |
+
except Exception as e:
|
| 101 |
+
st.warning(f"Stored Drive access expired or was revoked ({e}); reconnecting.")
|
| 102 |
cookie_token_store.delete_refresh_token(cookie_manager, st.user.email)
|
| 103 |
|
| 104 |
state = uuid.uuid4().hex
|
|
|
|
| 108 |
)
|
| 109 |
st.link_button("Connect Google Drive", authorize_url)
|
| 110 |
|
| 111 |
+
with st.expander("Drive connection diagnostics"):
|
| 112 |
+
st.write("Logged in as:", st.user.email)
|
| 113 |
+
st.write("Cookie storage configured:", cookie_manager is not None)
|
| 114 |
+
if cookie_manager is not None:
|
| 115 |
+
found = cookie_token_store.get_refresh_token(cookie_manager, st.user.email) is not None
|
| 116 |
+
st.write("Refresh token cookie present:", found)
|
| 117 |
+
st.write("Raw cookies seen by the app:", list(cookie_manager.cookies.keys()))
|
| 118 |
+
|
| 119 |
|
| 120 |
# Constructed at most once per script run -- see get_cookie_manager's docstring.
|
| 121 |
_cookie_manager = cookie_token_store.get_cookie_manager() if "cookies" in st.secrets else None
|
src/cookie_token_store.py
CHANGED
|
@@ -34,7 +34,9 @@ def save_refresh_token(cookie_manager: stx.CookieManager, email: str, refresh_to
|
|
| 34 |
encrypted = _fernet().encrypt(refresh_token.encode()).decode()
|
| 35 |
expires_at = datetime.now(timezone.utc) + timedelta(days=_COOKIE_MAX_AGE_DAYS)
|
| 36 |
name = _cookie_name(email)
|
| 37 |
-
|
|
|
|
|
|
|
| 38 |
|
| 39 |
|
| 40 |
def get_refresh_token(cookie_manager: stx.CookieManager, email: str) -> str | None:
|
|
@@ -44,6 +46,10 @@ def get_refresh_token(cookie_manager: stx.CookieManager, email: str) -> str | No
|
|
| 44 |
try:
|
| 45 |
return _fernet().decrypt(encrypted.encode()).decode()
|
| 46 |
except Exception:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
return None
|
| 48 |
|
| 49 |
|
|
|
|
| 34 |
encrypted = _fernet().encrypt(refresh_token.encode()).decode()
|
| 35 |
expires_at = datetime.now(timezone.utc) + timedelta(days=_COOKIE_MAX_AGE_DAYS)
|
| 36 |
name = _cookie_name(email)
|
| 37 |
+
# same_site="lax" (this library defaults to "strict") to match the SameSite
|
| 38 |
+
# policy Streamlit's own auth cookie uses.
|
| 39 |
+
cookie_manager.set(name, encrypted, expires_at=expires_at, key=f"set_{name}", same_site="lax")
|
| 40 |
|
| 41 |
|
| 42 |
def get_refresh_token(cookie_manager: stx.CookieManager, email: str) -> str | None:
|
|
|
|
| 46 |
try:
|
| 47 |
return _fernet().decrypt(encrypted.encode()).decode()
|
| 48 |
except Exception:
|
| 49 |
+
st.warning(
|
| 50 |
+
"Found a stored Drive cookie but couldn't decrypt it (likely the "
|
| 51 |
+
"encryption key changed since it was saved). Treating it as absent."
|
| 52 |
+
)
|
| 53 |
return None
|
| 54 |
|
| 55 |
|