mdb-engine 0.1.6__py3-none-any.whl → 0.1.7__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/__init__.py +38 -6
- mdb_engine/auth/README.md +534 -11
- mdb_engine/auth/__init__.py +129 -28
- mdb_engine/auth/audit.py +592 -0
- mdb_engine/auth/casbin_factory.py +10 -14
- mdb_engine/auth/config_helpers.py +7 -6
- mdb_engine/auth/cookie_utils.py +3 -7
- mdb_engine/auth/csrf.py +373 -0
- mdb_engine/auth/decorators.py +3 -10
- mdb_engine/auth/dependencies.py +37 -45
- mdb_engine/auth/helpers.py +3 -3
- mdb_engine/auth/integration.py +30 -73
- mdb_engine/auth/jwt.py +2 -6
- mdb_engine/auth/middleware.py +77 -34
- mdb_engine/auth/oso_factory.py +16 -36
- mdb_engine/auth/provider.py +17 -38
- mdb_engine/auth/rate_limiter.py +504 -0
- mdb_engine/auth/restrictions.py +8 -24
- mdb_engine/auth/session_manager.py +14 -29
- mdb_engine/auth/shared_middleware.py +600 -0
- mdb_engine/auth/shared_users.py +759 -0
- mdb_engine/auth/token_store.py +14 -28
- mdb_engine/auth/users.py +54 -113
- mdb_engine/auth/utils.py +213 -15
- mdb_engine/cli/commands/generate.py +545 -9
- mdb_engine/cli/commands/validate.py +3 -7
- mdb_engine/cli/utils.py +3 -3
- mdb_engine/config.py +7 -21
- mdb_engine/constants.py +65 -0
- mdb_engine/core/README.md +117 -6
- mdb_engine/core/__init__.py +39 -7
- mdb_engine/core/app_registration.py +22 -41
- mdb_engine/core/app_secrets.py +290 -0
- mdb_engine/core/connection.py +18 -9
- mdb_engine/core/encryption.py +223 -0
- mdb_engine/core/engine.py +758 -95
- mdb_engine/core/index_management.py +12 -16
- mdb_engine/core/manifest.py +424 -135
- mdb_engine/core/ray_integration.py +435 -0
- mdb_engine/core/seeding.py +10 -18
- mdb_engine/core/service_initialization.py +12 -23
- mdb_engine/core/types.py +2 -5
- mdb_engine/database/README.md +112 -16
- mdb_engine/database/__init__.py +17 -6
- mdb_engine/database/abstraction.py +25 -37
- mdb_engine/database/connection.py +11 -18
- mdb_engine/database/query_validator.py +367 -0
- mdb_engine/database/resource_limiter.py +204 -0
- mdb_engine/database/scoped_wrapper.py +713 -196
- mdb_engine/embeddings/__init__.py +17 -9
- mdb_engine/embeddings/dependencies.py +1 -3
- mdb_engine/embeddings/service.py +11 -25
- mdb_engine/exceptions.py +92 -0
- mdb_engine/indexes/README.md +30 -13
- mdb_engine/indexes/__init__.py +1 -0
- mdb_engine/indexes/helpers.py +1 -1
- mdb_engine/indexes/manager.py +50 -114
- mdb_engine/memory/README.md +2 -2
- mdb_engine/memory/__init__.py +1 -2
- mdb_engine/memory/service.py +30 -87
- mdb_engine/observability/README.md +4 -2
- mdb_engine/observability/__init__.py +26 -9
- mdb_engine/observability/health.py +8 -9
- mdb_engine/observability/metrics.py +32 -12
- mdb_engine/routing/README.md +1 -1
- mdb_engine/routing/__init__.py +1 -3
- mdb_engine/routing/websockets.py +25 -60
- mdb_engine-0.1.7.dist-info/METADATA +285 -0
- mdb_engine-0.1.7.dist-info/RECORD +85 -0
- mdb_engine-0.1.6.dist-info/METADATA +0 -213
- mdb_engine-0.1.6.dist-info/RECORD +0 -75
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/WHEEL +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.1.7.dist-info}/top_level.txt +0 -0
mdb_engine/__init__.py
CHANGED
|
@@ -3,29 +3,61 @@ MDB_ENGINE - MongoDB Engine
|
|
|
3
3
|
|
|
4
4
|
Enterprise-grade engine for building applications
|
|
5
5
|
with automatic database scoping, authentication, and resource management.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
# Simple usage
|
|
9
|
+
from mdb_engine import MongoDBEngine
|
|
10
|
+
engine = MongoDBEngine(mongo_uri=..., db_name=...)
|
|
11
|
+
await engine.initialize()
|
|
12
|
+
db = engine.get_scoped_db("my_app")
|
|
13
|
+
|
|
14
|
+
# With FastAPI integration
|
|
15
|
+
app = engine.create_app(slug="my_app", manifest=Path("manifest.json"))
|
|
16
|
+
|
|
17
|
+
# With Ray support (optional)
|
|
18
|
+
engine = MongoDBEngine(..., enable_ray=True)
|
|
6
19
|
"""
|
|
7
20
|
|
|
8
21
|
# Authentication
|
|
9
22
|
from .auth import AuthorizationProvider, get_current_user, require_admin
|
|
23
|
+
|
|
24
|
+
# Optional Ray integration
|
|
10
25
|
# Core MongoDB Engine
|
|
11
|
-
from .core import
|
|
26
|
+
from .core import (
|
|
27
|
+
RAY_AVAILABLE,
|
|
28
|
+
AppRayActor,
|
|
29
|
+
ManifestParser,
|
|
30
|
+
ManifestValidator,
|
|
31
|
+
MongoDBEngine,
|
|
32
|
+
get_ray_actor_handle,
|
|
33
|
+
ray_actor_decorator,
|
|
34
|
+
)
|
|
35
|
+
|
|
12
36
|
# Database layer
|
|
13
|
-
from .database import AppDB, ScopedMongoWrapper
|
|
37
|
+
from .database import AppDB, ScopedMongoWrapper
|
|
38
|
+
|
|
14
39
|
# Index management
|
|
15
|
-
from .indexes import (
|
|
16
|
-
|
|
40
|
+
from .indexes import (
|
|
41
|
+
AsyncAtlasIndexManager,
|
|
42
|
+
AutoIndexManager,
|
|
43
|
+
run_index_creation_for_collection,
|
|
44
|
+
)
|
|
17
45
|
|
|
18
46
|
__version__ = "0.1.6"
|
|
19
47
|
|
|
20
48
|
__all__ = [
|
|
21
|
-
# Core
|
|
49
|
+
# Core (includes FastAPI integration and optional Ray)
|
|
22
50
|
"MongoDBEngine",
|
|
23
51
|
"ManifestValidator",
|
|
24
52
|
"ManifestParser",
|
|
53
|
+
# Ray Integration (optional - only active if Ray installed)
|
|
54
|
+
"RAY_AVAILABLE",
|
|
55
|
+
"AppRayActor",
|
|
56
|
+
"get_ray_actor_handle",
|
|
57
|
+
"ray_actor_decorator",
|
|
25
58
|
# Database
|
|
26
59
|
"ScopedMongoWrapper",
|
|
27
60
|
"AppDB",
|
|
28
|
-
"get_shared_mongo_client",
|
|
29
61
|
# Auth
|
|
30
62
|
"AuthorizationProvider",
|
|
31
63
|
"get_current_user",
|
mdb_engine/auth/README.md
CHANGED
|
@@ -10,6 +10,76 @@ The engine provides **MongoDB-backed conveniences** without imposing solutions:
|
|
|
10
10
|
- **MongoDB-first**: All auth data (policies, sessions, tokens) is stored in MongoDB, leveraging the engine's scoping and isolation features
|
|
11
11
|
- **Pluggable authorization**: Choose Casbin (MongoDB-backed RBAC) or OSO (Cloud or library) - both auto-configured from manifest
|
|
12
12
|
- **App-level flexibility**: Apps can implement OAuth, custom auth flows, or use the provided app-level user management utilities
|
|
13
|
+
- **Two auth modes**: Choose between per-app isolation (`mode: "app"`) or shared user pool with SSO (`mode: "shared"`)
|
|
14
|
+
|
|
15
|
+
## Auth Modes
|
|
16
|
+
|
|
17
|
+
MDB_ENGINE supports two authentication modes, configured in manifest.json:
|
|
18
|
+
|
|
19
|
+
### Per-App Auth (`mode: "app"`) - Default
|
|
20
|
+
|
|
21
|
+
Each app has isolated authentication. Users, tokens, and sessions are specific to each app.
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"auth": {
|
|
26
|
+
"mode": "app",
|
|
27
|
+
"token_required": true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**When to use:**
|
|
33
|
+
- Apps are independent
|
|
34
|
+
- Each app manages its own users
|
|
35
|
+
- No need for SSO between apps
|
|
36
|
+
|
|
37
|
+
### Shared Auth (`mode: "shared"`) - SSO
|
|
38
|
+
|
|
39
|
+
All apps share a central user pool. Users authenticate once and can access any app (subject to role requirements).
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"auth": {
|
|
44
|
+
"mode": "shared",
|
|
45
|
+
"roles": ["viewer", "editor", "admin"],
|
|
46
|
+
"default_role": "viewer",
|
|
47
|
+
"require_role": "viewer",
|
|
48
|
+
"public_routes": ["/health", "/api/public"]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**When to use:**
|
|
54
|
+
- Building a platform with multiple related apps
|
|
55
|
+
- You want Single Sign-On (SSO)
|
|
56
|
+
- You need per-app role management
|
|
57
|
+
- Apps should share user identity
|
|
58
|
+
|
|
59
|
+
**Shared auth fields:**
|
|
60
|
+
| Field | Description |
|
|
61
|
+
|-------|-------------|
|
|
62
|
+
| `roles` | Available roles for this app |
|
|
63
|
+
| `default_role` | Role assigned to new users |
|
|
64
|
+
| `require_role` | Minimum role required to access app |
|
|
65
|
+
| `public_routes` | Routes that don't require authentication |
|
|
66
|
+
|
|
67
|
+
**How it works:**
|
|
68
|
+
1. Users are stored in `_mdb_engine_shared_users` collection
|
|
69
|
+
2. JWT tokens work across all apps (SSO)
|
|
70
|
+
3. `SharedAuthMiddleware` is auto-configured by `engine.create_app()`
|
|
71
|
+
4. User info is available via `request.state.user`
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# Accessing user in shared auth mode
|
|
75
|
+
@app.get("/protected")
|
|
76
|
+
async def protected(request: Request):
|
|
77
|
+
user = request.state.user # Populated by middleware
|
|
78
|
+
roles = request.state.user_roles
|
|
79
|
+
return {"email": user["email"], "roles": roles}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
See `examples/multi_app_shared/` for a complete SSO example.
|
|
13
83
|
|
|
14
84
|
## Features
|
|
15
85
|
|
|
@@ -265,11 +335,12 @@ The provider is automatically created and available via `get_authz_provider` dep
|
|
|
265
335
|
```python
|
|
266
336
|
from mdb_engine.auth import CasbinAdapter, create_casbin_enforcer
|
|
267
337
|
|
|
268
|
-
# Create enforcer with MongoDB adapter
|
|
338
|
+
# Create enforcer with MongoDB adapter (uses scoped database)
|
|
339
|
+
db = engine.get_scoped_db("my_app")
|
|
269
340
|
enforcer = await create_casbin_enforcer(
|
|
270
|
-
db=
|
|
341
|
+
db=db,
|
|
271
342
|
model="rbac",
|
|
272
|
-
policies_collection="casbin_policies"
|
|
343
|
+
policies_collection="casbin_policies" # Will be app-scoped
|
|
273
344
|
)
|
|
274
345
|
|
|
275
346
|
# Create adapter
|
|
@@ -534,12 +605,70 @@ async def logout(response: Response):
|
|
|
534
605
|
- `get_or_create_anonymous_user(db, app_slug, device_id)` - Get/create anonymous user
|
|
535
606
|
- `get_or_create_demo_user(db, app_slug, device_id)` - Get/create demo user
|
|
536
607
|
|
|
608
|
+
### Shared Auth (SSO)
|
|
609
|
+
|
|
610
|
+
- `SharedUserPool(mongo_db, jwt_secret, jwt_public_key, jwt_algorithm, ...)` - Shared user pool for SSO
|
|
611
|
+
- `create_user(email, password, app_roles)` - Create user in shared pool
|
|
612
|
+
- `authenticate(email, password, ip_address, fingerprint, session_binding)` - Authenticate with session binding
|
|
613
|
+
- `validate_token(token)` - Validate JWT and get user (checks blacklist)
|
|
614
|
+
- `revoke_token(token, reason)` - Revoke token by adding JTI to blacklist
|
|
615
|
+
- `revoke_all_user_tokens(user_id, reason)` - Revoke all user tokens
|
|
616
|
+
- `update_user_roles(email, app_slug, roles)` - Update user's roles for an app
|
|
617
|
+
- `user_has_role(user, app_slug, role)` - Check if user has role
|
|
618
|
+
- `get_secure_cookie_config(request)` - Get secure cookie settings
|
|
619
|
+
- `jwt_algorithm` - Property: configured algorithm (HS256, RS256, ES256)
|
|
620
|
+
- `is_asymmetric` - Property: True if using asymmetric algorithm
|
|
621
|
+
- `JWTSecretError` - Raised when JWT secret is missing
|
|
622
|
+
- `JWTKeyError` - Raised when JWT key configuration is invalid
|
|
623
|
+
- `SharedAuthMiddleware` - ASGI middleware for shared auth (supports session binding)
|
|
624
|
+
- `create_shared_auth_middleware(pool, slug, manifest_auth)` - Factory for configured middleware
|
|
625
|
+
- `create_shared_auth_middleware_lazy(slug, manifest_auth)` - Lazy factory for engine integration
|
|
626
|
+
|
|
627
|
+
### Rate Limiting
|
|
628
|
+
|
|
629
|
+
- `AuthRateLimitMiddleware(app, limits, store)` - ASGI rate limiting middleware
|
|
630
|
+
- `RateLimit(max_attempts, window_seconds)` - Rate limit configuration
|
|
631
|
+
- `InMemoryRateLimitStore` - In-memory storage for rate limiting
|
|
632
|
+
- `MongoDBRateLimitStore(db)` - MongoDB-backed distributed rate limiting
|
|
633
|
+
- `create_rate_limit_middleware(manifest_auth, store)` - Factory from manifest config
|
|
634
|
+
- `@rate_limit(max_attempts, window_seconds)` - Decorator for individual endpoints
|
|
635
|
+
|
|
636
|
+
### Audit Logging
|
|
637
|
+
|
|
638
|
+
- `AuthAuditLog(mongo_db, retention_days=90)` - Audit logger for auth events
|
|
639
|
+
- `log_event(action, success, user_email, ip_address, details)` - Log any event
|
|
640
|
+
- `log_login_success(email, ip_address, ...)` - Log successful login
|
|
641
|
+
- `log_login_failed(email, reason, ip_address, ...)` - Log failed login
|
|
642
|
+
- `log_logout(email, ip_address, ...)` - Log logout
|
|
643
|
+
- `log_register(email, ip_address, ...)` - Log registration
|
|
644
|
+
- `log_role_change(email, app_slug, old_roles, new_roles, ...)` - Log role change
|
|
645
|
+
- `log_token_revoked(email, reason, ...)` - Log token revocation
|
|
646
|
+
- `get_recent_events(hours, action, success)` - Query recent events
|
|
647
|
+
- `get_failed_logins(email, ip_address, hours)` - Query failed logins
|
|
648
|
+
- `get_security_summary(hours)` - Get security statistics
|
|
649
|
+
- `AuthAction` - Enum of audit action types
|
|
650
|
+
|
|
651
|
+
### CSRF Protection
|
|
652
|
+
|
|
653
|
+
- `CSRFMiddleware(app, secret, exempt_routes, ...)` - CSRF protection middleware
|
|
654
|
+
- `create_csrf_middleware(manifest_auth)` - Factory from manifest config
|
|
655
|
+
- `generate_csrf_token(secret)` - Generate CSRF token
|
|
656
|
+
- `validate_csrf_token(token, secret, max_age)` - Validate CSRF token
|
|
657
|
+
- `get_csrf_token(request)` - FastAPI dependency for getting CSRF token
|
|
658
|
+
|
|
659
|
+
### Password Policy
|
|
660
|
+
|
|
661
|
+
- `validate_password_strength(password, config)` - Validate password strength
|
|
662
|
+
- `validate_password_strength_async(password, config, check_breaches)` - Async validation with breach check
|
|
663
|
+
- `calculate_password_entropy(password)` - Calculate entropy in bits
|
|
664
|
+
- `is_common_password(password)` - Check against common password list
|
|
665
|
+
- `check_password_breach(password)` - Check against HaveIBeenPwned (async)
|
|
666
|
+
|
|
537
667
|
### Utilities
|
|
538
668
|
|
|
539
669
|
- `login_user(db, email, password, response)` - Login user and set cookies
|
|
540
670
|
- `register_user(db, email, password, **kwargs)` - Register new user
|
|
541
671
|
- `logout_user(response)` - Logout user and clear cookies
|
|
542
|
-
- `validate_password_strength(password)` - Validate password strength
|
|
543
672
|
|
|
544
673
|
## Decorators
|
|
545
674
|
|
|
@@ -595,16 +724,410 @@ async def login(credentials: dict):
|
|
|
595
724
|
9. **Log authentication events** - Track login, logout, and permission failures
|
|
596
725
|
10. **Use sub-authentication** - Isolate app-level users for multi-tenant applications
|
|
597
726
|
|
|
727
|
+
## Enterprise Security Features
|
|
728
|
+
|
|
729
|
+
MDB_ENGINE auth includes enterprise-grade security features for production deployments.
|
|
730
|
+
|
|
731
|
+
### JWT Secret Validation (Fail-Fast)
|
|
732
|
+
|
|
733
|
+
The `SharedUserPool` now **requires** a JWT secret - it will fail at startup if not configured:
|
|
734
|
+
|
|
735
|
+
```python
|
|
736
|
+
from mdb_engine.auth import SharedUserPool, JWTSecretError
|
|
737
|
+
|
|
738
|
+
# Production: Requires MDB_ENGINE_JWT_SECRET env var or explicit secret
|
|
739
|
+
try:
|
|
740
|
+
pool = SharedUserPool(db) # Will raise JWTSecretError if no secret
|
|
741
|
+
except JWTSecretError:
|
|
742
|
+
print("Set MDB_ENGINE_JWT_SECRET environment variable!")
|
|
743
|
+
|
|
744
|
+
# Development: Use allow_insecure_dev for local testing (NOT for production!)
|
|
745
|
+
pool = SharedUserPool(db, allow_insecure_dev=True) # Auto-generates ephemeral secret
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**Generate a secure secret:**
|
|
749
|
+
```bash
|
|
750
|
+
python -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Token Revocation (JTI)
|
|
754
|
+
|
|
755
|
+
Tokens now include a unique JWT ID (JTI) that enables server-side revocation:
|
|
756
|
+
|
|
757
|
+
```python
|
|
758
|
+
# Revoke a specific token (e.g., on logout)
|
|
759
|
+
await user_pool.revoke_token(token, reason="logout")
|
|
760
|
+
|
|
761
|
+
# Token is now blacklisted and will fail validation
|
|
762
|
+
user = await user_pool.validate_token(token) # Returns None
|
|
763
|
+
|
|
764
|
+
# Revoke all user tokens (e.g., password change)
|
|
765
|
+
await user_pool.revoke_all_user_tokens(user_id, reason="password_change")
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
**How it works:**
|
|
769
|
+
- Each token contains a `jti` (JWT ID) claim
|
|
770
|
+
- `revoke_token()` adds the JTI to a blacklist collection
|
|
771
|
+
- `validate_token()` checks the blacklist before accepting the token
|
|
772
|
+
- Blacklist entries auto-expire via MongoDB TTL index
|
|
773
|
+
|
|
774
|
+
### Rate Limiting
|
|
775
|
+
|
|
776
|
+
Protect auth endpoints from brute-force attacks with built-in rate limiting:
|
|
777
|
+
|
|
778
|
+
**Manifest Configuration:**
|
|
779
|
+
```json
|
|
780
|
+
{
|
|
781
|
+
"auth": {
|
|
782
|
+
"mode": "shared",
|
|
783
|
+
"rate_limits": {
|
|
784
|
+
"/login": {"max_attempts": 5, "window_seconds": 300},
|
|
785
|
+
"/register": {"max_attempts": 3, "window_seconds": 3600}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
**Programmatic Usage:**
|
|
792
|
+
```python
|
|
793
|
+
from mdb_engine.auth import AuthRateLimitMiddleware, RateLimit, rate_limit
|
|
794
|
+
|
|
795
|
+
# Via middleware (auto-configured by engine.create_app())
|
|
796
|
+
app.add_middleware(
|
|
797
|
+
AuthRateLimitMiddleware,
|
|
798
|
+
limits={"/login": RateLimit(max_attempts=5, window_seconds=300)}
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
# Via decorator
|
|
802
|
+
@app.post("/login")
|
|
803
|
+
@rate_limit(max_attempts=5, window_seconds=300)
|
|
804
|
+
async def login(request: Request):
|
|
805
|
+
...
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
**Features:**
|
|
809
|
+
- IP + email tracking for granular rate limiting
|
|
810
|
+
- Sliding window algorithm
|
|
811
|
+
- In-memory (single instance) or MongoDB (distributed) storage
|
|
812
|
+
- 429 responses with `Retry-After` header
|
|
813
|
+
- Automatic reset on successful login
|
|
814
|
+
|
|
815
|
+
### Audit Logging
|
|
816
|
+
|
|
817
|
+
Comprehensive audit trail for authentication events:
|
|
818
|
+
|
|
819
|
+
**Manifest Configuration:**
|
|
820
|
+
```json
|
|
821
|
+
{
|
|
822
|
+
"auth": {
|
|
823
|
+
"audit": {
|
|
824
|
+
"enabled": true,
|
|
825
|
+
"retention_days": 90
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
**Programmatic Usage:**
|
|
832
|
+
```python
|
|
833
|
+
from mdb_engine.auth import AuthAuditLog, AuthAction
|
|
834
|
+
|
|
835
|
+
audit = AuthAuditLog(db, retention_days=90)
|
|
836
|
+
await audit.ensure_indexes()
|
|
837
|
+
|
|
838
|
+
# Log authentication events
|
|
839
|
+
await audit.log_login_success(email="user@example.com", ip_address="192.168.1.1")
|
|
840
|
+
await audit.log_login_failed(email="user@example.com", reason="invalid_password")
|
|
841
|
+
await audit.log_logout(email="user@example.com")
|
|
842
|
+
await audit.log_register(email="new@example.com")
|
|
843
|
+
await audit.log_role_change(email="user@example.com", app_slug="my_app",
|
|
844
|
+
old_roles=["viewer"], new_roles=["editor"])
|
|
845
|
+
|
|
846
|
+
# Query audit logs
|
|
847
|
+
failed_logins = await audit.get_failed_logins(email="user@example.com", hours=24)
|
|
848
|
+
user_activity = await audit.get_user_activity(email="user@example.com", hours=168)
|
|
849
|
+
summary = await audit.get_security_summary(hours=24)
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
**Audit Actions:**
|
|
853
|
+
| Action | Description |
|
|
854
|
+
|--------|-------------|
|
|
855
|
+
| `login_success` | Successful login |
|
|
856
|
+
| `login_failed` | Failed login attempt |
|
|
857
|
+
| `logout` | User logout |
|
|
858
|
+
| `register` | New user registration |
|
|
859
|
+
| `token_revoked` | Token was revoked |
|
|
860
|
+
| `role_granted` | User received new role |
|
|
861
|
+
| `role_revoked` | User role was removed |
|
|
862
|
+
| `rate_limit_exceeded` | Rate limit was hit |
|
|
863
|
+
|
|
864
|
+
### Secure Cookies
|
|
865
|
+
|
|
866
|
+
Auto-configured secure cookie settings based on environment:
|
|
867
|
+
|
|
868
|
+
```python
|
|
869
|
+
# Get secure cookie config (auto-detects HTTPS/production)
|
|
870
|
+
cookie_config = user_pool.get_secure_cookie_config(request)
|
|
871
|
+
response.set_cookie(value=token, **cookie_config)
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
**Cookie settings by environment:**
|
|
875
|
+
| Setting | Development | Production |
|
|
876
|
+
|---------|-------------|------------|
|
|
877
|
+
| `httponly` | True | True |
|
|
878
|
+
| `secure` | False | True |
|
|
879
|
+
| `samesite` | lax | strict |
|
|
880
|
+
|
|
881
|
+
### Security Checklist
|
|
882
|
+
|
|
883
|
+
Before deploying to production:
|
|
884
|
+
|
|
885
|
+
- [ ] `MDB_ENGINE_JWT_SECRET` is set to a secure, unique value
|
|
886
|
+
- [ ] Rate limiting is configured for `/login` and `/register`
|
|
887
|
+
- [ ] Audit logging is enabled
|
|
888
|
+
- [ ] HTTPS is enforced (cookie `secure` flag)
|
|
889
|
+
- [ ] Token expiry is appropriately short (default: 24h)
|
|
890
|
+
- [ ] Logout endpoints call `revoke_token()`
|
|
891
|
+
|
|
892
|
+
## Advanced Security Features
|
|
893
|
+
|
|
894
|
+
### CSRF Protection
|
|
895
|
+
|
|
896
|
+
CSRF protection is **auto-enabled for shared auth mode**. The middleware uses the double-submit cookie pattern.
|
|
897
|
+
|
|
898
|
+
**Manifest Configuration:**
|
|
899
|
+
```json
|
|
900
|
+
{
|
|
901
|
+
"auth": {
|
|
902
|
+
"mode": "shared",
|
|
903
|
+
"csrf_protection": true
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
**Advanced Configuration:**
|
|
909
|
+
```json
|
|
910
|
+
{
|
|
911
|
+
"auth": {
|
|
912
|
+
"csrf_protection": {
|
|
913
|
+
"enabled": true,
|
|
914
|
+
"exempt_routes": ["/api/*"],
|
|
915
|
+
"rotate_tokens": false,
|
|
916
|
+
"token_ttl": 3600
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
**How it works:**
|
|
923
|
+
1. GET requests receive a CSRF token in a cookie
|
|
924
|
+
2. POST/PUT/DELETE must include the token in `X-CSRF-Token` header
|
|
925
|
+
3. Token is validated using constant-time comparison
|
|
926
|
+
4. SameSite=Lax cookies provide additional protection
|
|
927
|
+
|
|
928
|
+
**Frontend Integration:**
|
|
929
|
+
```javascript
|
|
930
|
+
// Read token from cookie
|
|
931
|
+
const csrfToken = document.cookie
|
|
932
|
+
.split('; ')
|
|
933
|
+
.find(row => row.startsWith('csrf_token='))
|
|
934
|
+
?.split('=')[1];
|
|
935
|
+
|
|
936
|
+
// Include in requests
|
|
937
|
+
fetch('/api/submit', {
|
|
938
|
+
method: 'POST',
|
|
939
|
+
headers: {'X-CSRF-Token': csrfToken}
|
|
940
|
+
});
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
### HSTS (HTTP Strict Transport Security)
|
|
944
|
+
|
|
945
|
+
HSTS forces HTTPS connections in production, protecting against protocol downgrade attacks.
|
|
946
|
+
|
|
947
|
+
**Manifest Configuration:**
|
|
948
|
+
```json
|
|
949
|
+
{
|
|
950
|
+
"auth": {
|
|
951
|
+
"security": {
|
|
952
|
+
"hsts": {
|
|
953
|
+
"enabled": true,
|
|
954
|
+
"max_age": 31536000,
|
|
955
|
+
"include_subdomains": true,
|
|
956
|
+
"preload": false
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
**Header Output:**
|
|
964
|
+
```
|
|
965
|
+
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
**Note:** Only enable `preload` if you're ready for permanent HTTPS commitment.
|
|
969
|
+
|
|
970
|
+
### JWT Algorithm Support
|
|
971
|
+
|
|
972
|
+
MDB_ENGINE supports multiple JWT signing algorithms for different security requirements.
|
|
973
|
+
|
|
974
|
+
| Algorithm | Type | Key | Use Case |
|
|
975
|
+
|-----------|------|-----|----------|
|
|
976
|
+
| HS256 | Symmetric | Shared secret | Default, simple deployments |
|
|
977
|
+
| RS256 | Asymmetric | RSA key pair | Microservices, token verification by multiple parties |
|
|
978
|
+
| ES256 | Asymmetric | ECDSA key pair | Modern alternative to RSA, smaller keys |
|
|
979
|
+
|
|
980
|
+
**Manifest Configuration:**
|
|
981
|
+
```json
|
|
982
|
+
{
|
|
983
|
+
"auth": {
|
|
984
|
+
"jwt": {
|
|
985
|
+
"algorithm": "RS256",
|
|
986
|
+
"token_expiry_hours": 24
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
**Environment Variables:**
|
|
993
|
+
```bash
|
|
994
|
+
# For HS256 (symmetric)
|
|
995
|
+
export MDB_ENGINE_JWT_SECRET="your-secret-key"
|
|
996
|
+
|
|
997
|
+
# For RS256/ES256 (asymmetric)
|
|
998
|
+
export MDB_ENGINE_JWT_SECRET="-----BEGIN RSA PRIVATE KEY-----..."
|
|
999
|
+
export MDB_ENGINE_JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----..."
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
**Programmatic Usage:**
|
|
1003
|
+
```python
|
|
1004
|
+
pool = SharedUserPool(
|
|
1005
|
+
db,
|
|
1006
|
+
jwt_secret=private_key,
|
|
1007
|
+
jwt_public_key=public_key,
|
|
1008
|
+
jwt_algorithm="RS256"
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
# Check algorithm
|
|
1012
|
+
print(pool.jwt_algorithm) # "RS256"
|
|
1013
|
+
print(pool.is_asymmetric) # True
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### Password Policy
|
|
1017
|
+
|
|
1018
|
+
Configurable password strength requirements with entropy calculation and breach detection.
|
|
1019
|
+
|
|
1020
|
+
**Manifest Configuration:**
|
|
1021
|
+
```json
|
|
1022
|
+
{
|
|
1023
|
+
"auth": {
|
|
1024
|
+
"password_policy": {
|
|
1025
|
+
"min_length": 12,
|
|
1026
|
+
"min_entropy_bits": 50,
|
|
1027
|
+
"require_uppercase": true,
|
|
1028
|
+
"require_lowercase": true,
|
|
1029
|
+
"require_numbers": true,
|
|
1030
|
+
"require_special": false,
|
|
1031
|
+
"check_common_passwords": true,
|
|
1032
|
+
"check_breaches": false
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
**Programmatic Usage:**
|
|
1039
|
+
```python
|
|
1040
|
+
from mdb_engine.auth import (
|
|
1041
|
+
validate_password_strength,
|
|
1042
|
+
validate_password_strength_async,
|
|
1043
|
+
calculate_password_entropy,
|
|
1044
|
+
is_common_password,
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
# Synchronous validation
|
|
1048
|
+
is_valid, errors = validate_password_strength(
|
|
1049
|
+
"MyPassword123",
|
|
1050
|
+
config=manifest.get("auth", {}).get("password_policy", {})
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
# Async validation with breach check
|
|
1054
|
+
is_valid, errors = await validate_password_strength_async(
|
|
1055
|
+
"MyPassword123",
|
|
1056
|
+
config=config,
|
|
1057
|
+
check_breaches=True # Queries HaveIBeenPwned
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
# Calculate entropy
|
|
1061
|
+
entropy = calculate_password_entropy("MyP@ssw0rd!")
|
|
1062
|
+
print(f"Entropy: {entropy} bits") # ~65 bits
|
|
1063
|
+
|
|
1064
|
+
# Check common passwords
|
|
1065
|
+
if is_common_password("password123"):
|
|
1066
|
+
print("Password is too common!")
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
**Entropy Guidelines:**
|
|
1070
|
+
| Entropy (bits) | Strength | Example |
|
|
1071
|
+
|----------------|----------|---------|
|
|
1072
|
+
| < 28 | Very Weak | "password" |
|
|
1073
|
+
| 28-35 | Weak | "Password1" |
|
|
1074
|
+
| 36-59 | Fair | "P@ssw0rd" |
|
|
1075
|
+
| 60-127 | Strong | "MyS3cur3P@ss!" |
|
|
1076
|
+
| 128+ | Very Strong | Random 20+ char |
|
|
1077
|
+
|
|
1078
|
+
### Session Binding
|
|
1079
|
+
|
|
1080
|
+
Tie sessions to client characteristics to prevent session hijacking.
|
|
1081
|
+
|
|
1082
|
+
**Manifest Configuration:**
|
|
1083
|
+
```json
|
|
1084
|
+
{
|
|
1085
|
+
"auth": {
|
|
1086
|
+
"session_binding": {
|
|
1087
|
+
"bind_ip": false,
|
|
1088
|
+
"bind_fingerprint": true,
|
|
1089
|
+
"allow_ip_change_with_reauth": true
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
| Setting | Default | Description |
|
|
1096
|
+
|---------|---------|-------------|
|
|
1097
|
+
| `bind_ip` | false | Strict: reject if client IP changes |
|
|
1098
|
+
| `bind_fingerprint` | true | Soft: log warning if fingerprint changes |
|
|
1099
|
+
| `allow_ip_change_with_reauth` | true | Allow IP change if user re-authenticates |
|
|
1100
|
+
|
|
1101
|
+
**How it works:**
|
|
1102
|
+
1. On login, IP and/or fingerprint are embedded in JWT claims
|
|
1103
|
+
2. Middleware validates these claims on each request
|
|
1104
|
+
3. IP binding = strict (rejects on mismatch)
|
|
1105
|
+
4. Fingerprint binding = soft (logs warning, useful for security monitoring)
|
|
1106
|
+
|
|
1107
|
+
**Token Claims:**
|
|
1108
|
+
```json
|
|
1109
|
+
{
|
|
1110
|
+
"sub": "user123",
|
|
1111
|
+
"email": "user@example.com",
|
|
1112
|
+
"ip": "192.168.1.100",
|
|
1113
|
+
"fp": "sha256-of-browser-fingerprint",
|
|
1114
|
+
"jti": "unique-token-id",
|
|
1115
|
+
"exp": 1234567890
|
|
1116
|
+
}
|
|
1117
|
+
```
|
|
1118
|
+
|
|
598
1119
|
## Security Considerations
|
|
599
1120
|
|
|
600
1121
|
- **Token Storage**: Store tokens in secure, HttpOnly cookies (not localStorage)
|
|
601
|
-
- **CSRF Protection**:
|
|
602
|
-
- **Password Hashing**: Always hash passwords using bcrypt
|
|
603
|
-
- **
|
|
604
|
-
- **
|
|
605
|
-
- **Token
|
|
606
|
-
- **
|
|
607
|
-
- **
|
|
1122
|
+
- **CSRF Protection**: Auto-enabled for shared auth mode with double-submit cookie pattern
|
|
1123
|
+
- **Password Hashing**: Always hash passwords using bcrypt (built-in)
|
|
1124
|
+
- **Password Policy**: Enforce entropy requirements and check common passwords
|
|
1125
|
+
- **Rate Limiting**: Configure via manifest for `/login` and `/register`
|
|
1126
|
+
- **Token Expiration**: Use short-lived access tokens (default: 24h)
|
|
1127
|
+
- **Token Blacklisting**: Revoke tokens on logout via JTI
|
|
1128
|
+
- **Session Binding**: Bind sessions to IP/fingerprint for hijacking protection
|
|
1129
|
+
- **HSTS**: Force HTTPS in production
|
|
1130
|
+
- **Audit Logging**: Track all auth events for forensics
|
|
608
1131
|
|
|
609
1132
|
## Integration with MongoDBEngine
|
|
610
1133
|
|