Skip to content

Commit c00601b

Browse files
Merge pull request #10 from C216-Distribuid-System-Project/feat/AR-token_auth
Feat/ar token auth
2 parents 30d3905 + 1b360b8 commit c00601b

File tree

7 files changed

+142
-18
lines changed

7 files changed

+142
-18
lines changed

requirements.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
annotated-types==0.7.0
22
anyio==4.10.0
3+
bcrypt==4.3.0
34
certifi==2025.8.3
45
cffi==2.0.0
56
click==8.1.8
67
colorama==0.4.6
78
cryptography==46.0.1
89
dnspython==2.7.0
10+
ecdsa==0.19.1
911
email-validator==2.3.0
1012
exceptiongroup==1.3.0
1113
fastapi==0.117.1
@@ -18,23 +20,28 @@ httptools==0.6.4
1820
httpx==0.28.1
1921
idna==3.10
2022
Jinja2==3.1.6
23+
jose==1.0.0
2124
markdown-it-py==3.0.0
2225
MarkupSafe==3.0.2
2326
mdurl==0.1.2
2427
passlib==1.7.4
28+
pyasn1==0.6.1
2529
pycparser==2.23
2630
pydantic==2.11.9
2731
pydantic_core==2.33.2
2832
Pygments==2.19.2
2933
PyMySQL==1.1.2
3034
python-dotenv==1.1.1
35+
python-jose==3.5.0
3136
python-multipart==0.0.20
3237
PyYAML==6.0.2
3338
rich==14.1.0
3439
rich-toolkit==0.15.1
3540
rignore==0.6.4
41+
rsa==4.9.1
3642
sentry-sdk==2.38.0
3743
shellingham==1.5.4
44+
six==1.17.0
3845
sniffio==1.3.1
3946
SQLAlchemy==2.0.43
4047
starlette==0.48.0
@@ -43,5 +50,6 @@ typing-inspection==0.4.1
4350
typing_extensions==4.15.0
4451
urllib3==2.5.0
4552
uvicorn==0.36.0
53+
uvloop==0.21.0
4654
watchfiles==1.1.0
4755
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 routes.authentication_routes import authentication_router
3+
from 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 utils.utils import get_db
22
from sqlalchemy.orm import Session
3-
from 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 schemas.users.CRUD.create import create_user
5-
from schemas.users.CRUD.read import authenticate_user
6-
from utils.utils import get_db
6+
from fastapi import APIRouter, Depends, HTTPException
7+
from schemas.users.CRUD.read import authenticate_user, get_user_by_email
8+
from 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)