Skip to content

Commit fd2d3ba

Browse files
committed
feat: add logout endpoint
1 parent dc6fc64 commit fd2d3ba

5 files changed

Lines changed: 52 additions & 14 deletions

File tree

src/core/authorization/dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async def dependency(
2828
),
2929
) -> dict:
3030
allowed = await authorization_service.can(
31-
subject=str(current_user["id"]),
31+
subject=str(current_user.get("id")),
3232
resource=resource,
3333
action=action,
3434
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from uuid import UUID
2+
3+
from pydantic import BaseModel
4+
5+
6+
class LogoutUserCommand(BaseModel):
7+
user_id: str
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from src.modules.user.application.logout_user.command import LogoutUserCommand
2+
from src.modules.user.domain.repositories.refresh_token_repository import (
3+
RefreshTokenRepository,
4+
)
5+
from src.shared.unit_of_work import UnitOfWork
6+
7+
8+
class LogoutUserCommandHandler:
9+
def __init__(
10+
self, refresh_token_repository: RefreshTokenRepository, unit_of_work: UnitOfWork
11+
):
12+
self._refresh_token_repository = refresh_token_repository
13+
self._unit_of_work = unit_of_work
14+
15+
async def excute(self, command: LogoutUserCommand) -> None:
16+
async with self._unit_of_work:
17+
self._refresh_token_repository.revoke_by_user_id(command.user_id)
18+
await self._unit_of_work.commit()

src/modules/user/presentation/dependency.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from src.core.database.session import get_db, get_unit_of_work
77
from src.modules.user.application.detail_user.handler import DetailUserQueryHandler
88
from src.modules.user.application.login_user.handler import LoginUserCommandHandler
9+
from src.modules.user.application.logout_user.handler import LogoutUserCommandHandler
910
from src.modules.user.application.refresh_token.handler import (
1011
RefreshTokenCommandHandler,
1112
)
@@ -62,3 +63,10 @@ def get_refresh_token_handler(
6263
unit_of_work: UnitOfWork = Depends(get_unit_of_work),
6364
) -> RefreshTokenCommandHandler:
6465
return RefreshTokenCommandHandler(refresh_token_repo, unit_of_work)
66+
67+
68+
def get_logout_handler(
69+
refresh_token_repo: RefreshTokenRepository = Depends(get_refresh_token_repository),
70+
unit_of_work: UnitOfWork = Depends(get_unit_of_work),
71+
) -> LogoutUserCommandHandler:
72+
return LogoutUserCommandHandler(refresh_token_repo, unit_of_work)

src/modules/user/presentation/routers/user_router.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
from fastapi import APIRouter, Depends, HTTPException, status
2-
from fastapi.security import OAuth2PasswordRequestForm
3-
4-
from src.core.authorization.dependencies import require_permission
5-
from src.core.authorization.permissions import ME_ACTION, USER_RESOURCE
1+
from fastapi import APIRouter, Depends, HTTPException, status
2+
from fastapi.security import OAuth2PasswordRequestForm
3+
4+
from src.core.authorization.dependencies import require_permission
5+
from src.core.authorization.permissions import ME_ACTION, UPDATE_ACTION, USER_RESOURCE
66
from src.modules.user.application.detail_user.handler import DetailUserQueryHandler
77
from src.modules.user.application.detail_user.query import DetailUserQuery
88
from src.modules.user.application.login_user.command import LoginUserCommand
99
from src.modules.user.application.login_user.handler import LoginUserCommandHandler
10+
from src.modules.user.application.logout_user.command import LogoutUserCommand
11+
from src.modules.user.application.logout_user.handler import LogoutUserCommandHandler
1012
from src.modules.user.application.refresh_token.command import RefreshTokenCommand
1113
from src.modules.user.application.refresh_token.handler import (
1214
RefreshTokenCommandHandler,
@@ -18,6 +20,7 @@
1820
from src.modules.user.domain.exceptions.user_exception import UserAlreadyExistsError
1921
from src.modules.user.presentation.dependency import (
2022
get_login_handler,
23+
get_logout_handler,
2124
get_refresh_token_handler,
2225
get_register_handler,
2326
get_user_detail_handler,
@@ -69,9 +72,6 @@ async def refresh_token(
6972
handler: RefreshTokenCommandHandler = Depends(get_refresh_token_handler),
7073
):
7174
try:
72-
# Note: In strict rotation, we might not even require a valid access token here,
73-
# just the refresh token. But requiring it adds a layer of security.
74-
# We pass current_user["id"] to ensure the RT belongs to the user making the request.
7575
result = await handler.execute(
7676
RefreshTokenCommand(
7777
token=request.refresh_token,
@@ -87,10 +87,10 @@ async def refresh_token(
8787

8888

8989
@router.get("/me")
90-
async def get_me(
91-
current_user: dict = Depends(require_permission(USER_RESOURCE, ME_ACTION)),
92-
handler: DetailUserQueryHandler = Depends(get_user_detail_handler),
93-
):
90+
async def get_me(
91+
current_user: dict = Depends(require_permission(USER_RESOURCE, ME_ACTION)),
92+
handler: DetailUserQueryHandler = Depends(get_user_detail_handler),
93+
):
9494
user = await handler.execute(
9595
DetailUserQuery(
9696
user_id=current_user.get("id"),
@@ -99,4 +99,9 @@ async def get_me(
9999
return {"id": str(user.id), "email": user.email}
100100

101101

102-
# TODO: need logout endpoint
102+
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
103+
async def logout(
104+
current_user: dict = Depends(require_permission(USER_RESOURCE, UPDATE_ACTION)),
105+
handler: LogoutUserCommandHandler = Depends(get_logout_handler),
106+
):
107+
await handler.excute(LogoutUserCommand(user_id=str(current_user.get("id"))))

0 commit comments

Comments
 (0)