Class: Axn::Webhooks::Outbound::Signer::CustomSigner

Inherits:
Object
  • Object
show all
Defined in:
lib/axn/webhooks/outbound/signer.rb

Overview

Wraps a user block; called with the same kwargs as the built-in signers, PLUS subscriber: (PRO-3214, a Subscriber or nil) -- filtered down to whatever the block actually declares (via CallableArity.accepted_keywords), so a block written against the original (id:, timestamp:, body:) contract keeps working byte-for-byte rather than raising an unexpected-keyword ArgumentError the moment a widened caller starts also offering subscriber:. A block declaring ** receives everything unfiltered.

Constant Summary collapse

SUPPLIED_KEYWORDS =

The only kwargs a signer is ever called with. A block may ignore any/all of them -- sign { { "X-API-Key" => key } } (zero params) is a legitimate, pre-existing pattern: Ruby blocks are always non-lambda Procs, which silently tolerate being called with kwargs they never declared (Codex P1 finding: an earlier version of this check REQUIRED every block to declare id:/timestamp:/body:, rejecting that working configuration at boot even though it was never actually broken).

%i[id timestamp body subscriber].freeze

Instance Method Summary collapse

Constructor Details

#initialize(block) ⇒ CustomSigner

Returns a new instance of CustomSigner.



48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/axn/webhooks/outbound/signer.rb', line 48

def initialize(block)
  @block = block
  @accepted = CallableArity.accepted_keywords(block)
  # A block declaring NO keywords at all but at least one POSITIONAL param (`sign { |options|
  # … }`) is the historical "options Hash" shape: Ruby auto-converts trailing keyword
  # arguments into a Hash for a lone positional parameter (true for both a Proc/block and a
  # lambda alike), which is exactly what the ORIGINAL unconditional `.call(id:, timestamp:,
  # body:)` relied on. Filtering down to `**{}` (zero keywords accepted) calls with ZERO
  # arguments instead -- fine for a Proc (leaves `options` nil, tolerated) but a REQUIRED-arity
  # lambda raises outright; either way `options` never gets the data (Codex P1 finding).
  @wants_positional_hash = @accepted != :all && @accepted.empty? && CallableArity.accepts_positional?(block)
  validate_required_keywords!
end

Instance Method Details

#call(id:, timestamp:, body:, subscriber: nil) ⇒ Object



62
63
64
65
66
67
68
69
70
71
# File 'lib/axn/webhooks/outbound/signer.rb', line 62

def call(id:, timestamp:, body:, subscriber: nil)
  # The positional-Hash shape's CONTRACT is exactly these three keys -- it has no way to
  # OPT IN to `subscriber:` the way a keyword-declaring block does, so it must never see
  # it: a pre-existing signer deriving its signature from the WHOLE hash (e.g. hashing
  # every key-value pair together) would compute a DIFFERENT signature the instant a 4th
  # key appeared, silently breaking verification on the receiving end (Codex P1 finding).
  return @block.call({ id:, timestamp:, body: }) if @wants_positional_hash

  @block.call(**filtered({ id:, timestamp:, body:, subscriber: }))
end