verikloak-bff 1.0.0 → 1.1.0

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: 9334e103b57c76a8723b73e30fd48f24e0eeb505a6b3d113ece410025b8f0f00
4
- data.tar.gz: 9b6c0b661cc28b8061176c1c294f28d0e6577b4645bfc286db72ca952b9caf7f
3
+ metadata.gz: 7fc1c8dd0dd14c3ae72321e7265c572cd2c5437285325a5ab5ae2836205fc2e1
4
+ data.tar.gz: 51a5f52cf417750da1454bd0bf8ec8635cfa65b3df3b8ff634d7fe556b2b8498
5
5
  SHA512:
6
- metadata.gz: 4c361c0afec931b6d5cbebfd76be1064362b4fb8eb7cb61d430029c6a10b9a42575973356657152f171ec15d9c0ad291a24db08006d9eb6bd503d791cbe2844a
7
- data.tar.gz: f222b9c2718aead0b08ae8c1aaed235d62c880a046bf08c81a8f80cf9b6005435087d71262bcf53b7e115bdef0a7963c65a36a7c568c903daa2a96e91138fc29
6
+ metadata.gz: 92ff1528409857382e5530fc6de4c1f20d23deb8888a298b8f610c96cdb7ecf554545e3561ba0ffeada9001bc641fb8aea42f11b3eb595c85732136ff7e912ec
7
+ data.tar.gz: a6faf3c0282d56aa048d2176a213473d716fde00fedab97bd8d4a8fa1e4e811aea9f84d9e14ee6e9ac8a87a2f5f498cb9f1a66efe3c1bcb2882c29152b23d8f5
data/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.1.0] - 2026-07-03
11
+
12
+ Verified against verikloak 1.1.0 (core) and verikloak-rails 1.2.0.
13
+
14
+ ### Fixed
15
+ - **Empty Bearer Authorization**: `ForwardedToken.normalize_auth` now returns `nil` for a Bearer scheme without a token (e.g. `Authorization: Bearer`). Previously it returned an empty string, which caused a spurious `401 header_mismatch` when a valid forwarded token was present. `seed_authorization_if_needed` no longer re-inspects the raw `HTTP_AUTHORIZATION` value either, so a bare `Authorization: Bearer` no longer blocks seeding a valid token from `token_header_priority` headers
16
+ - **Multi-line Authorization hardening**: `ForwardedToken.normalize_auth` is anchored with `\A`/`\z` (previously `^`/`$`) and only accepts space/tab between the scheme and token, so a multi-line value cannot smuggle a `Bearer <token>` line past a non-Bearer first line
17
+ - **`peer_preference` honored in trust decisions**: `HeaderGuard` now passes the configured `peer_preference` (`:remote_then_xff` / `:xff_only`) to `ProxyTrust.trusted?`. Previously the preference only affected the `verikloak.bff.selected_peer` env hint and the trust decision always used `:remote_then_xff`, contradicting the documented behavior
18
+ - **`peer_preference` fail-safe**: an unrecognized or `nil` `peer_preference` now resolves to the safe `REMOTE_ADDR`-first path instead of the client-controlled `X-Forwarded-For`, and `HeaderGuard` raises `ConfigurationError` at startup for an unrecognized value so a typo cannot silently weaken the trust decision
19
+ - **Trusted proxy rule isolation**: a rule that raises (invalid CIDR string, failing Proc) no longer silently disables the remaining `trusted_proxies` rules; each rule is evaluated independently, and the failure is surfaced under `$DEBUG` instead of being swallowed without a trace
20
+ - **Fail-fast IP/CIDR validation**: `HeaderGuard` raises `ConfigurationError` at startup when a `trusted_proxies` string cannot be parsed as an IP or CIDR (previously only CIDR strings containing `/` were checked, so a malformed plain IP such as `10.0.0.999` booted and then silently rejected every request)
21
+
22
+ ### Changed
23
+ - Minimum `verikloak` dependency raised to `~> 1.1` (from `~> 1.0`), matching the coordinated 1.1.0 core / 1.2.0 verikloak-rails release; `verikloak-rails` 1.2.0 already requires `verikloak ~> 1.1`
24
+ - **`Verikloak::BFF::Rails::Middleware.insert_before_core` added**: inserts HeaderGuard *before* `Verikloak::Middleware`, matching the documented stack order (`[HeaderGuard] → [Verikloak::Middleware] → [App]`) and the behavior of `verikloak-rails`
25
+ - `ForwardedToken.strip_suspicious!` and `Configuration` now share the default `X-Auth-Request-*` header list via `Constants::DEFAULT_AUTH_REQUEST_HEADERS` (previously duplicated)
26
+ - `HeaderGuard` no longer writes the `Authorization` header twice when seeding from `token_header_priority`; the header is written once during request finalization
27
+
28
+ ### Deprecated
29
+ - **`Verikloak::BFF::Rails::Middleware.insert_after_core`**: deprecated in favor of `insert_before_core`. It now emits a deprecation warning and delegates to `insert_before_core`, because inserting HeaderGuard *after* the core middleware let core verification run on un-normalized tokens. The dead `auto_insert_enabled?` / `core_config` checks (which read a `Verikloak.config` that does not exist in the core gem — the real flag lives in `Verikloak::Rails.config`) were removed along the way
30
+
31
+ ### Removed
32
+ - Internal helpers `HeaderGuardSanitizer.token_tags` and `HeaderGuardSanitizer.decode_unverified` (unused since token decoding was consolidated into `RequestTokens`; use `Verikloak::BFF::JwtUtils.decode_unverified` directly if needed)
33
+ - `ProxyTrust.from_trusted_proxy?` (unused thin wrapper around `ProxyTrust.trusted?`; call `ProxyTrust.trusted?(env, trusted, :rightmost, preference: ...)` directly)
34
+
35
+ ---
36
+
10
37
  ## [1.0.0] - 2026-02-15
11
38
 
12
39
  ### Fixed
@@ -25,6 +25,7 @@
25
25
  # @return [Logger, nil] optional logger for audit tags
26
26
 
27
27
  require 'verikloak/header_sources'
28
+ require 'verikloak/bff/constants'
28
29
 
29
30
  module Verikloak
30
31
  module BFF
@@ -59,11 +60,7 @@ module Verikloak
59
60
  @logger = nil
60
61
  @log_with = nil
61
62
  self.forwarded_header_name = Verikloak::HeaderSources::DEFAULT_FORWARDED_HEADER
62
- @auth_request_headers = {
63
- email: 'HTTP_X_AUTH_REQUEST_EMAIL',
64
- user: 'HTTP_X_AUTH_REQUEST_USER',
65
- groups: 'HTTP_X_AUTH_REQUEST_GROUPS'
66
- }
63
+ @auth_request_headers = Constants::DEFAULT_AUTH_REQUEST_HEADERS.dup
67
64
  # When Authorization is empty and no chosen token exists, try these env headers (in order)
68
65
  # to seed Authorization, similar to verikloak-rails behavior. HTTP_AUTHORIZATION is always ignored as a source.
69
66
  self.token_header_priority = Verikloak::HeaderSources.default_priority(forwarded_header: @forwarded_header_name)
@@ -16,6 +16,14 @@ module Verikloak
16
16
  # Maximum length for individual log field values to prevent log flooding
17
17
  # from oversized or malicious JWT claims.
18
18
  MAX_LOG_FIELD_LENGTH = 256
19
+
20
+ # Default X-Auth-Request-* env keys, shared by {Configuration} defaults
21
+ # and {ForwardedToken.strip_suspicious!} so both stay in sync.
22
+ DEFAULT_AUTH_REQUEST_HEADERS = {
23
+ email: 'HTTP_X_AUTH_REQUEST_EMAIL',
24
+ user: 'HTTP_X_AUTH_REQUEST_USER',
25
+ groups: 'HTTP_X_AUTH_REQUEST_GROUPS'
26
+ }.freeze
19
27
  end
20
28
  end
21
29
  end
@@ -5,6 +5,7 @@
5
5
  # @see .extract
6
6
 
7
7
  require 'verikloak/header_sources'
8
+ require 'verikloak/bff/constants'
8
9
 
9
10
  module Verikloak
10
11
  module BFF
@@ -28,17 +29,20 @@ module Verikloak
28
29
  end
29
30
 
30
31
  # Only accept Bearer scheme for Authorization header.
32
+ # A scheme without a token (e.g. "Bearer" or "Bearer ") is treated as
33
+ # absent so that an empty Authorization header never shadows a valid
34
+ # forwarded token.
35
+ #
36
+ # The pattern is anchored with \A/\z (not ^/$) so a multi-line value
37
+ # cannot smuggle a "Bearer <token>" line past a non-Bearer first line,
38
+ # and only space/tab may separate the scheme from the token.
31
39
  #
32
40
  # @param raw [String, nil]
33
- # @return [String, nil] token or nil when not Bearer
41
+ # @return [String, nil] token or nil when not Bearer or token is empty
34
42
  def normalize_auth(raw)
35
43
  return nil unless raw
36
44
 
37
- token = raw.to_s.strip
38
- return ::Regexp.last_match(1) if token =~ /^Bearer\s+(.+)$/i
39
- return token[6..] if token =~ /^Bearer(?!\s)/i
40
-
41
- nil
45
+ raw.to_s.strip[/\ABearer[ \t]*(.+)\z/i, 1]
42
46
  end
43
47
 
44
48
  # Accept either bare token or Bearer for forwarded header.
@@ -105,13 +109,8 @@ module Verikloak
105
109
  # @param headers [Hash{Symbol=>String}, nil] explicit headers to strip
106
110
  # @return [void]
107
111
  def strip_suspicious!(env, headers = nil)
108
- if headers.is_a?(Hash)
109
- headers.each_value { |h| env.delete(h) }
110
- return
111
- end
112
- env.delete('HTTP_X_AUTH_REQUEST_EMAIL')
113
- env.delete('HTTP_X_AUTH_REQUEST_USER')
114
- env.delete('HTTP_X_AUTH_REQUEST_GROUPS')
112
+ headers = Constants::DEFAULT_AUTH_REQUEST_HEADERS unless headers.is_a?(Hash)
113
+ headers.each_value { |h| env.delete(h) }
115
114
  end
116
115
  end
117
116
  end
@@ -32,21 +32,6 @@ module Verikloak
32
32
 
33
33
  module_function
34
34
 
35
- # Generate sanitized token metadata suitable for structured logging without
36
- # verifying the signature.
37
- #
38
- # @param token [String, nil]
39
- # @return [Hash{Symbol=>Object}] sanitized tags keyed by JWT claim/header
40
- def token_tags(token)
41
- return {} unless token
42
-
43
- payload, header = decode_unverified(token)
44
- token_tags_from_decoded(payload, header)
45
- rescue StandardError => e
46
- warn("[verikloak-bff] token_tags failed: #{e.class}: #{e.message}") if $DEBUG
47
- {}
48
- end
49
-
50
35
  # Build sanitized log tags from pre-decoded JWT payload and header.
51
36
  # Avoids redundant decoding when the caller already has decoded data.
52
37
  #
@@ -69,15 +54,6 @@ module Verikloak
69
54
  {}
70
55
  end
71
56
 
72
- # Decode a JWT without verifying the signature while guarding against
73
- # excessively large tokens.
74
- #
75
- # @param token [String, nil]
76
- # @return [Array<Hash>] payload and header hashes
77
- def decode_unverified(token)
78
- Verikloak::BFF::JwtUtils.decode_unverified(token)
79
- end
80
-
81
57
  # Remove unsafe characters from a structured logging payload.
82
58
  #
83
59
  # @param payload [Hash]
@@ -212,6 +188,9 @@ module Verikloak
212
188
  # Error raised when trusted_proxies is not configured and disabled is not explicitly set.
213
189
  class ConfigurationError < StandardError; end
214
190
 
191
+ # Recognized values for the `peer_preference` setting.
192
+ VALID_PEER_PREFERENCES = %i[remote_then_xff xff_only].freeze
193
+
215
194
  RequestTokens = Struct.new(:auth, :forwarded, :chosen, :decoded_payload, :decoded_header)
216
195
 
217
196
  # Accept both Rack 2 and Rack 3 builder call styles:
@@ -277,8 +256,10 @@ module Verikloak
277
256
 
278
257
  private
279
258
 
280
- # Validate that required configuration is present.
281
- # Raises ConfigurationError if trusted_proxies is not configured and disabled is false.
259
+ # Validate that required configuration is present and well-formed.
260
+ # Raises ConfigurationError if trusted_proxies is not configured and disabled is false,
261
+ # when an IP/CIDR rule cannot be parsed, or when peer_preference is unrecognized
262
+ # (fail-fast instead of silently rejecting every request at runtime).
282
263
  #
283
264
  # @raise [ConfigurationError]
284
265
  # @return [void]
@@ -286,12 +267,53 @@ module Verikloak
286
267
  return if @config.disabled
287
268
 
288
269
  proxies = @config.trusted_proxies
289
- return unless proxies.nil? || proxies.empty?
270
+ if proxies.nil? || proxies.empty?
271
+ raise ConfigurationError,
272
+ 'trusted_proxies must be configured for Verikloak::BFF::HeaderGuard. ' \
273
+ 'Set trusted_proxies to an array of allowed proxy addresses/CIDRs, ' \
274
+ 'or set disabled: true to explicitly skip BFF protection.'
275
+ end
276
+
277
+ validate_trusted_proxy_rules!(proxies)
278
+ validate_peer_preference!
279
+ end
280
+
281
+ # Fail fast on unparsable IP/CIDR strings in trusted_proxies. Both plain
282
+ # IPs (exact-match rules) and CIDRs are checked, since a malformed plain
283
+ # IP would otherwise boot successfully and silently reject every request.
284
+ # Parsing is delegated to ProxyTrust.ip_or_nil so validation and runtime
285
+ # matching stay in lockstep.
286
+ #
287
+ # @param proxies [Array<String, Regexp, Proc>]
288
+ # @raise [ConfigurationError]
289
+ # @return [void]
290
+ def validate_trusted_proxy_rules!(proxies)
291
+ proxies.each do |rule|
292
+ next unless rule.is_a?(String)
293
+ next if ProxyTrust.ip_or_nil(rule)
294
+
295
+ kind = rule.include?('/') ? 'CIDR' : 'IP'
296
+ raise ConfigurationError,
297
+ "invalid #{kind} rule in trusted_proxies: #{rule.inspect}. " \
298
+ 'Fix the rule so that proxy trust checks can match it.'
299
+ end
300
+ end
301
+
302
+ # Fail fast on an unrecognized peer_preference so a typo cannot silently
303
+ # change which peer the trust decision is based on (ProxyTrust treats any
304
+ # non-:xff_only value as the safe REMOTE_ADDR-first path, which would mask
305
+ # an intended :xff_only that was misspelled).
306
+ #
307
+ # @raise [ConfigurationError]
308
+ # @return [void]
309
+ def validate_peer_preference!
310
+ pref = @config.peer_preference
311
+ return if pref.nil?
312
+ return if VALID_PEER_PREFERENCES.include?(pref.to_s.to_sym)
290
313
 
291
314
  raise ConfigurationError,
292
- 'trusted_proxies must be configured for Verikloak::BFF::HeaderGuard. ' \
293
- 'Set trusted_proxies to an array of allowed proxy addresses/CIDRs, ' \
294
- 'or set disabled: true to explicitly skip BFF protection.'
315
+ "invalid peer_preference: #{pref.inspect}. " \
316
+ 'Expected :remote_then_xff or :xff_only (or nil for the default).'
295
317
  end
296
318
 
297
319
  # Build token state by extracting, validating, and selecting the active token.
@@ -361,11 +383,14 @@ module Verikloak
361
383
  end
362
384
 
363
385
  # Raise when the request did not come through a trusted proxy.
386
+ # Honors the configured peer_preference (:remote_then_xff or :xff_only)
387
+ # when selecting the peer used for the trust decision.
364
388
  #
365
389
  # @param env [Hash]
366
390
  # @raise [UntrustedProxyError]
367
391
  def ensure_trusted_proxy!(env)
368
- return if ProxyTrust.trusted?(env, @config.trusted_proxies, @config.xff_strategy)
392
+ return if ProxyTrust.trusted?(env, @config.trusted_proxies, @config.xff_strategy,
393
+ preference: @config.peer_preference)
369
394
 
370
395
  raise UntrustedProxyError
371
396
  end
@@ -444,35 +469,35 @@ module Verikloak
444
469
  end
445
470
 
446
471
  # Resolve the first env header from which to source a bearer token.
447
- # Forwarded is considered only when the peer is trusted; HTTP_AUTHORIZATION is never a source.
472
+ # HTTP_AUTHORIZATION is never a source. Proxy trust is already guaranteed
473
+ # by stage 1 ({#ensure_trusted_proxy!}) before this is reached.
448
474
  #
449
475
  # @param env [Hash]
450
476
  # @return [String, nil]
451
477
  def resolve_first_token_header(env)
452
- candidates = Array(@config.token_header_priority).dup
453
- candidates -= [Verikloak::HeaderSources::AUTHORIZATION_HEADER]
454
- fwd_key = @config.forwarded_header_name || Verikloak::HeaderSources::DEFAULT_FORWARDED_HEADER
455
- if candidates.include?(fwd_key) && !ProxyTrust.from_trusted_proxy?(env, @config.trusted_proxies)
456
- candidates -= [fwd_key]
457
- end
478
+ candidates = Array(@config.token_header_priority) - [Verikloak::HeaderSources::AUTHORIZATION_HEADER]
458
479
  candidates.find { |k| (v = env[k]) && !v.to_s.empty? }
459
480
  end
460
481
 
461
- # Seed Authorization from priority headers if nothing chosen and empty Authorization.
482
+ # Seed the chosen token from priority headers when no usable token was
483
+ # chosen. `chosen` is nil only when Authorization held no valid Bearer
484
+ # token (normalize_auth already treats a bare "Bearer" as absent) and no
485
+ # forwarded token was present, so inspecting the raw Authorization header
486
+ # here would re-introduce the empty-Bearer shadowing bug. The Authorization
487
+ # header itself is written once later by {#normalize_authorization!} from
488
+ # the chosen token.
462
489
  #
463
490
  # @param env [Hash]
464
491
  # @param chosen [String, nil]
465
492
  # @return [String, nil] possibly updated chosen token
466
493
  def seed_authorization_if_needed(env, chosen)
467
- return chosen unless chosen.nil? && env['HTTP_AUTHORIZATION'].to_s.empty?
494
+ return chosen unless chosen.nil?
468
495
  return chosen unless Array(@config.token_header_priority).any?
469
496
 
470
497
  seeded = resolve_first_token_header(env)
471
- if seeded
472
- ForwardedToken.set_authorization!(env, env[seeded])
473
- return ForwardedToken.normalize_forwarded(env[seeded]) || env[seeded].to_s
474
- end
475
- chosen
498
+ return chosen unless seeded
499
+
500
+ ForwardedToken.normalize_forwarded(env[seeded]) || env[seeded].to_s
476
501
  end
477
502
 
478
503
  # Expose hints to downstream middleware or apps.
@@ -19,11 +19,13 @@ module Verikloak
19
19
  # - Regexp: matched against the selected peer IP
20
20
  # - Proc: called as `->(ip, env) { ... }` and returns truthy when trusted
21
21
  # @param strategy [Symbol, String] `:rightmost` (default) or `:leftmost` for XFF parsing
22
+ # @param preference [Symbol] `:remote_then_xff` (default) prefers REMOTE_ADDR,
23
+ # `:xff_only` selects the peer from X-Forwarded-For first
22
24
  # @return [Boolean] true if the selected peer is trusted
23
25
  # @example CIDR + Regex allowlist
24
26
  # ProxyTrust.trusted?(env, ["10.0.0.0/8", /^192\.168\./], :rightmost)
25
- def trusted?(env, trusted, strategy = :rightmost)
26
- remote = resolve_peer(env, :remote_then_xff, strategy)
27
+ def trusted?(env, trusted, strategy = :rightmost, preference: :remote_then_xff)
28
+ remote = resolve_peer(env, preference, strategy)
27
29
  trusted_remote?(remote, trusted, env)
28
30
  end
29
31
 
@@ -68,6 +70,9 @@ module Verikloak
68
70
  end
69
71
 
70
72
  # Check whether a single rule trusts the selected remote.
73
+ # A rule that raises (e.g. an invalid CIDR string or a failing Proc) is
74
+ # treated as non-matching so that one bad rule cannot disable the rest
75
+ # of the allowlist.
71
76
  #
72
77
  # @param rule [String, Regexp, Proc]
73
78
  # @param remote [String]
@@ -90,31 +95,33 @@ module Verikloak
90
95
  else
91
96
  false
92
97
  end
93
- end
94
-
95
- # Determine if the request originates from a trusted proxy subnet.
96
- # Rails-aligned behavior: prefer REMOTE_ADDR, fallback to nearest (rightmost) X-Forwarded-For.
97
- #
98
- # @param env [Hash]
99
- # @param trusted [Array<String, Regexp, Proc>, nil]
100
- # @return [Boolean]
101
- def self.from_trusted_proxy?(env, trusted)
102
- trusted?(env, trusted, :rightmost)
98
+ rescue StandardError => e
99
+ # Isolate a single failing rule without disabling the rest of the
100
+ # allowlist, but surface it under $DEBUG so a broken Proc/CIDR rule is
101
+ # observable instead of silently denying trust.
102
+ warn("[verikloak-bff] trusted_proxies rule raised and was skipped: #{e.class}: #{e.message}") if $DEBUG
103
+ false
103
104
  end
104
105
 
105
106
  # Resolve the peer value based on preference and strategy.
106
107
  #
108
+ # Only `:xff_only` selects the peer from X-Forwarded-For first. Every other
109
+ # value — the `:remote_then_xff` default, `nil`, or an unrecognized/typo'd
110
+ # preference — falls back to the safe REMOTE_ADDR-first path so a
111
+ # misconfiguration can never switch the trust decision onto the
112
+ # client-controlled X-Forwarded-For header.
113
+ #
107
114
  # @param env [Hash]
108
115
  # @param preference [Symbol]
109
116
  # @param strategy [Symbol]
110
117
  # @return [String, nil]
111
118
  def resolve_peer(env, preference, strategy)
112
- case preference.to_s.to_sym
113
- when :remote_then_xff
114
- remote = (env['REMOTE_ADDR'] || '').to_s.strip
115
- return remote unless remote.empty?
116
- # Fall back to X-Forwarded-For when REMOTE_ADDR is empty
117
- end
119
+ return extract_peer_ip(env, strategy) if preference.to_s.to_sym == :xff_only
120
+
121
+ remote = (env['REMOTE_ADDR'] || '').to_s.strip
122
+ return remote unless remote.empty?
123
+
124
+ # Fall back to X-Forwarded-For when REMOTE_ADDR is empty
118
125
  extract_peer_ip(env, strategy)
119
126
  end
120
127
 
@@ -6,9 +6,14 @@ module Verikloak
6
6
  module Rails
7
7
  # Middleware management utilities for Rails applications.
8
8
  #
9
- # This module focuses on inserting the HeaderGuard middleware right after
10
- # the core Verikloak middleware while gracefully handling stacks that do
11
- # not contain the core component.
9
+ # This module focuses on inserting the HeaderGuard middleware right before
10
+ # the core Verikloak middleware (so tokens are normalized before core
11
+ # verification) while gracefully handling stacks that do not contain the
12
+ # core component.
13
+ #
14
+ # NOTE: When `verikloak-rails` is installed, middleware insertion happens
15
+ # automatically via its railtie — these helpers are only needed for manual
16
+ # (non verikloak-rails) setups.
12
17
  module Middleware
13
18
  module_function
14
19
 
@@ -17,12 +22,17 @@ module Verikloak
17
22
  SKIP_MESSAGE = <<~MSG.chomp.freeze
18
23
  [verikloak-bff] Skipping Verikloak::BFF::HeaderGuard insertion because Verikloak::Middleware is not present. Configure verikloak-rails discovery settings and restart once core verification is enabled.
19
24
  MSG
25
+ DEPRECATION_MESSAGE = <<~MSG.chomp.freeze
26
+ [verikloak-bff] Verikloak::BFF::Rails::Middleware.insert_after_core is deprecated and will be removed in a future release. Use insert_before_core instead — HeaderGuard must run before Verikloak::Middleware so that tokens are normalized prior to verification. insert_after_core now delegates to insert_before_core.
27
+ MSG
20
28
 
21
- # Inserts Verikloak::BFF::HeaderGuard middleware after Verikloak::Middleware
29
+ # Inserts Verikloak::BFF::HeaderGuard middleware before Verikloak::Middleware
22
30
  #
23
31
  # Attempts to insert the HeaderGuard middleware into the Rails middleware stack
24
- # after the core Verikloak::Middleware. If the core middleware is not present,
25
- # logs a warning and gracefully skips the insertion.
32
+ # before the core Verikloak::Middleware, matching the documented stack order:
33
+ # [Verikloak::BFF::HeaderGuard] [Verikloak::Middleware] [Your App]
34
+ # If the core middleware is not present, logs a warning and gracefully skips
35
+ # the insertion.
26
36
  #
27
37
  # @param stack [ActionDispatch::MiddlewareStack] Rails middleware stack
28
38
  # @param logger [Logger, nil] Optional logger for warning messages
@@ -30,13 +40,11 @@ module Verikloak
30
40
  # @raise [RuntimeError] Re-raises non-middleware-related runtime errors
31
41
  #
32
42
  # @example Inserting middleware in Rails configuration
33
- # Verikloak::BFF::Rails::Middleware.insert_after_core(
43
+ # Verikloak::BFF::Rails::Middleware.insert_before_core(
34
44
  # Rails.application.config.middleware,
35
45
  # logger: Rails.logger
36
46
  # )
37
- def insert_after_core(stack, logger: nil)
38
- return false unless auto_insert_enabled?
39
-
47
+ def insert_before_core(stack, logger: nil)
40
48
  core = core_middleware
41
49
  header_guard = header_guard_middleware
42
50
 
@@ -45,7 +53,7 @@ module Verikloak
45
53
  return false
46
54
  end
47
55
 
48
- stack.insert_after(core, header_guard)
56
+ stack.insert_before(core, header_guard)
49
57
  true
50
58
  rescue RuntimeError => e
51
59
  raise unless missing_core?(e)
@@ -54,35 +62,18 @@ module Verikloak
54
62
  false
55
63
  end
56
64
 
57
- # Determine whether automatic insertion is enabled via Verikloak core configuration.
58
- #
59
- # When the core gem exposes +auto_insert_bff_header_guard+, respect that flag so
60
- # consumers can opt out of automatic middleware wiring without triggering warnings.
61
- # Any failures while reading configuration default to enabling insertion in order
62
- # to preserve the previous behavior.
65
+ # @deprecated Use {.insert_before_core} instead. The previous behavior
66
+ # (inserting HeaderGuard *after* the core middleware) contradicted the
67
+ # documented stack order and let core verification see un-normalized
68
+ # tokens. This method now delegates to {.insert_before_core} and emits
69
+ # a deprecation warning.
63
70
  #
64
- # @return [Boolean]
65
- def auto_insert_enabled?
66
- config = core_config
67
- return true unless config
68
-
69
- return config.auto_insert_bff_header_guard if config.respond_to?(:auto_insert_bff_header_guard)
70
-
71
- true
72
- rescue StandardError
73
- true
74
- end
75
-
76
- # Returns the Verikloak configuration object when available.
77
- #
78
- # @return [Object, nil]
79
- def core_config
80
- return nil unless defined?(::Verikloak)
81
- return nil unless ::Verikloak.respond_to?(:config)
82
-
83
- ::Verikloak.config
84
- rescue StandardError
85
- nil
71
+ # @param stack [ActionDispatch::MiddlewareStack] Rails middleware stack
72
+ # @param logger [Logger, nil] Optional logger for warning messages
73
+ # @return [Boolean] true if insertion succeeded, false if skipped
74
+ def insert_after_core(stack, logger: nil)
75
+ logger ? logger.warn(DEPRECATION_MESSAGE) : warn(DEPRECATION_MESSAGE)
76
+ insert_before_core(stack, logger: logger)
86
77
  end
87
78
 
88
79
  # Detect whether the Verikloak core middleware is already present in the stack.
@@ -132,17 +123,10 @@ module Verikloak
132
123
  # Checks if the error indicates missing core Verikloak middleware
133
124
  #
134
125
  # Examines a RuntimeError to determine if it was caused by attempting
135
- # to insert middleware after a non-existent Verikloak::Middleware.
126
+ # to insert middleware relative to a non-existent Verikloak::Middleware.
136
127
  #
137
128
  # @param error [RuntimeError] The error to examine
138
129
  # @return [Boolean] true if error indicates missing Verikloak::Middleware
139
- #
140
- # @example Checking for missing middleware error
141
- # begin
142
- # stack.insert_after(::Verikloak::Middleware, SomeMiddleware)
143
- # rescue RuntimeError => e
144
- # puts "Missing core!" if missing_core?(e)
145
- # end
146
130
  def missing_core?(error)
147
131
  error.message.include?('No such middleware') &&
148
132
  error.message.include?(CORE_NAME)
@@ -155,12 +139,6 @@ module Verikloak
155
139
  # Uses the provided logger if available, otherwise falls back to warn().
156
140
  #
157
141
  # @param logger [Logger, nil] Optional logger instance for structured logging
158
- #
159
- # @example Logging with Rails logger
160
- # log_skip(Rails.logger)
161
- #
162
- # @example Logging without logger (uses warn)
163
- # log_skip(nil)
164
142
  def log_skip(logger)
165
143
  logger ? logger.warn(SKIP_MESSAGE) : warn(SKIP_MESSAGE)
166
144
  end
@@ -5,6 +5,6 @@
5
5
  # @return [String]
6
6
  module Verikloak
7
7
  module BFF
8
- VERSION = '1.0.0'
8
+ VERSION = '1.1.0'
9
9
  end
10
10
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Helpers shared across verikloak middleware for normalizing Rack env header
4
- # names and token source priority lists. Extracted to allow other gems (such as
5
- # verikloak-rails) to consume the same normalization logic.
4
+ # names and token source priority lists.
5
+ #
6
+ # NOTE: This file lives under the shared `Verikloak::` namespace but is
7
+ # currently shipped by verikloak-bff only. Moving it into the core `verikloak`
8
+ # gem is planned so that sibling gems can share it without load-path conflicts.
6
9
  module Verikloak
7
10
  # Provides normalization helpers for Rack env header keys and token priority lists.
8
11
  module HeaderSources
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verikloak-bff
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -55,14 +55,14 @@ dependencies:
55
55
  requirements:
56
56
  - - "~>"
57
57
  - !ruby/object:Gem::Version
58
- version: '1.0'
58
+ version: '1.1'
59
59
  type: :runtime
60
60
  prerelease: false
61
61
  version_requirements: !ruby/object:Gem::Requirement
62
62
  requirements:
63
63
  - - "~>"
64
64
  - !ruby/object:Gem::Version
65
- version: '1.0'
65
+ version: '1.1'
66
66
  description: Framework-agnostic Rack middleware that normalizes forwarded tokens,
67
67
  enforces trust boundaries, and checks header/claims consistency before verikloak.
68
68
  executables: []
@@ -95,7 +95,7 @@ metadata:
95
95
  source_code_uri: https://github.com/taiyaky/verikloak-bff
96
96
  changelog_uri: https://github.com/taiyaky/verikloak-bff/blob/main/CHANGELOG.md
97
97
  bug_tracker_uri: https://github.com/taiyaky/verikloak-bff/issues
98
- documentation_uri: https://rubydoc.info/gems/verikloak-bff/1.0.0
98
+ documentation_uri: https://rubydoc.info/gems/verikloak-bff/1.1.0
99
99
  rubygems_mfa_required: 'true'
100
100
  rdoc_options: []
101
101
  require_paths: