Module: Verikloak::BFF::HeaderGuardSanitizer
- Defined in:
- lib/verikloak/bff/header_guard.rb
Overview
Internal helpers that sanitize tokens and log payloads for HeaderGuard.
Constant Summary collapse
- LOG_CONTROL_CHARS =
Constants::LOG_CONTROL_CHARS
- MAX_LOG_FIELD_LENGTH =
Constants::MAX_LOG_FIELD_LENGTH
Class Method Summary collapse
- .emit_to_logger(logger, sanitized, kind) ⇒ void
- .invoke_log_hook(config, sanitized) ⇒ void
-
.log_event(env, config, kind, **attrs) ⇒ void
Emit a structured log line if a logger is present.
-
.request_id(env) ⇒ String?
Extract request id for logging from common headers.
-
.respond_with_error(env, config, error) ⇒ Array(Integer, Hash, Array<String>)
Build a minimal RFC6750-style error response.
-
.sanitize_log_field(value) ⇒ Object?
Sanitize an individual value destined for logs, pruning empty results.
-
.sanitize_payload(payload) ⇒ Hash
Remove unsafe characters from a structured logging payload.
-
.sanitize_string(value) ⇒ String
Remove control characters, invalid UTF-8, and truncate to MAX_LOG_FIELD_LENGTH.
-
.token_source(prefer_forwarded, auth_token, fwd_token) ⇒ String
Describe the source of the chosen token for logging purposes.
-
.token_tags_from_decoded(payload, header) ⇒ Hash{Symbol=>Object}
Build sanitized log tags from pre-decoded JWT payload and header.
Class Method Details
.emit_to_logger(logger, sanitized, kind) ⇒ void
This method returns an undefined value.
150 151 152 153 154 155 |
# File 'lib/verikloak/bff/header_guard.rb', line 150 def emit_to_logger(logger, sanitized, kind) return unless logger msg = sanitized.map { |k, v| v.nil? || v.to_s.empty? ? nil : "#{k}=#{v}" }.compact.join(' ') logger.public_send(kind == :ok ? :info : :warn, msg) end |
.invoke_log_hook(config, sanitized) ⇒ void
This method returns an undefined value.
138 139 140 141 142 143 144 |
# File 'lib/verikloak/bff/header_guard.rb', line 138 def invoke_log_hook(config, sanitized) return unless config.log_with.respond_to?(:call) config.log_with.call(sanitized) rescue StandardError => e warn("[verikloak-bff] log_with hook failed: #{e.class}: #{e.}") if $DEBUG end |
.log_event(env, config, kind, **attrs) ⇒ void
This method returns an undefined value.
Emit a structured log line if a logger is present.
126 127 128 129 130 131 132 133 |
# File 'lib/verikloak/bff/header_guard.rb', line 126 def log_event(env, config, kind, **attrs) payload = { event: 'bff.header_guard', kind: kind, rid: request_id(env) }.merge(attrs).compact sanitized = sanitize_payload(payload) invoke_log_hook(config, sanitized) emit_to_logger(config.logger || env['rack.logger'], sanitized, kind) rescue StandardError => e warn("[verikloak-bff] log_event failed: #{e.class}: #{e.}") if $DEBUG end |
.request_id(env) ⇒ String?
Extract request id for logging from common headers.
115 116 117 |
# File 'lib/verikloak/bff/header_guard.rb', line 115 def request_id(env) env['HTTP_X_REQUEST_ID'] || env['action_dispatch.request_id'] end |
.respond_with_error(env, config, error) ⇒ Array(Integer, Hash, Array<String>)
Build a minimal RFC6750-style error response. Delegates to ErrorResponse for consistent formatting across gems.
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/verikloak/bff/header_guard.rb', line 164 def respond_with_error(env, config, error) log_event(env, config, :error, code: error.code) require 'verikloak/error_response' status, headers, body = Verikloak::ErrorResponse.build( code: error.code, message: error., status: error.http_status ) # RFC 6750 ยง3.1: include WWW-Authenticate on 403 for client diagnostics if status == 403 && !headers.key?('WWW-Authenticate') sanitize = Verikloak::ErrorResponse.method(:sanitize_header_value) headers['WWW-Authenticate'] = format( 'Bearer realm="%<realm>s", error="%<code>s", error_description="%<msg>s"', realm: sanitize.call('verikloak-bff'), code: sanitize.call(error.code), msg: sanitize.call(error.) ) end [status, headers, body] end |
.sanitize_log_field(value) ⇒ Object?
Sanitize an individual value destined for logs, pruning empty results.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/verikloak/bff/header_guard.rb', line 69 def sanitize_log_field(value) case value when nil nil when String sanitized = sanitize_string(value) sanitized.empty? ? nil : sanitized when Array sanitized = value.map { |item| item.is_a?(String) ? sanitize_string(item) : item } sanitized.reject! { |item| item.nil? || (item.is_a?(String) && item.empty?) } sanitized.empty? ? nil : sanitized else value end end |
.sanitize_payload(payload) ⇒ Hash
Remove unsafe characters from a structured logging payload.
61 62 63 |
# File 'lib/verikloak/bff/header_guard.rb', line 61 def sanitize_payload(payload) payload.transform_values { |value| sanitize_log_field(value) }.compact end |
.sanitize_string(value) ⇒ String
Remove control characters, invalid UTF-8, and truncate to MAX_LOG_FIELD_LENGTH.
89 90 91 92 93 |
# File 'lib/verikloak/bff/header_guard.rb', line 89 def sanitize_string(value) sanitized = value.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '').gsub(LOG_CONTROL_CHARS, '') sanitized.length > MAX_LOG_FIELD_LENGTH ? "#{sanitized[0, MAX_LOG_FIELD_LENGTH]}..." : sanitized end |
.token_source(prefer_forwarded, auth_token, fwd_token) ⇒ String
Describe the source of the chosen token for logging purposes.
101 102 103 104 105 106 107 108 109 |
# File 'lib/verikloak/bff/header_guard.rb', line 101 def token_source(prefer_forwarded, auth_token, fwd_token) return 'forwarded' if prefer_forwarded && fwd_token if auth_token && fwd_token 'authorization' else (auth_token ? 'authorization' : 'forwarded') end end |
.token_tags_from_decoded(payload, header) ⇒ Hash{Symbol=>Object}
Build sanitized log tags from pre-decoded JWT payload and header. Avoids redundant decoding when the caller already has decoded data.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/verikloak/bff/header_guard.rb', line 41 def (payload, header) return {} unless payload.is_a?(Hash) aud = payload['aud'] aud = aud.join(' ') if aud.is_a?(Array) { sub: sanitize_log_field(payload['sub']&.to_s), iss: sanitize_log_field(payload['iss']&.to_s), aud: sanitize_log_field(aud&.to_s), kid: sanitize_log_field(header&.dig('kid')&.to_s) }.compact rescue StandardError => e warn("[verikloak-bff] token_tags_from_decoded failed: #{e.class}: #{e.}") if $DEBUG {} end |