maleo-foundation 0.1.58__tar.gz → 0.1.60__tar.gz

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 (88) hide show
  1. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/PKG-INFO +1 -1
  2. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/client/services/__init__.py +2 -0
  3. maleo_foundation-0.1.60/maleo_foundation/client/services/signature.py +74 -0
  4. maleo_foundation-0.1.60/maleo_foundation/expanded_types/signature.py +13 -0
  5. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/client/maleo.py +5 -0
  6. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/middlewares/authentication.py +9 -7
  7. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/middlewares/base.py +52 -39
  8. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/schemas/signature.py +4 -0
  9. maleo_foundation-0.1.60/maleo_foundation/models/transfers/general/signature.py +8 -0
  10. maleo_foundation-0.1.60/maleo_foundation/models/transfers/parameters/signature.py +14 -0
  11. maleo_foundation-0.1.60/maleo_foundation/models/transfers/results/signature.py +12 -0
  12. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation.egg-info/PKG-INFO +1 -1
  13. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation.egg-info/SOURCES.txt +5 -2
  14. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/pyproject.toml +1 -1
  15. maleo_foundation-0.1.58/maleo_foundation/services/__init__.py +0 -5
  16. maleo_foundation-0.1.58/maleo_foundation/services/token.py +0 -20
  17. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/README.md +0 -0
  18. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/__init__.py +0 -0
  19. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/authentication.py +0 -0
  20. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/client/__init__.py +0 -0
  21. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/client/manager.py +0 -0
  22. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/client/services/token.py +0 -0
  23. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/constants.py +0 -0
  24. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/enums.py +0 -0
  25. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/expanded_types/__init__.py +0 -0
  26. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/expanded_types/client.py +0 -0
  27. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/expanded_types/general.py +0 -0
  28. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/expanded_types/query.py +0 -0
  29. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/expanded_types/service.py +0 -0
  30. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/expanded_types/token.py +0 -0
  31. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/extended_types.py +0 -0
  32. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/__init__.py +0 -0
  33. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/client/__init__.py +0 -0
  34. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/client/base.py +0 -0
  35. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/client/google/__init__.py +0 -0
  36. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/client/google/base.py +0 -0
  37. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/client/google/secret.py +0 -0
  38. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/client/google/storage.py +0 -0
  39. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/db.py +0 -0
  40. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/middleware.py +0 -0
  41. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/managers/service.py +0 -0
  42. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/middlewares/__init__.py +0 -0
  43. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/middlewares/cors.py +0 -0
  44. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/__init__.py +0 -0
  45. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/responses.py +0 -0
  46. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/schemas/__init__.py +0 -0
  47. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/schemas/general.py +0 -0
  48. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/schemas/parameter.py +0 -0
  49. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/schemas/result.py +0 -0
  50. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/schemas/token.py +0 -0
  51. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/table.py +0 -0
  52. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/__init__.py +0 -0
  53. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/general/__init__.py +0 -0
  54. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/general/token.py +0 -0
  55. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/parameters/__init__.py +0 -0
  56. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/parameters/client.py +0 -0
  57. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/parameters/general.py +0 -0
  58. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/parameters/service.py +0 -0
  59. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/parameters/token.py +0 -0
  60. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/__init__.py +0 -0
  61. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/client/__init__.py +0 -0
  62. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/client/controllers/__init__.py +0 -0
  63. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/client/controllers/http.py +0 -0
  64. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/client/service.py +0 -0
  65. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/service/__init__.py +0 -0
  66. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/service/controllers/__init__.py +0 -0
  67. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/service/controllers/rest.py +0 -0
  68. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/service/general.py +0 -0
  69. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/service/query.py +0 -0
  70. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/models/transfers/results/token.py +0 -0
  71. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/types.py +0 -0
  72. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/__init__.py +0 -0
  73. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/controller.py +0 -0
  74. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/exceptions.py +0 -0
  75. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/extractor.py +0 -0
  76. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/formatter/__init__.py +0 -0
  77. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/formatter/case.py +0 -0
  78. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/loaders/__init__.py +0 -0
  79. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/loaders/json.py +0 -0
  80. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/loaders/key.py +0 -0
  81. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/loaders/yaml.py +0 -0
  82. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/logging.py +0 -0
  83. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/mergers.py +0 -0
  84. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation/utils/query.py +0 -0
  85. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation.egg-info/dependency_links.txt +0 -0
  86. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation.egg-info/requires.txt +0 -0
  87. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/maleo_foundation.egg-info/top_level.txt +0 -0
  88. {maleo_foundation-0.1.58 → maleo_foundation-0.1.60}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo_foundation
3
- Version: 0.1.58
3
+ Version: 0.1.60
4
4
  Summary: Foundation package for Maleo
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: MIT
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
  from pydantic import Field
3
3
  from maleo_foundation.managers.client.base import ClientServices
4
+ from maleo_foundation.client.services.signature import MaleoFoundationSignatureClientService
4
5
  from maleo_foundation.client.services.token import MaleoFoundationTokenClientService
5
6
 
6
7
  class MaleoFoundationServices(ClientServices):
8
+ signature:MaleoFoundationSignatureClientService = Field(..., description="Signature's service")
7
9
  token:MaleoFoundationTokenClientService = Field(..., description="Token's service")
@@ -0,0 +1,74 @@
1
+ from base64 import b64decode, b64encode
2
+ from Crypto.PublicKey import RSA
3
+ from Crypto.Hash import SHA256
4
+ from Crypto.Signature import pkcs1_15
5
+ from maleo_foundation.models.schemas.signature import BaseSignatureSchemas
6
+ from maleo_foundation.models.transfers.parameters.signature import BaseSignatureParametersTransfers
7
+ from maleo_foundation.models.transfers.results.signature import BaseSignatureResultsTransfers
8
+ from maleo_foundation.expanded_types.signature import BaseSignatureResultsTypes
9
+ from maleo_foundation.managers.client.base import ClientService
10
+ from maleo_foundation.utils.exceptions import BaseExceptions
11
+
12
+ class MaleoFoundationSignatureClientService(ClientService):
13
+ def sign(self, parameters:BaseSignatureParametersTransfers.Sign) -> BaseSignatureResultsTypes.Sign:
14
+ @BaseExceptions.service_exception_handler(
15
+ operation="signing single message",
16
+ logger=self._logger,
17
+ fail_result_class=BaseSignatureResultsTransfers.Fail
18
+ )
19
+ def _impl():
20
+ try:
21
+ #* Load RSA key using PyCryptodome's RSA.import_key
22
+ private_key = RSA.import_key(extern_key=parameters.key, passphrase=parameters.password)
23
+ #* Validate the key type
24
+ if not private_key.has_private():
25
+ message = "Invalid key type"
26
+ description = "A private key must be used for RSA Verify"
27
+ other = "Ensure the given key is of type private key"
28
+ return BaseSignatureResultsTransfers.Fail(message=message, description=description, other=other)
29
+ except Exception as e:
30
+ self._logger.error("Unexpected error occured while trying to import key:\n'%s'", str(e), exc_info=True)
31
+ message = "Invalid key"
32
+ description = "Unexpected error occured while trying to import key"
33
+ other = "Ensure given key is valid"
34
+ return BaseSignatureResultsTransfers.Fail(message=message, description=description, other=other)
35
+ hash = SHA256.new(parameters.message.encode()) #* Generate message hash
36
+ signature = b64encode(pkcs1_15.new(private_key).sign(hash)).decode() #* Sign the hashed message
37
+ data = BaseSignatureSchemas.Signature(signature=signature)
38
+ self._logger.info("Message successfully signed")
39
+ return BaseSignatureResultsTransfers.Sign(data=data)
40
+ return _impl()
41
+
42
+ def verify(self, parameters:BaseSignatureParametersTransfers.Verify) -> BaseSignatureResultsTypes.Verify:
43
+ @BaseExceptions.service_exception_handler(
44
+ operation="verify single signature",
45
+ logger=self._logger,
46
+ fail_result_class=BaseSignatureResultsTransfers.Fail
47
+ )
48
+ def _impl():
49
+ try:
50
+ #* Load RSA key using PyCryptodome's RSA.import_key
51
+ public_key = RSA.import_key(parameters.key)
52
+ #* Validate the key type
53
+ if public_key.has_private():
54
+ message = "Invalid key type"
55
+ description = "A public key must be used for RSA Sign"
56
+ other = "Ensure the given key is of type public key"
57
+ return BaseSignatureResultsTransfers.Fail(message=message, description=description, other=other)
58
+ except Exception as e:
59
+ self._logger.error("Unexpected error occured while trying to import key:\n'%s'", str(e), exc_info=True)
60
+ message = "Invalid key"
61
+ description = "Unexpected error occured while trying to import key"
62
+ other = "Ensure given key is valid"
63
+ return BaseSignatureResultsTransfers.Fail(message=message, description=description, other=other)
64
+ hash = SHA256.new(parameters.message.encode()) #* Generate message hash
65
+ #* Verify the hashed message and decoded signature
66
+ try:
67
+ pkcs1_15.new(public_key).verify(hash, b64decode(parameters.signature))
68
+ is_valid = True
69
+ except (TypeError, ValueError):
70
+ is_valid = False
71
+ data = BaseSignatureSchemas.IsValid(is_valid=is_valid)
72
+ self._logger.info("Signature successfully verified")
73
+ return BaseSignatureResultsTransfers.Verify(data=data)
74
+ return _impl()
@@ -0,0 +1,13 @@
1
+ from typing import Union
2
+ from maleo_foundation.models.transfers.results.signature import BaseSignatureResultsTransfers
3
+
4
+ class BaseSignatureResultsTypes:
5
+ Sign = Union[
6
+ BaseSignatureResultsTransfers.Fail,
7
+ BaseSignatureResultsTransfers.Sign
8
+ ]
9
+
10
+ Verify = Union[
11
+ BaseSignatureResultsTransfers.Fail,
12
+ BaseSignatureResultsTransfers.Verify
13
+ ]
@@ -26,8 +26,13 @@ class MaleoClientManager(ClientManager):
26
26
  service_manager:ServiceManager
27
27
  ):
28
28
  self._url = url
29
+ self._service_manager = service_manager
29
30
  super().__init__(key, name, service_manager.log_config, service_manager.configs.service.key)
30
31
 
32
+ @property
33
+ def service_manager(self) -> ServiceManager:
34
+ return self._service_manager
35
+
31
36
  def _initialize_controllers(self) -> None:
32
37
  #* Initialize managers
33
38
  http_controller_manager = ClientHTTPControllerManager(url=self._url)
@@ -4,16 +4,18 @@ from starlette.middleware.authentication import AuthenticationMiddleware
4
4
  from starlette.requests import HTTPConnection
5
5
  from typing import Tuple
6
6
  from maleo_foundation.authentication import Credentials, User
7
+ from maleo_foundation.client.manager import MaleoFoundationClientManager
8
+ from maleo_foundation.managers.service import Keys
7
9
  from maleo_foundation.models.transfers.parameters.token import BaseTokenParametersTransfers
8
- from maleo_foundation.services.token import BaseTokenService
9
10
  from maleo_foundation.utils.extractor import BaseExtractors
10
11
  from maleo_foundation.utils.logging import MiddlewareLogger
11
12
 
12
13
  class Backend(AuthenticationBackend):
13
- def __init__(self, logger:MiddlewareLogger, key:str):
14
+ def __init__(self, logger:MiddlewareLogger, keys:Keys, maleo_foundation:MaleoFoundationClientManager):
14
15
  super().__init__()
15
16
  self._logger = logger
16
- self._key = key
17
+ self._keys = keys
18
+ self._maleo_foundation = maleo_foundation
17
19
 
18
20
  async def authenticate(self, conn:HTTPConnection) -> Tuple[Credentials, User]:
19
21
  client_ip = BaseExtractors.extract_client_ip(conn)
@@ -27,8 +29,8 @@ class Backend(AuthenticationBackend):
27
29
  self._logger.info(f"Request | IP: {client_ip} | URL: {conn.url.path} - Result | General: Authorization scheme is not Bearer")
28
30
  return Credentials(), User(authenticated=False)
29
31
 
30
- decode_token_parameters = BaseTokenParametersTransfers.Decode(key=self._key, token=token)
31
- decode_token_result = BaseTokenService.decode(parameters=decode_token_parameters)
32
+ decode_token_parameters = BaseTokenParametersTransfers.Decode(key=self._keys.public, token=token)
33
+ decode_token_result = self._maleo_foundation.services.token.decode(parameters=decode_token_parameters)
32
34
  if not decode_token_result.success:
33
35
  self._logger.error(f"Request | IP: {client_ip} | URL: {conn.url.path} - Result | General: Failed decoding authorization token")
34
36
  return Credentials(token=token), User(authenticated=False)
@@ -36,7 +38,7 @@ class Backend(AuthenticationBackend):
36
38
  self._logger.info(f"Request | IP: {client_ip} | URL: {conn.url.path} - Result | Username: {decode_token_result.data.u_u} | Email: {decode_token_result.data.u_e}")
37
39
  return Credentials(token=token), User(authenticated=True, username=decode_token_result.data.u_u, email=decode_token_result.data.u_e)
38
40
 
39
- def add_authentication_middleware(app:FastAPI, logger:MiddlewareLogger, key:str) -> None:
41
+ def add_authentication_middleware(app:FastAPI, logger:MiddlewareLogger, keys:Keys) -> None:
40
42
  """
41
43
  Adds Authentication middleware to the FastAPI application.
42
44
 
@@ -62,4 +64,4 @@ def add_authentication_middleware(app:FastAPI, logger:MiddlewareLogger, key:str)
62
64
  add_authentication_middleware(app=app, limit=10, window=1, cleanup_interval=60, ip_timeout=300)
63
65
  ```
64
66
  """
65
- app.add_middleware(AuthenticationMiddleware, backend=Backend(logger=logger, key=key))
67
+ app.add_middleware(AuthenticationMiddleware, backend=Backend(logger=logger, keys=keys))
@@ -8,7 +8,10 @@ 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.client.manager import MaleoFoundationClientManager
12
+ from maleo_foundation.managers.service import Keys
11
13
  from maleo_foundation.models.responses import BaseResponses
14
+ from maleo_foundation.models.transfers.parameters.signature import BaseSignatureParametersTransfers
12
15
  from maleo_foundation.utils.extractor import BaseExtractors
13
16
  from maleo_foundation.utils.logging import MiddlewareLogger
14
17
 
@@ -18,8 +21,10 @@ ResponseProcessor = Callable[[Response], Awaitable[Response]]
18
21
  class BaseMiddleware(BaseHTTPMiddleware):
19
22
  def __init__(
20
23
  self,
21
- app,
24
+ app:FastAPI,
22
25
  logger:MiddlewareLogger,
26
+ keys:Keys,
27
+ maleo_foundation:MaleoFoundationClientManager,
23
28
  allow_origins:Sequence[str] = (),
24
29
  allow_methods:Sequence[str] = ("GET",),
25
30
  allow_headers:Sequence[str] = (),
@@ -27,23 +32,23 @@ class BaseMiddleware(BaseHTTPMiddleware):
27
32
  limit:int = 10,
28
33
  window:int = 1,
29
34
  cleanup_interval:int = 60,
30
- ip_timeout:int = 300,
31
- request_processor:Optional[RequestProcessor] = None
35
+ ip_timeout:int = 300
32
36
  ):
33
37
  super().__init__(app)
34
- self.logger = logger
35
- self.allow_origins = allow_origins
36
- self.allow_methods = allow_methods
37
- self.allow_headers = allow_headers
38
- self.allow_credentials = allow_credentials
39
- self.limit = limit
40
- self.window = timedelta(seconds=window)
41
- self.cleanup_interval = timedelta(seconds=cleanup_interval)
42
- self.ip_timeout = timedelta(seconds=ip_timeout)
43
- self.requests:dict[str, list[datetime]] = defaultdict(list)
44
- self.last_seen: dict[str, datetime] = {}
45
- self.last_cleanup = datetime.now()
46
- self.request_processor = request_processor if request_processor is not None else self._request_processor
38
+ self._logger = logger
39
+ self._keys = keys
40
+ self._maleo_foundation = maleo_foundation
41
+ self._allow_origins = allow_origins
42
+ self._allow_methods = allow_methods
43
+ self._allow_headers = allow_headers
44
+ self._allow_credentials = allow_credentials
45
+ self._limit = limit
46
+ self._window = timedelta(seconds=window)
47
+ self._cleanup_interval = timedelta(seconds=cleanup_interval)
48
+ self._ip_timeout = timedelta(seconds=ip_timeout)
49
+ self._requests:dict[str, list[datetime]] = defaultdict(list)
50
+ self._last_seen: dict[str, datetime] = {}
51
+ self._last_cleanup = datetime.now()
47
52
  self._lock = threading.RLock() #* Use RLock for thread safety
48
53
 
49
54
  def _cleanup_old_data(self) -> None:
@@ -54,64 +59,72 @@ class BaseMiddleware(BaseHTTPMiddleware):
54
59
  2. IPs that haven't been seen in ip_timeout period
55
60
  """
56
61
  now = datetime.now()
57
- if now - self.last_cleanup > self.cleanup_interval:
62
+ if now - self._last_cleanup > self._cleanup_interval:
58
63
  with self._lock:
59
64
  #* Remove inactive IPs (not seen recently) and empty lists
60
65
  inactive_ips = []
61
- for ip in list(self.requests.keys()):
66
+ for ip in list(self._requests.keys()):
62
67
  #* Remove IPs with empty timestamp lists
63
- if not self.requests[ip]:
68
+ if not self._requests[ip]:
64
69
  inactive_ips.append(ip)
65
70
  continue
66
71
 
67
72
  #* Remove IPs that haven't been active recently
68
- last_active = self.last_seen.get(ip, datetime.min)
69
- if now - last_active > self.ip_timeout:
73
+ last_active = self._last_seen.get(ip, datetime.min)
74
+ if now - last_active > self._ip_timeout:
70
75
  inactive_ips.append(ip)
71
76
 
72
77
  #* Remove the inactive IPs
73
78
  for ip in inactive_ips:
74
- if ip in self.requests:
75
- del self.requests[ip]
76
- if ip in self.last_seen:
77
- del self.last_seen[ip]
79
+ if ip in self._requests:
80
+ del self._requests[ip]
81
+ if ip in self._last_seen:
82
+ del self._last_seen[ip]
78
83
 
79
84
  # Update last cleanup time
80
- self.last_cleanup = now
81
- self.logger.debug(f"Cleaned up request cache. Removed {len(inactive_ips)} inactive IPs. Current tracked IPs: {len(self.requests)}")
85
+ self._last_cleanup = now
86
+ self._logger.debug(f"Cleaned up request cache. Removed {len(inactive_ips)} inactive IPs. Current tracked IPs: {len(self._requests)}")
82
87
 
83
88
  def _check_rate_limit(self, client_ip:str) -> bool:
84
89
  """Check if the client has exceeded their rate limit"""
85
90
  with self._lock:
86
91
  now = datetime.now() #* Define current timestamp
87
- self.last_seen[client_ip] = now #* Update last seen timestamp for this IP
92
+ self._last_seen[client_ip] = now #* Update last seen timestamp for this IP
88
93
 
89
94
  #* Filter requests within the window
90
- self.requests[client_ip] = [timestamp for timestamp in self.requests[client_ip] if now - timestamp <= self.window]
95
+ self._requests[client_ip] = [timestamp for timestamp in self._requests[client_ip] if now - timestamp <= self._window]
91
96
 
92
97
  #* Check if the request count exceeds the limit
93
- if len(self.requests[client_ip]) >= self.limit:
98
+ if len(self._requests[client_ip]) >= self._limit:
94
99
  return True
95
100
 
96
101
  #* Add the current request timestamp
97
- self.requests[client_ip].append(now)
102
+ self._requests[client_ip].append(now)
98
103
  return False
99
104
 
100
105
  def _append_cors_headers(self, request:Request, response:Response) -> Response:
101
106
  origin = request.headers.get("Origin")
102
107
 
103
- if origin in self.allow_origins:
108
+ if origin in self._allow_origins:
104
109
  response.headers["Access-Control-Allow-Origin"] = origin
105
- response.headers["Access-Control-Allow-Methods"] = ", ".join(self.allow_methods)
106
- response.headers["Access-Control-Allow-Headers"] = ", ".join(self.allow_headers)
107
- response.headers["Access-Control-Allow-Credentials"] = "true" if self.allow_credentials else "false"
110
+ response.headers["Access-Control-Allow-Methods"] = ", ".join(self._allow_methods)
111
+ response.headers["Access-Control-Allow-Headers"] = ", ".join(self._allow_headers)
112
+ response.headers["Access-Control-Allow-Credentials"] = "true" if self._allow_credentials else "false"
108
113
 
109
114
  return response
110
115
 
111
116
  def _add_response_headers(self, request:Request, response:Response, request_timestamp:datetime, process_time:int) -> Response:
112
117
  response.headers["X-Process-Time"] = str(process_time) #* Add Process Time Header
113
118
  response.headers["X-Request-Timestamp"] = request_timestamp.isoformat() #* Add request timestamp header
114
- response.headers["X-Response-Timestamp"] = datetime.now(tz=timezone.utc).isoformat() #* Add response timestamp header
119
+ #* Define and add response timestamp header
120
+ response_timestamp = datetime.now(tz=timezone.utc)
121
+ response.headers["X-Response-Timestamp"] = response_timestamp.isoformat()
122
+ #* Generate signature header
123
+ message = f"{request.method}|{request.url.path}|{request_timestamp.isoformat()}|{response_timestamp.isoformat()}|{str(process_time)}"
124
+ sign_parameters = BaseSignatureParametersTransfers.Sign(key=self._keys.private, password=self._keys.password, message=message)
125
+ sign_result = self._maleo_foundation.services.signature.sign(parameters=sign_parameters)
126
+ if sign_result.success:
127
+ response.headers["X-Signature"] = sign_result.data.signature
115
128
  response = self._append_cors_headers(request=request, response=response) #* Re-append CORS headers
116
129
  return response
117
130
 
@@ -125,7 +138,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
125
138
  client_ip:str = "unknown"
126
139
  ) -> Response:
127
140
  response = self._add_response_headers(request, response, request_timestamp, process_time)
128
- log_func = getattr(self.logger, log_level)
141
+ log_func = getattr(self._logger, log_level)
129
142
  log_func(
130
143
  f"Request | IP: {client_ip} | Method: {request.method} | URL: {request.url.path} | "
131
144
  f"Headers: {dict(request.headers)} - Response | Status: {response.status_code} | "
@@ -148,7 +161,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
148
161
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
149
162
  )
150
163
 
151
- self.logger.error(
164
+ self._logger.error(
152
165
  f"Request | IP: {client_ip} | Method: {request.method} | URL: {request.url.path} | "
153
166
  f"Headers: {dict(request.headers)} - Response | Status: 500 | Exception:\n{json.dumps(error_details, indent=4)}"
154
167
  )
@@ -180,7 +193,7 @@ class BaseMiddleware(BaseHTTPMiddleware):
180
193
  )
181
194
 
182
195
  #* 2. Optional preprocessing
183
- pre_response = await self.request_processor(request)
196
+ pre_response = await self._request_processor(request)
184
197
  if pre_response is not None:
185
198
  return self._build_response(
186
199
  request=request,
@@ -1,9 +1,13 @@
1
1
  from pydantic import BaseModel, Field
2
+ from maleo_foundation.types import BaseTypes
2
3
 
3
4
  class BaseSignatureSchemas:
4
5
  class Key(BaseModel):
5
6
  key:str = Field(..., description="Key")
6
7
 
8
+ class Password(BaseModel):
9
+ password:BaseTypes.OptionalString = Field(None, min_length=32, max_length=1024, description="password")
10
+
7
11
  class Message(BaseModel):
8
12
  message:str = Field(..., description="Message")
9
13
 
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+ from maleo_foundation.models.schemas.signature import BaseSignatureSchemas
3
+
4
+ class BaseSignatureGeneralTransfers:
5
+ class SignaturePackage(
6
+ BaseSignatureSchemas.Message,
7
+ BaseSignatureSchemas.Signature
8
+ ): pass
@@ -0,0 +1,14 @@
1
+ from maleo_foundation.models.schemas.signature import BaseSignatureSchemas
2
+
3
+ class BaseSignatureParametersTransfers:
4
+ class Sign(
5
+ BaseSignatureSchemas.Message,
6
+ BaseSignatureSchemas.Password,
7
+ BaseSignatureSchemas.Key
8
+ ): pass
9
+
10
+ class Verify(
11
+ BaseSignatureSchemas.Signature,
12
+ BaseSignatureSchemas.Message,
13
+ BaseSignatureSchemas.Key
14
+ ): pass
@@ -0,0 +1,12 @@
1
+ from pydantic import Field
2
+ from maleo_foundation.models.transfers.results.service.general import BaseServiceGeneralResultsTransfers
3
+ from maleo_foundation.models.schemas.signature import BaseSignatureSchemas
4
+
5
+ class BaseSignatureResultsTransfers:
6
+ class Fail(BaseServiceGeneralResultsTransfers.Fail): pass
7
+
8
+ class Sign(BaseServiceGeneralResultsTransfers.SingleData):
9
+ data:BaseSignatureSchemas.Signature = Field(..., description="Single signature data")
10
+
11
+ class Verify(BaseServiceGeneralResultsTransfers.SingleData):
12
+ data:BaseSignatureSchemas.IsValid = Field(..., description="Single verify data")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maleo_foundation
3
- Version: 0.1.58
3
+ Version: 0.1.60
4
4
  Summary: Foundation package for Maleo
5
5
  Author-email: Agra Bima Yuda <agra@nexmedis.com>
6
6
  License: MIT
@@ -14,12 +14,14 @@ maleo_foundation.egg-info/top_level.txt
14
14
  maleo_foundation/client/__init__.py
15
15
  maleo_foundation/client/manager.py
16
16
  maleo_foundation/client/services/__init__.py
17
+ maleo_foundation/client/services/signature.py
17
18
  maleo_foundation/client/services/token.py
18
19
  maleo_foundation/expanded_types/__init__.py
19
20
  maleo_foundation/expanded_types/client.py
20
21
  maleo_foundation/expanded_types/general.py
21
22
  maleo_foundation/expanded_types/query.py
22
23
  maleo_foundation/expanded_types/service.py
24
+ maleo_foundation/expanded_types/signature.py
23
25
  maleo_foundation/expanded_types/token.py
24
26
  maleo_foundation/managers/__init__.py
25
27
  maleo_foundation/managers/db.py
@@ -47,13 +49,16 @@ maleo_foundation/models/schemas/signature.py
47
49
  maleo_foundation/models/schemas/token.py
48
50
  maleo_foundation/models/transfers/__init__.py
49
51
  maleo_foundation/models/transfers/general/__init__.py
52
+ maleo_foundation/models/transfers/general/signature.py
50
53
  maleo_foundation/models/transfers/general/token.py
51
54
  maleo_foundation/models/transfers/parameters/__init__.py
52
55
  maleo_foundation/models/transfers/parameters/client.py
53
56
  maleo_foundation/models/transfers/parameters/general.py
54
57
  maleo_foundation/models/transfers/parameters/service.py
58
+ maleo_foundation/models/transfers/parameters/signature.py
55
59
  maleo_foundation/models/transfers/parameters/token.py
56
60
  maleo_foundation/models/transfers/results/__init__.py
61
+ maleo_foundation/models/transfers/results/signature.py
57
62
  maleo_foundation/models/transfers/results/token.py
58
63
  maleo_foundation/models/transfers/results/client/__init__.py
59
64
  maleo_foundation/models/transfers/results/client/service.py
@@ -64,8 +69,6 @@ maleo_foundation/models/transfers/results/service/general.py
64
69
  maleo_foundation/models/transfers/results/service/query.py
65
70
  maleo_foundation/models/transfers/results/service/controllers/__init__.py
66
71
  maleo_foundation/models/transfers/results/service/controllers/rest.py
67
- maleo_foundation/services/__init__.py
68
- maleo_foundation/services/token.py
69
72
  maleo_foundation/utils/__init__.py
70
73
  maleo_foundation/utils/controller.py
71
74
  maleo_foundation/utils/exceptions.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maleo_foundation"
7
- version = "0.1.58"
7
+ version = "0.1.60"
8
8
  description = "Foundation package for Maleo"
9
9
  authors = [
10
10
  { name = "Agra Bima Yuda", email = "agra@nexmedis.com" }
@@ -1,5 +0,0 @@
1
- from __future__ import annotations
2
- from .token import BaseTokenService
3
-
4
- class BaseServices:
5
- Token = BaseTokenService
@@ -1,20 +0,0 @@
1
- import jwt
2
- from maleo_foundation.models.transfers.general.token import BaseTokenGeneralTransfers
3
- from maleo_foundation.models.schemas.token import BaseTokenSchemas
4
- from maleo_foundation.models.transfers.parameters.token import BaseTokenParametersTransfers
5
- from maleo_foundation.models.transfers.results.token import BaseTokenResultsTransfers
6
- from maleo_foundation.expanded_types.token import BaseTokenResultsTypes
7
-
8
- class BaseTokenService:
9
- @staticmethod
10
- def encode(parameters:BaseTokenParametersTransfers.Encode) -> BaseTokenResultsTypes.Encode:
11
- payload = BaseTokenGeneralTransfers.EncodePayload.model_validate(parameters.payload.model_dump()).model_dump(mode="json")
12
- token = jwt.encode(payload=payload, key=parameters.key.encode(), algorithm="RS256")
13
- data = BaseTokenSchemas.Token(token=token)
14
- return BaseTokenResultsTransfers.Encode(data=data)
15
-
16
- @staticmethod
17
- def decode(parameters:BaseTokenParametersTransfers.Decode) -> BaseTokenResultsTypes.Decode:
18
- payload = jwt.decode(jwt=parameters.token, key=parameters.key, algorithms=["RS256"])
19
- data = BaseTokenGeneralTransfers.DecodePayload.model_validate(payload)
20
- return BaseTokenResultsTransfers.Decode(data=data)