verikloak-audience 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ff2e2e97325963725431051763d956fad12ae1b2ea04476d849a8f1d085e8e6
4
- data.tar.gz: c9ca99b0c0b0c0089abc3f854a8a50234000d27a93ae7092f322ac1c2283e59e
3
+ metadata.gz: f7b437ab60e6ddce3b76539ce7806633ef21509c2541d645ad1040ff0d840cd0
4
+ data.tar.gz: 3f181a7ffe9aef9973e27aee57ce14840937eb16dc7a2dcf00951b30115d367d
5
5
  SHA512:
6
- metadata.gz: 36f0c7cbddc7fcc7f2eea2a90743a5c7abd9a3d03b9f279a0bf35c99e77557af8f579e847c5169c56ecdc50794f5b99058eecc866565f03f2796e6e5a694e243
7
- data.tar.gz: 6e908e9720198cf36a40b91bd3b345c6ed622805cf7d10b53af0dd94c76a137fa94b64f66d7858aa1a7c007612445154c9c0a61a445eaa3930fd72a313b4febf
6
+ metadata.gz: 58bf73692f747dd60d6e8da0985fde737202152df279fe56d3eeecd684846e4ea3bb186583d735dc709b8a048b27a40709cb7bbd2c133e992eacadb306c22f69
7
+ data.tar.gz: 9b2d528270ebe27dc89a4c0d78f915ef43956e535f3d0cf9a40e2452e112136aaa654b31df97045d2a49f32e68b4ad51bd1217769f2cde725cd9bb790780a54e
data/CHANGELOG.md CHANGED
@@ -7,11 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.2.1] - 2025-09-22
11
+
12
+ ### Fixed
13
+ - Skip configuration validation while Rails generators run so `rails g verikloak:install` can execute before `required_aud` is configured.
14
+
15
+ ## [0.2.0] - 2025-09-21
16
+
17
+ ### Added
18
+ - README "Operational safeguards" section covering startup validation, logger precedence, and `:resource_or_aud` alignment guidance.
19
+ - YARD documentation for configuration helpers and middleware logging routines.
20
+
21
+ ### Changed
22
+ - Tightened `resource_client` inference/validation to enforce alignment with `required_aud` and infer single-entry clients automatically.
23
+ - Prefer request-scoped loggers over `Kernel#warn` when emitting audience failure messages.
24
+ - Bumped runtime dependency to `verikloak >= 0.1.5` to pick up shared logger support.
25
+ - Improved Rails Railtie test harness to mimic real initializer registration.
26
+
10
27
  ## [0.1.1] - 2025-09-20
11
28
 
12
29
  ### Changed
13
- - Documented `Configuration#safe_dup` behaviour and error handling in YARD.
14
- - Expanded error class documentation for clearer release guidance.
30
+ - Documented `Configuration#safe_dup` behaviour and tightened duplication semantics.
31
+ - Expanded error class YARD docs to clarify operational response codes.
15
32
 
16
33
  ## [0.1.0] - 2025-09-20
17
34
 
data/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # verikloak-audience
2
2
 
3
+ [![CI](https://github.com/taiyaky/verikloak-audience/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/taiyaky/verikloak-audience/actions/workflows/ci.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/verikloak-audience)](https://rubygems.org/gems/verikloak-audience)
5
+ ![Ruby Version](https://img.shields.io/badge/ruby-%3E%3D%203.1-blue)
6
+ [![Downloads](https://img.shields.io/gem/dt/verikloak-audience)](https://rubygems.org/gems/verikloak-audience)
7
+
3
8
  Rack middleware for validating the `aud` claim of Keycloak-issued tokens on top of the Verikloak stack. It ships with deploy-friendly presets that address common Keycloak patterns such as `account` co-existence and `resource_access`-driven role enforcement.
4
9
 
5
10
  For the full error behaviour (response shapes, exception classes, logging hints), see [ERRORS.md](ERRORS.md).
@@ -53,6 +58,11 @@ See [`examples/rack.ru`](examples/rack.ru) for a full Rack sample. In Rails, alw
53
58
 
54
59
  `env_claims_key` assumes the preceding `Verikloak::Middleware` populates the Rack env. If the middleware order changes, claims will be missing and the audience check will always reject.
55
60
 
61
+ ### Operational safeguards
62
+ - Middleware initialisation now fails fast when `required_aud` is empty. When Rails loads via the supplied Railtie, `Verikloak::Audience.config.validate!` runs after boot so configuration mistakes surface during startup instead of returning 403 for every request.
63
+ - When audience validation fails, the middleware consults `env['verikloak.logger']`, `env['rack.logger']`, and `env['action_dispatch.logger']` (in that order) before falling back to Ruby's `Kernel#warn`, keeping failure logs consistent with Rails and Verikloak observers.
64
+ - For the `:resource_or_aud` profile, `resource_client` must match one of the values in `required_aud`. A single-element `required_aud` automatically infers the client id, ensuring the same client identifier is shared with downstream BFF/Pundit integrations.
65
+
56
66
  ## Testing
57
67
  All pull requests and pushes are automatically tested with [RSpec](https://rspec.info/) and [RuboCop](https://rubocop.org/) via GitHub Actions.
58
68
  See the CI badge at the top for current build status.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'verikloak/audience/errors'
4
+
3
5
  module Verikloak
4
6
  module Audience
5
7
  # Configuration holder for verikloak-audience.
@@ -20,6 +22,8 @@ module Verikloak
20
22
  # Whether to log a suggestion when audience validation fails.
21
23
  # @return [Boolean]
22
24
  class Configuration
25
+ DEFAULT_RESOURCE_CLIENT = 'rails-api'
26
+
23
27
  attr_accessor :profile, :required_aud, :resource_client,
24
28
  :suggest_in_logs
25
29
  attr_reader :env_claims_key
@@ -30,7 +34,7 @@ module Verikloak
30
34
  def initialize
31
35
  @profile = :strict_single
32
36
  @required_aud = []
33
- @resource_client = 'rails-api'
37
+ @resource_client = DEFAULT_RESOURCE_CLIENT
34
38
  self.env_claims_key = 'verikloak.user'
35
39
  @suggest_in_logs = true
36
40
  end
@@ -61,6 +65,25 @@ module Verikloak
61
65
  @env_claims_key = value&.to_s
62
66
  end
63
67
 
68
+ # Validate the configuration to ensure required values are present.
69
+ #
70
+ # @return [Configuration] the validated configuration
71
+ def validate!
72
+ audiences = required_aud_list
73
+ if audiences.empty?
74
+ raise Verikloak::Audience::ConfigurationError,
75
+ 'required_aud must include at least one audience'
76
+ end
77
+
78
+ profile_name = profile
79
+ profile_name = profile_name.to_sym if profile_name.respond_to?(:to_sym)
80
+ profile_name ||= :strict_single
81
+
82
+ ensure_resource_client!(audiences) if profile_name == :resource_or_aud
83
+
84
+ self
85
+ end
86
+
64
87
  private
65
88
 
66
89
  # Attempt to duplicate a value while tolerating non-duplicable inputs.
@@ -76,6 +99,11 @@ module Verikloak
76
99
  value
77
100
  end
78
101
 
102
+ # Build a deep-ish copy of `required_aud` so that mutations on copies
103
+ # do not leak back into the original configuration instance.
104
+ #
105
+ # @param value [Array<String,Symbol>, String, Symbol, nil]
106
+ # @return [Array<String,Symbol>, String, Symbol, nil]
79
107
  def duplicate_required_aud(value)
80
108
  return if value.nil?
81
109
 
@@ -83,6 +111,45 @@ module Verikloak
83
111
 
84
112
  safe_dup(value)
85
113
  end
114
+
115
+ # Ensure that the configured `resource_client` fits the `required_aud`
116
+ # list when the :resource_or_aud profile is active. Attempts to infer
117
+ # the client id from `required_aud` when possible and raises when
118
+ # ambiguity remains.
119
+ #
120
+ # @param audiences [Array<String>] coerced required audiences
121
+ # @return [void]
122
+ def ensure_resource_client!(audiences)
123
+ client = resource_client.to_s
124
+
125
+ needs_inference = needs_resource_client_inference?(client, audiences)
126
+
127
+ if needs_inference
128
+ if audiences.one?
129
+ self.resource_client = audiences.first
130
+ client = resource_client.to_s
131
+ else
132
+ raise Verikloak::Audience::ConfigurationError,
133
+ 'resource_client must match one of required_aud when using :resource_or_aud profile'
134
+ end
135
+ end
136
+
137
+ return if audiences.include?(client)
138
+
139
+ raise Verikloak::Audience::ConfigurationError,
140
+ 'resource_client must match one of required_aud when using :resource_or_aud profile'
141
+ end
142
+
143
+ # Decide whether the resource client should be inferred from the
144
+ # required audiences based on the current client value.
145
+ #
146
+ # @param client [String]
147
+ # @param audiences [Array<String>]
148
+ # @return [Boolean]
149
+ def needs_resource_client_inference?(client, audiences)
150
+ client.empty? ||
151
+ (client == DEFAULT_RESOURCE_CLIENT && !audiences.include?(client))
152
+ end
86
153
  end
87
154
  end
88
155
  end
@@ -24,6 +24,7 @@ module Verikloak
24
24
  @app = app
25
25
  @config = Verikloak::Audience.config.dup
26
26
  apply_overrides!(opts)
27
+ @config.validate!
27
28
  end
28
29
 
29
30
  # Evaluate the request against the audience profile.
@@ -38,7 +39,8 @@ module Verikloak
38
39
  if @config.suggest_in_logs
39
40
  suggestion = Checker.suggest(claims, @config)
40
41
  aud_view = Array(claims['aud']).inspect
41
- warn("[verikloak-audience] insufficient_audience; suggestion profile=:#{suggestion} aud=#{aud_view}")
42
+ log_warning(env,
43
+ "[verikloak-audience] insufficient_audience; suggestion profile=:#{suggestion} aud=#{aud_view}")
42
44
  end
43
45
 
44
46
  body = { error: 'insufficient_audience',
@@ -51,7 +53,7 @@ module Verikloak
51
53
 
52
54
  # Apply provided options to the configuration instance.
53
55
  #
54
- # @param opts [Hash]
56
+ # @param opts [Hash] raw overrides provided to the middleware
55
57
  # @return [void]
56
58
  def apply_overrides!(opts)
57
59
  cfg = @config
@@ -67,6 +69,21 @@ module Verikloak
67
69
  cfg.public_send("#{k}=", v)
68
70
  end
69
71
  end
72
+
73
+ # Emit a warning for failed audience checks using request-scoped loggers
74
+ # when available.
75
+ #
76
+ # @param env [Hash] Rack environment
77
+ # @param message [String] warning payload
78
+ # @return [void]
79
+ def log_warning(env, message)
80
+ logger = env['verikloak.logger'] || env['rack.logger'] || env['action_dispatch.logger']
81
+ if logger.respond_to?(:warn)
82
+ logger.warn(message)
83
+ else
84
+ warn(message)
85
+ end
86
+ end
70
87
  end
71
88
  end
72
89
  end
@@ -33,6 +33,14 @@ module Verikloak
33
33
  self.class.insert_middleware(app)
34
34
  end
35
35
 
36
+ initializer 'verikloak_audience.configuration' do
37
+ config.after_initialize do
38
+ next if Verikloak::Audience::Railtie.skip_configuration_validation?
39
+
40
+ Verikloak::Audience.config.validate!
41
+ end
42
+ end
43
+
36
44
  # Performs the insertion into the middleware stack when the core
37
45
  # Verikloak middleware is available. Extracted for testability without
38
46
  # requiring a full Rails boot process.
@@ -44,6 +52,35 @@ module Verikloak
44
52
 
45
53
  app.middleware.insert_after ::Verikloak::Middleware, ::Verikloak::Audience::Middleware
46
54
  end
55
+
56
+ COMMANDS_SKIPPING_VALIDATION = %w[generate g destroy d].freeze
57
+
58
+ # Detect whether Rails is currently executing a generator-style command.
59
+ # Generators boot the application before configuration exists, so we
60
+ # temporarily skip validation to let the install task complete.
61
+ #
62
+ # @return [Boolean]
63
+ def self.skip_configuration_validation?
64
+ return false unless defined?(Rails::Generators)
65
+
66
+ command = first_rails_command
67
+ COMMANDS_SKIPPING_VALIDATION.include?(command)
68
+ end
69
+
70
+ # Capture the first non-option argument passed to the Rails CLI,
71
+ # ignoring wrapper tokens such as "rails".
72
+ #
73
+ # @return [String, nil]
74
+ def self.first_rails_command
75
+ ARGV.each do |arg|
76
+ next if arg.start_with?('-')
77
+ next if arg == 'rails'
78
+
79
+ return arg
80
+ end
81
+
82
+ nil
83
+ end
47
84
  end
48
85
  end
49
86
  end
@@ -4,6 +4,6 @@ module Verikloak
4
4
  module Audience
5
5
  # Current gem version.
6
6
  # @return [String]
7
- VERSION = '0.1.1'
7
+ VERSION = '0.2.1'
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verikloak-audience
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -35,20 +35,20 @@ dependencies:
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: 0.1.2
38
+ version: 0.2.0
39
39
  - - "<"
40
40
  - !ruby/object:Gem::Version
41
- version: '0.2'
41
+ version: 1.0.0
42
42
  type: :runtime
43
43
  prerelease: false
44
44
  version_requirements: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: 0.1.2
48
+ version: 0.2.0
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
- version: '0.2'
51
+ version: 1.0.0
52
52
  description: |
53
53
  Rack middleware that enforces audience checks with deployable profiles,
54
54
  layering on top of Verikloak token verification.
@@ -74,7 +74,7 @@ metadata:
74
74
  source_code_uri: https://github.com/taiyaky/verikloak-audience
75
75
  changelog_uri: https://github.com/taiyaky/verikloak-audience/blob/main/CHANGELOG.md
76
76
  bug_tracker_uri: https://github.com/taiyaky/verikloak-audience/issues
77
- documentation_uri: https://rubydoc.info/gems/verikloak-audience/0.1.1
77
+ documentation_uri: https://rubydoc.info/gems/verikloak-audience/0.2.1
78
78
  rubygems_mfa_required: 'true'
79
79
  rdoc_options: []
80
80
  require_paths: