Class: RackJwtAegis::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/rack_jwt_aegis/configuration.rb

Overview

Configuration class for RackJwtAegis middleware

Manages all configuration options for JWT authentication, multi-tenant validation, RBAC authorization, and caching behavior.

Examples:

Basic configuration

config = Configuration.new(jwt_secret: 'your-secret')

Full configuration

config = Configuration.new(
  jwt_secret: ENV['JWT_SECRET'],
  jwt_algorithm: 'HS256',
  validate_subdomain: true,
  validate_pathname_slug: true,
  rbac_enabled: true,
  skip_paths: ['/health', '/api/public/*'],
  debug_mode: Rails.env.development?
)

Author:

  • Ken Camajalan Demanawa

Since:

  • 0.1.0

Core JWT Settings collapse

Feature Toggles collapse

Multi-tenant Settings collapse

Path Management collapse

Cache Configuration collapse

Custom Validators collapse

Response Customization collapse

Development Settings collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Configuration

Initialize a new Configuration instance

Parameters:

  • options (Hash) (defaults to: {})

    configuration options

Options Hash (options):

  • :jwt_secret (String) — default: required

    JWT secret key for signature verification

  • :jwt_algorithm (String) — default: 'HS256'

    JWT algorithm to use

  • :validate_subdomain (Boolean) — default: false

    enable subdomain validation

  • :validate_pathname_slug (Boolean) — default: false

    enable pathname slug validation

  • :rbac_enabled (Boolean) — default: false

    enable RBAC authorization

  • :tenant_id_header_name (String) — default: 'X-Tenant-Id'

    tenant ID header name

  • :pathname_slug_pattern (Regexp)

    default pattern for pathname slugs

  • :payload_mapping (Hash)

    mapping of JWT claim names

  • :skip_paths (Array<String, Regexp>) — default: []

    paths to skip authentication

  • :rbac_cache_store (Symbol)

    cache adapter type

  • :rbac_cache_store_options (Hash)

    cache configuration options

  • :permissions_cache_store (Symbol)

    cache adapter type

  • :permissions_cache_store_options (Hash)

    cache configuration options

  • :cached_permissions_ttl (Integer) — default: 1800

    user permissions cache TTL in seconds

  • :debug_mode (Boolean) — default: false

    enable debug logging

Raises:

Since:

  • 0.1.0



164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/rack_jwt_aegis/configuration.rb', line 164

def initialize(options = {})
  # Set defaults
  set_defaults

  # Merge user options
  options.each do |key, value|
    raise ConfigurationError, "Unknown configuration option: #{key}" unless respond_to?("#{key}=")

    public_send("#{key}=", value)
  end

  # Validate configuration
  validate!
end

Instance Attribute Details

#cached_permissions_ttlInteger

Time-to-live for user permissions cache in seconds

Returns:

  • (Integer)

    TTL in seconds (default: 1800 - 30 minutes)

Since:

  • 0.1.0



107
108
109
# File 'lib/rack_jwt_aegis/configuration.rb', line 107

def cached_permissions_ttl
  @cached_permissions_ttl
end

#custom_payload_validatorProc

Custom payload validation proc

Examples:

->(payload, request) { payload['role'] == 'admin' }

Returns:

  • (Proc)

    a callable that receives (payload, request) and returns boolean

Since:

  • 0.1.0



117
118
119
# File 'lib/rack_jwt_aegis/configuration.rb', line 117

def custom_payload_validator
  @custom_payload_validator
end

#debug_modeBoolean

Whether debug mode is enabled for additional logging

Returns:

  • (Boolean)

    true if debug mode is enabled

Since:

  • 0.1.0



141
142
143
# File 'lib/rack_jwt_aegis/configuration.rb', line 141

def debug_mode
  @debug_mode
end

#forbidden_responseHash

Custom response for forbidden requests (403)

Examples:

{ error: 'Access denied', code: 'AUTH_002' }

Returns:

  • (Hash)

    the forbidden response body

Since:

  • 0.1.0



133
134
135
# File 'lib/rack_jwt_aegis/configuration.rb', line 133

def forbidden_response
  @forbidden_response
end

#jwt_algorithmString

Note:

Supported algorithms: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512

The JWT algorithm to use for token verification

Returns:

  • (String)

    the JWT algorithm (default: 'HS256')

Since:

  • 0.1.0



36
37
38
# File 'lib/rack_jwt_aegis/configuration.rb', line 36

def jwt_algorithm
  @jwt_algorithm
end

#jwt_secretString

Note:

This is required and must not be empty

The secret key used for JWT signature verification

Returns:

  • (String)

    the JWT secret key

Since:

  • 0.1.0



31
32
33
# File 'lib/rack_jwt_aegis/configuration.rb', line 31

def jwt_secret
  @jwt_secret
end

#pathname_slug_patternRegexp

The regular expression pattern to extract pathname slugs

Returns:

  • (Regexp)

    the pathname slug pattern (default: /^\/api\/v1\/([^\/]+)\//)

Since:

  • 0.1.0



68
69
70
# File 'lib/rack_jwt_aegis/configuration.rb', line 68

def pathname_slug_pattern
  @pathname_slug_pattern
end

#payload_mappingHash

Mapping of standard payload keys to custom JWT claim names

Examples:

{ user_id: :sub, tenant_id: :company_id, subdomain: :domain }

Returns:

  • (Hash)

    the payload mapping configuration

Since:

  • 0.1.0



74
75
76
# File 'lib/rack_jwt_aegis/configuration.rb', line 74

def payload_mapping
  @payload_mapping
end

#permissions_cache_storeSymbol

The permission cache store adapter type

Returns:

  • (Symbol)

    the permission cache store type

Since:

  • 0.1.0



99
100
101
# File 'lib/rack_jwt_aegis/configuration.rb', line 99

def permissions_cache_store
  @permissions_cache_store
end

#permissions_cache_store_optionsHash

Options for the permission cache store

Returns:

  • (Hash)

    permission cache configuration options

Since:

  • 0.1.0



103
104
105
# File 'lib/rack_jwt_aegis/configuration.rb', line 103

def permissions_cache_store_options
  @permissions_cache_store_options
end

#rbac_cache_storeSymbol

The RBAC cache store adapter type (separate from main cache)

Returns:

  • (Symbol)

    the RBAC cache store type

Since:

  • 0.1.0



91
92
93
# File 'lib/rack_jwt_aegis/configuration.rb', line 91

def rbac_cache_store
  @rbac_cache_store
end

#rbac_cache_store_optionsHash

Options for the RBAC cache store

Returns:

  • (Hash)

    RBAC cache configuration options

Since:

  • 0.1.0



95
96
97
# File 'lib/rack_jwt_aegis/configuration.rb', line 95

def rbac_cache_store_options
  @rbac_cache_store_options
end

#rbac_enabledBoolean

Whether RBAC (Role-Based Access Control) is enabled

Returns:

  • (Boolean)

    true if RBAC is enabled

Since:

  • 0.1.0



56
57
58
# File 'lib/rack_jwt_aegis/configuration.rb', line 56

def rbac_enabled
  @rbac_enabled
end

#skip_pathsArray<String, Regexp>

Array of paths that should skip JWT authentication

Examples:

['/health', '/api/public', /^\/assets/]

Returns:

  • (Array<String, Regexp>)

    paths to skip authentication for

Since:

  • 0.1.0



84
85
86
# File 'lib/rack_jwt_aegis/configuration.rb', line 84

def skip_paths
  @skip_paths
end

#tenant_id_header_nameString

The HTTP header name containing the tenant ID

Returns:

  • (String)

    the tenant ID header name (default: 'X-Tenant-Id')

Since:

  • 0.1.0



64
65
66
# File 'lib/rack_jwt_aegis/configuration.rb', line 64

def tenant_id_header_name
  @tenant_id_header_name
end

#unauthorized_responseHash

Custom response for unauthorized requests (401)

Examples:

{ error: 'Authentication required', code: 'AUTH_001' }

Returns:

  • (Hash)

    the unauthorized response body

Since:

  • 0.1.0



127
128
129
# File 'lib/rack_jwt_aegis/configuration.rb', line 127

def unauthorized_response
  @unauthorized_response
end

#validate_pathname_slugBoolean

Whether to validate pathname slug-based multi-tenancy

Returns:

  • (Boolean)

    true if pathname slug validation is enabled

Since:

  • 0.1.0



48
49
50
# File 'lib/rack_jwt_aegis/configuration.rb', line 48

def validate_pathname_slug
  @validate_pathname_slug
end

#validate_subdomainBoolean

Whether to validate subdomain-based multi-tenancy

Returns:

  • (Boolean)

    true if subdomain validation is enabled

Since:

  • 0.1.0



44
45
46
# File 'lib/rack_jwt_aegis/configuration.rb', line 44

def validate_subdomain
  @validate_subdomain
end

#validate_tenant_idBoolean

Whether to validate tenant id from request header against the tenant id from JWT payload

Returns:

  • (Boolean)

    true if tenant id validation is enabled

Since:

  • 0.1.0



52
53
54
# File 'lib/rack_jwt_aegis/configuration.rb', line 52

def validate_tenant_id
  @validate_tenant_id
end

Instance Method Details

#config_boolean?(value) ⇒ Boolean (private)

Convert various falsy/truthy values to proper boolean for configuration

Returns:

  • (Boolean)

Since:

  • 0.1.0



240
241
242
243
244
245
246
247
248
# File 'lib/rack_jwt_aegis/configuration.rb', line 240

def config_boolean?(value)
  if (value.is_a?(Numeric) && value.zero?) ||
     (value.is_a?(String) && ['false', '0', '', 'no'].include?(value.downcase.strip))
    return false
  end

  # Everything else is truthy
  !!value
end

#debug_mode?Boolean

Check if debug mode is enabled

Returns:

  • (Boolean)

    true if debug mode is enabled

Since:

  • 0.1.0



205
206
207
# File 'lib/rack_jwt_aegis/configuration.rb', line 205

def debug_mode?
  config_boolean?(debug_mode)
end

#payload_key(standard_key) ⇒ Symbol

Get the mapped payload key for a standard key

Examples:

config.payload_key(:user_id) #=> :sub (if mapped)
config.payload_key(:user_id) #=> :user_id (if not mapped)

Parameters:

  • standard_key (Symbol)

    the standard key to map

Returns:

  • (Symbol)

    the mapped key from payload_mapping, or the original key if no mapping exists

Since:

  • 0.1.0



233
234
235
# File 'lib/rack_jwt_aegis/configuration.rb', line 233

def payload_key(standard_key)
  payload_mapping&.fetch(standard_key, standard_key) || standard_key
end

#rbac_enabled?Boolean

Check if RBAC is enabled

Returns:

  • (Boolean)

    true if RBAC is enabled

Since:

  • 0.1.0



181
182
183
# File 'lib/rack_jwt_aegis/configuration.rb', line 181

def rbac_enabled?
  config_boolean?(rbac_enabled)
end

#set_defaultsObject (private)

Since:

  • 0.1.0



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/rack_jwt_aegis/configuration.rb', line 250

def set_defaults
  @jwt_algorithm = 'HS256'
  @validate_subdomain = false
  @validate_pathname_slug = false
  @validate_tenant_id = false
  @tenant_id_header_name = 'X-Tenant-Id'
  @pathname_slug_pattern = %r{^/api/v1/([^/]+)/}
  @skip_paths = []
  @payload_mapping = {
    user_id: :user_id,
    tenant_id: :tenant_id,
    subdomain: :subdomain,
    pathname_slugs: :pathname_slugs,
    role_ids: :role_ids,
  }
  @unauthorized_response = { error: 'Authentication required' }
  @forbidden_response = { error: 'Access denied' }
  @rbac_enabled = false
  @cached_permissions_ttl = 1800 # 30 minutes default
  @rbac_cache_store = if Object.const_defined?(:Rails) && Rails.const_defined?(:Application)
                        @debug_mode = Rails.env.development?
                        Rails.application.config.cache_store
                      else
                        @debug_mode = false
                        :memory
                      end
  @permissions_cache_store = @rbac_cache_store
  @rbac_cache_store_options = {}
  @permissions_cache_store_options = {}
end

#skip_path?(path) ⇒ Boolean

Check if the given path should skip JWT authentication

Parameters:

  • path (String)

    the request path to check

Returns:

  • (Boolean)

    true if the path should be skipped

Since:

  • 0.1.0



212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/rack_jwt_aegis/configuration.rb', line 212

def skip_path?(path)
  return false if skip_paths.nil? || skip_paths.empty?

  skip_paths.any? do |skip_path|
    case skip_path
    when String
      path == skip_path
    when Regexp
      skip_path.match?(path)
    else
      false
    end
  end
end

#validate!Object (private)

Since:

  • 0.1.0



281
282
283
284
285
286
# File 'lib/rack_jwt_aegis/configuration.rb', line 281

def validate!
  validate_jwt_settings!
  validate_payload_mapping!
  validate_cache_settings!
  validate_multi_tenant_settings!
end

#validate_cache_settings!Object (private)

Raises:

Since:

  • 0.1.0



315
316
317
318
319
320
# File 'lib/rack_jwt_aegis/configuration.rb', line 315

def validate_cache_settings!
  return unless rbac_enabled? && (rbac_cache_store.nil? || permissions_cache_store.nil?)

  raise ConfigurationError,
        'rbac_cache_store and permissions_cache_store are required when RBAC is enabled'
end

#validate_jwt_settings!Object (private)

Raises:

Since:

  • 0.1.0



288
289
290
291
292
293
294
295
# File 'lib/rack_jwt_aegis/configuration.rb', line 288

def validate_jwt_settings!
  raise ConfigurationError, 'jwt_secret is required' if jwt_secret.to_s.strip.empty?

  valid_algorithms = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512']
  return if valid_algorithms.include?(jwt_algorithm)

  raise ConfigurationError, "Unsupported JWT algorithm: #{jwt_algorithm}"
end

#validate_multi_tenant_settings!Object (private)

Raises:

Since:

  • 0.1.0



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/rack_jwt_aegis/configuration.rb', line 322

def validate_multi_tenant_settings!
  if validate_subdomain? && !payload_mapping.key?(:subdomain)
    raise ConfigurationError, 'payload_mapping must include :subdomain when validate_subdomain is true'
  end

  if validate_tenant_id?
    error_msg = []
    error_msg << 'payload_mapping must include :tenant_id' unless payload_mapping.key?(:tenant_id)
    error_msg << 'tenant_id_header_name is required' if tenant_id_header_name.to_s.strip.empty?
    raise ConfigurationError, "#{error_msg.join(' and ')} when validate_tenant_id is true" if error_msg.any?
  end

  return unless validate_pathname_slug?

  error_msg = []
  error_msg << 'payload_mapping must include :pathname_slugs' unless payload_mapping.key?(:pathname_slugs)
  error_msg << 'pathname_slug_pattern is required' if pathname_slug_pattern.to_s.empty?
  raise ConfigurationError, "#{error_msg.join(' and ')} when validate_pathname_slug is true" if error_msg.any?
end

#validate_pathname_slug?Boolean

Check if pathname slug validation is enabled

Returns:

  • (Boolean)

    true if pathname slug validation is enabled

Since:

  • 0.1.0



193
194
195
# File 'lib/rack_jwt_aegis/configuration.rb', line 193

def validate_pathname_slug?
  config_boolean?(validate_pathname_slug)
end

#validate_payload_mapping!Object (private)

Raises:

Since:

  • 0.1.0



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/rack_jwt_aegis/configuration.rb', line 297

def validate_payload_mapping!
  # Allow nil payload_mapping (will use defaults)
  return if payload_mapping.nil?

  raise ConfigurationError, 'payload_mapping must be a Hash' unless payload_mapping.is_a?(Hash)

  # Validate all values are symbols
  invalid_values = payload_mapping.reject { |_key, value| value.is_a?(Symbol) }
  return if invalid_values.empty?

  raise ConfigurationError, "payload_mapping values must be symbols, invalid: #{invalid_values.inspect}"

  # NOTE: We don't validate required keys because users may provide
  # partial mappings that are intended to override defaults. The payload_key method
  # handles missing keys by returning the standard key as fallback.
  # This includes RBAC keys - if :role_ids is not mapped, it falls back to 'role_ids'.
end

#validate_subdomain?Boolean

Check if subdomain validation is enabled

Returns:

  • (Boolean)

    true if subdomain validation is enabled

Since:

  • 0.1.0



187
188
189
# File 'lib/rack_jwt_aegis/configuration.rb', line 187

def validate_subdomain?
  config_boolean?(validate_subdomain)
end

#validate_tenant_id?Boolean

Check if tenant id validation is enabled

Returns:

  • (Boolean)

    true if tenant id validation is enabled

Since:

  • 0.1.0



199
200
201
# File 'lib/rack_jwt_aegis/configuration.rb', line 199

def validate_tenant_id?
  config_boolean?(validate_tenant_id)
end