Skip to content

Commit b4f21ad

Browse files
committed
Extract token refresh service
1 parent 83953b2 commit b4f21ad

File tree

4 files changed

+75
-85
lines changed

4 files changed

+75
-85
lines changed

futuramaapi/routers/rest/tokens/api.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
GetAuthUserTokenResponse,
1010
GetAuthUserTokenService,
1111
)
12-
13-
from .dependencies import refresh_token
14-
from .schemas import UserToken
12+
from futuramaapi.routers.services.tokens.get_refreshed_auth_user_token import (
13+
GetRefreshedAuthUserTokenRequest,
14+
GetRefreshedAuthUserTokenResponse,
15+
GetRefreshedAuthUserTokenService,
16+
)
1517

1618
router: APIRouter = APIRouter(
1719
prefix="/tokens",
@@ -57,15 +59,18 @@ async def get_user_auth_token(
5759
"model": UnauthorizedResponse,
5860
},
5961
},
60-
response_model=UserToken,
61-
name="user_token_auth_refresh",
62+
response_model=GetRefreshedAuthUserTokenResponse,
63+
name="get_refreshed_user_auth_token",
6264
)
63-
async def refresh_token_auth_user(
64-
token: Annotated[UserToken, Depends(refresh_token)],
65-
) -> UserToken:
65+
async def get_refreshed_user_auth_token(
66+
data: GetRefreshedAuthUserTokenRequest,
67+
) -> GetRefreshedAuthUserTokenResponse:
6668
"""Refresh JWT.
6769
6870
The Refresh JWT Token endpoint extends the lifespan of JSON Web Tokens (JWTs) without requiring user
6971
reauthentication. This API feature ensures uninterrupted access to secured resources.
7072
"""
71-
return token
73+
service: GetRefreshedAuthUserTokenService = GetRefreshedAuthUserTokenService(
74+
request_data=data,
75+
)
76+
return await service()

futuramaapi/routers/rest/tokens/dependencies.py

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
from typing import TYPE_CHECKING, ClassVar, Literal, Self
2-
3-
from pydantic import Field
1+
from typing import TYPE_CHECKING, Literal, Self
42

53
from futuramaapi.helpers.pydantic import BaseModel, BaseTokenModel
64
from futuramaapi.mixins.pydantic import BaseModelTokenMixin, DecodedTokenError
@@ -37,51 +35,3 @@ def decode(
3735
raise DecodedTokenError() from None
3836

3937
return decoded_token
40-
41-
42-
class UserTokenRefreshRequest(BaseModel):
43-
refresh_token: str
44-
45-
46-
class UserToken(BaseModel):
47-
access_token: str = Field(
48-
alias="access_token",
49-
description="Keep in mind, that the field is not in a camel case. That's the standard.",
50-
)
51-
refresh_token: str = Field(
52-
alias="refresh_token",
53-
description="Keep in mind, that the field is not in a camel case. That's the standard.",
54-
)
55-
56-
_default_access_seconds: ClassVar[int] = 15 * 60
57-
_default_refresh_seconds: ClassVar[int] = 5 * 24 * 60 * 60
58-
59-
@classmethod
60-
def from_user(
61-
cls,
62-
user: "User",
63-
/,
64-
) -> Self:
65-
access: DecodedUserToken = DecodedUserToken.from_user(user, "access")
66-
refresh: DecodedUserToken = DecodedUserToken.from_user(user, "refresh")
67-
return cls(
68-
access_token=access.tokenize(cls._default_access_seconds),
69-
refresh_token=refresh.tokenize(cls._default_refresh_seconds),
70-
)
71-
72-
def refresh(self) -> None:
73-
try:
74-
access: DecodedUserToken = DecodedUserToken.decode(self.access_token)
75-
except DecodedTokenError:
76-
raise
77-
78-
try:
79-
refresh: DecodedUserToken = DecodedUserToken.decode(self.refresh_token, allowed_type="refresh")
80-
except DecodedTokenError:
81-
raise
82-
83-
access.refresh_nonce()
84-
refresh.refresh_nonce()
85-
86-
self.access_token = access.tokenize(self._default_access_seconds)
87-
self.refresh_token = refresh.tokenize(self._default_refresh_seconds)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from typing import Any, ClassVar
2+
3+
import jwt
4+
from fastapi import HTTPException, status
5+
from jwt import ExpiredSignatureError, InvalidSignatureError, InvalidTokenError
6+
from pydantic import Field
7+
from sqlalchemy import Result, Select, select
8+
from sqlalchemy.exc import NoResultFound
9+
10+
from futuramaapi.core import settings
11+
from futuramaapi.helpers.pydantic import BaseModel
12+
from futuramaapi.repositories.models import UserModel
13+
from futuramaapi.routers.services import BaseSessionService
14+
15+
from .get_auth_user_token import GetAuthUserTokenResponse
16+
17+
18+
class GetRefreshedAuthUserTokenRequest(BaseModel):
19+
refresh_token: str = Field(
20+
alias="refresh_token",
21+
)
22+
23+
24+
class GetRefreshedAuthUserTokenResponse(GetAuthUserTokenResponse):
25+
pass
26+
27+
28+
class GetRefreshedAuthUserTokenService(BaseSessionService[GetRefreshedAuthUserTokenResponse]):
29+
request_data: GetRefreshedAuthUserTokenRequest
30+
31+
algorithm: ClassVar[str] = "HS256"
32+
33+
def __get_user_statement(self, decoded_token: dict[str, Any], /) -> Select[tuple[UserModel]]:
34+
return select(UserModel).where(UserModel.id == decoded_token["user"]["id"])
35+
36+
async def _get_user(self, decoded_token: dict[str, Any], /) -> UserModel:
37+
result: Result[tuple[UserModel]] = await self.session.execute(self.__get_user_statement(decoded_token))
38+
try:
39+
user: UserModel = result.scalars().one()
40+
except NoResultFound:
41+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None
42+
43+
return user
44+
45+
async def process(self, *args, **kwargs) -> GetRefreshedAuthUserTokenResponse:
46+
try:
47+
decoded_token: dict[str, Any] = jwt.decode(
48+
self.request_data.refresh_token,
49+
key=settings.secret_key.get_secret_value(),
50+
algorithms=[self.algorithm],
51+
)
52+
except (ExpiredSignatureError, InvalidSignatureError, InvalidTokenError):
53+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) from None
54+
55+
if decoded_token["type"] != "refresh":
56+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
57+
58+
user: UserModel = await self._get_user(decoded_token)
59+
60+
return GetRefreshedAuthUserTokenResponse.from_user_model(user)

0 commit comments

Comments
 (0)