verikloak-rails 0.1.0 → 0.2.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: d258ce7b00c8aed380623cc36b26669d86cf80c3c06e748bc677c4f8af93a4f1
4
- data.tar.gz: 874e2f3c8ed2eb372d7fd4238639f4fc16e9cdd0ee6a3de7dadc57e24bbcf629
3
+ metadata.gz: dda87b3f2293f4c0cf05710290a503fd5c15712758f685987385d1b0677938cc
4
+ data.tar.gz: a299ad22f31b4871fde437e026557b106134e32dece4e7cb45be4073d0269a48
5
5
  SHA512:
6
- metadata.gz: 3e035ee3bfa25ad7c9e9eb36d1279f2ea265445c60270aa2d8a8d3fcce1757cad6003892715a1faaec22cb62148bda295522ba6c12cc3bfa7858f3d2100182eb
7
- data.tar.gz: c94c073924c9ed1530978aadd7abd84855e7478350572ab70338b9e8cc0a8dd7b739865517dd488b347d87a23739580b09beee206bc8aeeda4afd40d0e5ab13e
6
+ metadata.gz: '0149e3053f208dd90c26ec07a960ff14969cdcda74400403f4ab02376110b31624189b6612bf2d6d3d7857c15d1c2b3190da9b472c487d9a3866eaf4de3aa8f7'
7
+ data.tar.gz: 2fa3507b18f362cb3523332256a0608bd95564e0a0e0dfb4a17b97b9fafbdb1ddad31fa34bbe8788d52546e1f22dd43b97174ce1474c24385838fef743f5b8ae
data/CHANGELOG.md CHANGED
@@ -7,22 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.2.0] - 2025-09-14
11
+
12
+ ### Breaking
13
+ - Extracted BFF-related functionality into a separate gem, "verikloak-bff". This gem no longer ships BFF-specific middleware or configuration
14
+
15
+ ### Removed
16
+ - Middleware `Verikloak::Rails::MiddlewareIntegration::ForwardedAccessToken`
17
+ - Configuration keys: `config.verikloak.trust_forwarded_access_token`, `config.verikloak.trusted_proxy_subnets`, `config.verikloak.token_header_priority`
18
+ - Corresponding entries in the installer initializer template
19
+ - BFF-related sections in README and related unit/integration specs
20
+
21
+ ### Migration
22
+ 1. Add `gem 'verikloak-bff'` to your application Gemfile and bundle
23
+ 2. Create an initializer (e.g., `config/initializers/verikloak_bff.rb`) and configure `Verikloak::BFF` (e.g., `trusted_proxies`, header/claims consistency)
24
+ 3. Insert the BFF middleware before the core `Verikloak::Middleware`:
25
+ ```ruby
26
+ config.middleware.insert_before Verikloak::Middleware, Verikloak::BFF::HeaderGuard
27
+ ```
28
+ 4. Remove any old `config.verikloak.*` BFF options from your app config; they are no longer used by this gem
29
+
30
+ Reference: verikloak-bff https://github.com/taiyaky/verikloak-bff (Rails guide: `docs/rails.md`)
31
+
32
+ ## [0.1.1] - 2025-09-13
33
+
34
+ ### Fixed
35
+ - ForwardedAccessToken: fix `ensure_bearer` accepting malformed values (e.g., `BearerXYZ`)
36
+
37
+ ### Changed
38
+ - Strengthen Bearer scheme normalization to always produce `Bearer <token>`
39
+ - Detect scheme case-insensitively
40
+ - Collapse tabs/multiple spaces after the scheme to a single space
41
+ - Normalize missing-space form `BearerXYZ` to `Bearer XYZ`
42
+ - Add/update middleware specs to cover the above normalization
43
+
10
44
  ## [0.1.0] - 2025-09-07
11
45
 
12
46
  ### Added
13
- - Initial release of `verikloak-rails` (Rails integration for Verikloak).
14
- - Railtie auto-wiring via `config.verikloak.*` and installer generator `rails g verikloak:install`.
47
+ - Initial release of `verikloak-rails` (Rails integration for Verikloak)
48
+ - Railtie auto-wiring via `config.verikloak.*` and installer generator `rails g verikloak:install`
15
49
  - Controller concern with authentication helpers:
16
50
  - `before_action :authenticate_user!`
17
51
  - `current_user_claims`, `current_subject`, `current_token`, `authenticated?`, `with_required_audience!`
18
52
  - Rack middleware integration:
19
- - Auto-inserts `Verikloak::Rails::MiddlewareIntegration::ForwardedAccessToken` and base `Verikloak::Middleware`.
20
- - Optional BFF header promotion from `X-Forwarded-Access-Token` to `Authorization`, gated by `trust_forwarded_access_token` and `trusted_proxy_subnets` (never overwrites existing `Authorization`).
21
- - Token sourcing priority configurable via `token_header_priority`.
53
+ - Auto-inserts `Verikloak::Rails::MiddlewareIntegration::ForwardedAccessToken` and base `Verikloak::Middleware`
54
+ - Optional BFF header promotion from `X-Forwarded-Access-Token` to `Authorization`, gated by `trust_forwarded_access_token` and `trusted_proxy_subnets` (never overwrites existing `Authorization`)
55
+ - Token sourcing priority configurable via `token_header_priority`
22
56
  - Consistent JSON error responses and statuses:
23
- - 401/403/503 standardized; `WWW-Authenticate` header on 401.
24
- - Optional global 500 JSON via `config.verikloak.render_500_json`.
25
- - Optional Pundit integration: rescue `Pundit::NotAuthorizedError` to 403 JSON (`config.verikloak.rescue_pundit`).
26
- - Request logging tags (`:request_id`, `:sub`) via `config.verikloak.logger_tags`.
27
- - Configurable initializer: `discovery_url`, `audience`, `issuer`, `leeway`, `skip_paths`, `trust_forwarded_access_token`, `trusted_proxy_subnets`, `auto_include_controller`, `error_renderer`, and more.
28
- - Compatibility: Ruby >= 3.1, Rails 6.1–8.x; depends on `verikloak` >= 0.1.2, < 0.2.
57
+ - 401/403/503 standardized; `WWW-Authenticate` header on 401
58
+ - Optional global 500 JSON via `config.verikloak.render_500_json`
59
+ - Optional Pundit integration: rescue `Pundit::NotAuthorizedError` to 403 JSON (`config.verikloak.rescue_pundit`)
60
+ - Request logging tags (`:request_id`, `:sub`) via `config.verikloak.logger_tags`
61
+ - Configurable initializer: `discovery_url`, `audience`, `issuer`, `leeway`, `skip_paths`, `trust_forwarded_access_token`, `trusted_proxy_subnets`, `auto_include_controller`, `error_renderer`, and more
62
+ - Compatibility: Ruby >= 3.1, Rails 6.1–8.x; depends on `verikloak` >= 0.1.2, < 0.2
data/README.md CHANGED
@@ -82,48 +82,19 @@ end
82
82
  ```
83
83
 
84
84
  ## Middleware
85
- ### Inserted Middlewares
85
+ ### Inserted Middleware
86
86
  | Component | Inserted after | Purpose |
87
87
  | --- | --- | --- |
88
- | `Verikloak::Rails::MiddlewareIntegration::ForwardedAccessToken` | `Rails::Rack::Logger` | Promote trusted `X-Forwarded-Access-Token` to `Authorization` and, when `Authorization` is empty, set it from a prioritized list of headers |
89
- | `Verikloak::Middleware` | `ForwardedAccessToken` | Validate Bearer JWT (OIDC discovery + JWKS), set `verikloak.user`/`verikloak.token`, and honor `skip_paths` |
88
+ | `Verikloak::Middleware` | `Rails::Rack::Logger` | Validate Bearer JWT (OIDC discovery + JWKS), set `verikloak.user`/`verikloak.token`, and honor `skip_paths` |
90
89
 
91
- ### Header Sources and Trust
92
- - Never overwrites an existing `Authorization` header.
93
- - Considers `X-Forwarded-Access-Token` only when both are true: `config.verikloak.trust_forwarded_access_token` is enabled and the direct peer IP is within `config.verikloak.trusted_proxy_subnets`.
94
- - `config.verikloak.token_header_priority` decides which env header can seed `Authorization` when it is empty. Note: `HTTP_AUTHORIZATION` is ignored as a source (it is the target header); include other headers if you need additional sources. Forwarded headers are skipped if not trusted.
95
- - Direct peer detection prefers `REMOTE_ADDR`, falling back to the nearest proxy in `X-Forwarded-For` when needed.
90
+ ### BFF Integration
91
+ Support for BFF header handling (e.g., normalizing or enforcing `X-Forwarded-Access-Token`) now lives in a dedicated gem: verikloak-bff.
92
+ Note: verikloak-bff's `HeaderGuard` never overwrites an existing `Authorization` header.
96
93
 
97
- ### BFF Header Promotion
98
- When fronted by a BFF (e.g., oauth2-proxy) that injects `X-Forwarded-Access-Token`, you can promote that header to `Authorization` from trusted sources only.
94
+ - Gem: https://github.com/taiyaky/verikloak-bff
95
+ - Rails guide: `docs/rails.md` in that repository
99
96
 
100
- Enable promotion and restrict to trusted subnets:
101
-
102
- ```ruby
103
- Rails.application.configure do
104
- config.verikloak.trust_forwarded_access_token = true
105
- config.verikloak.trusted_proxy_subnets = [
106
- '10.0.0.0/8',
107
- '192.168.0.0/16'
108
- ]
109
- end
110
- ```
111
-
112
- ### Reordering or Disabling (advanced)
113
- You can adjust the stack in an initializer after the gem loads, for example:
114
-
115
- ```ruby
116
- Rails.application.configure do
117
- # Remove header-promotion middleware if you never use BFF tokens
118
- config.middleware.delete Verikloak::Rails::MiddlewareIntegration::ForwardedAccessToken
119
-
120
- # Or move the middleware earlier/later if your stack requires it
121
- # config.middleware.insert_before SomeOtherMiddleware, Verikloak::Rails::MiddlewareIntegration::ForwardedAccessToken,
122
- # trust_forwarded: Verikloak::Rails.config.trust_forwarded_access_token,
123
- # trusted_proxies: Verikloak::Rails.config.trusted_proxy_subnets,
124
- # header_priority: Verikloak::Rails.config.token_header_priority
125
- end
126
- ```
97
+ Use verikloak-bff alongside this gem when you front Rails with a BFF/proxy such as oauth2-proxy and need to enforce trusted forwarding and header consistency.
127
98
 
128
99
  ## Configuration (initializer)
129
100
  ### Keys
@@ -136,13 +107,10 @@ Keys under `config.verikloak`:
136
107
  | `issuer` | String | Expected `iss` | `nil` |
137
108
  | `leeway` | Integer | Clock skew allowance (seconds) | `60` |
138
109
  | `skip_paths` | Array<String> | Paths to skip verification | `['/up','/health','/rails/health']` |
139
- | `trust_forwarded_access_token` | Boolean | Trust `X-Forwarded-Access-Token` from trusted proxies | `false` |
140
- | `trusted_proxy_subnets` | Array<String or IPAddr> | Subnets allowed to be treated as trusted | `[]` (treat all as trusted; set explicit ranges in production) |
141
110
  | `logger_tags` | Array<Symbol> | Tags to add to Rails logs. Supports `:request_id`, `:sub` | `[:request_id, :sub]` |
142
111
  | `error_renderer` | Object responding to `render(controller, error)` | Override error rendering | built-in JSON renderer |
143
112
  | `auto_include_controller` | Boolean | Auto-include controller concern | `true` |
144
113
  | `render_500_json` | Boolean | Rescue `StandardError` and render JSON 500 | `false` |
145
- | `token_header_priority` | Array<String> | Env header priority to source bearer token | `['HTTP_X_FORWARDED_ACCESS_TOKEN','HTTP_AUTHORIZATION']` |
146
114
  | `rescue_pundit` | Boolean | Rescue `Pundit::NotAuthorizedError` to 403 JSON when Pundit is present | `true` |
147
115
 
148
116
  Environment variable examples are in the generated initializer.
@@ -150,7 +118,6 @@ Environment variable examples are in the generated initializer.
150
118
  ### Minimum Setup
151
119
  - Required: set `discovery_url` to your provider’s OIDC discovery document URL.
152
120
  - Recommended: set `audience` (expected `aud`), and `issuer` when known.
153
- - Optional: enable BFF header promotion only with explicit `trusted_proxy_subnets`.
154
121
 
155
122
  ```ruby
156
123
  # config/initializers/verikloak.rb
@@ -160,15 +127,12 @@ Rails.application.configure do
160
127
  # Optional but recommended when you know it
161
128
  # config.verikloak.issuer = 'https://idp.example.com/realms/myrealm'
162
129
 
163
- # Leave header promotion off unless you run a trusted BFF/proxy
164
- # config.verikloak.trust_forwarded_access_token = false
165
- # config.verikloak.trusted_proxy_subnets = ['10.0.0.0/8']
130
+ # For BFF/proxy header handling, see verikloak-bff
166
131
  end
167
132
  ```
168
133
 
169
134
  Notes:
170
- - For array-like values (`audience`, `skip_paths`, `trusted_proxy_subnets`, `token_header_priority`), prefer defining Ruby arrays in the initializer. If passing via ENV, use comma-separated strings and parse in the initializer.
171
- - Header sourcing/trust behavior is described in “Middleware → Header Sources and Trust”.
135
+ - For array-like values (`audience`, `skip_paths`), prefer defining Ruby arrays in the initializer. If passing via ENV, use comma-separated strings and parse in the initializer.
172
136
 
173
137
  ### Full Example (selected options)
174
138
  ```ruby
@@ -179,16 +143,8 @@ Rails.application.configure do
179
143
  config.verikloak.leeway = Integer(ENV.fetch('VERIKLOAK_LEEWAY', '60'))
180
144
  config.verikloak.skip_paths = %w[/up /health /rails/health]
181
145
 
182
- # Enable BFF header promotion only from trusted subnets
183
- config.verikloak.trust_forwarded_access_token = ENV.fetch('VERIKLOAK_TRUST_FWD_TOKEN', 'false') == 'true'
184
- config.verikloak.trusted_proxy_subnets = [
185
- '10.0.0.0/8', # internal LB
186
- # '192.168.0.0/16'
187
- ]
188
-
189
146
  config.verikloak.logger_tags = %i[request_id sub]
190
147
  config.verikloak.render_500_json = ENV.fetch('VERIKLOAK_RENDER_500', 'false') == 'true'
191
- config.verikloak.token_header_priority = %w[HTTP_X_FORWARDED_ACCESS_TOKEN HTTP_AUTHORIZATION]
192
148
 
193
149
  # Optional Pundit rescue (403 JSON)
194
150
  config.verikloak.rescue_pundit = ENV.fetch('VERIKLOAK_RESCUE_PUNDIT', 'true') == 'true'
@@ -203,14 +159,9 @@ end
203
159
  | `audience` | `VERIKLOAK_AUDIENCE` |
204
160
  | `issuer` | `VERIKLOAK_ISSUER` |
205
161
  | `leeway` | `VERIKLOAK_LEEWAY` |
206
- | `trust_forwarded_access_token` | `VERIKLOAK_TRUST_FWD_TOKEN` |
207
162
  | `render_500_json` | `VERIKLOAK_RENDER_500` |
208
163
  | `rescue_pundit` | `VERIKLOAK_RESCUE_PUNDIT` |
209
164
 
210
- ### Notes
211
- - Default for `trust_forwarded_access_token` is secure (`false`). Set `trusted_proxy_subnets` before enabling.
212
- - The middleware never overwrites an existing `Authorization` header.
213
- - If `trusted_proxy_subnets` is empty, all peers are treated as trusted. In production, set explicit subnets or keep `trust_forwarded_access_token` disabled.
214
165
 
215
166
  ## Errors
216
167
  This gem standardizes JSON error responses and HTTP statuses. See [ERRORS.md](ERRORS.md) for details and examples.
@@ -220,7 +171,7 @@ This gem standardizes JSON error responses and HTTP statuses. See [ERRORS.md](ER
220
171
  | --- | --- | --- | --- | --- |
221
172
  | 401 Unauthorized | `invalid_token`, `unauthorized` | Missing/invalid Bearer token; failed signature/expiry/issuer/audience checks | `WWW-Authenticate: Bearer` with optional `error` and `error_description` | `{ "error": "invalid_token", "message": "token expired" }` |
222
173
  | 403 Forbidden | `forbidden` | Audience check failure via `with_required_audience!`; optionally `Pundit::NotAuthorizedError` when rescue is enabled | — | `{ "error": "forbidden", "message": "Required audience not satisfied" }` |
223
- | 503 Service Unavailable | `jwks_fetch_failed`, `jwks_parse_failed`, `discovery_metadata_fetch_failed`, `discovery_metadata_invalid` | Upstream metadata/JWKS issues | — | `{ "error": "jwks_fetch_failed", "message": "..." }` |
174
+ | 503 Service Unavailable | `jwks_fetch_failed`, `jwks_parse_failed`, `discovery_metadata_fetch_failed`, `discovery_metadata_invalid`, `invalid_discovery_url`, `discovery_redirect_error` | Upstream metadata/JWKS issues | — | `{ "error": "jwks_fetch_failed", "message": "..." }` |
224
175
 
225
176
  ### Customize
226
177
  Customize rendering by assigning `config.verikloak.error_renderer`.
@@ -261,8 +212,8 @@ Note: Always sanitize values placed into `WWW-Authenticate` header parameters to
261
212
  class CompactErrorRenderer
262
213
  private
263
214
  def sanitize_quoted(val)
264
- # Escape quotes/backslashes and strip CR/LF
265
- val.to_s.gsub(/(["\\])/) { |m| "\\#{m}" }.gsub(/[\r\n]/, ' ')
215
+ # Escape quotes/backslashes and strip CR/LF (collapse runs to a single space)
216
+ val.to_s.gsub(/(["\\])/) { |m| "\\#{m}" }.gsub(/[\r\n]+/, ' ')
266
217
  end
267
218
  end
268
219
  ```
@@ -338,5 +289,6 @@ See [CHANGELOG.md](CHANGELOG.md) for release history.
338
289
 
339
290
  ## References
340
291
  - verikloak-rails (this gem): https://rubygems.org/gems/verikloak-rails
292
+ - verikloak-bff: https://rubygems.org/gems/verikloak-bff
341
293
  - Verikloak (base gem): https://github.com/taiyaky/verikloak
342
294
  - Verikloak on RubyGems: https://rubygems.org/gems/verikloak
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ipaddr'
4
-
5
3
  module Verikloak
6
4
  module Rails
7
5
  # Configuration for verikloak-rails.
@@ -25,12 +23,6 @@ module Verikloak
25
23
  # @!attribute [rw] skip_paths
26
24
  # Paths to skip verification.
27
25
  # @return [Array<String>]
28
- # @!attribute [rw] trust_forwarded_access_token
29
- # Whether to trust `X-Forwarded-Access-Token` from trusted proxies.
30
- # @return [Boolean]
31
- # @!attribute [r] trusted_proxy_subnets
32
- # Trusted proxy subnets.
33
- # @return [Array<IPAddr>]
34
26
  # @!attribute [rw] logger_tags
35
27
  # Log tags to include (supports :request_id, :sub).
36
28
  # @return [Array<Symbol>]
@@ -43,14 +35,10 @@ module Verikloak
43
35
  # @!attribute [rw] render_500_json
44
36
  # Rescue StandardError and render a JSON 500 response.
45
37
  # @return [Boolean]
46
- # @!attribute [rw] token_header_priority
47
- # Env header keys in priority order for sourcing bearer token.
48
- # @return [Array<String>]
49
38
  class Configuration
50
- attr_accessor :discovery_url, :audience, :issuer, :leeway, :skip_paths, :trust_forwarded_access_token,
51
- :logger_tags, :error_renderer, :auto_include_controller, :render_500_json, :token_header_priority,
52
- :rescue_pundit
53
- attr_reader :trusted_proxy_subnets
39
+ attr_accessor :discovery_url, :audience, :issuer, :leeway, :skip_paths,
40
+ :logger_tags, :error_renderer, :auto_include_controller,
41
+ :render_500_json, :rescue_pundit
54
42
 
55
43
  def initialize
56
44
  @discovery_url = nil
@@ -58,22 +46,13 @@ module Verikloak
58
46
  @issuer = nil
59
47
  @leeway = 60
60
48
  @skip_paths = ['/up', '/health', '/rails/health']
61
- @trust_forwarded_access_token = false
62
- @trusted_proxy_subnets = []
63
49
  @logger_tags = %i[request_id sub]
64
50
  @error_renderer = Verikloak::Rails::ErrorRenderer.new
65
51
  @auto_include_controller = true
66
52
  @render_500_json = false
67
- @token_header_priority = %w[HTTP_X_FORWARDED_ACCESS_TOKEN HTTP_AUTHORIZATION]
68
53
  @rescue_pundit = true
69
54
  end
70
55
 
71
- # Assign trusted proxy subnets.
72
- # @param list [Array<String, IPAddr>, String, IPAddr] one or more CIDRs or IPAddr instances
73
- def trusted_proxy_subnets=(list)
74
- @trusted_proxy_subnets = Array(list).map { |e| e.is_a?(IPAddr) ? e : IPAddr.new(e) }
75
- end
76
-
77
56
  # Options forwarded to the base Verikloak Rack middleware.
78
57
  # @return [Hash]
79
58
  # @example
@@ -99,18 +99,29 @@ module Verikloak
99
99
  # @yieldreturn [Object] result of the block
100
100
  # @return [Object]
101
101
  def _verikloak_tag_logs(&)
102
- tags = []
103
- if Verikloak::Rails.config.logger_tags.include?(:request_id)
104
- rid = request.request_id || request.headers['X-Request-Id']
105
- tags << "req:#{rid}" if rid
106
- end
107
- tags << "sub:#{current_subject}" if Verikloak::Rails.config.logger_tags.include?(:sub) && current_subject
102
+ tags = _verikloak_build_log_tags
108
103
  if ::Rails.logger.respond_to?(:tagged) && tags.any?
109
104
  ::Rails.logger.tagged(*tags, &)
110
105
  else
111
106
  yield
112
107
  end
113
108
  end
109
+
110
+ # Build log tags from request context with minimal branching and safe values.
111
+ # @return [Array<String>]
112
+ def _verikloak_build_log_tags
113
+ tags = []
114
+ if Verikloak::Rails.config.logger_tags.include?(:request_id)
115
+ rid = request.request_id || request.headers['X-Request-Id']
116
+ rid = rid.to_s.gsub(/[\r\n]+/, ' ')
117
+ tags << "req:#{rid}" unless rid.empty?
118
+ end
119
+ if Verikloak::Rails.config.logger_tags.include?(:sub)
120
+ sub = current_subject
121
+ tags << "sub:#{sub}" if sub
122
+ end
123
+ tags
124
+ end
114
125
  end
115
126
  end
116
127
  end
@@ -14,7 +14,10 @@ module Verikloak
14
14
  'jwks_fetch_failed' => 503,
15
15
  'jwks_parse_failed' => 503,
16
16
  'discovery_metadata_fetch_failed' => 503,
17
- 'discovery_metadata_invalid' => 503
17
+ 'discovery_metadata_invalid' => 503,
18
+ # Additional infrastructure/configuration errors from core
19
+ 'invalid_discovery_url' => 503,
20
+ 'discovery_redirect_error' => 503
18
21
  }.freeze
19
22
 
20
23
  # Render an error as JSON, adding `WWW-Authenticate` when appropriate.
@@ -77,7 +80,7 @@ module Verikloak
77
80
  # @param val [String]
78
81
  # @return [String]
79
82
  def sanitize_quoted(val)
80
- val.to_s.gsub(/(["\\])/) { |m| "\\#{m}" }.gsub(/[\r\n]/, ' ')
83
+ val.to_s.gsub(/(["\\])/) { |m| "\\#{m}" }.gsub(/[\r\n]+/, ' ')
81
84
  end
82
85
  end
83
86
  end
@@ -8,7 +8,7 @@ module Verikloak
8
8
  # Hooks verikloak-rails into a Rails application lifecycle.
9
9
  #
10
10
  # - Applies configuration from `config.verikloak`
11
- # - Inserts middleware (`ForwardedAccessToken`, then `Verikloak::Middleware`)
11
+ # - Inserts base `Verikloak::Middleware`
12
12
  # - Auto-includes controller concern when enabled
13
13
  class Railtie < ::Rails::Railtie
14
14
  config.verikloak = ActiveSupport::OrderedOptions.new
@@ -18,18 +18,13 @@ module Verikloak
18
18
  initializer 'verikloak.configure' do |app|
19
19
  Verikloak::Rails.configure do |c|
20
20
  rails_cfg = app.config.verikloak
21
- %i[discovery_url audience issuer leeway skip_paths trust_forwarded_access_token
22
- trusted_proxy_subnets logger_tags error_renderer auto_include_controller
23
- render_500_json token_header_priority rescue_pundit].each do |key|
21
+ %i[discovery_url audience issuer leeway skip_paths
22
+ logger_tags error_renderer auto_include_controller
23
+ render_500_json rescue_pundit].each do |key|
24
24
  c.send("#{key}=", rails_cfg[key]) if rails_cfg.key?(key)
25
25
  end
26
26
  end
27
27
  app.middleware.insert_after ::Rails::Rack::Logger,
28
- Verikloak::Rails::MiddlewareIntegration::ForwardedAccessToken,
29
- trust_forwarded: Verikloak::Rails.config.trust_forwarded_access_token,
30
- trusted_proxies: Verikloak::Rails.config.trusted_proxy_subnets,
31
- header_priority: Verikloak::Rails.config.token_header_priority
32
- app.middleware.insert_after Verikloak::Rails::MiddlewareIntegration::ForwardedAccessToken,
33
28
  ::Verikloak::Middleware,
34
29
  **Verikloak::Rails.config.middleware_options
35
30
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Verikloak
4
4
  module Rails
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -4,7 +4,6 @@ require 'verikloak/rails/version'
4
4
  require 'verikloak/rails/configuration'
5
5
  require 'verikloak/rails/error_renderer'
6
6
  require 'verikloak/rails/controller'
7
- require 'verikloak/rails/middleware_integration'
8
7
  require 'verikloak/rails/railtie'
9
8
 
10
9
  module Verikloak
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verikloak-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - taiyaky
@@ -49,8 +49,8 @@ dependencies:
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
51
  version: '0.2'
52
- description: 'Rails integration for Verikloak: auto middleware, helpers, JSON errors,
53
- BFF header support.'
52
+ description: 'Rails integration for Verikloak: auto middleware, helpers, and standardized
53
+ JSON errors.'
54
54
  executables: []
55
55
  extensions: []
56
56
  extra_rdoc_files: []
@@ -64,7 +64,6 @@ files:
64
64
  - lib/verikloak/rails/configuration.rb
65
65
  - lib/verikloak/rails/controller.rb
66
66
  - lib/verikloak/rails/error_renderer.rb
67
- - lib/verikloak/rails/middleware_integration.rb
68
67
  - lib/verikloak/rails/railtie.rb
69
68
  - lib/verikloak/rails/version.rb
70
69
  homepage: https://github.com/taiyaky/verikloak-rails
@@ -74,7 +73,7 @@ metadata:
74
73
  source_code_uri: https://github.com/taiyaky/verikloak-rails
75
74
  changelog_uri: https://github.com/taiyaky/verikloak-rails/blob/main/CHANGELOG.md
76
75
  bug_tracker_uri: https://github.com/taiyaky/verikloak-rails/issues
77
- documentation_uri: https://rubydoc.info/gems/verikloak-rails/0.1.0
76
+ documentation_uri: https://rubydoc.info/gems/verikloak-rails/0.2.0
78
77
  rubygems_mfa_required: 'true'
79
78
  rdoc_options: []
80
79
  require_paths:
@@ -1,107 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ipaddr'
4
-
5
- module Verikloak
6
- module Rails
7
- # Internal namespace for Rack middleware glue.
8
- module MiddlewareIntegration
9
- # Promotes forwarded access tokens to Authorization when trusted.
10
- #
11
- # - Optionally trusts `X-Forwarded-Access-Token` from configured subnets
12
- # - Never overwrites an existing `Authorization` header
13
- # - Can derive the token from a prioritized list of headers
14
- class ForwardedAccessToken
15
- # Initialize the middleware.
16
- #
17
- # @param app [#call] next Rack app
18
- # @param trust_forwarded [Boolean] whether to trust forwarded access tokens
19
- # @param trusted_proxies [Array<IPAddr>, Array<String>] subnets considered trusted
20
- # @param header_priority [Array<String>] env header keys to search, in order
21
- def initialize(app, trust_forwarded:, trusted_proxies:,
22
- header_priority: %w[HTTP_X_FORWARDED_ACCESS_TOKEN HTTP_AUTHORIZATION])
23
- @app = app
24
- @trust_forwarded = trust_forwarded
25
- @trusted_proxies = Array(trusted_proxies)
26
- @header_priority = header_priority
27
- end
28
-
29
- # Rack entry point: possibly promote a token and pass to the next app.
30
- #
31
- # @param env [Hash] Rack environment
32
- # @return [Array(Integer, Hash, #each)] Rack response triple from downstream app
33
- # @example Promote forwarded token
34
- # env = { 'REMOTE_ADDR' => '10.0.0.1', 'HTTP_X_FORWARDED_ACCESS_TOKEN' => 'abc' }
35
- # status, headers, body = middleware.call(env)
36
- def call(env)
37
- promote_forwarded_if_trusted(env)
38
- first = resolve_first_token_header(env)
39
- set_authorization_from(env, first) if first
40
- @app.call(env)
41
- end
42
-
43
- private
44
-
45
- # Promote X-Forwarded-Access-Token to Authorization when trusted.
46
- # @param env [Hash]
47
- # @return [void]
48
- def promote_forwarded_if_trusted(env)
49
- return unless @trust_forwarded && from_trusted_proxy?(env)
50
-
51
- forwarded = env['HTTP_X_FORWARDED_ACCESS_TOKEN']
52
- return if forwarded.to_s.empty?
53
- return unless env['HTTP_AUTHORIZATION'].to_s.empty?
54
-
55
- env['HTTP_AUTHORIZATION'] = ensure_bearer(forwarded)
56
- end
57
-
58
- # Resolve the first header key from which to source a bearer token.
59
- # Respects trust policy for forwarded tokens and never returns
60
- # 'HTTP_AUTHORIZATION'.
61
- #
62
- # @param env [Hash]
63
- # @return [String, nil]
64
- def resolve_first_token_header(env)
65
- candidates = @header_priority.dup
66
- candidates -= ['HTTP_X_FORWARDED_ACCESS_TOKEN'] unless @trust_forwarded && from_trusted_proxy?(env)
67
- candidates.find { |k| (val = env[k]) && !val.to_s.empty? && k != 'HTTP_AUTHORIZATION' }
68
- end
69
-
70
- # Set Authorization header from the given env header key.
71
- # @param env [Hash]
72
- # @param header_key [String]
73
- # @return [void]
74
- def set_authorization_from(env, header_key)
75
- token = env[header_key]
76
- env['HTTP_AUTHORIZATION'] ||= ensure_bearer(token)
77
- end
78
-
79
- # Ensure the token string is prefixed with 'Bearer '.
80
- # @param token [String]
81
- # @return [String]
82
- def ensure_bearer(token)
83
- token.start_with?('Bearer') ? token : "Bearer #{token}"
84
- end
85
-
86
- # Whether the request originates from a trusted proxy subnet.
87
- # @param env [Hash]
88
- # @return [Boolean]
89
- def from_trusted_proxy?(env)
90
- return true if @trusted_proxies.empty?
91
-
92
- # Prefer REMOTE_ADDR (direct peer). Fallback to the nearest proxy from X-Forwarded-For if present.
93
- ip = (env['REMOTE_ADDR'] || '').to_s.strip
94
- ip = env['HTTP_X_FORWARDED_FOR'].to_s.split(',').last.to_s.strip if ip.empty? && env['HTTP_X_FORWARDED_FOR']
95
- return false if ip.empty?
96
-
97
- begin
98
- req_ip = IPAddr.new(ip)
99
- @trusted_proxies.any? { |subnet| subnet.include?(req_ip) }
100
- rescue IPAddr::InvalidAddressError
101
- false
102
- end
103
- end
104
- end
105
- end
106
- end
107
- end