Spaces:
Sleeping
Sleeping
Deploy Step 4A-5-R3 native cookie session restore
Browse files- README.md +2 -2
- managed_auth.py +71 -18
- requirements.txt +1 -2
README.md
CHANGED
|
@@ -7,8 +7,8 @@ sdk: streamlit
|
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
short_description: Agent Article
|
| 10 |
-
sdk_version: 1.
|
| 11 |
python_version: 3.11
|
| 12 |
---
|
| 13 |
|
| 14 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
short_description: Agent Article
|
| 10 |
+
sdk_version: 1.38.0
|
| 11 |
python_version: 3.11
|
| 12 |
---
|
| 13 |
|
| 14 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
managed_auth.py
CHANGED
|
@@ -11,6 +11,7 @@ import os
|
|
| 11 |
import time
|
| 12 |
from datetime import datetime, timezone
|
| 13 |
from typing import Any, Callable
|
|
|
|
| 14 |
|
| 15 |
import qrcode
|
| 16 |
import requests
|
|
@@ -203,10 +204,14 @@ def _clear_session(*, clear_restore: bool = False) -> None:
|
|
| 203 |
|
| 204 |
|
| 205 |
def _read_restore_cookie() -> str:
|
|
|
|
| 206 |
try:
|
| 207 |
-
|
| 208 |
-
|
|
|
|
|
|
|
| 209 |
except Exception:
|
|
|
|
| 210 |
return ""
|
| 211 |
|
| 212 |
|
|
@@ -214,8 +219,9 @@ def _render_restore_cookie_command(
|
|
| 214 |
token: str | None,
|
| 215 |
*,
|
| 216 |
expires_at: str | None = None,
|
|
|
|
| 217 |
) -> None:
|
| 218 |
-
"""Write or delete
|
| 219 |
safe_name = json.dumps(RESTORE_COOKIE_NAME)
|
| 220 |
safe_token = json.dumps(str(token or ""))
|
| 221 |
max_age = RESTORE_COOKIE_DEFAULT_DAYS * 24 * 60 * 60
|
|
@@ -227,33 +233,65 @@ def _render_restore_cookie_command(
|
|
| 227 |
)
|
| 228 |
if not token:
|
| 229 |
max_age = 0
|
|
|
|
| 230 |
script = f"""
|
| 231 |
<script>
|
| 232 |
(() => {{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
try {{
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
}})();
|
| 241 |
</script>
|
| 242 |
"""
|
| 243 |
components.html(script, height=0, width=0)
|
| 244 |
|
| 245 |
|
| 246 |
-
def _sync_restore_cookie_to_browser() -> None:
|
| 247 |
token = str(st.session_state.get("sp_restore_token") or "").strip()
|
| 248 |
if token:
|
| 249 |
_render_restore_cookie_command(
|
| 250 |
token,
|
| 251 |
expires_at=st.session_state.get("sp_restore_expires_at"),
|
|
|
|
| 252 |
)
|
| 253 |
|
| 254 |
|
| 255 |
-
def _delete_restore_cookie() -> None:
|
| 256 |
-
_render_restore_cookie_command(None)
|
| 257 |
|
| 258 |
|
| 259 |
def _verify_existing_session() -> bool:
|
|
@@ -322,7 +360,11 @@ def _create_login_request() -> dict[str, Any]:
|
|
| 322 |
return request_data
|
| 323 |
|
| 324 |
|
| 325 |
-
def _store_authenticated_session(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
session_token = result.get("session_token")
|
| 327 |
user = result.get("user")
|
| 328 |
|
|
@@ -341,7 +383,9 @@ def _store_authenticated_session(result: dict[str, Any]) -> None:
|
|
| 341 |
st.session_state["sp_restore_expires_at"] = result.get(
|
| 342 |
"restore_expires_at"
|
| 343 |
)
|
| 344 |
-
_sync_restore_cookie_to_browser(
|
|
|
|
|
|
|
| 345 |
_clear_login_request()
|
| 346 |
|
| 347 |
|
|
@@ -594,9 +638,16 @@ def _poll_login_once(
|
|
| 594 |
st.stop()
|
| 595 |
|
| 596 |
if status == "authenticated":
|
| 597 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
status_box.success(tr(language, "login_auto_success"))
|
| 599 |
-
st.
|
| 600 |
|
| 601 |
_clear_login_request()
|
| 602 |
|
|
@@ -733,11 +784,13 @@ def logout_alipay_session(
|
|
| 733 |
except Exception:
|
| 734 |
pass
|
| 735 |
|
| 736 |
-
|
|
|
|
|
|
|
| 737 |
_clear_session(clear_restore=True)
|
| 738 |
_clear_login_request()
|
| 739 |
_clear_generation_session_state()
|
| 740 |
-
st.
|
| 741 |
|
| 742 |
|
| 743 |
def render_logged_in_account(
|
|
|
|
| 11 |
import time
|
| 12 |
from datetime import datetime, timezone
|
| 13 |
from typing import Any, Callable
|
| 14 |
+
from urllib.parse import unquote
|
| 15 |
|
| 16 |
import qrcode
|
| 17 |
import requests
|
|
|
|
| 204 |
|
| 205 |
|
| 206 |
def _read_restore_cookie() -> str:
|
| 207 |
+
"""Read the restore cookie supplied with the initial Streamlit request."""
|
| 208 |
try:
|
| 209 |
+
context = getattr(st, "context", None)
|
| 210 |
+
cookies = getattr(context, "cookies", {}) if context is not None else {}
|
| 211 |
+
raw_value = str(cookies.get(RESTORE_COOKIE_NAME, "") or "").strip()
|
| 212 |
+
return unquote(raw_value)
|
| 213 |
except Exception:
|
| 214 |
+
# Compatibility fallback: never interrupt the approved login page.
|
| 215 |
return ""
|
| 216 |
|
| 217 |
|
|
|
|
| 219 |
token: str | None,
|
| 220 |
*,
|
| 221 |
expires_at: str | None = None,
|
| 222 |
+
reload_parent: bool = False,
|
| 223 |
) -> None:
|
| 224 |
+
"""Write or delete the recovery cookie without changing visible UI."""
|
| 225 |
safe_name = json.dumps(RESTORE_COOKIE_NAME)
|
| 226 |
safe_token = json.dumps(str(token or ""))
|
| 227 |
max_age = RESTORE_COOKIE_DEFAULT_DAYS * 24 * 60 * 60
|
|
|
|
| 233 |
)
|
| 234 |
if not token:
|
| 235 |
max_age = 0
|
| 236 |
+
|
| 237 |
script = f"""
|
| 238 |
<script>
|
| 239 |
(() => {{
|
| 240 |
+
const name = {safe_name};
|
| 241 |
+
const value = {safe_token};
|
| 242 |
+
const maxAge = {int(max_age)};
|
| 243 |
+
const reloadParent = {str(bool(reload_parent)).lower()};
|
| 244 |
+
const cookieText = `${{name}}=${{encodeURIComponent(value)}}; Max-Age=${{maxAge}}; Path=/; Secure; SameSite=Strict`;
|
| 245 |
+
|
| 246 |
+
let targetDocument = document;
|
| 247 |
+
let cookieActionSucceeded = false;
|
| 248 |
+
|
| 249 |
try {{
|
| 250 |
+
if (window.parent && window.parent.document) {{
|
| 251 |
+
targetDocument = window.parent.document;
|
| 252 |
+
}}
|
| 253 |
+
targetDocument.cookie = cookieText;
|
| 254 |
+
const cookiePresent = targetDocument.cookie
|
| 255 |
+
.split("; ")
|
| 256 |
+
.some((entry) => entry.startsWith(`${{name}}=`));
|
| 257 |
+
cookieActionSucceeded = maxAge === 0
|
| 258 |
+
? !cookiePresent
|
| 259 |
+
: cookiePresent;
|
| 260 |
+
}} catch (error) {{
|
| 261 |
+
console.error("SmartPaper restore-cookie action failed", error);
|
| 262 |
+
}}
|
| 263 |
+
|
| 264 |
+
if (!cookieActionSucceeded) {{
|
| 265 |
+
console.error("SmartPaper restore-cookie verification failed");
|
| 266 |
+
}}
|
| 267 |
+
|
| 268 |
+
if (reloadParent && cookieActionSucceeded) {{
|
| 269 |
+
window.setTimeout(() => {{
|
| 270 |
+
try {{
|
| 271 |
+
window.parent.location.reload();
|
| 272 |
+
}} catch (error) {{
|
| 273 |
+
window.location.reload();
|
| 274 |
+
}}
|
| 275 |
+
}}, 180);
|
| 276 |
+
}}
|
| 277 |
}})();
|
| 278 |
</script>
|
| 279 |
"""
|
| 280 |
components.html(script, height=0, width=0)
|
| 281 |
|
| 282 |
|
| 283 |
+
def _sync_restore_cookie_to_browser(*, reload_parent: bool = False) -> None:
|
| 284 |
token = str(st.session_state.get("sp_restore_token") or "").strip()
|
| 285 |
if token:
|
| 286 |
_render_restore_cookie_command(
|
| 287 |
token,
|
| 288 |
expires_at=st.session_state.get("sp_restore_expires_at"),
|
| 289 |
+
reload_parent=reload_parent,
|
| 290 |
)
|
| 291 |
|
| 292 |
|
| 293 |
+
def _delete_restore_cookie(*, reload_parent: bool = False) -> None:
|
| 294 |
+
_render_restore_cookie_command(None, reload_parent=reload_parent)
|
| 295 |
|
| 296 |
|
| 297 |
def _verify_existing_session() -> bool:
|
|
|
|
| 360 |
return request_data
|
| 361 |
|
| 362 |
|
| 363 |
+
def _store_authenticated_session(
|
| 364 |
+
result: dict[str, Any],
|
| 365 |
+
*,
|
| 366 |
+
reload_after_cookie: bool = False,
|
| 367 |
+
) -> None:
|
| 368 |
session_token = result.get("session_token")
|
| 369 |
user = result.get("user")
|
| 370 |
|
|
|
|
| 383 |
st.session_state["sp_restore_expires_at"] = result.get(
|
| 384 |
"restore_expires_at"
|
| 385 |
)
|
| 386 |
+
_sync_restore_cookie_to_browser(
|
| 387 |
+
reload_parent=reload_after_cookie,
|
| 388 |
+
)
|
| 389 |
_clear_login_request()
|
| 390 |
|
| 391 |
|
|
|
|
| 638 |
st.stop()
|
| 639 |
|
| 640 |
if status == "authenticated":
|
| 641 |
+
# Cookie assignment is synchronous in the browser. The hidden command
|
| 642 |
+
# then reloads the top-level page, so the new initial request carries
|
| 643 |
+
# the restore cookie and the approved login UI is not mixed with the
|
| 644 |
+
# writing interface in the same Streamlit run.
|
| 645 |
+
_store_authenticated_session(
|
| 646 |
+
result,
|
| 647 |
+
reload_after_cookie=True,
|
| 648 |
+
)
|
| 649 |
status_box.success(tr(language, "login_auto_success"))
|
| 650 |
+
st.stop()
|
| 651 |
|
| 652 |
_clear_login_request()
|
| 653 |
|
|
|
|
| 784 |
except Exception:
|
| 785 |
pass
|
| 786 |
|
| 787 |
+
# Delete the browser token first, then let browser JavaScript reload the
|
| 788 |
+
# page. This avoids interrupting the cookie deletion with st.rerun().
|
| 789 |
+
_delete_restore_cookie(reload_parent=True)
|
| 790 |
_clear_session(clear_restore=True)
|
| 791 |
_clear_login_request()
|
| 792 |
_clear_generation_session_state()
|
| 793 |
+
st.stop()
|
| 794 |
|
| 795 |
|
| 796 |
def render_logged_in_account(
|
requirements.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
streamlit==1.
|
| 2 |
requests
|
| 3 |
python-docx
|
| 4 |
pandas
|
|
@@ -15,4 +15,3 @@ tabulate
|
|
| 15 |
pycryptodome
|
| 16 |
qrcode[pil]>=8.0,<9
|
| 17 |
streamlit-autorefresh==1.0.1
|
| 18 |
-
extra-streamlit-components==0.1.71
|
|
|
|
| 1 |
+
streamlit==1.38.0
|
| 2 |
requests
|
| 3 |
python-docx
|
| 4 |
pandas
|
|
|
|
| 15 |
pycryptodome
|
| 16 |
qrcode[pil]>=8.0,<9
|
| 17 |
streamlit-autorefresh==1.0.1
|
|
|