hooksniff 0.3.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +56 -0
  4. data/README.md +23 -199
  5. data/Rakefile +2 -0
  6. data/hooksniff.gemspec +48 -0
  7. data/lib/hooksniff/api/authentication.rb +36 -0
  8. data/lib/hooksniff/api/endpoint.rb +102 -0
  9. data/lib/hooksniff/api/event_type.rb +66 -0
  10. data/lib/hooksniff/api/health.rb +16 -0
  11. data/lib/hooksniff/api/integration.rb +42 -0
  12. data/lib/hooksniff/api/message.rb +48 -0
  13. data/lib/hooksniff/api/message_attempt.rb +38 -0
  14. data/lib/hooksniff/api/statistics.rb +37 -0
  15. data/lib/hooksniff/api/stream.rb +45 -0
  16. data/lib/{openapi_client → hooksniff}/api_error.rb +8 -18
  17. data/lib/hooksniff/background_task.rb +21 -0
  18. data/lib/hooksniff/connector.rb +33 -0
  19. data/lib/hooksniff/environment.rb +53 -0
  20. data/lib/hooksniff/errors.rb +129 -0
  21. data/lib/hooksniff/hooksniff.rb +52 -0
  22. data/lib/hooksniff/hooksniff_http_client.rb +128 -0
  23. data/lib/hooksniff/http_error_out.rb +18 -0
  24. data/lib/hooksniff/http_validation_error.rb +18 -0
  25. data/lib/hooksniff/inbound.rb +25 -0
  26. data/lib/hooksniff/internal.rb +7 -0
  27. data/lib/hooksniff/message_poller.rb +32 -0
  28. data/lib/hooksniff/models/endpoint_created_event.rb +50 -0
  29. data/lib/hooksniff/models/endpoint_created_event_data.rb +63 -0
  30. data/lib/hooksniff/models/endpoint_deleted_event.rb +50 -0
  31. data/lib/hooksniff/models/endpoint_deleted_event_data.rb +63 -0
  32. data/lib/hooksniff/models/endpoint_disabled_event.rb +53 -0
  33. data/lib/hooksniff/models/endpoint_disabled_event_data.rb +69 -0
  34. data/lib/hooksniff/models/endpoint_enabled_event.rb +50 -0
  35. data/lib/hooksniff/models/endpoint_enabled_event_data.rb +63 -0
  36. data/lib/hooksniff/models/endpoint_headers_in.rb +46 -0
  37. data/lib/hooksniff/models/endpoint_headers_out.rb +52 -0
  38. data/lib/hooksniff/models/endpoint_headers_patch_in.rb +53 -0
  39. data/lib/hooksniff/models/endpoint_in.rb +102 -0
  40. data/lib/hooksniff/models/endpoint_out.rb +104 -0
  41. data/lib/hooksniff/models/endpoint_patch.rb +97 -0
  42. data/lib/hooksniff/models/endpoint_secret_out.rb +50 -0
  43. data/lib/hooksniff/models/endpoint_secret_rotate_in.rb +53 -0
  44. data/lib/hooksniff/models/endpoint_update.rb +90 -0
  45. data/lib/hooksniff/models/endpoint_updated_event.rb +50 -0
  46. data/lib/hooksniff/models/endpoint_updated_event_data.rb +63 -0
  47. data/lib/hooksniff/models/event_in.rb +50 -0
  48. data/lib/hooksniff/models/event_out.rb +53 -0
  49. data/lib/hooksniff/models/event_type_in.rb +80 -0
  50. data/lib/hooksniff/models/event_type_out.rb +87 -0
  51. data/lib/hooksniff/models/event_type_patch.rb +66 -0
  52. data/lib/hooksniff/models/event_type_update.rb +67 -0
  53. data/lib/hooksniff/models/list_response_endpoint_out.rb +58 -0
  54. data/lib/hooksniff/models/list_response_event_type_out.rb +58 -0
  55. data/lib/hooksniff/models/list_response_message_attempt_out.rb +58 -0
  56. data/lib/hooksniff/models/list_response_message_out.rb +58 -0
  57. data/lib/hooksniff/models/message_attempt_exhausted_event.rb +53 -0
  58. data/lib/hooksniff/models/message_attempt_exhausted_event_data.rb +70 -0
  59. data/lib/hooksniff/models/message_attempt_failed_data.rb +56 -0
  60. data/lib/hooksniff/models/message_attempt_failing_event.rb +54 -0
  61. data/lib/hooksniff/models/message_attempt_failing_event_data.rb +70 -0
  62. data/lib/hooksniff/models/message_attempt_log.rb +112 -0
  63. data/lib/hooksniff/models/message_attempt_log_event.rb +53 -0
  64. data/lib/hooksniff/models/message_attempt_out.rb +96 -0
  65. data/lib/hooksniff/models/message_attempt_trigger_type.rb +33 -0
  66. data/lib/hooksniff/models/message_endpoint_out.rb +112 -0
  67. data/lib/hooksniff/models/message_in.rb +99 -0
  68. data/lib/hooksniff/models/message_out.rb +71 -0
  69. data/lib/hooksniff/models/message_status.rb +39 -0
  70. data/lib/hooksniff/models/message_status_text.rb +32 -0
  71. data/lib/hooksniff/models/ordering.rb +30 -0
  72. data/lib/hooksniff/models/status_code_class.rb +41 -0
  73. data/lib/hooksniff/operational_webhook.rb +12 -0
  74. data/lib/hooksniff/util.rb +69 -0
  75. data/lib/hooksniff/validation_error.rb +28 -0
  76. data/lib/hooksniff/version.rb +5 -0
  77. data/lib/hooksniff/webhook.rb +84 -0
  78. data/lib/hooksniff.rb +78 -0
  79. data/test/test_hooksniff.rb +86 -0
  80. metadata +131 -159
  81. data/lib/openapi_client/api/admin_api.rb +0 -452
  82. data/lib/openapi_client/api/alerts_api.rb +0 -322
  83. data/lib/openapi_client/api/analytics_api.rb +0 -208
  84. data/lib/openapi_client/api/api_keys_api.rb +0 -252
  85. data/lib/openapi_client/api/audit_log_api.rb +0 -140
  86. data/lib/openapi_client/api/auth_api.rb +0 -1080
  87. data/lib/openapi_client/api/billing_api.rb +0 -500
  88. data/lib/openapi_client/api/contact_api.rb +0 -88
  89. data/lib/openapi_client/api/custom_domains_api.rb +0 -253
  90. data/lib/openapi_client/api/customer_portal_api.rb +0 -700
  91. data/lib/openapi_client/api/delivery_details_api.rb +0 -146
  92. data/lib/openapi_client/api/devices_api.rb +0 -202
  93. data/lib/openapi_client/api/embed_api.rb +0 -128
  94. data/lib/openapi_client/api/endpoints_api.rb +0 -468
  95. data/lib/openapi_client/api/events_api.rb +0 -75
  96. data/lib/openapi_client/api/health_api.rb +0 -193
  97. data/lib/openapi_client/api/inbound_api.rb +0 -170
  98. data/lib/openapi_client/api/notifications_api.rb +0 -309
  99. data/lib/openapi_client/api/o_auth_api.rb +0 -181
  100. data/lib/openapi_client/api/outbound_ips_api.rb +0 -77
  101. data/lib/openapi_client/api/playground_api.rb +0 -143
  102. data/lib/openapi_client/api/rate_limits_api.rb +0 -252
  103. data/lib/openapi_client/api/routing_api.rb +0 -393
  104. data/lib/openapi_client/api/schemas_api.rb +0 -268
  105. data/lib/openapi_client/api/search_api.rb +0 -96
  106. data/lib/openapi_client/api/simulator_api.rb +0 -82
  107. data/lib/openapi_client/api/sso_api.rb +0 -241
  108. data/lib/openapi_client/api/stats_api.rb +0 -77
  109. data/lib/openapi_client/api/stream_api.rb +0 -88
  110. data/lib/openapi_client/api/teams_api.rb +0 -476
  111. data/lib/openapi_client/api/templates_api.rb +0 -213
  112. data/lib/openapi_client/api/transforms_api.rb +0 -368
  113. data/lib/openapi_client/api/webhooks_api.rb +0 -534
  114. data/lib/openapi_client/api_client.rb +0 -397
  115. data/lib/openapi_client/api_model_base.rb +0 -88
  116. data/lib/openapi_client/configuration.rb +0 -312
  117. data/lib/openapi_client/models/admin_revenue_get200_response_inner.rb +0 -165
  118. data/lib/openapi_client/models/admin_sdk_update_post_request.rb +0 -156
  119. data/lib/openapi_client/models/admin_users_id_plan_put_request.rb +0 -181
  120. data/lib/openapi_client/models/admin_users_id_status_put_request.rb +0 -147
  121. data/lib/openapi_client/models/alert_rule.rb +0 -237
  122. data/lib/openapi_client/models/api_key_info.rb +0 -185
  123. data/lib/openapi_client/models/apply_template_request.rb +0 -173
  124. data/lib/openapi_client/models/apply_template_response.rb +0 -156
  125. data/lib/openapi_client/models/auth2fa_enable_post200_response.rb +0 -156
  126. data/lib/openapi_client/models/auth_login_post200_response.rb +0 -104
  127. data/lib/openapi_client/models/auth_response.rb +0 -167
  128. data/lib/openapi_client/models/batch_replay_request.rb +0 -166
  129. data/lib/openapi_client/models/batch_response.rb +0 -160
  130. data/lib/openapi_client/models/batch_response_errors_inner.rb +0 -156
  131. data/lib/openapi_client/models/batch_webhook_request.rb +0 -166
  132. data/lib/openapi_client/models/billing_portal_post200_response.rb +0 -147
  133. data/lib/openapi_client/models/change_password_request.rb +0 -199
  134. data/lib/openapi_client/models/change_role_request.rb +0 -188
  135. data/lib/openapi_client/models/confirm2fa_request.rb +0 -182
  136. data/lib/openapi_client/models/contact_request.rb +0 -242
  137. data/lib/openapi_client/models/contact_response.rb +0 -156
  138. data/lib/openapi_client/models/create_alert_request.rb +0 -277
  139. data/lib/openapi_client/models/create_api_key_response.rb +0 -175
  140. data/lib/openapi_client/models/create_endpoint_request.rb +0 -288
  141. data/lib/openapi_client/models/create_team_request.rb +0 -164
  142. data/lib/openapi_client/models/create_transform_rule_request.rb +0 -216
  143. data/lib/openapi_client/models/create_webhook_request.rb +0 -201
  144. data/lib/openapi_client/models/custom_domains_post_request.rb +0 -147
  145. data/lib/openapi_client/models/customer_response.rb +0 -256
  146. data/lib/openapi_client/models/delivery.rb +0 -246
  147. data/lib/openapi_client/models/delivery_attempt.rb +0 -205
  148. data/lib/openapi_client/models/delivery_list_response.rb +0 -176
  149. data/lib/openapi_client/models/delivery_trend_response.rb +0 -158
  150. data/lib/openapi_client/models/delivery_trend_response_buckets_inner.rb +0 -174
  151. data/lib/openapi_client/models/device_token_response.rb +0 -174
  152. data/lib/openapi_client/models/disable2fa_request.rb +0 -164
  153. data/lib/openapi_client/models/enable2fa_request.rb +0 -164
  154. data/lib/openapi_client/models/endpoint.rb +0 -321
  155. data/lib/openapi_client/models/endpoint_health.rb +0 -183
  156. data/lib/openapi_client/models/endpoints_endpoint_id_transforms_test_post_request.rb +0 -156
  157. data/lib/openapi_client/models/endpoints_id_rotate_secret_post200_response.rb +0 -156
  158. data/lib/openapi_client/models/error.rb +0 -165
  159. data/lib/openapi_client/models/forgot_password_request.rb +0 -164
  160. data/lib/openapi_client/models/invite_request.rb +0 -207
  161. data/lib/openapi_client/models/invoice_response.rb +0 -183
  162. data/lib/openapi_client/models/latency_trend_response.rb +0 -167
  163. data/lib/openapi_client/models/latency_trend_response_buckets_inner.rb +0 -165
  164. data/lib/openapi_client/models/login_request.rb +0 -190
  165. data/lib/openapi_client/models/notification.rb +0 -193
  166. data/lib/openapi_client/models/notification_list_response.rb +0 -167
  167. data/lib/openapi_client/models/notification_preferences.rb +0 -201
  168. data/lib/openapi_client/models/notifications_unread_count_get200_response.rb +0 -147
  169. data/lib/openapi_client/models/outbound_ips_response.rb +0 -158
  170. data/lib/openapi_client/models/paginated_users.rb +0 -176
  171. data/lib/openapi_client/models/playground_get200_response.rb +0 -160
  172. data/lib/openapi_client/models/portal_notifications_put200_response.rb +0 -156
  173. data/lib/openapi_client/models/portal_profile.rb +0 -184
  174. data/lib/openapi_client/models/refresh_token_request.rb +0 -164
  175. data/lib/openapi_client/models/register_device_request.rb +0 -208
  176. data/lib/openapi_client/models/register_request.rb +0 -201
  177. data/lib/openapi_client/models/register_schema_request.rb +0 -191
  178. data/lib/openapi_client/models/resend_verification_request.rb +0 -164
  179. data/lib/openapi_client/models/reset_password_request.rb +0 -199
  180. data/lib/openapi_client/models/retry_policy.rb +0 -216
  181. data/lib/openapi_client/models/routing_info.rb +0 -193
  182. data/lib/openapi_client/models/search_result.rb +0 -158
  183. data/lib/openapi_client/models/simulator_post_request.rb +0 -165
  184. data/lib/openapi_client/models/sso_config_post_request.rb +0 -190
  185. data/lib/openapi_client/models/stats_response.rb +0 -210
  186. data/lib/openapi_client/models/stream_params.rb +0 -201
  187. data/lib/openapi_client/models/subscription_response.rb +0 -201
  188. data/lib/openapi_client/models/success_rate_response.rb +0 -183
  189. data/lib/openapi_client/models/system_stats.rb +0 -185
  190. data/lib/openapi_client/models/system_stats_plan_breakdown_inner.rb +0 -156
  191. data/lib/openapi_client/models/system_status.rb +0 -210
  192. data/lib/openapi_client/models/system_status_components_inner.rb +0 -184
  193. data/lib/openapi_client/models/team.rb +0 -165
  194. data/lib/openapi_client/models/team_detail_response.rb +0 -169
  195. data/lib/openapi_client/models/team_invite.rb +0 -174
  196. data/lib/openapi_client/models/team_member.rb +0 -193
  197. data/lib/openapi_client/models/test_webhook_request.rb +0 -199
  198. data/lib/openapi_client/models/test_webhook_response.rb +0 -174
  199. data/lib/openapi_client/models/transform_rule.rb +0 -201
  200. data/lib/openapi_client/models/two_factor_required_response.rb +0 -165
  201. data/lib/openapi_client/models/update_endpoint_request.rb +0 -278
  202. data/lib/openapi_client/models/update_notification_preferences.rb +0 -195
  203. data/lib/openapi_client/models/update_profile_request.rb +0 -190
  204. data/lib/openapi_client/models/update_routing_request.rb +0 -190
  205. data/lib/openapi_client/models/upgrade_request.rb +0 -209
  206. data/lib/openapi_client/models/upgrade_response.rb +0 -166
  207. data/lib/openapi_client/models/usage_response.rb +0 -201
  208. data/lib/openapi_client/models/user_summary.rb +0 -193
  209. data/lib/openapi_client/models/validate_event_request.rb +0 -164
  210. data/lib/openapi_client/models/verify2fa_request.rb +0 -208
  211. data/lib/openapi_client/models/verify_email_request.rb +0 -164
  212. data/lib/openapi_client/models/webhook_template.rb +0 -183
  213. data/lib/openapi_client/version.rb +0 -15
  214. data/lib/openapi_client.rb +0 -169
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ # This file is @generated
3
+ require "json"
4
+
5
+ module HookSniff
6
+ class MessageIn
7
+ # Optionally creates a new application alongside the message.
8
+ #
9
+ # If the application id or uid that is used in the path already exists, this argument is ignored.
10
+ attr_accessor :application
11
+ # List of free-form identifiers that endpoints can filter by
12
+ attr_accessor :channels
13
+ # The date and time at which the message will be delivered.
14
+ #
15
+ # Note that this time is best-effort-only. Must be at least one minute and no more than 24 hours in the future.
16
+ attr_accessor :deliver_at
17
+ # Optional unique identifier for the message
18
+ attr_accessor :event_id
19
+ # The event type's name
20
+ attr_accessor :event_type
21
+ # JSON payload to send as the request body of the webhook.
22
+ #
23
+ # We also support sending non-JSON payloads. Please contact us for more information.
24
+ attr_accessor :payload
25
+ # Optional number of hours to retain the message payload. Note that this is mutually exclusive with `payloadRetentionPeriod`.
26
+ attr_accessor :payload_retention_hours
27
+ # Optional number of days to retain the message payload. Defaults to 90. Note that this is mutually exclusive with `payloadRetentionHours`.
28
+ attr_accessor :payload_retention_period
29
+ # List of free-form tags that can be filtered by when listing messages
30
+ attr_accessor :tags
31
+ # Extra parameters to pass to Transformations (for future use)
32
+ attr_accessor :transformations_params
33
+
34
+ ALL_FIELD ||= [
35
+ "application",
36
+ "channels",
37
+ "deliver_at",
38
+ "event_id",
39
+ "event_type",
40
+ "payload",
41
+ "payload_retention_hours",
42
+ "payload_retention_period",
43
+ "tags",
44
+ "transformations_params"
45
+ ].freeze
46
+ private_constant :ALL_FIELD
47
+
48
+ def initialize(attributes = {})
49
+ unless attributes.is_a?(Hash)
50
+ fail(ArgumentError, "The input argument (attributes) must be a hash in `HookSniff::MessageIn` new method")
51
+ end
52
+
53
+ attributes.each do |k, v|
54
+ unless ALL_FIELD.include?(k.to_s)
55
+ fail(ArgumentError, "The field #{k} is not part of HookSniff::MessageIn")
56
+ end
57
+
58
+ instance_variable_set("@#{k}", v)
59
+ instance_variable_set("@__#{k}_is_defined", true)
60
+ end
61
+ end
62
+
63
+ def self.deserialize(attributes = {})
64
+ attributes = attributes.transform_keys(&:to_s)
65
+ attrs = Hash.new
66
+ attrs["channels"] = attributes["channels"]
67
+ attrs["deliver_at"] = DateTime.rfc3339(attributes["deliverAt"]).to_time if attributes["deliverAt"]
68
+ attrs["event_id"] = attributes["eventId"]
69
+ attrs["event_type"] = attributes["eventType"]
70
+ attrs["payload"] = attributes["payload"]
71
+ attrs["payload_retention_hours"] = attributes["payloadRetentionHours"]
72
+ attrs["payload_retention_period"] = attributes["payloadRetentionPeriod"]
73
+ attrs["tags"] = attributes["tags"]
74
+ attrs["transformations_params"] = attributes["transformationsParams"]
75
+ new(attrs)
76
+ end
77
+
78
+ def serialize
79
+ out = Hash.new
80
+ out["application"] = HookSniff::serialize_schema_ref(@application) if @application
81
+ out["channels"] = HookSniff::serialize_primitive(@channels) if @channels
82
+ out["deliverAt"] = HookSniff::serialize_primitive(@deliver_at) if @deliver_at
83
+ out["eventId"] = HookSniff::serialize_primitive(@event_id) if @event_id
84
+ out["eventType"] = HookSniff::serialize_primitive(@event_type) if @event_type
85
+ out["payload"] = HookSniff::serialize_primitive(@payload) if @payload
86
+ out["payloadRetentionHours"] = HookSniff::serialize_primitive(@payload_retention_hours) if @payload_retention_hours
87
+ out["payloadRetentionPeriod"] = HookSniff::serialize_primitive(@payload_retention_period) if @payload_retention_period
88
+ out["tags"] = HookSniff::serialize_primitive(@tags) if @tags
89
+ out["transformationsParams"] = HookSniff::serialize_primitive(@transformations_params) if @transformations_params
90
+ out
91
+ end
92
+
93
+ # Serializes the object to a json string
94
+ # @return String
95
+ def to_json
96
+ JSON.dump(serialize)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+ # This file is @generated
3
+ require "json"
4
+
5
+ module HookSniff
6
+ class MessageOut
7
+ # List of free-form identifiers that endpoints can filter by
8
+ attr_accessor :channels
9
+ attr_accessor :deliver_at
10
+ # Optional unique identifier for the message
11
+ attr_accessor :event_id
12
+ # The event type's name
13
+ attr_accessor :event_type
14
+ # The Message's ID.
15
+ attr_accessor :id
16
+ attr_accessor :payload
17
+ attr_accessor :tags
18
+ attr_accessor :timestamp
19
+
20
+ ALL_FIELD ||= ["channels", "deliver_at", "event_id", "event_type", "id", "payload", "tags", "timestamp"].freeze
21
+ private_constant :ALL_FIELD
22
+
23
+ def initialize(attributes = {})
24
+ unless attributes.is_a?(Hash)
25
+ fail(ArgumentError, "The input argument (attributes) must be a hash in `HookSniff::MessageOut` new method")
26
+ end
27
+
28
+ attributes.each do |k, v|
29
+ unless ALL_FIELD.include?(k.to_s)
30
+ fail(ArgumentError, "The field #{k} is not part of HookSniff::MessageOut")
31
+ end
32
+
33
+ instance_variable_set("@#{k}", v)
34
+ instance_variable_set("@__#{k}_is_defined", true)
35
+ end
36
+ end
37
+
38
+ def self.deserialize(attributes = {})
39
+ attributes = attributes.transform_keys(&:to_s)
40
+ attrs = Hash.new
41
+ attrs["channels"] = attributes["channels"]
42
+ attrs["deliver_at"] = DateTime.rfc3339(attributes["deliverAt"]).to_time if attributes["deliverAt"]
43
+ attrs["event_id"] = attributes["eventId"]
44
+ attrs["event_type"] = attributes["eventType"]
45
+ attrs["id"] = attributes["id"]
46
+ attrs["payload"] = attributes["payload"]
47
+ attrs["tags"] = attributes["tags"]
48
+ attrs["timestamp"] = DateTime.rfc3339(attributes["timestamp"]).to_time
49
+ new(attrs)
50
+ end
51
+
52
+ def serialize
53
+ out = Hash.new
54
+ out["channels"] = HookSniff::serialize_primitive(@channels) if @channels
55
+ out["deliverAt"] = HookSniff::serialize_primitive(@deliver_at) if @deliver_at
56
+ out["eventId"] = HookSniff::serialize_primitive(@event_id) if @event_id
57
+ out["eventType"] = HookSniff::serialize_primitive(@event_type) if @event_type
58
+ out["id"] = HookSniff::serialize_primitive(@id) if @id
59
+ out["payload"] = HookSniff::serialize_primitive(@payload) if @payload
60
+ out["tags"] = HookSniff::serialize_primitive(@tags) if @tags
61
+ out["timestamp"] = HookSniff::serialize_primitive(@timestamp) if @timestamp
62
+ out
63
+ end
64
+
65
+ # Serializes the object to a json string
66
+ # @return String
67
+ def to_json
68
+ JSON.dump(serialize)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ # This file is @generated
3
+ module HookSniff
4
+ # The sending status of the message:
5
+ #
6
+ # - Success = 0
7
+ # - Pending = 1
8
+ # - Fail = 2
9
+ # - Sending = 3
10
+ # - Canceled = 4
11
+ class MessageStatus
12
+ SUCCESS = 0.freeze
13
+ PENDING = 1.freeze
14
+ FAIL = 2.freeze
15
+ SENDING = 3.freeze
16
+ CANCELED = 4.freeze
17
+
18
+ def self.all_vars
19
+ @all_vars ||= [SUCCESS, PENDING, FAIL, SENDING, CANCELED].freeze
20
+ end
21
+
22
+ def initialize(value)
23
+ unless MessageStatus.all_vars.include?(value)
24
+ raise "Invalid ENUM value '#{value}' for class #MessageStatus"
25
+ end
26
+
27
+ @value = value
28
+ end
29
+
30
+ def self.deserialize(value)
31
+ return value if MessageStatus.all_vars.include?(value)
32
+ raise "Invalid ENUM value '#{value}' for class #MessageStatus"
33
+ end
34
+
35
+ def serialize
36
+ @value
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ # This file is @generated
3
+ module HookSniff
4
+ class MessageStatusText
5
+ SUCCESS = "success".freeze
6
+ PENDING = "pending".freeze
7
+ FAIL = "fail".freeze
8
+ SENDING = "sending".freeze
9
+ CANCELED = "canceled".freeze
10
+
11
+ def self.all_vars
12
+ @all_vars ||= [SUCCESS, PENDING, FAIL, SENDING, CANCELED].freeze
13
+ end
14
+
15
+ def initialize(value)
16
+ unless MessageStatusText.all_vars.include?(value)
17
+ raise "Invalid ENUM value '#{value}' for class #MessageStatusText"
18
+ end
19
+
20
+ @value = value
21
+ end
22
+
23
+ def self.deserialize(value)
24
+ return value if MessageStatusText.all_vars.include?(value)
25
+ raise "Invalid ENUM value '#{value}' for class #MessageStatusText"
26
+ end
27
+
28
+ def serialize
29
+ @value
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # This file is @generated
3
+ module HookSniff
4
+ # Defines the ordering in a listing of results.
5
+ class Ordering
6
+ ASCENDING = "ascending".freeze
7
+ DESCENDING = "descending".freeze
8
+
9
+ def self.all_vars
10
+ @all_vars ||= [ASCENDING, DESCENDING].freeze
11
+ end
12
+
13
+ def initialize(value)
14
+ unless Ordering.all_vars.include?(value)
15
+ raise "Invalid ENUM value '#{value}' for class #Ordering"
16
+ end
17
+
18
+ @value = value
19
+ end
20
+
21
+ def self.deserialize(value)
22
+ return value if Ordering.all_vars.include?(value)
23
+ raise "Invalid ENUM value '#{value}' for class #Ordering"
24
+ end
25
+
26
+ def serialize
27
+ @value
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ # This file is @generated
3
+ module HookSniff
4
+ # The different classes of HTTP status codes:
5
+ #
6
+ # - CodeNone = 0
7
+ # - Code1xx = 100
8
+ # - Code2xx = 200
9
+ # - Code3xx = 300
10
+ # - Code4xx = 400
11
+ # - Code5xx = 500
12
+ class StatusCodeClass
13
+ CODE_NONE = 0.freeze
14
+ CODE1XX = 100.freeze
15
+ CODE2XX = 200.freeze
16
+ CODE3XX = 300.freeze
17
+ CODE4XX = 400.freeze
18
+ CODE5XX = 500.freeze
19
+
20
+ def self.all_vars
21
+ @all_vars ||= [CODE_NONE, CODE1XX, CODE2XX, CODE3XX, CODE4XX, CODE5XX].freeze
22
+ end
23
+
24
+ def initialize(value)
25
+ unless StatusCodeClass.all_vars.include?(value)
26
+ raise "Invalid ENUM value '#{value}' for class #StatusCodeClass"
27
+ end
28
+
29
+ @value = value
30
+ end
31
+
32
+ def self.deserialize(value)
33
+ return value if StatusCodeClass.all_vars.include?(value)
34
+ raise "Invalid ENUM value '#{value}' for class #StatusCodeClass"
35
+ end
36
+
37
+ def serialize
38
+ @value
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module HookSniff
3
+ class OperationalWebhook
4
+ def initialize(api_client) = @api_client = api_client
5
+ def list = @api_client.execute(method: :get, path: "/api/v1/operational-webhooks")
6
+ def create(body) = @api_client.execute(method: :post, path: "/api/v1/operational-webhooks", body: body)
7
+ def get(id) = @api_client.execute(method: :get, path: "/api/v1/operational-webhooks/#{id}")
8
+ def update(id, body) = @api_client.execute(method: :put, path: "/api/v1/operational-webhooks/#{id}", body: body)
9
+ def delete(id) = @api_client.execute(method: :delete, path: "/api/v1/operational-webhooks/#{id}")
10
+ def list_deliveries(id) = @api_client.execute(method: :get, path: "/api/v1/operational-webhooks/#{id}/deliveries")
11
+ end
12
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ require "date"
3
+
4
+ # Constant time string comparison, for fixed length strings.
5
+ # Code borrowed from ActiveSupport
6
+ # https://github.com/rails/rails/blob/75ac626c4e21129d8296d4206a1960563cc3d4aa/activesupport/lib/active_support/security_utils.rb#L33
7
+ #
8
+ # The values compared should be of fixed length, such as strings
9
+ # that have already been processed by HMAC. Raises in case of length mismatch.
10
+ module HookSniff
11
+ if defined?(OpenSSL.fixed_length_secure_compare)
12
+ def fixed_length_secure_compare(a, b)
13
+ OpenSSL.fixed_length_secure_compare(a, b)
14
+ end
15
+ else
16
+ def fixed_length_secure_compare(a, b)
17
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
18
+
19
+ l = a.unpack("C#{a.bytesize}")
20
+
21
+ res = 0
22
+ b.each_byte { |byte| res |= byte ^ l.shift }
23
+ res == 0
24
+ end
25
+ end
26
+
27
+ module_function :fixed_length_secure_compare
28
+
29
+ # Secure string comparison for strings of variable length.
30
+ #
31
+ # While a timing attack would not be able to discern the content of
32
+ # a secret compared via secure_compare, it is possible to determine
33
+ # the secret length. This should be considered when using secure_compare
34
+ # to compare weak, short secrets to user input.
35
+ def secure_compare(a, b)
36
+ a.length == b.length && fixed_length_secure_compare(a, b)
37
+ end
38
+
39
+ module_function :secure_compare
40
+
41
+ def serialize_primitive(v)
42
+ if v.kind_of?(Time)
43
+ v.utc.to_datetime.rfc3339
44
+ else
45
+ v
46
+ end
47
+ end
48
+
49
+ module_function :serialize_primitive
50
+
51
+ def deserialize_date(v)
52
+ DateTime.rfc3339(v)
53
+ end
54
+
55
+ module_function :deserialize_date
56
+
57
+ def serialize_schema_ref(v)
58
+ # Enums are a schema_ref but since we pass them around using the underlying value
59
+ # we need to check if they have the serialize method before calling it
60
+ if v.class.method_defined? :serialize
61
+ v.serialize
62
+ else
63
+ v
64
+ end
65
+ end
66
+
67
+ module_function :serialize_schema_ref
68
+
69
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HookSniff
4
+ # Validation errors have their own schema to provide context for invalid requests eg. mismatched types and out of bounds values. There may be any number of these per 422 UNPROCESSABLE ENTITY error.
5
+ class ValidationError
6
+ # The location as a [`Vec`] of [`String`]s -- often in the form `[\"body\", \"field_name\"]`, `[\"query\", \"field_name\"]`, etc. They may, however, be arbitrarily deep.
7
+ attr_accessor :loc
8
+
9
+ # The message accompanying the validation error item.
10
+ attr_accessor :msg
11
+
12
+ # The type of error, often \"type_error\" or \"value_error\", but sometimes with more context like as \"value_error.number.not_ge\"
13
+ attr_accessor :type
14
+
15
+ def initialize(attributes = {})
16
+ if (!attributes.is_a?(Hash))
17
+ fail(
18
+ ArgumentError,
19
+ "The input argument (attributes) must be a hash in `HookSniff::ValidationError` initialize method"
20
+ )
21
+ end
22
+
23
+ @loc = attributes[:"loc"]
24
+ @msg = attributes[:"msg"]
25
+ @type = attributes[:"type"]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HookSniff
4
+ VERSION = "1.1.0"
5
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HookSniff
4
+ class Webhook
5
+
6
+ def self.new_using_raw_bytes(secret)
7
+ self.new(secret.pack("C*").force_encoding("UTF-8"))
8
+ end
9
+
10
+ def initialize(secret)
11
+ if secret.start_with?(SECRET_PREFIX)
12
+ secret = secret[SECRET_PREFIX.length..-1]
13
+ end
14
+
15
+ @secret = Base64.decode64(secret)
16
+ end
17
+
18
+ def verify(payload, headers)
19
+ msgId = headers["hooksniff-id"]
20
+ msgSignature = headers["hooksniff-signature"]
21
+ msgTimestamp = headers["hooksniff-timestamp"]
22
+ if !msgSignature || !msgId || !msgTimestamp
23
+ msgId = headers["webhook-id"]
24
+ msgSignature = headers["webhook-signature"]
25
+ msgTimestamp = headers["webhook-timestamp"]
26
+ if !msgSignature || !msgId || !msgTimestamp
27
+ raise WebhookVerificationError, "Missing required headers"
28
+ end
29
+ end
30
+
31
+ verify_timestamp(msgTimestamp)
32
+
33
+ _, signature = sign(msgId, msgTimestamp, payload).split(",", 2)
34
+
35
+ passedSignatures = msgSignature.split(" ")
36
+ passedSignatures.each do |versionedSignature|
37
+ version, expectedSignature = versionedSignature.split(",", 2)
38
+ if version != "v1"
39
+ next
40
+ end
41
+
42
+ if ::HookSniff::secure_compare(signature, expectedSignature)
43
+ return nil if payload.empty?
44
+ return JSON.parse(payload, symbolize_names: true)
45
+ end
46
+ end
47
+
48
+ raise WebhookVerificationError, "No matching signature found"
49
+ end
50
+
51
+ def sign(msgId, timestamp, payload)
52
+ begin
53
+ now = Integer(timestamp)
54
+ rescue
55
+ raise WebhookSigningError, "Invalid timestamp"
56
+ end
57
+
58
+ toSign = "#{msgId}.#{timestamp}.#{payload}"
59
+ signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), @secret, toSign)).strip
60
+ return "v1,#{signature}"
61
+ end
62
+
63
+ private
64
+ SECRET_PREFIX = "whsec_"
65
+ TOLERANCE = 5 * 60
66
+
67
+ def verify_timestamp(timestampHeader)
68
+ begin
69
+ now = Integer(Time.now)
70
+ timestamp = Integer(timestampHeader)
71
+ rescue
72
+ raise WebhookVerificationError, "Invalid Signature Headers"
73
+ end
74
+
75
+ if timestamp < (now - TOLERANCE)
76
+ raise WebhookVerificationError, "Message timestamp too old"
77
+ end
78
+
79
+ if timestamp > (now + TOLERANCE)
80
+ raise WebhookVerificationError, "Message timestamp too new"
81
+ end
82
+ end
83
+ end
84
+ end
data/lib/hooksniff.rb ADDED
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "openssl"
5
+ require "base64"
6
+ require "uri"
7
+ require "logger"
8
+
9
+ # API
10
+ require "hooksniff/api/authentication"
11
+ require "hooksniff/api/endpoint"
12
+ require "hooksniff/api/event_type"
13
+ require "hooksniff/api/health"
14
+ require "hooksniff/api/message"
15
+ require "hooksniff/api/message_attempt"
16
+ require "hooksniff/api/statistics"
17
+
18
+ # Models
19
+ require "hooksniff/models/aggregate_event_types_out"
20
+ require "hooksniff/models/endpoint_created_event"
21
+ require "hooksniff/models/endpoint_created_event_data"
22
+ require "hooksniff/models/endpoint_deleted_event"
23
+ require "hooksniff/models/endpoint_deleted_event_data"
24
+ require "hooksniff/models/endpoint_disabled_event"
25
+ require "hooksniff/models/endpoint_disabled_event_data"
26
+ require "hooksniff/models/endpoint_enabled_event"
27
+ require "hooksniff/models/endpoint_enabled_event_data"
28
+ require "hooksniff/models/endpoint_headers_in"
29
+ require "hooksniff/models/endpoint_headers_out"
30
+ require "hooksniff/models/endpoint_headers_patch_in"
31
+ require "hooksniff/models/endpoint_in"
32
+ require "hooksniff/models/endpoint_out"
33
+ require "hooksniff/models/endpoint_patch"
34
+ require "hooksniff/models/endpoint_secret_out"
35
+ require "hooksniff/models/endpoint_secret_rotate_in"
36
+ require "hooksniff/models/endpoint_update"
37
+ require "hooksniff/models/endpoint_updated_event"
38
+ require "hooksniff/models/endpoint_updated_event_data"
39
+ require "hooksniff/models/event_in"
40
+ require "hooksniff/models/event_out"
41
+ require "hooksniff/models/event_type_in"
42
+ require "hooksniff/models/event_type_out"
43
+ require "hooksniff/models/event_type_patch"
44
+ require "hooksniff/models/event_type_update"
45
+ require "hooksniff/models/list_response_endpoint_out"
46
+ require "hooksniff/models/list_response_event_type_out"
47
+ require "hooksniff/models/list_response_message_attempt_out"
48
+ require "hooksniff/models/list_response_message_out"
49
+ require "hooksniff/models/message_attempt_exhausted_event"
50
+ require "hooksniff/models/message_attempt_exhausted_event_data"
51
+ require "hooksniff/models/message_attempt_failed_data"
52
+ require "hooksniff/models/message_attempt_failing_event"
53
+ require "hooksniff/models/message_attempt_failing_event_data"
54
+ require "hooksniff/models/message_attempt_log"
55
+ require "hooksniff/models/message_attempt_log_event"
56
+ require "hooksniff/models/message_attempt_out"
57
+ require "hooksniff/models/message_attempt_recovered_event"
58
+ require "hooksniff/models/message_attempt_recovered_event_data"
59
+ require "hooksniff/models/message_attempt_trigger_type"
60
+ require "hooksniff/models/message_in"
61
+ require "hooksniff/models/message_out"
62
+ require "hooksniff/models/message_status"
63
+ require "hooksniff/models/message_status_text"
64
+ require "hooksniff/models/ordering"
65
+ require "hooksniff/models/status_code_class"
66
+
67
+ # Core
68
+ require "hooksniff/api_error"
69
+ require "hooksniff/errors"
70
+ require "hooksniff/hooksniff"
71
+ require "hooksniff/util"
72
+ require "hooksniff/version"
73
+ require "hooksniff/webhook"
74
+ require "hooksniff/http_error_out"
75
+ require "hooksniff/http_validation_error"
76
+ require "hooksniff/validation_error"
77
+ require "hooksniff/hooksniff_http_client"
78
+ require "hooksniff/internal"
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "hooksniff"
5
+
6
+ class WebhookTest < Minitest::Test
7
+ SECRET = "whsec_dGVzdA=="
8
+ MSG_ID = "msg_test123"
9
+ TIMESTAMP = Time.now.to_i
10
+ PAYLOAD = '{"event":"test"}'
11
+
12
+ def sign(secret, msg_id, timestamp, payload)
13
+ decoded = Base64.decode64(secret.sub("whsec_", ""))
14
+ to_sign = "#{msg_id}.#{timestamp}.#{payload}"
15
+ sig = Base64.strict_encode64(
16
+ OpenSSL::HMAC.digest("SHA256", decoded, to_sign)
17
+ )
18
+ "v1,#{sig}"
19
+ end
20
+
21
+ def test_verify_valid_signature
22
+ wh = HookSniff::Webhook.new(SECRET)
23
+ sig = sign(SECRET, MSG_ID, TIMESTAMP, PAYLOAD)
24
+ headers = {
25
+ "webhook-id" => MSG_ID,
26
+ "webhook-timestamp" => TIMESTAMP.to_s,
27
+ "webhook-signature" => sig,
28
+ }
29
+ result = wh.verify(PAYLOAD, headers)
30
+ assert_equal({"event" => "test"}, result)
31
+ end
32
+
33
+ def test_reject_invalid_signature
34
+ wh = HookSniff::Webhook.new(SECRET)
35
+ headers = {
36
+ "webhook-id" => MSG_ID,
37
+ "webhook-timestamp" => TIMESTAMP.to_s,
38
+ "webhook-signature" => "v1,invalid",
39
+ }
40
+ assert_raises(HookSniff::WebhookVerificationError) do
41
+ wh.verify(PAYLOAD, headers)
42
+ end
43
+ end
44
+
45
+ def test_reject_old_timestamp
46
+ wh = HookSniff::Webhook.new(SECRET)
47
+ old_ts = Time.now.to_i - 600
48
+ sig = sign(SECRET, MSG_ID, old_ts, PAYLOAD)
49
+ headers = {
50
+ "webhook-id" => MSG_ID,
51
+ "webhook-timestamp" => old_ts.to_s,
52
+ "webhook-signature" => sig,
53
+ }
54
+ assert_raises(HookSniff::WebhookVerificationError) do
55
+ wh.verify(PAYLOAD, headers)
56
+ end
57
+ end
58
+
59
+ def test_svix_branded_headers
60
+ wh = HookSniff::Webhook.new(SECRET)
61
+ sig = sign(SECRET, MSG_ID, TIMESTAMP, PAYLOAD)
62
+ headers = {
63
+ "svix-id" => MSG_ID,
64
+ "svix-timestamp" => TIMESTAMP.to_s,
65
+ "svix-signature" => sig,
66
+ }
67
+ result = wh.verify(PAYLOAD, headers)
68
+ assert_equal({"event" => "test"}, result)
69
+ end
70
+ end
71
+
72
+ class ErrorTest < Minitest::Test
73
+ def test_create_error_from_status
74
+ err = HookSniff.create_error_from_status(400)
75
+ assert_instance_of HookSniff::BadRequestError, err
76
+ assert_equal 400, err.code
77
+
78
+ err = HookSniff.create_error_from_status(429)
79
+ assert_instance_of HookSniff::RateLimitError, err
80
+ assert_equal 429, err.code
81
+
82
+ err = HookSniff.create_error_from_status(500)
83
+ assert_instance_of HookSniff::InternalServerError, err
84
+ assert_equal 500, err.code
85
+ end
86
+ end