tirtho149's picture
Deploy AgAdvisor
b30f068 verified
Raw
History Blame Contribute Delete
5.39 kB
"""Streamlit sign-in / sign-up gate and session-cookie glue.
Kept out of the main app file so the auth flow is self-contained. The cookie
component (``extra_streamlit_components``) is imported lazily; if it is not
installed the app still works, but "stay logged in" downgrades to per-session
(a browser refresh will require signing in again).
"""
from __future__ import annotations
import datetime as _dt
import logging
from typing import Any, Dict, Optional
import streamlit as st
from .auth import AuthError
logger = logging.getLogger("agadvisor.accounts.ui")
COOKIE_NAME = "agadvisor_session"
_COOKIE_TTL_DAYS = 30
def _get_cookie_manager():
"""Return a cached CookieManager, or None if the component is unavailable."""
if "cookie_manager" in st.session_state:
return st.session_state.cookie_manager
try:
import extra_streamlit_components as stx # lazy
mgr = stx.CookieManager(key="agadvisor_cookie_mgr")
st.session_state.cookie_manager = mgr
return mgr
except Exception as e: # not installed / component error
logger.info("Cookie manager unavailable (%s); using session-only auth.", e)
st.session_state.cookie_manager = None
return None
def _set_session_cookie(token: str) -> None:
mgr = _get_cookie_manager()
if mgr is None:
return
try:
expires = _dt.datetime.now() + _dt.timedelta(days=_COOKIE_TTL_DAYS)
mgr.set(COOKIE_NAME, token, expires_at=expires, key="set_session_cookie")
except Exception as e:
logger.info("Could not set session cookie: %s", e)
def _clear_session_cookie() -> None:
mgr = _get_cookie_manager()
if mgr is None:
return
try:
mgr.delete(COOKIE_NAME, key="del_session_cookie")
except Exception as e:
logger.info("Could not clear session cookie: %s", e)
def _read_cookie_token() -> Optional[str]:
mgr = _get_cookie_manager()
if mgr is None:
return None
try:
return mgr.get(COOKIE_NAME)
except Exception:
return None
def _resolve_user(service) -> Optional[Dict[str, Any]]:
"""Return the logged-in user from session_state, else from a valid cookie."""
if st.session_state.get("user"):
return st.session_state["user"]
token = _read_cookie_token()
if token:
user = service.user_from_token(token)
if user:
st.session_state["user"] = {"id": user["id"], "username": user["username"]}
return st.session_state["user"]
return None
def logout(service=None) -> None:
"""Flush any pending sync, clear the cookie and session, and rerun."""
try:
if service is not None and getattr(service, "sync", None) is not None:
service.sync.flush()
except Exception:
pass
_clear_session_cookie()
for k in ("user", "chats", "current_chat_id", "chat_counter"):
st.session_state.pop(k, None)
st.rerun()
def render_login_gate(service) -> Optional[Dict[str, Any]]:
"""Render the sign-in / sign-up UI. Returns the user on success, else None
(call ``st.stop()`` in the caller when None)."""
st.markdown("### 🌿 Welcome to AgAdvisor")
st.caption("Sign in to save your conversations. Your chat history is private to your account.")
tab_login, tab_signup = st.tabs(["Sign in", "Create account"])
with tab_login:
with st.form("login_form", clear_on_submit=False):
username = st.text_input("Username", key="login_username")
password = st.text_input("Password", type="password", key="login_password")
submitted = st.form_submit_button("Sign in", use_container_width=True, type="primary")
if submitted:
try:
user = service.login(username, password)
except AuthError as e:
st.error(str(e))
else:
st.session_state["user"] = user
_set_session_cookie(service.issue_token(user["id"]))
st.success(f"Welcome back, {user['username']}!")
st.rerun()
with tab_signup:
with st.form("signup_form", clear_on_submit=False):
new_username = st.text_input("Choose a username", key="signup_username")
new_password = st.text_input("Choose a password", type="password", key="signup_password")
confirm = st.text_input("Confirm password", type="password", key="signup_confirm")
submitted = st.form_submit_button("Create account", use_container_width=True, type="primary")
if submitted:
if new_password != confirm:
st.error("Passwords do not match.")
else:
try:
user = service.signup(new_username, new_password)
except AuthError as e:
st.error(str(e))
else:
st.session_state["user"] = user
_set_session_cookie(service.issue_token(user["id"]))
st.success(f"Account created — welcome, {user['username']}!")
st.rerun()
return None
def require_auth(service) -> Dict[str, Any]:
"""Gate the app: return the current user, or render the login gate and stop."""
user = _resolve_user(service)
if user:
return user
render_login_gate(service)
st.stop()