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.
- mdb_engine/README.md +144 -0
- mdb_engine/__init__.py +37 -0
- mdb_engine/auth/README.md +631 -0
- mdb_engine/auth/__init__.py +128 -0
- mdb_engine/auth/casbin_factory.py +199 -0
- mdb_engine/auth/casbin_models.py +46 -0
- mdb_engine/auth/config_defaults.py +71 -0
- mdb_engine/auth/config_helpers.py +213 -0
- mdb_engine/auth/cookie_utils.py +158 -0
- mdb_engine/auth/decorators.py +350 -0
- mdb_engine/auth/dependencies.py +747 -0
- mdb_engine/auth/helpers.py +64 -0
- mdb_engine/auth/integration.py +578 -0
- mdb_engine/auth/jwt.py +225 -0
- mdb_engine/auth/middleware.py +241 -0
- mdb_engine/auth/oso_factory.py +323 -0
- mdb_engine/auth/provider.py +570 -0
- mdb_engine/auth/restrictions.py +271 -0
- mdb_engine/auth/session_manager.py +477 -0
- mdb_engine/auth/token_lifecycle.py +213 -0
- mdb_engine/auth/token_store.py +289 -0
- mdb_engine/auth/users.py +1516 -0
- mdb_engine/auth/utils.py +614 -0
- mdb_engine/cli/__init__.py +13 -0
- mdb_engine/cli/commands/__init__.py +7 -0
- mdb_engine/cli/commands/generate.py +105 -0
- mdb_engine/cli/commands/migrate.py +83 -0
- mdb_engine/cli/commands/show.py +70 -0
- mdb_engine/cli/commands/validate.py +63 -0
- mdb_engine/cli/main.py +41 -0
- mdb_engine/cli/utils.py +92 -0
- mdb_engine/config.py +217 -0
- mdb_engine/constants.py +160 -0
- mdb_engine/core/README.md +542 -0
- mdb_engine/core/__init__.py +42 -0
- mdb_engine/core/app_registration.py +392 -0
- mdb_engine/core/connection.py +243 -0
- mdb_engine/core/engine.py +749 -0
- mdb_engine/core/index_management.py +162 -0
- mdb_engine/core/manifest.py +2793 -0
- mdb_engine/core/seeding.py +179 -0
- mdb_engine/core/service_initialization.py +355 -0
- mdb_engine/core/types.py +413 -0
- mdb_engine/database/README.md +522 -0
- mdb_engine/database/__init__.py +31 -0
- mdb_engine/database/abstraction.py +635 -0
- mdb_engine/database/connection.py +387 -0
- mdb_engine/database/scoped_wrapper.py +1721 -0
- mdb_engine/embeddings/README.md +184 -0
- mdb_engine/embeddings/__init__.py +62 -0
- mdb_engine/embeddings/dependencies.py +193 -0
- mdb_engine/embeddings/service.py +759 -0
- mdb_engine/exceptions.py +167 -0
- mdb_engine/indexes/README.md +651 -0
- mdb_engine/indexes/__init__.py +21 -0
- mdb_engine/indexes/helpers.py +145 -0
- mdb_engine/indexes/manager.py +895 -0
- mdb_engine/memory/README.md +451 -0
- mdb_engine/memory/__init__.py +30 -0
- mdb_engine/memory/service.py +1285 -0
- mdb_engine/observability/README.md +515 -0
- mdb_engine/observability/__init__.py +42 -0
- mdb_engine/observability/health.py +296 -0
- mdb_engine/observability/logging.py +161 -0
- mdb_engine/observability/metrics.py +297 -0
- mdb_engine/routing/README.md +462 -0
- mdb_engine/routing/__init__.py +73 -0
- mdb_engine/routing/websockets.py +813 -0
- mdb_engine/utils/__init__.py +7 -0
- mdb_engine-0.1.6.dist-info/METADATA +213 -0
- mdb_engine-0.1.6.dist-info/RECORD +75 -0
- mdb_engine-0.1.6.dist-info/WHEEL +5 -0
- mdb_engine-0.1.6.dist-info/entry_points.txt +2 -0
- mdb_engine-0.1.6.dist-info/licenses/LICENSE +661 -0
- 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
|