mdb-engine 0.1.6__py3-none-any.whl → 0.4.12__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 +116 -11
- mdb_engine/auth/ARCHITECTURE.md +112 -0
- mdb_engine/auth/README.md +654 -11
- mdb_engine/auth/__init__.py +136 -29
- mdb_engine/auth/audit.py +592 -0
- mdb_engine/auth/base.py +252 -0
- mdb_engine/auth/casbin_factory.py +265 -70
- mdb_engine/auth/config_defaults.py +5 -5
- mdb_engine/auth/config_helpers.py +19 -18
- mdb_engine/auth/cookie_utils.py +12 -16
- mdb_engine/auth/csrf.py +483 -0
- mdb_engine/auth/decorators.py +10 -16
- mdb_engine/auth/dependencies.py +69 -71
- mdb_engine/auth/helpers.py +3 -3
- mdb_engine/auth/integration.py +61 -88
- mdb_engine/auth/jwt.py +11 -15
- mdb_engine/auth/middleware.py +79 -35
- mdb_engine/auth/oso_factory.py +21 -41
- mdb_engine/auth/provider.py +270 -171
- mdb_engine/auth/rate_limiter.py +505 -0
- mdb_engine/auth/restrictions.py +21 -36
- mdb_engine/auth/session_manager.py +24 -41
- mdb_engine/auth/shared_middleware.py +977 -0
- mdb_engine/auth/shared_users.py +775 -0
- mdb_engine/auth/token_lifecycle.py +10 -12
- mdb_engine/auth/token_store.py +17 -32
- mdb_engine/auth/users.py +99 -159
- mdb_engine/auth/utils.py +236 -42
- mdb_engine/cli/commands/generate.py +546 -10
- mdb_engine/cli/commands/validate.py +3 -7
- mdb_engine/cli/utils.py +7 -7
- mdb_engine/config.py +13 -28
- 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 +31 -50
- mdb_engine/core/app_secrets.py +289 -0
- mdb_engine/core/connection.py +20 -12
- mdb_engine/core/encryption.py +222 -0
- mdb_engine/core/engine.py +2862 -115
- mdb_engine/core/index_management.py +12 -16
- mdb_engine/core/manifest.py +628 -204
- mdb_engine/core/ray_integration.py +436 -0
- mdb_engine/core/seeding.py +13 -21
- mdb_engine/core/service_initialization.py +20 -30
- mdb_engine/core/types.py +40 -43
- mdb_engine/database/README.md +140 -17
- mdb_engine/database/__init__.py +17 -6
- mdb_engine/database/abstraction.py +37 -50
- mdb_engine/database/connection.py +51 -30
- mdb_engine/database/query_validator.py +367 -0
- mdb_engine/database/resource_limiter.py +204 -0
- mdb_engine/database/scoped_wrapper.py +747 -237
- mdb_engine/dependencies.py +427 -0
- mdb_engine/di/__init__.py +34 -0
- mdb_engine/di/container.py +247 -0
- mdb_engine/di/providers.py +206 -0
- mdb_engine/di/scopes.py +139 -0
- mdb_engine/embeddings/README.md +54 -24
- mdb_engine/embeddings/__init__.py +31 -24
- mdb_engine/embeddings/dependencies.py +38 -155
- mdb_engine/embeddings/service.py +78 -75
- mdb_engine/exceptions.py +104 -12
- mdb_engine/indexes/README.md +30 -13
- mdb_engine/indexes/__init__.py +1 -0
- mdb_engine/indexes/helpers.py +11 -11
- mdb_engine/indexes/manager.py +59 -123
- mdb_engine/memory/README.md +95 -4
- mdb_engine/memory/__init__.py +1 -2
- mdb_engine/memory/service.py +363 -1168
- mdb_engine/observability/README.md +4 -2
- mdb_engine/observability/__init__.py +26 -9
- mdb_engine/observability/health.py +17 -17
- mdb_engine/observability/logging.py +10 -10
- mdb_engine/observability/metrics.py +40 -19
- mdb_engine/repositories/__init__.py +34 -0
- mdb_engine/repositories/base.py +325 -0
- mdb_engine/repositories/mongo.py +233 -0
- mdb_engine/repositories/unit_of_work.py +166 -0
- mdb_engine/routing/README.md +1 -1
- mdb_engine/routing/__init__.py +1 -3
- mdb_engine/routing/websockets.py +41 -75
- mdb_engine/utils/__init__.py +3 -1
- mdb_engine/utils/mongo.py +117 -0
- mdb_engine-0.4.12.dist-info/METADATA +492 -0
- mdb_engine-0.4.12.dist-info/RECORD +97 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/WHEEL +1 -1
- 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.4.12.dist-info}/entry_points.txt +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/licenses/LICENSE +0 -0
- {mdb_engine-0.1.6.dist-info → mdb_engine-0.4.12.dist-info}/top_level.txt +0 -0
mdb_engine/auth/README.md
CHANGED
|
@@ -10,6 +10,82 @@ 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
|
+
"auth_hub_url": "http://localhost:8000",
|
|
46
|
+
"related_apps": {
|
|
47
|
+
"dashboard": "http://localhost:8001"
|
|
48
|
+
},
|
|
49
|
+
"roles": ["viewer", "editor", "admin"],
|
|
50
|
+
"default_role": "viewer",
|
|
51
|
+
"require_role": "viewer",
|
|
52
|
+
"public_routes": ["/health", "/api/public"]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**When to use:**
|
|
58
|
+
- Building a platform with multiple related apps
|
|
59
|
+
- You want Single Sign-On (SSO)
|
|
60
|
+
- You need per-app role management
|
|
61
|
+
- Apps should share user identity
|
|
62
|
+
|
|
63
|
+
**Shared auth fields:**
|
|
64
|
+
| Field | Description |
|
|
65
|
+
|-------|-------------|
|
|
66
|
+
| `roles` | Available roles for this app |
|
|
67
|
+
| `auth_hub_url` | URL of the authentication hub for SSO apps. Used for redirecting unauthenticated users to login. Can be overridden via `AUTH_HUB_URL` environment variable |
|
|
68
|
+
| `related_apps` | Map of related app slugs to their URLs for cross-app navigation. Keys are app slugs, values are URLs. Can be overridden via `{APP_SLUG_UPPER}_URL` environment variables |
|
|
69
|
+
| `default_role` | Role assigned to new users |
|
|
70
|
+
| `require_role` | Minimum role required to access app |
|
|
71
|
+
| `public_routes` | Routes that don't require authentication |
|
|
72
|
+
|
|
73
|
+
**How it works:**
|
|
74
|
+
1. Users are stored in `_mdb_engine_shared_users` collection
|
|
75
|
+
2. JWT tokens work across all apps (SSO)
|
|
76
|
+
3. `SharedAuthMiddleware` is auto-configured by `engine.create_app()`
|
|
77
|
+
4. User info is available via `request.state.user`
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
# Accessing user in shared auth mode
|
|
81
|
+
@app.get("/protected")
|
|
82
|
+
async def protected(request: Request):
|
|
83
|
+
user = request.state.user # Populated by middleware
|
|
84
|
+
roles = request.state.user_roles
|
|
85
|
+
return {"email": user["email"], "roles": roles}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
See `examples/multi_app_shared/` for a complete SSO example.
|
|
13
89
|
|
|
14
90
|
## Features
|
|
15
91
|
|
|
@@ -265,11 +341,12 @@ The provider is automatically created and available via `get_authz_provider` dep
|
|
|
265
341
|
```python
|
|
266
342
|
from mdb_engine.auth import CasbinAdapter, create_casbin_enforcer
|
|
267
343
|
|
|
268
|
-
# Create enforcer with MongoDB adapter
|
|
344
|
+
# Create enforcer with MongoDB adapter (uses scoped database)
|
|
345
|
+
db = engine.get_scoped_db("my_app")
|
|
269
346
|
enforcer = await create_casbin_enforcer(
|
|
270
|
-
db=
|
|
347
|
+
db=db,
|
|
271
348
|
model="rbac",
|
|
272
|
-
policies_collection="casbin_policies"
|
|
349
|
+
policies_collection="casbin_policies" # Will be app-scoped
|
|
273
350
|
)
|
|
274
351
|
|
|
275
352
|
# Create adapter
|
|
@@ -534,12 +611,70 @@ async def logout(response: Response):
|
|
|
534
611
|
- `get_or_create_anonymous_user(db, app_slug, device_id)` - Get/create anonymous user
|
|
535
612
|
- `get_or_create_demo_user(db, app_slug, device_id)` - Get/create demo user
|
|
536
613
|
|
|
614
|
+
### Shared Auth (SSO)
|
|
615
|
+
|
|
616
|
+
- `SharedUserPool(mongo_db, jwt_secret, jwt_public_key, jwt_algorithm, ...)` - Shared user pool for SSO
|
|
617
|
+
- `create_user(email, password, app_roles)` - Create user in shared pool
|
|
618
|
+
- `authenticate(email, password, ip_address, fingerprint, session_binding)` - Authenticate with session binding
|
|
619
|
+
- `validate_token(token)` - Validate JWT and get user (checks blacklist)
|
|
620
|
+
- `revoke_token(token, reason)` - Revoke token by adding JTI to blacklist
|
|
621
|
+
- `revoke_all_user_tokens(user_id, reason)` - Revoke all user tokens
|
|
622
|
+
- `update_user_roles(email, app_slug, roles)` - Update user's roles for an app
|
|
623
|
+
- `user_has_role(user, app_slug, role)` - Check if user has role
|
|
624
|
+
- `get_secure_cookie_config(request)` - Get secure cookie settings
|
|
625
|
+
- `jwt_algorithm` - Property: configured algorithm (HS256, RS256, ES256)
|
|
626
|
+
- `is_asymmetric` - Property: True if using asymmetric algorithm
|
|
627
|
+
- `JWTSecretError` - Raised when JWT secret is missing
|
|
628
|
+
- `JWTKeyError` - Raised when JWT key configuration is invalid
|
|
629
|
+
- `SharedAuthMiddleware` - ASGI middleware for shared auth (supports session binding)
|
|
630
|
+
- `create_shared_auth_middleware(pool, slug, manifest_auth)` - Factory for configured middleware
|
|
631
|
+
- `create_shared_auth_middleware_lazy(slug, manifest_auth)` - Lazy factory for engine integration
|
|
632
|
+
|
|
633
|
+
### Rate Limiting
|
|
634
|
+
|
|
635
|
+
- `AuthRateLimitMiddleware(app, limits, store)` - ASGI rate limiting middleware
|
|
636
|
+
- `RateLimit(max_attempts, window_seconds)` - Rate limit configuration
|
|
637
|
+
- `InMemoryRateLimitStore` - In-memory storage for rate limiting
|
|
638
|
+
- `MongoDBRateLimitStore(db)` - MongoDB-backed distributed rate limiting
|
|
639
|
+
- `create_rate_limit_middleware(manifest_auth, store)` - Factory from manifest config
|
|
640
|
+
- `@rate_limit(max_attempts, window_seconds)` - Decorator for individual endpoints
|
|
641
|
+
|
|
642
|
+
### Audit Logging
|
|
643
|
+
|
|
644
|
+
- `AuthAuditLog(mongo_db, retention_days=90)` - Audit logger for auth events
|
|
645
|
+
- `log_event(action, success, user_email, ip_address, details)` - Log any event
|
|
646
|
+
- `log_login_success(email, ip_address, ...)` - Log successful login
|
|
647
|
+
- `log_login_failed(email, reason, ip_address, ...)` - Log failed login
|
|
648
|
+
- `log_logout(email, ip_address, ...)` - Log logout
|
|
649
|
+
- `log_register(email, ip_address, ...)` - Log registration
|
|
650
|
+
- `log_role_change(email, app_slug, old_roles, new_roles, ...)` - Log role change
|
|
651
|
+
- `log_token_revoked(email, reason, ...)` - Log token revocation
|
|
652
|
+
- `get_recent_events(hours, action, success)` - Query recent events
|
|
653
|
+
- `get_failed_logins(email, ip_address, hours)` - Query failed logins
|
|
654
|
+
- `get_security_summary(hours)` - Get security statistics
|
|
655
|
+
- `AuthAction` - Enum of audit action types
|
|
656
|
+
|
|
657
|
+
### CSRF Protection
|
|
658
|
+
|
|
659
|
+
- `CSRFMiddleware(app, secret, exempt_routes, ...)` - CSRF protection middleware
|
|
660
|
+
- `create_csrf_middleware(manifest_auth)` - Factory from manifest config
|
|
661
|
+
- `generate_csrf_token(secret)` - Generate CSRF token
|
|
662
|
+
- `validate_csrf_token(token, secret, max_age)` - Validate CSRF token
|
|
663
|
+
- `get_csrf_token(request)` - FastAPI dependency for getting CSRF token
|
|
664
|
+
|
|
665
|
+
### Password Policy
|
|
666
|
+
|
|
667
|
+
- `validate_password_strength(password, config)` - Validate password strength
|
|
668
|
+
- `validate_password_strength_async(password, config, check_breaches)` - Async validation with breach check
|
|
669
|
+
- `calculate_password_entropy(password)` - Calculate entropy in bits
|
|
670
|
+
- `is_common_password(password)` - Check against common password list
|
|
671
|
+
- `check_password_breach(password)` - Check against HaveIBeenPwned (async)
|
|
672
|
+
|
|
537
673
|
### Utilities
|
|
538
674
|
|
|
539
675
|
- `login_user(db, email, password, response)` - Login user and set cookies
|
|
540
676
|
- `register_user(db, email, password, **kwargs)` - Register new user
|
|
541
677
|
- `logout_user(response)` - Logout user and clear cookies
|
|
542
|
-
- `validate_password_strength(password)` - Validate password strength
|
|
543
678
|
|
|
544
679
|
## Decorators
|
|
545
680
|
|
|
@@ -595,16 +730,524 @@ async def login(credentials: dict):
|
|
|
595
730
|
9. **Log authentication events** - Track login, logout, and permission failures
|
|
596
731
|
10. **Use sub-authentication** - Isolate app-level users for multi-tenant applications
|
|
597
732
|
|
|
733
|
+
## Enterprise Security Features
|
|
734
|
+
|
|
735
|
+
MDB_ENGINE auth includes enterprise-grade security features for production deployments.
|
|
736
|
+
|
|
737
|
+
### JWT Secret Validation (Fail-Fast)
|
|
738
|
+
|
|
739
|
+
The `SharedUserPool` now **requires** a JWT secret - it will fail at startup if not configured:
|
|
740
|
+
|
|
741
|
+
```python
|
|
742
|
+
from mdb_engine.auth import SharedUserPool, JWTSecretError
|
|
743
|
+
|
|
744
|
+
# Production: Requires MDB_ENGINE_JWT_SECRET env var or explicit secret
|
|
745
|
+
try:
|
|
746
|
+
pool = SharedUserPool(db) # Will raise JWTSecretError if no secret
|
|
747
|
+
except JWTSecretError:
|
|
748
|
+
print("Set MDB_ENGINE_JWT_SECRET environment variable!")
|
|
749
|
+
|
|
750
|
+
# Development: Use allow_insecure_dev for local testing (NOT for production!)
|
|
751
|
+
pool = SharedUserPool(db, allow_insecure_dev=True) # Auto-generates ephemeral secret
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
**Generate a secure secret:**
|
|
755
|
+
```bash
|
|
756
|
+
python -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### Token Revocation (JTI)
|
|
760
|
+
|
|
761
|
+
Tokens now include a unique JWT ID (JTI) that enables server-side revocation:
|
|
762
|
+
|
|
763
|
+
```python
|
|
764
|
+
# Revoke a specific token (e.g., on logout)
|
|
765
|
+
await user_pool.revoke_token(token, reason="logout")
|
|
766
|
+
|
|
767
|
+
# Token is now blacklisted and will fail validation
|
|
768
|
+
user = await user_pool.validate_token(token) # Returns None
|
|
769
|
+
|
|
770
|
+
# Revoke all user tokens (e.g., password change)
|
|
771
|
+
await user_pool.revoke_all_user_tokens(user_id, reason="password_change")
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
**How it works:**
|
|
775
|
+
- Each token contains a `jti` (JWT ID) claim
|
|
776
|
+
- `revoke_token()` adds the JTI to a blacklist collection
|
|
777
|
+
- `validate_token()` checks the blacklist before accepting the token
|
|
778
|
+
- Blacklist entries auto-expire via MongoDB TTL index
|
|
779
|
+
|
|
780
|
+
### Rate Limiting
|
|
781
|
+
|
|
782
|
+
Protect auth endpoints from brute-force attacks with built-in rate limiting:
|
|
783
|
+
|
|
784
|
+
**Manifest Configuration:**
|
|
785
|
+
```json
|
|
786
|
+
{
|
|
787
|
+
"auth": {
|
|
788
|
+
"mode": "shared",
|
|
789
|
+
"rate_limits": {
|
|
790
|
+
"/login": {"max_attempts": 5, "window_seconds": 300},
|
|
791
|
+
"/register": {"max_attempts": 3, "window_seconds": 3600}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
**Programmatic Usage:**
|
|
798
|
+
```python
|
|
799
|
+
from mdb_engine.auth import AuthRateLimitMiddleware, RateLimit, rate_limit
|
|
800
|
+
|
|
801
|
+
# Via middleware (auto-configured by engine.create_app())
|
|
802
|
+
app.add_middleware(
|
|
803
|
+
AuthRateLimitMiddleware,
|
|
804
|
+
limits={"/login": RateLimit(max_attempts=5, window_seconds=300)}
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
# Via decorator
|
|
808
|
+
@app.post("/login")
|
|
809
|
+
@rate_limit(max_attempts=5, window_seconds=300)
|
|
810
|
+
async def login(request: Request):
|
|
811
|
+
...
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
**Features:**
|
|
815
|
+
- IP + email tracking for granular rate limiting
|
|
816
|
+
- Sliding window algorithm
|
|
817
|
+
- In-memory (single instance) or MongoDB (distributed) storage
|
|
818
|
+
- 429 responses with `Retry-After` header
|
|
819
|
+
- Automatic reset on successful login
|
|
820
|
+
|
|
821
|
+
### Audit Logging
|
|
822
|
+
|
|
823
|
+
Comprehensive audit trail for authentication events:
|
|
824
|
+
|
|
825
|
+
**Manifest Configuration:**
|
|
826
|
+
```json
|
|
827
|
+
{
|
|
828
|
+
"auth": {
|
|
829
|
+
"audit": {
|
|
830
|
+
"enabled": true,
|
|
831
|
+
"retention_days": 90
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
**Programmatic Usage:**
|
|
838
|
+
```python
|
|
839
|
+
from mdb_engine.auth import AuthAuditLog, AuthAction
|
|
840
|
+
|
|
841
|
+
audit = AuthAuditLog(db, retention_days=90)
|
|
842
|
+
await audit.ensure_indexes()
|
|
843
|
+
|
|
844
|
+
# Log authentication events
|
|
845
|
+
await audit.log_login_success(email="user@example.com", ip_address="192.168.1.1")
|
|
846
|
+
await audit.log_login_failed(email="user@example.com", reason="invalid_password")
|
|
847
|
+
await audit.log_logout(email="user@example.com")
|
|
848
|
+
await audit.log_register(email="new@example.com")
|
|
849
|
+
await audit.log_role_change(email="user@example.com", app_slug="my_app",
|
|
850
|
+
old_roles=["viewer"], new_roles=["editor"])
|
|
851
|
+
|
|
852
|
+
# Query audit logs
|
|
853
|
+
failed_logins = await audit.get_failed_logins(email="user@example.com", hours=24)
|
|
854
|
+
user_activity = await audit.get_user_activity(email="user@example.com", hours=168)
|
|
855
|
+
summary = await audit.get_security_summary(hours=24)
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
**Audit Actions:**
|
|
859
|
+
| Action | Description |
|
|
860
|
+
|--------|-------------|
|
|
861
|
+
| `login_success` | Successful login |
|
|
862
|
+
| `login_failed` | Failed login attempt |
|
|
863
|
+
| `logout` | User logout |
|
|
864
|
+
| `register` | New user registration |
|
|
865
|
+
| `token_revoked` | Token was revoked |
|
|
866
|
+
| `role_granted` | User received new role |
|
|
867
|
+
| `role_revoked` | User role was removed |
|
|
868
|
+
| `rate_limit_exceeded` | Rate limit was hit |
|
|
869
|
+
|
|
870
|
+
### Secure Cookies
|
|
871
|
+
|
|
872
|
+
Auto-configured secure cookie settings based on environment:
|
|
873
|
+
|
|
874
|
+
```python
|
|
875
|
+
# Get secure cookie config (auto-detects HTTPS/production)
|
|
876
|
+
cookie_config = user_pool.get_secure_cookie_config(request)
|
|
877
|
+
response.set_cookie(value=token, **cookie_config)
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
**Cookie settings by environment:**
|
|
881
|
+
| Setting | Development | Production |
|
|
882
|
+
|---------|-------------|------------|
|
|
883
|
+
| `httponly` | True | True |
|
|
884
|
+
| `secure` | False | True |
|
|
885
|
+
| `samesite` | lax | strict |
|
|
886
|
+
|
|
887
|
+
### Security Checklist
|
|
888
|
+
|
|
889
|
+
Before deploying to production:
|
|
890
|
+
|
|
891
|
+
- [ ] `MDB_ENGINE_JWT_SECRET` is set to a secure, unique value
|
|
892
|
+
- [ ] Rate limiting is configured for `/login` and `/register`
|
|
893
|
+
- [ ] Audit logging is enabled
|
|
894
|
+
- [ ] HTTPS is enforced (cookie `secure` flag)
|
|
895
|
+
- [ ] Token expiry is appropriately short (default: 24h)
|
|
896
|
+
- [ ] Logout endpoints call `revoke_token()`
|
|
897
|
+
|
|
898
|
+
## Advanced Security Features
|
|
899
|
+
|
|
900
|
+
### CSRF Protection
|
|
901
|
+
|
|
902
|
+
CSRF protection is **auto-enabled for shared auth mode**. The middleware uses the double-submit cookie pattern.
|
|
903
|
+
|
|
904
|
+
**Manifest Configuration:**
|
|
905
|
+
```json
|
|
906
|
+
{
|
|
907
|
+
"auth": {
|
|
908
|
+
"mode": "shared",
|
|
909
|
+
"csrf_protection": true
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
**Advanced Configuration:**
|
|
915
|
+
```json
|
|
916
|
+
{
|
|
917
|
+
"auth": {
|
|
918
|
+
"csrf_protection": {
|
|
919
|
+
"enabled": true,
|
|
920
|
+
"exempt_routes": ["/api/*"],
|
|
921
|
+
"rotate_tokens": false,
|
|
922
|
+
"token_ttl": 3600
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
**How it works:**
|
|
929
|
+
1. GET requests receive a CSRF token in a cookie
|
|
930
|
+
2. POST/PUT/DELETE must include the token in `X-CSRF-Token` header
|
|
931
|
+
3. Token is validated using constant-time comparison
|
|
932
|
+
4. SameSite=Lax cookies provide additional protection
|
|
933
|
+
|
|
934
|
+
**Frontend Integration:**
|
|
935
|
+
|
|
936
|
+
Helper function for reading cookies:
|
|
937
|
+
|
|
938
|
+
```javascript
|
|
939
|
+
// Reusable cookie helper
|
|
940
|
+
function getCookie(name) {
|
|
941
|
+
const value = `; ${document.cookie}`;
|
|
942
|
+
const parts = value.split(`; ${name}=`);
|
|
943
|
+
if (parts.length === 2) return parts.pop().split(';').shift();
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
Include in all state-changing requests:
|
|
949
|
+
|
|
950
|
+
```javascript
|
|
951
|
+
// POST request with CSRF token
|
|
952
|
+
async function createItem(data) {
|
|
953
|
+
const response = await fetch('/api/items', {
|
|
954
|
+
method: 'POST',
|
|
955
|
+
headers: {
|
|
956
|
+
'Content-Type': 'application/json',
|
|
957
|
+
'X-CSRF-Token': getCookie('csrf_token')
|
|
958
|
+
},
|
|
959
|
+
credentials: 'same-origin',
|
|
960
|
+
body: JSON.stringify(data)
|
|
961
|
+
});
|
|
962
|
+
return response.json();
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// DELETE request with CSRF token
|
|
966
|
+
async function deleteItem(id) {
|
|
967
|
+
const response = await fetch(`/api/items/${id}`, {
|
|
968
|
+
method: 'DELETE',
|
|
969
|
+
headers: {
|
|
970
|
+
'X-CSRF-Token': getCookie('csrf_token')
|
|
971
|
+
},
|
|
972
|
+
credentials: 'same-origin'
|
|
973
|
+
});
|
|
974
|
+
return response.json();
|
|
975
|
+
}
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
**Logout Must Be POST:**
|
|
979
|
+
|
|
980
|
+
For security, logout endpoints should use POST method, not GET:
|
|
981
|
+
|
|
982
|
+
```javascript
|
|
983
|
+
// Correct: POST with CSRF token
|
|
984
|
+
async function logout() {
|
|
985
|
+
const response = await fetch('/logout', {
|
|
986
|
+
method: 'POST',
|
|
987
|
+
headers: {
|
|
988
|
+
'X-CSRF-Token': getCookie('csrf_token')
|
|
989
|
+
},
|
|
990
|
+
credentials: 'same-origin'
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
const result = await response.json();
|
|
994
|
+
if (result.success) {
|
|
995
|
+
window.location.href = result.redirect || '/login';
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
Backend endpoint:
|
|
1001
|
+
|
|
1002
|
+
```python
|
|
1003
|
+
@app.post("/logout")
|
|
1004
|
+
async def logout(request: Request):
|
|
1005
|
+
"""Logout must be POST with CSRF token."""
|
|
1006
|
+
response = JSONResponse({"success": True, "redirect": "/login"})
|
|
1007
|
+
response = await logout_user(request, response)
|
|
1008
|
+
return response
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
**Login/Register JSON Pattern:**
|
|
1012
|
+
|
|
1013
|
+
Return JSON responses for AJAX forms:
|
|
1014
|
+
|
|
1015
|
+
```python
|
|
1016
|
+
@app.post("/login")
|
|
1017
|
+
async def login(request: Request, email: str = Form(...), password: str = Form(...)):
|
|
1018
|
+
"""Login returning JSON for JavaScript frontend."""
|
|
1019
|
+
result = await authenticate_user(email, password)
|
|
1020
|
+
|
|
1021
|
+
if result["success"]:
|
|
1022
|
+
json_response = JSONResponse({"success": True, "redirect": "/dashboard"})
|
|
1023
|
+
# Copy auth cookies from result
|
|
1024
|
+
for key, value in result["response"].headers.items():
|
|
1025
|
+
if key.lower() == "set-cookie":
|
|
1026
|
+
json_response.headers.append(key, value)
|
|
1027
|
+
return json_response
|
|
1028
|
+
|
|
1029
|
+
return JSONResponse(
|
|
1030
|
+
{"success": False, "detail": result.get("error", "Login failed")},
|
|
1031
|
+
status_code=401
|
|
1032
|
+
)
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
**Error Handling:**
|
|
1036
|
+
|
|
1037
|
+
Handle CSRF validation failures (403 status):
|
|
1038
|
+
|
|
1039
|
+
```javascript
|
|
1040
|
+
async function secureRequest(url, options = {}) {
|
|
1041
|
+
const response = await fetch(url, {
|
|
1042
|
+
...options,
|
|
1043
|
+
headers: {
|
|
1044
|
+
...options.headers,
|
|
1045
|
+
'X-CSRF-Token': getCookie('csrf_token')
|
|
1046
|
+
},
|
|
1047
|
+
credentials: 'same-origin'
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
if (response.status === 403) {
|
|
1051
|
+
const data = await response.json();
|
|
1052
|
+
if (data.detail?.includes('CSRF')) {
|
|
1053
|
+
// Token expired - refresh the page
|
|
1054
|
+
window.location.reload();
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
return response;
|
|
1060
|
+
}
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
### HSTS (HTTP Strict Transport Security)
|
|
1064
|
+
|
|
1065
|
+
HSTS forces HTTPS connections in production, protecting against protocol downgrade attacks.
|
|
1066
|
+
|
|
1067
|
+
**Manifest Configuration:**
|
|
1068
|
+
```json
|
|
1069
|
+
{
|
|
1070
|
+
"auth": {
|
|
1071
|
+
"security": {
|
|
1072
|
+
"hsts": {
|
|
1073
|
+
"enabled": true,
|
|
1074
|
+
"max_age": 31536000,
|
|
1075
|
+
"include_subdomains": true,
|
|
1076
|
+
"preload": false
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
**Header Output:**
|
|
1084
|
+
```
|
|
1085
|
+
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
**Note:** Only enable `preload` if you're ready for permanent HTTPS commitment.
|
|
1089
|
+
|
|
1090
|
+
### JWT Algorithm Support
|
|
1091
|
+
|
|
1092
|
+
MDB_ENGINE supports multiple JWT signing algorithms for different security requirements.
|
|
1093
|
+
|
|
1094
|
+
| Algorithm | Type | Key | Use Case |
|
|
1095
|
+
|-----------|------|-----|----------|
|
|
1096
|
+
| HS256 | Symmetric | Shared secret | Default, simple deployments |
|
|
1097
|
+
| RS256 | Asymmetric | RSA key pair | Microservices, token verification by multiple parties |
|
|
1098
|
+
| ES256 | Asymmetric | ECDSA key pair | Modern alternative to RSA, smaller keys |
|
|
1099
|
+
|
|
1100
|
+
**Manifest Configuration:**
|
|
1101
|
+
```json
|
|
1102
|
+
{
|
|
1103
|
+
"auth": {
|
|
1104
|
+
"jwt": {
|
|
1105
|
+
"algorithm": "RS256",
|
|
1106
|
+
"token_expiry_hours": 24
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
**Environment Variables:**
|
|
1113
|
+
```bash
|
|
1114
|
+
# For HS256 (symmetric)
|
|
1115
|
+
export MDB_ENGINE_JWT_SECRET="your-secret-key"
|
|
1116
|
+
|
|
1117
|
+
# For RS256/ES256 (asymmetric)
|
|
1118
|
+
export MDB_ENGINE_JWT_SECRET="-----BEGIN RSA PRIVATE KEY-----..."
|
|
1119
|
+
export MDB_ENGINE_JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----..."
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
**Programmatic Usage:**
|
|
1123
|
+
```python
|
|
1124
|
+
pool = SharedUserPool(
|
|
1125
|
+
db,
|
|
1126
|
+
jwt_secret=private_key,
|
|
1127
|
+
jwt_public_key=public_key,
|
|
1128
|
+
jwt_algorithm="RS256"
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
# Check algorithm
|
|
1132
|
+
print(pool.jwt_algorithm) # "RS256"
|
|
1133
|
+
print(pool.is_asymmetric) # True
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
### Password Policy
|
|
1137
|
+
|
|
1138
|
+
Configurable password strength requirements with entropy calculation and breach detection.
|
|
1139
|
+
|
|
1140
|
+
**Manifest Configuration:**
|
|
1141
|
+
```json
|
|
1142
|
+
{
|
|
1143
|
+
"auth": {
|
|
1144
|
+
"password_policy": {
|
|
1145
|
+
"min_length": 12,
|
|
1146
|
+
"min_entropy_bits": 50,
|
|
1147
|
+
"require_uppercase": true,
|
|
1148
|
+
"require_lowercase": true,
|
|
1149
|
+
"require_numbers": true,
|
|
1150
|
+
"require_special": false,
|
|
1151
|
+
"check_common_passwords": true,
|
|
1152
|
+
"check_breaches": false
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
**Programmatic Usage:**
|
|
1159
|
+
```python
|
|
1160
|
+
from mdb_engine.auth import (
|
|
1161
|
+
validate_password_strength,
|
|
1162
|
+
validate_password_strength_async,
|
|
1163
|
+
calculate_password_entropy,
|
|
1164
|
+
is_common_password,
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
# Synchronous validation
|
|
1168
|
+
is_valid, errors = validate_password_strength(
|
|
1169
|
+
"MyPassword123",
|
|
1170
|
+
config=manifest.get("auth", {}).get("password_policy", {})
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
# Async validation with breach check
|
|
1174
|
+
is_valid, errors = await validate_password_strength_async(
|
|
1175
|
+
"MyPassword123",
|
|
1176
|
+
config=config,
|
|
1177
|
+
check_breaches=True # Queries HaveIBeenPwned
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
# Calculate entropy
|
|
1181
|
+
entropy = calculate_password_entropy("MyP@ssw0rd!")
|
|
1182
|
+
print(f"Entropy: {entropy} bits") # ~65 bits
|
|
1183
|
+
|
|
1184
|
+
# Check common passwords
|
|
1185
|
+
if is_common_password("password123"):
|
|
1186
|
+
print("Password is too common!")
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
**Entropy Guidelines:**
|
|
1190
|
+
| Entropy (bits) | Strength | Example |
|
|
1191
|
+
|----------------|----------|---------|
|
|
1192
|
+
| < 28 | Very Weak | "password" |
|
|
1193
|
+
| 28-35 | Weak | "Password1" |
|
|
1194
|
+
| 36-59 | Fair | "P@ssw0rd" |
|
|
1195
|
+
| 60-127 | Strong | "MyS3cur3P@ss!" |
|
|
1196
|
+
| 128+ | Very Strong | Random 20+ char |
|
|
1197
|
+
|
|
1198
|
+
### Session Binding
|
|
1199
|
+
|
|
1200
|
+
Tie sessions to client characteristics to prevent session hijacking.
|
|
1201
|
+
|
|
1202
|
+
**Manifest Configuration:**
|
|
1203
|
+
```json
|
|
1204
|
+
{
|
|
1205
|
+
"auth": {
|
|
1206
|
+
"session_binding": {
|
|
1207
|
+
"bind_ip": false,
|
|
1208
|
+
"bind_fingerprint": true,
|
|
1209
|
+
"allow_ip_change_with_reauth": true
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
| Setting | Default | Description |
|
|
1216
|
+
|---------|---------|-------------|
|
|
1217
|
+
| `bind_ip` | false | Strict: reject if client IP changes |
|
|
1218
|
+
| `bind_fingerprint` | true | Soft: log warning if fingerprint changes |
|
|
1219
|
+
| `allow_ip_change_with_reauth` | true | Allow IP change if user re-authenticates |
|
|
1220
|
+
|
|
1221
|
+
**How it works:**
|
|
1222
|
+
1. On login, IP and/or fingerprint are embedded in JWT claims
|
|
1223
|
+
2. Middleware validates these claims on each request
|
|
1224
|
+
3. IP binding = strict (rejects on mismatch)
|
|
1225
|
+
4. Fingerprint binding = soft (logs warning, useful for security monitoring)
|
|
1226
|
+
|
|
1227
|
+
**Token Claims:**
|
|
1228
|
+
```json
|
|
1229
|
+
{
|
|
1230
|
+
"sub": "user123",
|
|
1231
|
+
"email": "user@example.com",
|
|
1232
|
+
"ip": "192.168.1.100",
|
|
1233
|
+
"fp": "sha256-of-browser-fingerprint",
|
|
1234
|
+
"jti": "unique-token-id",
|
|
1235
|
+
"exp": 1234567890
|
|
1236
|
+
}
|
|
1237
|
+
```
|
|
1238
|
+
|
|
598
1239
|
## Security Considerations
|
|
599
1240
|
|
|
600
1241
|
- **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
|
-
- **
|
|
1242
|
+
- **CSRF Protection**: Auto-enabled for shared auth mode with double-submit cookie pattern
|
|
1243
|
+
- **Password Hashing**: Always hash passwords using bcrypt (built-in)
|
|
1244
|
+
- **Password Policy**: Enforce entropy requirements and check common passwords
|
|
1245
|
+
- **Rate Limiting**: Configure via manifest for `/login` and `/register`
|
|
1246
|
+
- **Token Expiration**: Use short-lived access tokens (default: 24h)
|
|
1247
|
+
- **Token Blacklisting**: Revoke tokens on logout via JTI
|
|
1248
|
+
- **Session Binding**: Bind sessions to IP/fingerprint for hijacking protection
|
|
1249
|
+
- **HSTS**: Force HTTPS in production
|
|
1250
|
+
- **Audit Logging**: Track all auth events for forensics
|
|
608
1251
|
|
|
609
1252
|
## Integration with MongoDBEngine
|
|
610
1253
|
|