Skip to content

Commit c82ed8b

Browse files
committed
refactor: Update imports and reorganize module structure for better organization
1 parent d7da73d commit c82ed8b

8 files changed

Lines changed: 76 additions & 6 deletions

File tree

app/core/security.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
1010

1111

12+
# Algorithm use for encoding & decoding of JWT tokens
1213
ALGORITHM = "HS256"
1314

1415

File renamed without changes.

app/db/dao/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"""DAO classes."""
1+
"""Data Access Objects (DAO) classes."""

app/db/dependencies.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
from collections.abc import AsyncGenerator
22
from typing import Annotated
33

4+
import jwt
5+
from fastapi import Depends, HTTPException, status
6+
from fastapi.security import OAuth2PasswordBearer
7+
from jwt.exceptions import InvalidTokenError
8+
from pydantic import ValidationError
49
from sqlalchemy.ext.asyncio import AsyncSession
510
from starlette.requests import Request
611
from taskiq import TaskiqDepends
712

13+
from app.core import security
14+
from app.core.config import settings
15+
from app.db.models.jwt_token import TokenPayload
16+
from app.db.models.users import User
17+
18+
reusable_oauth2 = OAuth2PasswordBearer(tokenUrl=f"{settings.API_BASE_PATH}/login/access-token")
19+
820

921
async def get_db_session(
1022
request: Annotated[Request, TaskiqDepends()],
@@ -22,3 +34,61 @@ async def get_db_session(
2234
finally:
2335
await session.commit()
2436
await session.close()
37+
38+
39+
SessionDep = Annotated[AsyncSession, Depends(get_db_session)]
40+
TokenDep = Annotated[str, Depends(reusable_oauth2)]
41+
42+
43+
async def get_current_user(session: SessionDep, token: TokenDep) -> User:
44+
"""
45+
Retrieve the currently authenticated user from the database based on the JWT token.
46+
47+
Args:
48+
session: Database session dependency.
49+
token: JWT bearer token dependency.
50+
51+
Returns:
52+
The User object corresponding to the token subject.
53+
54+
Raises:
55+
HTTPException: If the token is invalid, user does not exist, or is inactive.
56+
"""
57+
try:
58+
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[security.ALGORITHM])
59+
token_data = TokenPayload(**payload)
60+
except (InvalidTokenError, ValidationError) as err:
61+
# Raised if token is tampered, expired, or payload is malformed
62+
raise HTTPException(
63+
status_code=status.HTTP_403_FORBIDDEN,
64+
detail="Could not validate credentials",
65+
) from err
66+
67+
user: User | None = await session.get(User, token_data.sub)
68+
if not user:
69+
raise HTTPException(status_code=404, detail="User not found")
70+
if not user.is_active:
71+
raise HTTPException(status_code=400, detail="Inactive user")
72+
73+
return user
74+
75+
76+
CurrentUser = Annotated[User, Depends(get_current_user)]
77+
78+
79+
async def get_current_active_superuser(current_user: CurrentUser) -> User:
80+
"""
81+
Ensure that the current user has superuser privileges.
82+
83+
Args:
84+
current_user: The currently authenticated user dependency.
85+
86+
Returns:
87+
The current user if they are a superuser.
88+
89+
Raises:
90+
HTTPException: If the user is not a superuser.
91+
"""
92+
if not current_user.is_superuser:
93+
raise HTTPException(status_code=403, detail="The user doesn't have enough privileges")
94+
return current_user

app/db/models/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""app models."""
1+
"""Application models."""
22

33
import pkgutil
44
from pathlib import Path
File renamed without changes.

app/web/lifespan.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@
2424
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
2525

2626
from app.core.config import settings
27-
from app.core.logger import configure_logging
27+
from app.core.tkq import broker
2828
from app.services.kafka.lifespan import init_kafka, shutdown_kafka
2929
from app.services.rabbit.lifespan import init_rabbit, shutdown_rabbit
3030
from app.services.redis.lifespan import init_redis, shutdown_redis
31-
from app.tkq import broker
3231

3332

3433
def _setup_db(app: FastAPI) -> None: # pragma: no cover
@@ -44,6 +43,7 @@ def _setup_db(app: FastAPI) -> None: # pragma: no cover
4443
engine = create_async_engine(str(settings.db_url), echo=settings.DB_ECHO)
4544
session_factory = async_sessionmaker(
4645
engine,
46+
# See https://fastapi-users.github.io/fastapi-users/latest/configuration/databases/sqlalchemy/#asynchronous-driver
4747
expire_on_commit=False,
4848
)
4949
app.state.db_engine = engine
@@ -148,7 +148,6 @@ async def lifespan_setup(
148148
app.middleware_stack = None
149149
if not broker.is_worker_process:
150150
await broker.startup()
151-
configure_logging()
152151
_setup_db(app)
153152
setup_opentelemetry(app)
154153
init_redis(app)

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from app.db.dependencies import get_db_session
2424
from app.db.meta import meta
2525
from app.db.models import load_all_models
26-
from app.db.utils import create_database, drop_database
26+
from app.db.setup import create_database, drop_database
2727
from app.services.kafka.dependencies import get_kafka_producer
2828
from app.services.kafka.lifespan import init_kafka, shutdown_kafka
2929
from app.services.rabbit.dependencies import get_rmq_channel_pool

0 commit comments

Comments
 (0)