From 7551d7c7e0ed52ab6f5f56f10a9cc0b2d7570c04 Mon Sep 17 00:00:00 2001 From: Eros Stein Date: Fri, 8 May 2026 07:24:55 -0300 Subject: [PATCH] Adding embedding functionality - fixing issues --- app/controllers/api/attachments_controller.rb | 57 ++++--- app/controllers/concerns/embed_cors.rb | 8 +- app/controllers/embed/forms_controller.rb | 143 +++++++++++++----- .../send_submission_email_controller.rb | 41 +++-- .../submit_form_metadata_controller.rb | 26 +++- 5 files changed, 186 insertions(+), 89 deletions(-) diff --git a/app/controllers/api/attachments_controller.rb b/app/controllers/api/attachments_controller.rb index e9bf5d5f..607d93ff 100644 --- a/app/controllers/api/attachments_controller.rb +++ b/app/controllers/api/attachments_controller.rb @@ -14,28 +14,8 @@ module Api set_embed_cors_headers - unless can_upload?(submitter) - Rollbar.error("Can't upload: #{submitter.id}") if defined?(Rollbar) - - return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content - end - - if params[:type].in?(%w[initials signature]) - image = Vips::Image.new_from_file(params[:file].path) - - if ImageUtils.blank?(image) - Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar) - - return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content - end - - if ImageUtils.error?(image) - Rollbar.error("Error signature: #{submitter.id}") if defined?(Rollbar) - - return render json: { error: "#{params[:type]} error, try to sign on another device" }, - status: :unprocessable_content - end - end + return if render_upload_error?(submitter) + return if render_signature_error?(submitter) attachment = Submitters.create_attachment!(submitter, params) @@ -50,6 +30,39 @@ module Api render json: { error: e.message }, status: :unprocessable_content end + private + + def render_upload_error?(submitter) + return false if can_upload?(submitter) + + Rollbar.error("Can't upload: #{submitter.id}") if defined?(Rollbar) + + render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content + end + + def render_signature_error?(submitter) + return false unless params[:type].in?(%w[initials signature]) + + image = Vips::Image.new_from_file(params[:file].path) + + return render_empty_signature_error(submitter) if ImageUtils.blank?(image) + return render_invalid_signature_error(submitter) if ImageUtils.error?(image) + + false + end + + def render_empty_signature_error(submitter) + Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar) + + render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content + end + + def render_invalid_signature_error(submitter) + Rollbar.error("Error signature: #{submitter.id}") if defined?(Rollbar) + + render json: { error: "#{params[:type]} error, try to sign on another device" }, status: :unprocessable_content + end + def can_upload?(submitter) !submitter.declined_at? && !submitter.completed_at? && diff --git a/app/controllers/concerns/embed_cors.rb b/app/controllers/concerns/embed_cors.rb index 98601df0..b3ede89f 100644 --- a/app/controllers/concerns/embed_cors.rb +++ b/app/controllers/concerns/embed_cors.rb @@ -38,9 +38,11 @@ module EmbedCors end def configured_embed_origins_for_account(account) - normalize_embed_origins( - account&.account_configs&.find_by(key: AccountConfig::EMBED_ALLOWED_ORIGINS_KEY)&.value - ) + return [] unless account + + config = account.account_configs.find_by(key: AccountConfig::EMBED_ALLOWED_ORIGINS_KEY) + + normalize_embed_origins(config&.value) end def configured_embed_origins diff --git a/app/controllers/embed/forms_controller.rb b/app/controllers/embed/forms_controller.rb index 53ded7e5..aeb55404 100644 --- a/app/controllers/embed/forms_controller.rb +++ b/app/controllers/embed/forms_controller.rb @@ -39,8 +39,7 @@ module Embed private def form_json_for_template(template) - raise ActiveRecord::RecordNotFound if template.archived_at? || template.account.archived_at? - raise ActiveRecord::RecordNotFound unless template.shared_link? + validate_shared_link_template!(template) attrs = submitter_params @@ -48,60 +47,35 @@ module Embed submitter = find_or_initialize_submitter(template, attrs) - if selected_template_submitter(template).blank? && filter_undefined_submitters(template).size > 1 && submitter.new_record? - return { error: I18n.t('this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link') } - end - - if submitter.new_record? - assign_submission_attributes(submitter, template) - Submissions::AssignDefinedSubmitters.call(submitter.submission) - else - submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent) - end + return shared_link_multiple_parties_error if multiple_parties_shared_link?(template, submitter) - submitter.values = values_param.presence || submitter.values - submitter.metadata = metadata_param.presence || submitter.metadata - submitter.external_id = params[:external_id].presence || submitter.external_id + prepare_shared_link_submitter(submitter, template) + assign_shared_link_params(submitter) - if template.preferences['shared_link_2fa'] == true && - !Submitters::AuthorizedForForm.pass_link_2fa?(submitter, nil, request) + if shared_link_2fa_required?(template, submitter) Submitters.send_shared_link_email_verification_code(submitter, request: request) - return { template: template_json(template), unverified_email: submitter.email, logo: logo_json(template.account) } + return unverified_shared_link_email_json(template, submitter) end - if submitter.errors.blank? && submitter.save - enqueue_new_submitter_jobs(submitter) if submitter.previous_changes.key?('id') - - form_json(submitter) - else - { error: submitter.errors.full_messages.to_sentence } - end + save_shared_link_submitter(submitter) end def form_json(submitter) raise ActiveRecord::RecordNotFound if submitter.account.archived_at? - return { expired_submitter: submitter_json(submitter), submission: submission_json(submitter.submission, submitter), - template: template_json(submitter.template), logo: logo_json(submitter.account) } if submitter.submission.expired? + unavailable_payload = unavailable_submitter_json(submitter) - return { completed_submitter: submitter_json(submitter), submission: submission_json(submitter.submission, submitter), - template: template_json(submitter.template), logo: logo_json(submitter.account) } if submitter.completed_at? + return unavailable_payload if unavailable_payload - return { expired_submitter: submitter_json(submitter), submission: submission_json(submitter.submission, submitter), - template: template_json(submitter.template), logo: logo_json(submitter.account) } if submitter.declined_at? + pending_payload = pending_verification_json(submitter) - unless Submitters::AuthorizedForForm.pass_email_2fa?(submitter, request) - return { submitter_email_2fa: submitter_json(submitter), - submission: submission_json(submitter.submission, submitter), - template: template_json(submitter.template), logo: logo_json(submitter.account) } - end + return pending_payload if pending_payload - unless Submitters::AuthorizedForForm.pass_link_2fa?(submitter, nil, request) - return { unverified_email: submitter.email, submission: submission_json(submitter.submission, submitter), - template: template_json(submitter.template), logo: logo_json(submitter.account) } - end + ready_submitter_json(submitter) + end + def ready_submitter_json(submitter) submission = submitter.submission Submissions.preload_with_pages(submission) @@ -118,14 +92,99 @@ module Embed } end + def unavailable_submitter_json(submitter) + return submitter_status_json(submitter, :expired_submitter) if submitter.submission.expired? + return submitter_status_json(submitter, :completed_submitter) if submitter.completed_at? + return submitter_status_json(submitter, :expired_submitter) if submitter.declined_at? + end + + def submitter_status_json(submitter, key) + { + key => submitter_json(submitter), + submission: submission_json(submitter.submission, submitter), + template: template_json(submitter.template), + logo: logo_json(submitter.account) + } + end + + def pending_verification_json(submitter) + unless Submitters::AuthorizedForForm.pass_email_2fa?(submitter, request) + return submitter_status_json(submitter, :submitter_email_2fa) + end + + return if Submitters::AuthorizedForForm.pass_link_2fa?(submitter, nil, request) + + { + unverified_email: submitter.email, + submission: submission_json(submitter.submission, submitter), + template: template_json(submitter.template), + logo: logo_json(submitter.account) + } + end + + def validate_shared_link_template!(template) + raise ActiveRecord::RecordNotFound if template.archived_at? || template.account.archived_at? + raise ActiveRecord::RecordNotFound unless template.shared_link? + end + + def multiple_parties_shared_link?(template, submitter) + selected_template_submitter(template).blank? && + filter_undefined_submitters(template).size > 1 && + submitter.new_record? + end + + def shared_link_multiple_parties_error + { error: I18n.t('this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link') } + end + + def prepare_shared_link_submitter(submitter, template) + if submitter.new_record? + assign_submission_attributes(submitter, template) + Submissions::AssignDefinedSubmitters.call(submitter.submission) + else + submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent) + end + end + + def assign_shared_link_params(submitter) + submitter.values = values_param.presence || submitter.values + submitter.metadata = metadata_param.presence || submitter.metadata + submitter.external_id = params[:external_id].presence || submitter.external_id + end + + def shared_link_2fa_required?(template, submitter) + template.preferences['shared_link_2fa'] == true && + !Submitters::AuthorizedForForm.pass_link_2fa?(submitter, nil, request) + end + + def unverified_shared_link_email_json(template, submitter) + { + template: template_json(template), + unverified_email: submitter.email, + logo: logo_json(template.account) + } + end + + def save_shared_link_submitter(submitter) + if submitter.errors.blank? && submitter.save + enqueue_new_submitter_jobs(submitter) if submitter.previous_changes.key?('id') + + form_json(submitter) + else + { error: submitter.errors.full_messages.to_sentence } + end + end + def template_json(template) template.as_json(only: %i[id name slug preferences schema submitters archived_at account_id]) end def submission_json(submission, submitter = nil) submission.as_json( - only: %i[id slug name source submitters_order expire_at archived_at created_at updated_at template_id account_id], - methods: %i[expired?], + only: %i[ + id slug name source submitters_order expire_at archived_at created_at updated_at template_id account_id + ], + methods: %i[expired?] ).merge( 'template_schema' => Submissions.filtered_conditions_schema(submission, include_submitter_uuid: submitter&.uuid), diff --git a/app/controllers/send_submission_email_controller.rb b/app/controllers/send_submission_email_controller.rb index ef8449e4..3c4fd9c9 100644 --- a/app/controllers/send_submission_email_controller.rb +++ b/app/controllers/send_submission_email_controller.rb @@ -12,22 +12,9 @@ class SendSubmissionEmailController < ApplicationController SEND_DURATION = 30.minutes def create - if params[:template_slug] - template = Template.find_by!(slug: params[:template_slug]) - - @submitter = - Submitter.completed.where(submission: template.submissions).find_by!(email: params[:email].to_s.downcase) - elsif params[:submission_slug] - submission = Submission.find_by(slug: params[:submission_slug]) + @submitter = find_completed_submitter - if submission - @submitter = Submitter.completed.find_by(submission: submission, email: params[:email].to_s.downcase) - end - - return redirect_to submissions_preview_completed_path(params[:submission_slug], status: :error) unless @submitter - else - @submitter = Submitter.completed.find_by!(slug: params[:submitter_slug]) - end + return redirect_to submissions_preview_completed_path(params[:submission_slug], status: :error) unless @submitter @embed_cors_account = @submitter.account set_embed_cors_headers @@ -44,6 +31,30 @@ class SendSubmissionEmailController < ApplicationController private + def find_completed_submitter + if params[:template_slug] + template = Template.find_by!(slug: params[:template_slug]) + + Submitter.completed.where(submission: template.submissions).find_by!(email: normalized_email_param) + elsif params[:submission_slug] + find_completed_submitter_for_submission + else + Submitter.completed.find_by!(slug: params[:submitter_slug]) + end + end + + def find_completed_submitter_for_submission + submission = Submission.find_by(slug: params[:submission_slug]) + + return unless submission + + Submitter.completed.find_by(submission: submission, email: normalized_email_param) + end + + def normalized_email_param + params[:email].to_s.downcase + end + def can_send?(submitter) return false if submitter.account.archived_at? return false if EmailEvent.exists?(tag: :submitter_documents_copy, email: submitter.email, emailable: submitter, diff --git a/app/controllers/submit_form_metadata_controller.rb b/app/controllers/submit_form_metadata_controller.rb index b34f51bc..81fcd88b 100644 --- a/app/controllers/submit_form_metadata_controller.rb +++ b/app/controllers/submit_form_metadata_controller.rb @@ -12,13 +12,7 @@ class SubmitFormMetadataController < ApplicationController set_embed_cors_headers - return head :not_found if submitter.declined_at? || - submitter.completed_at? || - submitter.submission.archived_at? || - submitter.submission.expired? || - submitter.submission.template&.archived_at? || - submitter.account.archived_at? || - !Submitters::AuthorizedForForm.call(submitter, current_user, request) + return head :not_found unless render_metadata?(submitter) submission = submitter.submission values = submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } @@ -39,4 +33,22 @@ class SubmitFormMetadataController < ApplicationController render json: { text_runs: text_runs } end + + private + + def render_metadata?(submitter) + return false if unavailable_submitter?(submitter) + return false unless Submitters::AuthorizedForForm.call(submitter, current_user, request) + + true + end + + def unavailable_submitter?(submitter) + submitter.declined_at? || + submitter.completed_at? || + submitter.submission.archived_at? || + submitter.submission.expired? || + submitter.submission.template&.archived_at? || + submitter.account.archived_at? + end end