maleo-foundation 0.3.46__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.
- maleo_foundation/authentication.py +24 -13
- maleo_foundation/authorization.py +2 -1
- maleo_foundation/client/manager.py +22 -21
- maleo_foundation/client/services/__init__.py +16 -7
- maleo_foundation/client/services/encryption/__init__.py +13 -4
- maleo_foundation/client/services/encryption/aes.py +41 -36
- maleo_foundation/client/services/encryption/rsa.py +50 -50
- maleo_foundation/client/services/hash/__init__.py +19 -6
- maleo_foundation/client/services/hash/bcrypt.py +20 -18
- maleo_foundation/client/services/hash/hmac.py +20 -17
- maleo_foundation/client/services/hash/sha256.py +18 -15
- maleo_foundation/client/services/key.py +50 -42
- maleo_foundation/client/services/signature.py +46 -42
- maleo_foundation/client/services/token.py +49 -58
- maleo_foundation/constants.py +12 -19
- maleo_foundation/enums.py +14 -13
- maleo_foundation/expanded_types/__init__.py +2 -3
- maleo_foundation/expanded_types/client.py +30 -34
- maleo_foundation/expanded_types/encryption/__init__.py +2 -1
- maleo_foundation/expanded_types/encryption/aes.py +7 -5
- maleo_foundation/expanded_types/encryption/rsa.py +7 -5
- maleo_foundation/expanded_types/general.py +13 -11
- maleo_foundation/expanded_types/hash.py +7 -5
- maleo_foundation/expanded_types/key.py +8 -6
- maleo_foundation/expanded_types/service.py +30 -34
- maleo_foundation/expanded_types/signature.py +7 -5
- maleo_foundation/expanded_types/token.py +7 -5
- maleo_foundation/extended_types.py +4 -3
- maleo_foundation/managers/cache.py +2 -1
- maleo_foundation/managers/client/base.py +25 -12
- maleo_foundation/managers/client/google/base.py +11 -4
- maleo_foundation/managers/client/google/parameter.py +9 -11
- maleo_foundation/managers/client/google/secret.py +53 -35
- maleo_foundation/managers/client/google/storage.py +52 -22
- maleo_foundation/managers/client/google/subscription.py +37 -39
- maleo_foundation/managers/client/maleo.py +18 -23
- maleo_foundation/managers/configuration.py +5 -9
- maleo_foundation/managers/credential.py +14 -17
- maleo_foundation/managers/db.py +51 -40
- maleo_foundation/managers/middleware.py +9 -9
- maleo_foundation/managers/service.py +47 -54
- maleo_foundation/middlewares/authentication.py +29 -54
- maleo_foundation/middlewares/base.py +83 -72
- maleo_foundation/middlewares/cors.py +8 -7
- maleo_foundation/models/__init__.py +2 -1
- maleo_foundation/models/responses.py +57 -29
- maleo_foundation/models/schemas/__init__.py +2 -1
- maleo_foundation/models/schemas/encryption.py +5 -2
- maleo_foundation/models/schemas/general.py +38 -18
- maleo_foundation/models/schemas/hash.py +2 -1
- maleo_foundation/models/schemas/key.py +5 -2
- maleo_foundation/models/schemas/parameter.py +45 -15
- maleo_foundation/models/schemas/result.py +35 -20
- maleo_foundation/models/schemas/signature.py +5 -2
- maleo_foundation/models/schemas/token.py +5 -2
- maleo_foundation/models/table.py +33 -27
- maleo_foundation/models/transfers/__init__.py +2 -1
- maleo_foundation/models/transfers/general/__init__.py +2 -1
- maleo_foundation/models/transfers/general/configurations/__init__.py +10 -4
- maleo_foundation/models/transfers/general/configurations/cache/__init__.py +3 -2
- maleo_foundation/models/transfers/general/configurations/cache/redis.py +13 -5
- maleo_foundation/models/transfers/general/configurations/client/__init__.py +5 -1
- maleo_foundation/models/transfers/general/configurations/client/maleo.py +38 -12
- maleo_foundation/models/transfers/general/configurations/database.py +5 -2
- maleo_foundation/models/transfers/general/configurations/middleware.py +22 -15
- maleo_foundation/models/transfers/general/configurations/service.py +2 -1
- maleo_foundation/models/transfers/general/credentials.py +2 -1
- maleo_foundation/models/transfers/general/database.py +11 -4
- maleo_foundation/models/transfers/general/key.py +13 -4
- maleo_foundation/models/transfers/general/request.py +28 -9
- maleo_foundation/models/transfers/general/settings.py +12 -22
- maleo_foundation/models/transfers/general/signature.py +4 -2
- maleo_foundation/models/transfers/general/token.py +34 -27
- maleo_foundation/models/transfers/parameters/__init__.py +2 -1
- maleo_foundation/models/transfers/parameters/client.py +15 -19
- maleo_foundation/models/transfers/parameters/encryption/__init__.py +2 -1
- maleo_foundation/models/transfers/parameters/encryption/aes.py +7 -5
- maleo_foundation/models/transfers/parameters/encryption/rsa.py +7 -5
- maleo_foundation/models/transfers/parameters/general.py +15 -13
- maleo_foundation/models/transfers/parameters/hash/__init__.py +2 -1
- maleo_foundation/models/transfers/parameters/hash/bcrypt.py +5 -5
- maleo_foundation/models/transfers/parameters/hash/hmac.py +6 -6
- maleo_foundation/models/transfers/parameters/hash/sha256.py +5 -5
- maleo_foundation/models/transfers/parameters/key.py +9 -8
- maleo_foundation/models/transfers/parameters/service.py +42 -48
- maleo_foundation/models/transfers/parameters/signature.py +7 -4
- maleo_foundation/models/transfers/parameters/token.py +10 -10
- maleo_foundation/models/transfers/results/__init__.py +2 -1
- maleo_foundation/models/transfers/results/client/__init__.py +2 -1
- maleo_foundation/models/transfers/results/client/controllers/__init__.py +2 -1
- maleo_foundation/models/transfers/results/client/controllers/http.py +10 -7
- maleo_foundation/models/transfers/results/client/service.py +12 -6
- maleo_foundation/models/transfers/results/encryption/__init__.py +2 -1
- maleo_foundation/models/transfers/results/encryption/aes.py +13 -5
- maleo_foundation/models/transfers/results/encryption/rsa.py +12 -4
- maleo_foundation/models/transfers/results/hash.py +7 -3
- maleo_foundation/models/transfers/results/key.py +18 -6
- maleo_foundation/models/transfers/results/service/__init__.py +2 -3
- maleo_foundation/models/transfers/results/service/controllers/__init__.py +2 -1
- maleo_foundation/models/transfers/results/service/controllers/rest.py +14 -11
- maleo_foundation/models/transfers/results/service/general.py +16 -10
- maleo_foundation/models/transfers/results/signature.py +12 -4
- maleo_foundation/models/transfers/results/token.py +10 -4
- maleo_foundation/rest_controller_result.py +23 -21
- maleo_foundation/types.py +15 -14
- maleo_foundation/utils/__init__.py +2 -1
- maleo_foundation/utils/cache.py +10 -13
- maleo_foundation/utils/client.py +25 -12
- maleo_foundation/utils/controller.py +59 -37
- maleo_foundation/utils/dependencies/__init__.py +2 -1
- maleo_foundation/utils/dependencies/auth.py +5 -12
- maleo_foundation/utils/dependencies/context.py +3 -4
- maleo_foundation/utils/exceptions.py +50 -28
- maleo_foundation/utils/extractor.py +18 -6
- maleo_foundation/utils/formatter/__init__.py +2 -1
- maleo_foundation/utils/formatter/case.py +5 -4
- maleo_foundation/utils/loaders/__init__.py +2 -1
- maleo_foundation/utils/loaders/credential/__init__.py +2 -1
- maleo_foundation/utils/loaders/credential/google.py +29 -15
- maleo_foundation/utils/loaders/json.py +3 -2
- maleo_foundation/utils/loaders/key/__init__.py +2 -1
- maleo_foundation/utils/loaders/key/rsa.py +26 -13
- maleo_foundation/utils/loaders/yaml.py +2 -1
- maleo_foundation/utils/logging.py +70 -46
- maleo_foundation/utils/merger.py +7 -9
- maleo_foundation/utils/query.py +41 -34
- maleo_foundation/utils/repository.py +28 -13
- maleo_foundation/utils/searcher.py +4 -6
- {maleo_foundation-0.3.46.dist-info → maleo_foundation-0.3.47.dist-info}/METADATA +14 -1
- maleo_foundation-0.3.47.dist-info/RECORD +137 -0
- maleo_foundation/expanded_types/repository.py +0 -68
- maleo_foundation/models/transfers/results/service/repository.py +0 -39
- maleo_foundation-0.3.46.dist-info/RECORD +0 -139
- {maleo_foundation-0.3.46.dist-info → maleo_foundation-0.3.47.dist-info}/WHEEL +0 -0
- {maleo_foundation-0.3.46.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
|
-
|
10
|
+
from maleo_foundation.models.transfers.parameters.token import (
|
11
|
+
MaleoFoundationTokenParametersTransfers,
|
12
|
+
)
|
12
13
|
from maleo_foundation.utils.exceptions import BaseExceptions
|
13
|
-
|
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 !=
|
33
|
+
if scheme != "Bearer":
|
36
34
|
raise AuthenticationError("Authorization scheme must be Bearer token")
|
37
35
|
|
38
|
-
|
39
|
-
decode_token_parameters = (
|
40
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
69
|
-
decode_token_parameters = (
|
70
|
-
|
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
|
-
|
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
|
-
|
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
|
19
|
-
|
20
|
-
|
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
|
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(
|
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(
|
159
|
-
|
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
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
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:
|
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
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
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
|
+
)
|