"""OphthalmoCapture — Basic Authentication Service Provides a simple login gate using streamlit-authenticator. Doctors must authenticate before accessing the labeling interface. Their name is automatically set in the session for audit trails. If streamlit-authenticator is not installed, authentication is skipped and the app works in "anonymous" mode. """ import streamlit as st try: import streamlit_authenticator as stauth AUTH_AVAILABLE = True except ImportError: AUTH_AVAILABLE = False from i18n import t # ── Default credentials ────────────────────────────────────────────────────── # In production, load these from a secure YAML/env. For now, hardcoded demo. DEFAULT_CREDENTIALS = { "usernames": { "admin": { "name": "Administrador", "password": "$2b$12$dcvvIg0q/2hZ1pO9gBKqY./LfujFHvoJUvPDLx1qhLS0LtD2kzJoq", # plain: "admin123" — generate new hashes with stauth.Hasher }, "Saul": { "name": "Dr. Saul", "password": "$2b$12$dcvvIg0q/2hZ1pO9gBKqY./LfujFHvoJUvPDLx1qhLS0LtD2kzJoq", # plain: "admin123" }, "Marisse": { "name": "Dra. Marisse", "password": "$2b$12$dcvvIg0q/2hZ1pO9gBKqY./LfujFHvoJUvPDLx1qhLS0LtD2kzJoq", # plain: "admin123" }, "Angel": { "name": "Dr. Angel", "password": "$2b$12$dcvvIg0q/2hZ1pO9gBKqY./LfujFHvoJUvPDLx1qhLS0LtD2kzJoq", # plain: "admin123" }, "Enmanuel": { "name": "Dr. Enmanuel", "password": "$2b$12$dcvvIg0q/2hZ1pO9gBKqY./LfujFHvoJUvPDLx1qhLS0LtD2kzJoq", # plain: "admin123" }, "Micaela": { "name": "Dra. Micaela", "password": "$2b$12$dcvvIg0q/2hZ1pO9gBKqY./LfujFHvoJUvPDLx1qhLS0LtD2kzJoq", # plain: "admin123" }, "Miguel": { "name": "Dr. Miguel", "password": "$2b$12$dcvvIg0q/2hZ1pO9gBKqY./LfujFHvoJUvPDLx1qhLS0LtD2kzJoq", # plain: "admin123" }, } } COOKIE_NAME = "ophthalmocapture_auth" COOKIE_KEY = "ophthalmocapture_secret_key" COOKIE_EXPIRY_DAYS = 1 def _get_authenticator(): """Return a single shared Authenticate instance per session.""" if "authenticator" not in st.session_state: st.session_state["authenticator"] = stauth.Authenticate( credentials=DEFAULT_CREDENTIALS, cookie_name=COOKIE_NAME, cookie_key=COOKIE_KEY, cookie_expiry_days=COOKIE_EXPIRY_DAYS, ) return st.session_state["authenticator"] def require_auth() -> bool: """Show login form and return True if the user is authenticated. If streamlit-authenticator is not installed, returns True immediately (anonymous mode) and sets doctor_name to empty string. """ if not AUTH_AVAILABLE: # Graceful degradation: no auth library → anonymous mode return True authenticator = _get_authenticator() try: authenticator.login(location="main") except Exception: pass if st.session_state.get("authentication_status"): # Set doctor name from authenticated user username = st.session_state.get("username", "") user_info = DEFAULT_CREDENTIALS["usernames"].get(username, {}) st.session_state.doctor_name = user_info.get("name", username) return True elif st.session_state.get("authentication_status") is False: st.error(t("login_error")) return False else: st.info(t("login_prompt")) return False def render_logout_button(): """Show a logout button in the sidebar (only if auth is active).""" if not AUTH_AVAILABLE: return if st.session_state.get("authentication_status"): authenticator = _get_authenticator() authenticator.logout(t("logout"), location="sidebar") def do_logout(): """Programmatically log out the current user.""" if not AUTH_AVAILABLE: return try: authenticator = _get_authenticator() authenticator.logout(location="unrendered") except Exception: # Fallback: clear auth keys manually for key in ("authentication_status", "username", "name", "logout"): st.session_state.pop(key, None) st.session_state.pop("authenticator", None)