rack_jwt_aegis 1.0.0 → 1.0.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/CHANGELOG.md +39 -0
- data/README.md +73 -12
- data/lib/rack_jwt_aegis/configuration.rb +20 -0
- data/lib/rack_jwt_aegis/debug_logger.rb +51 -0
- data/lib/rack_jwt_aegis/middleware.rb +11 -14
- data/lib/rack_jwt_aegis/rbac_manager.rb +20 -27
- data/lib/rack_jwt_aegis/version.rb +1 -1
- data/lib/rack_jwt_aegis.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bc9e89c08947641810cb3c0a5aef91c56554dcf44e593abc10cb7b75ebe6478
|
4
|
+
data.tar.gz: bcbd0711caf2bd74d65ede714cf4cf24eb016146fc69e20bfdf76a165d8a009c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d279d25565397a64e45be0b157a01e63ca7c8e688fd1bb3592649cef8a2037380f5c9c1ddad645ad46f2326d5cd8cc279661207371825d26fe0c6cc036dd272
|
7
|
+
data.tar.gz: fe7e42dc37a5dca3a4fe443fc14e0a64178994b20406471ab6139be4e0b8917a663bcc45d29a819749875a9ef4cecd7d85cd8e13ef9cf5b6054ace0be090d540
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [1.0.1] - 2025-08-13
|
9
|
+
|
10
|
+
### 🔧 Fixed
|
11
|
+
|
12
|
+
#### Code Quality & Maintenance
|
13
|
+
|
14
|
+
- **DRY Refactoring**: Eliminated duplicate `debug_log` method implementations by creating a shared `DebugLogger` module
|
15
|
+
- Created `lib/rack_jwt_aegis/debug_logger.rb` with consistent debug logging functionality
|
16
|
+
- Updated `Middleware` and `RbacManager` classes to include the shared module
|
17
|
+
- Improved code maintainability by centralizing debug logging logic
|
18
|
+
- Maintains all existing functionality and logging behavior
|
19
|
+
- **RBAC Cache Validation**: Enhanced wildcard permission validation in `validate_rbac_cache_format` to support `admin/*` patterns
|
20
|
+
- **JWT Payload Resolution**: Fixed JWT payload key resolution to handle string keys consistently across components
|
21
|
+
- **Test Coverage**: Maintained high test coverage (98.17% line coverage) after refactoring
|
22
|
+
|
23
|
+
#### Developer Experience
|
24
|
+
|
25
|
+
- **Consistent Logging**: Unified debug log format across all components with automatic timestamp formatting
|
26
|
+
- **Component Identification**: Automatic component name inference for better log traceability
|
27
|
+
- **Configurable Log Levels**: Support for info, warn, and error log levels with appropriate output streams
|
28
|
+
|
29
|
+
### 🏗️ Technical Details
|
30
|
+
|
31
|
+
#### Architecture Improvements
|
32
|
+
|
33
|
+
- **Shared Module Pattern**: Introduced consistent module inclusion pattern for cross-cutting concerns
|
34
|
+
- **Code Organization**: Better separation of concerns with dedicated debug logging module
|
35
|
+
- **Maintainability**: Reduced code duplication from ~40 lines to a single shared implementation
|
36
|
+
|
37
|
+
#### Testing & Quality
|
38
|
+
|
39
|
+
- **Test Suite**: All 340 tests pass with 975 assertions
|
40
|
+
- **Coverage Maintained**: 98.17% line coverage, 92.83% branch coverage
|
41
|
+
- **RBAC Integration**: Verified all role-based authorization tests pass after refactoring
|
42
|
+
- **Zero Regression**: No functional changes, only structural improvements
|
43
|
+
|
44
|
+
---
|
45
|
+
|
8
46
|
## [1.0.0] - 2025-08-13
|
9
47
|
|
10
48
|
### 🎉 Initial Release
|
@@ -201,4 +239,5 @@ This 1.0.0 release represents a production-ready JWT authentication middleware w
|
|
201
239
|
|
202
240
|
**Deprecations**: None (initial release).
|
203
241
|
|
242
|
+
[1.0.1]: https://github.com/kanutocd/rack_jwt_aegis/releases/tag/v1.0.1
|
204
243
|
[1.0.0]: https://github.com/kanutocd/rack_jwt_aegis/releases/tag/v1.0.0
|
data/README.md
CHANGED
@@ -14,7 +14,9 @@ JWT authentication middleware for hierarchical multi-tenant Rack applications wi
|
|
14
14
|
- 2-level multi-tenant support (Example: Company-Group → Company, Organization → Department, etc.)
|
15
15
|
- Subdomain-based tenant isolation for top-level tenants
|
16
16
|
- URL pathname slug access control for sub-level tenants
|
17
|
+
- **RBAC (Role-Based Access Control)** with flexible role extraction from JWT payloads
|
17
18
|
- Configurable path exclusions for public endpoints
|
19
|
+
- **Flexible payload mapping** for custom JWT claim names
|
18
20
|
- Custom payload validation
|
19
21
|
- Debug mode for development
|
20
22
|
|
@@ -175,14 +177,8 @@ RackJwtAegis::Middleware.new(app, {
|
|
175
177
|
user_id: :sub, # Map 'sub' claim to user_id
|
176
178
|
tenant_id: :company_group_id, # Map 'company_group_id' claim
|
177
179
|
subdomain: :company_group_domain_name, # Map 'company_group_domain_name' claim
|
178
|
-
pathname_slugs: :accessible_company_slugs
|
179
|
-
|
180
|
-
|
181
|
-
# Custom Tenant Extraction
|
182
|
-
tenant_strategy: :custom,
|
183
|
-
tenant_extractor: ->(request) {
|
184
|
-
# Extract tenant from custom header or logic
|
185
|
-
request.get_header('HTTP_X_TENANT_ID')
|
180
|
+
pathname_slugs: :accessible_company_slugs, # Map array of accessible companies
|
181
|
+
role_ids: :user_roles # Map 'user_roles' claim for RBAC authorization
|
186
182
|
}
|
187
183
|
})
|
188
184
|
```
|
@@ -285,7 +281,8 @@ The middleware expects JWT payloads with the following structure:
|
|
285
281
|
"tenant_id": 67890,
|
286
282
|
"subdomain": "acme-group-of-companies", # the subdomain part of the host of the request url, e.g. `http://acme-group-of-companies.example.com`
|
287
283
|
"pathname_slugs": ["an-acme-company-subsidiary", "another-acme-company-the-user-has-access"], # the user has access to these kind of request urls: https://acme-group-of-companies.example.com/api/v1/an-acme-company-subsidiary/* or https://acme-group-of-companies.example.com/api/v1/another-acme-company-the-user-has-access/
|
288
|
-
"
|
284
|
+
"role_ids": ["123", "456"], # Role IDs for RBAC authorization (can also be integers)
|
285
|
+
"roles": ["admin", "user"], # Legacy role names (kept for backward compatibility)
|
289
286
|
"exp": 1640995200,
|
290
287
|
"iat": 1640991600
|
291
288
|
}
|
@@ -293,6 +290,69 @@ The middleware expects JWT payloads with the following structure:
|
|
293
290
|
|
294
291
|
You can customize the payload mapping using the `payload_mapping` configuration option.
|
295
292
|
|
293
|
+
### RBAC Role Extraction
|
294
|
+
|
295
|
+
When RBAC is enabled, the middleware extracts user roles from the JWT payload for authorization. The default payload mapping includes:
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
payload_mapping: {
|
299
|
+
user_id: :user_id,
|
300
|
+
tenant_id: :tenant_id,
|
301
|
+
subdomain: :subdomain,
|
302
|
+
pathname_slugs: :pathname_slugs,
|
303
|
+
role_ids: :role_ids # Default field for user roles
|
304
|
+
}
|
305
|
+
```
|
306
|
+
|
307
|
+
#### Role Field Resolution
|
308
|
+
|
309
|
+
The middleware looks for roles in the following priority order:
|
310
|
+
|
311
|
+
1. **Configured Field**: Uses the `role_ids` mapping (e.g., if mapped to `:user_roles`, looks for `user_roles` field)
|
312
|
+
2. **Fallback Fields**: If the mapped field is not found, tries these common alternatives:
|
313
|
+
- `roles` - Array of role identifiers
|
314
|
+
- `role` - Single role identifier
|
315
|
+
- `user_roles` - Array of user role identifiers
|
316
|
+
- `role_ids` - Array of role IDs (numeric or string)
|
317
|
+
|
318
|
+
#### Custom Role Field Mapping
|
319
|
+
|
320
|
+
You can customize the role field using payload mapping:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
# Use a custom field name for roles
|
324
|
+
payload_mapping: {
|
325
|
+
role_ids: :user_permissions # Look for roles in 'user_permissions' field
|
326
|
+
}
|
327
|
+
|
328
|
+
# JWT payload would contain:
|
329
|
+
{
|
330
|
+
"user_id": 123,
|
331
|
+
"user_permissions": ["admin", "manager"],
|
332
|
+
...
|
333
|
+
}
|
334
|
+
```
|
335
|
+
|
336
|
+
#### Role Format Support
|
337
|
+
|
338
|
+
The middleware supports flexible role formats:
|
339
|
+
|
340
|
+
```ruby
|
341
|
+
# Array of strings (recommended)
|
342
|
+
"role_ids": ["123", "456", "admin"]
|
343
|
+
|
344
|
+
# Array of integers
|
345
|
+
"role_ids": [123, 456]
|
346
|
+
|
347
|
+
# Single string
|
348
|
+
"role_ids": "admin"
|
349
|
+
|
350
|
+
# Single integer
|
351
|
+
"role_ids": 123
|
352
|
+
```
|
353
|
+
|
354
|
+
All role values are normalized to strings internally for consistent matching against RBAC cache permissions.
|
355
|
+
|
296
356
|
## Security Features
|
297
357
|
|
298
358
|
- JWT signature verification
|
@@ -410,7 +470,8 @@ When RBAC is enabled, the middleware expects permissions to be stored in the cac
|
|
410
470
|
|
411
471
|
2. **RBAC Permissions Validation**: Full permission evaluation
|
412
472
|
|
413
|
-
- Extract user roles from JWT payload
|
473
|
+
- Extract user roles from JWT payload using configurable field mapping (default: `role_ids`)
|
474
|
+
- Fallback to common fields: `roles`, `role`, `user_roles`, or `role_ids` if mapped field not found
|
414
475
|
- Load RBAC permissions collection and validate format
|
415
476
|
- For each user role, check if any permission matches:
|
416
477
|
- Extract resource path from request URL (removes subdomain/pathname slug)
|
@@ -491,8 +552,8 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
491
552
|
|
492
553
|
This project maintains high test coverage:
|
493
554
|
|
494
|
-
- **Line Coverage**: 97.
|
495
|
-
- **Branch Coverage**:
|
555
|
+
- **Line Coverage**: 97.81% (670/685 lines)
|
556
|
+
- **Branch Coverage**: 87.13% (264/303 branches)
|
496
557
|
|
497
558
|
Run tests with coverage: `bundle exec rake test`
|
498
559
|
|
@@ -273,6 +273,7 @@ module RackJwtAegis
|
|
273
273
|
tenant_id: :tenant_id,
|
274
274
|
subdomain: :subdomain,
|
275
275
|
pathname_slugs: :pathname_slugs,
|
276
|
+
role_ids: :role_ids,
|
276
277
|
}
|
277
278
|
@unauthorized_response = { error: 'Authentication required' }
|
278
279
|
@forbidden_response = { error: 'Access denied' }
|
@@ -280,6 +281,7 @@ module RackJwtAegis
|
|
280
281
|
|
281
282
|
def validate!
|
282
283
|
validate_jwt_settings!
|
284
|
+
validate_payload_mapping!
|
283
285
|
validate_cache_settings!
|
284
286
|
validate_multi_tenant_settings!
|
285
287
|
end
|
@@ -293,6 +295,24 @@ module RackJwtAegis
|
|
293
295
|
raise ConfigurationError, "Unsupported JWT algorithm: #{jwt_algorithm}"
|
294
296
|
end
|
295
297
|
|
298
|
+
def validate_payload_mapping!
|
299
|
+
# Allow nil payload_mapping (will use defaults)
|
300
|
+
return if payload_mapping.nil?
|
301
|
+
|
302
|
+
raise ConfigurationError, 'payload_mapping must be a Hash' unless payload_mapping.is_a?(Hash)
|
303
|
+
|
304
|
+
# Validate all values are symbols
|
305
|
+
invalid_values = payload_mapping.reject { |_key, value| value.is_a?(Symbol) }
|
306
|
+
return if invalid_values.empty?
|
307
|
+
|
308
|
+
raise ConfigurationError, "payload_mapping values must be symbols, invalid: #{invalid_values.inspect}"
|
309
|
+
|
310
|
+
# NOTE: We don't validate required keys because users may provide
|
311
|
+
# partial mappings that are intended to override defaults. The payload_key method
|
312
|
+
# handles missing keys by returning the standard key as fallback.
|
313
|
+
# This includes RBAC keys - if :role_ids is not mapped, it falls back to 'role_ids'.
|
314
|
+
end
|
315
|
+
|
296
316
|
def validate_cache_settings!
|
297
317
|
return unless rbac_enabled?
|
298
318
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RackJwtAegis
|
4
|
+
# Shared debug logging functionality
|
5
|
+
#
|
6
|
+
# Provides consistent debug logging across all RackJwtAegis components
|
7
|
+
# with configurable log levels and automatic timestamp formatting.
|
8
|
+
#
|
9
|
+
# @author Ken Camajalan Demanawa
|
10
|
+
# @since 1.0.0
|
11
|
+
module DebugLogger
|
12
|
+
# Log debug message if debug mode is enabled
|
13
|
+
#
|
14
|
+
# @param message [String] the message to log
|
15
|
+
# @param level [Symbol] the log level (:info, :warn, :error) (default: :info)
|
16
|
+
# @param component [String] the component name for log prefixing (optional)
|
17
|
+
def debug_log(message, level = :info, component = nil)
|
18
|
+
return unless @config.debug_mode?
|
19
|
+
|
20
|
+
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S.%L')
|
21
|
+
|
22
|
+
# Determine component name for log prefix
|
23
|
+
component_name = component || infer_component_name
|
24
|
+
|
25
|
+
formatted_message = "[#{timestamp}] #{component_name}: #{message}"
|
26
|
+
|
27
|
+
case level
|
28
|
+
when :error, :warn
|
29
|
+
warn formatted_message
|
30
|
+
else
|
31
|
+
puts formatted_message
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Infer component name from class name
|
38
|
+
#
|
39
|
+
# @return [String] the inferred component name
|
40
|
+
def infer_component_name
|
41
|
+
case self.class.name
|
42
|
+
when /Middleware/
|
43
|
+
'RackJwtAegis'
|
44
|
+
when /RbacManager/
|
45
|
+
'RbacManager'
|
46
|
+
else
|
47
|
+
self.class.name.split('::').last || 'RackJwtAegis'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -25,6 +25,8 @@ module RackJwtAegis
|
|
25
25
|
# skip_paths: ['/health', '/api/public/*']
|
26
26
|
# }
|
27
27
|
class Middleware
|
28
|
+
include DebugLogger
|
29
|
+
|
28
30
|
# Initialize the middleware
|
29
31
|
#
|
30
32
|
# @param app [#call] the Rack application
|
@@ -65,7 +67,7 @@ module RackJwtAegis
|
|
65
67
|
token = extract_jwt_token(request)
|
66
68
|
payload = @jwt_validator.validate(token)
|
67
69
|
|
68
|
-
debug_log("JWT validation successful for user: #{payload[@config.payload_key(:user_id)]}")
|
70
|
+
debug_log("JWT validation successful for user: #{payload[@config.payload_key(:user_id).to_s]}")
|
69
71
|
|
70
72
|
# Step 3: Multi-tenant validation (if enabled)
|
71
73
|
if multi_tenant_enabled?
|
@@ -159,8 +161,12 @@ module RackJwtAegis
|
|
159
161
|
# @param payload [Hash] the JWT payload
|
160
162
|
# @return [Array] array of user role IDs
|
161
163
|
def extract_user_roles(payload)
|
162
|
-
#
|
163
|
-
|
164
|
+
# Use configured payload mapping for role_ids, with fallback to common field names
|
165
|
+
role_key = @config.payload_key(:role_ids).to_s
|
166
|
+
roles = payload[role_key]
|
167
|
+
|
168
|
+
# If mapped key doesn't exist, try common fallback field names
|
169
|
+
roles = payload['roles'] || payload['role'] || payload['user_roles'] || payload['role_ids'] if roles.nil?
|
164
170
|
|
165
171
|
case roles
|
166
172
|
when Array
|
@@ -168,19 +174,10 @@ module RackJwtAegis
|
|
168
174
|
when String, Integer
|
169
175
|
[roles.to_s] # Single role as array
|
170
176
|
else
|
171
|
-
debug_log("Warning: No valid roles found in JWT payload.
|
177
|
+
debug_log("Warning: No valid roles found in JWT payload. Looking for '#{role_key}' field. \
|
178
|
+
Available fields: #{payload.keys}".squeeze)
|
172
179
|
[]
|
173
180
|
end
|
174
181
|
end
|
175
|
-
|
176
|
-
# Log debug message if debug mode is enabled
|
177
|
-
#
|
178
|
-
# @param message [String] the message to log
|
179
|
-
def debug_log(message)
|
180
|
-
return unless @config.debug_mode?
|
181
|
-
|
182
|
-
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S.%L')
|
183
|
-
puts "[#{timestamp}] RackJwtAegis: #{message}"
|
184
|
-
end
|
185
182
|
end
|
186
183
|
end
|
@@ -15,6 +15,8 @@ module RackJwtAegis
|
|
15
15
|
# manager = RbacManager.new(config)
|
16
16
|
# manager.authorize(request, jwt_payload)
|
17
17
|
class RbacManager
|
18
|
+
include DebugLogger
|
19
|
+
|
18
20
|
CACHE_TTL = 300 # 5 minutes default cache TTL
|
19
21
|
|
20
22
|
# Initialize the RBAC manager
|
@@ -122,14 +124,12 @@ module RackJwtAegis
|
|
122
124
|
end
|
123
125
|
|
124
126
|
# Permission is fresh
|
125
|
-
|
126
|
-
|
127
|
-
#{permission_age}s, RBAC age: #{rbac_update_age || 'unknown'}s)".squeeze)
|
128
|
-
end
|
127
|
+
debug_log("Cache hit: #{permission_key} (permission age: \
|
128
|
+
#{permission_age}s, RBAC age: #{rbac_update_age || 'unknown'}s)".squeeze)
|
129
129
|
true
|
130
130
|
rescue CacheError => e
|
131
131
|
# Log cache error but don't fail the request
|
132
|
-
|
132
|
+
debug_log("RbacManager cache read error: #{e.message}", :warn)
|
133
133
|
nil
|
134
134
|
end
|
135
135
|
end
|
@@ -146,7 +146,7 @@ module RackJwtAegis
|
|
146
146
|
false
|
147
147
|
rescue CacheError => e
|
148
148
|
# Cache error - fail secure (deny access)
|
149
|
-
|
149
|
+
debug_log("RbacManager RBAC cache error: #{e.message}", :warn)
|
150
150
|
false
|
151
151
|
end
|
152
152
|
|
@@ -166,10 +166,10 @@ module RackJwtAegis
|
|
166
166
|
# Write back to cache
|
167
167
|
@permission_cache.write('user_permissions', user_permissions, expires_in: CACHE_TTL)
|
168
168
|
|
169
|
-
debug_log("Cached permission: #{permission_key} => #{current_time}")
|
169
|
+
debug_log("Cached permission: #{permission_key} => #{current_time}")
|
170
170
|
rescue CacheError => e
|
171
171
|
# Log cache error but don't fail the request
|
172
|
-
|
172
|
+
debug_log("RbacManager permission cache write error: #{e.message}", :warn)
|
173
173
|
end
|
174
174
|
end
|
175
175
|
|
@@ -177,7 +177,7 @@ module RackJwtAegis
|
|
177
177
|
# Extract user roles from JWT payload
|
178
178
|
user_roles = extract_user_roles_from_request(request)
|
179
179
|
if user_roles.nil? || user_roles.empty?
|
180
|
-
|
180
|
+
debug_log('RbacManager: No user roles found in request context', :warn)
|
181
181
|
return false
|
182
182
|
end
|
183
183
|
|
@@ -214,7 +214,7 @@ module RackJwtAegis
|
|
214
214
|
|
215
215
|
nil
|
216
216
|
rescue CacheError => e
|
217
|
-
|
217
|
+
debug_log("RbacManager RBAC last-update read error: #{e.message}", :warn)
|
218
218
|
nil
|
219
219
|
end
|
220
220
|
end
|
@@ -233,14 +233,14 @@ module RackJwtAegis
|
|
233
233
|
# If no permissions remain, remove the entire cache
|
234
234
|
if user_permissions.empty?
|
235
235
|
@permission_cache.delete('user_permissions')
|
236
|
-
debug_log("Removed last permission, cleared entire cache: #{reason}")
|
236
|
+
debug_log("Removed last permission, cleared entire cache: #{reason}")
|
237
237
|
else
|
238
238
|
# Update the cache with the modified permissions
|
239
239
|
@permission_cache.write('user_permissions', user_permissions, expires_in: CACHE_TTL)
|
240
|
-
debug_log("Removed stale permission #{permission_key}: #{reason}")
|
240
|
+
debug_log("Removed stale permission #{permission_key}: #{reason}")
|
241
241
|
end
|
242
242
|
rescue CacheError => e
|
243
|
-
|
243
|
+
debug_log("RbacManager stale permission removal error: #{e.message}", :warn)
|
244
244
|
end
|
245
245
|
end
|
246
246
|
|
@@ -250,9 +250,9 @@ module RackJwtAegis
|
|
250
250
|
|
251
251
|
begin
|
252
252
|
@permission_cache.delete('user_permissions')
|
253
|
-
debug_log("Nuked user permissions cache: #{reason}")
|
253
|
+
debug_log("Nuked user permissions cache: #{reason}")
|
254
254
|
rescue CacheError => e
|
255
|
-
|
255
|
+
debug_log("RbacManager cache nuke error: #{e.message}", :warn)
|
256
256
|
end
|
257
257
|
end
|
258
258
|
|
@@ -313,10 +313,10 @@ module RackJwtAegis
|
|
313
313
|
# Write back to cache
|
314
314
|
@permission_cache.write('user_permissions', user_permissions, expires_in: CACHE_TTL)
|
315
315
|
|
316
|
-
debug_log("Cached user permission: #{permission_key} => #{current_time}")
|
316
|
+
debug_log("Cached user permission: #{permission_key} => #{current_time}")
|
317
317
|
rescue CacheError => e
|
318
318
|
# Log cache error but don't fail the request
|
319
|
-
|
319
|
+
debug_log("RbacManager permission cache write error: #{e.message}", :warn)
|
320
320
|
end
|
321
321
|
end
|
322
322
|
|
@@ -380,7 +380,7 @@ module RackJwtAegis
|
|
380
380
|
regex = Regexp.new(regex_pattern)
|
381
381
|
return regex.match?(resource_path)
|
382
382
|
rescue RegexpError => e
|
383
|
-
|
383
|
+
debug_log("RbacManager: Invalid regex pattern '#{regex_pattern}': #{e.message}", :warn)
|
384
384
|
return false
|
385
385
|
end
|
386
386
|
end
|
@@ -422,6 +422,7 @@ module RackJwtAegis
|
|
422
422
|
# Each permission should be a string in format "endpoint:method"
|
423
423
|
role_permissions.each do |permission|
|
424
424
|
return false unless permission.is_a?(String)
|
425
|
+
# Permission must include ':' (resource:method format)
|
425
426
|
return false unless permission.include?(':')
|
426
427
|
end
|
427
428
|
end
|
@@ -429,16 +430,8 @@ module RackJwtAegis
|
|
429
430
|
|
430
431
|
true
|
431
432
|
rescue StandardError => e
|
432
|
-
|
433
|
+
debug_log("RbacManager: Cache format validation error: #{e.message}", :warn)
|
433
434
|
false
|
434
435
|
end
|
435
|
-
|
436
|
-
# Log debug message if debug mode is enabled
|
437
|
-
def debug_log(message)
|
438
|
-
return unless @config.debug_mode?
|
439
|
-
|
440
|
-
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S.%L')
|
441
|
-
puts "[#{timestamp}] RbacManager: #{message}"
|
442
|
-
end
|
443
436
|
end
|
444
437
|
end
|
data/lib/rack_jwt_aegis.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'rack_jwt_aegis/version'
|
4
4
|
require_relative 'rack_jwt_aegis/configuration'
|
5
|
+
require_relative 'rack_jwt_aegis/debug_logger'
|
5
6
|
require_relative 'rack_jwt_aegis/middleware'
|
6
7
|
require_relative 'rack_jwt_aegis/jwt_validator'
|
7
8
|
require_relative 'rack_jwt_aegis/multi_tenant_validator'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack_jwt_aegis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ken C. Demanawa
|
@@ -68,6 +68,7 @@ files:
|
|
68
68
|
- lib/rack_jwt_aegis.rb
|
69
69
|
- lib/rack_jwt_aegis/cache_adapter.rb
|
70
70
|
- lib/rack_jwt_aegis/configuration.rb
|
71
|
+
- lib/rack_jwt_aegis/debug_logger.rb
|
71
72
|
- lib/rack_jwt_aegis/jwt_validator.rb
|
72
73
|
- lib/rack_jwt_aegis/middleware.rb
|
73
74
|
- lib/rack_jwt_aegis/multi_tenant_validator.rb
|