prefill signature
This commit is contained in:
@@ -8,6 +8,7 @@ class AccountConfigsController < ApplicationController
|
||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||
AccountConfig::FORCE_MFA,
|
||||
AccountConfig::ALLOW_TO_RESUBMIT,
|
||||
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
||||
AccountConfig::ESIGNING_PREFERENCE_KEY,
|
||||
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
||||
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
||||
|
||||
@@ -1,16 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
class AttachmentsController < ApiBaseController
|
||||
skip_before_action :authenticate_user!
|
||||
skip_authorization_check
|
||||
class AttachmentsController < ActionController::API
|
||||
include ActionController::Cookies
|
||||
include ActiveStorage::SetCurrent
|
||||
|
||||
COOKIE_STORE_LIMIT = 10
|
||||
|
||||
def create
|
||||
submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||
|
||||
attachment = Submitters.create_attachment!(submitter, params)
|
||||
|
||||
if params[:remember_signature] == 'true' && submitter.email.present?
|
||||
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(submitter, attachment)
|
||||
end
|
||||
|
||||
render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type])
|
||||
end
|
||||
|
||||
def build_new_cookie_signatures_json(submitter, attachment)
|
||||
values =
|
||||
begin
|
||||
JSON.parse(cookies.encrypted[:signature_uuids].presence || '{}')
|
||||
rescue JSON::ParserError
|
||||
{}
|
||||
end
|
||||
|
||||
values[submitter.email] = attachment.uuid
|
||||
|
||||
values = values.to_a.last(COOKIE_STORE_LIMIT).to_h if values.size > COOKIE_STORE_LIMIT
|
||||
|
||||
values.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,12 +7,14 @@ class SubmitFormController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
skip_authorization_check
|
||||
|
||||
CONFIG_KEYS = [].freeze
|
||||
PRELOAD_ALL_PAGES_AMOUNT = 200
|
||||
|
||||
def show
|
||||
@submitter = Submitter.find_by!(slug: params[:slug])
|
||||
|
||||
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
|
||||
return render :archived if @submitter.submission.template.archived_at? || @submitter.submission.archived_at?
|
||||
|
||||
ActiveRecord::Associations::Preloader.new(
|
||||
records: [@submitter],
|
||||
@@ -34,11 +36,14 @@ class SubmitFormController < ApplicationController
|
||||
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
|
||||
.preload(:blob).index_by(&:uuid)
|
||||
|
||||
unless Docuseal.multitenant?
|
||||
@signature_attachment = Submitters::MaybeAssignDefaultSignature.call(@submitter, params, @attachments_index)
|
||||
end
|
||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||
|
||||
render(@submitter.submission.template.archived_at? || @submitter.submission.archived_at? ? :archived : :show)
|
||||
return unless @form_configs[:prefill_signature]
|
||||
|
||||
@signature_attachment =
|
||||
Submitters::MaybeAssignDefaultBrowserSignature.call(@submitter, params, cookies, @attachments_index.values)
|
||||
|
||||
@attachments_index[@signature_attachment.uuid] = @signature_attachment if @signature_attachment
|
||||
end
|
||||
|
||||
def update
|
||||
|
||||
@@ -70,6 +70,7 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
formData.append('file', file)
|
||||
formData.append('submitter_slug', this.dataset.slug)
|
||||
formData.append('name', 'attachments')
|
||||
formData.append('remember_signature', 'true')
|
||||
|
||||
return fetch('/api/attachments', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -23,6 +23,7 @@ safeRegisterElement('submission-form', class extends HTMLElement {
|
||||
withDisclosure: this.dataset.withDisclosure === 'true',
|
||||
withTypedSignature: this.dataset.withTypedSignature !== 'false',
|
||||
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
|
||||
rememberSignature: this.dataset.rememberSignature === 'true',
|
||||
values: reactive(JSON.parse(this.dataset.values)),
|
||||
completedButton: JSON.parse(this.dataset.completedButton || '{}'),
|
||||
withQrButton: true,
|
||||
|
||||
@@ -323,11 +323,12 @@
|
||||
:field="currentField"
|
||||
:previous-value="previousSignatureValueFor(currentField) || previousSignatureValue"
|
||||
:with-typed-signature="withTypedSignature"
|
||||
:remember-signature="rememberSignature"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:button-text="buttonText"
|
||||
:with-disclosure="withDisclosure"
|
||||
:with-qr-button="withQrButton"
|
||||
:submitter-slug="submitterSlug"
|
||||
:submitter="submitter"
|
||||
:show-field-names="showFieldNames"
|
||||
@attached="attachments.push($event)"
|
||||
@start="scrollIntoField(currentField)"
|
||||
@@ -549,6 +550,11 @@ export default {
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
rememberSignature: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
minimize: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
||||
@@ -255,8 +255,8 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
submitterSlug: {
|
||||
type: String,
|
||||
submitter: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
showFieldNames: {
|
||||
@@ -284,6 +284,11 @@ export default {
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
rememberSignature: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
attachmentsIndex: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -311,6 +316,9 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
submitterSlug () {
|
||||
return this.submitter.slug
|
||||
},
|
||||
computedPreviousValue () {
|
||||
if (this.isUsePreviousValue) {
|
||||
return this.previousValue
|
||||
@@ -521,6 +529,27 @@ export default {
|
||||
this.uploadImageInputKey = Math.random().toString()
|
||||
}
|
||||
},
|
||||
maybeSetSignedUuid (signedUuid) {
|
||||
try {
|
||||
if (window.localStorage && signedUuid && this.rememberSignature) {
|
||||
const values = window.localStorage.getItem('signed_signature_uuids')
|
||||
|
||||
let data
|
||||
|
||||
if (values) {
|
||||
data = JSON.parse(values)
|
||||
} else {
|
||||
data = {}
|
||||
}
|
||||
|
||||
data[this.submitter.email] = signedUuid
|
||||
|
||||
window.localStorage.setItem('signed_signature_uuids', JSON.stringify(data))
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
async submit () {
|
||||
if (this.modelValue || this.computedPreviousValue) {
|
||||
if (this.computedPreviousValue) {
|
||||
@@ -539,6 +568,7 @@ export default {
|
||||
formData.append('file', file)
|
||||
formData.append('submitter_slug', this.submitterSlug)
|
||||
formData.append('name', 'attachments')
|
||||
formData.append('remember_signature', this.rememberSignature)
|
||||
|
||||
return fetch(this.baseUrl + '/api/attachments', {
|
||||
method: 'POST',
|
||||
@@ -547,6 +577,8 @@ export default {
|
||||
this.$emit('attached', attachment)
|
||||
this.$emit('update:model-value', attachment.uuid)
|
||||
|
||||
this.maybeSetSignedUuid(attachment.signed_uuid)
|
||||
|
||||
return resolve(attachment)
|
||||
})
|
||||
}).catch((error) => {
|
||||
|
||||
@@ -32,6 +32,7 @@ class AccountConfig < ApplicationRecord
|
||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||
FORM_COMPLETED_MESSAGE_KEY = 'form_completed_message'
|
||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||
FORM_PREFILL_SIGNATURE_KEY = 'form_prefill_signature'
|
||||
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
||||
WEBHOOK_PREFERENCES_KEY = 'webhook_preferences'
|
||||
DOWNLOAD_LINKS_AUTH_KEY = 'download_links_auth'
|
||||
|
||||
@@ -75,6 +75,18 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORM_PREFILL_SIGNATURE_KEY) %>
|
||||
<% if can?(:manage, account_config) %>
|
||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span>
|
||||
Remember and pre-fill signatures
|
||||
</span>
|
||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY) %>
|
||||
<% if can?(:manage, account_config) %>
|
||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>
|
||||
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
|
||||
<% configs = Submitters::FormConfigs.call(submitter) %>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-message="<%= configs[:completed_message].to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>"></submission-form>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-message="<%= configs[:completed_message].to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>"></submission-form>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class="fixed bottom-0 w-full h-0 z-20">
|
||||
<div class="mx-auto" style="max-width: 1000px">
|
||||
<div class="relative md:mx-32">
|
||||
<%= render 'submission_form', attachments_index: @attachments_index, submitter: @submitter, signature_attachment: @signature_attachment %>
|
||||
<%= render 'submission_form', attachments_index: @attachments_index, submitter: @submitter, signature_attachment: @signature_attachment, configs: @form_configs %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,20 +5,21 @@ module Submitters
|
||||
DEFAULT_KEYS = [AccountConfig::FORM_COMPLETED_BUTTON_KEY,
|
||||
AccountConfig::FORM_COMPLETED_MESSAGE_KEY,
|
||||
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
||||
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
||||
AccountConfig::ALLOW_TYPED_SIGNATURE].freeze
|
||||
|
||||
module_function
|
||||
|
||||
def call(submitter, keys = [])
|
||||
configs = submitter.submission.account.account_configs
|
||||
.where(key: DEFAULT_KEYS + keys)
|
||||
configs = submitter.submission.account.account_configs.where(key: DEFAULT_KEYS + keys)
|
||||
|
||||
completed_button = configs.find { |e| e.key == AccountConfig::FORM_COMPLETED_BUTTON_KEY }&.value || {}
|
||||
completed_message = configs.find { |e| e.key == AccountConfig::FORM_COMPLETED_MESSAGE_KEY }&.value || {}
|
||||
with_typed_signature = configs.find { |e| e.key == AccountConfig::ALLOW_TYPED_SIGNATURE }&.value != false
|
||||
with_confetti = configs.find { |e| e.key == AccountConfig::FORM_WITH_CONFETTI_KEY }&.value != false
|
||||
completed_button = find_safe_value(configs, AccountConfig::FORM_COMPLETED_BUTTON_KEY) || {}
|
||||
completed_message = find_safe_value(configs, AccountConfig::FORM_COMPLETED_MESSAGE_KEY) || {}
|
||||
with_typed_signature = find_safe_value(configs, AccountConfig::ALLOW_TYPED_SIGNATURE) != false
|
||||
with_confetti = find_safe_value(configs, AccountConfig::FORM_WITH_CONFETTI_KEY) != false
|
||||
prefill_signature = find_safe_value(configs, AccountConfig::FORM_PREFILL_SIGNATURE_KEY) != false
|
||||
|
||||
attrs = { completed_button:, with_typed_signature:, with_confetti:, completed_message: }
|
||||
attrs = { completed_button:, with_typed_signature:, with_confetti:, completed_message:, prefill_signature: }
|
||||
|
||||
keys.each do |key|
|
||||
attrs[key.to_sym] = configs.find { |e| e.key == key.to_s }&.value
|
||||
@@ -26,5 +27,9 @@ module Submitters
|
||||
|
||||
attrs
|
||||
end
|
||||
|
||||
def find_safe_value(configs, key)
|
||||
configs.find { |e| e.key == key }&.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Submitters
|
||||
module MaybeAssignDefaultBrowserSignature
|
||||
SIGNED_UUID_PURPPOSE = 'signature'
|
||||
|
||||
module_function
|
||||
|
||||
def call(submitter, params, cookies = nil, attachments = [])
|
||||
if (value = params[:signature_src].presence || params[:signature].presence)
|
||||
find_or_create_signature_from_value(submitter, value, attachments)
|
||||
elsif params[:signed_signature_uuids].present?
|
||||
find_storage_signature(submitter, params[:signed_signature_uuids], attachments)
|
||||
elsif cookies
|
||||
find_session_signature(submitter, cookies, attachments)
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_create_signature_from_value(submitter, value, attachments)
|
||||
_, attachment = Submitters::NormalizeValues.normalize_attachment_value(value,
|
||||
'signature',
|
||||
submitter.account,
|
||||
attachments,
|
||||
submitter)
|
||||
|
||||
attachment.record ||= submitter
|
||||
|
||||
attachment.save!
|
||||
|
||||
attachment
|
||||
end
|
||||
|
||||
def sign_signature_uuid(uuid)
|
||||
ApplicationRecord.signed_id_verifier.generate(uuid, purpose: SIGNED_UUID_PURPPOSE)
|
||||
end
|
||||
|
||||
def verify_signature_uuid(signed_uuid)
|
||||
ApplicationRecord.signed_id_verifier.verified(signed_uuid, purpose: SIGNED_UUID_PURPPOSE)
|
||||
end
|
||||
|
||||
def find_storage_signature(submitter, signed_uuids, attachments)
|
||||
signed_uuid = signed_uuids[submitter.email]
|
||||
|
||||
return if signed_uuid.blank?
|
||||
|
||||
uuid = verify_signature_uuid(signed_uuid)
|
||||
|
||||
return if uuid.blank?
|
||||
|
||||
find_signature_from_uuid(submitter, uuid, attachments)
|
||||
end
|
||||
|
||||
def find_session_signature(submitter, cookies, attachments)
|
||||
values =
|
||||
begin
|
||||
JSON.parse(cookies.encrypted[:signature_uuids].presence || '{}')
|
||||
rescue JSON::ParserError
|
||||
{}
|
||||
end
|
||||
|
||||
return if values.blank?
|
||||
|
||||
uuid = values[submitter.email]
|
||||
|
||||
return if uuid.blank?
|
||||
|
||||
find_signature_from_uuid(submitter, uuid, attachments)
|
||||
end
|
||||
|
||||
def find_signature_from_uuid(submitter, uuid, attachments)
|
||||
signature_attachment = ActiveStorage::Attachment.find_by(uuid:)
|
||||
|
||||
return unless signature_attachment
|
||||
|
||||
return if signature_attachment.record.email != submitter.email
|
||||
|
||||
existing_attachment = attachments.find do |a|
|
||||
a.blob_id == signature_attachment.blob_id && submitter.id == a.record_id
|
||||
end
|
||||
|
||||
return existing_attachment if existing_attachment
|
||||
|
||||
submitter.attachments_attachments.create_or_find_by!(blob_id: signature_attachment.blob_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
+5
-10
@@ -1,10 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Submitters
|
||||
module MaybeAssignDefaultSignature
|
||||
module MaybeAssignDefaultEmailSignature
|
||||
module_function
|
||||
|
||||
def call(submitter, params, attachments_index)
|
||||
def call(submitter, params, attachments)
|
||||
return if params[:t].present? && params[:t] != SubmissionEvents.build_tracking_param(submitter, 'click_email')
|
||||
return if params[:t].blank? && !submitter.submission_events.exists?(event_type: :click_email)
|
||||
|
||||
@@ -12,18 +12,13 @@ module Submitters
|
||||
|
||||
return if signature_attachment.blank?
|
||||
|
||||
existing_attachment = attachments_index.values.find do |a|
|
||||
a.blob_id == signature_attachment.blob_id && submitter.id == signature_attachment.record_id
|
||||
existing_attachment = attachments.find do |a|
|
||||
a.blob_id == signature_attachment.blob_id && submitter.id == a.record_id
|
||||
end
|
||||
|
||||
return existing_attachment if existing_attachment
|
||||
|
||||
attachment =
|
||||
submitter.attachments_attachments.create_or_find_by!(blob_id: signature_attachment.blob_id)
|
||||
|
||||
attachments_index[attachment.uuid] = attachment
|
||||
|
||||
attachment
|
||||
submitter.attachments_attachments.create_or_find_by!(blob_id: signature_attachment.blob_id)
|
||||
end
|
||||
|
||||
def find_previous_signature(submitter)
|
||||
Reference in New Issue
Block a user