auth-gate 0.2.3__tar.gz → 0.3.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.
- {auth_gate-0.2.3/src/auth_gate.egg-info → auth_gate-0.3.0}/PKG-INFO +167 -18
- {auth_gate-0.2.3 → auth_gate-0.3.0}/README.md +165 -16
- {auth_gate-0.2.3 → auth_gate-0.3.0}/pyproject.toml +2 -2
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate/__init__.py +58 -4
- auth_gate-0.3.0/src/auth_gate/exceptions.py +58 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate/fastapi_utils.py +264 -2
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate/middleware.py +6 -1
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate/schemas.py +56 -1
- auth_gate-0.3.0/src/auth_gate/subscription.py +126 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate/user_auth.py +47 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0/src/auth_gate.egg-info}/PKG-INFO +167 -18
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate.egg-info/SOURCES.txt +3 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/tests/conftest.py +111 -0
- auth_gate-0.3.0/src/tests/test_subscription.py +237 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/LICENSE +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/setup.cfg +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate/config.py +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate/s2s_auth.py +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate.egg-info/dependency_links.txt +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate.egg-info/requires.txt +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/auth_gate.egg-info/top_level.txt +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/tests/__init__.py +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/tests/test_config.py +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/tests/test_fastapi_utils.py +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/tests/test_intergration.py +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/tests/test_middleware.py +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/tests/test_s2s_auth.py +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/tests/test_schema.py +0 -0
- {auth_gate-0.2.3 → auth_gate-0.3.0}/src/tests/test_user_auth.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: auth-gate
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Enterprise-grade authentication for microservices with Kong and
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Enterprise-grade authentication for microservices with Kong/Keycloak integration and subscription tier support
|
|
5
5
|
Home-page: https://github.com/tradelink-org/auth-gate
|
|
6
6
|
Author: Brian Mburu
|
|
7
7
|
Author-email: Brian Mburu <brian.mburu@students.jkuat.ac.ke>
|
|
@@ -41,12 +41,13 @@ Dynamic: license-file
|
|
|
41
41
|
|
|
42
42
|
# Auth Gate
|
|
43
43
|
|
|
44
|
-
Enterprise-grade authentication for microservices with Kong and Keycloak integration, supporting
|
|
44
|
+
Enterprise-grade authentication for microservices with Kong and Keycloak integration, supporting user authentication, service-to-service authentication, and subscription tier enforcement.
|
|
45
45
|
|
|
46
46
|
## Features
|
|
47
47
|
|
|
48
48
|
- **Dual authentication types**: Support for both user authentication and service-to-service authentication
|
|
49
49
|
- **Unified authentication flow**: Single middleware handles both user and service tokens seamlessly
|
|
50
|
+
- **Subscription tier enforcement**: Built-in support for FREE, BASIC, PROFESSIONAL, and ENTERPRISE tiers
|
|
50
51
|
- **Flexible endpoint protection**: Configure endpoints as user-only, service-only, or accessible by both
|
|
51
52
|
- **Dual-mode authentication**: Support for both Kong header-based auth (production) and direct Keycloak validation (development)
|
|
52
53
|
- **Service-to-service authentication**: Built-in client credentials flow for secure inter-service communication
|
|
@@ -55,6 +56,7 @@ Enterprise-grade authentication for microservices with Kong and Keycloak integra
|
|
|
55
56
|
- **FastAPI integration**: Ready-to-use dependencies for protecting endpoints
|
|
56
57
|
- **Role-based access control**: Fine-grained permission management with role and scope validation for both users and services
|
|
57
58
|
- **Middleware support**: Automatic request authentication with configurable exclusions
|
|
59
|
+
- **Organization context**: Multi-tenant support with organization ID tracking
|
|
58
60
|
|
|
59
61
|
## Installation
|
|
60
62
|
|
|
@@ -136,6 +138,44 @@ async def get_data(auth: AuthContext = Depends(get_current_auth)):
|
|
|
136
138
|
return {"data": "full", "service": auth.service_name}
|
|
137
139
|
```
|
|
138
140
|
|
|
141
|
+
### Subscription Tier Enforcement
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from auth_gate import (
|
|
145
|
+
require_tier,
|
|
146
|
+
require_tier_and_active,
|
|
147
|
+
require_basic,
|
|
148
|
+
require_professional,
|
|
149
|
+
require_enterprise,
|
|
150
|
+
require_paid_subscription,
|
|
151
|
+
SubscriptionTier,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Require minimum tier (PROFESSIONAL or higher)
|
|
155
|
+
@app.get("/api/analytics/advanced")
|
|
156
|
+
async def get_advanced_analytics(
|
|
157
|
+
auth: AuthContext = Depends(require_tier(SubscriptionTier.PROFESSIONAL))
|
|
158
|
+
):
|
|
159
|
+
return {"data": "advanced analytics"}
|
|
160
|
+
|
|
161
|
+
# Convenience dependency for common tiers
|
|
162
|
+
@app.get("/api/reports/enterprise")
|
|
163
|
+
async def get_enterprise_reports(auth: AuthContext = Depends(require_enterprise)):
|
|
164
|
+
return {"reports": [...]}
|
|
165
|
+
|
|
166
|
+
# Require both minimum tier AND active subscription
|
|
167
|
+
@app.get("/api/premium/dashboard")
|
|
168
|
+
async def get_premium_dashboard(
|
|
169
|
+
auth: AuthContext = Depends(require_tier_and_active(SubscriptionTier.BASIC))
|
|
170
|
+
):
|
|
171
|
+
return {"dashboard": "premium data"}
|
|
172
|
+
|
|
173
|
+
# Require any paid subscription (non-free)
|
|
174
|
+
@app.get("/api/paid-feature")
|
|
175
|
+
async def get_paid_feature(auth: AuthContext = Depends(require_paid_subscription)):
|
|
176
|
+
return {"feature": "paid-only data"}
|
|
177
|
+
```
|
|
178
|
+
|
|
139
179
|
### Role-Based Access Control
|
|
140
180
|
|
|
141
181
|
```python
|
|
@@ -214,6 +254,16 @@ VERIFY_HMAC=false
|
|
|
214
254
|
INTERNAL_HMAC_KEY=your-hmac-key
|
|
215
255
|
```
|
|
216
256
|
|
|
257
|
+
### Kong Headers for Subscription
|
|
258
|
+
|
|
259
|
+
When using Kong, configure the Token Introspector plugin to inject these subscription headers:
|
|
260
|
+
|
|
261
|
+
| Header | Description | Example Values |
|
|
262
|
+
| ----------------------- | ------------------------------ | --------------------------------------- |
|
|
263
|
+
| `X-Subscription-Tier` | User's subscription tier | `free`, `basic`, `professional`, `enterprise` |
|
|
264
|
+
| `X-Subscription-Status` | Subscription status | `active`, `suspended`, `cancelled`, `past_due` |
|
|
265
|
+
| `X-Organization-ID` | Organization identifier | `org-12345` |
|
|
266
|
+
|
|
217
267
|
## Service-to-Service Authentication
|
|
218
268
|
|
|
219
269
|
### Making Service Calls
|
|
@@ -379,14 +429,17 @@ The S2S auth client includes automatic circuit breaker protection:
|
|
|
379
429
|
|
|
380
430
|
```python
|
|
381
431
|
class UserContext:
|
|
382
|
-
user_id: str
|
|
383
|
-
username: str | None
|
|
384
|
-
email: str | None
|
|
385
|
-
roles: List[str]
|
|
386
|
-
scopes: List[str]
|
|
387
|
-
session_id: str | None
|
|
388
|
-
client_id: str | None
|
|
389
|
-
auth_source: str
|
|
432
|
+
user_id: str # Unique user identifier
|
|
433
|
+
username: str | None # Username
|
|
434
|
+
email: str | None # Email address
|
|
435
|
+
roles: List[str] # User roles
|
|
436
|
+
scopes: List[str] # OAuth scopes
|
|
437
|
+
session_id: str | None # Session identifier
|
|
438
|
+
client_id: str | None # OAuth client ID
|
|
439
|
+
auth_source: str # Authentication source
|
|
440
|
+
organization_id: str | None # Organization identifier
|
|
441
|
+
subscription_tier: SubscriptionTier # Subscription tier (FREE, BASIC, PROFESSIONAL, ENTERPRISE)
|
|
442
|
+
subscription_status: SubscriptionStatus # Status (ACTIVE, SUSPENDED, CANCELLED, PAST_DUE)
|
|
390
443
|
|
|
391
444
|
# Properties
|
|
392
445
|
is_service: bool # Always False for users
|
|
@@ -394,6 +447,9 @@ class UserContext:
|
|
|
394
447
|
is_supplier: bool # True if has supplier role
|
|
395
448
|
is_customer: bool # True if has customer role
|
|
396
449
|
is_moderator: bool # True if has moderator role
|
|
450
|
+
is_buyer: bool # True if has buyer or customer role
|
|
451
|
+
is_subscription_active: bool # True if subscription status is ACTIVE
|
|
452
|
+
is_paid_subscriber: bool # True if tier is not FREE
|
|
397
453
|
|
|
398
454
|
# Methods
|
|
399
455
|
has_role(role: str) -> bool
|
|
@@ -401,18 +457,23 @@ class UserContext:
|
|
|
401
457
|
has_all_roles(roles: List[str]) -> bool
|
|
402
458
|
has_scope(scope: str) -> bool
|
|
403
459
|
has_any_scope(scopes: List[str]) -> bool
|
|
460
|
+
has_minimum_tier(required_tier: SubscriptionTier) -> bool
|
|
461
|
+
can_access_feature(required_tier: SubscriptionTier) -> bool
|
|
404
462
|
```
|
|
405
463
|
|
|
406
464
|
### ServiceContext
|
|
407
465
|
|
|
408
466
|
```python
|
|
409
467
|
class ServiceContext:
|
|
410
|
-
service_name: str
|
|
411
|
-
service_id: str | None
|
|
412
|
-
roles: List[str]
|
|
413
|
-
session_id: str | None
|
|
414
|
-
client_id: str | None
|
|
415
|
-
auth_source: str
|
|
468
|
+
service_name: str # Service identifier (client_id)
|
|
469
|
+
service_id: str | None # Service sub claim
|
|
470
|
+
roles: List[str] # Service roles
|
|
471
|
+
session_id: str | None # Session identifier
|
|
472
|
+
client_id: str | None # OAuth client ID
|
|
473
|
+
auth_source: str # Authentication source
|
|
474
|
+
organization_id: str | None # Organization identifier
|
|
475
|
+
subscription_tier: SubscriptionTier # Defaults to FREE (services bypass tier checks)
|
|
476
|
+
subscription_status: SubscriptionStatus # Defaults to ACTIVE
|
|
416
477
|
|
|
417
478
|
# Properties
|
|
418
479
|
is_service: bool # Always True for services
|
|
@@ -424,6 +485,8 @@ class ServiceContext:
|
|
|
424
485
|
has_all_roles(roles: List[str]) -> bool
|
|
425
486
|
```
|
|
426
487
|
|
|
488
|
+
**Note:** Services bypass subscription tier checks by default when using `require_tier()` and related dependencies.
|
|
489
|
+
|
|
427
490
|
## Available Dependencies
|
|
428
491
|
|
|
429
492
|
### Authentication Dependencies
|
|
@@ -462,6 +525,59 @@ require_service_roles("role1", "role2", ...)
|
|
|
462
525
|
require_scopes("scope1", "scope2", ...)
|
|
463
526
|
```
|
|
464
527
|
|
|
528
|
+
### Subscription Dependencies
|
|
529
|
+
|
|
530
|
+
| Dependency | Description |
|
|
531
|
+
| ----------------------------- | --------------------------------------------------- |
|
|
532
|
+
| `require_tier(tier)` | Factory requiring minimum subscription tier |
|
|
533
|
+
| `require_active_subscription()` | Factory requiring active subscription status |
|
|
534
|
+
| `require_tier_and_active(tier)` | Factory requiring both tier and active status |
|
|
535
|
+
| `require_basic` | Requires BASIC tier or higher |
|
|
536
|
+
| `require_professional` | Requires PROFESSIONAL tier or higher |
|
|
537
|
+
| `require_enterprise` | Requires ENTERPRISE tier |
|
|
538
|
+
| `require_paid_subscription` | Requires any paid tier (non-FREE) |
|
|
539
|
+
| `get_subscription_tier` | Extract tier from header |
|
|
540
|
+
| `get_organization_id` | Extract organization ID from header |
|
|
541
|
+
|
|
542
|
+
### Subscription Types
|
|
543
|
+
|
|
544
|
+
```python
|
|
545
|
+
from auth_gate import SubscriptionTier, SubscriptionStatus
|
|
546
|
+
|
|
547
|
+
# Tier hierarchy (lowest to highest)
|
|
548
|
+
SubscriptionTier.FREE
|
|
549
|
+
SubscriptionTier.BASIC
|
|
550
|
+
SubscriptionTier.PROFESSIONAL
|
|
551
|
+
SubscriptionTier.ENTERPRISE
|
|
552
|
+
|
|
553
|
+
# Subscription statuses
|
|
554
|
+
SubscriptionStatus.ACTIVE
|
|
555
|
+
SubscriptionStatus.SUSPENDED
|
|
556
|
+
SubscriptionStatus.CANCELLED
|
|
557
|
+
SubscriptionStatus.PAST_DUE
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Subscription Utilities
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
from auth_gate import (
|
|
564
|
+
meets_minimum_tier,
|
|
565
|
+
compare_tiers,
|
|
566
|
+
is_paid_tier,
|
|
567
|
+
get_tier_level,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Check if user tier meets requirement
|
|
571
|
+
meets_minimum_tier(SubscriptionTier.PROFESSIONAL, SubscriptionTier.BASIC) # True
|
|
572
|
+
|
|
573
|
+
# Compare tiers (-1, 0, 1)
|
|
574
|
+
compare_tiers(SubscriptionTier.ENTERPRISE, SubscriptionTier.FREE) # > 0
|
|
575
|
+
|
|
576
|
+
# Check if tier is paid
|
|
577
|
+
is_paid_tier(SubscriptionTier.BASIC) # True
|
|
578
|
+
is_paid_tier(SubscriptionTier.FREE) # False
|
|
579
|
+
```
|
|
580
|
+
|
|
465
581
|
## Migration Guide
|
|
466
582
|
|
|
467
583
|
### Updating Existing Applications
|
|
@@ -529,11 +645,15 @@ from auth_gate import (
|
|
|
529
645
|
AuthContext,
|
|
530
646
|
UserContext,
|
|
531
647
|
ServiceContext,
|
|
648
|
+
SubscriptionTier,
|
|
532
649
|
get_current_auth,
|
|
533
650
|
get_current_user,
|
|
534
651
|
get_current_service,
|
|
535
652
|
require_roles,
|
|
536
653
|
require_user_roles,
|
|
654
|
+
require_tier,
|
|
655
|
+
require_tier_and_active,
|
|
656
|
+
require_professional,
|
|
537
657
|
)
|
|
538
658
|
|
|
539
659
|
app = FastAPI()
|
|
@@ -552,7 +672,11 @@ async def health():
|
|
|
552
672
|
# User-only endpoint
|
|
553
673
|
@app.get("/api/user/profile")
|
|
554
674
|
async def get_profile(user: UserContext = Depends(get_current_user)):
|
|
555
|
-
return {
|
|
675
|
+
return {
|
|
676
|
+
"user": user.user_id,
|
|
677
|
+
"tier": user.subscription_tier.value,
|
|
678
|
+
"organization": user.organization_id
|
|
679
|
+
}
|
|
556
680
|
|
|
557
681
|
# Service-only endpoint
|
|
558
682
|
@app.post("/api/internal/batch")
|
|
@@ -573,6 +697,31 @@ require_user_editor = require_user_roles("editor")
|
|
|
573
697
|
@app.post("/api/articles")
|
|
574
698
|
async def create_article(user: UserContext = Depends(require_user_editor)):
|
|
575
699
|
return {"author": user.user_id}
|
|
700
|
+
|
|
701
|
+
# Tier-protected endpoint (PROFESSIONAL or higher)
|
|
702
|
+
@app.get("/api/analytics")
|
|
703
|
+
async def get_analytics(auth: AuthContext = Depends(require_professional)):
|
|
704
|
+
return {"analytics": "professional data"}
|
|
705
|
+
|
|
706
|
+
# Tier and active subscription required
|
|
707
|
+
@app.get("/api/premium/reports")
|
|
708
|
+
async def get_premium_reports(
|
|
709
|
+
auth: AuthContext = Depends(require_tier_and_active(SubscriptionTier.BASIC))
|
|
710
|
+
):
|
|
711
|
+
return {"reports": "premium data"}
|
|
712
|
+
|
|
713
|
+
# Custom tier check within endpoint
|
|
714
|
+
@app.get("/api/features")
|
|
715
|
+
async def get_features(user: UserContext = Depends(get_current_user)):
|
|
716
|
+
features = ["basic_dashboard"]
|
|
717
|
+
|
|
718
|
+
if user.has_minimum_tier(SubscriptionTier.PROFESSIONAL):
|
|
719
|
+
features.append("advanced_analytics")
|
|
720
|
+
|
|
721
|
+
if user.has_minimum_tier(SubscriptionTier.ENTERPRISE):
|
|
722
|
+
features.append("custom_integrations")
|
|
723
|
+
|
|
724
|
+
return {"features": features, "tier": user.subscription_tier.value}
|
|
576
725
|
```
|
|
577
726
|
|
|
578
727
|
## License
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# Auth Gate
|
|
2
2
|
|
|
3
|
-
Enterprise-grade authentication for microservices with Kong and Keycloak integration, supporting
|
|
3
|
+
Enterprise-grade authentication for microservices with Kong and Keycloak integration, supporting user authentication, service-to-service authentication, and subscription tier enforcement.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Dual authentication types**: Support for both user authentication and service-to-service authentication
|
|
8
8
|
- **Unified authentication flow**: Single middleware handles both user and service tokens seamlessly
|
|
9
|
+
- **Subscription tier enforcement**: Built-in support for FREE, BASIC, PROFESSIONAL, and ENTERPRISE tiers
|
|
9
10
|
- **Flexible endpoint protection**: Configure endpoints as user-only, service-only, or accessible by both
|
|
10
11
|
- **Dual-mode authentication**: Support for both Kong header-based auth (production) and direct Keycloak validation (development)
|
|
11
12
|
- **Service-to-service authentication**: Built-in client credentials flow for secure inter-service communication
|
|
@@ -14,6 +15,7 @@ Enterprise-grade authentication for microservices with Kong and Keycloak integra
|
|
|
14
15
|
- **FastAPI integration**: Ready-to-use dependencies for protecting endpoints
|
|
15
16
|
- **Role-based access control**: Fine-grained permission management with role and scope validation for both users and services
|
|
16
17
|
- **Middleware support**: Automatic request authentication with configurable exclusions
|
|
18
|
+
- **Organization context**: Multi-tenant support with organization ID tracking
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
19
21
|
|
|
@@ -95,6 +97,44 @@ async def get_data(auth: AuthContext = Depends(get_current_auth)):
|
|
|
95
97
|
return {"data": "full", "service": auth.service_name}
|
|
96
98
|
```
|
|
97
99
|
|
|
100
|
+
### Subscription Tier Enforcement
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from auth_gate import (
|
|
104
|
+
require_tier,
|
|
105
|
+
require_tier_and_active,
|
|
106
|
+
require_basic,
|
|
107
|
+
require_professional,
|
|
108
|
+
require_enterprise,
|
|
109
|
+
require_paid_subscription,
|
|
110
|
+
SubscriptionTier,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Require minimum tier (PROFESSIONAL or higher)
|
|
114
|
+
@app.get("/api/analytics/advanced")
|
|
115
|
+
async def get_advanced_analytics(
|
|
116
|
+
auth: AuthContext = Depends(require_tier(SubscriptionTier.PROFESSIONAL))
|
|
117
|
+
):
|
|
118
|
+
return {"data": "advanced analytics"}
|
|
119
|
+
|
|
120
|
+
# Convenience dependency for common tiers
|
|
121
|
+
@app.get("/api/reports/enterprise")
|
|
122
|
+
async def get_enterprise_reports(auth: AuthContext = Depends(require_enterprise)):
|
|
123
|
+
return {"reports": [...]}
|
|
124
|
+
|
|
125
|
+
# Require both minimum tier AND active subscription
|
|
126
|
+
@app.get("/api/premium/dashboard")
|
|
127
|
+
async def get_premium_dashboard(
|
|
128
|
+
auth: AuthContext = Depends(require_tier_and_active(SubscriptionTier.BASIC))
|
|
129
|
+
):
|
|
130
|
+
return {"dashboard": "premium data"}
|
|
131
|
+
|
|
132
|
+
# Require any paid subscription (non-free)
|
|
133
|
+
@app.get("/api/paid-feature")
|
|
134
|
+
async def get_paid_feature(auth: AuthContext = Depends(require_paid_subscription)):
|
|
135
|
+
return {"feature": "paid-only data"}
|
|
136
|
+
```
|
|
137
|
+
|
|
98
138
|
### Role-Based Access Control
|
|
99
139
|
|
|
100
140
|
```python
|
|
@@ -173,6 +213,16 @@ VERIFY_HMAC=false
|
|
|
173
213
|
INTERNAL_HMAC_KEY=your-hmac-key
|
|
174
214
|
```
|
|
175
215
|
|
|
216
|
+
### Kong Headers for Subscription
|
|
217
|
+
|
|
218
|
+
When using Kong, configure the Token Introspector plugin to inject these subscription headers:
|
|
219
|
+
|
|
220
|
+
| Header | Description | Example Values |
|
|
221
|
+
| ----------------------- | ------------------------------ | --------------------------------------- |
|
|
222
|
+
| `X-Subscription-Tier` | User's subscription tier | `free`, `basic`, `professional`, `enterprise` |
|
|
223
|
+
| `X-Subscription-Status` | Subscription status | `active`, `suspended`, `cancelled`, `past_due` |
|
|
224
|
+
| `X-Organization-ID` | Organization identifier | `org-12345` |
|
|
225
|
+
|
|
176
226
|
## Service-to-Service Authentication
|
|
177
227
|
|
|
178
228
|
### Making Service Calls
|
|
@@ -338,14 +388,17 @@ The S2S auth client includes automatic circuit breaker protection:
|
|
|
338
388
|
|
|
339
389
|
```python
|
|
340
390
|
class UserContext:
|
|
341
|
-
user_id: str
|
|
342
|
-
username: str | None
|
|
343
|
-
email: str | None
|
|
344
|
-
roles: List[str]
|
|
345
|
-
scopes: List[str]
|
|
346
|
-
session_id: str | None
|
|
347
|
-
client_id: str | None
|
|
348
|
-
auth_source: str
|
|
391
|
+
user_id: str # Unique user identifier
|
|
392
|
+
username: str | None # Username
|
|
393
|
+
email: str | None # Email address
|
|
394
|
+
roles: List[str] # User roles
|
|
395
|
+
scopes: List[str] # OAuth scopes
|
|
396
|
+
session_id: str | None # Session identifier
|
|
397
|
+
client_id: str | None # OAuth client ID
|
|
398
|
+
auth_source: str # Authentication source
|
|
399
|
+
organization_id: str | None # Organization identifier
|
|
400
|
+
subscription_tier: SubscriptionTier # Subscription tier (FREE, BASIC, PROFESSIONAL, ENTERPRISE)
|
|
401
|
+
subscription_status: SubscriptionStatus # Status (ACTIVE, SUSPENDED, CANCELLED, PAST_DUE)
|
|
349
402
|
|
|
350
403
|
# Properties
|
|
351
404
|
is_service: bool # Always False for users
|
|
@@ -353,6 +406,9 @@ class UserContext:
|
|
|
353
406
|
is_supplier: bool # True if has supplier role
|
|
354
407
|
is_customer: bool # True if has customer role
|
|
355
408
|
is_moderator: bool # True if has moderator role
|
|
409
|
+
is_buyer: bool # True if has buyer or customer role
|
|
410
|
+
is_subscription_active: bool # True if subscription status is ACTIVE
|
|
411
|
+
is_paid_subscriber: bool # True if tier is not FREE
|
|
356
412
|
|
|
357
413
|
# Methods
|
|
358
414
|
has_role(role: str) -> bool
|
|
@@ -360,18 +416,23 @@ class UserContext:
|
|
|
360
416
|
has_all_roles(roles: List[str]) -> bool
|
|
361
417
|
has_scope(scope: str) -> bool
|
|
362
418
|
has_any_scope(scopes: List[str]) -> bool
|
|
419
|
+
has_minimum_tier(required_tier: SubscriptionTier) -> bool
|
|
420
|
+
can_access_feature(required_tier: SubscriptionTier) -> bool
|
|
363
421
|
```
|
|
364
422
|
|
|
365
423
|
### ServiceContext
|
|
366
424
|
|
|
367
425
|
```python
|
|
368
426
|
class ServiceContext:
|
|
369
|
-
service_name: str
|
|
370
|
-
service_id: str | None
|
|
371
|
-
roles: List[str]
|
|
372
|
-
session_id: str | None
|
|
373
|
-
client_id: str | None
|
|
374
|
-
auth_source: str
|
|
427
|
+
service_name: str # Service identifier (client_id)
|
|
428
|
+
service_id: str | None # Service sub claim
|
|
429
|
+
roles: List[str] # Service roles
|
|
430
|
+
session_id: str | None # Session identifier
|
|
431
|
+
client_id: str | None # OAuth client ID
|
|
432
|
+
auth_source: str # Authentication source
|
|
433
|
+
organization_id: str | None # Organization identifier
|
|
434
|
+
subscription_tier: SubscriptionTier # Defaults to FREE (services bypass tier checks)
|
|
435
|
+
subscription_status: SubscriptionStatus # Defaults to ACTIVE
|
|
375
436
|
|
|
376
437
|
# Properties
|
|
377
438
|
is_service: bool # Always True for services
|
|
@@ -383,6 +444,8 @@ class ServiceContext:
|
|
|
383
444
|
has_all_roles(roles: List[str]) -> bool
|
|
384
445
|
```
|
|
385
446
|
|
|
447
|
+
**Note:** Services bypass subscription tier checks by default when using `require_tier()` and related dependencies.
|
|
448
|
+
|
|
386
449
|
## Available Dependencies
|
|
387
450
|
|
|
388
451
|
### Authentication Dependencies
|
|
@@ -421,6 +484,59 @@ require_service_roles("role1", "role2", ...)
|
|
|
421
484
|
require_scopes("scope1", "scope2", ...)
|
|
422
485
|
```
|
|
423
486
|
|
|
487
|
+
### Subscription Dependencies
|
|
488
|
+
|
|
489
|
+
| Dependency | Description |
|
|
490
|
+
| ----------------------------- | --------------------------------------------------- |
|
|
491
|
+
| `require_tier(tier)` | Factory requiring minimum subscription tier |
|
|
492
|
+
| `require_active_subscription()` | Factory requiring active subscription status |
|
|
493
|
+
| `require_tier_and_active(tier)` | Factory requiring both tier and active status |
|
|
494
|
+
| `require_basic` | Requires BASIC tier or higher |
|
|
495
|
+
| `require_professional` | Requires PROFESSIONAL tier or higher |
|
|
496
|
+
| `require_enterprise` | Requires ENTERPRISE tier |
|
|
497
|
+
| `require_paid_subscription` | Requires any paid tier (non-FREE) |
|
|
498
|
+
| `get_subscription_tier` | Extract tier from header |
|
|
499
|
+
| `get_organization_id` | Extract organization ID from header |
|
|
500
|
+
|
|
501
|
+
### Subscription Types
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
from auth_gate import SubscriptionTier, SubscriptionStatus
|
|
505
|
+
|
|
506
|
+
# Tier hierarchy (lowest to highest)
|
|
507
|
+
SubscriptionTier.FREE
|
|
508
|
+
SubscriptionTier.BASIC
|
|
509
|
+
SubscriptionTier.PROFESSIONAL
|
|
510
|
+
SubscriptionTier.ENTERPRISE
|
|
511
|
+
|
|
512
|
+
# Subscription statuses
|
|
513
|
+
SubscriptionStatus.ACTIVE
|
|
514
|
+
SubscriptionStatus.SUSPENDED
|
|
515
|
+
SubscriptionStatus.CANCELLED
|
|
516
|
+
SubscriptionStatus.PAST_DUE
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Subscription Utilities
|
|
520
|
+
|
|
521
|
+
```python
|
|
522
|
+
from auth_gate import (
|
|
523
|
+
meets_minimum_tier,
|
|
524
|
+
compare_tiers,
|
|
525
|
+
is_paid_tier,
|
|
526
|
+
get_tier_level,
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
# Check if user tier meets requirement
|
|
530
|
+
meets_minimum_tier(SubscriptionTier.PROFESSIONAL, SubscriptionTier.BASIC) # True
|
|
531
|
+
|
|
532
|
+
# Compare tiers (-1, 0, 1)
|
|
533
|
+
compare_tiers(SubscriptionTier.ENTERPRISE, SubscriptionTier.FREE) # > 0
|
|
534
|
+
|
|
535
|
+
# Check if tier is paid
|
|
536
|
+
is_paid_tier(SubscriptionTier.BASIC) # True
|
|
537
|
+
is_paid_tier(SubscriptionTier.FREE) # False
|
|
538
|
+
```
|
|
539
|
+
|
|
424
540
|
## Migration Guide
|
|
425
541
|
|
|
426
542
|
### Updating Existing Applications
|
|
@@ -488,11 +604,15 @@ from auth_gate import (
|
|
|
488
604
|
AuthContext,
|
|
489
605
|
UserContext,
|
|
490
606
|
ServiceContext,
|
|
607
|
+
SubscriptionTier,
|
|
491
608
|
get_current_auth,
|
|
492
609
|
get_current_user,
|
|
493
610
|
get_current_service,
|
|
494
611
|
require_roles,
|
|
495
612
|
require_user_roles,
|
|
613
|
+
require_tier,
|
|
614
|
+
require_tier_and_active,
|
|
615
|
+
require_professional,
|
|
496
616
|
)
|
|
497
617
|
|
|
498
618
|
app = FastAPI()
|
|
@@ -511,7 +631,11 @@ async def health():
|
|
|
511
631
|
# User-only endpoint
|
|
512
632
|
@app.get("/api/user/profile")
|
|
513
633
|
async def get_profile(user: UserContext = Depends(get_current_user)):
|
|
514
|
-
return {
|
|
634
|
+
return {
|
|
635
|
+
"user": user.user_id,
|
|
636
|
+
"tier": user.subscription_tier.value,
|
|
637
|
+
"organization": user.organization_id
|
|
638
|
+
}
|
|
515
639
|
|
|
516
640
|
# Service-only endpoint
|
|
517
641
|
@app.post("/api/internal/batch")
|
|
@@ -532,6 +656,31 @@ require_user_editor = require_user_roles("editor")
|
|
|
532
656
|
@app.post("/api/articles")
|
|
533
657
|
async def create_article(user: UserContext = Depends(require_user_editor)):
|
|
534
658
|
return {"author": user.user_id}
|
|
659
|
+
|
|
660
|
+
# Tier-protected endpoint (PROFESSIONAL or higher)
|
|
661
|
+
@app.get("/api/analytics")
|
|
662
|
+
async def get_analytics(auth: AuthContext = Depends(require_professional)):
|
|
663
|
+
return {"analytics": "professional data"}
|
|
664
|
+
|
|
665
|
+
# Tier and active subscription required
|
|
666
|
+
@app.get("/api/premium/reports")
|
|
667
|
+
async def get_premium_reports(
|
|
668
|
+
auth: AuthContext = Depends(require_tier_and_active(SubscriptionTier.BASIC))
|
|
669
|
+
):
|
|
670
|
+
return {"reports": "premium data"}
|
|
671
|
+
|
|
672
|
+
# Custom tier check within endpoint
|
|
673
|
+
@app.get("/api/features")
|
|
674
|
+
async def get_features(user: UserContext = Depends(get_current_user)):
|
|
675
|
+
features = ["basic_dashboard"]
|
|
676
|
+
|
|
677
|
+
if user.has_minimum_tier(SubscriptionTier.PROFESSIONAL):
|
|
678
|
+
features.append("advanced_analytics")
|
|
679
|
+
|
|
680
|
+
if user.has_minimum_tier(SubscriptionTier.ENTERPRISE):
|
|
681
|
+
features.append("custom_integrations")
|
|
682
|
+
|
|
683
|
+
return {"features": features, "tier": user.subscription_tier.value}
|
|
535
684
|
```
|
|
536
685
|
|
|
537
686
|
## License
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "auth-gate"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Enterprise-grade authentication for microservices with Kong and
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Enterprise-grade authentication for microservices with Kong/Keycloak integration and subscription tier support"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
license = {text = "MIT"}
|