diff --git a/app/controllers/esign_settings_controller.rb b/app/controllers/esign_settings_controller.rb
index 4260b3a3..b9138383 100644
--- a/app/controllers/esign_settings_controller.rb
+++ b/app/controllers/esign_settings_controller.rb
@@ -12,11 +12,9 @@ class EsignSettingsController < ApplicationController
HexaPDF::Document.new(io: StringIO.new(blob.download))
end
- cert = EncryptedConfig.find_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY).value
+ certs = Accounts.load_signing_certs(current_account)
- trusted_certs = [OpenSSL::X509::Certificate.new(cert['cert']),
- OpenSSL::X509::Certificate.new(cert['sub_ca']),
- OpenSSL::X509::Certificate.new(cert['root_ca'])]
+ trusted_certs = [certs[:cert], certs[:sub_ca], certs[:root_ca]]
render turbo_stream: turbo_stream.replace('result', partial: 'result', locals: { pdfs:, blobs:, trusted_certs: })
rescue HexaPDF::MalformedPDFError
diff --git a/app/controllers/setup_controller.rb b/app/controllers/setup_controller.rb
index e9a8a806..83bb3788 100644
--- a/app/controllers/setup_controller.rb
+++ b/app/controllers/setup_controller.rb
@@ -22,7 +22,7 @@ class SetupController < ApplicationController
if @user.save
encrypted_configs = [
{ key: EncryptedConfig::APP_URL_KEY, value: encrypted_config_params[:value] },
- { key: EncryptedConfig::ESIGN_CERTS_KEY, value: GenerateCertificate.call }
+ { key: EncryptedConfig::ESIGN_CERTS_KEY, value: GenerateCertificate.call.transform_values(&:to_pem) }
]
@account.encrypted_configs.create!(encrypted_configs)
diff --git a/app/javascript/elements/download_button.js b/app/javascript/elements/download_button.js
index 2e81a176..9fb292ba 100644
--- a/app/javascript/elements/download_button.js
+++ b/app/javascript/elements/download_button.js
@@ -25,7 +25,7 @@ export default targetable(class extends HTMLElement {
const link = document.createElement('a')
link.href = blobUrl
- link.setAttribute('download', decodeURI(resp.headers.get('content-disposition').split('"')[1]))
+ link.setAttribute('download', decodeURI(url.split('/').pop()))
link.click()
diff --git a/app/javascript/form.js b/app/javascript/form.js
index 6c2e7dd0..2c9453c6 100644
--- a/app/javascript/form.js
+++ b/app/javascript/form.js
@@ -12,6 +12,7 @@ window.customElements.define('submission-form', class extends HTMLElement {
submitterSlug: this.dataset.submitterSlug,
submitterUuid: this.dataset.submitterUuid,
authenticityToken: this.dataset.authenticityToken,
+ canSendEmail: this.dataset.canSendEmail === 'true',
values: reactive(JSON.parse(this.dataset.values)),
attachments: reactive(JSON.parse(this.dataset.attachments)),
fields: JSON.parse(this.dataset.fields)
diff --git a/app/javascript/submission_form/completed.vue b/app/javascript/submission_form/completed.vue
index 37d23dbc..1342de33 100644
--- a/app/javascript/submission_form/completed.vue
+++ b/app/javascript/submission_form/completed.vue
@@ -12,6 +12,7 @@
<% end %>
- <%= f.fields_for @encrypted_config || EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::APP_URL_KEY) do |ff| %>
-
- <%= ff.label :value, 'App URL', class: 'label' %>
- <%= ff.text_field :value, autocomplete: 'off', class: 'base-input' %>
-
+ <% unless Docuseal.multitenant? %>
+ <%= f.fields_for @encrypted_config || EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::APP_URL_KEY) do |ff| %>
+
+ <%= ff.label :value, 'App URL', class: 'label' %>
+ <%= ff.text_field :value, autocomplete: 'off', class: 'base-input' %>
+
+ <% end %>
<% end %>
<%= f.button button_title(title: 'Update', disabled_with: 'Updating'), class: 'base-button' %>
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb
index 25d6959f..3d722106 100644
--- a/app/views/devise/registrations/new.html.erb
+++ b/app/views/devise/registrations/new.html.erb
@@ -22,6 +22,8 @@
<% end %>
<%= f.fields_for resource.account do |ff| %>
+
+ <%= ff.hidden_field :timezone %>
<%= ff.label :name, 'Company name', class: 'label' %>
<%= ff.text_field :name, required: true, class: 'base-input' %>
diff --git a/app/views/shared/_settings_nav.html.erb b/app/views/shared/_settings_nav.html.erb
index 20560969..8ed90f49 100644
--- a/app/views/shared/_settings_nav.html.erb
+++ b/app/views/shared/_settings_nav.html.erb
@@ -9,12 +9,14 @@
<%= link_to 'Account', settings_account_path, class: 'text-base hover:bg-base-300' %>
-
- <%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
-
-
- <%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
-
+ <% unless Docuseal.multitenant? %>
+
+ <%= link_to 'Email', settings_email_index_path, class: 'text-base hover:bg-base-300' %>
+
+
+ <%= link_to 'Storage', settings_storage_index_path, class: 'text-base hover:bg-base-300' %>
+
+ <% end %>
<%= link_to 'Signature', settings_esign_index_path, class: 'text-base hover:bg-base-300' %>
diff --git a/app/views/start_form/completed.html.erb b/app/views/start_form/completed.html.erb
index fd5da2ba..0436f4e9 100644
--- a/app/views/start_form/completed.html.erb
+++ b/app/views/start_form/completed.html.erb
@@ -29,9 +29,11 @@
Form has been submitter already
-
- <%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
-
+ <% if Accounts.can_send_emails?(@submitter.submission.template.account) %>
+
+ <%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
+
+ <% end %>
<%= render 'shared/attribution' %>
diff --git a/app/views/submissions/new.html.erb b/app/views/submissions/new.html.erb
index 40cb9ab6..0a8717c1 100644
--- a/app/views/submissions/new.html.erb
+++ b/app/views/submissions/new.html.erb
@@ -36,7 +36,7 @@
<% end %>
- <%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
-
OR
+ <% if Accounts.can_send_emails?(@submitter.submission.template.account) %>
+ <%= button_to button_title(title: 'Send copy to Email', disabled_with: 'Sending', icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, form: { onsubmit: 'event.submitter.disabled = true' }, class: 'base-button w-full' %>
+
OR
+ <% end %>
<%= svg_icon('download', class: 'w-6 h-6') %>
diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb
index 020c0e32..0b607238 100644
--- a/app/views/submit_form/show.html.erb
+++ b/app/views/submit_form/show.html.erb
@@ -29,7 +29,7 @@
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 5f7a43c4..11b3069c 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -45,7 +45,7 @@ Rails.application.configure do
end
# Store uploaded files on the local file system (see config/storage.yml for options).
- config.active_storage.service = :disk
+ config.active_storage.service = :local
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = true
diff --git a/config/environments/production.rb b/config/environments/production.rb
index e1b52f66..0ad1f2c8 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -38,7 +38,16 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options).
- config.active_storage.service = :local
+ config.active_storage.service =
+ if ENV['S3_ATTACHMENTS_BUCKET'].present?
+ :aws_s3
+ elsif ENV['GCS_BUCKET'].present?
+ :google
+ elsif ENV['AZURE_CONTAINER'].present?
+ :azure
+ else
+ :local
+ end
# Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
@@ -46,7 +55,7 @@ Rails.application.configure do
# config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
- # config.force_ssl = true
+ config.force_ssl = ENV['FORCE_SSL'] == 'true'
# Include generic and useful information about system operation, but avoid logging too much
# information to avoid inadvertent exposure of personally identifiable information (PII).
@@ -61,7 +70,20 @@ Rails.application.configure do
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
- # config.action_mailer.raise_delivery_errors = false
+ config.action_mailer.raise_delivery_errors = false
+
+ if ENV['SMTP_USERNAME']
+ config.action_mailer.delivery_method = :smtp
+ config.action_mailer.smtp_settings = {
+ address: ENV.fetch('SMTP_ADDRESS', nil),
+ 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),
+ authentication: ENV.fetch('SMTP_AUTHENTICATION', 'plain'),
+ enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true'
+ }
+ end
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
diff --git a/config/routes.rb b/config/routes.rb
index bdf8ea86..b8ed904f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -8,7 +8,7 @@ Rails.application.routes.draw do
devise_for :users, path: '/', only: %i[sessions passwords]
devise_scope :user do
- if User.devise_modules.include?(:registerable)
+ if Docuseal.multitenant?
resource :registration, only: %i[create], path: 'sign_up' do
get '' => :new, as: :new
end
@@ -52,8 +52,10 @@ Rails.application.routes.draw do
end
scope '/settings', as: :settings do
- resources :storage, only: %i[index create], controller: 'storage_settings'
- resources :email, only: %i[index create], controller: 'email_settings'
+ unless Docuseal.multitenant?
+ resources :storage, only: %i[index create], controller: 'storage_settings'
+ resources :email, only: %i[index create], controller: 'email_settings'
+ end
resources :esign, only: %i[index create], controller: 'esign_settings'
resources :users, only: %i[index]
resource :account, only: %i[show update]
diff --git a/lib/accounts.rb b/lib/accounts.rb
index a6237484..2d83c4a3 100644
--- a/lib/accounts.rb
+++ b/lib/accounts.rb
@@ -3,6 +3,30 @@
module Accounts
module_function
+ def load_signing_certs(account)
+ certs =
+ if Docuseal.multitenant?
+ Docuseal::CERTS
+ else
+ EncryptedConfig.find_by(account:, key: EncryptedConfig::ESIGN_CERTS_KEY).value
+ end
+
+ {
+ cert: OpenSSL::X509::Certificate.new(certs['cert']),
+ key: OpenSSL::PKey::RSA.new(certs['key']),
+ sub_ca: OpenSSL::X509::Certificate.new(certs['sub_ca']),
+ sub_key: OpenSSL::PKey::RSA.new(certs['sub_key']),
+ root_ca: OpenSSL::X509::Certificate.new(certs['root_ca']),
+ root_key: OpenSSL::PKey::RSA.new(certs['root_key'])
+ }
+ end
+
+ def can_send_emails?(account)
+ return true if Docuseal.multitenant?
+
+ EncryptedConfig.exists?(account_id: account.id, key: EncryptedConfig::EMAIL_SMTP_KEY)
+ end
+
def normalize_timezone(timezone)
tzinfo = TZInfo::Timezone.get(ActiveSupport::TimeZone::MAPPING[timezone] || timezone)
diff --git a/lib/action_mailer_configs_interceptor.rb b/lib/action_mailer_configs_interceptor.rb
index c7706d48..4cd93932 100644
--- a/lib/action_mailer_configs_interceptor.rb
+++ b/lib/action_mailer_configs_interceptor.rb
@@ -4,7 +4,11 @@ module ActionMailerConfigsInterceptor
module_function
def delivering_email(message)
- return message unless Rails.env.production?
+ if Rails.env.production? && Rails.application.config.action_mailer.delivery_method
+ message.from = ENV.fetch('SMTP_FROM')
+
+ return message
+ end
email_configs = EncryptedConfig.find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
diff --git a/lib/docuseal.rb b/lib/docuseal.rb
index 2127d30b..72c0cee7 100644
--- a/lib/docuseal.rb
+++ b/lib/docuseal.rb
@@ -5,13 +5,22 @@ module Docuseal
PRODUCT_NAME = 'DocuSeal'
DEFAULT_APP_URL = 'http://localhost:3000'
+ CERTS = JSON.parse(ENV.fetch('CERTS', '{}'))
+
+ DEFAULT_URL_OPTIONS = {
+ host: ENV.fetch('HOST', 'localhost'),
+ protocol: ENV['FORCE_SSL'] == 'true' ? 'https' : 'http'
+ }.freeze
+
module_function
def multitenant?
- ENV['MULTITENANT'] == true
+ ENV['MULTITENANT'] == 'true'
end
def default_url_options
+ return DEFAULT_URL_OPTIONS if multitenant?
+
@default_url_options ||= begin
value = EncryptedConfig.find_by(key: EncryptedConfig::APP_URL_KEY)&.value
value ||= DEFAULT_APP_URL
diff --git a/lib/generate_certificate.rb b/lib/generate_certificate.rb
index bc82dec7..3c35df13 100644
--- a/lib/generate_certificate.rb
+++ b/lib/generate_certificate.rb
@@ -13,12 +13,12 @@ module GenerateCertificate
cert, key = generate_certificate(name, sub_cert, sub_key)
{
- cert: cert.to_pem,
- key: key.to_pem,
- root_ca: root_cert.to_pem,
- root_key: root_key.to_pem,
- sub_ca: sub_cert.to_pem,
- sub_key: sub_key.to_pem
+ cert:,
+ key:,
+ root_ca: root_cert,
+ root_key:,
+ sub_ca: sub_cert,
+ sub_key:
}
end
diff --git a/lib/load_active_storage_configs.rb b/lib/load_active_storage_configs.rb
index 152955b3..263066fc 100644
--- a/lib/load_active_storage_configs.rb
+++ b/lib/load_active_storage_configs.rb
@@ -2,6 +2,8 @@
module LoadActiveStorageConfigs
STORAGE_YML_PATH = Rails.root.join('config/storage.yml')
+ IS_ENV_CONFIGURED =
+ ENV['S3_ATTACHMENTS_BUCKET'].present? || ENV['GCS_BUCKET'].present? || ENV['AZURE_CONTAINER'].present?
module_function
@@ -14,6 +16,9 @@ module LoadActiveStorageConfigs
end
def reload
+ return if Docuseal.multitenant?
+ return if IS_ENV_CONFIGURED
+
encrypted_config = EncryptedConfig.find_by(key: EncryptedConfig::FILES_STORAGE_KEY)
return unless encrypted_config
diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb
index 69ccbc15..391d725d 100644
--- a/lib/submissions/generate_result_attachments.rb
+++ b/lib/submissions/generate_result_attachments.rb
@@ -23,8 +23,7 @@ module Submissions
template = submitter.submission.template
- cert = submitter.submission.template.account.encrypted_configs
- .find_by(key: EncryptedConfig::ESIGN_CERTS_KEY).value
+ certs = Accounts.load_signing_certs(submitter.submission.template.account)
pdfs_index = build_pdfs_index(submitter)
@@ -158,7 +157,7 @@ module Submissions
template.schema.map do |item|
pdf = pdfs_index[item['attachment_uuid']]
- attachment = save_signed_pdf(pdf:, submitter:, cert:, uuid: item['attachment_uuid'], name: item['name'])
+ attachment = save_signed_pdf(pdf:, submitter:, certs:, uuid: item['attachment_uuid'], name: item['name'])
image_pdfs << pdf if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image?
@@ -176,7 +175,7 @@ module Submissions
save_signed_pdf(
pdf: images_pdf,
submitter:,
- cert:,
+ certs:,
uuid: images_pdf_uuid(original_documents.select(&:image?)),
name: template.name
)
@@ -185,16 +184,15 @@ module Submissions
end
# rubocop:enable Metrics
- def save_signed_pdf(pdf:, submitter:, cert:, uuid:, name:)
+ def save_signed_pdf(pdf:, submitter:, certs:, uuid:, name:)
io = StringIO.new
pdf.trailer.info[:Creator] = INFO_CREATOR
pdf.sign(io, reason: format(SIGN_REASON, email: submitter.email),
- certificate: OpenSSL::X509::Certificate.new(cert['cert']),
- key: OpenSSL::PKey::RSA.new(cert['key']),
- certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']),
- OpenSSL::X509::Certificate.new(cert['root_ca'])])
+ certificate: certs[:cert],
+ key: certs[:key],
+ certificate_chain: [certs[:sub_ca], certs[:root_ca]])
ActiveStorage::Attachment.create!(
uuid:,