maleo-foundation 0.3.45__py3-none-any.whl → 0.3.47__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.
Files changed (135) hide show
  1. maleo_foundation/authentication.py +24 -13
  2. maleo_foundation/authorization.py +2 -1
  3. maleo_foundation/client/manager.py +22 -21
  4. maleo_foundation/client/services/__init__.py +16 -7
  5. maleo_foundation/client/services/encryption/__init__.py +13 -4
  6. maleo_foundation/client/services/encryption/aes.py +41 -36
  7. maleo_foundation/client/services/encryption/rsa.py +50 -50
  8. maleo_foundation/client/services/hash/__init__.py +19 -6
  9. maleo_foundation/client/services/hash/bcrypt.py +20 -18
  10. maleo_foundation/client/services/hash/hmac.py +20 -17
  11. maleo_foundation/client/services/hash/sha256.py +18 -15
  12. maleo_foundation/client/services/key.py +50 -42
  13. maleo_foundation/client/services/signature.py +46 -42
  14. maleo_foundation/client/services/token.py +49 -58
  15. maleo_foundation/constants.py +13 -14
  16. maleo_foundation/enums.py +14 -13
  17. maleo_foundation/expanded_types/__init__.py +2 -3
  18. maleo_foundation/expanded_types/client.py +30 -34
  19. maleo_foundation/expanded_types/encryption/__init__.py +2 -1
  20. maleo_foundation/expanded_types/encryption/aes.py +7 -5
  21. maleo_foundation/expanded_types/encryption/rsa.py +7 -5
  22. maleo_foundation/expanded_types/general.py +13 -11
  23. maleo_foundation/expanded_types/hash.py +7 -5
  24. maleo_foundation/expanded_types/key.py +8 -6
  25. maleo_foundation/expanded_types/service.py +30 -34
  26. maleo_foundation/expanded_types/signature.py +7 -5
  27. maleo_foundation/expanded_types/token.py +7 -5
  28. maleo_foundation/extended_types.py +4 -3
  29. maleo_foundation/managers/cache.py +2 -1
  30. maleo_foundation/managers/client/base.py +25 -12
  31. maleo_foundation/managers/client/google/base.py +11 -4
  32. maleo_foundation/managers/client/google/parameter.py +9 -11
  33. maleo_foundation/managers/client/google/secret.py +53 -35
  34. maleo_foundation/managers/client/google/storage.py +52 -22
  35. maleo_foundation/managers/client/google/subscription.py +37 -39
  36. maleo_foundation/managers/client/maleo.py +18 -23
  37. maleo_foundation/managers/configuration.py +5 -9
  38. maleo_foundation/managers/credential.py +14 -17
  39. maleo_foundation/managers/db.py +51 -40
  40. maleo_foundation/managers/middleware.py +9 -9
  41. maleo_foundation/managers/service.py +47 -54
  42. maleo_foundation/middlewares/authentication.py +29 -54
  43. maleo_foundation/middlewares/base.py +83 -72
  44. maleo_foundation/middlewares/cors.py +8 -7
  45. maleo_foundation/models/__init__.py +2 -1
  46. maleo_foundation/models/responses.py +57 -29
  47. maleo_foundation/models/schemas/__init__.py +2 -1
  48. maleo_foundation/models/schemas/encryption.py +5 -2
  49. maleo_foundation/models/schemas/general.py +38 -18
  50. maleo_foundation/models/schemas/hash.py +2 -1
  51. maleo_foundation/models/schemas/key.py +5 -2
  52. maleo_foundation/models/schemas/parameter.py +45 -15
  53. maleo_foundation/models/schemas/result.py +35 -20
  54. maleo_foundation/models/schemas/signature.py +5 -2
  55. maleo_foundation/models/schemas/token.py +5 -2
  56. maleo_foundation/models/table.py +33 -27
  57. maleo_foundation/models/transfers/__init__.py +2 -1
  58. maleo_foundation/models/transfers/general/__init__.py +2 -1
  59. maleo_foundation/models/transfers/general/configurations/__init__.py +10 -4
  60. maleo_foundation/models/transfers/general/configurations/cache/__init__.py +3 -2
  61. maleo_foundation/models/transfers/general/configurations/cache/redis.py +13 -5
  62. maleo_foundation/models/transfers/general/configurations/client/__init__.py +5 -1
  63. maleo_foundation/models/transfers/general/configurations/client/maleo.py +38 -12
  64. maleo_foundation/models/transfers/general/configurations/database.py +5 -2
  65. maleo_foundation/models/transfers/general/configurations/middleware.py +22 -15
  66. maleo_foundation/models/transfers/general/configurations/service.py +2 -1
  67. maleo_foundation/models/transfers/general/credentials.py +2 -1
  68. maleo_foundation/models/transfers/general/database.py +11 -4
  69. maleo_foundation/models/transfers/general/key.py +13 -4
  70. maleo_foundation/models/transfers/general/request.py +28 -9
  71. maleo_foundation/models/transfers/general/settings.py +12 -22
  72. maleo_foundation/models/transfers/general/signature.py +4 -2
  73. maleo_foundation/models/transfers/general/token.py +34 -27
  74. maleo_foundation/models/transfers/parameters/__init__.py +2 -1
  75. maleo_foundation/models/transfers/parameters/client.py +15 -19
  76. maleo_foundation/models/transfers/parameters/encryption/__init__.py +2 -1
  77. maleo_foundation/models/transfers/parameters/encryption/aes.py +7 -5
  78. maleo_foundation/models/transfers/parameters/encryption/rsa.py +7 -5
  79. maleo_foundation/models/transfers/parameters/general.py +15 -13
  80. maleo_foundation/models/transfers/parameters/hash/__init__.py +2 -1
  81. maleo_foundation/models/transfers/parameters/hash/bcrypt.py +5 -5
  82. maleo_foundation/models/transfers/parameters/hash/hmac.py +6 -6
  83. maleo_foundation/models/transfers/parameters/hash/sha256.py +5 -5
  84. maleo_foundation/models/transfers/parameters/key.py +9 -8
  85. maleo_foundation/models/transfers/parameters/service.py +42 -48
  86. maleo_foundation/models/transfers/parameters/signature.py +7 -4
  87. maleo_foundation/models/transfers/parameters/token.py +10 -10
  88. maleo_foundation/models/transfers/results/__init__.py +2 -1
  89. maleo_foundation/models/transfers/results/client/__init__.py +2 -1
  90. maleo_foundation/models/transfers/results/client/controllers/__init__.py +2 -1
  91. maleo_foundation/models/transfers/results/client/controllers/http.py +10 -7
  92. maleo_foundation/models/transfers/results/client/service.py +12 -6
  93. maleo_foundation/models/transfers/results/encryption/__init__.py +2 -1
  94. maleo_foundation/models/transfers/results/encryption/aes.py +13 -5
  95. maleo_foundation/models/transfers/results/encryption/rsa.py +12 -4
  96. maleo_foundation/models/transfers/results/hash.py +7 -3
  97. maleo_foundation/models/transfers/results/key.py +18 -6
  98. maleo_foundation/models/transfers/results/service/__init__.py +2 -3
  99. maleo_foundation/models/transfers/results/service/controllers/__init__.py +2 -1
  100. maleo_foundation/models/transfers/results/service/controllers/rest.py +14 -11
  101. maleo_foundation/models/transfers/results/service/general.py +16 -10
  102. maleo_foundation/models/transfers/results/signature.py +12 -4
  103. maleo_foundation/models/transfers/results/token.py +10 -4
  104. maleo_foundation/rest_controller_result.py +23 -21
  105. maleo_foundation/types.py +15 -14
  106. maleo_foundation/utils/__init__.py +2 -1
  107. maleo_foundation/utils/cache.py +10 -13
  108. maleo_foundation/utils/client.py +25 -12
  109. maleo_foundation/utils/controller.py +59 -37
  110. maleo_foundation/utils/dependencies/__init__.py +2 -1
  111. maleo_foundation/utils/dependencies/auth.py +5 -12
  112. maleo_foundation/utils/dependencies/context.py +3 -4
  113. maleo_foundation/utils/exceptions.py +50 -28
  114. maleo_foundation/utils/extractor.py +18 -6
  115. maleo_foundation/utils/formatter/__init__.py +2 -1
  116. maleo_foundation/utils/formatter/case.py +5 -4
  117. maleo_foundation/utils/loaders/__init__.py +2 -1
  118. maleo_foundation/utils/loaders/credential/__init__.py +2 -1
  119. maleo_foundation/utils/loaders/credential/google.py +29 -15
  120. maleo_foundation/utils/loaders/json.py +3 -2
  121. maleo_foundation/utils/loaders/key/__init__.py +2 -1
  122. maleo_foundation/utils/loaders/key/rsa.py +26 -13
  123. maleo_foundation/utils/loaders/yaml.py +2 -1
  124. maleo_foundation/utils/logging.py +70 -46
  125. maleo_foundation/utils/merger.py +7 -9
  126. maleo_foundation/utils/query.py +41 -34
  127. maleo_foundation/utils/repository.py +28 -13
  128. maleo_foundation/utils/searcher.py +4 -6
  129. {maleo_foundation-0.3.45.dist-info → maleo_foundation-0.3.47.dist-info}/METADATA +14 -1
  130. maleo_foundation-0.3.47.dist-info/RECORD +137 -0
  131. maleo_foundation/expanded_types/repository.py +0 -68
  132. maleo_foundation/models/transfers/results/service/repository.py +0 -39
  133. maleo_foundation-0.3.45.dist-info/RECORD +0 -139
  134. {maleo_foundation-0.3.45.dist-info → maleo_foundation-0.3.47.dist-info}/WHEEL +0 -0
  135. {maleo_foundation-0.3.45.dist-info → maleo_foundation-0.3.47.dist-info}/top_level.txt +0 -0
@@ -7,98 +7,73 @@ from maleo_foundation.authentication import Token, Credentials, User
7
7
  from maleo_foundation.enums import BaseEnums
8
8
  from maleo_foundation.client.manager import MaleoFoundationClientManager
9
9
  from maleo_foundation.models.schemas import BaseGeneralSchemas
10
- from maleo_foundation.models.transfers.parameters.token \
11
- import MaleoFoundationTokenParametersTransfers
10
+ from maleo_foundation.models.transfers.parameters.token import (
11
+ MaleoFoundationTokenParametersTransfers,
12
+ )
12
13
  from maleo_foundation.utils.exceptions import BaseExceptions
13
- from maleo_foundation.utils.logging import MiddlewareLogger
14
+
14
15
 
15
16
  class Backend(AuthenticationBackend):
16
17
  def __init__(
17
18
  self,
18
19
  keys: BaseGeneralSchemas.RSAKeys,
19
- maleo_foundation: MaleoFoundationClientManager
20
+ maleo_foundation: MaleoFoundationClientManager,
20
21
  ):
21
22
  super().__init__()
22
23
  self._keys = keys
23
24
  self._maleo_foundation = maleo_foundation
24
25
 
25
- async def authenticate(
26
- self,
27
- conn: HTTPConnection
28
- ) -> Tuple[Credentials, User]:
26
+ async def authenticate(self, conn: HTTPConnection) -> Tuple[Credentials, User]:
29
27
  if "Authorization" in conn.headers:
30
28
  auth = conn.headers["Authorization"]
31
29
  parts = auth.split()
32
30
  if len(parts) != 2 or parts[0] != "Bearer":
33
31
  raise AuthenticationError("Invalid Authorization header format")
34
32
  scheme, token = parts
35
- if scheme != 'Bearer':
33
+ if scheme != "Bearer":
36
34
  raise AuthenticationError("Authorization scheme must be Bearer token")
37
35
 
38
- #* Decode token
39
- decode_token_parameters = (
40
- MaleoFoundationTokenParametersTransfers
41
- .Decode(key=self._keys.public, token=token)
36
+ # * Decode token
37
+ decode_token_parameters = MaleoFoundationTokenParametersTransfers.Decode(
38
+ key=self._keys.public, token=token
42
39
  )
43
- decode_token_result = (
44
- self._maleo_foundation.services.token
45
- .decode(parameters=decode_token_parameters)
40
+ decode_token_result = self._maleo_foundation.services.token.decode(
41
+ parameters=decode_token_parameters
46
42
  )
47
- if decode_token_result.success:
43
+ if decode_token_result.success and decode_token_result.data is not None:
48
44
  type = BaseEnums.TokenType.ACCESS
49
45
  payload = decode_token_result.data
50
- token = Token(
51
- type=type,
52
- payload=payload
53
- )
46
+ token = Token(type=type, payload=payload)
54
47
  return (
55
- Credentials(
56
- token=token,
57
- scopes=["authenticated", payload.sr]
58
- ),
59
- User(
60
- authenticated=True,
61
- username=payload.u_u,
62
- email=payload.u_e
63
- )
48
+ Credentials(token=token, scopes=["authenticated", payload.sr]),
49
+ User(authenticated=True, username=payload.u_u, email=payload.u_e),
64
50
  )
65
51
 
66
52
  if "token" in conn.cookies:
67
53
  token = conn.cookies["token"]
68
- #* Decode token
69
- decode_token_parameters = (
70
- MaleoFoundationTokenParametersTransfers
71
- .Decode(key=self._keys.public, token=token)
54
+ # * Decode token
55
+ decode_token_parameters = MaleoFoundationTokenParametersTransfers.Decode(
56
+ key=self._keys.public, token=token
72
57
  )
73
- decode_token_result = (
74
- self._maleo_foundation.services.token
75
- .decode(parameters=decode_token_parameters)
58
+ decode_token_result = self._maleo_foundation.services.token.decode(
59
+ parameters=decode_token_parameters
76
60
  )
77
- if decode_token_result.success:
61
+ if decode_token_result.success and decode_token_result.data is not None:
78
62
  type = BaseEnums.TokenType.REFRESH
79
63
  payload = decode_token_result.data
80
- token = Token(
81
- type=type,
82
- payload=payload
83
- )
64
+ token = Token(type=type, payload=payload)
84
65
  return (
85
- Credentials(
86
- token=token,
87
- scopes=["authenticated", payload.sr]
88
- ),
89
- User(
90
- authenticated=True,
91
- username=payload.u_u,
92
- email=payload.u_e
93
- )
66
+ Credentials(token=token, scopes=["authenticated", payload.sr]),
67
+ User(authenticated=True, username=payload.u_u, email=payload.u_e),
94
68
  )
95
69
 
96
70
  return Credentials(), User(authenticated=False)
97
71
 
72
+
98
73
  def add_authentication_middleware(
99
74
  app: FastAPI,
100
75
  keys: BaseGeneralSchemas.RSAKeys,
101
- maleo_foundation: MaleoFoundationClientManager
76
+ maleo_foundation: MaleoFoundationClientManager,
102
77
  ) -> None:
103
78
  """
104
79
  Adds Authentication middleware to the FastAPI application.
@@ -125,5 +100,5 @@ def add_authentication_middleware(
125
100
  app.add_middleware(
126
101
  AuthenticationMiddleware,
127
102
  backend=Backend(keys, maleo_foundation),
128
- on_error=BaseExceptions.authentication_error_handler
129
- )
103
+ on_error=BaseExceptions.authentication_error_handler, # type: ignore
104
+ )
@@ -9,15 +9,22 @@ from typing import Awaitable, Callable, Optional, Sequence, Dict, List
9
9
  from fastapi import FastAPI, Request, Response, status
10
10
  from fastapi.responses import JSONResponse
11
11
  from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
12
+ from starlette.types import ASGIApp
12
13
 
13
14
  from maleo_foundation.authentication import Authentication
14
15
  from maleo_foundation.enums import BaseEnums
15
16
  from maleo_foundation.client.manager import MaleoFoundationClientManager
16
17
  from maleo_foundation.models.schemas import BaseGeneralSchemas
17
18
  from maleo_foundation.models.responses import BaseResponses
18
- from maleo_foundation.models.transfers.general.token import MaleoFoundationTokenGeneralTransfers
19
- from maleo_foundation.models.transfers.parameters.token import MaleoFoundationTokenParametersTransfers
20
- from maleo_foundation.models.transfers.parameters.signature import MaleoFoundationSignatureParametersTransfers
19
+ from maleo_foundation.models.transfers.general.token import (
20
+ MaleoFoundationTokenGeneralTransfers,
21
+ )
22
+ from maleo_foundation.models.transfers.parameters.token import (
23
+ MaleoFoundationTokenParametersTransfers,
24
+ )
25
+ from maleo_foundation.models.transfers.parameters.signature import (
26
+ MaleoFoundationSignatureParametersTransfers,
27
+ )
21
28
  from maleo_foundation.models.transfers.general.request import RequestContext
22
29
  from maleo_foundation.utils.extractor import extract_request_context
23
30
  from maleo_foundation.utils.logging import MiddlewareLogger
@@ -25,6 +32,7 @@ from maleo_foundation.utils.logging import MiddlewareLogger
25
32
  RequestProcessor = Callable[[Request], Awaitable[Optional[Response]]]
26
33
  ResponseProcessor = Callable[[Response], Awaitable[Response]]
27
34
 
35
+
28
36
  class RateLimiter:
29
37
  """Thread-safe rate limiter with automatic cleanup."""
30
38
 
@@ -33,21 +41,18 @@ class RateLimiter:
33
41
  limit: int,
34
42
  window: timedelta,
35
43
  ip_timeout: timedelta,
36
- cleanup_interval: timedelta
44
+ cleanup_interval: timedelta,
37
45
  ):
38
46
  self.limit = limit
39
47
  self.window = window
40
48
  self.ip_timeout = ip_timeout
41
49
  self.cleanup_interval = cleanup_interval
42
- self._requests:Dict[str, List[datetime]] = defaultdict(list)
43
- self._last_seen:Dict[str, datetime] = {}
50
+ self._requests: Dict[str, List[datetime]] = defaultdict(list)
51
+ self._last_seen: Dict[str, datetime] = {}
44
52
  self._last_cleanup = datetime.now()
45
53
  self._lock = threading.RLock()
46
54
 
47
- def is_rate_limited(
48
- self,
49
- request_context: RequestContext
50
- ) -> bool:
55
+ def is_rate_limited(self, request_context: RequestContext) -> bool:
51
56
  """Check if client IP is rate limited and record the request."""
52
57
  with self._lock:
53
58
  now = datetime.now()
@@ -56,7 +61,8 @@ class RateLimiter:
56
61
 
57
62
  # Remove old requests outside the window
58
63
  self._requests[client_ip] = [
59
- timestamp for timestamp in self._requests[client_ip]
64
+ timestamp
65
+ for timestamp in self._requests[client_ip]
60
66
  if now - timestamp <= self.window
61
67
  ]
62
68
 
@@ -68,10 +74,7 @@ class RateLimiter:
68
74
  self._requests[client_ip].append(now)
69
75
  return False
70
76
 
71
- def cleanup_old_data(
72
- self,
73
- logger: MiddlewareLogger
74
- ) -> None:
77
+ def cleanup_old_data(self, logger: MiddlewareLogger) -> None:
75
78
  """Clean up old request data to prevent memory growth."""
76
79
  now = datetime.now()
77
80
  if now - self._last_cleanup <= self.cleanup_interval:
@@ -102,13 +105,14 @@ class RateLimiter:
102
105
  f"Current tracked IPs: {len(self._requests)}"
103
106
  )
104
107
 
108
+
105
109
  class ResponseBuilder:
106
110
  """Handles response building and header management."""
107
111
 
108
112
  def __init__(
109
113
  self,
110
114
  keys: BaseGeneralSchemas.RSAKeys,
111
- maleo_foundation: MaleoFoundationClientManager
115
+ maleo_foundation: MaleoFoundationClientManager,
112
116
  ):
113
117
  self.keys = keys
114
118
  self.maleo_foundation = maleo_foundation
@@ -119,7 +123,7 @@ class ResponseBuilder:
119
123
  response: Response,
120
124
  request_context: RequestContext,
121
125
  responded_at: datetime,
122
- process_time: float
126
+ process_time: float,
123
127
  ) -> Response:
124
128
  """Add custom headers to response."""
125
129
  # Basic headers
@@ -129,7 +133,9 @@ class ResponseBuilder:
129
133
  response.headers["X-Responded-At"] = responded_at.isoformat()
130
134
 
131
135
  # Add signature header
132
- self._add_signature_header(response, request_context, responded_at, process_time)
136
+ self._add_signature_header(
137
+ response, request_context, responded_at, process_time
138
+ )
133
139
 
134
140
  # Add new authorization header if needed
135
141
  self._add_new_authorization_header(request_context, authentication, response)
@@ -141,7 +147,7 @@ class ResponseBuilder:
141
147
  response: Response,
142
148
  request_context: RequestContext,
143
149
  responded_at: datetime,
144
- process_time: float
150
+ process_time: float,
145
151
  ) -> None:
146
152
  """Generate and add signature header."""
147
153
  message = (
@@ -150,57 +156,61 @@ class ResponseBuilder:
150
156
  )
151
157
 
152
158
  sign_parameters = MaleoFoundationSignatureParametersTransfers.Sign(
153
- key=self.keys.private,
154
- password=self.keys.password,
155
- message=message
159
+ key=self.keys.private, password=self.keys.password, message=message
156
160
  )
157
161
 
158
- sign_result = self.maleo_foundation.services.signature.sign(parameters=sign_parameters)
159
- if sign_result.success:
162
+ sign_result = self.maleo_foundation.services.signature.sign(
163
+ parameters=sign_parameters
164
+ )
165
+ if sign_result.success and sign_result.data is not None:
160
166
  response.headers["X-Signature"] = sign_result.data.signature
161
167
 
162
168
  def _add_new_authorization_header(
163
169
  self,
164
- request_context:RequestContext,
165
- authentication:Authentication,
166
- response:Response
170
+ request_context: RequestContext,
171
+ authentication: Authentication,
172
+ response: Response,
167
173
  ) -> None:
168
174
  """Add new authorization header for refresh tokens."""
169
175
  if not self._should_regenerate_auth(request_context, authentication, response):
170
176
  return
171
177
 
178
+ if authentication.credentials.token is None:
179
+ return
180
+
172
181
  payload = MaleoFoundationTokenGeneralTransfers.BaseEncodePayload.model_validate(
173
182
  authentication.credentials.token.payload.model_dump()
174
183
  )
175
184
 
176
185
  parameters = MaleoFoundationTokenParametersTransfers.Encode(
177
- key=self.keys.private,
178
- password=self.keys.password,
179
- payload=payload
186
+ key=self.keys.private, password=self.keys.password, payload=payload
180
187
  )
181
188
 
182
189
  result = self.maleo_foundation.services.token.encode(parameters=parameters)
183
- if result.success:
190
+ if result.success and result.data is not None:
184
191
  response.headers["X-New-Authorization"] = result.data.token
185
192
 
186
193
  def _should_regenerate_auth(
187
194
  self,
188
195
  request_context: RequestContext,
189
196
  authentication: Authentication,
190
- response: Response
197
+ response: Response,
191
198
  ) -> bool:
192
199
  """Check if authorization should be regenerated."""
193
- return (
194
- authentication.user.is_authenticated
195
- and authentication.credentials.token.type == BaseEnums.TokenType.REFRESH
196
- and 200 <= response.status_code < 300
197
- and "logout" not in request_context.url
198
- )
200
+ if authentication.credentials.token is not None:
201
+ return (
202
+ authentication.user.is_authenticated
203
+ and authentication.credentials.token.type == BaseEnums.TokenType.REFRESH
204
+ and 200 <= response.status_code < 300
205
+ and "logout" not in request_context.url
206
+ )
207
+ return False
208
+
199
209
 
200
210
  class RequestLogger:
201
211
  """Handles request/response logging."""
202
212
 
203
- def __init__(self, logger:MiddlewareLogger):
213
+ def __init__(self, logger: MiddlewareLogger):
204
214
  self.logger = logger
205
215
 
206
216
  def log_request_response(
@@ -208,7 +218,7 @@ class RequestLogger:
208
218
  authentication: Authentication,
209
219
  response: Response,
210
220
  request_context: RequestContext,
211
- log_level: str = "info"
221
+ log_level: str = "info",
212
222
  ) -> None:
213
223
  """Log request and response details."""
214
224
  authentication_info = self._get_authentication_info(authentication)
@@ -221,12 +231,12 @@ class RequestLogger:
221
231
  f"Query Parameters: {request_context.query_params} - "
222
232
  f"Response | Status: {response.status_code}"
223
233
  )
224
-
234
+
225
235
  def log_exception(
226
236
  self,
227
237
  authentication: Authentication,
228
238
  error: Exception,
229
- request_context: RequestContext
239
+ request_context: RequestContext,
230
240
  ) -> None:
231
241
  """Log exception details."""
232
242
  authentication_info = self._get_authentication_info(authentication)
@@ -234,7 +244,7 @@ class RequestLogger:
234
244
  error_details = {
235
245
  "request_context": request_context.model_dump(mode="json"),
236
246
  "error": str(error),
237
- "traceback": traceback.format_exc().split("\n")
247
+ "traceback": traceback.format_exc().split("\n"),
238
248
  }
239
249
 
240
250
  self.logger.error(
@@ -245,16 +255,19 @@ class RequestLogger:
245
255
  f"Response | Status: 500 | Exception:\n{json.dumps(error_details, indent=4)}"
246
256
  )
247
257
 
248
- def _get_authentication_info(self, authentication:Authentication) -> str:
258
+ def _get_authentication_info(self, authentication: Authentication) -> str:
249
259
  """Get authentication info string."""
250
260
  if not authentication.user.is_authenticated:
251
261
  return "| Unauthenticated"
252
262
 
253
- return (
254
- f"| Token type: {authentication.credentials.token.type} | "
255
- f"Username: {authentication.user.display_name} | "
256
- f"Email: {authentication.user.identity}"
257
- )
263
+ if authentication.credentials.token is not None:
264
+ return (
265
+ f"| Token type: {authentication.credentials.token.type} | "
266
+ f"Username: {authentication.user.display_name} | "
267
+ f"Email: {authentication.user.identity}"
268
+ )
269
+
270
+ return "| Unauthenticated"
258
271
 
259
272
 
260
273
  class BaseMiddleware(BaseHTTPMiddleware):
@@ -262,7 +275,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
262
275
 
263
276
  def __init__(
264
277
  self,
265
- app: FastAPI,
278
+ app: ASGIApp,
266
279
  keys: BaseGeneralSchemas.RSAKeys,
267
280
  logger: MiddlewareLogger,
268
281
  maleo_foundation: MaleoFoundationClientManager,
@@ -273,7 +286,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
273
286
  limit: int = 10,
274
287
  window: int = 1,
275
288
  cleanup_interval: int = 60,
276
- ip_timeout: int = 300
289
+ ip_timeout: int = 300,
277
290
  ):
278
291
  super().__init__(app)
279
292
 
@@ -282,23 +295,21 @@ class BaseMiddleware(BaseHTTPMiddleware):
282
295
  limit=limit,
283
296
  window=timedelta(seconds=window),
284
297
  ip_timeout=timedelta(seconds=ip_timeout),
285
- cleanup_interval=timedelta(seconds=cleanup_interval)
298
+ cleanup_interval=timedelta(seconds=cleanup_interval),
286
299
  )
287
300
  self.response_builder = ResponseBuilder(keys, maleo_foundation)
288
301
  self.request_logger = RequestLogger(logger)
289
302
 
290
303
  # CORS settings (if needed)
291
304
  self.cors_config = {
292
- 'allow_origins': allow_origins,
293
- 'allow_methods': allow_methods,
294
- 'allow_headers': allow_headers,
295
- 'allow_credentials': allow_credentials,
305
+ "allow_origins": allow_origins,
306
+ "allow_methods": allow_methods,
307
+ "allow_headers": allow_headers,
308
+ "allow_credentials": allow_credentials,
296
309
  }
297
310
 
298
311
  async def dispatch(
299
- self,
300
- request: Request,
301
- call_next: RequestResponseEndpoint
312
+ self, request: Request, call_next: RequestResponseEndpoint
302
313
  ) -> Response:
303
314
  """Main middleware dispatch method."""
304
315
  # Setup
@@ -333,7 +344,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
333
344
  authentication, e, request_context, start_time
334
345
  )
335
346
 
336
- async def _request_processor(self, request:Request) -> Optional[Response]:
347
+ async def _request_processor(self, request: Request) -> Optional[Response]:
337
348
  """Override this method for custom request preprocessing."""
338
349
  return None
339
350
 
@@ -341,14 +352,14 @@ class BaseMiddleware(BaseHTTPMiddleware):
341
352
  self,
342
353
  authentication: Authentication,
343
354
  request_context: RequestContext,
344
- start_time: float
355
+ start_time: float,
345
356
  ) -> Response:
346
357
  """Create rate limit exceeded response."""
347
358
  response = JSONResponse(
348
- content=BaseResponses.RateLimitExceeded().model_dump(),
359
+ content=BaseResponses.RateLimitExceeded().model_dump(), # type: ignore
349
360
  status_code=status.HTTP_429_TOO_MANY_REQUESTS,
350
361
  )
351
-
362
+
352
363
  return self._build_final_response(
353
364
  authentication, response, request_context, start_time, log_level="warning"
354
365
  )
@@ -359,12 +370,12 @@ class BaseMiddleware(BaseHTTPMiddleware):
359
370
  response: Response,
360
371
  request_context: RequestContext,
361
372
  start_time: float,
362
- log_level: str = "info"
373
+ log_level: str = "info",
363
374
  ) -> Response:
364
375
  """Build final response with headers and logging."""
365
376
  responded_at = datetime.now(tz=timezone.utc)
366
377
  process_time = time.perf_counter() - start_time
367
-
378
+
368
379
  # Add headers
369
380
  response = self.response_builder.add_response_headers(
370
381
  authentication, response, request_context, responded_at, process_time
@@ -382,20 +393,20 @@ class BaseMiddleware(BaseHTTPMiddleware):
382
393
  authentication: Authentication,
383
394
  error: Exception,
384
395
  request_context: RequestContext,
385
- start_time: float
396
+ start_time: float,
386
397
  ) -> Response:
387
398
  """Handle exceptions and create error response."""
388
399
  responded_at = datetime.now(tz=timezone.utc)
389
400
  process_time = time.perf_counter() - start_time
390
-
401
+
391
402
  response = JSONResponse(
392
- content=BaseResponses.ServerError().model_dump(),
403
+ content=BaseResponses.ServerError().model_dump(), # type: ignore
393
404
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
394
405
  )
395
406
 
396
407
  # Log exception
397
408
  self.request_logger.log_exception(authentication, error, request_context)
398
-
409
+
399
410
  # Add headers
400
411
  return self.response_builder.add_response_headers(
401
412
  authentication, response, request_context, responded_at, process_time
@@ -414,7 +425,7 @@ def add_base_middleware(
414
425
  limit: int = 10,
415
426
  window: int = 1,
416
427
  cleanup_interval: int = 60,
417
- ip_timeout: int = 300
428
+ ip_timeout: int = 300,
418
429
  ) -> None:
419
430
  """
420
431
  Add Base middleware to the FastAPI application.
@@ -459,5 +470,5 @@ def add_base_middleware(
459
470
  limit=limit,
460
471
  window=window,
461
472
  cleanup_interval=cleanup_interval,
462
- ip_timeout=ip_timeout
463
- )
473
+ ip_timeout=ip_timeout,
474
+ )
@@ -2,18 +2,19 @@ from fastapi import FastAPI
2
2
  from fastapi.middleware.cors import CORSMiddleware
3
3
  from typing import Sequence
4
4
 
5
+
5
6
  def add_cors_middleware(
6
7
  app: FastAPI,
7
8
  allow_origins: Sequence[str] = (),
8
9
  allow_methods: Sequence[str] = ("GET",),
9
10
  allow_headers: Sequence[str] = (),
10
11
  allow_credentials: bool = False,
11
- expose_headers: Sequence[str] = ()
12
+ expose_headers: Sequence[str] = (),
12
13
  ) -> None:
13
14
  """
14
15
  Adds CORS (Cross-Origin Resource Sharing) middleware to the FastAPI application.
15
16
 
16
- This middleware allows the server to handle requests from different origins,
17
+ This middleware allows the server to handle requests from different origins,
17
18
  which is essential for enabling communication between the backend and frontend hosted on different domains.
18
19
 
19
20
  Args:
@@ -21,15 +22,15 @@ def add_cors_middleware(
21
22
  The FastAPI application instance to which the middleware will be added.
22
23
 
23
24
  allow_origins: Sequence[str]
24
- A Sequence of allowed origins (e.g., ["http://localhost:3000", "https://example.com"]).
25
+ A Sequence of allowed origins (e.g., ["http://localhost:3000", "https://example.com"]).
25
26
  Use ["*"] to allow requests from any origin.
26
27
 
27
28
  allow_methods: Sequence[str]
28
- A Sequence of allowed HTTP methods (e.g., ["GET", "POST", "PUT", "DELETE"]).
29
+ A Sequence of allowed HTTP methods (e.g., ["GET", "POST", "PUT", "DELETE"]).
29
30
  Use ["*"] to allow all methods.
30
31
 
31
32
  allow_headers: Sequence[str]
32
- A Sequence of allowed request headers (e.g., ["Authorization", "Content-Type"]).
33
+ A Sequence of allowed request headers (e.g., ["Authorization", "Content-Type"]).
33
34
  Use ["*"] to allow all headers.
34
35
 
35
36
  allow_credentials: bool
@@ -59,5 +60,5 @@ def add_cors_middleware(
59
60
  allow_methods=allow_methods,
60
61
  allow_headers=allow_headers,
61
62
  allow_credentials=allow_credentials,
62
- expose_headers=expose_headers
63
- )
63
+ expose_headers=expose_headers,
64
+ )
@@ -3,7 +3,8 @@ from .schemas import BaseSchemas
3
3
  from .transfers import BaseTransfers
4
4
  from .responses import BaseResponses
5
5
 
6
+
6
7
  class BaseModels:
7
8
  Schemas = BaseSchemas
8
9
  Transfers = BaseTransfers
9
- Responses = BaseResponses
10
+ Responses = BaseResponses