From c9e255f5892034a3d195500f3560f99d8083de80 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Sat, 24 Jun 2023 12:27:34 +0300 Subject: [PATCH] add signature verification --- app/controllers/api/attachments_controller.rb | 2 +- app/controllers/esign_settings_controller.rb | 31 ++++----- app/controllers/submit_form_controller.rb | 4 +- app/javascript/elements/file_dropzone.js | 51 +++++++------- app/javascript/submission_form/dropzone.vue | 22 +++++- app/views/esign_settings/_result.html.erb | 68 +++++++++++++++++++ app/views/esign_settings/index.html.erb | 41 ++++++++--- app/views/icons/_certificate.html.erb | 9 +++ app/views/icons/_circle_check.html.erb | 5 ++ app/views/icons/_cloud_upload.html.erb | 6 ++ app/views/icons/_file_text.html.erb | 8 +++ app/views/icons/_lock_access.html.erb | 9 +++ app/views/icons/_user.html.erb | 5 ++ app/views/icons/_x_circle.html.erb | 6 +- config/initializers/active_storage.rb | 7 ++ lib/generate_certificate.rb | 2 +- .../generate_result_attachments.rb | 6 +- package.json | 2 +- yarn.lock | 8 +-- 19 files changed, 231 insertions(+), 61 deletions(-) create mode 100644 app/views/esign_settings/_result.html.erb create mode 100644 app/views/icons/_certificate.html.erb create mode 100644 app/views/icons/_circle_check.html.erb create mode 100644 app/views/icons/_cloud_upload.html.erb create mode 100644 app/views/icons/_file_text.html.erb create mode 100644 app/views/icons/_lock_access.html.erb create mode 100644 app/views/icons/_user.html.erb diff --git a/app/controllers/api/attachments_controller.rb b/app/controllers/api/attachments_controller.rb index fc17a9b1..9a297da7 100644 --- a/app/controllers/api/attachments_controller.rb +++ b/app/controllers/api/attachments_controller.rb @@ -12,7 +12,7 @@ module Api attachment = ActiveStorage::Attachment.create!( blob:, name: params[:name], - record: submitter || current_account + record: submitter ) render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type]) diff --git a/app/controllers/esign_settings_controller.rb b/app/controllers/esign_settings_controller.rb index d0d307f7..4260b3a3 100644 --- a/app/controllers/esign_settings_controller.rb +++ b/app/controllers/esign_settings_controller.rb @@ -1,26 +1,25 @@ # frozen_string_literal: true class EsignSettingsController < ApplicationController - before_action :load_encrypted_config - def create - attachment = ActiveStorage::Attachment.find_by!(uuid: params[:attachment_uuid]) - - pdf = HexaPDF::Document.new(io: StringIO.new(attachment.download)) + blobs = + params[:blob_signed_ids].map do |sid| + ActiveStorage::Blob.find_signed(sid) + end - pdf.signatures - end + pdfs = + blobs.map do |blob| + HexaPDF::Document.new(io: StringIO.new(blob.download)) + end - private + cert = EncryptedConfig.find_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY).value - def load_encrypted_config - @encrypted_config = - EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY) - end + trusted_certs = [OpenSSL::X509::Certificate.new(cert['cert']), + OpenSSL::X509::Certificate.new(cert['sub_ca']), + OpenSSL::X509::Certificate.new(cert['root_ca'])] - def storage_configs - params.require(:encrypted_config).permit(value: {}).tap do |e| - e[:value].compact_blank! - end + render turbo_stream: turbo_stream.replace('result', partial: 'result', locals: { pdfs:, blobs:, trusted_certs: }) + rescue HexaPDF::MalformedPDFError + render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result')) end end diff --git a/app/controllers/submit_form_controller.rb b/app/controllers/submit_form_controller.rb index 2c5ad926..ef386e2c 100644 --- a/app/controllers/submit_form_controller.rb +++ b/app/controllers/submit_form_controller.rb @@ -10,6 +10,8 @@ class SubmitFormController < ApplicationController Submitter.preload(submission: { template: { documents_attachments: { preview_images_attachments: :blob } } }) .find_by!(slug: params[:slug]) + cookies.signed[:submitter_sid] = @submitter.signed_id + redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at? end @@ -18,7 +20,7 @@ class SubmitFormController < ApplicationController submitter.values.merge!(normalized_values) submitter.completed_at = Time.current if params[:completed] == 'true' - submitter.save + submitter.save! head :ok end diff --git a/app/javascript/elements/file_dropzone.js b/app/javascript/elements/file_dropzone.js index 24285b19..e6010337 100644 --- a/app/javascript/elements/file_dropzone.js +++ b/app/javascript/elements/file_dropzone.js @@ -6,8 +6,8 @@ import { target, targetable } from '@github/catalyst/lib/targetable' export default actionable(targetable(class extends HTMLElement { static [target.static] = [ 'loading', - 'input', - 'valueField' + 'icon', + 'input' ] connectedCallback () { @@ -30,8 +30,16 @@ export default actionable(targetable(class extends HTMLElement { }) } + toggleLoading () { + this.loading.classList.toggle('hidden') + this.icon.classList.toggle('hidden') + this.classList.toggle('opacity-50') + } + async uploadFiles (files) { - const blobs = await Promise.all( + this.toggleLoading() + + await Promise.all( Array.from(files).map(async (file) => { const upload = new DirectUpload( file, @@ -53,29 +61,26 @@ export default actionable(targetable(class extends HTMLElement { console.error(error) }) }) - ) + ).then((blobs) => { + if (this.dataset.submitOnUpload) { + this.querySelectorAll('[name="blob_signed_ids[]"]').forEach((e) => e.remove()) + } - await Promise.all( - blobs.map((blob) => { - return fetch('/api/attachments', { - method: 'POST', - body: JSON.stringify({ - name: this.dataset.name, - blob_signed_id: blob.signed_id, - submitter_slug: this.dataset.submitterSlug - }), - headers: { 'Content-Type': 'application/json' } - }).then(resp => resp.json()).then((data) => { - return data - }) - })).then((result) => { - result.forEach((attachment) => { - if (this.valueField) { - this.valueField.value = attachment.uuid - } + blobs.forEach((blob) => { + const input = document.createElement('input') - this.dispatchEvent(new CustomEvent('upload', { detail: attachment })) + input.type = 'hidden' + input.name = 'blob_signed_ids[]' + input.value = blob.signed_id + + this.append(input) }) + + if (this.dataset.submitOnUpload) { + this.closest('form').querySelector('button[type="submit"]').click() + } + }).finally(() => { + this.toggleLoading() }) } })) diff --git a/app/javascript/submission_form/dropzone.vue b/app/javascript/submission_form/dropzone.vue index 8447a079..47fd7ba6 100644 --- a/app/javascript/submission_form/dropzone.vue +++ b/app/javascript/submission_form/dropzone.vue @@ -7,10 +7,18 @@