forked from ahmedfgad/GeneticAlgorithmPython
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth_handler.py
More file actions
157 lines (128 loc) · 5.11 KB
/
auth_handler.py
File metadata and controls
157 lines (128 loc) · 5.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""
Streamlit login/register using streamlit-authenticator with SQLite backend.
Credentials are loaded from the users table on each run; new registrations
are persisted via database.db_manager.create_user.
"""
from __future__ import annotations
from typing import Any
import streamlit as st
import streamlit_authenticator as stauth
import config
from database import db_manager
from database.models.users import UserRecord
from utils.encryption import ensure_development_key_file
def build_credentials_from_db() -> dict[str, Any]:
"""
Build the credentials dict expected by streamlit-authenticator from SQLite.
Structure:
{
"usernames": {
"jane": {"email": "...", "name": "...", "password": "<bcrypt>"},
}
}
"""
users = db_manager.list_active_users()
usernames: dict[str, dict[str, str]] = {}
for user in users:
usernames[user.username] = user.to_credentials_entry()
return {"usernames": usernames}
class AuthHandler:
"""
Wraps streamlit-authenticator and syncs state to st.session_state.
Usage in a Streamlit app::
auth = AuthHandler()
auth.render_login()
if not auth.is_authenticated():
st.stop()
"""
def __init__(self) -> None:
if config.IS_DEVELOPMENT:
ensure_development_key_file()
self._credentials = build_credentials_from_db()
self._authenticator = stauth.Authenticate(
self._credentials,
config.AUTH_COOKIE_NAME,
config.AUTH_COOKIE_KEY,
config.AUTH_COOKIE_EXPIRY_DAYS,
)
@property
def authenticator(self) -> stauth.Authenticate:
return self._authenticator
def reload_credentials(self) -> None:
"""Refresh credentials from DB (call after registration)."""
self._credentials = build_credentials_from_db()
self._authenticator.credentials = self._credentials
def render_login(self, location: str = "main", key: str = "Login") -> tuple[str | None, bool | None, str | None]:
"""
Render the login widget.
Returns (name, authentication_status, username) from streamlit-authenticator.
"""
fields = self._authenticator.login(location=location, key=key)
if fields is None:
return None, None, None
name, status, username = fields
if status:
user = db_manager.get_user_by_username(username or "")
if user:
st.session_state["user_id"] = user.user_id
st.session_state["username"] = user.username
st.session_state["user_name"] = user.name
st.session_state["user_email"] = user.email
return name, status, username
def render_logout(self, button_name: str = "Logout", location: str = "sidebar", key: str = "Logout") -> None:
"""Render logout and clear session keys."""
self._authenticator.logout(button_name, location, key=key)
for key_name in ("user_id", "username", "user_name", "user_email"):
st.session_state.pop(key_name, None)
def is_authenticated(self) -> bool:
return st.session_state.get("authentication_status") is True
def get_logged_in_user(self) -> UserRecord | None:
user_id = st.session_state.get("user_id")
if user_id is None:
username = st.session_state.get("username")
if username:
return db_manager.get_user_by_username(username)
return None
return db_manager.get_user_by_id(int(user_id))
def render_register(
self,
*,
location: str = "main",
pre_authorized: list[str] | None = None,
key: str = "Register",
captcha: bool = False,
) -> bool:
"""
Render registration form; on success, persist user to SQLite.
pre_authorized:
None — open registration (first install / development)
list — only listed emails may register (production)
Returns True if a new user was registered and saved to the database.
"""
# Empty list blocks everyone; use None for open registration.
email, username, full_name = self._authenticator.register_user(
location=location,
pre_authorized=pre_authorized,
key=key,
captcha=captcha,
)
if not username:
return False
if db_manager.username_exists(username):
self.reload_credentials()
return False
creds = self._authenticator.credentials.get("usernames", {})
entry = creds.get(username)
if not entry:
return False
db_manager.create_user(
username=username,
email=email or entry.get("email", f"{username}@local"),
name=full_name or entry.get("name", username),
password_hash=entry["password"],
)
self.reload_credentials()
return True
def hash_password(plain_password: str) -> str:
"""Hash a password with bcrypt for storage (streamlit-authenticator compatible)."""
return stauth.Hasher.hash(plain_password)