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,
  cache_store: :redis,
  cache_write_enabled: true,
  skip_paths: ['/health', '/api/public/*'],
  debug_mode: Rails.env.development?
)

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

  • :cache_store (Symbol)

    cache adapter type

  • :cache_options (Hash)

    cache configuration options

  • :cache_write_enabled (Boolean) — default: false

    enable cache writing

  • :user_permissions_ttl (Integer) — default: 1800

    user permissions cache TTL in seconds

  • :debug_mode (Boolean) — default: false

    enable debug logging

Raises:

Since:

  • 0.1.0



178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/rack_jwt_aegis/configuration.rb', line 178

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

#cache_optionsHash

Options passed to the cache store adapter

Returns:

  • (Hash)

    cache store configuration options

Since:

  • 0.1.0



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

def cache_options
  @cache_options
end

#cache_storeSymbol

The primary cache store adapter type

Returns:

  • (Symbol)

    the cache store type (:memory, :redis, :memcached, :solid_cache)

Since:

  • 0.1.0



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

def cache_store
  @cache_store
end

#cache_write_enabledBoolean

Whether the middleware can write to cache stores

Returns:

  • (Boolean)

    true if cache writing is enabled

Since:

  • 0.1.0



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

def cache_write_enabled
  @cache_write_enabled
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



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

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



156
157
158
# File 'lib/rack_jwt_aegis/configuration.rb', line 156

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



148
149
150
# File 'lib/rack_jwt_aegis/configuration.rb', line 148

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



38
39
40
# File 'lib/rack_jwt_aegis/configuration.rb', line 38

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



33
34
35
# File 'lib/rack_jwt_aegis/configuration.rb', line 33

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



70
71
72
# File 'lib/rack_jwt_aegis/configuration.rb', line 70

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



76
77
78
# File 'lib/rack_jwt_aegis/configuration.rb', line 76

def payload_mapping
  @payload_mapping
end

#permission_cache_optionsHash

Options for the permission cache store

Returns:

  • (Hash)

    permission cache configuration options

Since:

  • 0.1.0



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

def permission_cache_options
  @permission_cache_options
end

#permission_cache_storeSymbol

The permission cache store adapter type

Returns:

  • (Symbol)

    the permission cache store type

Since:

  • 0.1.0



114
115
116
# File 'lib/rack_jwt_aegis/configuration.rb', line 114

def permission_cache_store
  @permission_cache_store
end

#rbac_cache_optionsHash

Options for the RBAC cache store

Returns:

  • (Hash)

    RBAC cache configuration options

Since:

  • 0.1.0



110
111
112
# File 'lib/rack_jwt_aegis/configuration.rb', line 110

def rbac_cache_options
  @rbac_cache_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



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

def rbac_cache_store
  @rbac_cache_store
end

#rbac_enabledBoolean

Whether RBAC (Role-Based Access Control) is enabled

Returns:

  • (Boolean)

    true if RBAC is enabled

Since:

  • 0.1.0



58
59
60
# File 'lib/rack_jwt_aegis/configuration.rb', line 58

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



86
87
88
# File 'lib/rack_jwt_aegis/configuration.rb', line 86

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



66
67
68
# File 'lib/rack_jwt_aegis/configuration.rb', line 66

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



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

def unauthorized_response
  @unauthorized_response
end

#user_permissions_ttlInteger

Time-to-live for user permissions cache in seconds

Returns:

  • (Integer)

    TTL in seconds (default: 1800 - 30 minutes)

Since:

  • 0.1.0



122
123
124
# File 'lib/rack_jwt_aegis/configuration.rb', line 122

def user_permissions_ttl
  @user_permissions_ttl
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



50
51
52
# File 'lib/rack_jwt_aegis/configuration.rb', line 50

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



46
47
48
# File 'lib/rack_jwt_aegis/configuration.rb', line 46

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



54
55
56
# File 'lib/rack_jwt_aegis/configuration.rb', line 54

def validate_tenant_id
  @validate_tenant_id
end

Instance Method Details

#cache_write_enabled?Boolean

Check if cache write access is enabled

Returns:

  • (Boolean)

    true if cache writing is enabled

Since:

  • 0.1.0



225
226
227
# File 'lib/rack_jwt_aegis/configuration.rb', line 225

def cache_write_enabled?
  config_boolean?(cache_write_enabled)
end

#config_boolean?(value) ⇒ Boolean (private)

Convert various falsy/truthy values to proper boolean for configuration

Returns:

  • (Boolean)

Since:

  • 0.1.0



260
261
262
263
264
265
266
267
268
# File 'lib/rack_jwt_aegis/configuration.rb', line 260

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



219
220
221
# File 'lib/rack_jwt_aegis/configuration.rb', line 219

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



253
254
255
# File 'lib/rack_jwt_aegis/configuration.rb', line 253

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



195
196
197
# File 'lib/rack_jwt_aegis/configuration.rb', line 195

def rbac_enabled?
  config_boolean?(rbac_enabled)
end

#set_defaultsObject (private)

Since:

  • 0.1.0



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/rack_jwt_aegis/configuration.rb', line 270

def set_defaults
  @jwt_algorithm = 'HS256'
  @validate_subdomain = false
  @validate_pathname_slug = false
  @validate_tenant_id = false
  @rbac_enabled = false
  @tenant_id_header_name = 'X-Tenant-Id'
  @pathname_slug_pattern = %r{^/api/v1/([^/]+)/}
  @skip_paths = []
  @cache_write_enabled = false
  @user_permissions_ttl = 1800 # 30 minutes default
  @debug_mode = false
  @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' }
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



232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/rack_jwt_aegis/configuration.rb', line 232

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



293
294
295
296
297
298
# File 'lib/rack_jwt_aegis/configuration.rb', line 293

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

#validate_cache_settings!Object (private)

Since:

  • 0.1.0



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/rack_jwt_aegis/configuration.rb', line 327

def validate_cache_settings!
  return unless rbac_enabled?

  # Validate cache store configuration
  if cache_store && !cache_write_enabled?
    # Zero trust mode - separate caches required
    if rbac_cache_store.nil?
      raise ConfigurationError, 'rbac_cache_store is required when cache_write_enabled is false'
    end

    if permission_cache_store.nil?
      @permission_cache_store = :memory # Default fallback
    end
  elsif cache_store.nil? && rbac_cache_store.nil?
    # Both cache stores are missing - at least one is required for RBAC
    raise ConfigurationError, 'cache_store or rbac_cache_store is required when RBAC is enabled'
  end

  # Set default fallback for permission_cache_store when rbac_cache_store is provided
  return unless !rbac_cache_store.nil? && permission_cache_store.nil?

  @permission_cache_store = :memory # Default fallback
end

#validate_jwt_settings!Object (private)

Raises:

Since:

  • 0.1.0



300
301
302
303
304
305
306
307
# File 'lib/rack_jwt_aegis/configuration.rb', line 300

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



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/rack_jwt_aegis/configuration.rb', line 351

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



207
208
209
# File 'lib/rack_jwt_aegis/configuration.rb', line 207

def validate_pathname_slug?
  config_boolean?(validate_pathname_slug)
end

#validate_payload_mapping!Object (private)

Raises:

Since:

  • 0.1.0



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/rack_jwt_aegis/configuration.rb', line 309

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



201
202
203
# File 'lib/rack_jwt_aegis/configuration.rb', line 201

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



213
214
215
# File 'lib/rack_jwt_aegis/configuration.rb', line 213

def validate_tenant_id?
  config_boolean?(validate_tenant_id)
end