Class: SignalWire::Security::WebhookMiddleware

Inherits:
Object
  • Object
show all
Defined in:
lib/signalwire/security/webhook_middleware.rb

Overview

Rack middleware that rejects webhook requests with bad signatures.

Configure with the customer’s Signing Key (and optional “trust_proxy“ to honor X-Forwarded headers). Mount upstream of any body-parsing middleware so the raw bytes survive intact.

Constant Summary collapse

SIGNALWIRE_SIGNATURE_HEADER =
'HTTP_X_SIGNALWIRE_SIGNATURE'
TWILIO_COMPAT_SIGNATURE_HEADER =
'HTTP_X_TWILIO_SIGNATURE'
RAW_BODY_ENV_KEY =

Key under which the captured raw body is stashed on the request env.

'signalwire.raw_body'

Instance Method Summary collapse

Constructor Details

#initialize(app, signing_key:, trust_proxy: false, paths: nil, methods: ['POST']) ⇒ WebhookMiddleware

Returns a new instance of WebhookMiddleware.

Parameters:

  • app (#call)

    the wrapped Rack app.

  • signing_key (String)

    customer Signing Key (required, non-empty).

  • trust_proxy (Boolean) (defaults to: false)

    honor X-Forwarded-Proto / X-Forwarded-Host when reconstructing the request URL. Default false — proxy headers are spoofable, so opt in only when you control the proxy.

  • paths (Array<String>, nil) (defaults to: nil)

    when set, only apply the validation on these PATH_INFO values; everything else passes through. When nil, apply to every request.

  • methods (Array<String>, nil) (defaults to: ['POST'])

    limit to these HTTP methods. When nil, apply to every method.

Raises:

  • (ArgumentError)

    when “signing_key“ is missing.



66
67
68
69
70
71
72
73
74
# File 'lib/signalwire/security/webhook_middleware.rb', line 66

def initialize(app, signing_key:, trust_proxy: false, paths: nil, methods: ['POST'])
  raise ArgumentError, 'signing_key is required' if signing_key.nil? || signing_key.to_s.empty?

  @app          = app
  @signing_key  = signing_key
  @trust_proxy  = trust_proxy
  @paths        = paths.nil? ? nil : Array(paths).map(&:to_s)
  @methods      = methods.nil? ? nil : Array(methods).map { |m| m.to_s.upcase }
end

Instance Method Details

#call(env) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/signalwire/security/webhook_middleware.rb', line 77

def call(env)
  return @app.call(env) unless _applies?(env)

  # Capture raw body BEFORE any other middleware reads the stream.
  raw_body = _read_raw_body(env)
  env[RAW_BODY_ENV_KEY] = raw_body

  signature = _extract_signature_header(env)
  return _forbidden if signature.nil? || signature.empty?

  url = _reconstruct_url(env)

  valid = begin
    WebhookValidator.validate_webhook_signature(@signing_key, signature, url, raw_body)
  rescue ArgumentError, TypeError
    # Programming errors at the boundary — never leak which branch
    # tripped. Reject the request without raising.
    return _forbidden
  end

  return _forbidden unless valid

  @app.call(env)
end