Skip to content

Commit 303be63

Browse files
Merge branch 'develop' of https://github.com/C216-Distribuid-System-Project/to-do-list-backend into test/MF-route_register
2 parents 81b8e56 + c00601b commit 303be63

File tree

7 files changed

+140
-18
lines changed

7 files changed

+140
-18
lines changed

requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ click==8.1.8
77
colorama==0.4.6
88
cryptography==46.0.1
99
dnspython==2.7.0
10+
ecdsa==0.19.1
1011
email-validator==2.3.0
1112
exceptiongroup==1.3.0
1213
fastapi==0.117.1
@@ -20,6 +21,7 @@ httpx==0.28.1
2021
idna==3.10
2122
iniconfig==2.1.0
2223
Jinja2==3.1.6
24+
jose==1.0.0
2325
markdown-it-py==3.0.0
2426
MarkupSafe==3.0.2
2527
mdurl==0.1.2
@@ -33,13 +35,16 @@ Pygments==2.19.2
3335
PyMySQL==1.1.2
3436
pytest==8.4.2
3537
python-dotenv==1.1.1
38+
python-jose==3.5.0
3639
python-multipart==0.0.20
3740
PyYAML==6.0.2
3841
rich==14.1.0
3942
rich-toolkit==0.15.1
4043
rignore==0.6.4
44+
rsa==4.9.1
4145
sentry-sdk==2.38.0
4246
shellingham==1.5.4
47+
six==1.17.0
4348
sniffio==1.3.1
4449
SQLAlchemy==2.0.43
4550
starlette==0.48.0
@@ -49,5 +54,6 @@ typing-inspection==0.4.1
4954
typing_extensions==4.15.0
5055
urllib3==2.5.0
5156
uvicorn==0.36.0
57+
uvloop==0.21.0
5258
watchfiles==1.1.0
5359
websockets==15.0.1

src/modules/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SECRET_KEY = "peteco"
2+
ALGORITHM = "HS256"
3+
ACCESS_TOKEN_EXPIRE_MINUTES = 30

src/modules/modules_api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from fastapi import APIRouter
22
from src.routes.authentication_routes import authentication_router
3+
from src.routes.user_routes import user_router
34

45
# -------------------- API ROUTES -------------------- #
56
router = APIRouter()
67

78
router.include_router(authentication_router)
9+
router.include_router(user_router)
Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,58 @@
1-
from fastapi import APIRouter, Depends, HTTPException
1+
from src.utils.utils import get_db
22
from sqlalchemy.orm import Session
3-
from src.schemas.users.users_schema import UserCreate, UserLogin, UserResponse
3+
from schemas.token_schema import Token
4+
from utils.security import create_access_token
45
from src.schemas.users.CRUD.create import create_user
5-
from src.schemas.users.CRUD.read import authenticate_user
6-
from src.utils.utils import get_db
6+
from fastapi import APIRouter, Depends, HTTPException
7+
from src.schemas.users.CRUD.read import authenticate_user, get_user_by_email
8+
from src.schemas.users.users_schema import UserCreate, UserLogin, UserResponse
79

810
authentication_router = APIRouter(prefix="/auth", tags=["auth"])
911

1012

1113
@authentication_router.post("/register", response_model=UserResponse)
1214
def register_user(user: UserCreate, db: Session = Depends(get_db)):
1315
"""
14-
Registers a new user in the system.
16+
Register a new user in the system.
1517
1618
Args:
1719
user (UserCreate): The user data to be registered.
1820
db (Session, optional): The database session dependency.
1921
2022
Raises:
21-
HTTPException: If a user with the provided email already exists.
23+
HTTPException: Raised if a user with the provided email already exists.
2224
2325
Returns:
2426
UserResponse: The newly created user information.
2527
"""
26-
existing = authenticate_user(db, user.email, user.password)
27-
if existing:
28-
raise HTTPException(status_code=400, detail="User already exists.")
28+
existing_user = get_user_by_email(db, email=user.email)
29+
if existing_user:
30+
raise HTTPException(
31+
status_code=400, detail="User with this email already exists."
32+
)
2933
return create_user(db, user)
3034

3135

32-
@authentication_router.post("/login")
36+
@authentication_router.post("/login", response_model=Token)
3337
def login_user(user: UserLogin, db: Session = Depends(get_db)):
3438
"""
35-
Handles user login by verifying credentials.
39+
Handle user login by verifying credentials and returning a JWT token.
3640
3741
Args:
38-
user (UserLogin): The user login data containing email and password.
42+
user (UserLogin): The login data containing email and password.
3943
db (Session, optional): The database session dependency.
4044
41-
Returns:
42-
dict: A message indicating successful login.
43-
4445
Raises:
45-
HTTPException: If the credentials are invalid (status code 401).
46+
HTTPException: Raised if the credentials are invalid (401).
47+
48+
Returns:
49+
dict: A dictionary containing the access token and its type.
4650
"""
4751
auth_user = authenticate_user(db, user.email, user.password)
4852
if not auth_user:
4953
raise HTTPException(status_code=401, detail="Invalid credentials.")
50-
# TODO: gerar JWT
51-
return {"msg": "Login successful!"}
54+
55+
# NOVO: Geração do token JWT
56+
access_token = create_access_token(data={"sub": auth_user.email})
57+
58+
return {"access_token": access_token, "token_type": "bearer"}

src/routes/user_routes.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from models.user_model import User
2+
from fastapi import APIRouter, Depends
3+
from utils.security import get_current_user
4+
from schemas.users.users_schema import UserResponse
5+
6+
user_router = APIRouter(prefix="/users", tags=["users"])
7+
8+
9+
@user_router.get("/me", response_model=UserResponse)
10+
def read_users_me(current_user: User = Depends(get_current_user)):
11+
"""
12+
Get the details of the currently authenticated user.
13+
14+
This route is protected and requires a valid JWT token. The `get_current_user`
15+
dependency handles token validation and user retrieval.
16+
17+
Args:
18+
current_user (User): The user object injected by the dependency.
19+
20+
Returns:
21+
UserResponse: The details of the authenticated user.
22+
"""
23+
return current_user

src/schemas/token_schema.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from pydantic import BaseModel
2+
3+
class Token(BaseModel):
4+
"""
5+
Pydantic model for the JWT access token response.
6+
7+
Attributes:
8+
access_token (str): The JWT token string.
9+
token_type (str): The type of the token (e.g., "bearer").
10+
"""
11+
access_token: str
12+
token_type: str

src/utils/security.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from utils.utils import get_db
2+
from jose import JWTError, jwt
3+
from sqlalchemy.orm import Session
4+
from models.user_model import User
5+
from datetime import datetime, timedelta, timezone
6+
from fastapi import Depends, HTTPException, status
7+
from schemas.users.CRUD.read import get_user_by_email
8+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
9+
from modules.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES
10+
11+
security_scheme = HTTPBearer()
12+
13+
14+
def create_access_token(data: dict) -> str:
15+
"""
16+
Create a JWT access token.
17+
18+
Args:
19+
data (dict): Data to encode inside the token payload.
20+
21+
Returns:
22+
str: The encoded JWT access token.
23+
"""
24+
to_encode = data.copy()
25+
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
26+
to_encode.update({"exp": expire})
27+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
28+
return encoded_jwt
29+
30+
31+
def get_current_user(
32+
db: Session = Depends(get_db),
33+
token: HTTPAuthorizationCredentials = Depends(security_scheme),
34+
) -> User:
35+
"""
36+
Decode the JWT token to get the current authenticated user.
37+
38+
Acts as a dependency for protected routes: validates the token,
39+
extracts the subject (email), and fetches the user from the database.
40+
41+
Args:
42+
db (Session): Database session dependency.
43+
token (HTTPAuthorizationCredentials): JWT token passed in the
44+
Authorization header.
45+
46+
Raises:
47+
HTTPException: Raised if the token is invalid, expired,
48+
or the user is not found.
49+
50+
Returns:
51+
User: The authenticated user object.
52+
"""
53+
credentials_exception = HTTPException(
54+
status_code=status.HTTP_401_UNAUTHORIZED,
55+
detail="Could not validate credentials",
56+
headers={"WWW-Authenticate": "Bearer"},
57+
)
58+
try:
59+
payload = jwt.decode(token.credentials, SECRET_KEY, algorithms=[ALGORITHM])
60+
email: str = payload.get("sub")
61+
if email is None:
62+
raise credentials_exception
63+
except JWTError:
64+
raise credentials_exception
65+
66+
user = get_user_by_email(db, email=email)
67+
if user is None:
68+
raise credentials_exception
69+
return user

0 commit comments

Comments
 (0)