mdb-engine 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. mdb_engine/README.md +144 -0
  2. mdb_engine/__init__.py +37 -0
  3. mdb_engine/auth/README.md +631 -0
  4. mdb_engine/auth/__init__.py +128 -0
  5. mdb_engine/auth/casbin_factory.py +199 -0
  6. mdb_engine/auth/casbin_models.py +46 -0
  7. mdb_engine/auth/config_defaults.py +71 -0
  8. mdb_engine/auth/config_helpers.py +213 -0
  9. mdb_engine/auth/cookie_utils.py +158 -0
  10. mdb_engine/auth/decorators.py +350 -0
  11. mdb_engine/auth/dependencies.py +747 -0
  12. mdb_engine/auth/helpers.py +64 -0
  13. mdb_engine/auth/integration.py +578 -0
  14. mdb_engine/auth/jwt.py +225 -0
  15. mdb_engine/auth/middleware.py +241 -0
  16. mdb_engine/auth/oso_factory.py +323 -0
  17. mdb_engine/auth/provider.py +570 -0
  18. mdb_engine/auth/restrictions.py +271 -0
  19. mdb_engine/auth/session_manager.py +477 -0
  20. mdb_engine/auth/token_lifecycle.py +213 -0
  21. mdb_engine/auth/token_store.py +289 -0
  22. mdb_engine/auth/users.py +1516 -0
  23. mdb_engine/auth/utils.py +614 -0
  24. mdb_engine/cli/__init__.py +13 -0
  25. mdb_engine/cli/commands/__init__.py +7 -0
  26. mdb_engine/cli/commands/generate.py +105 -0
  27. mdb_engine/cli/commands/migrate.py +83 -0
  28. mdb_engine/cli/commands/show.py +70 -0
  29. mdb_engine/cli/commands/validate.py +63 -0
  30. mdb_engine/cli/main.py +41 -0
  31. mdb_engine/cli/utils.py +92 -0
  32. mdb_engine/config.py +217 -0
  33. mdb_engine/constants.py +160 -0
  34. mdb_engine/core/README.md +542 -0
  35. mdb_engine/core/__init__.py +42 -0
  36. mdb_engine/core/app_registration.py +392 -0
  37. mdb_engine/core/connection.py +243 -0
  38. mdb_engine/core/engine.py +749 -0
  39. mdb_engine/core/index_management.py +162 -0
  40. mdb_engine/core/manifest.py +2793 -0
  41. mdb_engine/core/seeding.py +179 -0
  42. mdb_engine/core/service_initialization.py +355 -0
  43. mdb_engine/core/types.py +413 -0
  44. mdb_engine/database/README.md +522 -0
  45. mdb_engine/database/__init__.py +31 -0
  46. mdb_engine/database/abstraction.py +635 -0
  47. mdb_engine/database/connection.py +387 -0
  48. mdb_engine/database/scoped_wrapper.py +1721 -0
  49. mdb_engine/embeddings/README.md +184 -0
  50. mdb_engine/embeddings/__init__.py +62 -0
  51. mdb_engine/embeddings/dependencies.py +193 -0
  52. mdb_engine/embeddings/service.py +759 -0
  53. mdb_engine/exceptions.py +167 -0
  54. mdb_engine/indexes/README.md +651 -0
  55. mdb_engine/indexes/__init__.py +21 -0
  56. mdb_engine/indexes/helpers.py +145 -0
  57. mdb_engine/indexes/manager.py +895 -0
  58. mdb_engine/memory/README.md +451 -0
  59. mdb_engine/memory/__init__.py +30 -0
  60. mdb_engine/memory/service.py +1285 -0
  61. mdb_engine/observability/README.md +515 -0
  62. mdb_engine/observability/__init__.py +42 -0
  63. mdb_engine/observability/health.py +296 -0
  64. mdb_engine/observability/logging.py +161 -0
  65. mdb_engine/observability/metrics.py +297 -0
  66. mdb_engine/routing/README.md +462 -0
  67. mdb_engine/routing/__init__.py +73 -0
  68. mdb_engine/routing/websockets.py +813 -0
  69. mdb_engine/utils/__init__.py +7 -0
  70. mdb_engine-0.1.6.dist-info/METADATA +213 -0
  71. mdb_engine-0.1.6.dist-info/RECORD +75 -0
  72. mdb_engine-0.1.6.dist-info/WHEEL +5 -0
  73. mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
  74. mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
  75. mdb_engine-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,289 @@
1
+ """
2
+ Token Blacklist Management
3
+
4
+ Provides token blacklist functionality for revoking tokens before expiration.
5
+
6
+ This module is part of MDB_ENGINE - MongoDB Engine.
7
+ """
8
+
9
+ import logging
10
+ from datetime import datetime, timedelta
11
+ from typing import Optional
12
+
13
+ try:
14
+ from pymongo.errors import (ConnectionFailure, OperationFailure,
15
+ ServerSelectionTimeoutError)
16
+ except ImportError:
17
+ ConnectionFailure = Exception
18
+ OperationFailure = Exception
19
+ ServerSelectionTimeoutError = Exception
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class TokenBlacklist:
25
+ """
26
+ Manages token blacklist for revoked tokens.
27
+
28
+ Stores revoked tokens in MongoDB with TTL index for automatic cleanup.
29
+ Supports immediate revocation and scheduled revocation.
30
+ """
31
+
32
+ def __init__(self, db, collection_name: str = "token_blacklist"):
33
+ """
34
+ Initialize token blacklist manager.
35
+
36
+ Args:
37
+ db: MongoDB database instance (Motor AsyncIOMotorDatabase)
38
+ collection_name: Name of the blacklist collection (default: "token_blacklist")
39
+ """
40
+ self.db = db
41
+ self.collection = db[collection_name]
42
+ self._indexes_created = False
43
+
44
+ async def ensure_indexes(self):
45
+ """
46
+ Ensure required indexes exist for the blacklist collection.
47
+
48
+ Creates:
49
+ - Unique index on 'jti' (JWT ID) for fast lookups
50
+ - TTL index on 'expires_at' for automatic cleanup
51
+ """
52
+ if self._indexes_created:
53
+ return
54
+
55
+ try:
56
+ # Create unique index on jti
57
+ await self.collection.create_index(
58
+ "jti", unique=True, name="jti_unique_idx"
59
+ )
60
+
61
+ # Create TTL index on expires_at (auto-delete expired entries)
62
+ await self.collection.create_index(
63
+ "expires_at", expireAfterSeconds=0, name="expires_at_ttl_idx"
64
+ )
65
+
66
+ self._indexes_created = True
67
+ logger.info("Token blacklist indexes created successfully")
68
+ except (
69
+ OperationFailure,
70
+ ConnectionFailure,
71
+ ServerSelectionTimeoutError,
72
+ AttributeError,
73
+ TypeError,
74
+ ) as e:
75
+ logger.error(f"Error creating token blacklist indexes: {e}", exc_info=True)
76
+ # Don't raise - indexes might already exist
77
+
78
+ async def revoke_token(
79
+ self,
80
+ jti: str,
81
+ user_id: Optional[str] = None,
82
+ expires_at: Optional[datetime] = None,
83
+ reason: Optional[str] = None,
84
+ ) -> bool:
85
+ """
86
+ Revoke a token by adding it to the blacklist.
87
+
88
+ Args:
89
+ jti: JWT ID (unique token identifier)
90
+ user_id: Optional user ID for tracking
91
+ expires_at: Optional expiration time (defaults to token's exp claim if available)
92
+ reason: Optional reason for revocation (e.g., "logout", "password_change")
93
+
94
+ Returns:
95
+ True if token was successfully blacklisted, False otherwise
96
+ """
97
+ try:
98
+ await self.ensure_indexes()
99
+
100
+ # If expires_at not provided, use a default (e.g., 7 days from now)
101
+ if expires_at is None:
102
+ expires_at = datetime.utcnow() + timedelta(days=7)
103
+
104
+ blacklist_entry = {
105
+ "jti": jti,
106
+ "user_id": user_id,
107
+ "revoked_at": datetime.utcnow(),
108
+ "expires_at": expires_at,
109
+ "reason": reason or "manual_revocation",
110
+ }
111
+
112
+ # Use upsert to handle duplicate jti gracefully
113
+ await self.collection.update_one(
114
+ {"jti": jti}, {"$set": blacklist_entry}, upsert=True
115
+ )
116
+
117
+ logger.debug(f"Token {jti} added to blacklist (reason: {reason})")
118
+ return True
119
+ except (
120
+ OperationFailure,
121
+ ConnectionFailure,
122
+ ServerSelectionTimeoutError,
123
+ ValueError,
124
+ TypeError,
125
+ ) as e:
126
+ logger.error(f"Error revoking token {jti}: {e}", exc_info=True)
127
+ return False
128
+
129
+ async def is_revoked(self, jti: str) -> bool:
130
+ """
131
+ Check if a token is revoked (blacklisted).
132
+
133
+ Args:
134
+ jti: JWT ID to check
135
+
136
+ Returns:
137
+ True if token is blacklisted, False otherwise
138
+ """
139
+ try:
140
+ await self.ensure_indexes()
141
+
142
+ entry = await self.collection.find_one({"jti": jti})
143
+ if entry:
144
+ # Check if entry hasn't expired (TTL index should handle this, but double-check)
145
+ expires_at = entry.get("expires_at")
146
+ if expires_at and isinstance(expires_at, datetime):
147
+ if datetime.utcnow() < expires_at:
148
+ return True
149
+ elif expires_at is None:
150
+ # No expiration means it's permanently blacklisted
151
+ return True
152
+
153
+ return False
154
+ except (
155
+ OperationFailure,
156
+ ConnectionFailure,
157
+ ServerSelectionTimeoutError,
158
+ ValueError,
159
+ TypeError,
160
+ ) as e:
161
+ logger.error(
162
+ f"Error checking token revocation status for {jti}: {e}", exc_info=True
163
+ )
164
+ # On error, assume not revoked (fail open for availability)
165
+ return False
166
+
167
+ async def revoke_all_user_tokens(
168
+ self, user_id: str, reason: Optional[str] = None
169
+ ) -> int:
170
+ """
171
+ Revoke all tokens for a specific user.
172
+
173
+ Note: This doesn't add tokens to blacklist individually (would require
174
+ storing all jti values). Instead, it marks the user as having all tokens
175
+ revoked, which should be checked alongside individual token checks.
176
+
177
+ Args:
178
+ user_id: User ID whose tokens should be revoked
179
+ reason: Optional reason for revocation
180
+
181
+ Returns:
182
+ Number of tokens revoked (approximate)
183
+ """
184
+ try:
185
+ await self.ensure_indexes()
186
+
187
+ # Store a user-level revocation marker
188
+ revocation_marker = {
189
+ "user_id": user_id,
190
+ "revoked_at": datetime.utcnow(),
191
+ "reason": reason or "logout_all",
192
+ "expires_at": datetime.utcnow()
193
+ + timedelta(days=30), # Keep for 30 days
194
+ }
195
+
196
+ # Use upsert to update or create marker
197
+ await self.collection.update_one(
198
+ {"user_id": user_id, "type": "user_revocation"},
199
+ {
200
+ "$set": revocation_marker,
201
+ "$setOnInsert": {"type": "user_revocation"},
202
+ },
203
+ upsert=True,
204
+ )
205
+
206
+ logger.info(f"All tokens revoked for user {user_id} (reason: {reason})")
207
+ # Return approximate count (we don't track exact count)
208
+ return 1
209
+ except (
210
+ OperationFailure,
211
+ ConnectionFailure,
212
+ ServerSelectionTimeoutError,
213
+ ValueError,
214
+ TypeError,
215
+ ) as e:
216
+ logger.error(
217
+ f"Error revoking all tokens for user {user_id}: {e}", exc_info=True
218
+ )
219
+ return 0
220
+
221
+ async def is_user_revoked(self, user_id: str) -> bool:
222
+ """
223
+ Check if all tokens for a user have been revoked.
224
+
225
+ Args:
226
+ user_id: User ID to check
227
+
228
+ Returns:
229
+ True if user has all tokens revoked, False otherwise
230
+ """
231
+ try:
232
+ await self.ensure_indexes()
233
+
234
+ marker = await self.collection.find_one(
235
+ {"user_id": user_id, "type": "user_revocation"}
236
+ )
237
+
238
+ if marker:
239
+ # Check if marker hasn't expired
240
+ expires_at = marker.get("expires_at")
241
+ if expires_at and isinstance(expires_at, datetime):
242
+ if datetime.utcnow() < expires_at:
243
+ return True
244
+ elif expires_at is None:
245
+ return True
246
+
247
+ return False
248
+ except (
249
+ OperationFailure,
250
+ ConnectionFailure,
251
+ ServerSelectionTimeoutError,
252
+ ValueError,
253
+ TypeError,
254
+ ) as e:
255
+ logger.error(
256
+ f"Error checking user revocation status for {user_id}: {e}",
257
+ exc_info=True,
258
+ )
259
+ return False
260
+
261
+ async def clear_expired(self) -> int:
262
+ """
263
+ Manually clear expired blacklist entries.
264
+
265
+ Note: TTL index should handle this automatically, but this method
266
+ can be used for manual cleanup or verification.
267
+
268
+ Returns:
269
+ Number of entries deleted
270
+ """
271
+ try:
272
+ result = await self.collection.delete_many(
273
+ {"expires_at": {"$lt": datetime.utcnow()}}
274
+ )
275
+ deleted_count = result.deleted_count
276
+ if deleted_count > 0:
277
+ logger.info(f"Cleared {deleted_count} expired blacklist entries")
278
+ return deleted_count
279
+ except (
280
+ OperationFailure,
281
+ ConnectionFailure,
282
+ ServerSelectionTimeoutError,
283
+ ValueError,
284
+ TypeError,
285
+ ) as e:
286
+ logger.error(
287
+ f"Error clearing expired blacklist entries: {e}", exc_info=True
288
+ )
289
+ return 0