Spaces:
Runtime error
Runtime error
from db.mongoDB import mongo_db | |
from fastapi import HTTPException, Request, Response | |
import math | |
from datetime import datetime, timedelta, timezone | |
from fastapi import status | |
import logging | |
import secrets | |
from bson import ObjectId | |
from passlib.context import CryptContext | |
from services.send_email import send_reset_password_email,send_verification_code_email,send_suspicious_activity_email | |
from schemas.user import LoginRequest | |
from utils.utils import create_access_token,create_refresh_token | |
import os | |
logger = logging.getLogger(__name__) | |
# Initialize password hashing context | |
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") | |
async def get_users(skip: int = 0, limit: int = 100, query: dict = None): | |
""" | |
Lấy danh sách người dùng với phân trang và tìm kiếm nâng cao | |
Args: | |
skip (int): Số lượng bản ghi bỏ qua (để phân trang) | |
limit (int): Số lượng bản ghi tối đa trả về | |
query (dict, optional): Điều kiện tìm kiếm người dùng | |
Returns: | |
list: Danh sách người dùng đã được lọc và phân trang | |
Raises: | |
HTTPException: Nếu có lỗi xảy ra khi truy vấn cơ sở dữ liệu | |
""" | |
try: | |
# Khởi tạo query nếu chưa có | |
search_query = query or {} | |
# Thực hiện truy vấn với phân trang | |
users_cursor = mongo_db.users.find( | |
search_query, | |
{"_id": 0, "password": 0} # Loại bỏ các trường nhạy cảm | |
).skip(skip).limit(limit) | |
# Chuyển cursor thành danh sách | |
user_list = list(users_cursor) | |
# Thêm log để debug nếu cần | |
logger.debug(f"Đã lấy {len(user_list)} người dùng với skip={skip}, limit={limit}") | |
return user_list | |
except Exception as e: | |
logger.error(f"Lỗi khi lấy danh sách người dùng: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=f"Lỗi khi lấy danh sách người dùng: {str(e)}" | |
) | |
async def count_users(query: dict = None): | |
""" | |
Đếm tổng số người dùng thỏa mãn điều kiện tìm kiếm | |
Args: | |
query (dict, optional): Điều kiện tìm kiếm người dùng | |
Returns: | |
int: Tổng số người dùng | |
""" | |
try: | |
search_query = query or {} | |
return await mongo_db.users.count_documents(search_query) | |
except Exception as e: | |
logger.error(f"Lỗi khi đếm người dùng: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=f"Lỗi khi đếm người dùng: {str(e)}" | |
) | |
async def get_paginated_users( | |
skip: int = 0, | |
limit: int = 10, | |
search: str = None, | |
sort_by: str = "created_at", | |
sort_order: int = -1 | |
): | |
""" | |
Lấy danh sách người dùng với đầy đủ thông tin phân trang và tìm kiếm | |
Args: | |
skip (int): Số lượng bản ghi bỏ qua | |
limit (int): Số lượng bản ghi tối đa trả về | |
search (str, optional): Từ khóa tìm kiếm (tên, email, v.v.) | |
sort_by (str): Trường để sắp xếp | |
sort_order (int): Thứ tự sắp xếp (1: tăng dần, -1: giảm dần) | |
Returns: | |
dict: Kết quả phân trang với items và metadata | |
""" | |
try: | |
# Xây dựng query tìm kiếm nếu có từ khóa | |
query = {} | |
if search: | |
# Tìm kiếm theo tên hoặc email | |
query = { | |
"$or": [ | |
{"email": {"$regex": search, "$options": "i"}}, | |
{"username": {"$regex": search, "$options": "i"}} | |
] | |
} | |
# Sắp xếp | |
sort_criteria = [(sort_by, sort_order)] | |
# Thực hiện truy vấn | |
users_cursor = mongo_db.users.find( | |
query, | |
{"_id": 0, "password": 0} # Loại bỏ các trường nhạy cảm | |
).sort(sort_criteria).skip(skip).limit(limit) | |
# Chuyển cursor thành danh sách | |
user_list = await users_cursor.to_list(length=limit) | |
# Đếm tổng số bản ghi | |
total =await mongo_db.users.count_documents(query) | |
# Tính toán thông tin phân trang | |
total_pages = math.ceil(total / limit) if limit > 0 else 0 | |
current_page = math.floor(skip / limit) + 1 if limit > 0 else 1 | |
# Trả về kết quả với metadata phân trang | |
return { | |
"items": user_list, | |
"metadata": { | |
"total": total, | |
"page": current_page, | |
"page_size": limit, | |
"pages": total_pages, | |
"has_more": current_page < total_pages | |
} | |
} | |
except Exception as e: | |
logger.error(f"Lỗi khi lấy danh sách người dùng phân trang: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=f"Lỗi khi lấy danh sách người dùng: {str(e)}" | |
) | |
async def get_current_user_profile(email: str): | |
""" | |
Lấy thông tin profile của người dùng hiện tại | |
Args: | |
email (str): Email của người dùng | |
Returns: | |
dict: Thông tin profile của người dùng | |
""" | |
try: | |
user =await mongo_db.users.find_one({"email": email}, {"_id": 0, "password": 0}) | |
logger.info(f"check user user_service: {user}") | |
if not user: | |
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Người dùng không tồn tại") | |
return user | |
except HTTPException as he: | |
raise he | |
except Exception as e: | |
logger.error(f"Lỗi khi lấy thông tin profile người dùng: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=f"Lỗi khi lấy thông tin profile người dùng: {str(e)}" | |
) | |
async def delete_user(user_id: str): | |
""" | |
Xóa người dùng theo ID | |
Args: | |
user_id (str): ID của người dùng cần xóa | |
Returns: | |
bool: True nếu xóa thành công, False nếu không tìm thấy người dùng | |
""" | |
try: | |
result =await mongo_db.users.delete_one({"_id": user_id}) | |
return result.deleted_count > 0 | |
except Exception as e: | |
logger.error(f"Lỗi khi xóa người dùng: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=f"Lỗi khi xóa người dùng: {str(e)}" | |
) | |
async def change_password(email: str, current_password: str, new_password: str): | |
""" | |
Đổi mật khẩu của người dùng | |
Args: | |
user_id (str): ID của người dùng | |
current_password (str): Mật khẩu hiện tại | |
new_password (str): Mật khẩu mới | |
Returns: | |
bool: True nếu đổi mật khẩu thành công, False nếu không thành công | |
""" | |
try: | |
user =await mongo_db.users.find_one({"email": email}) | |
if not user: | |
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Người dùng không tồn tại") | |
if not pwd_context.verify(current_password, user["password"]): | |
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Mật khẩu hiện tại không chính xác") | |
# Hash the new password | |
hashed_password = pwd_context.hash(new_password) | |
# Update the password in the database | |
result =await mongo_db.users.update_one({"email": email}, {"$set": {"password": hashed_password}}) | |
return result.modified_count > 0 | |
except HTTPException as he: | |
raise he | |
except Exception as e: | |
logger.error(f"Lỗi khi đổi mật khẩu: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail=f"Lỗi khi đổi mật khẩu" | |
) | |
async def reset_password_request(email: str) -> bool: | |
""" | |
Yêu cầu đặt lại mật khẩu cho người dùng | |
Args: | |
email (str): Địa chỉ email của người dùng | |
Returns: | |
bool: True nếu yêu cầu thành công, False nếu không tìm thấy người dùng | |
Raises: | |
HTTPException: Nếu có lỗi trong quá trình xử lý | |
""" | |
try: | |
# Check if user exists | |
user = await mongo_db.users.find_one({"email": email.lower()}) | |
if not user: | |
# Return True even if user not found to prevent email enumeration | |
return True | |
# Check for rate limiting (e.g., max 3 requests per hour) | |
reset_requests =await mongo_db.users.count_documents({ | |
"email": email.lower(), | |
"reset_password_timestamp": { | |
"$gte": datetime.now(tz=timezone.utc) - timedelta(hours=1) | |
} | |
}) | |
if reset_requests >= 3: | |
raise HTTPException( | |
status_code=status.HTTP_429_TOO_MANY_REQUESTS, | |
detail="Quá nhiều yêu cầu đặt lại mật khẩu. Vui lòng thử lại sau." | |
) | |
# Generate secure reset token | |
code = str(secrets.randbelow(1000000)).zfill(6) # e.g., '123456' | |
expiry = datetime.now() + timedelta(minutes=10) | |
# Store reset token and timestamp in database | |
await mongo_db.users.update_one( | |
{"_id": ObjectId(user["_id"])}, | |
{ | |
"$set": { | |
"reset_password_code": code, | |
"reset_password_timestamp": datetime.now(tz=timezone.utc), | |
"reset_password_expiry": expiry, | |
} | |
} | |
) | |
# Send reset password email (uncomment and implement actual email sending) | |
await send_reset_password_email( | |
email=email, | |
code=code, | |
expiry=15 | |
) | |
return True | |
except HTTPException as he: | |
raise he | |
except Exception as e: | |
logger.error(f"Lỗi khi yêu cầu đặt lại mật khẩu: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Lỗi hệ thống. Vui lòng thử lại sau." | |
) | |
async def reset_password(code: str, newPassword: str) -> bool: | |
""" | |
Hoàn tất đặt lại mật khẩu với token và mật khẩu mới. | |
Args: | |
code (str): code đặt lại mật khẩu. | |
newPassword (str): Mật khẩu mới của người dùng. | |
Returns: | |
bool: True nếu đặt lại mật khẩu thành công, False nếu token không hợp lệ hoặc đã hết hạn. | |
Raises: | |
HTTPException: Nếu có lỗi hệ thống trong quá trình xử lý. | |
""" | |
try: | |
# Find user by reset token | |
user =await mongo_db.users.find_one({"reset_password_code": code}) | |
if not user: | |
logger.warning(f"Code không hợp lệ: {code}") | |
return False | |
# Check if token is expired | |
expiry = user.get("reset_password_expiry") | |
if not expiry or expiry < datetime.now(): | |
logger.warning(f"Code đã hết hạn: {code}") | |
return False | |
# Validate new password (basic example, can add more rules) | |
if len(newPassword) < 8: | |
raise HTTPException( | |
status_code=status.HTTP_400_BAD_REQUEST, | |
detail="Mật khẩu mới phải có ít nhất 8 ký tự" | |
) | |
# Hash the new password | |
hashed_password = pwd_context.hash(newPassword) | |
# Update user's password and clear reset token | |
result =await mongo_db.users.update_one( | |
{"_id": ObjectId(user["_id"])}, | |
{ | |
"$set": {"password": hashed_password}, | |
"$unset": { | |
"reset_password_code": "", | |
"reset_password_expiry": "", | |
"reset_password_timestamp": "" | |
} | |
} | |
) | |
if result.modified_count != 1: | |
logger.error(f"Không thể cập nhật mật khẩu cho user: {user['_id']}") | |
return False | |
logger.info(f"Đặt lại mật khẩu thành công cho email: {user['email']}") | |
return True | |
except Exception as e: | |
logger.error(f"Lỗi khi đặt lại mật khẩu: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Lỗi hệ thống khi đặt lại mật khẩu. Vui lòng thử lại sau." | |
) | |
async def generate_and_store_verification_code(email: str) -> bool: | |
""" | |
Tạo và lưu mã xác minh đăng nhập vào cơ sở dữ liệu. | |
Args: | |
email (str): Địa chỉ email của người dùng. | |
Returns: | |
bool: True nếu lưu mã thành công. | |
Raises: | |
HTTPException: Nếu có lỗi hệ thống. | |
""" | |
try: | |
# Generate 6-digit code | |
code = str(secrets.randbelow(1000000)).zfill(6) # e.g., '123456' | |
expiry = datetime.now() + timedelta(minutes=10) | |
# Store code and expiry in database | |
result =await mongo_db.users.update_one( | |
{"email": email.lower()}, | |
{ | |
"$set": { | |
"login_verification_code": code, | |
"login_verification_expiry": expiry, | |
"login_verification_timestamp": datetime.now() | |
} | |
} | |
) | |
if result.modified_count != 1: | |
logger.error(f"Không thể lưu mã xác minh cho email: {email}") | |
return False | |
# Send verification email | |
await send_verification_code_email(email, code) | |
return True | |
except Exception as e: | |
logger.error(f"Lỗi khi tạo mã xác minh: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Lỗi hệ thống khi tạo mã xác minh. Vui lòng thử lại sau." | |
) | |
async def verify_login_code(email: str, code: str, res: Response): # Bỏ kiểu trả về dict, để FastAPI tự suy luận hoặc dùng Pydantic model | |
""" | |
Xác minh mã đăng nhập, set access và refresh tokens vào HttpOnly cookies, | |
và trả về thông tin người dùng. | |
Args: | |
email (str): Địa chỉ email của người dùng. | |
code (str): Mã xác minh do người dùng cung cấp. | |
res (Response): Đối tượng Response của FastAPI để set cookies. | |
Returns: | |
dict: Thông tin người dùng và thông báo thành công. | |
Raises: | |
HTTPException: Nếu mã không hợp lệ, đã hết hạn hoặc có lỗi hệ thống. | |
""" | |
try: | |
user = await mongo_db.users.find_one({ # Sử dụng await nếu mongo_db.users là async (ví dụ Motor) | |
"email": email.lower(), | |
"login_verification_code": code | |
}) | |
# Nếu mongo_db.users là đồng bộ (ví dụ PyMongo) | |
# user = mongo_db.users.find_one({ ... }) | |
if not user: | |
logger.warning(f"Mã xác minh không hợp lệ: {code} cho email: {email}") | |
raise HTTPException( | |
status_code=status.HTTP_400_BAD_REQUEST, | |
detail="Mã xác minh không hợp lệ hoặc đã được sử dụng." # Thêm "đã được sử dụng" | |
) | |
expiry = user.get("login_verification_expiry") | |
if not expiry or expiry < datetime.now(expiry.tzinfo if expiry.tzinfo else None): # So sánh aware với aware, naive với naive | |
logger.warning(f"Mã xác minh đã hết hạn cho email: {email}") | |
# Xóa mã đã hết hạn để tránh sử dụng lại | |
await mongo_db.users.update_one( | |
{"_id": ObjectId(user["_id"])}, | |
{ | |
"$unset": { | |
"login_verification_code": "", | |
"login_verification_expiry": "", | |
"login_verification_timestamp": "" | |
} | |
} | |
) | |
raise HTTPException( | |
status_code=status.HTTP_400_BAD_REQUEST, | |
detail="Mã xác minh đã hết hạn." | |
) | |
try: | |
token_expire_minutes_str = os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60") # Default là chuỗi "60" | |
ACCESS_TOKEN_EXPIRE_MINUTES_INT = int(token_expire_minutes_str) | |
if ACCESS_TOKEN_EXPIRE_MINUTES_INT <= 0: | |
logger.warning(f"Giá trị ACCESS_TOKEN_EXPIRE_MINUTES ('{token_expire_minutes_str}') không hợp lệ, sử dụng mặc định 60 phút.") | |
ACCESS_TOKEN_EXPIRE_MINUTES_INT = 60 | |
except ValueError: | |
logger.error(f"Không thể chuyển đổi ACCESS_TOKEN_EXPIRE_MINUTES ('{os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES')}') sang số nguyên. Sử dụng mặc định 60 phút.") | |
ACCESS_TOKEN_EXPIRE_MINUTES_INT = 60 | |
logger.info(f"Sử dụng thời gian hết hạn cho access token: {ACCESS_TOKEN_EXPIRE_MINUTES_INT} phút.") | |
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES_INT) # Sử dụng biến đã chuyển đổi | |
refresh_token_expires = timedelta(days=7) # Sử dụng biến đã chuyển đổi | |
access_token = create_access_token( | |
data={"sub": email.lower()}, expires_delta=access_token_expires | |
) # Giả sử create_access_token chấp nhận expires_delta | |
IS_PRODUCTION = os.getenv("APP_ENVIRONMENT", "development").lower() == "production" | |
# Generate refresh token (logic của bạn đã có) | |
refresh_token_value = await create_refresh_token(email.lower()) # Đảm bảo email là lowercase | |
# --- SET COOKIES --- | |
# Môi trường (ví dụ: "development", "production") | |
# Bạn nên có một biến môi trường để xác định điều này | |
# IS_PRODUCTION = os.getenv("ENVIRONMENT") == "production" | |
# Tạm đặt là True, bạn nên lấy từ biến môi trường | |
max_age_seconds = int(access_token_expires.total_seconds()) | |
logger.info(f"Setting access_token_cookie with Max-Age: {max_age_seconds} seconds") | |
# Access Token Cookie | |
res.set_cookie( | |
key="access_token_cookie", # Tên cookie cho access token | |
value=access_token, | |
max_age=int(access_token_expires.total_seconds()), # Thời gian sống bằng access token | |
httponly=True, | |
secure=IS_PRODUCTION, # True trong production (HTTPS), False khi dev với HTTP | |
samesite="none", # Hoặc "strict" | |
path="/", | |
) | |
# Refresh Token Cookie (logic của bạn đã có, điều chỉnh secure) | |
res.set_cookie( | |
key="refresh_token", | |
value=refresh_token_value, | |
max_age=int(refresh_token_expires.total_seconds()), | |
httponly=True, | |
secure=IS_PRODUCTION, # True trong production, False khi dev với HTTP | |
samesite="none", | |
path="/api/user/refresh-token", | |
) | |
# -------------------- | |
# Clear verification code và cập nhật last_login | |
await mongo_db.users.update_one( # Sử dụng await nếu là async | |
{"_id": ObjectId(user["_id"])}, | |
{ | |
"$unset": { | |
"login_verification_code": "", | |
"login_verification_expiry": "", | |
"login_verification_timestamp": "" | |
}, | |
"$set": { | |
"last_login": datetime.now(timezone.utc if expiry.tzinfo else None), # Giữ timezone nhất quán | |
"login_type": "email", # Giả sử đây là đăng nhập bằng email | |
} | |
} | |
) | |
# if update_result.modified_count == 0: | |
# logger.warning(f"Không thể cập nhật user sau khi xác minh: {email}") | |
# Cân nhắc có nên raise lỗi ở đây không, hoặc chỉ log | |
logger.info(f"Đăng nhập thành công cho email: {email}. Tokens đã được set vào cookies.") | |
# Response trả về không còn chứa accessToken | |
return { | |
# "accessToken": access_token, # KHÔNG TRẢ VỀ ACCESS TOKEN TRONG BODY NỮA | |
"user": { | |
"email": email.lower(), | |
"username": user.get("username", email.lower().split('@')[0]), # Cung cấp username mặc định nếu không có | |
"role": user.get("role", "user"), | |
"avatar_url": user.get("avatar_url", None), # Hoặc một avatar mặc định, | |
"login_type": user.get("login_type", "email") # Giả sử đây là đăng nhập bằng email | |
}, | |
"message": "Đăng nhập thành công. Tokens đã được lưu trữ an toàn." | |
} | |
except HTTPException as he: | |
# Ghi log chi tiết hơn cho HTTPException nếu cần | |
# logger.error(f"HTTPException trong verify_login_code cho {email}: {he.detail}", exc_info=not (he.status_code < 500)) | |
raise he | |
except Exception as e: | |
logger.error(f"Lỗi hệ thống khi xác minh mã đăng nhập cho {email}: {e}", exc_info=True) | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Lỗi hệ thống, vui lòng thử lại sau." | |
) | |
async def authenticate_user(request: LoginRequest) -> dict: | |
""" | |
Xác thực người dùng dựa trên email và mật khẩu. | |
Args: | |
request (LoginRequest): Yêu cầu đăng nhập chứa email và mật khẩu. | |
Returns: | |
dict: Thông tin người dùng nếu xác thực thành công. | |
Raises: | |
HTTPException: Nếu thông tin đăng nhập không hợp lệ. | |
""" | |
user = await mongo_db.users.find_one({"email": request.email.lower()}) | |
if not user or not pwd_context.verify(request.password, user["password"]): | |
logger.warning(f"Xác thực thất bại cho email: {request.email}") | |
raise HTTPException( | |
status_code=status.HTTP_401_UNAUTHORIZED, | |
detail="Email hoặc mật khẩu không đúng", | |
headers={"WWW-Authenticate": "Bearer"} | |
) | |
return user | |
async def refresh_access_token(req: Request , res: Response ) -> dict: | |
""" | |
Làm mới access token dựa trên refresh token từ cookie. | |
Args: | |
req (Request): FastAPI request object chứa cookie. | |
Returns: | |
JSONResponse: Phản hồi chứa access_token, refresh_token, username, email, role và set cookie mới. | |
Raises: | |
HTTPException: Nếu refresh token không hợp lệ, đã hết hạn hoặc có lỗi hệ thống. | |
""" | |
try: | |
# Retrieve refresh token from cookie | |
refresh_token = req.cookies.get("refresh_token") | |
if not refresh_token: | |
logger.warning("Không tìm thấy refresh token trong cookie") | |
raise HTTPException( | |
status_code=status.HTTP_401_UNAUTHORIZED, | |
detail="Không tìm thấy refresh token", | |
headers={"WWW-Authenticate": "Bearer"} | |
) | |
# Find user by refresh token in database | |
user =await mongo_db.users.find_one({"refresh_token": refresh_token}) | |
if not user: | |
logger.warning(f"Refresh token không hợp lệ: {refresh_token}") | |
await send_suspicious_activity_email(user.get("email", "unknown") if user else "unknown") | |
raise HTTPException( | |
status_code=status.HTTP_401_UNAUTHORIZED, | |
detail="Refresh token không hợp lệ", | |
headers={"WWW-Authenticate": "Bearer"} | |
) | |
# Check if refresh token is expired | |
expiry = user.get("refresh_token_expiry") | |
if not expiry or expiry < datetime.now(): | |
logger.warning(f"Refresh token đã hết hạn cho email: {user['email']}") | |
await send_suspicious_activity_email(user["email"]) | |
raise HTTPException( | |
status_code=status.HTTP_401_UNAUTHORIZED, | |
detail="Refresh token đã hết hạn", | |
headers={"WWW-Authenticate": "Bearer"} | |
) | |
email = user.get('email') | |
# Generate new access token | |
token_expire_minutes_str = os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60") # Default là chuỗi "60" | |
ACCESS_TOKEN_EXPIRE_MINUTES_INT = int(token_expire_minutes_str) | |
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES_INT) # Sử dụng biến đã chuyển đổi | |
access_token = create_access_token( | |
data={"sub": email.lower()}, expires_delta=access_token_expires | |
) # Giả sử create_access_token chấp nhận expires_delta | |
IS_PRODUCTION = os.getenv("APP_ENVIRONMENT", "development").lower() == "production" | |
# Generate new refresh token (rotation) | |
new_refresh_token = await create_refresh_token(email) | |
# Update database with new refresh token | |
await mongo_db.users.update_one( | |
{"_id": user["_id"]}, | |
{ | |
"$set": { | |
"refresh_token": new_refresh_token, | |
"refresh_token_expiry": datetime.now() + timedelta(days=7), | |
"last_login": datetime.now(timezone.utc) | |
} | |
} | |
) | |
# Set access token mới vào cookie | |
res.set_cookie( | |
key="access_token_cookie", # Đảm bảo tên này khớp với client mong đợi | |
value=access_token, | |
max_age= access_token_expires, # tính bằng giây | |
httponly=True, | |
secure=IS_PRODUCTION, | |
samesite='none', # type: ignore | |
path='/', | |
) | |
# Set new refresh token in cookie | |
res.set_cookie( | |
key="refresh_token", | |
value=new_refresh_token, | |
max_age=7 * 24 * 60 * 60, # 7 days | |
httponly=True, | |
secure=IS_PRODUCTION, # Set to False for local development | |
samesite="none", # CSRF protection | |
path="/api/user/refresh-token", | |
) | |
logger.info(f"Access token refreshed for email: {user['email']}") | |
return { | |
"success": True, | |
"message": "Token đã được làm mới thành công.", | |
"user": { | |
"username": user['username'], # Lấy username, fallback về email | |
"email": user['email'], | |
"role": user['role'], | |
"avatar_url": user['avatar_url'] | |
} | |
# Không cần trả về token trong body nếu chúng đã được set trong HttpOnly cookies | |
} | |
except HTTPException as he: | |
raise he | |
except Exception as e: | |
logger.error(f"Lỗi khi làm mới access token: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Lỗi hệ thống khi làm mới access token" | |
) | |
async def verify_forgot_password_code(email: str, code: str) -> dict: | |
try: | |
# Find user by email and code | |
user =await mongo_db.users.find_one({ | |
"email": email.lower(), | |
"reset_password_code": code | |
}) | |
if not user: | |
logger.warning(f"Mã xác minh không hợp lệ: {code}") | |
raise HTTPException( | |
status_code=status.HTTP_400_BAD_REQUEST, | |
detail="Mã xác minh không hợp lệ" | |
) | |
# Check if code is expired | |
expiry = user.get("reset_password_expiry") | |
if not expiry or expiry < datetime.now(): | |
logger.warning(f"Mã xác minh đã hết hạn cho email: {email}") | |
raise HTTPException( | |
status_code=status.HTTP_400_BAD_REQUEST, | |
detail="Mã xác minh đã hết hạn" | |
) | |
return True | |
except HTTPException as he: | |
raise he | |
except Exception as e: | |
logger.error(f"Lỗi khi xác minh mã quen mat khau: {str(e)}") | |
raise HTTPException( | |
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
detail="Lỗi hệ thống khi xác minh mã quen mat khau." | |
) | |