auth-gate 0.2.2__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.
Files changed (30) hide show
  1. {auth_gate-0.2.2/src/auth_gate.egg-info → auth_gate-0.3.0}/PKG-INFO +205 -18
  2. {auth_gate-0.2.2 → auth_gate-0.3.0}/README.md +203 -16
  3. {auth_gate-0.2.2 → auth_gate-0.3.0}/pyproject.toml +2 -2
  4. {auth_gate-0.2.2 → auth_gate-0.3.0}/setup.cfg +1 -1
  5. auth_gate-0.3.0/src/auth_gate/__init__.py +148 -0
  6. auth_gate-0.3.0/src/auth_gate/exceptions.py +58 -0
  7. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate/fastapi_utils.py +264 -2
  8. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate/middleware.py +198 -6
  9. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate/schemas.py +56 -1
  10. auth_gate-0.3.0/src/auth_gate/subscription.py +126 -0
  11. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate/user_auth.py +48 -1
  12. {auth_gate-0.2.2 → auth_gate-0.3.0/src/auth_gate.egg-info}/PKG-INFO +205 -18
  13. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate.egg-info/SOURCES.txt +3 -0
  14. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/tests/conftest.py +111 -0
  15. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/tests/test_intergration.py +2 -2
  16. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/tests/test_middleware.py +344 -1
  17. auth_gate-0.3.0/src/tests/test_subscription.py +237 -0
  18. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/tests/test_user_auth.py +1 -1
  19. auth_gate-0.2.2/src/auth_gate/__init__.py +0 -67
  20. {auth_gate-0.2.2 → auth_gate-0.3.0}/LICENSE +0 -0
  21. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate/config.py +0 -0
  22. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate/s2s_auth.py +0 -0
  23. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate.egg-info/dependency_links.txt +0 -0
  24. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate.egg-info/requires.txt +0 -0
  25. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/auth_gate.egg-info/top_level.txt +0 -0
  26. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/tests/__init__.py +0 -0
  27. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/tests/test_config.py +0 -0
  28. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/tests/test_fastapi_utils.py +0 -0
  29. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/tests/test_s2s_auth.py +0 -0
  30. {auth_gate-0.2.2 → auth_gate-0.3.0}/src/tests/test_schema.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: auth-gate
3
- Version: 0.2.2
4
- Summary: Enterprise-grade authentication for microservices with Kong and Keycloak integration
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 both user and service-to-service authentication.
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
@@ -290,6 +340,44 @@ app.add_middleware(
290
340
  )
291
341
  ```
292
342
 
343
+ ### Parameterized Paths with UUID Matching
344
+
345
+ You can exclude or make paths optional using UUID v4 parameters:
346
+
347
+ ```python
348
+ app.add_middleware(
349
+ AuthMiddleware,
350
+ excluded_paths={
351
+ "/api/v1/categories/{category_id:uuid}": {"GET"}, # Public read
352
+ "/api/v1/products/{product_id:uuid}": {"GET"},
353
+ },
354
+ excluded_prefixes={
355
+ "/api/{version:uuid}": {"GET"}, # Version-specific docs
356
+ },
357
+ optional_auth_paths={
358
+ "/api/v1/recommendations/{user_id:uuid}": {"GET"}, # Personalized if authenticated
359
+ }
360
+ )
361
+ ```
362
+
363
+ **Pattern Syntax:**
364
+ - `{param:uuid}` - Matches valid UUID v4 format (case-insensitive)
365
+ - Works with exact paths, prefixes, and optional auth paths
366
+ - Supports method-specific exclusions
367
+ - Exact matches take precedence over patterns
368
+
369
+ **Example Behavior:**
370
+ ```python
371
+ # Matches: /api/v1/categories/7b5bcc8f-2c99-43c0-9c7d-e27c10881bd2
372
+ # Does not match: /api/v1/categories/invalid-id
373
+ # Does not match: /api/v1/categories/all
374
+ ```
375
+
376
+ **UUID v4 Validation:**
377
+ - Must have version digit "4" in the correct position
378
+ - Must have variant bits (8, 9, a, or b) in the correct position
379
+ - Accepts uppercase, lowercase, or mixed case
380
+
293
381
  ### Direct Validator Usage
294
382
 
295
383
  ```python
@@ -341,14 +429,17 @@ The S2S auth client includes automatic circuit breaker protection:
341
429
 
342
430
  ```python
343
431
  class UserContext:
344
- user_id: str # Unique user identifier
345
- username: str | None # Username
346
- email: str | None # Email address
347
- roles: List[str] # User roles
348
- scopes: List[str] # OAuth scopes
349
- session_id: str | None # Session identifier
350
- client_id: str | None # OAuth client ID
351
- auth_source: str # Authentication source
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)
352
443
 
353
444
  # Properties
354
445
  is_service: bool # Always False for users
@@ -356,6 +447,9 @@ class UserContext:
356
447
  is_supplier: bool # True if has supplier role
357
448
  is_customer: bool # True if has customer role
358
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
359
453
 
360
454
  # Methods
361
455
  has_role(role: str) -> bool
@@ -363,18 +457,23 @@ class UserContext:
363
457
  has_all_roles(roles: List[str]) -> bool
364
458
  has_scope(scope: str) -> bool
365
459
  has_any_scope(scopes: List[str]) -> bool
460
+ has_minimum_tier(required_tier: SubscriptionTier) -> bool
461
+ can_access_feature(required_tier: SubscriptionTier) -> bool
366
462
  ```
367
463
 
368
464
  ### ServiceContext
369
465
 
370
466
  ```python
371
467
  class ServiceContext:
372
- service_name: str # Service identifier (client_id)
373
- service_id: str | None # Service sub claim
374
- roles: List[str] # Service roles
375
- session_id: str | None # Session identifier
376
- client_id: str | None # OAuth client ID
377
- auth_source: str # Authentication source
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
378
477
 
379
478
  # Properties
380
479
  is_service: bool # Always True for services
@@ -386,6 +485,8 @@ class ServiceContext:
386
485
  has_all_roles(roles: List[str]) -> bool
387
486
  ```
388
487
 
488
+ **Note:** Services bypass subscription tier checks by default when using `require_tier()` and related dependencies.
489
+
389
490
  ## Available Dependencies
390
491
 
391
492
  ### Authentication Dependencies
@@ -424,6 +525,59 @@ require_service_roles("role1", "role2", ...)
424
525
  require_scopes("scope1", "scope2", ...)
425
526
  ```
426
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
+
427
581
  ## Migration Guide
428
582
 
429
583
  ### Updating Existing Applications
@@ -491,11 +645,15 @@ from auth_gate import (
491
645
  AuthContext,
492
646
  UserContext,
493
647
  ServiceContext,
648
+ SubscriptionTier,
494
649
  get_current_auth,
495
650
  get_current_user,
496
651
  get_current_service,
497
652
  require_roles,
498
653
  require_user_roles,
654
+ require_tier,
655
+ require_tier_and_active,
656
+ require_professional,
499
657
  )
500
658
 
501
659
  app = FastAPI()
@@ -514,7 +672,11 @@ async def health():
514
672
  # User-only endpoint
515
673
  @app.get("/api/user/profile")
516
674
  async def get_profile(user: UserContext = Depends(get_current_user)):
517
- return {"user": user.user_id}
675
+ return {
676
+ "user": user.user_id,
677
+ "tier": user.subscription_tier.value,
678
+ "organization": user.organization_id
679
+ }
518
680
 
519
681
  # Service-only endpoint
520
682
  @app.post("/api/internal/batch")
@@ -535,6 +697,31 @@ require_user_editor = require_user_roles("editor")
535
697
  @app.post("/api/articles")
536
698
  async def create_article(user: UserContext = Depends(require_user_editor)):
537
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}
538
725
  ```
539
726
 
540
727
  ## License
@@ -1,11 +1,12 @@
1
1
  # Auth Gate
2
2
 
3
- Enterprise-grade authentication for microservices with Kong and Keycloak integration, supporting both user and service-to-service authentication.
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
@@ -249,6 +299,44 @@ app.add_middleware(
249
299
  )
250
300
  ```
251
301
 
302
+ ### Parameterized Paths with UUID Matching
303
+
304
+ You can exclude or make paths optional using UUID v4 parameters:
305
+
306
+ ```python
307
+ app.add_middleware(
308
+ AuthMiddleware,
309
+ excluded_paths={
310
+ "/api/v1/categories/{category_id:uuid}": {"GET"}, # Public read
311
+ "/api/v1/products/{product_id:uuid}": {"GET"},
312
+ },
313
+ excluded_prefixes={
314
+ "/api/{version:uuid}": {"GET"}, # Version-specific docs
315
+ },
316
+ optional_auth_paths={
317
+ "/api/v1/recommendations/{user_id:uuid}": {"GET"}, # Personalized if authenticated
318
+ }
319
+ )
320
+ ```
321
+
322
+ **Pattern Syntax:**
323
+ - `{param:uuid}` - Matches valid UUID v4 format (case-insensitive)
324
+ - Works with exact paths, prefixes, and optional auth paths
325
+ - Supports method-specific exclusions
326
+ - Exact matches take precedence over patterns
327
+
328
+ **Example Behavior:**
329
+ ```python
330
+ # Matches: /api/v1/categories/7b5bcc8f-2c99-43c0-9c7d-e27c10881bd2
331
+ # Does not match: /api/v1/categories/invalid-id
332
+ # Does not match: /api/v1/categories/all
333
+ ```
334
+
335
+ **UUID v4 Validation:**
336
+ - Must have version digit "4" in the correct position
337
+ - Must have variant bits (8, 9, a, or b) in the correct position
338
+ - Accepts uppercase, lowercase, or mixed case
339
+
252
340
  ### Direct Validator Usage
253
341
 
254
342
  ```python
@@ -300,14 +388,17 @@ The S2S auth client includes automatic circuit breaker protection:
300
388
 
301
389
  ```python
302
390
  class UserContext:
303
- user_id: str # Unique user identifier
304
- username: str | None # Username
305
- email: str | None # Email address
306
- roles: List[str] # User roles
307
- scopes: List[str] # OAuth scopes
308
- session_id: str | None # Session identifier
309
- client_id: str | None # OAuth client ID
310
- auth_source: str # Authentication source
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)
311
402
 
312
403
  # Properties
313
404
  is_service: bool # Always False for users
@@ -315,6 +406,9 @@ class UserContext:
315
406
  is_supplier: bool # True if has supplier role
316
407
  is_customer: bool # True if has customer role
317
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
318
412
 
319
413
  # Methods
320
414
  has_role(role: str) -> bool
@@ -322,18 +416,23 @@ class UserContext:
322
416
  has_all_roles(roles: List[str]) -> bool
323
417
  has_scope(scope: str) -> bool
324
418
  has_any_scope(scopes: List[str]) -> bool
419
+ has_minimum_tier(required_tier: SubscriptionTier) -> bool
420
+ can_access_feature(required_tier: SubscriptionTier) -> bool
325
421
  ```
326
422
 
327
423
  ### ServiceContext
328
424
 
329
425
  ```python
330
426
  class ServiceContext:
331
- service_name: str # Service identifier (client_id)
332
- service_id: str | None # Service sub claim
333
- roles: List[str] # Service roles
334
- session_id: str | None # Session identifier
335
- client_id: str | None # OAuth client ID
336
- auth_source: str # Authentication source
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
337
436
 
338
437
  # Properties
339
438
  is_service: bool # Always True for services
@@ -345,6 +444,8 @@ class ServiceContext:
345
444
  has_all_roles(roles: List[str]) -> bool
346
445
  ```
347
446
 
447
+ **Note:** Services bypass subscription tier checks by default when using `require_tier()` and related dependencies.
448
+
348
449
  ## Available Dependencies
349
450
 
350
451
  ### Authentication Dependencies
@@ -383,6 +484,59 @@ require_service_roles("role1", "role2", ...)
383
484
  require_scopes("scope1", "scope2", ...)
384
485
  ```
385
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
+
386
540
  ## Migration Guide
387
541
 
388
542
  ### Updating Existing Applications
@@ -450,11 +604,15 @@ from auth_gate import (
450
604
  AuthContext,
451
605
  UserContext,
452
606
  ServiceContext,
607
+ SubscriptionTier,
453
608
  get_current_auth,
454
609
  get_current_user,
455
610
  get_current_service,
456
611
  require_roles,
457
612
  require_user_roles,
613
+ require_tier,
614
+ require_tier_and_active,
615
+ require_professional,
458
616
  )
459
617
 
460
618
  app = FastAPI()
@@ -473,7 +631,11 @@ async def health():
473
631
  # User-only endpoint
474
632
  @app.get("/api/user/profile")
475
633
  async def get_profile(user: UserContext = Depends(get_current_user)):
476
- return {"user": user.user_id}
634
+ return {
635
+ "user": user.user_id,
636
+ "tier": user.subscription_tier.value,
637
+ "organization": user.organization_id
638
+ }
477
639
 
478
640
  # Service-only endpoint
479
641
  @app.post("/api/internal/batch")
@@ -494,6 +656,31 @@ require_user_editor = require_user_roles("editor")
494
656
  @app.post("/api/articles")
495
657
  async def create_article(user: UserContext = Depends(require_user_editor)):
496
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}
497
684
  ```
498
685
 
499
686
  ## License
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "auth-gate"
7
- version = "0.2.2"
8
- description = "Enterprise-grade authentication for microservices with Kong and Keycloak integration"
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"}
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = auth-gate
3
- version = 0.2.2
3
+ version = 0.2.3
4
4
  author = Brian Mburu
5
5
  author_email = brian.mburu@students.jkuat.ac.ke
6
6
  description = Enterprise-grade authentication for microservices with Kong and Keycloak integration