maleo-foundation 0.2.7__py3-none-any.whl → 0.2.8__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.
@@ -1,25 +1,26 @@
1
1
  from pydantic import BaseModel, Field
2
2
  from starlette.authentication import AuthCredentials, BaseUser
3
3
  from typing import Optional, Sequence
4
+ from maleo_foundation.enums import BaseEnums
4
5
  from maleo_foundation.models.transfers.general.token import MaleoFoundationTokenGeneralTransfers
5
6
  from maleo_foundation.types import BaseTypes
6
7
 
7
8
  class Credentials(AuthCredentials):
8
9
  def __init__(
9
10
  self,
10
- key:BaseTypes.OptionalString = None,
11
+ token_type:Optional[BaseEnums.TokenType] = None,
11
12
  token:BaseTypes.OptionalString = None,
12
13
  payload:Optional[MaleoFoundationTokenGeneralTransfers.DecodePayload] = None,
13
14
  scopes:Optional[Sequence[str]] = None
14
15
  ) -> None:
15
- self._key = key
16
+ self._token_type = token_type
16
17
  self._token = token
17
18
  self._payload = payload
18
19
  super().__init__(scopes)
19
20
 
20
21
  @property
21
- def key(self) -> BaseTypes.OptionalString:
22
- return self._key
22
+ def token_type(self) -> Optional[BaseEnums.TokenType]:
23
+ return self._token_type
23
24
 
24
25
  @property
25
26
  def token(self) -> BaseTypes.OptionalString:
@@ -261,7 +261,7 @@ class ServiceManager:
261
261
  raise NotImplementedError()
262
262
 
263
263
  @property
264
- def token(self) -> str:
264
+ def token(self) -> BaseTypes.OptionalString:
265
265
  payload = MaleoFoundationTokenGeneralTransfers.BaseEncodePayload(
266
266
  iss=None,
267
267
  sub=str(self._maleo_credentials.id),
@@ -272,9 +272,7 @@ class ServiceManager:
272
272
  )
273
273
  parameters = MaleoFoundationTokenParametersTransfers.Encode(key=self._keys.private, password=self._keys.password, payload=payload)
274
274
  result = self._foundation.services.token.encode(parameters=parameters)
275
- if not result.success:
276
- raise RuntimeError("Failed encoding payload into token")
277
- return result.data.token
275
+ return result.data.token if result.success else None
278
276
 
279
277
  def create_app(self, router:APIRouter, lifespan:Optional[Lifespan[AppType]] = None) -> FastAPI:
280
278
  self._loggers.application.info("Creating FastAPI application")
@@ -1,10 +1,10 @@
1
- from datetime import datetime, timezone
2
1
  from fastapi import FastAPI
3
2
  from starlette.authentication import AuthenticationBackend, AuthenticationError
4
3
  from starlette.middleware.authentication import AuthenticationMiddleware
5
4
  from starlette.requests import HTTPConnection
6
5
  from typing import Tuple
7
6
  from maleo_foundation.authentication import Credentials, User
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
10
  from maleo_foundation.models.transfers.parameters.token import MaleoFoundationTokenParametersTransfers
@@ -19,37 +19,56 @@ class Backend(AuthenticationBackend):
19
19
  self._maleo_foundation = maleo_foundation
20
20
 
21
21
  async def authenticate(self, conn:HTTPConnection) -> Tuple[Credentials, User]:
22
- if "Authorization" not in conn.headers:
23
- return Credentials(), User(authenticated=False)
22
+ if "Authorization" in conn.headers:
23
+ auth = conn.headers["Authorization"]
24
+ parts = auth.split()
25
+ if len(parts) != 2 or parts[0] != "Bearer":
26
+ raise AuthenticationError("Invalid Authorization header format")
27
+ scheme, token = parts
28
+ if scheme != 'Bearer':
29
+ raise AuthenticationError("Authorization scheme must be Bearer token")
30
+
31
+ #* Decode token
32
+ decode_token_parameters = MaleoFoundationTokenParametersTransfers.Decode(key=self._keys.public, token=token)
33
+ decode_token_result = self._maleo_foundation.services.token.decode(parameters=decode_token_parameters)
34
+ if decode_token_result.success:
35
+ payload = decode_token_result.data
36
+ return (
37
+ Credentials(
38
+ token_type=BaseEnums.TokenType.ACCESS,
39
+ token=token,
40
+ payload=payload,
41
+ scopes=["authenticated", payload.sr]
42
+ ),
43
+ User(
44
+ authenticated=True,
45
+ username=payload.u_u,
46
+ email=payload.u_e
47
+ )
48
+ )
24
49
 
25
- auth = conn.headers["Authorization"]
26
- scheme, token = auth.split()
27
- if scheme != 'Bearer':
28
- # raise AuthenticationError("Authorization scheme must be Bearer token")
29
- return Credentials(), User(authenticated=False)
50
+ if "token" in conn.cookies:
51
+ token = conn.cookies["token"]
52
+ #* Decode token
53
+ decode_token_parameters = MaleoFoundationTokenParametersTransfers.Decode(key=self._keys.public, token=token)
54
+ decode_token_result = self._maleo_foundation.services.token.decode(parameters=decode_token_parameters)
55
+ if decode_token_result.success:
56
+ payload = decode_token_result.data
57
+ return (
58
+ Credentials(
59
+ token_type=BaseEnums.TokenType.REFRESH,
60
+ token=token,
61
+ payload=payload,
62
+ scopes=["authenticated", payload.sr]
63
+ ),
64
+ User(
65
+ authenticated=True,
66
+ username=payload.u_u,
67
+ email=payload.u_e
68
+ )
69
+ )
30
70
 
31
- decode_token_parameters = MaleoFoundationTokenParametersTransfers.Decode(key=self._keys.public, token=token)
32
- decode_token_result = self._maleo_foundation.services.token.decode(parameters=decode_token_parameters)
33
- if not decode_token_result.success:
34
- # raise AuthenticationError("Invalid Bearer token, unable to decode token")
35
- return Credentials(), User(authenticated=False)
36
- if decode_token_result.data.exp_dt <= datetime.now(tz=timezone.utc):
37
- # raise AuthenticationError("Expired Bearer token, request new or refresh token")
38
- return Credentials(), User(authenticated=False)
39
-
40
- payload = decode_token_result.data
41
- return (
42
- Credentials(
43
- token=token,
44
- payload=payload,
45
- scopes=["authenticated", payload.sr]
46
- ),
47
- User(
48
- authenticated=True,
49
- username=payload.u_u,
50
- email=payload.u_e
51
- )
52
- )
71
+ return Credentials(), User(authenticated=False)
53
72
 
54
73
  def add_authentication_middleware(app:FastAPI, keys:BaseGeneralSchemas.RSAKeys, logger:MiddlewareLogger, maleo_foundation:MaleoFoundationClientManager) -> None:
55
74
  """
@@ -8,10 +8,13 @@ from fastapi import FastAPI, Request, Response, status
8
8
  from fastapi.responses import JSONResponse
9
9
  from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
10
10
  from typing import Awaitable, Callable, Optional, Sequence
11
- from maleo_foundation.authentication import Credentials, User
11
+ from maleo_foundation.authentication import Authentication
12
+ from maleo_foundation.enums import BaseEnums
12
13
  from maleo_foundation.client.manager import MaleoFoundationClientManager
13
14
  from maleo_foundation.models.schemas import BaseGeneralSchemas
14
15
  from maleo_foundation.models.responses import BaseResponses
16
+ from maleo_foundation.models.transfers.general.token import MaleoFoundationTokenGeneralTransfers
17
+ from maleo_foundation.models.transfers.parameters.token import MaleoFoundationTokenParametersTransfers
15
18
  from maleo_foundation.models.transfers.parameters.signature import MaleoFoundationSignatureParametersTransfers
16
19
  from maleo_foundation.utils.extractor import BaseExtractors
17
20
  from maleo_foundation.utils.logging import MiddlewareLogger
@@ -117,6 +120,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
117
120
  def _add_response_headers(
118
121
  self,
119
122
  request:Request,
123
+ authentication:Authentication,
120
124
  response:Response,
121
125
  request_timestamp:datetime,
122
126
  response_timestamp:datetime,
@@ -132,11 +136,22 @@ class BaseMiddleware(BaseHTTPMiddleware):
132
136
  if sign_result.success:
133
137
  response.headers["X-Signature"] = sign_result.data.signature
134
138
  response = self._append_cors_headers(request=request, response=response) #* Re-append CORS headers
139
+ if authentication.user.is_authenticated \
140
+ and authentication.credentials.token_type == BaseEnums.TokenType.REFRESH \
141
+ and authentication.credentials.payload is not None \
142
+ and (response.status_code >= 200 and response.status_code < 300):
143
+ #* Regenerate new authorization
144
+ payload = MaleoFoundationTokenGeneralTransfers.BaseEncodePayload.model_validate(authentication.credentials.payload.model_dump())
145
+ parameters = MaleoFoundationTokenParametersTransfers.Encode(key=self._keys.private, password=self._keys.password, payload=payload)
146
+ result = self._maleo_foundation.services.token.encode(parameters=parameters)
147
+ if result.success:
148
+ response.headers["X-New-Authorization"] = result.data.token
135
149
  return response
136
150
 
137
151
  def _build_response(
138
152
  self,
139
153
  request:Request,
154
+ authentication:Authentication,
140
155
  authentication_info:str,
141
156
  response:Response,
142
157
  request_timestamp:datetime,
@@ -145,7 +160,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
145
160
  log_level:str = "info",
146
161
  client_ip:str = "unknown"
147
162
  ) -> Response:
148
- response = self._add_response_headers(request, response, request_timestamp, response_timestamp, process_time)
163
+ response = self._add_response_headers(request, authentication, response, request_timestamp, response_timestamp, process_time)
149
164
  log_func = getattr(self._logger, log_level)
150
165
  log_func(
151
166
  f"Request {authentication_info} | IP: {client_ip} | Host: {request.client.host} | Port: {request.client.port} | Method: {request.method} | URL: {request.url.path} | "
@@ -156,6 +171,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
156
171
  def _handle_exception(
157
172
  self,
158
173
  request:Request,
174
+ authentication:Authentication,
159
175
  authentication_info:str,
160
176
  error,
161
177
  request_timestamp:datetime,
@@ -183,7 +199,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
183
199
  f"Headers: {dict(request.headers)} - Response | Status: 500 | Exception:\n{json.dumps(error_details, indent=4)}"
184
200
  )
185
201
 
186
- return self._add_response_headers(request, response, request_timestamp, response_timestamp, process_time)
202
+ return self._add_response_headers(request, authentication, response, request_timestamp, response_timestamp, process_time)
187
203
 
188
204
  async def _request_processor(self, request:Request) -> Optional[Response]:
189
205
  return None
@@ -193,17 +209,21 @@ class BaseMiddleware(BaseHTTPMiddleware):
193
209
  request_timestamp = datetime.now(tz=timezone.utc) #* Record the request timestamp
194
210
  start_time = time.perf_counter() #* Record the start time
195
211
  client_ip = BaseExtractors.extract_client_ip(request) #* Get request IP with improved extraction
196
- user:User = request.user
197
- if not user.is_authenticated:
212
+ authentication = Authentication(
213
+ credentials=request.auth,
214
+ user=request.user
215
+ )
216
+ if not authentication.user.is_authenticated:
198
217
  authentication_info = "| Unauthenticated"
199
218
  else:
200
- authentication_info = f"| Username: {user.display_name} | Email:{user.identity}"
219
+ authentication_info = f"| Username: {authentication.user.display_name} | Email:{authentication.user.identity}"
201
220
 
202
221
  try:
203
222
  #* 1. Rate limit check
204
223
  if self._check_rate_limit(client_ip):
205
224
  return self._build_response(
206
225
  request=request,
226
+ authentication=authentication,
207
227
  authentication_info=authentication_info,
208
228
  response=JSONResponse(
209
229
  content=BaseResponses.RateLimitExceeded().model_dump(),
@@ -221,6 +241,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
221
241
  if pre_response is not None:
222
242
  return self._build_response(
223
243
  request=request,
244
+ authentication=authentication,
224
245
  authentication_info=authentication_info,
225
246
  response=pre_response,
226
247
  request_timestamp=request_timestamp,
@@ -234,6 +255,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
234
255
  response = await call_next(request)
235
256
  response = self._build_response(
236
257
  request=request,
258
+ authentication=authentication,
237
259
  authentication_info=authentication_info,
238
260
  response=response,
239
261
  request_timestamp=request_timestamp,
@@ -248,6 +270,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
248
270
  except Exception as e:
249
271
  return self._handle_exception(
250
272
  request=request,
273
+ authentication=authentication,
251
274
  authentication_info=authentication_info,
252
275
  error=e,
253
276
  request_timestamp=request_timestamp,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo_foundation
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Foundation package for Maleo
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: MIT
@@ -1,5 +1,5 @@
1
1
  maleo_foundation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- maleo_foundation/authentication.py,sha256=0HhZsmZM_oMiC71FpnTBLbx2sQzdsI-RgwNqvxvn5zg,1750
2
+ maleo_foundation/authentication.py,sha256=kJfuRKgQY5cjmn0r36z4a4jF4bg7UifZ9D9mgMMnTsw,1840
3
3
  maleo_foundation/constants.py,sha256=aBmEfWlBqZxi0k-n6h2NM1YRLOjMnheEiLyQcjP-zCQ,1164
4
4
  maleo_foundation/enums.py,sha256=uvwl3dl2r6BoJMEbtSETiLoyJubHup9Lc7VOg7w7zQo,2943
5
5
  maleo_foundation/extended_types.py,sha256=pIKt-_9tby4rmune3fmWcCW_mohaNRh_1lywBmdc-L4,301
@@ -32,7 +32,7 @@ maleo_foundation/expanded_types/encryption/rsa.py,sha256=MuhB_DGrjnsl4t96W4pKuCt
32
32
  maleo_foundation/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  maleo_foundation/managers/db.py,sha256=Pn5EZ-c1Hy6-BihN7KokHJWmBIt3Ty96fZ0zF-srtF4,5208
34
34
  maleo_foundation/managers/middleware.py,sha256=ODIQU1Hpu-Xempjjo_VRbVtxiD5oi74mNuoWuDawRh0,4250
35
- maleo_foundation/managers/service.py,sha256=VggsQUS_i80_fGZYrErBALYXLLFx1JnE4prGJ0L6HjU,16240
35
+ maleo_foundation/managers/service.py,sha256=ub8Fqa8frR2Hel806tGnTR4EnaQ1vODQ3IxzJ_ynoew,16189
36
36
  maleo_foundation/managers/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  maleo_foundation/managers/client/base.py,sha256=5z9l2GN4QASF0-Lft8o5QQ3SRPXqeNZNT1S1CgaE764,4384
38
38
  maleo_foundation/managers/client/maleo.py,sha256=iCM47TLL-RSQ2FkTmHVPdsb2JCd1LebMx6OJvIr4vCQ,2035
@@ -40,8 +40,8 @@ maleo_foundation/managers/client/google/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
40
40
  maleo_foundation/managers/client/google/base.py,sha256=eIdd6C2BFIu4EyZ1j017VZaJn_nSTPGFftBwQmVAUDA,1366
41
41
  maleo_foundation/managers/client/google/secret.py,sha256=Ski1CHYeA8vjSk2Oc2Pf4CfFrzT_RcA6NEZwza7gM7Y,4464
42
42
  maleo_foundation/managers/client/google/storage.py,sha256=JFqXd9QgusT75KAWyWdin8V6BbbKcbBCrmWDpqg6i3Q,2530
43
- maleo_foundation/middlewares/authentication.py,sha256=hBGaMiCt0CnUw7sg4PZJ3kFJ0OVXvkOR5LhZcz9QUU8,3545
44
- maleo_foundation/middlewares/base.py,sha256=g4cg9gIUveK9zbjhwQtkdefhuoSlv9BUWVCFaSlOClw,13303
43
+ maleo_foundation/middlewares/authentication.py,sha256=RWFjBciFVVyd-Dl-blFymwzS2F-fGXiN7E3W8a4nPy8,4414
44
+ maleo_foundation/middlewares/base.py,sha256=q8RJJkq39QBqU43DkriJCJlri7S7RVIMsArzfcy7T5Q,14825
45
45
  maleo_foundation/middlewares/cors.py,sha256=9uvBvY2N6Vxa9RP_YtESxcWo6Doi6uS0lzAG9iLY7Uc,2288
46
46
  maleo_foundation/models/__init__.py,sha256=AaKehO7c1HyKhoTGRmNHDddSeBXkW-_YNrpOGBu8Ms8,246
47
47
  maleo_foundation/models/responses.py,sha256=iKJs9EFemCuUOc-IxEUwjX9RgNnPf-mw7yGheCGyVDA,4896
@@ -107,7 +107,7 @@ maleo_foundation/utils/loaders/credential/__init__.py,sha256=qopTKvcMVoTFwyRijeg
107
107
  maleo_foundation/utils/loaders/credential/google.py,sha256=deksZXT5wPhEsSMHbZ3x05WHXxCjLDt76Ns-1Tmhp7g,948
108
108
  maleo_foundation/utils/loaders/key/__init__.py,sha256=hVygcC2ImHc_aVrSrOmyedR8tMUZokWUKCKOSh5ctbo,106
109
109
  maleo_foundation/utils/loaders/key/rsa.py,sha256=gDhyX6iTFtHiluuhFCozaZ3pOLKU2Y9TlrNMK_GVyGU,3796
110
- maleo_foundation-0.2.7.dist-info/METADATA,sha256=VMS1nmB6vuioBkMlZiflqejZ9d0jPbvldXzjNM3HdCA,3418
111
- maleo_foundation-0.2.7.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
112
- maleo_foundation-0.2.7.dist-info/top_level.txt,sha256=_iBos3F_bhEOdjOnzeiEYSrCucasc810xXtLBXI8cQc,17
113
- maleo_foundation-0.2.7.dist-info/RECORD,,
110
+ maleo_foundation-0.2.8.dist-info/METADATA,sha256=7ymyQxmICDgq1TL3oWA-kDMKHB3zLk-1dgy1hgGGOMg,3418
111
+ maleo_foundation-0.2.8.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
112
+ maleo_foundation-0.2.8.dist-info/top_level.txt,sha256=_iBos3F_bhEOdjOnzeiEYSrCucasc810xXtLBXI8cQc,17
113
+ maleo_foundation-0.2.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5