fastapi-factory-utilities 0.4.0__py3-none-any.whl → 0.8.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fastapi-factory-utilities might be problematic. Click here for more details.

Files changed (72) hide show
  1. fastapi_factory_utilities/core/api/__init__.py +1 -1
  2. fastapi_factory_utilities/core/api/v1/sys/health.py +1 -1
  3. fastapi_factory_utilities/core/app/__init__.py +4 -4
  4. fastapi_factory_utilities/core/app/application.py +22 -26
  5. fastapi_factory_utilities/core/app/builder.py +8 -32
  6. fastapi_factory_utilities/core/app/fastapi_builder.py +3 -2
  7. fastapi_factory_utilities/core/exceptions.py +64 -29
  8. fastapi_factory_utilities/core/plugins/__init__.py +2 -31
  9. fastapi_factory_utilities/core/plugins/abstracts.py +40 -0
  10. fastapi_factory_utilities/core/plugins/aiopika/__init__.py +25 -0
  11. fastapi_factory_utilities/core/plugins/aiopika/abstract.py +48 -0
  12. fastapi_factory_utilities/core/plugins/aiopika/configs.py +85 -0
  13. fastapi_factory_utilities/core/plugins/aiopika/depends.py +20 -0
  14. fastapi_factory_utilities/core/plugins/aiopika/exceptions.py +29 -0
  15. fastapi_factory_utilities/core/plugins/aiopika/exchange.py +69 -0
  16. fastapi_factory_utilities/core/plugins/aiopika/listener/__init__.py +7 -0
  17. fastapi_factory_utilities/core/plugins/aiopika/listener/abstract.py +72 -0
  18. fastapi_factory_utilities/core/plugins/aiopika/message.py +86 -0
  19. fastapi_factory_utilities/core/plugins/aiopika/plugins.py +84 -0
  20. fastapi_factory_utilities/core/plugins/aiopika/publisher/__init__.py +7 -0
  21. fastapi_factory_utilities/core/plugins/aiopika/publisher/abstract.py +66 -0
  22. fastapi_factory_utilities/core/plugins/aiopika/queue.py +88 -0
  23. fastapi_factory_utilities/core/plugins/odm_plugin/__init__.py +14 -157
  24. fastapi_factory_utilities/core/plugins/odm_plugin/builder.py +3 -3
  25. fastapi_factory_utilities/core/plugins/odm_plugin/configs.py +1 -1
  26. fastapi_factory_utilities/core/plugins/odm_plugin/documents.py +1 -1
  27. fastapi_factory_utilities/core/plugins/odm_plugin/helpers.py +16 -0
  28. fastapi_factory_utilities/core/plugins/odm_plugin/plugins.py +155 -0
  29. fastapi_factory_utilities/core/plugins/odm_plugin/repositories.py +1 -0
  30. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/__init__.py +8 -121
  31. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/instruments/__init__.py +85 -0
  32. fastapi_factory_utilities/core/plugins/opentelemetry_plugin/plugins.py +137 -0
  33. fastapi_factory_utilities/core/plugins/taskiq_plugins/__init__.py +31 -0
  34. fastapi_factory_utilities/core/plugins/taskiq_plugins/configs.py +12 -0
  35. fastapi_factory_utilities/core/plugins/taskiq_plugins/depends.py +51 -0
  36. fastapi_factory_utilities/core/plugins/taskiq_plugins/exceptions.py +13 -0
  37. fastapi_factory_utilities/core/plugins/taskiq_plugins/plugin.py +41 -0
  38. fastapi_factory_utilities/core/plugins/taskiq_plugins/schedulers.py +187 -0
  39. fastapi_factory_utilities/core/protocols.py +1 -54
  40. fastapi_factory_utilities/core/security/__init__.py +5 -0
  41. fastapi_factory_utilities/core/security/abstracts.py +42 -0
  42. fastapi_factory_utilities/core/security/jwt/__init__.py +45 -0
  43. fastapi_factory_utilities/core/security/jwt/configs.py +32 -0
  44. fastapi_factory_utilities/core/security/jwt/decoders.py +130 -0
  45. fastapi_factory_utilities/core/security/jwt/exceptions.py +23 -0
  46. fastapi_factory_utilities/core/security/jwt/objects.py +107 -0
  47. fastapi_factory_utilities/core/security/jwt/services.py +176 -0
  48. fastapi_factory_utilities/core/security/jwt/stores.py +43 -0
  49. fastapi_factory_utilities/core/security/jwt/types.py +9 -0
  50. fastapi_factory_utilities/core/security/jwt/verifiers.py +46 -0
  51. fastapi_factory_utilities/core/security/kratos.py +43 -43
  52. fastapi_factory_utilities/core/services/hydra/__init__.py +10 -3
  53. fastapi_factory_utilities/core/services/hydra/services.py +112 -34
  54. fastapi_factory_utilities/core/services/status/__init__.py +2 -2
  55. fastapi_factory_utilities/core/services/status/exceptions.py +1 -1
  56. fastapi_factory_utilities/core/utils/status.py +2 -1
  57. fastapi_factory_utilities/core/utils/yaml_reader.py +1 -1
  58. fastapi_factory_utilities/example/app.py +15 -5
  59. fastapi_factory_utilities/example/entities/books/__init__.py +1 -1
  60. fastapi_factory_utilities/example/models/books/__init__.py +1 -1
  61. {fastapi_factory_utilities-0.4.0.dist-info → fastapi_factory_utilities-0.8.3.dist-info}/METADATA +14 -8
  62. fastapi_factory_utilities-0.8.3.dist-info/RECORD +111 -0
  63. {fastapi_factory_utilities-0.4.0.dist-info → fastapi_factory_utilities-0.8.3.dist-info}/WHEEL +1 -1
  64. fastapi_factory_utilities/core/app/plugin_manager/__init__.py +0 -15
  65. fastapi_factory_utilities/core/app/plugin_manager/exceptions.py +0 -33
  66. fastapi_factory_utilities/core/app/plugin_manager/plugin_manager.py +0 -190
  67. fastapi_factory_utilities/core/plugins/example/__init__.py +0 -31
  68. fastapi_factory_utilities/core/plugins/httpx_plugin/__init__.py +0 -31
  69. fastapi_factory_utilities/core/security/jwt.py +0 -158
  70. fastapi_factory_utilities-0.4.0.dist-info/RECORD +0 -82
  71. {fastapi_factory_utilities-0.4.0.dist-info → fastapi_factory_utilities-0.8.3.dist-info}/entry_points.txt +0 -0
  72. {fastapi_factory_utilities-0.4.0.dist-info → fastapi_factory_utilities-0.8.3.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,107 @@
1
+ """Provides the JWT bearer token objects."""
2
+
3
+ import datetime
4
+ from typing import Annotated, Any, ClassVar
5
+
6
+ from pydantic import BaseModel, BeforeValidator, ConfigDict, Field
7
+
8
+ from .types import OAuth2Audience, OAuth2Issuer, OAuth2Scope, OAuth2Subject
9
+
10
+
11
+ def validate_string_list_field(value: Any) -> list[str]:
12
+ """Validate a string list field.
13
+
14
+ Accepts either a space-separated string or a list of strings.
15
+ Converts all values to lowercase strings.
16
+
17
+ Args:
18
+ value: Either a string (space-separated) or a list of strings.
19
+
20
+ Returns:
21
+ A list of lowercase strings.
22
+
23
+ Raises:
24
+ ValueError: If the value is not a string or list, or if the resulting list is empty.
25
+ """
26
+ cleaned_value: list[str]
27
+ if isinstance(value, str):
28
+ cleaned_value = value.split(sep=" ")
29
+ elif isinstance(value, list):
30
+ cleaned_value = [str(item) for item in value if item is not None]
31
+ else:
32
+ raise ValueError(f"Invalid value type: expected str or list, got {type(value).__name__}")
33
+ cleaned_value = [item.lower() for item in cleaned_value if item.strip()]
34
+ if len(cleaned_value) == 0:
35
+ raise ValueError("Invalid value: empty list after processing")
36
+ return cleaned_value
37
+
38
+
39
+ def validate_timestamp_field(value: Any) -> datetime.datetime:
40
+ """Validate a timestamp field.
41
+
42
+ Accepts either a Unix timestamp (int or string) or a datetime object.
43
+ Converts timestamps to UTC datetime objects.
44
+
45
+ Args:
46
+ value: Either a Unix timestamp (int or string) or a datetime object.
47
+
48
+ Returns:
49
+ A datetime object in UTC timezone.
50
+
51
+ Raises:
52
+ ValueError: If the value cannot be converted to a datetime.
53
+ """
54
+ if isinstance(value, datetime.datetime):
55
+ return value
56
+ if isinstance(value, str):
57
+ try:
58
+ value = int(value)
59
+ except ValueError as e:
60
+ raise ValueError(f"Invalid timestamp string: {value}") from e
61
+ if isinstance(value, int):
62
+ try:
63
+ return datetime.datetime.fromtimestamp(value, tz=datetime.UTC)
64
+ except (ValueError, OSError) as e:
65
+ raise ValueError(f"Invalid timestamp value: {value}") from e
66
+ raise ValueError(f"Invalid value type: expected int, str, or datetime, got {type(value).__name__}")
67
+
68
+
69
+ class JWTPayload(BaseModel):
70
+ """JWT bearer token payload.
71
+
72
+ Represents a decoded JWT bearer token with OAuth2 claims.
73
+ All fields are required and validated according to OAuth2/JWT standards.
74
+
75
+ Attributes:
76
+ scope: List of OAuth2 scopes granted by the token.
77
+ aud: List of audiences (intended recipients) of the token.
78
+ iss: The issuer of the JWT token.
79
+ exp: The expiration date/time of the JWT token (UTC).
80
+ iat: The issued at date/time of the JWT token (UTC).
81
+ nbf: The not before date/time of the JWT token (UTC).
82
+ sub: The subject (user identifier) of the JWT token.
83
+ """
84
+
85
+ model_config: ClassVar[ConfigDict] = ConfigDict(
86
+ arbitrary_types_allowed=True,
87
+ extra="ignore",
88
+ frozen=True,
89
+ )
90
+
91
+ scope: Annotated[list[OAuth2Scope], BeforeValidator(validate_string_list_field)] = Field(
92
+ description="The scope of the JWT token."
93
+ )
94
+ aud: Annotated[list[OAuth2Audience], BeforeValidator(validate_string_list_field)] = Field(
95
+ description="The audiences of the JWT token."
96
+ )
97
+ iss: OAuth2Issuer = Field(description="The issuer of the JWT token.")
98
+ exp: Annotated[datetime.datetime, BeforeValidator(validate_timestamp_field)] = Field(
99
+ description="The expiration date of the JWT token."
100
+ )
101
+ iat: Annotated[datetime.datetime, BeforeValidator(validate_timestamp_field)] = Field(
102
+ description="The issued at date of the JWT token."
103
+ )
104
+ nbf: Annotated[datetime.datetime, BeforeValidator(validate_timestamp_field)] = Field(
105
+ description="The not before date of the JWT token."
106
+ )
107
+ sub: OAuth2Subject = Field(description="The subject of the JWT token.")
@@ -0,0 +1,176 @@
1
+ """Provides the JWT bearer authentication service."""
2
+
3
+ from http import HTTPStatus
4
+ from typing import Generic, TypeVar
5
+
6
+ from fastapi import HTTPException, Request
7
+
8
+ from fastapi_factory_utilities.core.security.abstracts import AuthenticationAbstract
9
+
10
+ from .configs import JWTBearerAuthenticationConfig
11
+ from .decoders import JWTBearerTokenDecoder, JWTBearerTokenDecoderAbstract
12
+ from .exceptions import InvalidJWTError, InvalidJWTPayploadError, MissingJWTCredentialsError, NotVerifiedJWTError
13
+ from .objects import JWTPayload
14
+ from .stores import JWKStoreAbstract
15
+ from .types import JWTToken
16
+ from .verifiers import JWTNoneVerifier, JWTVerifierAbstract
17
+
18
+ JWTBearerPayloadGeneric = TypeVar("JWTBearerPayloadGeneric", bound=JWTPayload)
19
+
20
+
21
+ class JWTAuthenticationServiceAbstract(AuthenticationAbstract, Generic[JWTBearerPayloadGeneric]):
22
+ """JWT authentication service.
23
+
24
+ This service is the orchestrator for the JWT bearer authentication.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ jwt_bearer_authentication_config: JWTBearerAuthenticationConfig,
30
+ jwt_verifier: JWTVerifierAbstract[JWTBearerPayloadGeneric],
31
+ jwt_decoder: JWTBearerTokenDecoderAbstract[JWTBearerPayloadGeneric],
32
+ raise_exception: bool = True,
33
+ ) -> None:
34
+ """Initialize the JWT bearer authentication service.
35
+
36
+ Args:
37
+ jwt_bearer_authentication_config (JWTBearerAuthenticationConfig): The JWT bearer authentication
38
+ configuration.
39
+ jwt_verifier (JWTVerifierAbstract): The JWT bearer token verifier.
40
+ jwt_decoder (JWTBearerTokenDecoderAbstract[JWTBearerPayloadGeneric]): The JWT bearer token decoder.
41
+ raise_exception (bool, optional): Whether to raise an exception or return None. Defaults to True.
42
+ """
43
+ # Configuration and Behavior
44
+ self._jwt_bearer_authentication_config: JWTBearerAuthenticationConfig = jwt_bearer_authentication_config
45
+ self._jwt_verifier: JWTVerifierAbstract[JWTBearerPayloadGeneric] = jwt_verifier
46
+ self._jwt_decoder: JWTBearerTokenDecoderAbstract[JWTBearerPayloadGeneric] = jwt_decoder
47
+ # Runtime variables
48
+ self._jwt: JWTToken | None = None
49
+ self._jwt_payload: JWTBearerPayloadGeneric | None = None
50
+ super().__init__(raise_exception=raise_exception)
51
+
52
+ @property
53
+ def verifier(self) -> JWTVerifierAbstract[JWTBearerPayloadGeneric]:
54
+ """Get the JWT bearer token verifier.
55
+
56
+ Returns:
57
+ JWTVerifierAbstract[JWTBearerPayloadGeneric]: The JWT bearer token verifier.
58
+ """
59
+ return self._jwt_verifier
60
+
61
+ @property
62
+ def decoder(self) -> JWTBearerTokenDecoderAbstract[JWTBearerPayloadGeneric]:
63
+ """Get the JWT bearer token decoder.
64
+
65
+ Returns:
66
+ JWTBearerTokenDecoderAbstract[JWTBearerPayloadGeneric]: The JWT bearer token decoder.
67
+ """
68
+ return self._jwt_decoder
69
+
70
+ @classmethod
71
+ def extract_authorization_header_from_request(cls, request: Request) -> str:
72
+ """Extract the authorization header from the request.
73
+
74
+ Args:
75
+ request (Request): The request object.
76
+
77
+ Returns:
78
+ str: The authorization header.
79
+
80
+ Raises:
81
+ MissingJWTCredentialsError: If the authorization header is missing.
82
+ """
83
+ authorization_header: str | None = request.headers.get("Authorization", None)
84
+ if not authorization_header:
85
+ raise MissingJWTCredentialsError(message="Missing Credentials")
86
+ return authorization_header
87
+
88
+ @classmethod
89
+ def extract_bearer_token_from_authorization_header(cls, authorization_header: str) -> JWTToken:
90
+ """Extract the bearer token from the authorization header.
91
+
92
+ Args:
93
+ authorization_header (str): The authorization header.
94
+
95
+ Returns:
96
+ JWTToken: The bearer token.
97
+
98
+ Raises:
99
+ InvalidJWTError: If the authorization header is invalid.
100
+ """
101
+ if not authorization_header.startswith("Bearer "):
102
+ raise InvalidJWTError(message="Invalid Credentials")
103
+ return JWTToken(authorization_header.split(sep=" ")[1])
104
+
105
+ @property
106
+ def payload(self) -> JWTBearerPayloadGeneric | None:
107
+ """Get the JWT bearer payload.
108
+
109
+ Returns:
110
+ JWTBearerPayloadGeneric | None: The JWT bearer payload, or None if not authenticated yet.
111
+ """
112
+ return self._jwt_payload
113
+
114
+ async def authenticate(self, request: Request) -> None:
115
+ """Authenticate the JWT bearer token.
116
+
117
+ Args:
118
+ request (Request): The request object.
119
+
120
+ Returns:
121
+ None: If the authentication is successful or not raise_exception is False.
122
+
123
+ Raises:
124
+ MissingJWTCredentialsError: If the authorization header is missing.
125
+ InvalidJWTError: If the authorization header is invalid.
126
+ InvalidJWTPayploadError: If the JWT bearer token payload is invalid.
127
+ NotVerifiedJWTError: If the JWT bearer token is not verified.
128
+ """
129
+ authorization_header: str
130
+ try:
131
+ authorization_header = self.extract_authorization_header_from_request(request=request)
132
+ self._jwt = self.extract_bearer_token_from_authorization_header(authorization_header=authorization_header)
133
+ except (MissingJWTCredentialsError, InvalidJWTError) as e:
134
+ return self.raise_exception(HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail=str(e)))
135
+
136
+ try:
137
+ self._jwt_payload = await self._jwt_decoder.decode_payload(jwt_token=self._jwt)
138
+ except (InvalidJWTError, InvalidJWTPayploadError) as e:
139
+ return self.raise_exception(HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=str(e)))
140
+
141
+ try:
142
+ await self._jwt_verifier.verify(jwt_token=self._jwt, jwt_payload=self._jwt_payload)
143
+ except NotVerifiedJWTError as e:
144
+ return self.raise_exception(HTTPException(status_code=HTTPStatus.FORBIDDEN, detail=str(e)))
145
+
146
+ return
147
+
148
+
149
+ class JWTAuthenticationService(JWTAuthenticationServiceAbstract[JWTPayload]):
150
+ """JWT bearer authentication service."""
151
+
152
+ def __init__(
153
+ self,
154
+ jwt_bearer_authentication_config: JWTBearerAuthenticationConfig,
155
+ jwks_store: JWKStoreAbstract,
156
+ raise_exception: bool = True,
157
+ ) -> None:
158
+ """Initialize the JWT bearer authentication service.
159
+
160
+ Don't enforce the public_key from configuration, for the developper to
161
+ provide it through the dependency injection freely from any source.
162
+
163
+ Args:
164
+ jwt_bearer_authentication_config (JWTBearerAuthenticationConfig): The JWT bearer authentication
165
+ configuration.
166
+ jwks_store (JWKStoreAbstract): The JWKS store.
167
+ raise_exception (bool, optional): Whether to raise an exception or return None. Defaults to True.
168
+ """
169
+ super().__init__(
170
+ jwt_bearer_authentication_config=jwt_bearer_authentication_config,
171
+ jwt_verifier=JWTNoneVerifier(),
172
+ jwt_decoder=JWTBearerTokenDecoder(
173
+ jwt_bearer_authentication_config=jwt_bearer_authentication_config, jwks_store=jwks_store
174
+ ),
175
+ raise_exception=raise_exception,
176
+ )
@@ -0,0 +1,43 @@
1
+ """Provides the JWK stores."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from asyncio import Lock
5
+
6
+ from jwt.api_jwk import PyJWK, PyJWKSet
7
+
8
+
9
+ class JWKStoreAbstract(ABC):
10
+ """JWK store abstract class."""
11
+
12
+ async def get_jwk(self, kid: str) -> PyJWK:
13
+ """Get the JWK from the store."""
14
+ return (await self.get_jwks())[kid]
15
+
16
+ @abstractmethod
17
+ async def get_jwks(self) -> PyJWKSet:
18
+ """Get the JWKS from the store."""
19
+ raise NotImplementedError()
20
+
21
+ @abstractmethod
22
+ async def store_jwks(self, jwks: PyJWKSet) -> None:
23
+ """Store the JWKS in the store."""
24
+ raise NotImplementedError()
25
+
26
+
27
+ class JWKStoreMemory(JWKStoreAbstract):
28
+ """JWK store in memory. Concurrent safe."""
29
+
30
+ def __init__(self) -> None:
31
+ """Initialize the JWK store in memory."""
32
+ self._jwks: PyJWKSet = PyJWKSet([])
33
+ self._lock: Lock = Lock()
34
+
35
+ async def get_jwks(self) -> PyJWKSet:
36
+ """Get the JWKS from the store."""
37
+ async with self._lock:
38
+ return self._jwks
39
+
40
+ async def store_jwks(self, jwks: PyJWKSet) -> None:
41
+ """Store the JWKS in the store."""
42
+ async with self._lock:
43
+ self._jwks = jwks
@@ -0,0 +1,9 @@
1
+ """Provides the JWT bearer token types."""
2
+
3
+ from typing import NewType
4
+
5
+ JWTToken = NewType("JWTToken", str)
6
+ OAuth2Scope = NewType("OAuth2Scope", str)
7
+ OAuth2Audience = NewType("OAuth2Audience", str)
8
+ OAuth2Issuer = NewType("OAuth2Issuer", str)
9
+ OAuth2Subject = NewType("OAuth2Subject", str)
@@ -0,0 +1,46 @@
1
+ """Provides the JWT bearer token validator."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Generic, TypeVar
5
+
6
+ from .objects import JWTPayload
7
+ from .types import JWTToken
8
+
9
+ JWTBearerPayloadGeneric = TypeVar("JWTBearerPayloadGeneric", bound=JWTPayload)
10
+
11
+
12
+ class JWTVerifierAbstract(ABC, Generic[JWTBearerPayloadGeneric]):
13
+ """JWT verifier."""
14
+
15
+ @abstractmethod
16
+ async def verify(
17
+ self,
18
+ jwt_token: JWTToken,
19
+ jwt_payload: JWTBearerPayloadGeneric,
20
+ ) -> None:
21
+ """Verify the JWT bearer token.
22
+
23
+ Args:
24
+ jwt_token (JWTToken): The JWT bearer token.
25
+ jwt_payload (JWTBearerPayloadGeneric): The JWT bearer payload.
26
+
27
+ Raises:
28
+ NotVerifiedJWTError: If the JWT bearer token is not verified.
29
+ """
30
+ raise NotImplementedError()
31
+
32
+
33
+ class JWTNoneVerifier(JWTVerifierAbstract[JWTPayload]):
34
+ """JWT none verifier."""
35
+
36
+ async def verify(self, jwt_token: JWTToken, jwt_payload: JWTPayload) -> None:
37
+ """Verify the JWT bearer token.
38
+
39
+ Args:
40
+ jwt_token (JWTToken): The JWT bearer token.
41
+ jwt_payload (JWTBearerPayload): The JWT bearer payload.
42
+
43
+ Raises:
44
+ NotVerifiedJWTError: If the JWT bearer token is not verified.
45
+ """
46
+ return
@@ -1,41 +1,37 @@
1
1
  """Provide Kratos Session and Identity classes."""
2
2
 
3
- from enum import StrEnum
4
- from typing import Annotated
3
+ from http import HTTPStatus
5
4
 
6
- from fastapi import Depends, HTTPException, Request
5
+ from fastapi import HTTPException, Request
7
6
 
7
+ from fastapi_factory_utilities.core.security.abstracts import AuthenticationAbstract
8
8
  from fastapi_factory_utilities.core.services.kratos import (
9
9
  KratosOperationError,
10
10
  KratosService,
11
11
  KratosSessionInvalidError,
12
12
  KratosSessionObject,
13
- depends_kratos_service,
14
13
  )
15
14
 
16
15
 
17
- class KratosSessionAuthenticationErrors(StrEnum):
18
- """Kratos Session Authentication Errors."""
19
-
20
- MISSING_CREDENTIALS = "Missing Credentials"
21
- INVALID_CREDENTIALS = "Invalid Credentials"
22
- INTERNAL_SERVER_ERROR = "Internal Server Error"
23
-
24
-
25
- class KratosSessionAuthentication:
16
+ class KratosSessionAuthenticationService(AuthenticationAbstract):
26
17
  """Kratos Session class."""
27
18
 
28
19
  DEFAULT_COOKIE_NAME: str = "ory_kratos_session"
29
20
 
30
- def __init__(self, cookie_name: str = DEFAULT_COOKIE_NAME, raise_exception: bool = True) -> None:
21
+ def __init__(
22
+ self, kratos_service: KratosService, cookie_name: str = DEFAULT_COOKIE_NAME, raise_exception: bool = True
23
+ ) -> None:
31
24
  """Initialize the KratosSessionAuthentication class.
32
25
 
33
26
  Args:
34
- cookie_name (str): Name of the cookie to extract the session
35
- raise_exception (bool): Whether to raise an exception or return None
27
+ kratos_service (KratosService): Kratos service object.
28
+ cookie_name (str): Name of the cookie to extract the session.
29
+ raise_exception (bool): Whether to raise an exception or return None.
36
30
  """
31
+ self._kratos_service: KratosService = kratos_service
37
32
  self._cookie_name: str = cookie_name
38
- self._raise_exception: bool = raise_exception
33
+ self._session: KratosSessionObject
34
+ super().__init__(raise_exception=raise_exception)
39
35
 
40
36
  def _extract_cookie(self, request: Request) -> str | None:
41
37
  """Extract the cookie from the request.
@@ -51,9 +47,16 @@ class KratosSessionAuthentication:
51
47
  """
52
48
  return request.cookies.get(self._cookie_name, None)
53
49
 
54
- async def __call__(
55
- self, request: Request, kratos_service: Annotated[KratosService, Depends(depends_kratos_service)]
56
- ) -> KratosSessionObject | KratosSessionAuthenticationErrors:
50
+ @property
51
+ def session(self) -> KratosSessionObject:
52
+ """Get the Kratos session.
53
+
54
+ Returns:
55
+ KratosSessionObject: Kratos session object.
56
+ """
57
+ return self._session
58
+
59
+ async def authenticate(self, request: Request) -> None:
57
60
  """Extract the Kratos session from the request.
58
61
 
59
62
  Args:
@@ -61,38 +64,35 @@ class KratosSessionAuthentication:
61
64
  kratos_service (KratosService): Kratos service object.
62
65
 
63
66
  Returns:
64
- KratosSessionObject | KratosSessionAuthenticationErrors: Kratos session object or error.
67
+ None: If the authentication is successful or not raise_exception is False.
65
68
 
66
69
  Raises:
67
70
  HTTPException: If the session is invalid and raise_exception is True.
68
71
  """
69
72
  cookie: str | None = self._extract_cookie(request)
70
73
  if not cookie:
71
- if self._raise_exception:
72
- raise HTTPException(
73
- status_code=401,
74
- detail=KratosSessionAuthenticationErrors.MISSING_CREDENTIALS,
74
+ return self.raise_exception(
75
+ HTTPException(
76
+ status_code=HTTPStatus.UNAUTHORIZED,
77
+ detail="Missing Credentials",
75
78
  )
76
- else:
77
- return KratosSessionAuthenticationErrors.MISSING_CREDENTIALS
79
+ )
78
80
 
79
81
  try:
80
- session: KratosSessionObject = await kratos_service.whoami(cookie_value=cookie)
81
- except KratosSessionInvalidError as e:
82
- if self._raise_exception:
83
- raise HTTPException(
84
- status_code=401,
82
+ self._session = await self._kratos_service.whoami(cookie_value=cookie)
83
+ except KratosSessionInvalidError:
84
+ return self.raise_exception(
85
+ HTTPException(
86
+ status_code=HTTPStatus.UNAUTHORIZED,
85
87
  detail="Invalid Credentials",
86
- ) from e
87
- else:
88
- return KratosSessionAuthenticationErrors.INVALID_CREDENTIALS
89
- except KratosOperationError as e:
90
- if self._raise_exception:
91
- raise HTTPException(
92
- status_code=500,
88
+ )
89
+ )
90
+ except KratosOperationError:
91
+ return self.raise_exception(
92
+ HTTPException(
93
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
93
94
  detail="Internal Server Error",
94
- ) from e
95
- else:
96
- return KratosSessionAuthenticationErrors.INTERNAL_SERVER_ERROR
95
+ )
96
+ )
97
97
 
98
- return session
98
+ return
@@ -2,12 +2,19 @@
2
2
 
3
3
  from .exceptions import HydraOperationError, HydraTokenInvalidError
4
4
  from .objects import HydraTokenIntrospectObject
5
- from .services import HydraService, depends_hydra_service
5
+ from .services import (
6
+ HydraIntrospectService,
7
+ HydraOAuth2ClientCredentialsService,
8
+ depends_hydra_introspect_service,
9
+ depends_hydra_oauth2_client_credentials_service,
10
+ )
6
11
 
7
12
  __all__: list[str] = [
13
+ "HydraIntrospectService",
14
+ "HydraOAuth2ClientCredentialsService",
8
15
  "HydraOperationError",
9
- "HydraService",
10
16
  "HydraTokenIntrospectObject",
11
17
  "HydraTokenInvalidError",
12
- "depends_hydra_service",
18
+ "depends_hydra_introspect_service",
19
+ "depends_hydra_oauth2_client_credentials_service",
13
20
  ]