Skip to content

Commit 39942b4

Browse files
committed
Extract auth cookie session auth service
1 parent 6e65807 commit 39942b4

File tree

7 files changed

+110
-65
lines changed

7 files changed

+110
-65
lines changed

futuramaapi/apps/fastapi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ def _setup_middlewares(self) -> None:
8080
self.add_middleware(APIRequestsCounter)
8181

8282
def _setup_routers(self) -> None:
83-
from futuramaapi.routers import api_router, graphql_router, root_router, views_router # noqa: PLC0415
83+
from futuramaapi.routers import api_router, graphql_router, views_router # noqa: PLC0415
8484

85-
for router in [api_router, graphql_router, root_router, views_router]:
85+
for router in [api_router, graphql_router, views_router]:
8686
self.include_router(router)
8787

8888
def _setup_static(self) -> None:

futuramaapi/routers/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from .rest.links import router as links_router
1010
from .rest.notifications import router as notification_router
1111
from .rest.randoms import router as randoms_router
12-
from .rest.root import router as root_router
1312
from .rest.seasons import router as seasons_router
1413
from .rest.tokens import router as tokens_router
1514
from .rest.users import router as users_router
@@ -19,7 +18,6 @@
1918
__all__ = [
2019
"api_router",
2120
"graphql_router",
22-
"root_router",
2321
"views_router",
2422
]
2523

futuramaapi/routers/rest/root/__init__.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

futuramaapi/routers/rest/root/api.py

Lines changed: 0 additions & 30 deletions
This file was deleted.

futuramaapi/routers/rest/users/dependencies.py

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
from typing import Annotated, Literal
22

3-
from fastapi import Depends, Form, HTTPException, Request, status
4-
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
3+
from fastapi import Depends, Form, HTTPException, status
4+
from fastapi.security import OAuth2PasswordBearer
55
from pydantic import SecretStr
66
from sqlalchemy.ext.asyncio.session import AsyncSession
77

88
from futuramaapi.mixins.pydantic import DecodedTokenError
9-
from futuramaapi.repositories.models import AuthSessionModel
109
from futuramaapi.repositories.session import get_async_session
1110
from futuramaapi.routers.exceptions import ModelNotFoundError
1211
from futuramaapi.routers.rest.tokens.schemas import DecodedUserToken
13-
from futuramaapi.routers.rest.users.schemas import User, UserPasswordError, UserUpdateRequest
12+
from futuramaapi.routers.rest.users.schemas import User, UserUpdateRequest
1413

1514
_oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/tokens/users/auth")
1615

@@ -78,24 +77,3 @@ def password_from_form_data(
7877
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Passwords mismatch") from None
7978

8079
return UserUpdateRequest(password=password1.get_secret_value())
81-
82-
83-
async def cookie_user_from_form_data(
84-
request: Request,
85-
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
86-
session: AsyncSession = Depends(get_async_session), # noqa: B008
87-
) -> User | None:
88-
try:
89-
user: User = await User.auth(session, form_data.username, form_data.password)
90-
except (ModelNotFoundError, UserPasswordError):
91-
return None
92-
93-
auth_session: AuthSessionModel = AuthSessionModel()
94-
auth_session.user_id = user.id
95-
auth_session.ip_address = request.client.host
96-
97-
session.add(auth_session)
98-
await session.commit()
99-
100-
user._cookie_session = auth_session.key
101-
return user
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from typing import ClassVar
2+
3+
from fastapi import Request, status
4+
from fastapi.responses import RedirectResponse
5+
from pydantic import SecretStr
6+
from sqlalchemy import Result, Select, select
7+
from sqlalchemy.exc import NoResultFound
8+
9+
from futuramaapi.repositories.models import AuthSessionModel, UserModel
10+
from futuramaapi.routers.services import BaseSessionService
11+
12+
13+
class AuthCookieSessionUserService(BaseSessionService[RedirectResponse]):
14+
username: str
15+
password: SecretStr
16+
17+
cookie_auth_key: ClassVar[str] = "Authorization"
18+
19+
@property
20+
def request(self) -> Request:
21+
if self.context is None:
22+
raise AttributeError("Request is not defined.")
23+
24+
if "request" not in self.context:
25+
raise AttributeError("Request is not defined.")
26+
27+
return self.context["request"]
28+
29+
@property
30+
def __get_user_statement(self) -> Select[tuple[UserModel]]:
31+
return select(UserModel).where(UserModel.username == self.username)
32+
33+
async def _get_user(self) -> UserModel:
34+
result: Result[tuple[UserModel]] = await self.session.execute(self.__get_user_statement)
35+
try:
36+
return result.scalars().one()
37+
except NoResultFound:
38+
raise
39+
40+
async def _get_auth_session(self, user: UserModel, /) -> AuthSessionModel:
41+
auth_session: AuthSessionModel = AuthSessionModel()
42+
auth_session.user_id = user.id
43+
auth_session.ip_address = self.request.client.host
44+
45+
self.session.add(auth_session)
46+
await self.session.commit()
47+
48+
return auth_session
49+
50+
def _build_response(self, auth_session: AuthSessionModel, /) -> RedirectResponse:
51+
response: RedirectResponse = RedirectResponse(
52+
"/",
53+
status_code=status.HTTP_302_FOUND,
54+
)
55+
response.set_cookie(
56+
self.cookie_auth_key,
57+
value=auth_session.key,
58+
expires=AuthSessionModel.cookie_expiration_time,
59+
)
60+
return response
61+
62+
async def process(self, *args, **kwargs) -> RedirectResponse:
63+
try:
64+
user: UserModel = await self._get_user()
65+
except NoResultFound:
66+
return RedirectResponse(
67+
url="/auth",
68+
status_code=status.HTTP_302_FOUND,
69+
)
70+
71+
if not self.hasher.verify(self.password.get_secret_value(), user.password):
72+
return RedirectResponse(
73+
url="/auth",
74+
status_code=status.HTTP_302_FOUND,
75+
)
76+
77+
auth_session: AuthSessionModel = await self._get_auth_session(user)
78+
return self._build_response(auth_session)

futuramaapi/routers/views/api.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
from fastapi import APIRouter, Request, Response, status
1+
from typing import Annotated
2+
3+
from fastapi import APIRouter, Depends, Request, Response, status
24
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
35
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
6+
from fastapi.security import OAuth2PasswordRequestForm
47

58
from futuramaapi.routers.services.about.get_about import GetAboutService
9+
from futuramaapi.routers.services.auth.auth_cookie_session_user import AuthCookieSessionUserService
610
from futuramaapi.routers.services.auth.get_user_auth import GetUserAuthService
711
from futuramaapi.routers.services.auth.logout_cookie_session_user import LogoutCookieSessionUserService
812
from futuramaapi.routers.services.changelog.get_changelog import GetChangelogService
@@ -144,3 +148,25 @@ async def logout_cookie_session_user(
144148
},
145149
)
146150
return await service()
151+
152+
153+
@router.post(
154+
"/auth",
155+
include_in_schema=False,
156+
name="auth_cookie_session_user",
157+
)
158+
async def auth_cookie_session_user(
159+
request: Request,
160+
form_data: Annotated[
161+
OAuth2PasswordRequestForm,
162+
Depends(),
163+
],
164+
) -> RedirectResponse:
165+
service: AuthCookieSessionUserService = AuthCookieSessionUserService(
166+
username=form_data.username,
167+
password=form_data.password,
168+
context={
169+
"request": request,
170+
},
171+
)
172+
return await service()

0 commit comments

Comments
 (0)