Skip to content

Commit a8c2f6d

Browse files
committed
feature: add get_current_community()
refactor: reorganize imports and improve authentication logic in routers
1 parent bb56834 commit a8c2f6d

5 files changed

Lines changed: 111 additions & 52 deletions

File tree

app/routers/authentication.py

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,119 @@
1-
from fastapi import APIRouter, Depends, HTTPException, status, Request
2-
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
3-
from sqlmodel.ext.asyncio.session import AsyncSession
1+
from typing import Annotated
2+
43
import jwt
4+
from fastapi import APIRouter, Depends, HTTPException, Request, status
5+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
56
from jwt.exceptions import InvalidTokenError
7+
from sqlmodel.ext.asyncio.session import AsyncSession
68

9+
from app.schemas import Token, TokenPayload
710
from app.services import auth
8-
from app.schemas import Token, TokenPayload, Community
911
from app.services.database.models import Community as DBCommunity
1012
from app.services.database.orm.community import get_community_by_username
1113

1214
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/authentication/token")
1315

16+
1417
def setup():
15-
router = APIRouter(prefix='/authentication', tags=['authentication'])
16-
async def authenticate_community( request: Request , username: str, password: str):
17-
# Valida se o usuário existe e se a senha está correta
18-
session: AsyncSession = request.app.db_session_factory
19-
found_community = await get_community_by_username(
20-
username=username,
21-
session= session
22-
)
23-
if not found_community or not auth.verify_password(password, found_community.password):
18+
router = APIRouter(prefix="/authentication", tags=["authentication"])
19+
20+
async def authenticate_community(
21+
request: Request, username: str, password: str
22+
):
23+
# Valida se o usuário existe e se a senha está correta
24+
session: AsyncSession = request.app.db_session_factory
25+
found_community = await get_community_by_username(
26+
username=username, session=session
27+
)
28+
if not found_community or not auth.verify_password(
29+
password, found_community.password
30+
):
2431
return None
25-
return found_community
32+
return found_community
2633

34+
async def get_current_community(
35+
request: Request,
36+
token: Annotated[str, Depends(oauth2_scheme)],
37+
) -> DBCommunity:
38+
credentials_exception = HTTPException(
39+
status_code=status.HTTP_401_UNAUTHORIZED,
40+
detail="Could not validate credentials",
41+
headers={"WWW-Authenticate": "Bearer"},
42+
)
2743

28-
#### Teste
44+
try:
45+
payload = jwt.decode(
46+
token, auth.SECRET_KEY, algorithms=[auth.ALGORITHM]
47+
)
48+
username = payload.get("sub")
49+
if username is None:
50+
raise credentials_exception
51+
token_data = TokenPayload(username=username)
52+
except InvalidTokenError:
53+
raise credentials_exception
54+
session: AsyncSession = request.app.db_session_factory
55+
community = await get_community_by_username(
56+
session=session, username=token_data.username
57+
)
58+
if community is None:
59+
raise credentials_exception
60+
61+
return community
62+
63+
async def get_current_active_community(
64+
current_user: Annotated[DBCommunity, Depends(get_current_community)],
65+
) -> DBCommunity:
66+
# A função simplesmente retorna o usuário.
67+
# Pode ser estendido futuramente para verificar um status "ativo".
68+
return current_user
69+
70+
# Teste
2971

3072
@router.post("/create_commumity")
31-
async def create_community(request: Request ):
73+
async def create_community(request: Request):
3274
password = "123Asd!@#"
33-
hashed_password=auth.hash_password(password)
34-
community = DBCommunity(username="username", email="username@test.com", password=hashed_password)
75+
hashed_password = auth.hash_password(password)
76+
community = DBCommunity(
77+
username="username",
78+
email="username@test.com",
79+
password=hashed_password,
80+
)
3581
session: AsyncSession = request.app.db_session_factory
3682
session.add(community)
3783
await session.commit()
3884
await session.refresh(community)
39-
return {'msg':'succes? '}
40-
#### Teste
85+
return {"msg": "succes? "}
86+
87+
# Teste
4188

4289
@router.post("/token", response_model=Token)
43-
async def login_for_access_token(request: Request , form_data: OAuth2PasswordRequestForm = Depends() ) :
90+
async def login_for_access_token(
91+
request: Request, form_data: OAuth2PasswordRequestForm = Depends()
92+
):
4493
# Rota de login: valida credenciais e retorna token JWT
45-
community = await authenticate_community( request, form_data.username, form_data.password)
94+
community = await authenticate_community(
95+
request, form_data.username, form_data.password
96+
)
4697
if not community:
4798
raise HTTPException(
4899
status_code=status.HTTP_401_UNAUTHORIZED,
49-
detail="Credenciais inválidas"
100+
detail="Credenciais inválidas",
50101
)
51102
payload = TokenPayload(username=community.username)
52103
token, expires_in = auth.create_access_token(data=payload)
53104
return {
54105
"access_token": token,
55106
"token_type": "Bearer",
56-
"expires_in": expires_in
107+
"expires_in": expires_in,
57108
}
58-
return router # Retorna o router configurado com as rotas de autenticação
109+
110+
@router.get("/me", response_model=DBCommunity)
111+
async def read_community_me(
112+
current_community: Annotated[
113+
DBCommunity, Depends(get_current_active_community)
114+
],
115+
):
116+
# Rota para obter informações do usuário autenticado
117+
return current_community
118+
119+
return router # Retorna o router configurado com as rotas de autenticação

app/routers/libraries/routes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from fastapi import APIRouter, Request, status
22
from pydantic import BaseModel
3-
from services.database.orm.library import insert_library
43

54
from app.schemas import Library as LibrarySchema
65
from app.services.database.models.libraries import Library
6+
from app.services.database.orm.library import insert_library
77

88

99
class LibraryResponse(BaseModel):

app/services/auth.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,36 @@
1-
#from passlib.context import CryptContext
2-
import bcrypt
1+
# from passlib.context import CryptContext
2+
import os
33
from datetime import datetime, timedelta, timezone
4-
from app.schemas import TokenPayload
4+
5+
import bcrypt
56
import jwt
6-
import os
7+
8+
from app.schemas import TokenPayload
79

810
SECRET_KEY = os.getenv("SECRET_KEY", "default_fallback_key")
911
ALGORITHM = os.getenv("ALGORITHM", "HS256")
1012
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", 20))
1113

12-
def verify_password(plain, hashed):
14+
15+
def verify_password(plain, hashed):
1316
# Verifica se a senha passada bate com a hash da comunidade
1417
return bcrypt.checkpw(
1518
bytes(plain, encoding="utf-8"),
1619
hashed,
1720
)
1821

19-
def hash_password(password):
22+
23+
def hash_password(password):
2024
# Retorna a senha em hash para salvar no banco de dados
2125
return bcrypt.hashpw(
2226
bytes(password, encoding="utf-8"),
2327
bcrypt.gensalt(),
2428
)
2529

2630

27-
28-
#pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
29-
30-
#def verify_password(plain, hashed):
31-
# # Verifica se a senha passada bate com a hash da comunidade
32-
# return pwd_context.verify(plain, hashed)
33-
#
34-
#def hash_password(password):
35-
# # Retorna a senha em hash para salvar no banco de dados
36-
# return pwd_context.hash(password)
37-
38-
def create_access_token(data: TokenPayload, expires_delta: timedelta | None = None):
31+
def create_access_token(
32+
data: TokenPayload, expires_delta: timedelta | None = None
33+
):
3934
"""
4035
Gera um token JWT contendo os dados do usuário (payload) e uma data de expiração.
4136
JWT specification says that there's a key sub (subject) that should be used to identify the user.
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
from typing import Optional
2+
23
from sqlmodel import select
34
from sqlmodel.ext.asyncio.session import AsyncSession
5+
46
from app.services.database.models import Community
57

68

79
async def get_community_by_username(
810
username: str,
9-
session: AsyncSession,) -> Optional[Community]:
11+
session: AsyncSession,
12+
) -> Optional[Community]:
1013
"""
1114
Busca e retorna um membro da comunidade pelo nome de usuário.
1215
Retorna None se o usuário não for encontrado.
1316
"""
1417
# Cria a declaração SQL para buscar a comunidade pelo nome de usuário
1518
statement = select(Community).where(Community.username == username)
16-
19+
1720
# Executa a declaração na sessão e retorna o primeiro resultado
1821
result = await session.exec(statement)
1922
community = result.first()
20-
21-
return community
23+
24+
return community

tests/test_subscriptions.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import pytest
22
import pytest_asyncio
3-
3+
from services.database.models import Community, Subscription
44
from sqlmodel import select
55
from sqlmodel.ext.asyncio.session import AsyncSession
66

7-
from services.database.models import Community, Subscription
8-
97

108
@pytest_asyncio.fixture
119
async def community(session: AsyncSession):
@@ -26,7 +24,9 @@ async def test_insert_subscription(session: AsyncSession, community: Community):
2624
session.add(subscription)
2725
await session.commit()
2826

29-
statement = select(Subscription).where(Subscription.email == "teste@teste.com")
27+
statement = select(Subscription).where(
28+
Subscription.email == "teste@teste.com"
29+
)
3030
result = await session.exec(statement)
3131
found = result.first()
3232

0 commit comments

Comments
 (0)