diff --git a/.rubocop.yml b/.rubocop.yml index 165bc73a..ff5b5c4d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -66,7 +66,7 @@ RSpec/ExampleLength: Max: 40 RSpec/MultipleMemoizedHelpers: - Max: 6 + Max: 9 Metrics/BlockNesting: Max: 4 diff --git a/README.md b/README.md index b50558e6..86b22694 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,12 @@ DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.

- ✨ Live Demo + ✨ Live Demo | - ☁️ Try in Cloud + ☁️ Try in Cloud

-[![Demo](https://github.com/docusealco/docuseal/assets/5418788/d8703ea3-361a-423f-8bfe-eff1bd9dbe14)](https://demo.docuseal.co) +[![Demo](https://github.com/docusealco/docuseal/assets/5418788/d8703ea3-361a-423f-8bfe-eff1bd9dbe14)](https://demo.docuseal.tech) ## Features - PDF form fields builder (WYSIWYG) diff --git a/app/controllers/account_configs_controller.rb b/app/controllers/account_configs_controller.rb index 82f90393..1130feef 100644 --- a/app/controllers/account_configs_controller.rb +++ b/app/controllers/account_configs_controller.rb @@ -8,6 +8,7 @@ class AccountConfigsController < ApplicationController AccountConfig::ALLOW_TYPED_SIGNATURE, AccountConfig::FORCE_MFA, AccountConfig::ALLOW_TO_RESUBMIT, + AccountConfig::ALLOW_TO_DECLINE_KEY, AccountConfig::FORM_PREFILL_SIGNATURE_KEY, AccountConfig::ESIGNING_PREFERENCE_KEY, AccountConfig::FORM_WITH_CONFETTI_KEY, diff --git a/app/controllers/api/api_base_controller.rb b/app/controllers/api/api_base_controller.rb index 267fd9bd..b681aa67 100644 --- a/app/controllers/api/api_base_controller.rb +++ b/app/controllers/api/api_base_controller.rb @@ -25,9 +25,9 @@ module Api render json: { error: 'Too many requests' }, status: :too_many_requests end - if Rails.env.production? + unless Rails.env.development? rescue_from CanCan::AccessDenied do |e| - render json: { error: e.message }, status: :forbidden + render json: { error: access_denied_error_message(e) }, status: :forbidden end rescue_from JSON::ParserError do |e| @@ -39,6 +39,33 @@ module Api private + def access_denied_error_message(error) + return 'Not authorized' if request.headers['X-Auth-Token'].blank? + return 'Not authorized' unless error.subject.is_a?(ActiveRecord::Base) + return 'Not authorized' unless error.subject.respond_to?(:account_id) + + linked_account_record_exists = + if current_user.account.testing? + current_user.account.linked_account_accounts.where(account_type: 'testing') + .exists?(account_id: error.subject.account_id) + else + current_user.account.testing_accounts.exists?(id: error.subject.account_id) + end + + return 'Not authorized' unless linked_account_record_exists + + object_name = error.subject.model_name.human + id = error.subject.id + + if current_user.account.testing? + "#{object_name} #{id} not found using testing API key; Use production API key to " \ + "access production #{object_name.downcase.pluralize}." + else + "#{object_name} #{id} not found using production API key; Use testing API key to " \ + "access testing #{object_name.downcase.pluralize}." + end + end + def paginate(relation, field: :id) result = relation.order(field => :desc) .limit([params.fetch(:limit, DEFAULT_LIMIT).to_i, MAX_LIMIT].min) diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index 908a04b4..bda564c0 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -74,13 +74,14 @@ module Api Submissions.send_signature_requests(submissions) submissions.each do |submission| - if submission.submitters.all?(&:completed_at?) && submission.submitters.last - ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submission.submitters.last.id }) + submission.submitters.each do |submitter| + ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submitter.id }) if submitter.completed_at? end end render json: build_create_json(submissions) - rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e + rescue Submitters::NormalizeValues::BaseError, Submissions::CreateFromSubmitters::BaseError, + DownloadUtils::UnableToDownload => e Rollbar.warning(e) if defined?(Rollbar) render json: { error: e.message }, status: :unprocessable_entity diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb index e9fc496b..210b783d 100644 --- a/app/controllers/api/submitters_controller.rb +++ b/app/controllers/api/submitters_controller.rb @@ -120,6 +120,7 @@ module Api end&.dig('uuid') submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email) + submitter.name = attrs[:name] if attrs.key?(:name) if attrs.key?(:phone) submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') diff --git a/app/controllers/api/tools_controller.rb b/app/controllers/api/tools_controller.rb index 32c59808..7b2d5cb4 100644 --- a/app/controllers/api/tools_controller.rb +++ b/app/controllers/api/tools_controller.rb @@ -20,10 +20,7 @@ module Api pdf = HexaPDF::Document.new(io: StringIO.new(file)) trusted_certs = Accounts.load_trusted_certs(current_account) - - is_checksum_found = ActiveStorage::Attachment.joins(:blob) - .where(name: 'documents', record_type: 'Submitter') - .exists?(blob: { checksum: Digest::MD5.base64digest(file) }) + is_checksum_found = CompletedDocument.exists?(sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(file))) render json: { checksum_status: is_checksum_found ? 'verified' : 'not_found', diff --git a/app/controllers/email_smtp_settings_controller.rb b/app/controllers/email_smtp_settings_controller.rb index 6da10172..d41ca570 100644 --- a/app/controllers/email_smtp_settings_controller.rb +++ b/app/controllers/email_smtp_settings_controller.rb @@ -9,7 +9,7 @@ class EmailSmtpSettingsController < ApplicationController def create if @encrypted_config.update(email_configs) - SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now! + SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now! redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved') else diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index cf37d24c..80767193 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -9,9 +9,11 @@ class UsersController < ApplicationController def index @users = if params[:status] == 'archived' - @users.archived + @users.archived.where.not(role: 'integration') + elsif params[:status] == 'integration' + @users.active.where(role: 'integration') else - @users.active + @users.active.where.not(role: 'integration') end @pagy, @users = pagy(@users.where(account: current_account).order(id: :desc)) diff --git a/app/javascript/application.js b/app/javascript/application.js index 8434e415..03339e0c 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -29,6 +29,7 @@ import SearchInput from './elements/search_input' import ToggleAttribute from './elements/toggle_attribute' import LinkedInput from './elements/linked_input' import CheckboxGroup from './elements/checkbox_group' +import MaskedInput from './elements/masked_input' import * as TurboInstantClick from './lib/turbo_instant_click' @@ -95,6 +96,7 @@ safeRegisterElement('search-input', SearchInput) safeRegisterElement('toggle-attribute', ToggleAttribute) safeRegisterElement('linked-input', LinkedInput) safeRegisterElement('checkbox-group', CheckboxGroup) +safeRegisterElement('masked-input', MaskedInput) safeRegisterElement('template-builder', class extends HTMLElement { connectedCallback () { diff --git a/app/javascript/elements/masked_input.js b/app/javascript/elements/masked_input.js new file mode 100644 index 00000000..14ee29ff --- /dev/null +++ b/app/javascript/elements/masked_input.js @@ -0,0 +1,18 @@ +export default class extends HTMLElement { + connectedCallback () { + const maskedToken = this.input.value + + this.input.addEventListener('focus', () => { + this.input.value = this.dataset.token + this.input.select() + }) + + this.input.addEventListener('focusout', () => { + this.input.value = maskedToken + }) + } + + get input () { + return this.querySelector('input') + } +} diff --git a/app/javascript/submission_form/completed.vue b/app/javascript/submission_form/completed.vue index b1ef464b..694e1b9c 100644 --- a/app/javascript/submission_form/completed.vue +++ b/app/javascript/submission_form/completed.vue @@ -76,7 +76,7 @@ diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index e511b047..03950e47 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -1107,6 +1107,10 @@ export default { const currentFieldUuids = this.currentStepFields.map((f) => f.uuid) const currentFieldType = this.currentField.type + if (!formData && !this.$refs.form.checkValidity()) { + return + } + if (this.dryRun) { currentFieldUuids.forEach((fieldUuid) => { this.submittedValues[fieldUuid] = this.values[fieldUuid] diff --git a/app/javascript/template_builder/conditions_modal.vue b/app/javascript/template_builder/conditions_modal.vue index 6b88aa96..cfb045cf 100644 --- a/app/javascript/template_builder/conditions_modal.vue +++ b/app/javascript/template_builder/conditions_modal.vue @@ -90,7 +90,7 @@ - <%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %> + <% token = current_user.access_token.token %> + + + + <%= render 'shared/clipboard_copy', icon: 'copy', text: token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %> <%= button_to button_title(title: t('rotate'), disabled_with: t('rotate'), icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button w-full', data: { turbo_confirm: t('remove_existing_api_token_and_generated_a_new_one_are_you_sure_') } %> diff --git a/app/views/devise/shared/_select_server.html.erb b/app/views/devise/shared/_select_server.html.erb index 60f5e56a..2b98ec18 100644 --- a/app/views/devise/shared/_select_server.html.erb +++ b/app/views/devise/shared/_select_server.html.erb @@ -1,10 +1,10 @@
- + <%= svg_icon 'world', class: 'w-5 h-5' %> Global - + <%= svg_icon 'eu_flag', class: 'w-5 h-5' %> Europe diff --git a/app/views/email_smtp_settings/index.html.erb b/app/views/email_smtp_settings/index.html.erb index 5616f216..ad047b2b 100644 --- a/app/views/email_smtp_settings/index.html.erb +++ b/app/views/email_smtp_settings/index.html.erb @@ -48,7 +48,7 @@
<%= ff.label :from_email, t('send_from_email'), class: 'label' %> - <%= ff.email_field :from_email, value: value['from_email'], required: true, class: 'base-input' %> + <%= ff.email_field :from_email, value: value['from_email'], required: !Docuseal.multitenant?, class: 'base-input' %>
<% end %>
diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb index 0bb4c41d..98acc80e 100644 --- a/app/views/shared/_navbar.html.erb +++ b/app/views/shared/_navbar.html.erb @@ -10,7 +10,7 @@ <% if signed_in? %>
<% if Docuseal.demo? %> - + <%= t('sign_up') %>