File size: 3,944 Bytes
39af4d2
f8f1290
39af4d2
 
 
 
 
 
 
 
 
 
 
 
f8f1290
 
 
39af4d2
 
f8f1290
39af4d2
 
 
 
f8f1290
39af4d2
f8f1290
 
39af4d2
 
f8f1290
 
39af4d2
 
 
f8f1290
39af4d2
 
 
 
f8f1290
39af4d2
f8f1290
 
 
 
 
 
 
39af4d2
 
f8f1290
 
 
 
 
39af4d2
 
 
 
 
f8f1290
39af4d2
f8f1290
 
 
 
 
 
39af4d2
 
 
f8f1290
39af4d2
 
f8f1290
39af4d2
 
 
f8f1290
39af4d2
 
f8f1290
39af4d2
f8f1290
 
 
 
39af4d2
f8f1290
39af4d2
 
 
 
 
f8f1290
39af4d2
 
f8f1290
39af4d2
 
 
 
 
f8f1290
 
39af4d2
 
 
f8f1290
39af4d2
f8f1290
 
 
 
39af4d2
f8f1290
39af4d2
 
 
 
 
 
f8f1290
 
 
 
 
39af4d2
f8f1290
 
39af4d2
f8f1290
39af4d2
 
f8f1290
39af4d2
 
 
f8f1290
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
from datetime import datetime, timedelta
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext

from src.models.user import User
from src.data.user_repository import UserRepository
from src.core.config import settings

router = APIRouter()

# Updated hashing scheme (no 72-byte limit)
pwd_context = CryptContext(schemes=["bcrypt_sha256"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


# Dependency to get UserRepository instance
def get_user_repository() -> UserRepository:
    return UserRepository()


def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()

    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)

    to_encode.update({"exp": expire})

    encoded_jwt = jwt.encode(
        to_encode,
        settings.SECRET_KEY,
        algorithm=settings.ALGORITHM,
    )

    return encoded_jwt


async def get_current_user(
    token: str = Depends(oauth2_scheme),
    user_repo: UserRepository = Depends(get_user_repository),
):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )

    try:
        payload = jwt.decode(
            token,
            settings.SECRET_KEY,
            algorithms=[settings.ALGORITHM],
        )

        email: str = payload.get("sub")
        if email is None:
            raise credentials_exception

    except JWTError:
        raise credentials_exception

    user = user_repo.get_user_by_email(email)
    if user is None:
        raise credentials_exception

    return user


@router.post("/signup", response_model=User, tags=["Authentication"])
async def signup(
    user: User,
    user_repo: UserRepository = Depends(get_user_repository),
):
    db_user = user_repo.get_user_by_email(user.email)

    if db_user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Email already registered",
        )

    hashed_password = get_password_hash(user.password)
    created_user = user_repo.create_user(user, hashed_password)

    if not created_user:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Failed to create user",
        )

    # Do not return hashed password
    created_user.password = "[PASSWORD_REDACTED]"
    return created_user


@router.post("/token", tags=["Authentication"])
async def login_for_access_token(
    form_data: OAuth2PasswordRequestForm = Depends(),
    user_repo: UserRepository = Depends(get_user_repository),
):
    user = user_repo.get_user_by_email(form_data.username)

    if not user or not verify_password(form_data.password, user.password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )

    access_token_expires = timedelta(
        minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
    )

    access_token = create_access_token(
        data={"sub": user.email},
        expires_delta=access_token_expires,
    )

    return {"access_token": access_token, "token_type": "bearer"}


@router.get("/users/me", response_model=User, tags=["Authentication"])
async def read_users_me(current_user: User = Depends(get_current_user)):
    current_user.password = "[PASSWORD_REDACTED]"
    return current_user