File size: 6,058 Bytes
a6fd1a3
 
 
 
 
3e7b272
a6fd1a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8d71681
a6fd1a3
 
 
 
 
 
 
 
5137439
a6fd1a3
 
 
 
 
 
 
 
 
 
 
 
3e7b272
8d71681
a6fd1a3
 
 
 
 
 
 
 
 
3e7b272
 
a6fd1a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a56ad25
a6fd1a3
 
 
 
 
 
 
 
8d71681
a6fd1a3
5137439
a6fd1a3
 
 
 
 
 
 
 
 
 
 
 
 
 
530a737
 
a6fd1a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from fastapi.responses import JSONResponse
from jose import JWTError, jwt
from datetime import datetime, timedelta, timezone
from fastapi import HTTPException, Depends, Request, status
from schemas.user import RegisterRequest, LoginRequest
from db.mongoDB import mongo_db
from utils.utils import  verify_password
from config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from passlib.context import CryptContext
from services.uplash import get_random_unsplash_image
from jose import JWTError
import logging

logger = logging.getLogger(__name__)
# Khởi tạo context mật khẩu

bearer_scheme = HTTPBearer(auto_error=False)

# Initialize password hashing context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Hàm tạo token truy cập
def create_access_token(data: dict, expires_delta: timedelta = None):
    to_encode = data.copy()
    expire = datetime.now() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

# Hàm đăng ký người dùng
async def register_user(user: RegisterRequest):

    try:
                # Kiểm tra username đã tồn tại chưa
        existing_user =await mongo_db.users.find_one({"email": user.email})
        if existing_user:
            raise HTTPException(status_code=400, detail="Tài khoản đã tồn tại.")

        # Mã hóa mật khẩu
        hashed_password = pwd_context.hash(user.password)

        avatar = await get_random_unsplash_image()
        # Lưu user mới
        await mongo_db.users.insert_one({
            "username": user.username,
            "password": hashed_password,
            "email": user.email,
            "avatar_url": avatar,
            "role": user.role or "user",
            "is_active": True,
        })

        return {"message": "Đăng ký thành công."}
    except HTTPException as e:
        raise HTTPException(status_code=e.status_code, detail=e.detail)
# Hàm xác thực đăng nhập
async def authenticate_user(request: LoginRequest):
    user = await mongo_db.users.find_one({"email": request.email})
    if not user:
        raise HTTPException(status_code=401, detail="Sai tài khoản hoặc mật khẩu")

    if not verify_password(request.password, user["password"]):
        raise HTTPException(status_code=401, detail="Sai tài khoản hoặc mật khẩu")

    return user

# Hàm tạo và trả về JWT token sau khi đăng nhập thành công
async def login_user(request: LoginRequest):
    user = await authenticate_user(request)

    accessToken = create_access_token(data={"sub": request.email})

    return  {
        "accessToken": accessToken,
        "username": user["username"],
        "email": user["email"],
        "role": user.get("role", "user")
    }

# Ham dang xuat
async def logout_user(req: Request, credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)) :
    """
    Đăng xuất người dùng, vô hiệu hóa access token và refresh token.

    Args:
        req (Request): FastAPI request object chứa cookie.
        credentials (HTTPAuthorizationCredentials): Access token từ header Authorization.

    Returns:
        JSONResponse: Thông báo đăng xuất thành công và xóa cookie refresh_token.

    Raises:
        HTTPException: Nếu access token hoặc refresh token không hợp lệ.
    """
    try:
        # Decode access token to get payload
        token = credentials.credentials
        payload = verify_token(token)
        exp = payload.get("exp")
        if not exp:
            logger.warning("Access token không có thời gian hết hạn")
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Access token không hợp lệ")

        # Blacklist access token
        await mongo_db.token_blacklist.insert_one({
            "token": token,
            "expires_at": datetime.fromtimestamp(exp, tz=timezone.utc)
        })

        # Get refresh token from cookie
        refresh_token = req.cookies.get("refresh_token")
        if refresh_token:
            # Invalidate refresh token in database
            user = await mongo_db.users.find_one({"refresh_token": refresh_token})
            if user:
                await mongo_db.users.update_one(
                    {"_id": user["_id"]},
                    {"$set": {"refresh_token": None, "refresh_token_expiry": None, "revoked": True}}
                )
                logger.info(f"Refresh token invalidated for email: {user.get('email', 'unknown')}")
            else:
                logger.warning(f"Refresh token không hợp lệ trong database: {refresh_token}")

        # Prepare response and clear refresh token cookie
        response = JSONResponse(content={"message": "Đăng xuất thành công"}, status_code=200)
        response.set_cookie(
            key="refresh_token",
            value="",
            max_age=0,  # Expire cookie immediately
            httponly=True,
            secure=True,  # Set to False for local development
            samesite="none",
            path="/",
        )

        logger.info("User logged out successfully")
        return response

    except JWTError as e:
        logger.error(f"Lỗi khi xác minh access token: {str(e)}")
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Access token không hợp lệ")
    except Exception as e:
        logger.error(f"Lỗi hệ thống khi đăng xuất: {str(e)}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Lỗi hệ thống khi đăng xuất"
        )

# Hàm kiểm tra xem token có hợp lệ không
def verify_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError:
        raise HTTPException(status_code=401, detail="Token không hợp lệ")