aws-python-helper 1.0.3__tar.gz → 1.1.0__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 (55) hide show
  1. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/PKG-INFO +3 -1
  2. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/api/auth_validators.py +109 -0
  3. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/api/dispatcher.py +5 -5
  4. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/api/fetcher.py +10 -1
  5. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper.egg-info/PKG-INFO +3 -1
  6. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper.egg-info/requires.txt +3 -0
  7. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/pyproject.toml +5 -1
  8. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/README.md +0 -0
  9. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/__init__.py +0 -0
  10. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/api/__init__.py +0 -0
  11. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/api/auth_middleware.py +0 -0
  12. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/api/base.py +0 -0
  13. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/api/exceptions.py +0 -0
  14. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/api/handler.py +0 -0
  15. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/context/__init__.py +0 -0
  16. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/context/session.py +0 -0
  17. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/context/state_validator.py +0 -0
  18. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/database/__init__.py +0 -0
  19. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/database/database_proxy.py +0 -0
  20. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/database/external_database_proxy.py +0 -0
  21. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/database/external_mongo_manager.py +0 -0
  22. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/database/mongo_manager.py +0 -0
  23. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/fargate/__init__.py +0 -0
  24. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/fargate/executor.py +0 -0
  25. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/fargate/fetcher.py +0 -0
  26. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/fargate/handler.py +0 -0
  27. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/fargate/task_base.py +0 -0
  28. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/invoker/__init__.py +0 -0
  29. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/invoker/api_client.py +0 -0
  30. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/invoker/exceptions.py +0 -0
  31. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/invoker/lambda_invoker.py +0 -0
  32. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/lambda_standalone/__init__.py +0 -0
  33. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/lambda_standalone/base.py +0 -0
  34. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/lambda_standalone/fetcher.py +0 -0
  35. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/lambda_standalone/handler.py +0 -0
  36. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/model_index_sync/__init__.py +0 -0
  37. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/model_index_sync/base.py +0 -0
  38. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/model_query/__init__.py +0 -0
  39. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/model_query/base.py +0 -0
  40. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/repository/__init__.py +0 -0
  41. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/repository/base.py +0 -0
  42. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/sns/__init__.py +0 -0
  43. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/sns/publisher.py +0 -0
  44. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/sqs/__init__.py +0 -0
  45. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/sqs/consumer_base.py +0 -0
  46. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/sqs/fetcher.py +0 -0
  47. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/sqs/handler.py +0 -0
  48. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/utils/__init__.py +0 -0
  49. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/utils/json_encoder.py +0 -0
  50. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/utils/response.py +0 -0
  51. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper/utils/serializer.py +0 -0
  52. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper.egg-info/SOURCES.txt +0 -0
  53. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper.egg-info/dependency_links.txt +0 -0
  54. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/aws_python_helper.egg-info/top_level.txt +0 -0
  55. {aws_python_helper-1.0.3 → aws_python_helper-1.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.0.3
3
+ Version: 1.1.0
4
4
  Summary: AWS Python Helper Framework
5
5
  Author-email: Fabian Claros <neufabiae@gmail.com>
6
6
  License-Expression: MIT
@@ -17,6 +17,8 @@ Requires-Dist: motor==3.3.2
17
17
  Requires-Dist: pymongo==4.6.1
18
18
  Requires-Dist: pydantic>=2.0
19
19
  Requires-Dist: httpx>=0.27.0
20
+ Provides-Extra: jwt
21
+ Requires-Dist: PyJWT[crypto]>=2.9.0; extra == "jwt"
20
22
 
21
23
  # AWS Python Framework
22
24
 
@@ -141,3 +141,112 @@ class TokenValidator(AuthValidator):
141
141
  'token_data': token_doc,
142
142
  'is_bypass': False
143
143
  }
144
+
145
+
146
+ class JWTValidator(AuthValidator):
147
+ """
148
+ Validates RS256 JWTs issued by constitution-core.
149
+
150
+ Stateless: the signature and the claims are enough, so no database is
151
+ touched. That is what lets any microservice authenticate a request without
152
+ calling the issuer.
153
+
154
+ Environment:
155
+ JWT_PUBLIC_KEY: RSA public key in PEM, raw or base64-encoded.
156
+ JWT_ISSUER: expected `iss`. Defaults to 'constitution-core'.
157
+ JWT_AUDIENCE: expected `aud`. Only verified when set.
158
+ AUTH_BYPASS_TOKEN: still honoured, same as TokenValidator.
159
+ """
160
+
161
+ _public_key_cache: Optional[str] = None
162
+
163
+ @classmethod
164
+ def _public_key(cls) -> str:
165
+ """The configured public key, decoded once per container."""
166
+ if cls._public_key_cache:
167
+ return cls._public_key_cache
168
+
169
+ raw = os.getenv('JWT_PUBLIC_KEY')
170
+ if not raw:
171
+ raise ValueError(
172
+ "JWT_PUBLIC_KEY environment variable not set. "
173
+ "Required when AUTH_STRATEGY=jwt."
174
+ )
175
+
176
+ key = raw.strip()
177
+ if not key.startswith('-----BEGIN'):
178
+ # PEMs are multi-line, so they travel base64-encoded in env vars.
179
+ import base64
180
+ key = base64.b64decode(key).decode('utf-8')
181
+
182
+ cls._public_key_cache = key
183
+ return key
184
+
185
+ async def validate_token(self, token: str) -> Dict[str, Any]:
186
+ bypass_token = os.getenv('AUTH_BYPASS_TOKEN')
187
+ if bypass_token and token == bypass_token:
188
+ logger.info("Bypass token used - skipping JWT validation")
189
+ return {
190
+ 'user_id': 'bypass',
191
+ 'user': {
192
+ 'email': 'bypass@system',
193
+ 'role': 'admin',
194
+ 'name': 'Bypass User',
195
+ '_id': 'bypass'
196
+ },
197
+ 'is_bypass': True,
198
+ 'token_data': None
199
+ }
200
+
201
+ try:
202
+ import jwt
203
+ except ImportError as exc:
204
+ raise RuntimeError(
205
+ "PyJWT is required for AUTH_STRATEGY=jwt. Install aws-python-helper[jwt]."
206
+ ) from exc
207
+
208
+ audience = os.getenv('JWT_AUDIENCE')
209
+ try:
210
+ claims = jwt.decode(
211
+ token,
212
+ self._public_key(),
213
+ algorithms=['RS256'],
214
+ issuer=os.getenv('JWT_ISSUER', 'constitution-core'),
215
+ audience=audience,
216
+ options={'verify_aud': bool(audience)},
217
+ )
218
+ except jwt.ExpiredSignatureError:
219
+ logger.warning("JWT expired")
220
+ raise UnauthorizedError("Token has expired")
221
+ except jwt.InvalidTokenError as exc:
222
+ logger.warning("JWT rejected: %s", exc)
223
+ raise UnauthorizedError("Invalid token")
224
+
225
+ if not claims.get('sub'):
226
+ raise UnauthorizedError("Invalid token")
227
+
228
+ # Same shape TokenValidator returns, so nothing downstream changes.
229
+ return {
230
+ 'user_id': str(claims['sub']),
231
+ 'user': {
232
+ '_id': claims['sub'],
233
+ 'email': claims.get('email'),
234
+ 'name': claims.get('name', ''),
235
+ 'role': claims.get('role', 'user'),
236
+ **(claims.get('extra') or {}),
237
+ },
238
+ 'token_data': claims,
239
+ 'is_bypass': False,
240
+ }
241
+
242
+
243
+ def get_auth_validator() -> AuthValidator:
244
+ """The validator this service is configured to use.
245
+
246
+ AUTH_STRATEGY=jwt switches to stateless validation. Anything else keeps the
247
+ database lookup, so a service only migrates when its environment says so.
248
+ """
249
+ strategy = (os.getenv('AUTH_STRATEGY') or 'db').strip().lower()
250
+ if strategy == 'jwt':
251
+ return JWTValidator()
252
+ return TokenValidator()
@@ -11,7 +11,7 @@ from .fetcher import Fetcher
11
11
  from .base import API
12
12
  from .exceptions import UnauthorizedError, ForbiddenError, AuthenticationError
13
13
  from .auth_middleware import AuthMiddleware
14
- from .auth_validators import TokenValidator
14
+ from .auth_validators import get_auth_validator
15
15
  from ..context.session import get_session
16
16
  from ..context.state_validator import StateValidator, InvalidStateError
17
17
 
@@ -240,8 +240,8 @@ class Dispatcher:
240
240
  """
241
241
  Execute authentication middleware
242
242
 
243
- Uses TokenValidator to validate tokens against AUTH_BYPASS_TOKEN
244
- or MongoDB database.
243
+ The validator is chosen by AUTH_STRATEGY: stateless JWT, or the
244
+ database lookup used so far.
245
245
 
246
246
  Args:
247
247
  api: API instance to inject authentication data into
@@ -249,8 +249,8 @@ class Dispatcher:
249
249
  Raises:
250
250
  UnauthorizedError: If authentication fails
251
251
  """
252
- # Use unified token validator
253
- validator = TokenValidator()
252
+ # Strategy comes from configuration, per service
253
+ validator = get_auth_validator()
254
254
 
255
255
  # Create middleware and authenticate
256
256
  middleware = AuthMiddleware(validator)
@@ -91,7 +91,7 @@ class Fetcher:
91
91
 
92
92
  def _looks_like_id(self, part: str) -> bool:
93
93
  """
94
- Check if a URL part looks like an ID (numeric or UUID format)
94
+ Check if a URL part looks like an ID (numeric, ObjectId or UUID format)
95
95
 
96
96
  Args:
97
97
  part: URL part to check
@@ -105,6 +105,15 @@ class Fetcher:
105
105
  # Check if it's numeric
106
106
  if part.isdigit():
107
107
  return True
108
+
109
+ # ObjectId de MongoDB: 24 caracteres hexadecimales. Sin esto, un
110
+ # PATCH /users/<oid> busca un directorio con el id como nombre.
111
+ if len(part) == 24:
112
+ try:
113
+ int(part, 16)
114
+ return True
115
+ except ValueError:
116
+ pass
108
117
 
109
118
  # Check if it's a UUID format (8-4-4-4-12 hex digits)
110
119
  if len(part) == 36 and part.count('-') == 4:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-python-helper
3
- Version: 1.0.3
3
+ Version: 1.1.0
4
4
  Summary: AWS Python Helper Framework
5
5
  Author-email: Fabian Claros <neufabiae@gmail.com>
6
6
  License-Expression: MIT
@@ -17,6 +17,8 @@ Requires-Dist: motor==3.3.2
17
17
  Requires-Dist: pymongo==4.6.1
18
18
  Requires-Dist: pydantic>=2.0
19
19
  Requires-Dist: httpx>=0.27.0
20
+ Provides-Extra: jwt
21
+ Requires-Dist: PyJWT[crypto]>=2.9.0; extra == "jwt"
20
22
 
21
23
  # AWS Python Framework
22
24
 
@@ -2,3 +2,6 @@ motor==3.3.2
2
2
  pymongo==4.6.1
3
3
  pydantic>=2.0
4
4
  httpx>=0.27.0
5
+
6
+ [jwt]
7
+ PyJWT[crypto]>=2.9.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aws-python-helper"
7
- version = "1.0.3"
7
+ version = "1.1.0"
8
8
  description = "AWS Python Helper Framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -31,6 +31,10 @@ keywords = [
31
31
  "aws", "python", "framework", "helper", "mongodb", "sqs", "sns", "fargate", "lambda"
32
32
  ]
33
33
 
34
+ # Only needed by services running AUTH_STRATEGY=jwt.
35
+ [project.optional-dependencies]
36
+ jwt = ["PyJWT[crypto]>=2.9.0"]
37
+
34
38
  [project.urls]
35
39
  "Homepage" = "https://github.com/fabiae/aws-python-framework"
36
40
  "Source Code" = "https://github.com/fabiae/aws-python-framework"