rack_jwt_aegis 1.0.1 → 1.0.2
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/.yardopts +2 -4
- data/CHANGELOG.md +12 -2
- data/README.md +35 -13
- data/Rakefile +0 -8
- data/lib/rack_jwt_aegis/configuration.rb +24 -7
- data/lib/rack_jwt_aegis/multi_tenant_validator.rb +29 -55
- data/lib/rack_jwt_aegis/version.rb +1 -1
- metadata +3 -4
- data/.yard/yard_gfm_config.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85f1445f011d0e42c075e3777164d61b215aa04b6f78e535db987c3f0e239c6f
|
4
|
+
data.tar.gz: 94fa6e5dd9041709d394e1c42dc5cc938aeb4fb9325218a94cab6352a4a7cf47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e3e83285e30c8b86efb2977aba6cfeccf728539b689fb80fb658e29ac91e61c455ab48c5793b689293154d3fe985ad6fee71d3b2381261f34b481370c89e8a1
|
7
|
+
data.tar.gz: f42e6cdb32d72f06ef14d7c016cbb7970ad5b04f9626b16cc1bf7a4062cdb6b68a899548e80259bac934622b2adc3080121c2b7e82daa84ab93f2df30ad34e63
|
data/.yardopts
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
--output-dir doc
|
2
2
|
--readme README.md
|
3
|
-
--markup-provider=kramdown
|
4
|
-
--markup=markdown
|
5
3
|
--main README.md
|
4
|
+
--markup=markdown
|
6
5
|
--protected
|
7
6
|
--private
|
8
7
|
--no-private
|
9
|
-
--title "RackJwtAegis API Documentation"
|
8
|
+
--title "RackJwtAegis Rack Middleware API Documentation"
|
10
9
|
--charset utf-8
|
11
10
|
lib/**/*.rb
|
12
11
|
-
|
13
12
|
README.md
|
14
13
|
LICENSE.txt
|
15
14
|
CODE_OF_CONDUCT.md
|
16
|
-
adrs/architecture.md
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,16 @@ 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.2] - 2025-08-13
|
9
|
+
|
10
|
+
### 🔧 Fixed
|
11
|
+
|
12
|
+
- Add :validate_tenant_id configuration and incorporate this to the MultiTenantValidator#validate_tenant_id_header
|
13
|
+
|
14
|
+
#### Code Quality & Maintenance
|
15
|
+
|
16
|
+
- Refactor the Configuration and MultiTenantValidator
|
17
|
+
|
8
18
|
## [1.0.1] - 2025-08-13
|
9
19
|
|
10
20
|
### 🔧 Fixed
|
@@ -23,7 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
23
33
|
#### Developer Experience
|
24
34
|
|
25
35
|
- **Consistent Logging**: Unified debug log format across all components with automatic timestamp formatting
|
26
|
-
- **Component Identification**: Automatic component name inference for better log traceability
|
36
|
+
- **Component Identification**: Automatic component name inference for better log traceability
|
27
37
|
- **Configurable Log Levels**: Support for info, warn, and error log levels with appropriate output streams
|
28
38
|
|
29
39
|
### 🏗️ Technical Details
|
@@ -37,7 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
37
47
|
#### Testing & Quality
|
38
48
|
|
39
49
|
- **Test Suite**: All 340 tests pass with 975 assertions
|
40
|
-
- **Coverage Maintained**: 98.17% line coverage, 92.83% branch coverage
|
50
|
+
- **Coverage Maintained**: 98.17% line coverage, 92.83% branch coverage
|
41
51
|
- **RBAC Integration**: Verified all role-based authorization tests pass after refactoring
|
42
52
|
- **Zero Regression**: No functional changes, only structural improvements
|
43
53
|
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
[](https://www.ruby-lang.org/en/)
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
8
8
|
|
9
|
-
JWT authentication middleware for hierarchical multi-tenant Rack applications with 2-level tenant support.
|
9
|
+
JWT authentication and authorization middleware for hierarchical multi-tenant Rack applications with 2-level tenant support.
|
10
10
|
|
11
11
|
## Features
|
12
12
|
|
@@ -46,25 +46,25 @@ Rack JWT Aegis includes a command-line tool for generating secure JWT secrets:
|
|
46
46
|
|
47
47
|
```bash
|
48
48
|
# Generate a secure JWT secret
|
49
|
-
|
49
|
+
rack_jwt_aegis secret
|
50
50
|
|
51
51
|
# Generate base64-encoded secret
|
52
|
-
|
52
|
+
rack_jwt_aegis secret --format base64
|
53
53
|
|
54
54
|
# Generate secret in environment variable format
|
55
|
-
|
55
|
+
rack_jwt_aegis secret --env
|
56
56
|
|
57
57
|
# Generate multiple secrets
|
58
|
-
|
58
|
+
rack_jwt_aegis secret --count 3
|
59
59
|
|
60
60
|
# Quiet mode (secret only)
|
61
|
-
|
61
|
+
rack_jwt_aegis secret --quiet
|
62
62
|
|
63
63
|
# Custom length (32 bytes)
|
64
|
-
|
64
|
+
rack_jwt_aegis secret --length 32
|
65
65
|
|
66
66
|
# Show help
|
67
|
-
|
67
|
+
rack_jwt_aegis --help
|
68
68
|
```
|
69
69
|
|
70
70
|
### Security Features
|
@@ -82,6 +82,7 @@ Rack JWT Aegis includes a command-line tool for generating secure JWT secrets:
|
|
82
82
|
# config/application.rb
|
83
83
|
config.middleware.insert_before 0, RackJwtAegis::Middleware, {
|
84
84
|
jwt_secret: ENV['JWT_SECRET'],
|
85
|
+
validate_tenant_id: true,
|
85
86
|
tenant_id_header_name: 'X-Tenant-Id',
|
86
87
|
skip_paths: ['/api/v1/login', '/health']
|
87
88
|
}
|
@@ -94,6 +95,7 @@ Rack JWT Aegis includes a command-line tool for generating secure JWT secrets:
|
|
94
95
|
|
95
96
|
use RackJwtAegis::Middleware, {
|
96
97
|
jwt_secret: ENV['JWT_SECRET'],
|
98
|
+
validate_tenant_id: true,
|
97
99
|
tenant_id_header_name: 'X-Tenant-Id',
|
98
100
|
skip_paths: ['/login', '/health']
|
99
101
|
}
|
@@ -126,10 +128,20 @@ RackJwtAegis::Middleware.new(app, {
|
|
126
128
|
jwt_algorithm: 'HS256', # Default: 'HS256'
|
127
129
|
|
128
130
|
# Multi-Tenant Settings
|
131
|
+
validate_tenant_id: true, # Default: false
|
129
132
|
tenant_id_header_name: 'X-Tenant-Id', # Default: 'X-Tenant-Id'
|
130
133
|
validate_subdomain: true, # Default: false
|
131
134
|
validate_pathname_slug: true, # Default: false
|
132
135
|
|
136
|
+
# Default Payload Mapping:
|
137
|
+
payload_mapping: {
|
138
|
+
user_id: :user_id,
|
139
|
+
tenant_id: :tenant_id,
|
140
|
+
subdomain: :subdomain,
|
141
|
+
pathname_slugs: :pathname_slugs,
|
142
|
+
role_ids: :role_ids,
|
143
|
+
},
|
144
|
+
|
133
145
|
# Path Configuration
|
134
146
|
skip_paths: ['/health', '/api/v1/login'],
|
135
147
|
pathname_slug_pattern: /^\/api\/v1\/([^\/]+)\//, # Default pattern
|
@@ -172,7 +184,16 @@ RackJwtAegis::Middleware.new(app, {
|
|
172
184
|
payload['role'] == 'admin' || payload['permissions'].include?('read')
|
173
185
|
},
|
174
186
|
|
175
|
-
#
|
187
|
+
# Payload Mapping defaults:
|
188
|
+
# payload_mapping = {
|
189
|
+
# user_id: :user_id,
|
190
|
+
# tenant_id: :tenant_id,
|
191
|
+
# subdomain: :subdomain,
|
192
|
+
# pathname_slugs: :pathname_slugs,
|
193
|
+
# role_ids: :role_ids,
|
194
|
+
# }
|
195
|
+
|
196
|
+
# Flexible Payload Mapping can be customized into:
|
176
197
|
payload_mapping: {
|
177
198
|
user_id: :sub, # Map 'sub' claim to user_id
|
178
199
|
tenant_id: :company_group_id, # Map 'company_group_id' claim
|
@@ -190,7 +211,7 @@ Rack JWT Aegis provides multiple strategies for multi-tenant authentication:
|
|
190
211
|
### Subdomain Validation
|
191
212
|
|
192
213
|
```ruby
|
193
|
-
# Validates that the JWT's
|
214
|
+
# Validates that the JWT's subdomain claim matches the request's host subdomain
|
194
215
|
config.validate_subdomain = true
|
195
216
|
```
|
196
217
|
|
@@ -206,6 +227,7 @@ config.pathname_slug_pattern = /^\/api\/v1\/([^\/]+)\//
|
|
206
227
|
|
207
228
|
```ruby
|
208
229
|
# Validates the X-Tenant-Id header against JWT payload
|
230
|
+
config.validate_tenant_id = true
|
209
231
|
config.tenant_id_header_name = 'X-Tenant-Id'
|
210
232
|
```
|
211
233
|
|
@@ -297,7 +319,7 @@ When RBAC is enabled, the middleware extracts user roles from the JWT payload fo
|
|
297
319
|
```ruby
|
298
320
|
payload_mapping: {
|
299
321
|
user_id: :user_id,
|
300
|
-
tenant_id: :tenant_id,
|
322
|
+
tenant_id: :tenant_id,
|
301
323
|
subdomain: :subdomain,
|
302
324
|
pathname_slugs: :pathname_slugs,
|
303
325
|
role_ids: :role_ids # Default field for user roles
|
@@ -311,7 +333,7 @@ The middleware looks for roles in the following priority order:
|
|
311
333
|
1. **Configured Field**: Uses the `role_ids` mapping (e.g., if mapped to `:user_roles`, looks for `user_roles` field)
|
312
334
|
2. **Fallback Fields**: If the mapped field is not found, tries these common alternatives:
|
313
335
|
- `roles` - Array of role identifiers
|
314
|
-
- `role` - Single role identifier
|
336
|
+
- `role` - Single role identifier
|
315
337
|
- `user_roles` - Array of user role identifiers
|
316
338
|
- `role_ids` - Array of role IDs (numeric or string)
|
317
339
|
|
@@ -341,7 +363,7 @@ The middleware supports flexible role formats:
|
|
341
363
|
# Array of strings (recommended)
|
342
364
|
"role_ids": ["123", "456", "admin"]
|
343
365
|
|
344
|
-
# Array of integers
|
366
|
+
# Array of integers
|
345
367
|
"role_ids": [123, 456]
|
346
368
|
|
347
369
|
# Single string
|
data/Rakefile
CHANGED
@@ -14,19 +14,11 @@ begin
|
|
14
14
|
require 'yard'
|
15
15
|
require 'yard/rake/yardoc_task'
|
16
16
|
|
17
|
-
# Load custom GFM configuration if available
|
18
|
-
begin
|
19
|
-
require_relative '.yard/yard_gfm_config'
|
20
|
-
rescue LoadError
|
21
|
-
# GFM config not available, use default markdown processing
|
22
|
-
end
|
23
|
-
|
24
17
|
YARD::Rake::YardocTask.new do |t|
|
25
18
|
t.files = ['lib/**/*.rb']
|
26
19
|
t.options = [
|
27
20
|
'--output-dir', 'doc',
|
28
21
|
'--readme', 'README.md',
|
29
|
-
'--markup-provider', 'kramdown',
|
30
22
|
'--markup', 'markdown'
|
31
23
|
]
|
32
24
|
t.stats_options = ['--list-undoc']
|
@@ -49,6 +49,10 @@ module RackJwtAegis
|
|
49
49
|
# @return [Boolean] true if pathname slug validation is enabled
|
50
50
|
attr_accessor :validate_pathname_slug
|
51
51
|
|
52
|
+
# Whether to validate tenant id from request header against the tenant id from JWT payload
|
53
|
+
# @return [Boolean] true if tenant id validation is enabled
|
54
|
+
attr_accessor :validate_tenant_id
|
55
|
+
|
52
56
|
# Whether RBAC (Role-Based Access Control) is enabled
|
53
57
|
# @return [Boolean] true if RBAC is enabled
|
54
58
|
attr_accessor :rbac_enabled
|
@@ -204,6 +208,12 @@ module RackJwtAegis
|
|
204
208
|
config_boolean?(validate_pathname_slug)
|
205
209
|
end
|
206
210
|
|
211
|
+
# Check if tenant id validation is enabled
|
212
|
+
# @return [Boolean] true if tenant id validation is enabled
|
213
|
+
def validate_tenant_id?
|
214
|
+
config_boolean?(validate_tenant_id)
|
215
|
+
end
|
216
|
+
|
207
217
|
# Check if debug mode is enabled
|
208
218
|
# @return [Boolean] true if debug mode is enabled
|
209
219
|
def debug_mode?
|
@@ -261,6 +271,7 @@ module RackJwtAegis
|
|
261
271
|
@jwt_algorithm = 'HS256'
|
262
272
|
@validate_subdomain = false
|
263
273
|
@validate_pathname_slug = false
|
274
|
+
@validate_tenant_id = false
|
264
275
|
@rbac_enabled = false
|
265
276
|
@tenant_id_header_name = 'X-Tenant-Id'
|
266
277
|
@pathname_slug_pattern = %r{^/api/v1/([^/]+)/}
|
@@ -287,7 +298,7 @@ module RackJwtAegis
|
|
287
298
|
end
|
288
299
|
|
289
300
|
def validate_jwt_settings!
|
290
|
-
raise ConfigurationError, 'jwt_secret is required' if jwt_secret.
|
301
|
+
raise ConfigurationError, 'jwt_secret is required' if jwt_secret.to_s.strip.empty?
|
291
302
|
|
292
303
|
valid_algorithms = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512']
|
293
304
|
return if valid_algorithms.include?(jwt_algorithm)
|
@@ -338,17 +349,23 @@ module RackJwtAegis
|
|
338
349
|
end
|
339
350
|
|
340
351
|
def validate_multi_tenant_settings!
|
341
|
-
if validate_pathname_slug? && pathname_slug_pattern.nil?
|
342
|
-
raise ConfigurationError, 'pathname_slug_pattern is required when validate_pathname_slug is true'
|
343
|
-
end
|
344
|
-
|
345
352
|
if validate_subdomain? && !payload_mapping.key?(:subdomain)
|
346
353
|
raise ConfigurationError, 'payload_mapping must include :subdomain when validate_subdomain is true'
|
347
354
|
end
|
348
355
|
|
349
|
-
|
356
|
+
if validate_tenant_id?
|
357
|
+
error_msg = []
|
358
|
+
error_msg << 'payload_mapping must include :tenant_id' unless payload_mapping.key?(:tenant_id)
|
359
|
+
error_msg << 'tenant_id_header_name is required' if tenant_id_header_name.to_s.strip.empty?
|
360
|
+
raise ConfigurationError, "#{error_msg.join(' and ')} when validate_tenant_id is true" if error_msg.any?
|
361
|
+
end
|
362
|
+
|
363
|
+
return unless validate_pathname_slug?
|
350
364
|
|
351
|
-
|
365
|
+
error_msg = []
|
366
|
+
error_msg << 'payload_mapping must include :pathname_slugs' unless payload_mapping.key?(:pathname_slugs)
|
367
|
+
error_msg << 'pathname_slug_pattern is required' if pathname_slug_pattern.to_s.empty?
|
368
|
+
raise ConfigurationError, "#{error_msg.join(' and ')} when validate_pathname_slug is true" if error_msg.any?
|
352
369
|
end
|
353
370
|
end
|
354
371
|
end
|
@@ -33,83 +33,72 @@ module RackJwtAegis
|
|
33
33
|
# @param payload [Hash] the JWT payload containing tenant information
|
34
34
|
# @raise [AuthorizationError] if tenant validation fails
|
35
35
|
def validate(request, payload)
|
36
|
-
validate_subdomain(request, payload)
|
37
|
-
validate_pathname_slug(request, payload)
|
38
|
-
|
36
|
+
validate_subdomain(request, payload)
|
37
|
+
validate_pathname_slug(request, payload)
|
38
|
+
validate_tenant_id_header(request, payload)
|
39
39
|
end
|
40
40
|
|
41
41
|
private
|
42
42
|
|
43
43
|
# Level 1 Multi-Tenant: Top-level tenant (Company-Group) validation via subdomain
|
44
44
|
def validate_subdomain(request, payload)
|
45
|
+
return unless @config.validate_subdomain?
|
46
|
+
|
45
47
|
request_host = request.host
|
46
|
-
return if request_host.
|
48
|
+
return if request_host.to_s.empty?
|
47
49
|
|
48
50
|
# Extract subdomain from request host
|
49
|
-
req_subdomain = extract_subdomain(request_host)
|
51
|
+
req_subdomain = extract_subdomain(request_host).to_s.downcase
|
50
52
|
|
51
53
|
# Get JWT domain claim
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
if jwt_domain.nil? || jwt_domain.empty?
|
56
|
-
raise AuthorizationError, 'JWT payload missing subdomain for subdomain validation'
|
57
|
-
end
|
58
|
-
|
59
|
-
# Extract subdomain from JWT domain
|
60
|
-
jwt_subdomain = extract_subdomain(jwt_domain)
|
54
|
+
jwt_claim = payload[@config.payload_key(:subdomain).to_s].to_s.strip.downcase
|
55
|
+
raise AuthorizationError, 'JWT payload missing subdomain for subdomain validation' if jwt_claim.empty?
|
61
56
|
|
62
57
|
# Compare subdomains
|
63
|
-
return if
|
58
|
+
return if req_subdomain.eql?(jwt_claim)
|
64
59
|
|
65
60
|
raise AuthorizationError,
|
66
61
|
"Subdomain access denied: request subdomain '#{req_subdomain}' " \
|
67
|
-
"does not match JWT subdomain '#{
|
62
|
+
"does not match JWT subdomain '#{jwt_claim}'"
|
68
63
|
end
|
69
64
|
|
70
65
|
# Level 2 Multi-Tenant: Sub-level tenant (Company) validation via URL path
|
71
66
|
def validate_pathname_slug(request, payload)
|
67
|
+
return unless @config.validate_pathname_slug?
|
68
|
+
|
72
69
|
# Extract company slug from URL path
|
73
|
-
|
70
|
+
pathname_slug = extract_slug_from_path(request.path)
|
74
71
|
|
75
|
-
return if
|
72
|
+
return if pathname_slug.nil? # No company slug in path
|
76
73
|
|
77
74
|
# Get accessible company slugs from JWT
|
78
|
-
|
79
|
-
accessible_slugs = payload[jwt_slugs_key]
|
75
|
+
accessible_slugs = payload[@config.payload_key(:pathname_slugs).to_s]
|
80
76
|
|
81
77
|
if accessible_slugs.nil? || !accessible_slugs.is_a?(Array) || accessible_slugs.empty?
|
82
78
|
raise AuthorizationError, 'JWT payload missing or invalid pathname_slugs for company access validation'
|
83
79
|
end
|
84
80
|
|
85
81
|
# Check if requested company slug is in user's accessible list
|
86
|
-
return if accessible_slugs.include?(
|
82
|
+
return if accessible_slugs.map(&:downcase).include?(pathname_slug)
|
87
83
|
|
88
84
|
raise AuthorizationError,
|
89
|
-
"Company access denied: '#{
|
85
|
+
"Company access denied: '#{pathname_slug}' not in accessible companies #{accessible_slugs}"
|
90
86
|
end
|
91
87
|
|
92
88
|
# Company Group header validation (additional security layer)
|
93
|
-
def
|
94
|
-
|
95
|
-
header_value = request.get_header(header_name)
|
96
|
-
|
97
|
-
return if header_value.nil? # Header not present, skip validation
|
98
|
-
|
99
|
-
# Get company group ID from JWT
|
100
|
-
jwt_company_group_key = @config.payload_key(:tenant_id).to_s
|
101
|
-
jwt_tenant_id = payload[jwt_company_group_key]
|
89
|
+
def validate_tenant_id_header(request, payload)
|
90
|
+
return unless @config.validate_tenant_id?
|
102
91
|
|
103
|
-
|
92
|
+
# Get tenant id from request header
|
93
|
+
header_value = request.get_header("HTTP_#{@config.tenant_id_header_name.upcase.tr('-', '_')}").to_s.downcase
|
94
|
+
# Get tenant id from JWT payload
|
95
|
+
jwt_claim = payload[@config.payload_key(:tenant_id).to_s].to_s.strip.downcase
|
96
|
+
raise AuthorizationError, 'JWT payload missing tenant_id for header validation' if jwt_claim.empty?
|
104
97
|
|
105
|
-
|
106
|
-
header_value_str = header_value.to_s
|
107
|
-
jwt_value_str = jwt_tenant_id.to_s
|
108
|
-
|
109
|
-
return if header_value_str == jwt_value_str
|
98
|
+
return if !header_value.empty? && header_value.eql?(jwt_claim)
|
110
99
|
|
111
100
|
raise AuthorizationError,
|
112
|
-
"
|
101
|
+
"Tenant id header mismatch: header '#{header_value}' does not match JWT '#{jwt_claim}'"
|
113
102
|
end
|
114
103
|
|
115
104
|
def extract_subdomain(host)
|
@@ -131,24 +120,9 @@ module RackJwtAegis
|
|
131
120
|
parts.first
|
132
121
|
end
|
133
122
|
|
134
|
-
def
|
135
|
-
return nil if path.nil? || path.empty?
|
136
|
-
|
123
|
+
def extract_slug_from_path(path)
|
137
124
|
# Use configured pattern to extract company slug
|
138
|
-
|
139
|
-
return nil unless match && match[1]
|
140
|
-
|
141
|
-
# Return captured group (company slug)
|
142
|
-
match[1]
|
143
|
-
end
|
144
|
-
|
145
|
-
def subdomains_match?(first_subdomain, second_subdomain)
|
146
|
-
# Handle nil cases
|
147
|
-
return true if first_subdomain.nil? && second_subdomain.nil?
|
148
|
-
return false if first_subdomain.nil? || second_subdomain.nil?
|
149
|
-
|
150
|
-
# Case-insensitive comparison
|
151
|
-
first_subdomain.downcase == second_subdomain.downcase
|
125
|
+
@config.pathname_slug_pattern.match(path.to_s.strip.downcase)&.to_a&.last
|
152
126
|
end
|
153
127
|
end
|
154
128
|
end
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ken C. Demanawa
|
@@ -44,7 +44,7 @@ dependencies:
|
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '3.2'
|
46
46
|
description: |-
|
47
|
-
JWT authentication midleware with multi-tenant suport,\
|
47
|
+
JWT authentication and authorization midleware with multi-tenant suport,\
|
48
48
|
company validation, and subdomain isolation.
|
49
49
|
email:
|
50
50
|
- kenneth.c.demanawa@gmail.com
|
@@ -54,7 +54,6 @@ extensions: []
|
|
54
54
|
extra_rdoc_files: []
|
55
55
|
files:
|
56
56
|
- ".rubocop.yml"
|
57
|
-
- ".yard/yard_gfm_config.rb"
|
58
57
|
- ".yardopts"
|
59
58
|
- CHANGELOG.md
|
60
59
|
- CODE_OF_CONDUCT.md
|
@@ -101,5 +100,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
100
|
requirements: []
|
102
101
|
rubygems_version: 3.6.9
|
103
102
|
specification_version: 4
|
104
|
-
summary: JWT authentication middleware for multi-tenant Rack applications
|
103
|
+
summary: JWT authentication and authorization middleware for multi-tenant Rack applications
|
105
104
|
test_files: []
|
data/.yard/yard_gfm_config.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Custom YARD configuration for GitHub Flavored Markdown support
|
4
|
-
#
|
5
|
-
# This configuration ensures the kramdown-parser-gfm gem is available for
|
6
|
-
# proper rendering of fenced code blocks (```language) in YARD documentation.
|
7
|
-
#
|
8
|
-
# The actual GFM parsing integration is handled by YARD when kramdown is
|
9
|
-
# configured with the GFM input parser.
|
10
|
-
|
11
|
-
begin
|
12
|
-
require 'kramdown'
|
13
|
-
require 'kramdown-parser-gfm'
|
14
|
-
|
15
|
-
# Ensure GFM parser is available - YARD will use it automatically
|
16
|
-
# when kramdown processes markdown with input: 'GFM'
|
17
|
-
|
18
|
-
rescue LoadError => e
|
19
|
-
# Fallback gracefully if GFM parser is not available
|
20
|
-
puts "Warning: kramdown-parser-gfm not available, fenced code blocks may not render properly: #{e.message}"
|
21
|
-
end
|