Module: PlatformSdk::Identity::ErrorHandleable

Included in:
AuthClient, Client, Zitadel::AuthClient, Zitadel::Client
Defined in:
lib/platform_sdk/identity/error_handleable.rb

Overview

Error handling for Identity SDK

Constant Summary collapse

SENSITIVE_KEYS =

The logged payload is the raw request body, which for token endpoints carries client credentials; redact those fields (form-encoded and JSON shapes) so an auth outage can't write secrets to consumer logs.

'client_secret|client_assertion|refresh_token|password'
FORM_SECRET_PATTERN =
/(#{SENSITIVE_KEYS})=[^&]*/i
JSON_SECRET_PATTERN =
/("(?:#{SENSITIVE_KEYS})"\s*:\s*")[^"]*(")/i

Instance Method Summary collapse

Instance Method Details

#error_log_payload(exception_class, error) ⇒ Object

No ActiveSupport here (String#demodulize): this must work for non-Rails consumers, where the log line is skipped but this hash is still built.



36
37
38
39
40
41
42
43
# File 'lib/platform_sdk/identity/error_handleable.rb', line 36

def error_log_payload(exception_class, error)
  {
    exception: exception_class.name.split('::').last,
    payload: scrub_secrets(error.response.dig(:request, :body)),
    response_body: error.response[:body],
    status: error.response[:status]
  }.compact
end

#raise_error_with_payload(exception_class, error) ⇒ Object

Raises:

  • (exception_class)


24
25
26
27
28
29
30
31
# File 'lib/platform_sdk/identity/error_handleable.rb', line 24

def raise_error_with_payload(exception_class, error)
  Rails.logger.info(error_log_payload(exception_class, error).to_json) if defined?(Rails)
  # The exception itself carries error.response on to consumer
  # instrumentation (Sentry, custom error handlers), so the raw
  # request body/headers - which can hold a client_secret or bearer
  # token - must be scrubbed here too, not just in the log line above.
  raise exception_class, scrub_response(error.response)
end

#scrub_headers(headers) ⇒ Object

dup (not a rebuilt Hash) so a Faraday::Utils::Headers keeps its class and case-insensitive lookup on the scrubbed copy.



75
76
77
78
79
80
81
# File 'lib/platform_sdk/identity/error_handleable.rb', line 75

def scrub_headers(headers)
  scrubbed = headers.dup
  scrubbed.each_key do |key|
    scrubbed[key] = '[REDACTED]' if key.to_s.casecmp('authorization').zero?
  end
  scrubbed
end

#scrub_request(request) ⇒ Object



66
67
68
69
70
71
# File 'lib/platform_sdk/identity/error_handleable.rb', line 66

def scrub_request(request)
  scrubbed_request = request.dup
  scrubbed_request[:body] = scrub_secrets(request[:body]) if request[:body].is_a?(String)
  scrubbed_request[:headers] = scrub_headers(request[:headers]) if request[:headers].is_a?(Hash)
  scrubbed_request
end

#scrub_response(response) ⇒ Object

Returns a scrubbed copy of the Faraday error response hash; the original (and its nested :request hash) are left untouched. Only [:request][:body] and [:request][:headers] are touched - [:status] and [:body] (the response body) are read elsewhere (e.g. e.response&.dig(:status), e.response&.dig(:body, 'paramName')) and must reach consumers unchanged.



58
59
60
61
62
63
64
# File 'lib/platform_sdk/identity/error_handleable.rb', line 58

def scrub_response(response)
  return response unless response.is_a?(Hash)

  scrubbed = response.dup
  scrubbed[:request] = scrub_request(response[:request]) if response[:request].is_a?(Hash)
  scrubbed
end

#scrub_secrets(body) ⇒ Object



45
46
47
48
49
50
# File 'lib/platform_sdk/identity/error_handleable.rb', line 45

def scrub_secrets(body)
  return body unless body.is_a?(String)

  body.gsub(FORM_SECRET_PATTERN) { "#{Regexp.last_match(1)}=[REDACTED]" }
      .gsub(JSON_SECRET_PATTERN, '\1[REDACTED]\2')
end

#with_rescueObject



14
15
16
17
18
19
20
21
22
# File 'lib/platform_sdk/identity/error_handleable.rb', line 14

def with_rescue
  yield
rescue Faraday::TimeoutError => e
  raise TimeoutError, e
rescue Faraday::ServerError => e
  raise_error_with_payload(ServerError, e)
rescue Faraday::ClientError => e
  raise_error_with_payload(ClientError, e)
end