Skip to content

Commit

Permalink
prefill signature
Browse files Browse the repository at this point in the history
  • Loading branch information
omohokcoj committed Jun 15, 2024
1 parent 9e66f84 commit f9d52e5
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 30 deletions.
1 change: 1 addition & 0 deletions app/controllers/account_configs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
27 changes: 24 additions & 3 deletions app/controllers/api/attachments_controller.rb
Original file line number Diff line number Diff line change
@@ -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
13 changes: 9 additions & 4 deletions app/controllers/submit_form_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -34,11 +36,14 @@ def show
@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)

return unless @form_configs[:prefill_signature]

@signature_attachment =
Submitters::MaybeAssignDefaultBrowserSignature.call(@submitter, params, cookies, @attachments_index.values)

render(@submitter.submission.template.archived_at? || @submitter.submission.archived_at? ? :archived : :show)
@attachments_index[@signature_attachment.uuid] = @signature_attachment if @signature_attachment
end

def update
Expand Down
1 change: 1 addition & 0 deletions app/javascript/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions app/javascript/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion app/javascript/submission_form/form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down Expand Up @@ -549,6 +550,11 @@ export default {
required: false,
default: null
},
rememberSignature: {
type: Boolean,
required: false,
default: false
},
minimize: {
type: Boolean,
required: false,
Expand Down
36 changes: 34 additions & 2 deletions app/javascript/submission_form/signature_step.vue
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,8 @@ export default {
type: Object,
required: true
},
submitterSlug: {
type: String,
submitter: {
type: Object,
required: true
},
showFieldNames: {
Expand Down Expand Up @@ -284,6 +284,11 @@ export default {
required: false,
default: true
},
rememberSignature: {
type: Boolean,
required: false,
default: false
},
attachmentsIndex: {
type: Object,
required: false,
Expand Down Expand Up @@ -311,6 +316,9 @@ export default {
}
},
computed: {
submitterSlug () {
return this.submitter.slug
},
computedPreviousValue () {
if (this.isUsePreviousValue) {
return this.previousValue
Expand Down Expand Up @@ -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) {
Expand All @@ -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',
Expand All @@ -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) => {
Expand Down
1 change: 1 addition & 0 deletions app/models/account_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
12 changes: 12 additions & 0 deletions app/views/accounts/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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| %>
Expand Down
3 changes: 1 addition & 2 deletions app/views/submit_form/_submission_form.html.erb
Original file line number Diff line number Diff line change
@@ -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>
2 changes: 1 addition & 1 deletion app/views/submit_form/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
19 changes: 12 additions & 7 deletions lib/submitters/form_configs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,31 @@ module FormConfigs
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
end

attrs
end

def find_safe_value(configs, key)
configs.find { |e| e.key == key }&.value
end
end
end
86 changes: 86 additions & 0 deletions lib/submitters/maybe_assign_default_browser_signature.rb
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit f9d52e5

Please sign in to comment.