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