rack_jwt_aegis 1.0.2 → 1.1.1
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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/.yardopts +6 -12
- data/CHANGELOG.md +217 -5
- data/README.md +42 -48
- data/lib/rack_jwt_aegis/cache_adapter.rb +4 -4
- data/lib/rack_jwt_aegis/circuit_breaker.rb +51 -0
- data/lib/rack_jwt_aegis/configuration.rb +204 -67
- data/lib/rack_jwt_aegis/debug_logger.rb +1 -17
- data/lib/rack_jwt_aegis/jwt_validator.rb +38 -23
- data/lib/rack_jwt_aegis/middleware.rb +44 -8
- data/lib/rack_jwt_aegis/multi_tenant_validator.rb +49 -2
- data/lib/rack_jwt_aegis/rbac_manager.rb +69 -99
- data/lib/rack_jwt_aegis/request_context.rb +19 -25
- data/lib/rack_jwt_aegis/version.rb +1 -1
- data/lib/rack_jwt_aegis.rb +2 -3
- metadata +23 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 355b1a9e875355d5a3c5c7ca9c83dd24af066e1c69e89c9030fab8bb2c5a3f71
|
|
4
|
+
data.tar.gz: e151caf3879396e067aaf2c7f5ba0228fece3de07c5bdf41ac3df3e37d271ab9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9709c05464dade807e95a135d6c5fe7d4e301127c4ea23209086298bc3078882cd1c5454262b1bc1759d1b48b27ac7cd630690e735a210f354bbb20fea808e8
|
|
7
|
+
data.tar.gz: 505983dd1f954880f7ad1a9a6e33beaf3ff83734c5034f90f5515d1ac0d48bac7c6b4e75842aeb7860695bf10efd7aba24c012cafd97b7e1089270d853c0bb23
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.2
|
data/.yardopts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
--
|
|
2
|
-
--readme README.md
|
|
3
|
-
--main README.md
|
|
4
|
-
--markup=markdown
|
|
1
|
+
--title "RackJwtAegis Rack Middleware API Documentation""
|
|
5
2
|
--protected
|
|
6
|
-
--
|
|
7
|
-
--
|
|
8
|
-
|
|
9
|
-
--charset utf-8
|
|
10
|
-
lib/**/*.rb
|
|
3
|
+
--markup markdown
|
|
4
|
+
--readme README.md
|
|
5
|
+
'lib/**/*.rb'
|
|
11
6
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
CODE_OF_CONDUCT.md
|
|
7
|
+
--files CHANGELOG.md LICENSE.txt CODE_OF_CONDUCT.md
|
|
8
|
+
|
data/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,218 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Unreleased
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## [1.1.1] - 2026-06-13
|
|
6
|
+
|
|
7
|
+
### 🚀 Added
|
|
8
|
+
|
|
9
|
+
#### Method-Aware Skip Routes
|
|
10
|
+
|
|
11
|
+
- Added `skip_routes` to support request-aware public route matching by path and HTTP verb.
|
|
12
|
+
- Kept `skip_paths` as a backward-compatible alias for all-method skips.
|
|
13
|
+
- Normalizes route rules once at configuration time so middleware checks stay fast per request.
|
|
14
|
+
|
|
15
|
+
#### Route Skipping Behavior
|
|
16
|
+
|
|
17
|
+
- Middleware now evaluates `METHOD + path` for skip decisions, so `POST /login` can be public while `GET /login` remains protected.
|
|
18
|
+
- Added support for blank verb lists to mean all methods on a route entry.
|
|
19
|
+
- Updated the Pink House API initializer to use the new `skip_routes` shape.
|
|
20
|
+
|
|
21
|
+
### 🔧 Fixed
|
|
22
|
+
|
|
23
|
+
#### Simplified RBAC Cache Strategy
|
|
24
|
+
|
|
25
|
+
- **Streamlined Configuration**: Removed legacy cache configuration options to eliminate confusion
|
|
26
|
+
- **Removed**: `cache_store`, `cache_options`, and `cache_write_enabled` (deprecated in favor of dedicated RBAC cache stores)
|
|
27
|
+
- **Required**: When `rbac_enabled: true`, both `rbac_cache_store` and `permissions_cache_store` must be explicitly configured
|
|
28
|
+
- **Simplified**: Consistent naming with `rbac_cache_store_options` and `permissions_cache_store_options`
|
|
29
|
+
|
|
30
|
+
#### Cache Store Validation
|
|
31
|
+
|
|
32
|
+
- **Mandatory Configuration**: RBAC cache stores are now required when RBAC is enabled
|
|
33
|
+
- Prevents runtime errors from missing cache configuration
|
|
34
|
+
- Provides clear error messages during initialization: "rbac_cache_store and permissions_cache_store are required when RBAC is enabled"
|
|
35
|
+
- Ensures production deployments are properly configured for performance
|
|
36
|
+
|
|
37
|
+
#### Code Cleanup
|
|
38
|
+
|
|
39
|
+
- **Removed Complexity**: Eliminated dual cache mode logic and fallback mechanisms
|
|
40
|
+
- Simplified `RbacManager#setup_cache_adapters` method
|
|
41
|
+
- Removed conditional cache write logic that added complexity
|
|
42
|
+
- Cleaner permission checking flow with consistent cache behavior
|
|
43
|
+
|
|
44
|
+
#### Developer Experience
|
|
45
|
+
|
|
46
|
+
- **Clear Configuration**: Explicit cache store requirements eliminate guesswork
|
|
47
|
+
- **Better Error Messages**: Configuration validation happens at startup, not at runtime
|
|
48
|
+
- **Consistent Naming**: Standardized cache option parameter names across all adapters
|
|
49
|
+
|
|
50
|
+
### 🏗️ Technical Details
|
|
51
|
+
|
|
52
|
+
#### Breaking Changes (Minor - Configuration Only)
|
|
53
|
+
|
|
54
|
+
- **Removed Configuration Options**:
|
|
55
|
+
- `cache_store` → Use `rbac_cache_store` and `permissions_cache_store` instead
|
|
56
|
+
- `cache_options` → Use `rbac_cache_store_options` and `permissions_cache_store_options` instead
|
|
57
|
+
- `cache_write_enabled` → Cache writing is now always enabled when RBAC is active
|
|
58
|
+
|
|
59
|
+
#### Migration Guide
|
|
60
|
+
|
|
61
|
+
**Before (v1.1.0):**
|
|
62
|
+
```ruby
|
|
63
|
+
use RackJwtAegis::Middleware, {
|
|
64
|
+
jwt_secret: ENV['JWT_SECRET'],
|
|
65
|
+
rbac_enabled: true,
|
|
66
|
+
cache_store: :redis,
|
|
67
|
+
cache_options: { url: ENV['REDIS_URL'] },
|
|
68
|
+
cache_write_enabled: true
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**After (v1.1.1):**
|
|
73
|
+
```ruby
|
|
74
|
+
use RackJwtAegis::Middleware, {
|
|
75
|
+
jwt_secret: ENV['JWT_SECRET'],
|
|
76
|
+
rbac_enabled: true,
|
|
77
|
+
rbac_cache_store: :redis,
|
|
78
|
+
rbac_cache_store_options: { url: ENV['REDIS_URL'] },
|
|
79
|
+
permissions_cache_store: :redis,
|
|
80
|
+
permissions_cache_store_options: { url: ENV['REDIS_URL'] }
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### Benefits
|
|
85
|
+
|
|
86
|
+
- **🚀 Simpler Configuration**: No more dual cache modes or conditional logic
|
|
87
|
+
- **🛡️ Fail-Fast Validation**: Configuration errors caught at startup
|
|
88
|
+
- **📈 Better Performance**: Dedicated cache stores optimized for their specific use cases
|
|
89
|
+
- **🧹 Cleaner Code**: Reduced complexity in cache management logic
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## [1.1.0] - 2025-08-14
|
|
94
|
+
|
|
95
|
+
### 🚀 Added
|
|
96
|
+
|
|
97
|
+
#### Enhanced RBAC Cache Format
|
|
98
|
+
|
|
99
|
+
- **Improved Performance**: Changed RBAC cache format from array-based to flat object structure for O(1) role lookup
|
|
100
|
+
- **Before**: `"permissions": [{ "role-id": ["resource:method"] }]` (O(n) array iteration)
|
|
101
|
+
- **After**: `"permissions": { "role-id": ["resource:method"] }` (O(1) direct access)
|
|
102
|
+
- **Developer Experience**: Enhanced error detection with descriptive ConfigurationError exceptions
|
|
103
|
+
- Catches common migration mistakes when upgrading from array to object format
|
|
104
|
+
- Provides clear error messages with expected format examples
|
|
105
|
+
- Helps developers quickly identify and fix RBAC configuration issues
|
|
106
|
+
|
|
107
|
+
#### Documentation Improvements
|
|
108
|
+
|
|
109
|
+
- **Clean Documentation**: Fixed YARD documentation rendering issues with improved Markdown processing
|
|
110
|
+
- Added Redcarpet gem for better Markdown parsing in YARD
|
|
111
|
+
- Removed complex Jekyll integration that was causing rendering issues
|
|
112
|
+
- Maintained YARD for comprehensive API documentation with cleaner output
|
|
113
|
+
- **API Documentation**: Enhanced code comments for better YARD rendering
|
|
114
|
+
- Updated key method documentation with clearer parameter descriptions
|
|
115
|
+
- Improved examples and usage patterns in documentation
|
|
116
|
+
- Better integration with YARD's HTML generation
|
|
117
|
+
|
|
118
|
+
### 🔧 Fixed
|
|
119
|
+
|
|
120
|
+
#### RBAC System Improvements
|
|
121
|
+
|
|
122
|
+
- **Cache Format Validation**: Added explicit validation for RBAC permissions format
|
|
123
|
+
- Raises `ConfigurationError` when permissions is not a Hash
|
|
124
|
+
- Provides helpful error messages for developers
|
|
125
|
+
- Maintains backward compatibility for other validation scenarios
|
|
126
|
+
- **Role Lookup Logic**: Optimized role permission checking with direct hash access
|
|
127
|
+
- Eliminated unnecessary array iteration in permission validation
|
|
128
|
+
- Improved performance for applications with many roles
|
|
129
|
+
- Maintains support for both string and integer role keys
|
|
130
|
+
|
|
131
|
+
#### Bug Fixes
|
|
132
|
+
|
|
133
|
+
- **Test Coverage**: Fixed `test_check_rbac_format?` test that was using old array format
|
|
134
|
+
- **Key Resolution**: Fixed JWT payload key resolution bug in `rbac_last_update_timestamp` method
|
|
135
|
+
- **Validation Logic**: Updated all validation tests to use new flat object format
|
|
136
|
+
|
|
137
|
+
### 🏗️ Technical Details
|
|
138
|
+
|
|
139
|
+
#### Architecture Changes
|
|
140
|
+
|
|
141
|
+
- **RBAC Manager**: Updated `validate_rbac_cache_format` and `check_rbac_format?` methods
|
|
142
|
+
- Direct hash lookup: `permissions_data[role_id]` instead of array iteration
|
|
143
|
+
- Improved error handling with specific ConfigurationError exceptions
|
|
144
|
+
- Enhanced validation with clear developer feedback
|
|
145
|
+
- **Exception Handling**: Re-raises ConfigurationError while preserving other error handling
|
|
146
|
+
- Developer errors bubble up for immediate attention
|
|
147
|
+
- Runtime errors (cache issues) are still handled gracefully
|
|
148
|
+
- Maintains existing debug logging for troubleshooting
|
|
149
|
+
|
|
150
|
+
#### Performance Improvements
|
|
151
|
+
|
|
152
|
+
- **O(1) Role Lookup**: Direct hash access for role permissions
|
|
153
|
+
- **Reduced Memory**: Eliminated nested array structures
|
|
154
|
+
- **Faster Validation**: Simplified permission checking logic
|
|
155
|
+
|
|
156
|
+
#### Developer Experience
|
|
157
|
+
|
|
158
|
+
- **Better Error Messages**: Clear configuration error descriptions
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# Example error message:
|
|
162
|
+
"RBAC permissions must be a Hash with role-id keys, not Array.
|
|
163
|
+
Expected format: {\"role-id\": [\"resource:method\", ...]}, but got: Array"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- **Migration Support**: Catches common mistakes when upgrading cache format
|
|
167
|
+
- **Improved Documentation**: Cleaner YARD output with better Markdown rendering
|
|
168
|
+
|
|
169
|
+
### 📚 Documentation Updates
|
|
170
|
+
|
|
171
|
+
- **README.md**: Updated RBAC cache format examples and specifications
|
|
172
|
+
- **YARD Integration**: Fixed documentation rendering with Redcarpet Markdown processor
|
|
173
|
+
- **Code Examples**: Updated all examples to use new flat object format
|
|
174
|
+
|
|
175
|
+
### 🧪 Testing
|
|
176
|
+
|
|
177
|
+
- **Test Coverage Maintained**: All tests updated to use new cache format
|
|
178
|
+
- **Enhanced Validation**: Added tests for new configuration error scenarios
|
|
179
|
+
- **Comprehensive Coverage**: Validated migration scenarios and edge cases
|
|
180
|
+
|
|
181
|
+
### ⚠️ Migration Guide
|
|
182
|
+
|
|
183
|
+
#### Updating RBAC Cache Format
|
|
184
|
+
|
|
185
|
+
**Before (v1.0.x):**
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
Rails.cache.write("permissions", {
|
|
189
|
+
'last_update' => Time.now.to_i,
|
|
190
|
+
'permissions' => [
|
|
191
|
+
{ '123' => ['sales/invoices:get', 'sales/invoices:post'] },
|
|
192
|
+
{ '456' => ['admin/*:*'] }
|
|
193
|
+
]
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**After (v1.1.0+):**
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
Rails.cache.write("permissions", {
|
|
201
|
+
'last_update' => Time.now.to_i,
|
|
202
|
+
'permissions' => {
|
|
203
|
+
'123' => ['sales/invoices:get', 'sales/invoices:post'],
|
|
204
|
+
'456' => ['admin/*:*']
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Benefits of Migration
|
|
210
|
+
|
|
211
|
+
- **🚀 Better Performance**: O(1) role lookup instead of O(n) iteration
|
|
212
|
+
- **🛠️ Easier Management**: Direct role access for permission updates
|
|
213
|
+
- **📝 Cleaner Code**: Simpler data structure that's easier to understand and maintain
|
|
214
|
+
|
|
215
|
+
---
|
|
7
216
|
|
|
8
217
|
## [1.0.2] - 2025-08-13
|
|
9
218
|
|
|
@@ -174,8 +383,8 @@ use RackJwtAegis::Middleware, {
|
|
|
174
383
|
pathname_slug_pattern: /^\/api\/v1\/([^\/]+)\//,
|
|
175
384
|
rbac_enabled: true,
|
|
176
385
|
rbac_cache_store: :redis,
|
|
177
|
-
|
|
178
|
-
|
|
386
|
+
permissions_cache_store: :memory,
|
|
387
|
+
cached_permissions_ttl: 300,
|
|
179
388
|
cache_write_enabled: true,
|
|
180
389
|
skip_paths: [/^\/health/, /^\/metrics/, /^\/api\/public/],
|
|
181
390
|
custom_payload_validation: ->(payload) { payload['active'] == true },
|
|
@@ -249,5 +458,8 @@ This 1.0.0 release represents a production-ready JWT authentication middleware w
|
|
|
249
458
|
|
|
250
459
|
**Deprecations**: None (initial release).
|
|
251
460
|
|
|
461
|
+
[1.1.1]: https://github.com/kanutocd/rack_jwt_aegis/releases/tag/v1.1.1
|
|
462
|
+
[1.1.0]: https://github.com/kanutocd/rack_jwt_aegis/releases/tag/v1.1.0
|
|
463
|
+
[1.0.2]: https://github.com/kanutocd/rack_jwt_aegis/releases/tag/v1.0.2
|
|
252
464
|
[1.0.1]: https://github.com/kanutocd/rack_jwt_aegis/releases/tag/v1.0.1
|
|
253
465
|
[1.0.0]: https://github.com/kanutocd/rack_jwt_aegis/releases/tag/v1.0.0
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://badge.fury.io/rb/rack_jwt_aegis)
|
|
4
4
|
[](https://github.com/kanutocd/rack_jwt_aegis/actions)
|
|
5
5
|
[](https://codecov.io/gh/kanutocd/rack_jwt_aegis)
|
|
6
|
-
[](https://www.ruby-lang.org/en/)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
9
|
JWT authentication and authorization middleware for hierarchical multi-tenant Rack applications with 2-level tenant support.
|
|
@@ -147,28 +147,24 @@ RackJwtAegis::Middleware.new(app, {
|
|
|
147
147
|
pathname_slug_pattern: /^\/api\/v1\/([^\/]+)\//, # Default pattern
|
|
148
148
|
|
|
149
149
|
# RBAC Configuration
|
|
150
|
-
rbac_enabled: true,
|
|
151
|
-
rbac_cache_store: :redis,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
#
|
|
161
|
-
rbac_cache_store: :redis, # For RBAC permissions data
|
|
162
|
-
rbac_cache_options: { url: ENV['REDIS_URL'] },
|
|
163
|
-
permission_cache_store: :memory, # For cached user permissions
|
|
164
|
-
permission_cache_options: {},
|
|
150
|
+
rbac_enabled: true, # Default: false
|
|
151
|
+
rbac_cache_store: :redis, # Required when RBAC enabled. Default: :memory
|
|
152
|
+
rbac_cache_store_options: { url: ENV['REDIS_URL'] }, # Cache Store specific options. Default: {}
|
|
153
|
+
|
|
154
|
+
cached_permissions_ttl: 3600, # Default: 1800 (30 minutes) - TTL for cached user permissions
|
|
155
|
+
permissions_cache_store: :solid_cache, # Required when RBAC enabled. Default: :memory
|
|
156
|
+
permissions_cache_store_options: {}, # Cache Store specific options. Default: {}
|
|
157
|
+
|
|
158
|
+
# Or can also be the same Redis instance
|
|
159
|
+
# permissions_cache_store: :redis, # Required when RBAC enabled. Default: :memory
|
|
160
|
+
# permissions_cache_store_options: { url: ENV['REDIS_URL'] },
|
|
165
161
|
|
|
166
162
|
# Response Customization
|
|
167
163
|
unauthorized_response: { error: 'Authentication required' },
|
|
168
164
|
forbidden_response: { error: 'Access denied' },
|
|
169
165
|
|
|
170
166
|
# Debugging
|
|
171
|
-
debug_mode: Rails.env.development? # Default: false
|
|
167
|
+
debug_mode: Rails.env.development? # Default: when in Rails, Rails.env.development? otherwise false
|
|
172
168
|
})
|
|
173
169
|
```
|
|
174
170
|
|
|
@@ -273,7 +269,7 @@ accessible_companies = RackJwtAegis::RequestContext.pathname_slugs(request.env)
|
|
|
273
269
|
# => ["company-a", "company-b"]
|
|
274
270
|
|
|
275
271
|
# Check if user has access to specific company
|
|
276
|
-
has_access = RackJwtAegis::RequestContext.
|
|
272
|
+
has_access = RackJwtAegis::RequestContext.has_pathname_slug_access?(request.env, "company-a")
|
|
277
273
|
# => true
|
|
278
274
|
|
|
279
275
|
# Helper methods for request objects
|
|
@@ -291,7 +287,7 @@ tenant_id = RackJwtAegis::RequestContext.current_tenant_id(request)
|
|
|
291
287
|
- `pathname_slugs(env)` - Get array of accessible company slugs
|
|
292
288
|
- `current_user_id(request)` - Helper for request objects
|
|
293
289
|
- `current_tenant_id(request)` - Helper for request objects
|
|
294
|
-
- `
|
|
290
|
+
- `has_pathname_slug_access?(env, slug)` - Check company access
|
|
295
291
|
|
|
296
292
|
## JWT Payload Structure
|
|
297
293
|
|
|
@@ -404,18 +400,19 @@ All role values are normalized to strings internally for consistent matching aga
|
|
|
404
400
|
|
|
405
401
|
```ruby
|
|
406
402
|
# Memory cache (development/testing)
|
|
407
|
-
config.
|
|
403
|
+
config.rbac_cache_store = :memory # Default. When in Rails, it will default to Rails.application.config.cache_store
|
|
404
|
+
config.permissions_cache_store = :memory # Default. When in Rails, it will default to Rails.application.config.cache_store
|
|
408
405
|
|
|
409
406
|
# Redis cache
|
|
410
|
-
config.
|
|
411
|
-
config.
|
|
407
|
+
config.rbac_cache_store = :redis
|
|
408
|
+
config.rbac_cache_store_options = { url: ENV['REDIS_URL'] }
|
|
412
409
|
|
|
413
410
|
# Memcached cache
|
|
414
|
-
config.
|
|
415
|
-
config.
|
|
411
|
+
config.rbac_cache_store = :memcached
|
|
412
|
+
config.rbac_cache_store_options = { servers: ['localhost:11211'] }
|
|
416
413
|
|
|
417
414
|
# Solid Cache (Rails 8+)
|
|
418
|
-
config.
|
|
415
|
+
config.rbac_cache_store = :solid_cache
|
|
419
416
|
```
|
|
420
417
|
|
|
421
418
|
#### Separate Cache Stores
|
|
@@ -425,11 +422,11 @@ You can configure separate cache stores for RBAC permissions data and cached use
|
|
|
425
422
|
```ruby
|
|
426
423
|
# Use Redis for RBAC data (shared across instances)
|
|
427
424
|
config.rbac_cache_store = :redis
|
|
428
|
-
config.
|
|
425
|
+
config.rbac_cache_store_options = { url: ENV['REDIS_URL'] }
|
|
429
426
|
|
|
430
427
|
# Use memory for user permission cache (faster local access)
|
|
431
|
-
config.
|
|
432
|
-
config.
|
|
428
|
+
config.permissions_cache_store = :memory
|
|
429
|
+
config.permissions_cache_store_options = {}
|
|
433
430
|
```
|
|
434
431
|
|
|
435
432
|
## RBAC Cache Format
|
|
@@ -439,31 +436,28 @@ When RBAC is enabled, the middleware expects permissions to be stored in the cac
|
|
|
439
436
|
```json
|
|
440
437
|
{
|
|
441
438
|
"last_update": 1640995200,
|
|
442
|
-
"permissions":
|
|
443
|
-
|
|
444
|
-
"
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
{
|
|
453
|
-
"456": ["admin/*:*", "reports:get"]
|
|
454
|
-
}
|
|
455
|
-
]
|
|
439
|
+
"permissions": {
|
|
440
|
+
"123": [
|
|
441
|
+
"sales/invoices:get",
|
|
442
|
+
"sales/invoices:post",
|
|
443
|
+
"%r{sales/invoices/\\d+}:get",
|
|
444
|
+
"%r{sales/invoices/\\d+}:put",
|
|
445
|
+
"users/*:get"
|
|
446
|
+
],
|
|
447
|
+
"456": ["admin/*:*", "reports:get"]
|
|
448
|
+
}
|
|
456
449
|
}
|
|
457
450
|
```
|
|
458
451
|
|
|
459
452
|
### Format Specification
|
|
460
453
|
|
|
461
454
|
- **last_update**: Timestamp for cache invalidation
|
|
462
|
-
- **permissions**:
|
|
463
|
-
- **Role ID
|
|
464
|
-
- **
|
|
465
|
-
- **resource-endpoint
|
|
466
|
-
|
|
455
|
+
- **permissions**: Object containing direct role-to-permissions mapping:
|
|
456
|
+
- **Role ID** (key): String or numeric identifier
|
|
457
|
+
- **Permissions** (value): Array of permission strings
|
|
458
|
+
- **Permission Format**: `"resource-endpoint:http-method"`
|
|
459
|
+
- **resource-endpoint**: API path (literal string or regex pattern)
|
|
460
|
+
- **http-method**: `get`, `post`, `put`, `delete`, or `*` (wildcard)
|
|
467
461
|
|
|
468
462
|
### Permission Examples
|
|
469
463
|
|
|
@@ -509,7 +503,7 @@ When RBAC is enabled, the middleware expects permissions to be stored in the cac
|
|
|
509
503
|
"12345:acme-group.localhost.local/api/v1/company/sales/invoices:post": 1640995200
|
|
510
504
|
}
|
|
511
505
|
```
|
|
512
|
-
TTL configurable via `
|
|
506
|
+
TTL configurable via `cached_permissions_ttl` option (default: 30 minutes)
|
|
513
507
|
|
|
514
508
|
### Cache Invalidation Strategy
|
|
515
509
|
|
|
@@ -4,13 +4,13 @@ module RackJwtAegis
|
|
|
4
4
|
class CacheAdapter
|
|
5
5
|
def self.build(store_type, options = {})
|
|
6
6
|
case store_type
|
|
7
|
-
when :memory
|
|
7
|
+
when :memory, :memory_store
|
|
8
8
|
MemoryAdapter.new(options)
|
|
9
|
-
when :redis
|
|
9
|
+
when :redis, :redis_cache_store
|
|
10
10
|
RedisAdapter.new(options)
|
|
11
|
-
when :memcached
|
|
11
|
+
when :memcached, :mem_cache_store
|
|
12
12
|
MemcachedAdapter.new(options)
|
|
13
|
-
when :solid_cache
|
|
13
|
+
when :solid_cache, :solid_cache_store
|
|
14
14
|
SolidCacheAdapter.new(options)
|
|
15
15
|
else
|
|
16
16
|
raise ConfigurationError, "Unsupported cache store: #{store_type}"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ratomic'
|
|
4
|
+
|
|
5
|
+
module RackJwtAegis
|
|
6
|
+
class CircuitBreaker
|
|
7
|
+
STATE_FAILURE_COUNT = 'failure_count'
|
|
8
|
+
STATE_OPENED_AT = 'opened_at'
|
|
9
|
+
|
|
10
|
+
def initialize(failure_threshold:, cooldown_seconds:)
|
|
11
|
+
@failure_threshold = failure_threshold.to_i
|
|
12
|
+
@cooldown_seconds = cooldown_seconds.to_i
|
|
13
|
+
@state = Ratomic::Map.new
|
|
14
|
+
reset
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def allow_request?
|
|
18
|
+
return true unless open?
|
|
19
|
+
return false unless cooldown_elapsed?
|
|
20
|
+
|
|
21
|
+
reset
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def record_success
|
|
26
|
+
reset
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def record_failure
|
|
30
|
+
failures = @state.increment(STATE_FAILURE_COUNT)
|
|
31
|
+
@state[STATE_OPENED_AT] = Time.now.to_f if failures >= @failure_threshold
|
|
32
|
+
failures
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def open?
|
|
36
|
+
!@state[STATE_OPENED_AT].nil?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def reset
|
|
42
|
+
@state[STATE_FAILURE_COUNT] = 0
|
|
43
|
+
@state.delete(STATE_OPENED_AT)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def cooldown_elapsed?
|
|
47
|
+
opened_at = @state[STATE_OPENED_AT]
|
|
48
|
+
opened_at && (Time.now.to_f - opened_at) >= @cooldown_seconds
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|