diff --git a/app/controllers/account_configs_controller.rb b/app/controllers/account_configs_controller.rb
index e868d08a..66683e3f 100644
--- a/app/controllers/account_configs_controller.rb
+++ b/app/controllers/account_configs_controller.rb
@@ -15,6 +15,7 @@ class AccountConfigsController < ApplicationController
AccountConfig::ESIGNING_PREFERENCE_KEY,
AccountConfig::FORM_WITH_CONFETTI_KEY,
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
+ AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY,
AccountConfig::FORCE_SSO_AUTH_KEY,
AccountConfig::FLATTEN_RESULT_PDF_KEY,
AccountConfig::ENFORCE_SIGNING_ORDER_KEY,
diff --git a/app/controllers/api/active_storage_blobs_proxy_controller.rb b/app/controllers/api/active_storage_blobs_proxy_controller.rb
index 6f505992..8df3b523 100644
--- a/app/controllers/api/active_storage_blobs_proxy_controller.rb
+++ b/app/controllers/api/active_storage_blobs_proxy_controller.rb
@@ -13,7 +13,7 @@ module Api
def show
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
- if blob_uuid.blank? || (purp.present? && purp != 'blob') || (exp && exp < Time.current.to_i)
+ if blob_uuid.blank? || purp != 'blob'
Rollbar.error('Blob not found') if defined?(Rollbar)
return head :not_found
@@ -24,8 +24,9 @@ module Api
attachment = blob.attachments.take
@record = attachment.record
+ @record = @record.record if @record.is_a?(ActiveStorage::Attachment)
- authorization_check!(attachment) if exp.blank?
+ authorization_check!(attachment, @record, exp)
if request.headers['Range'].present?
send_blob_byte_range_data blob, request.headers['Range']
@@ -41,14 +42,19 @@ module Api
private
- def authorization_check!(attachment)
- is_authorized = attachment.name.in?(%w[logo preview_images]) ||
- (current_user && attachment.record.account.id == current_user.account_id) ||
- (current_user && !Docuseal.multitenant? && current_user.role == 'superadmin') ||
- !attachment.record.account.account_configs
- .find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
+ def authorization_check!(attachment, record, exp)
+ return if attachment.name == 'logo'
+ return if exp.to_i >= Time.current.to_i
- return if is_authorized
+ return if current_user && current_ability.can?(:read, record)
+
+ configs = record.account.account_configs.where(key: [AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
+ AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY])
+
+ require_auth = configs.any? { |c| c.key == AccountConfig::DOWNLOAD_LINKS_AUTH_KEY && c.value }
+ require_ttl = configs.none? { |c| c.key == AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY && c.value == false }
+
+ return if !require_ttl && !require_auth
Rollbar.error('Blob aunauthorized') if defined?(Rollbar)
diff --git a/app/controllers/api/form_events_controller.rb b/app/controllers/api/form_events_controller.rb
index 759cd230..4c881594 100644
--- a/app/controllers/api/form_events_controller.rb
+++ b/app/controllers/api/form_events_controller.rb
@@ -18,12 +18,14 @@ module Api
field: :completed_at
)
+ expires_at = Accounts.link_expires_at(current_account)
+
render json: {
data: submitters.map do |s|
{
event_type: 'form.completed',
timestamp: s.completed_at,
- data: Submitters::SerializeForWebhook.call(s)
+ data: Submitters::SerializeForWebhook.call(s, expires_at:)
}
end,
pagination: {
diff --git a/app/controllers/api/submission_documents_controller.rb b/app/controllers/api/submission_documents_controller.rb
index 283b55bf..148d499f 100644
--- a/app/controllers/api/submission_documents_controller.rb
+++ b/app/controllers/api/submission_documents_controller.rb
@@ -34,10 +34,12 @@ module Api
associations: [:blob]
).call
+ expires_at = Accounts.link_expires_at(current_account)
+
render json: {
id: @submission.id,
documents: documents.map do |attachment|
- { name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob) }
+ { name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:) }
end
}
end
diff --git a/app/controllers/api/submission_events_controller.rb b/app/controllers/api/submission_events_controller.rb
index 1a32023b..54d01443 100644
--- a/app/controllers/api/submission_events_controller.rb
+++ b/app/controllers/api/submission_events_controller.rb
@@ -14,16 +14,19 @@ module Api
:created_by_user, :submission_events,
template: :folder,
submitters: { documents_attachments: :blob, attachments_attachments: :blob },
- audit_trail_attachment: :blob
+ audit_trail_attachment: :blob,
+ combined_document_attachment: :blob
),
field: :completed_at)
+ expires_at = Accounts.link_expires_at(current_account)
+
render json: {
data: submissions.map do |s|
{
event_type: 'submission.completed',
timestamp: s.completed_at,
- data: Submissions::SerializeForApi.call(s, s.submitters)
+ data: Submissions::SerializeForApi.call(s, s.submitters, expires_at:)
}
end,
pagination: {
diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb
index 82839f05..32b51c17 100644
--- a/app/controllers/api/submissions_controller.rb
+++ b/app/controllers/api/submissions_controller.rb
@@ -18,10 +18,12 @@ module Api
combined_document_attachment: :blob,
audit_trail_attachment: :blob))
+ expires_at = Accounts.link_expires_at(current_account)
+
render json: {
data: submissions.map do |s|
Submissions::SerializeForApi.call(s, s.submitters, params,
- with_events: false, with_documents: false, with_values: false)
+ with_events: false, with_documents: false, with_values: false, expires_at:)
end,
pagination: {
count: submissions.size,
diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb
index 990f11ba..e7635591 100644
--- a/app/controllers/api/submitters_controller.rb
+++ b/app/controllers/api/submitters_controller.rb
@@ -14,9 +14,11 @@ module Api
documents_attachments: :blob, attachments_attachments: :blob)
)
+ expires_at = Accounts.link_expires_at(current_account)
+
render json: {
data: submitters.map do |s|
- Submitters::SerializeForApi.call(s, with_template: true, with_events: true, params:)
+ Submitters::SerializeForApi.call(s, with_template: true, with_events: true, params:, expires_at:)
end,
pagination: {
count: submitters.size,
diff --git a/app/controllers/api/templates_clone_controller.rb b/app/controllers/api/templates_clone_controller.rb
index ec101433..d87a2eb1 100644
--- a/app/controllers/api/templates_clone_controller.rb
+++ b/app/controllers/api/templates_clone_controller.rb
@@ -34,7 +34,7 @@ module Api
SearchEntries.enqueue_reindex(cloned_template)
- render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
+ render json: Templates::SerializeForApi.call(cloned_template, schema_documents:)
end
end
end
diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb
index a098573d..c8211b7f 100644
--- a/app/controllers/api/templates_controller.rb
+++ b/app/controllers/api/templates_controller.rb
@@ -24,13 +24,14 @@ module Api
name: :preview_images)
.preload(:blob)
+ expires_at = Accounts.link_expires_at(current_account)
+
render json: {
data: templates.map do |t|
- Templates::SerializeForApi.call(
- t,
- schema_documents.select { |e| e.record_id == t.id },
- preview_image_attachments
- )
+ Templates::SerializeForApi.call(t,
+ schema_documents: schema_documents.select { |e| e.record_id == t.id },
+ preview_image_attachments:,
+ expires_at:)
end,
pagination: {
count: templates.size,
diff --git a/app/controllers/submissions_export_controller.rb b/app/controllers/submissions_export_controller.rb
index e929c12d..c12a2e6e 100644
--- a/app/controllers/submissions_export_controller.rb
+++ b/app/controllers/submissions_export_controller.rb
@@ -10,11 +10,13 @@ class SubmissionsExportController < ApplicationController
attachments_attachments: :blob })
.order(id: :asc)
+ expires_at = Accounts.link_expires_at(current_account)
+
if params[:format] == 'csv'
- send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format]),
+ send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format], expires_at:),
filename: "#{@template.name}.csv"
elsif params[:format] == 'xlsx'
- send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format]),
+ send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format], expires_at:),
filename: "#{@template.name}.xlsx"
end
end
diff --git a/app/models/account_config.rb b/app/models/account_config.rb
index 66b48577..2ef39cbf 100644
--- a/app/models/account_config.rb
+++ b/app/models/account_config.rb
@@ -38,6 +38,7 @@ class AccountConfig < ApplicationRecord
FORM_PREFILL_SIGNATURE_KEY = 'form_prefill_signature'
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
DOWNLOAD_LINKS_AUTH_KEY = 'download_links_auth'
+ DOWNLOAD_LINKS_EXPIRE_KEY = 'download_links_expire'
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
WITH_SIGNATURE_ID = 'with_signature_id'
diff --git a/app/models/submission.rb b/app/models/submission.rb
index 4e61c691..351bcfa5 100644
--- a/app/models/submission.rb
+++ b/app/models/submission.rb
@@ -114,16 +114,16 @@ class Submission < ApplicationRecord
@fields_uuid_index ||= (template_fields || template.fields).index_by { |f| f['uuid'] }
end
- def audit_trail_url
+ def audit_trail_url(expires_at: nil)
return if audit_trail.blank?
- ActiveStorage::Blob.proxy_url(audit_trail.blob)
+ ActiveStorage::Blob.proxy_url(audit_trail.blob, expires_at:)
end
alias audit_log_url audit_trail_url
- def combined_document_url
+ def combined_document_url(expires_at: nil)
return if combined_document.blank?
- ActiveStorage::Blob.proxy_url(combined_document.blob)
+ ActiveStorage::Blob.proxy_url(combined_document.blob, expires_at:)
end
end
diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb
index ac63ff87..d5c5453f 100644
--- a/app/views/accounts/show.html.erb
+++ b/app/views/accounts/show.html.erb
@@ -130,6 +130,18 @@
<% end %>
<% end %>
+ <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY) %>
+ <% if can?(:manage, account_config) %>
+ <%= form_for account_config, url: account_configs_path, method: :post do |f| %>
+ <%= f.hidden_field :key %>
+
+
+ <%= t('expirable_file_download_links') %>
+
+ <%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
+
+ <% 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| %>
diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml
index 0e2805a7..c7a607f9 100644
--- a/config/locales/i18n.yml
+++ b/config/locales/i18n.yml
@@ -26,6 +26,7 @@ en: &en
select: Select
party: Party
edit_order: Edit Order
+ expirable_file_download_links: Expirable file download links
invite_form_fields: Invite form fields
default_parties: Default parties
authenticate_embedded_form_preview_with_token: Authenticate embedded form preview with token
@@ -901,6 +902,7 @@ en: &en
range_without_total: "%{from}-%{to} events"
es: &es
+ expirable_file_download_links: Enlaces de descarga de archivos con vencimiento
create_templates_with_private_access_by_default: Crear plantillas con acceso privado por defecto
party: Parte
edit_order: Edita Pedido
@@ -1782,6 +1784,7 @@ es: &es
range_without_total: "%{from}-%{to} eventos"
it: &it
+ expirable_file_download_links: Link di download di file con scadenza
create_templates_with_private_access_by_default: Crea modelli con accesso privato per impostazione predefinita
party: Parte
edit_order: Modifica Ordine
@@ -2663,6 +2666,7 @@ it: &it
range_without_total: "%{from}-%{to} eventi"
fr: &fr
+ expirable_file_download_links: Liens de téléchargement de fichiers expirables
create_templates_with_private_access_by_default: Créer des modèles avec un accès privé par défaut
party: Partie
edit_order: Modifier la commande
@@ -3547,6 +3551,7 @@ fr: &fr
range_without_total: "%{from} à %{to} événements"
pt: &pt
+ expirable_file_download_links: Links de download de arquivos com expiração
create_templates_with_private_access_by_default: Criar modelos com acesso privado por padrão
party: Parte
edit_order: Edita Pedido
@@ -4429,6 +4434,7 @@ pt: &pt
range_without_total: "%{from}-%{to} eventos"
de: &de
+ expirable_file_download_links: Ablaufbare Datei-Download-Links
create_templates_with_private_access_by_default: Vorlagen standardmäßig mit privatem Zugriff erstellen
party: Partei
edit_order: Bestellung bearbeiten
diff --git a/db/migrate/20250831125322_populate_expire_link_configs.rb b/db/migrate/20250831125322_populate_expire_link_configs.rb
new file mode 100644
index 00000000..352f2f77
--- /dev/null
+++ b/db/migrate/20250831125322_populate_expire_link_configs.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class PopulateExpireLinkConfigs < ActiveRecord::Migration[8.0]
+ disable_ddl_transaction!
+
+ class MigrationAccount < ActiveRecord::Base
+ self.table_name = 'accounts'
+ end
+
+ class MigrationAccountConfig < ActiveRecord::Base
+ self.table_name = 'account_configs'
+
+ serialize :value, coder: JSON
+ end
+
+ def up
+ MigrationAccount.find_each do |account|
+ config = MigrationAccountConfig.find_or_initialize_by(key: 'download_links_expire', account_id: account.id)
+
+ next if config.persisted?
+
+ config.value = false
+ config.save!
+ end
+ end
+
+ def down
+ nil
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 136d878a..bb59b018 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.0].define(version: 2025_07_18_121133) do
+ActiveRecord::Schema[8.0].define(version: 2025_08_31_125322) do
# These are extensions that must be enabled in order to support this database
enable_extension "btree_gin"
enable_extension "plpgsql"
diff --git a/lib/accounts.rb b/lib/accounts.rb
index 6153111b..5127d2c7 100644
--- a/lib/accounts.rb
+++ b/lib/accounts.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module Accounts
+ LINK_EXPIRES_AT = 40.minutes
+
module_function
def create_duplicate(account)
@@ -185,4 +187,11 @@ module Accounts
rescue TZInfo::InvalidTimezoneIdentifier
'UTC'
end
+
+ def link_expires_at(account)
+ return if AccountConfig.find_or_initialize_by(account: account,
+ key: AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY).value == false
+
+ LINK_EXPIRES_AT.from_now
+ end
end
diff --git a/lib/replace_email_variables.rb b/lib/replace_email_variables.rb
index 3cd2d698..4222e676 100644
--- a/lib/replace_email_variables.rb
+++ b/lib/replace_email_variables.rb
@@ -102,6 +102,7 @@ module ReplaceEmailVariables
return unless submitter
value = submitter.try(field_name)
+ expires_at = nil
if value_name
field = (submission.template_fields || submission.template.fields).find { |e| e['name'] == value_name }
@@ -114,7 +115,11 @@ module ReplaceEmailVariables
attachment = submitter.attachments.find { |e| e.uuid == attachment_uuid }
- ActiveStorage::Blob.proxy_url(attachment.blob) if attachment
+ if attachment
+ expires_at ||= Accounts.link_expires_at(Account.new(id: submission.account_id))
+
+ ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:)
+ end
else
value[field&.dig('uuid')]
end
diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb
index 81095648..2489c91f 100644
--- a/lib/submissions/generate_audit_trail.rb
+++ b/lib/submissions/generate_audit_trail.rb
@@ -391,8 +391,8 @@ module Submissions
composer.formatted_text_box(
Array.wrap(value).map do |uuid|
attachment = submitter.attachments.find { |a| a.uuid == uuid }
- link =
- ActiveStorage::Blob.proxy_url(attachment.blob)
+
+ link = r.submissions_preview_url(submission.slug, **Docuseal.default_url_options)
{ link:, text: "#{attachment.filename}\n", style: :link }
end,
@@ -489,6 +489,10 @@ module Submissions
padding: [5, 0, 0, 8],
position: :float, text_align: :left)
end
+
+ def r
+ Rails.application.routes.url_helpers
+ end
# rubocop:enable Metrics
end
end
diff --git a/lib/submissions/generate_export_files.rb b/lib/submissions/generate_export_files.rb
index 0742710a..b2b8f076 100644
--- a/lib/submissions/generate_export_files.rb
+++ b/lib/submissions/generate_export_files.rb
@@ -6,8 +6,8 @@ module Submissions
module_function
- def call(submissions, format: :csv)
- rows = build_table_rows(submissions)
+ def call(submissions, format: :csv, expires_at: nil)
+ rows = build_table_rows(submissions, expires_at:)
if format.to_sym == :csv
rows_to_csv(rows)
@@ -57,7 +57,7 @@ module Submissions
headers.map { |key| row.find { |e| e[:name] == key }&.dig(:value) }
end
- def build_table_rows(submissions)
+ def build_table_rows(submissions, expires_at: nil)
submissions.preload(submitters: [attachments_attachments: :blob, documents_attachments: :blob])
.find_each.map do |submission|
submission_data = []
@@ -69,7 +69,7 @@ module Submissions
submission_data += build_submission_data(submitter, submitter_name, submitters_count)
- submission_data += submitter_formatted_fields(submitter).map do |field|
+ submission_data += submitter_formatted_fields(submitter, expires_at:).map do |field|
{
name: column_name(field[:name], submitter_name, submitters_count),
value: field[:value]
@@ -81,7 +81,7 @@ module Submissions
submission_data += submitter.documents.map.with_index(1) do |attachment, index|
{
name: "#{I18n.t('document')} #{index}",
- value: ActiveStorage::Blob.proxy_url(attachment.blob)
+ value: ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:)
}
end
end
@@ -123,7 +123,7 @@ module Submissions
submitters_count > 1 ? "#{submitter_name} - #{name}" : name
end
- def submitter_formatted_fields(submitter)
+ def submitter_formatted_fields(submitter, expires_at: nil)
fields = submitter.submission.template_fields || submitter.submission.template.fields
template_fields = fields.select { |f| f['submitter_uuid'] == submitter.uuid }
@@ -142,11 +142,13 @@ module Submissions
value =
if template_field_type.in?(%w[image signature])
attachment = attachments_index[submitter_value]
- ActiveStorage::Blob.proxy_url(attachment.blob) if attachment
+
+ ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:) if attachment
elsif template_field_type == 'file'
Array.wrap(submitter_value).compact_blank.filter_map do |e|
attachment = attachments_index[e]
- ActiveStorage::Blob.proxy_url(attachment.blob) if attachment
+
+ ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:) if attachment
end
else
submitter_value
diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb
index 3a6f1b94..4cad84bf 100644
--- a/lib/submissions/generate_result_attachments.rb
+++ b/lib/submissions/generate_result_attachments.rb
@@ -201,13 +201,15 @@ module Submissions
attachments_data_cache = {}
- return pdfs_index if submitter.submission.template_fields.blank?
+ submission = submitter.submission
+
+ return pdfs_index if submission.template_fields.blank?
- with_headings = find_last_submitter(submitter.submission, submitter:).blank? if with_headings.nil?
+ with_headings = find_last_submitter(submission, submitter:).blank? if with_headings.nil?
locale = submitter.metadata.fetch('lang', account.locale)
- submitter.submission.template_fields.each do |field|
+ submission.template_fields.each do |field|
next if field['type'] == 'heading' && !with_headings
next if field['submitter_uuid'] != submitter.uuid && field['type'] != 'heading'
@@ -476,7 +478,7 @@ module Submissions
height_diff - (height_diff.zero? ? diff : 0)
],
A: { Type: :Action, S: :URI,
- URI: ActiveStorage::Blob.proxy_url(attachment.blob) }
+ URI: r.submissions_preview_url(submission.slug, **Docuseal.default_url_options) }
}
)
@@ -877,7 +879,7 @@ module Submissions
end
end
- def h
+ def r
Rails.application.routes.url_helpers
end
end
diff --git a/lib/submissions/serialize_for_api.rb b/lib/submissions/serialize_for_api.rb
index 92ad96e1..616803cf 100644
--- a/lib/submissions/serialize_for_api.rb
+++ b/lib/submissions/serialize_for_api.rb
@@ -4,7 +4,6 @@ module Submissions
module SerializeForApi
SERIALIZE_PARAMS = {
only: %i[id name slug source submitters_order expire_at created_at updated_at archived_at],
- methods: %i[audit_log_url combined_document_url],
include: {
submitters: { only: %i[id] },
template: { only: %i[id name external_id created_at updated_at],
@@ -15,11 +14,13 @@ module Submissions
module_function
- def call(submission, submitters = nil, params = {}, with_events: true, with_documents: true, with_values: true)
+ def call(submission, submitters = nil, params = {}, with_events: true, with_documents: true, with_values: true,
+ expires_at: Accounts.link_expires_at(Account.new(id: submission.account_id)))
submitters ||= submission.submitters.preload(documents_attachments: :blob, attachments_attachments: :blob)
serialized_submitters = submitters.map do |submitter|
- Submitters::SerializeForApi.call(submitter, with_documents:, with_events: false, with_values:, params:)
+ Submitters::SerializeForApi.call(submitter, with_documents:, with_events: false, with_values:, params:,
+ expires_at:)
end
json = submission.as_json(SERIALIZE_PARAMS)
@@ -30,8 +31,6 @@ module Submissions
json['submission_events'] = Submitters::SerializeForApi.serialize_events(submission.submission_events)
end
- json['combined_document_url'] ||= maybe_build_combined_url(submitters, submission, params)
-
if submitters.all?(&:completed_at?)
last_submitter = submitters.max_by(&:completed_at)
@@ -39,10 +38,16 @@ module Submissions
json['documents'] = serialized_submitters.find { |e| e['id'] == last_submitter.id }['documents']
end
+ json['audit_log_url'] = submission.audit_log_url(expires_at:)
+ json['combined_document_url'] = submission.combined_document_url(expires_at:)
+ json['combined_document_url'] ||= maybe_build_combined_url(submitters, submission, params, expires_at:)
+
json['status'] = 'completed'
json['completed_at'] = last_submitter.completed_at.as_json
else
json['documents'] = [] if with_documents
+ json['audit_log_url'] = nil
+ json['combined_document_url'] = nil
json['status'] = build_status(submission, submitters)
json['completed_at'] = nil
end
@@ -60,7 +65,7 @@ module Submissions
end
end
- def maybe_build_combined_url(submitters, submission, params)
+ def maybe_build_combined_url(submitters, submission, params, expires_at: nil)
return unless submitters.all?(&:completed_at?)
attachment = submission.combined_document_attachment
@@ -71,7 +76,7 @@ module Submissions
attachment = Submissions::GenerateCombinedAttachment.call(submitter)
end
- ActiveStorage::Blob.proxy_url(attachment.blob) if attachment
+ ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:) if attachment
end
end
end
diff --git a/lib/submitters/serialize_for_api.rb b/lib/submitters/serialize_for_api.rb
index ee5046c1..96a85c73 100644
--- a/lib/submitters/serialize_for_api.rb
+++ b/lib/submitters/serialize_for_api.rb
@@ -11,7 +11,7 @@ module Submitters
module_function
def call(submitter, with_template: false, with_events: false, with_documents: true, with_urls: false,
- with_values: true, params: {})
+ with_values: true, params: {}, expires_at: Accounts.link_expires_at(Account.new(id: submitter.account_id)))
ActiveRecord::Associations::Preloader.new(
records: [submitter],
associations: if with_documents
@@ -24,15 +24,19 @@ module Submitters
additional_attrs = {}
if params[:include].to_s.include?('fields')
- additional_attrs['fields'] = SerializeForWebhook.build_fields_array(submitter)
+ additional_attrs['fields'] = SerializeForWebhook.build_fields_array(submitter, expires_at:)
end
if with_template
additional_attrs['template'] = submitter.submission.template.as_json(only: %i[id name created_at updated_at])
end
- additional_attrs['values'] = SerializeForWebhook.build_values_array(submitter) if with_values
- additional_attrs['documents'] = SerializeForWebhook.build_documents_array(submitter) if with_documents
+ additional_attrs['values'] = SerializeForWebhook.build_values_array(submitter, expires_at:) if with_values
+
+ if with_documents
+ additional_attrs['documents'] = SerializeForWebhook.build_documents_array(submitter, expires_at:)
+ end
+
additional_attrs['preferences'] = submitter.preferences.except('default_values')
additional_attrs['submission_events'] = serialize_events(submitter.submission_events) if with_events
diff --git a/lib/submitters/serialize_for_webhook.rb b/lib/submitters/serialize_for_webhook.rb
index 9ef81cbc..0f9cb4a2 100644
--- a/lib/submitters/serialize_for_webhook.rb
+++ b/lib/submitters/serialize_for_webhook.rb
@@ -10,13 +10,13 @@ module Submitters
module_function
- def call(submitter)
+ def call(submitter, expires_at: Accounts.link_expires_at(Account.new(id: submitter.account_id)))
ActiveRecord::Associations::Preloader.new(
records: [submitter], associations: [documents_attachments: :blob, attachments_attachments: :blob]
).call
- values = build_values_array(submitter)
- documents = build_documents_array(submitter)
+ values = build_values_array(submitter, expires_at:)
+ documents = build_documents_array(submitter, expires_at:)
submission = submitter.submission
@@ -32,7 +32,7 @@ module Submitters
'preferences' => submitter.preferences.except('default_values'),
'values' => values,
'documents' => documents,
- 'audit_log_url' => submitter.submission.audit_log_url,
+ 'audit_log_url' => submitter.submission.audit_log_url(expires_at:),
'submission_url' => r.submissions_preview_url(submission.slug, **Docuseal.default_url_options),
'template' => submission.template.as_json(
only: %i[id name external_id created_at updated_at],
@@ -40,15 +40,15 @@ module Submitters
),
'submission' => {
'id' => submission.id,
- 'audit_log_url' => submission.audit_log_url,
- 'combined_document_url' => submission.combined_document_url,
+ 'audit_log_url' => submission.audit_log_url(expires_at:),
+ 'combined_document_url' => submission.combined_document_url(expires_at:),
'status' => build_submission_status(submission),
'url' => r.submissions_preview_url(submission.slug, **Docuseal.default_url_options),
'created_at' => submission.created_at.as_json
})
end
- def build_values_array(submitter)
+ def build_values_array(submitter, expires_at: nil)
fields = submitter.submission.template_fields.presence || submitter.submission&.template&.fields || []
attachments_index = submitter.attachments.index_by(&:uuid)
submitter_field_counters = Hash.new { 0 }
@@ -64,13 +64,13 @@ module Submitters
next if !submitter.values.key?(field['uuid']) && !submitter.completed_at?
- value = fetch_field_value(field, submitter.values[field['uuid']], attachments_index)
+ value = fetch_field_value(field, submitter.values[field['uuid']], attachments_index, expires_at:)
{ 'field' => field_name, 'value' => value }
end
end
- def build_fields_array(submitter)
+ def build_fields_array(submitter, expires_at: nil)
fields = submitter.submission.template_fields.presence || submitter.submission&.template&.fields || []
attachments_index = submitter.attachments.index_by(&:uuid)
submitter_field_counters = Hash.new { 0 }
@@ -86,7 +86,7 @@ module Submitters
next if !submitter.values.key?(field['uuid']) && !submitter.completed_at?
- value = fetch_field_value(field, submitter.values[field['uuid']], attachments_index)
+ value = fetch_field_value(field, submitter.values[field['uuid']], attachments_index, expires_at:)
{ 'name' => field_name, 'uuid' => field['uuid'], 'value' => value, 'readonly' => field['readonly'] == true }
end
@@ -104,26 +104,26 @@ module Submitters
end
end
- def build_documents_array(submitter)
+ def build_documents_array(submitter, expires_at: nil)
submitter.documents.map do |attachment|
- { 'name' => attachment.filename.base, 'url' => rails_storage_proxy_url(attachment) }
+ { 'name' => attachment.filename.base, 'url' => rails_storage_proxy_url(attachment, expires_at:) }
end
end
- def fetch_field_value(field, value, attachments_index)
+ def fetch_field_value(field, value, attachments_index, expires_at: nil)
if field['type'].in?(%w[image signature initials stamp payment])
- rails_storage_proxy_url(attachments_index[value])
+ rails_storage_proxy_url(attachments_index[value], expires_at:)
elsif field['type'] == 'file'
- Array.wrap(value).compact_blank.filter_map { |e| rails_storage_proxy_url(attachments_index[e]) }
+ Array.wrap(value).compact_blank.filter_map { |e| rails_storage_proxy_url(attachments_index[e], expires_at:) }
else
value
end
end
- def rails_storage_proxy_url(attachment)
+ def rails_storage_proxy_url(attachment, expires_at: nil)
return if attachment.blank?
- ActiveStorage::Blob.proxy_url(attachment.blob)
+ ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:)
end
def r
diff --git a/lib/templates/serialize_for_api.rb b/lib/templates/serialize_for_api.rb
index 1e1d2dcf..ec9cb628 100644
--- a/lib/templates/serialize_for_api.rb
+++ b/lib/templates/serialize_for_api.rb
@@ -14,7 +14,8 @@ module Templates
module_function
- def call(template, schema_documents = template.schema_documents.preload(:blob), preview_image_attachments = nil)
+ def call(template, schema_documents: template.schema_documents.preload(:blob), preview_image_attachments: nil,
+ expires_at: Accounts.link_expires_at(Account.new(id: template.account_id)))
json = template.as_json(SERIALIZE_PARAMS)
preview_image_attachments ||=
@@ -40,8 +41,8 @@ module Templates
{
'id' => attachment.id,
'uuid' => attachment.uuid,
- 'url' => ActiveStorage::Blob.proxy_url(attachment.blob),
- 'preview_image_url' => first_page_blob && ActiveStorage::Blob.proxy_url(first_page_blob),
+ 'url' => ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:),
+ 'preview_image_url' => first_page_blob && ActiveStorage::Blob.proxy_url(first_page_blob, expires_at:),
'filename' => attachment.filename
}
end
diff --git a/spec/requests/templates_spec.rb b/spec/requests/templates_spec.rb
index 7763d8cd..94c4205b 100644
--- a/spec/requests/templates_spec.rb
+++ b/spec/requests/templates_spec.rb
@@ -8,6 +8,10 @@ describe 'Templates API' do
let(:folder) { create(:template_folder, account:) }
let(:template_preferences) { { 'request_email_subject' => 'Subject text', 'request_email_body' => 'Body Text' } }
+ before do
+ allow(Accounts).to receive(:link_expires_at).and_return(Accounts::LINK_EXPIRES_AT)
+ end
+
describe 'GET /api/templates' do
it 'returns a list of templates' do
templates = [
@@ -211,8 +215,8 @@ describe 'Templates API' do
{
id: template.documents.first.id,
uuid: template.documents.first.uuid,
- url: ActiveStorage::Blob.proxy_url(attachment.blob),
- preview_image_url: ActiveStorage::Blob.proxy_url(first_page_blob),
+ url: ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: Accounts::LINK_EXPIRES_AT),
+ preview_image_url: ActiveStorage::Blob.proxy_url(first_page_blob, expires_at: Accounts::LINK_EXPIRES_AT),
filename: 'sample-document.pdf'
}
],
@@ -235,7 +239,7 @@ describe 'Templates API' do
folder_name: folder.name,
source: 'native',
external_id: template.external_id,
- application_key: template.external_id # Backward compatibility
+ application_key: template.external_id
}
end