Update services/auth.py
Browse files- services/auth.py +65 -12
services/auth.py
CHANGED
@@ -1,9 +1,12 @@
|
|
|
|
|
|
1 |
from passlib.context import CryptContext
|
2 |
from sqlmodel import Session, select
|
3 |
from typing import Optional
|
4 |
|
|
|
5 |
from models.user import User, UserCreate
|
6 |
-
from models.db import get_session_context
|
7 |
from services.logger import app_logger
|
8 |
|
9 |
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
@@ -19,28 +22,78 @@ def get_user(db: Session, username: str) -> Optional[User]:
|
|
19 |
user = db.exec(statement).first()
|
20 |
return user
|
21 |
|
22 |
-
# In services/auth.py
|
23 |
def create_user_in_db(user_data: UserCreate) -> Optional[User]:
|
24 |
hashed_password = get_password_hash(user_data.password)
|
|
|
|
|
25 |
db_user = User(
|
26 |
username=user_data.username,
|
27 |
hashed_password=hashed_password,
|
28 |
email=user_data.email,
|
29 |
-
full_name=user_data.full_name
|
30 |
-
|
31 |
)
|
|
|
|
|
|
|
32 |
try:
|
33 |
-
with get_session_context() as db:
|
34 |
-
|
35 |
-
|
|
|
36 |
app_logger.warning(f"User {user_data.username} already exists.")
|
37 |
return None
|
38 |
|
39 |
db.add(db_user)
|
40 |
-
|
41 |
-
db.refresh(db_user) #
|
42 |
-
|
43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
except Exception as e:
|
45 |
app_logger.error(f"Error creating user {user_data.username}: {e}")
|
46 |
-
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# services/auth.py
|
2 |
+
|
3 |
from passlib.context import CryptContext
|
4 |
from sqlmodel import Session, select
|
5 |
from typing import Optional
|
6 |
|
7 |
+
# Import User directly to avoid potential circularity with models.__init__ if not careful
|
8 |
from models.user import User, UserCreate
|
9 |
+
from models.db import get_session_context
|
10 |
from services.logger import app_logger
|
11 |
|
12 |
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
|
22 |
user = db.exec(statement).first()
|
23 |
return user
|
24 |
|
|
|
25 |
def create_user_in_db(user_data: UserCreate) -> Optional[User]:
|
26 |
hashed_password = get_password_hash(user_data.password)
|
27 |
+
|
28 |
+
# Create the Python object
|
29 |
db_user = User(
|
30 |
username=user_data.username,
|
31 |
hashed_password=hashed_password,
|
32 |
email=user_data.email,
|
33 |
+
full_name=user_data.full_name,
|
34 |
+
chat_sessions=[] # Initialize relationships if they are expected to be non-None
|
35 |
)
|
36 |
+
|
37 |
+
created_user_id: Optional[int] = None
|
38 |
+
|
39 |
try:
|
40 |
+
with get_session_context() as db:
|
41 |
+
# Check if user already exists
|
42 |
+
existing_user_check = db.exec(select(User).where(User.username == user_data.username)).first()
|
43 |
+
if existing_user_check:
|
44 |
app_logger.warning(f"User {user_data.username} already exists.")
|
45 |
return None
|
46 |
|
47 |
db.add(db_user)
|
48 |
+
db.commit() # Commit to save the user and generate ID
|
49 |
+
db.refresh(db_user) # Refresh to get all attributes, especially ID
|
50 |
+
created_user_id = db_user.id # Store the ID
|
51 |
+
|
52 |
+
# Explicitly load the username to be safe, though it should be loaded.
|
53 |
+
# This is more of a "just in case" step.
|
54 |
+
_ = db_user.username
|
55 |
+
_ = db_user.email # if you plan to use it immediately after
|
56 |
+
|
57 |
+
# After session closes, db_user is detached.
|
58 |
+
# If we need to return the full User object, and it might be used in a way
|
59 |
+
# that requires re-attaching or accessing lazy-loaded fields, this can be an issue.
|
60 |
+
# For simple display like username, it should be fine.
|
61 |
+
# The error suggests something is trying to "refresh" it.
|
62 |
+
|
63 |
+
# To be absolutely safe for the immediate use case (displaying username),
|
64 |
+
# we can fetch a "clean" version of the user by ID in a new, short-lived session,
|
65 |
+
# though this is usually overkill for just displaying a username.
|
66 |
+
# This ensures the returned object is "fresh" but also detached.
|
67 |
+
if created_user_id is not None:
|
68 |
+
with get_session_context() as db:
|
69 |
+
# Re-fetch the user to ensure it's a clean, detached object
|
70 |
+
# with its basic attributes loaded.
|
71 |
+
user_to_return = db.get(User, created_user_id)
|
72 |
+
if user_to_return:
|
73 |
+
# Access attributes needed immediately to ensure they are loaded
|
74 |
+
# before the session closes again.
|
75 |
+
_ = user_to_return.username
|
76 |
+
_ = user_to_return.email
|
77 |
+
return user_to_return # This user object is now detached.
|
78 |
+
return None # Should not happen if creation was successful
|
79 |
+
|
80 |
except Exception as e:
|
81 |
app_logger.error(f"Error creating user {user_data.username}: {e}")
|
82 |
+
return None
|
83 |
+
|
84 |
+
def authenticate_user(username: str, password: str) -> Optional[User]:
|
85 |
+
with get_session_context() as db:
|
86 |
+
user = get_user(db, username)
|
87 |
+
if not user:
|
88 |
+
return None
|
89 |
+
if not verify_password(password, user.hashed_password):
|
90 |
+
return None
|
91 |
+
# Before returning, ensure any attributes needed by the caller are loaded
|
92 |
+
# if they might be lazy-loaded and the user object will be used after this session.
|
93 |
+
# For st.session_state.authenticated_user, this is important.
|
94 |
+
_ = user.id
|
95 |
+
_ = user.username
|
96 |
+
_ = user.email # etc.
|
97 |
+
# For relationships, you might need to configure eager loading (e.g. selectinload)
|
98 |
+
# or accept that they will be lazy-loaded (requiring a session) or not accessible.
|
99 |
+
return user
|