From 2c802b3a5a442ddf089763b332cc2e351fa5cd72 Mon Sep 17 00:00:00 2001 From: Usman Sarwar Date: Fri, 27 Feb 2026 16:20:25 +0500 Subject: [PATCH] SMTP fix --- Booting | 0 Rails | 0 Run | 0 .../submissions_download_controller.rb | 38 ++++++++++++-- .../submit_form_download_controller.rb | 2 +- app/javascript/submission_form/completed.vue | 34 +++++++++---- .../send_submitter_invitation_email_job.rb | 8 ++- app/models/submission.rb | 1 + app/views/submit_form/completed.html.erb | 4 +- config/dotenv.rb | 18 +++++++ config/environments/development.rb | 15 +++++- config/environments/production.rb | 2 +- config/routes.rb | 1 + docuseal.env | 10 +++- lib/action_mailer_configs_interceptor.rb | 7 +-- .../generate_result_attachments.rb | 51 ++++++++++++++----- lib/submitters.rb | 18 +++++-- lib/templates/create_attachments.rb | 8 +-- staged_changes.diff | 0 19 files changed, 172 insertions(+), 45 deletions(-) create mode 100644 Booting create mode 100644 Rails create mode 100644 Run create mode 100644 staged_changes.diff diff --git a/Booting b/Booting new file mode 100644 index 00000000..e69de29b diff --git a/Rails b/Rails new file mode 100644 index 00000000..e69de29b diff --git a/Run b/Run new file mode 100644 index 00000000..e69de29b diff --git a/app/controllers/submissions_download_controller.rb b/app/controllers/submissions_download_controller.rb index 4bcd3237..5ae8b2b0 100644 --- a/app/controllers/submissions_download_controller.rb +++ b/app/controllers/submissions_download_controller.rb @@ -10,11 +10,12 @@ class SubmissionsDownloadController < ApplicationController def index @submitter = Submitter.find_signed(params[:sig], purpose: :download_completed) if params[:sig].present? - signature_valid = + @signature_valid = if @submitter&.slug == params[:submitter_slug] true else @submitter = nil + false end @submitter ||= Submitter.find_by!(slug: params[:submitter_slug]) @@ -27,7 +28,7 @@ class SubmissionsDownloadController < ApplicationController Submissions::EnsureResultGenerated.call(last_submitter) - if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter) + if last_submitter.completed_at < TTL.ago && !@signature_valid && !current_user_submitter?(last_submitter) Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar) return head :not_found @@ -46,8 +47,32 @@ class SubmissionsDownloadController < ApplicationController end end + def signed_download_url + @submitter = Submitter.find_by!(slug: params[:slug]) + last_submitter = @submitter.submission.submitters.where.not(completed_at: nil).order(:completed_at).last + + return head :not_found unless last_submitter + + Submissions::EnsureResultGenerated.call(last_submitter) + + if last_submitter.completed_at < TTL.ago && !current_user_submitter?(last_submitter) + return head :not_found + end + + url = submitter_download_index_url( + @submitter.slug, + sig: @submitter.signed_id(expires_in: TTL, purpose: :download_completed) + ) + render json: { url: url } + end + private + def admin_download?(last_submitter) + # No valid signature link = download from app (e.g. submissions page) → serve unredacted + !@signature_valid + end + def current_user_submitter?(submitter) current_user && current_user.account.submitters.exists?(id: submitter.id) end @@ -56,7 +81,14 @@ class SubmissionsDownloadController < ApplicationController filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id, key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value - Submitters.select_attachments_for_download(submitter).map do |attachment| + attachments = if admin_download?(submitter) + Submissions::GenerateResultAttachments.call(submitter, for_admin: true) + Submitters.select_admin_attachments_for_download(submitter) + else + Submitters.select_attachments_for_download(submitter) + end + + attachments.map do |attachment| ActiveStorage::Blob.proxy_url( attachment.blob, expires_at: FILES_TTL.from_now.to_i, diff --git a/app/controllers/submit_form_download_controller.rb b/app/controllers/submit_form_download_controller.rb index d6e0b692..1aba46ca 100644 --- a/app/controllers/submit_form_download_controller.rb +++ b/app/controllers/submit_form_download_controller.rb @@ -9,7 +9,7 @@ class SubmitFormDownloadController < ApplicationController def index @submitter = Submitter.find_by!(slug: params[:submit_form_slug]) - return redirect_to submitter_download_index_path(@submitter.slug) if @submitter.completed_at? + return redirect_to submitter_download_index_path(@submitter.slug, sig: @submitter.signed_id(expires_in: 40.minutes, purpose: :download_completed)) if @submitter.completed_at? return head :unprocessable_content if @submitter.declined_at? || @submitter.submission.archived_at? || diff --git a/app/javascript/submission_form/completed.vue b/app/javascript/submission_form/completed.vue index 708843bf..414a3d63 100644 --- a/app/javascript/submission_form/completed.vue +++ b/app/javascript/submission_form/completed.vue @@ -214,21 +214,33 @@ export default { download () { this.isDownloading = true - fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then(async (response) => { - if (response.ok) { - const urls = await response.json() - const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent) - const isSafariIos = isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent) + fetch(this.baseUrl + `/submitters/${this.submitterSlug}/signed_download_url`) + .then(async (response) => { + if (!response.ok) { + throw new Error('failed') + } + const { url } = await response.json() + return fetch(url) + }) + .then(async (response) => { + if (response.ok) { + const urls = await response.json() + const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent) + const isSafariIos = isMobileSafariIos || /iPhone|iPad|iPod/i.test(navigator.userAgent) - if (isSafariIos && urls.length > 1) { - this.downloadSafariIos(urls) + if (isSafariIos && urls.length > 1) { + this.downloadSafariIos(urls) + } else { + this.downloadUrls(urls) + } } else { - this.downloadUrls(urls) + alert(this.t('failed_to_download_files')) } - } else { + }) + .catch(() => { alert(this.t('failed_to_download_files')) - } - }) + this.isDownloading = false + }) }, downloadUrls (urls) { const fileRequests = urls.map((url) => { diff --git a/app/jobs/send_submitter_invitation_email_job.rb b/app/jobs/send_submitter_invitation_email_job.rb index 49bf22bd..9aebcfec 100644 --- a/app/jobs/send_submitter_invitation_email_job.rb +++ b/app/jobs/send_submitter_invitation_email_job.rb @@ -21,7 +21,13 @@ class SendSubmitterInvitationEmailJob Submitters::ValidateSending.call(submitter, mail) - mail.deliver_now! + begin + mail.deliver_now! + rescue StandardError => e + Rollbar.error(e, submitter_id: submitter.id) if defined?(Rollbar) + + raise + end SubmissionEvent.create!(submitter:, event_type: 'send_email') diff --git a/app/models/submission.rb b/app/models/submission.rb index 2e2701f5..6f200771 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -68,6 +68,7 @@ class Submission < ApplicationRecord has_many_attached :preview_documents has_many_attached :documents + has_many_attached :admin_result_documents has_many :template_accesses, primary_key: :template_id, foreign_key: :template_id, dependent: nil, inverse_of: false diff --git a/app/views/submit_form/completed.html.erb b/app/views/submit_form/completed.html.erb index 004c9027..045703e5 100644 --- a/app/views/submit_form/completed.html.erb +++ b/app/views/submit_form/completed.html.erb @@ -29,8 +29,8 @@
<% end %> <% end %> - <% if @submitter.completed_at > 30.minutes.ago || (current_user && current_user.account.submitters.exists?(id: @submitter.id)) %> - + <% if @submitter.completed_at > 30.minutes.ago || (current_user && current_user.account.submitters.exists?(id: @submitter.id)) %> + <%= svg_icon('download', class: 'w-6 h-6') %> <%= t('download_documents') %> diff --git a/config/dotenv.rb b/config/dotenv.rb index c12e6b2c..8450b015 100644 --- a/config/dotenv.rb +++ b/config/dotenv.rb @@ -70,6 +70,24 @@ if ENV['RAILS_ENV'] == 'production' end end +# In non-production environments (e.g. development), also load `docuseal.env` +# so that SMTP and other settings defined there are available via ENV. +if ENV['RAILS_ENV'] != 'production' + dotenv_path = "#{ENV.fetch('WORKDIR', '.')}/docuseal.env" + + if File.exist?(dotenv_path) + File.foreach(dotenv_path) do |line| + line = line.strip + next if line.empty? || line.start_with?('#') + + key, value = line.split('=', 2) + next if key.to_s.empty? + + ENV[key] = value.to_s + end + end +end + if ENV['DATABASE_URL'].to_s.split('@').last.to_s.split('/').first.to_s.include?('_') require 'addressable' diff --git a/config/environments/development.rb b/config/environments/development.rb index bc9dc2d4..67fe4794 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -53,10 +53,21 @@ Rails.application.configure do config.active_storage.service = :disk config.active_storage.resolve_model_to_route = :rails_storage_proxy - # Don't care if the mailer can't send. + # Mailer: send real emails in development using SMTP (e.g. Gmail). config.action_mailer.raise_delivery_errors = true config.action_mailer.perform_deliveries = true - config.action_mailer.delivery_method = :letter_opener_web + config.action_mailer.delivery_method = :smtp + + config.action_mailer.smtp_settings = { + address: ENV.fetch('SMTP_ADDRESS', 'smtp.gmail.com'), + port: ENV.fetch('SMTP_PORT', 587), + domain: ENV.fetch('SMTP_DOMAIN', 'gmail.com'), + user_name: ENV.fetch('SMTP_USERNAME', nil), + password: ENV.fetch('SMTP_PASSWORD', nil), + authentication: (ENV['SMTP_PASSWORD'].present? ? ENV.fetch('SMTP_AUTHENTICATION', 'plain') : nil), + enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] != 'false', + openssl_verify_mode: OpenSSL::SSL::VERIFY_NONE + }.compact config.action_mailer.perform_caching = false diff --git a/config/environments/production.rb b/config/environments/production.rb index 36b0bfaa..23dea926 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -84,7 +84,7 @@ Rails.application.configure do port: ENV.fetch('SMTP_PORT', 587), domain: ENV.fetch('SMTP_DOMAIN', nil), user_name: ENV.fetch('SMTP_USERNAME', nil), - password: ENV.fetch('SMTP_PASSWORD', nil), + password: ENV.fetch('SMTP_PASSWORD', uqqq vkuy qivk lntt), authentication: ENV.fetch('SMTP_PASSWORD', nil).present? ? ENV.fetch('SMTP_AUTHENTICATION', 'plain') : nil, enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] != 'false' }.compact diff --git a/config/routes.rb b/config/routes.rb index be9d7d04..f057a7b0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -159,6 +159,7 @@ Rails.application.routes.draw do resources :send_submission_email, only: %i[create] resources :submitters, only: %i[], param: 'slug' do + get :signed_download_url, on: :member, to: 'submissions_download#signed_download_url' resources :download, only: %i[index], controller: 'submissions_download' resources :send_email, only: %i[create], controller: 'submitters_send_email' resources :debug, only: %i[index], controller: 'submissions_debug' if Rails.env.development? diff --git a/docuseal.env b/docuseal.env index 252808e5..d650640e 100644 --- a/docuseal.env +++ b/docuseal.env @@ -1,2 +1,10 @@ -DATABASE_URL= # keep empty to use sqlite or specify postgresql database URL +#DATABASE_URL= # keep empty to use sqlite or specify postgresql database URL SECRET_KEY_BASE=001f0f350ae2b0d7898d11cd9fd25cc2f193990470d9fd4b7feafa7d363794c55902d56ebd75a079f75e57b9706388ce9279f1db2153a0a63588bb867cb7371c + +#SMTP_ADDRESS=smtp.gmail.com +SMTP_PORT=587 +SMTP_DOMAIN=gmail.com +SMTP_USERNAME=huzaifahafeez10@gmail.com +SMTP_PASSWORD=uqqq vkuy qivk lntt +SMTP_ENABLE_STARTTLS_AUTO=true +SMTP_AUTHENTICATION=plain diff --git a/lib/action_mailer_configs_interceptor.rb b/lib/action_mailer_configs_interceptor.rb index b81cb45d..4fffb9d7 100644 --- a/lib/action_mailer_configs_interceptor.rb +++ b/lib/action_mailer_configs_interceptor.rb @@ -16,11 +16,12 @@ module ActionMailerConfigsInterceptor end if Rails.env.production? && Rails.application.config.action_mailer.delivery_method - from = ENV.fetch('SMTP_FROM').to_s.split(',').sample + from_candidates = ENV['SMTP_FROM'].to_s.split(',').map(&:strip).reject(&:blank?) + from = from_candidates.sample.presence || Array(message.from).first - if from.match?(User::FULL_EMAIL_REGEXP) + if from.present? && from.match?(User::FULL_EMAIL_REGEXP) message[:from] = message[:from].to_s.sub(User::EMAIL_REGEXP, from) - else + elsif from.present? message.from = from end diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index b9e57841..5472ebf8 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -81,10 +81,10 @@ module Submissions module_function # rubocop:disable Metrics - def call(submitter) + def call(submitter, for_admin: false) return generate_detached_signature_attachments(submitter) if detached_signature?(submitter) - pdfs_index = generate_pdfs(submitter) + pdfs_index = generate_pdfs(submitter, for_admin:) account = submitter.account submission = submitter.submission @@ -97,6 +97,8 @@ module Submissions result_attachments = submission.template_schema.filter_map do |item| + next if item.blank? || !item.is_a?(Hash) + pdf = pdfs_index[item['attachment_uuid']] next if pdf.nil? @@ -109,9 +111,11 @@ module Submissions build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:, uuid: item['attachment_uuid'], - name: item['name']) + name: item['name'], + for_admin:, submission:) end + submission.admin_result_documents.purge if for_admin return ApplicationRecord.no_touching { result_attachments.map { |e| e.tap(&:save!) } } if image_pdfs.size < 2 images_pdf = @@ -128,15 +132,18 @@ module Submissions tsa_url:, pkcs:, uuid: images_pdf_uuid(original_documents.select(&:image?)), - name: submission.name || submission.template.name + name: submission.name || submission.template.name, + for_admin:, + submission: ) + submission.admin_result_documents.purge if for_admin ApplicationRecord.no_touching do (result_attachments + [images_pdf_attachment]).map { |e| e.tap(&:save!) } end end - def generate_pdfs(submitter) + def generate_pdfs(submitter, for_admin: false) configs = submitter.account.account_configs.where(key: [AccountConfig::FLATTEN_RESULT_PDF_KEY, AccountConfig::WITH_SIGNATURE_ID, AccountConfig::WITH_FILE_LINKS_KEY, @@ -195,13 +202,17 @@ module Submissions fill_submitter_fields(submitter, submitter.account, pdfs_index, with_signature_id:, is_flatten:, with_submitter_timezone:, with_file_links:, - with_signature_id_reason:) + with_signature_id_reason:, + for_admin:) + + rasterize_redacted_pages(submitter.submission, pdfs_index) unless for_admin - rasterize_redacted_pages(submitter.submission, pdfs_index) + pdfs_index end def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:, with_headings: nil, - with_submitter_timezone: false, with_signature_id_reason: true, with_file_links: nil) + with_submitter_timezone: false, with_signature_id_reason: true, with_file_links: nil, + for_admin: false) cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center) attachments_data_cache = {} @@ -635,6 +646,8 @@ module Submissions end end when 'redact' + next if for_admin + area_x = area['x'] * width area_y = area['y'] * height area_w = area['w'] * width @@ -715,7 +728,7 @@ module Submissions pdfs_index end - def build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:, uuid:, name:) + def build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:, uuid:, name:, for_admin: false, submission: nil) io = StringIO.new pdf.trailer.info[:Creator] = info_creator @@ -764,13 +777,16 @@ module Submissions end end + record = for_admin ? submission : submitter + attachment_name = for_admin ? 'admin_result_documents' : 'documents' + ActiveStorage::Attachment.new( blob: ActiveStorage::Blob.create_and_upload!(io: io.tap(&:rewind), filename: "#{name}.pdf"), metadata: { original_uuid: uuid, analyzed: true, sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(io.string)) }, - name: 'documents', - record: submitter + name: attachment_name, + record: ) end # rubocop:enable Metrics @@ -806,12 +822,21 @@ module Submissions documents = latest_submitter&.documents&.preload(:blob).to_a.presence documents ||= submission.schema_documents.preload(:blob) - attachment_uuids = Submissions.filtered_conditions_schema(submission).pluck('attachment_uuid') + schema_documents = submission.schema_documents.preload(:blob) + schema_items = submission.template_schema || submission.template.schema + + attachment_uuids = + schema_items.filter_map do |item| + next unless item.is_a?(Hash) + + item['attachment_uuid'] + end.uniq + attachments_index = documents.index_by { |a| a.metadata['original_uuid'] || a.uuid } attachment_uuids.each_with_object({}) do |uuid, acc| attachment = attachments_index[uuid] - attachment ||= submission.schema_documents.preload(:blob).find { |a| a.uuid == uuid } + attachment ||= schema_documents.find { |a| a.uuid == uuid || a.metadata['original_uuid'] == uuid } next unless attachment diff --git a/lib/submitters.rb b/lib/submitters.rb index c557dd3e..51e45cd7 100644 --- a/lib/submitters.rb +++ b/lib/submitters.rb @@ -103,10 +103,7 @@ module Submitters end def select_attachments_for_download(submitter) - if AccountConfig.exists?(account_id: submitter.submission.account_id, - key: AccountConfig::COMBINE_PDF_RESULT_KEY, - value: true) && - submitter.submission.submitters.all?(&:completed_at?) && + if submitter.submission.submitters.all?(&:completed_at?) && submitter.submission.template_fields.none? { |f| f['type'] == 'verification' } return [submitter.submission.combined_document_attachment || Submissions::EnsureCombinedGenerated.call(submitter)] end @@ -120,6 +117,19 @@ module Submitters end end + def select_admin_attachments_for_download(submitter) + submission = submitter.submission + return [] unless submission.admin_result_documents.attached? + + original_documents = submission.schema_documents.preload(:blob) + is_more_than_two_images = original_documents.many?(&:image?) + + submission.admin_result_documents.reject do |attachment| + is_more_than_two_images && + original_documents.find { |a| a.uuid == (attachment.metadata['original_uuid'] || attachment.uuid) }&.image? + end + end + def create_attachment!(submitter, params) blob = if (file = params[:file]) diff --git a/lib/templates/create_attachments.rb b/lib/templates/create_attachments.rb index 507f4983..829998bb 100644 --- a/lib/templates/create_attachments.rb +++ b/lib/templates/create_attachments.rb @@ -142,9 +142,11 @@ module Templates # Use LibreOffice headless mode to convert to PDF success = system(libreoffice_path, '--headless', '--convert-to', 'pdf', '--outdir', output_dir, input_temp.path, out: File::NULL, err: File::NULL) - if success && File.exist?(output_file) - pdf_data = File.binread(output_file) - return pdf_data + if success + generated_pdf = Dir.glob(File.join(output_dir, '*.pdf')).first + if generated_pdf && File.exist?(generated_pdf) + return File.binread(generated_pdf) + end end rescue StandardError => e Rails.logger.warn("Document conversion failed: #{e.message}") diff --git a/staged_changes.diff b/staged_changes.diff new file mode 100644 index 00000000..e69de29b