mirror of https://github.com/docusealco/docuseal
parent
1304849b55
commit
45ae954c0c
@ -0,0 +1,7 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class WebhookHmacController < ApplicationController
|
||||||
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
end
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<%= render 'shared/turbo_modal', title: t('webhook_security') do %>
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<div class="inline-flex justify-center">
|
||||||
|
<%= link_to t('secret'), webhook_secret_path(@webhook_url), class: 'block bg-base-200 md:min-w-[112px] text-sm font-semibold whitespace-nowrap py-1.5 px-4 rounded-l-3xl', data: { turbo_frame: 'modal' } %>
|
||||||
|
<%= link_to t('hmac'), webhook_hmac_path(@webhook_url), class: 'block bg-base-300 md:min-w-[112px] text-sm font-semibold whitespace-nowrap py-1.5 px-4 rounded-r-3xl', data: { turbo_frame: 'modal' } %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label" for="hmac_secret"><%= t('hmac_signing_secret') %></label>
|
||||||
|
<% token = @webhook_url.hmac_secret %>
|
||||||
|
<% obscured = "#{token[0, 10]}#{'*' * [token.length - 10, 0].max}" %>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<masked-input class="block w-full" data-token="<%= token %>">
|
||||||
|
<input id="hmac_secret" type="text" value="<%= obscured %>" class="base-input font-mono w-full" autocomplete="off" readonly>
|
||||||
|
</masked-input>
|
||||||
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm mt-2 opacity-70"><%= t('hmac_signature_header_hint_html', header: '<code>X-Docuseal-Signature</code>'.html_safe) %></p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddHmacToWebhookUrls < ActiveRecord::Migration[8.1]
|
||||||
|
class MigrationWebhookUrl < ApplicationRecord
|
||||||
|
self.table_name = 'webhook_urls'
|
||||||
|
|
||||||
|
encrypts :hmac_secret
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_column :webhook_urls, :hmac_secret, :text
|
||||||
|
|
||||||
|
MigrationWebhookUrl.find_each do |webhook_url|
|
||||||
|
webhook_url.update_columns(hmac_secret: WebhookUrls::Signatures.generate_secret)
|
||||||
|
end
|
||||||
|
|
||||||
|
change_column_null :webhook_urls, :hmac_secret, false
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :webhook_urls, :hmac_secret
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WebhookUrls
|
||||||
|
module Signatures
|
||||||
|
SECRET_PREFIX = 'whsec_'
|
||||||
|
SECRET_BYTES = 24
|
||||||
|
TOLERANCE = 5 * 60
|
||||||
|
|
||||||
|
InvalidSignatureError = Class.new(StandardError)
|
||||||
|
TimestampError = Class.new(StandardError)
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def generate_secret
|
||||||
|
SECRET_PREFIX + Base64.strict_encode64(SecureRandom.bytes(SECRET_BYTES))
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign(secret, body:, timestamp: Time.current.to_i)
|
||||||
|
"#{timestamp}.#{OpenSSL::HMAC.hexdigest('sha256', secret, "#{timestamp}.#{body}")}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify(secret, body:, header:, tolerance: TOLERANCE)
|
||||||
|
ts, sig = header.to_s.split('.', 2)
|
||||||
|
ts = Integer(ts, exception: false)
|
||||||
|
|
||||||
|
raise InvalidSignatureError unless ts && sig
|
||||||
|
|
||||||
|
now = Time.current.to_i
|
||||||
|
|
||||||
|
raise TimestampError, 'Too old' if ts < now - tolerance
|
||||||
|
raise TimestampError, 'In future' if ts > now + tolerance
|
||||||
|
|
||||||
|
expected = OpenSSL::HMAC.hexdigest('sha256', secret, "#{ts}.#{body}")
|
||||||
|
|
||||||
|
raise InvalidSignatureError unless ActiveSupport::SecurityUtils.secure_compare(expected, sig)
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in new issue