Skip to content

Commit 55b8f72

Browse files
committed
RequestValidator is a bad name, auth is better - also expose a global log to auth handlers
1 parent 2cf10f9 commit 55b8f72

File tree

16 files changed

+108
-73
lines changed

16 files changed

+108
-73
lines changed

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ AllCops:
1414

1515
GitHub/InsecureHashAlgorithm:
1616
Exclude:
17-
- "spec/unit/lib/hooks/plugins/request_validator/hmac_spec.rb"
17+
- "spec/unit/lib/hooks/plugins/auth/hmac_spec.rb"
1818

1919
GitHub/AvoidObjectSendWithDynamicMethod:
2020
Exclude:

docs/design.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ path: /team1 # Mounted at <root_path>/team1
167167
handler: Team1Handler # Class in handler_dir
168168

169169
# Signature validation
170-
request_validator:
170+
auth:
171171
type: default # 'default' uses HMACSHA256, or a custom class name
172172
secret_env_key: TEAM1_SECRET
173173
header: X-Hub-Signature
@@ -613,7 +613,7 @@ path: string # Endpoint path (mounted under root_path)
613613
handler: string # Handler class name
614614
615615
# Optional signature validation
616-
request_validator:
616+
auth:
617617
type: string # 'default' or custom validator class name
618618
secret_env_key: string # ENV key containing secret
619619
header: string # Header containing signature (default: X-Hub-Signature)

lib/hooks/app/api.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require "securerandom"
66
require_relative "../handlers/base"
77
require_relative "../core/logger_factory"
8+
require_relative "../core/log"
89

910
module Hooks
1011
module App
@@ -22,6 +23,9 @@ def self.create(config:, endpoints:, log:, signal_handler:)
2223
_captured_signal_handler = signal_handler
2324
captured_start_time = start_time
2425

26+
# Set global logger instance for plugins/validators
27+
Hooks::Log.instance = log
28+
2529
# Create the API class with dynamic routes
2630
api_class = Class.new(Grape::API) do
2731
# Accept all content types but don't auto-parse
@@ -58,9 +62,9 @@ def enforce_request_limits(config)
5862

5963
# Verify the incoming request
6064
def validate_request(payload, headers, endpoint_config)
61-
request_validator_config = endpoint_config[:request_validator]
62-
validator_type = request_validator_config[:type].downcase
63-
secret_env_key = request_validator_config[:secret_env_key]
65+
auth_config = endpoint_config[:auth]
66+
validator_type = auth_config[:type].downcase
67+
secret_env_key = auth_config[:secret_env_key]
6468

6569
return unless secret_env_key
6670

@@ -73,9 +77,9 @@ def validate_request(payload, headers, endpoint_config)
7377

7478
case validator_type
7579
when "hmac"
76-
validator_class = Plugins::RequestValidator::HMAC
80+
validator_class = Plugins::Auth::HMAC
7781
when "shared_secret"
78-
validator_class = Plugins::RequestValidator::SharedSecret
82+
validator_class = Plugins::Auth::SharedSecret
7983
else
8084
error!("Custom validators not implemented in POC", 500)
8185
end
@@ -198,8 +202,8 @@ def determine_error_code(exception)
198202
raw_body = request.body.read
199203

200204
# Verify/validate request if configured
201-
log.info "validating request (id: #{request_id}, handler: #{handler_class_name})" if endpoint_config[:request_validator]
202-
validate_request(raw_body, headers, endpoint_config) if endpoint_config[:request_validator]
205+
log.info "validating request (id: #{request_id}, handler: #{handler_class_name})" if endpoint_config[:auth]
206+
validate_request(raw_body, headers, endpoint_config) if endpoint_config[:auth]
203207

204208
# Parse payload (symbolize_payload is true by default)
205209
payload = parse_payload(raw_body, headers, symbolize: config[:symbolize_payload])

lib/hooks/core/config_validator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class ValidationError < StandardError; end
3030
required(:path).filled(:string)
3131
required(:handler).filled(:string)
3232

33-
optional(:request_validator).hash do
33+
optional(:auth).hash do
3434
required(:type).filled(:string)
3535
optional(:secret_env_key).filled(:string)
3636
optional(:header).filled(:string)

lib/hooks/core/log.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
module Hooks
4+
module Log
5+
class << self
6+
attr_accessor :instance
7+
end
8+
end
9+
end

lib/hooks/plugins/request_validator/base.rb renamed to lib/hooks/plugins/auth/base.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# frozen_string_literal: true
22

33
require "rack/utils"
4+
require_relative "../../core/log"
45

56
module Hooks
67
module Plugins
7-
module RequestValidator
8-
# Abstract base class for request validators
8+
module Auth
9+
# Abstract base class for request validators via authentication
910
#
10-
# All custom request validators must inherit from this class
11+
# All custom Auth plugins must inherit from this class
1112
class Base
1213
# Validate request
1314
#
@@ -20,6 +21,18 @@ class Base
2021
def self.valid?(payload:, headers:, secret:, config:)
2122
raise NotImplementedError, "Validator must implement .valid? class method"
2223
end
24+
25+
# Short logger accessor for all subclasses
26+
# @return [Hooks::Log] Logger instance for request validation
27+
#
28+
# Provides a convenient way for validators to log messages without needing
29+
# to reference the full Hooks::Log namespace.
30+
#
31+
# @example Logging an error in an inherited class
32+
# log.error("oh no an error occured")
33+
def self.log
34+
Hooks::Log.instance
35+
end
2336
end
2437
end
2538
end

lib/hooks/plugins/request_validator/hmac.rb renamed to lib/hooks/plugins/auth/hmac.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@
66

77
module Hooks
88
module Plugins
9-
module RequestValidator
9+
module Auth
1010
# Generic HMAC signature validator for webhooks
1111
#
1212
# This validator supports multiple webhook providers with different signature formats.
1313
# It provides flexible configuration options to handle various HMAC-based authentication schemes.
1414
#
1515
# @example Basic configuration with algorithm prefix
16-
# request_validator:
16+
# auth:
1717
# type: HMAC
1818
# secret_env_key: WEBHOOK_SECRET
1919
# header: X-Hub-Signature-256
2020
# algorithm: sha256
2121
# format: "algorithm=signature"
2222
#
2323
# @example Configuration with timestamp validation
24-
# request_validator:
24+
# auth:
2525
# type: HMAC
2626
# secret_env_key: WEBHOOK_SECRET
2727
# header: X-Signature
@@ -66,7 +66,7 @@ class HMAC < Base
6666
# @param headers [Hash<String, String>] HTTP headers from the request
6767
# @param secret [String] Secret key for HMAC computation
6868
# @param config [Hash] Endpoint configuration containing validator settings
69-
# @option config [Hash] :request_validator Validator-specific configuration
69+
# @option config [Hash] :auth Validator-specific configuration
7070
# @option config [String] :header ('X-Signature') Header containing the signature
7171
# @option config [String] :timestamp_header Header containing timestamp (optional)
7272
# @option config [Integer] :timestamp_tolerance (300) Timestamp tolerance in seconds
@@ -83,7 +83,7 @@ class HMAC < Base
8383
# payload: request_body,
8484
# headers: request.headers,
8585
# secret: ENV['WEBHOOK_SECRET'],
86-
# config: { request_validator: { header: 'X-Signature' } }
86+
# config: { auth: { header: 'X-Signature' } }
8787
# )
8888
def self.valid?(payload:, headers:, secret:, config:)
8989
return false if secret.nil? || secret.empty?
@@ -131,8 +131,8 @@ def self.valid?(payload:, headers:, secret:, config:)
131131

132132
# Use secure comparison to prevent timing attacks
133133
Rack::Utils.secure_compare(computed_signature, provided_signature)
134-
rescue StandardError => _e
135-
# Log error in production - for now just return false
134+
rescue StandardError => e
135+
log.error("Auth::HMAC validation failed: #{e.message}")
136136
false
137137
end
138138

@@ -148,7 +148,7 @@ def self.valid?(payload:, headers:, secret:, config:)
148148
# @note Missing configuration values are filled with DEFAULT_CONFIG values
149149
# @api private
150150
def self.build_config(config)
151-
validator_config = config.dig(:request_validator) || {}
151+
validator_config = config.dig(:auth) || {}
152152

153153
algorithm = validator_config[:algorithm] || DEFAULT_CONFIG[:algorithm]
154154
tolerance = validator_config[:timestamp_tolerance] || DEFAULT_CONFIG[:timestamp_tolerance]

lib/hooks/plugins/request_validator/shared_secret.rb renamed to lib/hooks/plugins/auth/shared_secret.rb

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
module Hooks
66
module Plugins
7-
module RequestValidator
7+
module Auth
88
# Generic shared secret validator for webhooks
99
#
1010
# This validator provides simple shared secret authentication for webhook requests.
@@ -13,13 +13,13 @@ module RequestValidator
1313
# used by various webhook providers.
1414
#
1515
# @example Basic configuration
16-
# request_validator:
16+
# auth:
1717
# type: shared_secret
1818
# secret_env_key: WEBHOOK_SECRET
1919
# header: Authorization
2020
#
2121
# @example Custom header configuration
22-
# request_validator:
22+
# auth:
2323
# type: shared_secret
2424
# secret_env_key: SOME_OTHER_WEBHOOK_SECRET
2525
# header: X-API-Key
@@ -45,7 +45,7 @@ class SharedSecret < Base
4545
# @param headers [Hash<String, String>] HTTP headers from the request
4646
# @param secret [String] Expected secret value for comparison
4747
# @param config [Hash] Endpoint configuration containing validator settings
48-
# @option config [Hash] :request_validator Validator-specific configuration
48+
# @option config [Hash] :auth Validator-specific configuration
4949
# @option config [String] :header ('Authorization') Header containing the secret
5050
# @return [Boolean] true if secret is valid, false otherwise
5151
# @raise [StandardError] Rescued internally, returns false on any error
@@ -56,7 +56,7 @@ class SharedSecret < Base
5656
# payload: request_body,
5757
# headers: request.headers,
5858
# secret: ENV['WEBHOOK_SECRET'],
59-
# config: { request_validator: { header: 'Authorization' } }
59+
# config: { auth: { header: 'Authorization' } }
6060
# )
6161
def self.valid?(payload:, headers:, secret:, config:)
6262
return false if secret.nil? || secret.empty?
@@ -90,7 +90,6 @@ def self.valid?(payload:, headers:, secret:, config:)
9090
# Use secure comparison to prevent timing attacks
9191
Rack::Utils.secure_compare(secret, stripped_secret)
9292
rescue StandardError => _e
93-
# Log error in production - for now just return false
9493
false
9594
end
9695

@@ -106,7 +105,7 @@ def self.valid?(payload:, headers:, secret:, config:)
106105
# @note Missing configuration values are filled with DEFAULT_CONFIG values
107106
# @api private
108107
def self.build_config(config)
109-
validator_config = config.dig(:request_validator) || {}
108+
validator_config = config.dig(:auth) || {}
110109

111110
DEFAULT_CONFIG.merge({
112111
header: validator_config[:header] || DEFAULT_CONFIG[:header]

spec/acceptance/config/endpoints/github.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ path: /github
33
handler: GithubHandler
44

55
# GitHub uses HMAC SHA256 signature validation
6-
request_validator:
6+
auth:
77
type: hmac
88
secret_env_key: GITHUB_WEBHOOK_SECRET
99
header: X-Hub-Signature-256
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
path: /okta
22
handler: OktaHandler
33

4-
request_validator:
4+
auth:
55
type: shared_secret
66
secret_env_key: SHARED_SECRET # the name of the environment variable containing the shared secret
77
header: Authorization

0 commit comments

Comments
 (0)