- <% if params[:action] == 'index' %>
+ <% if params[:action] == 'index' && (current_user == true_user || current_account.testing?) %>
<%= render 'shared/test_mode_toggle' %>
<% end %>
<% if @webhook_url.persisted? && params[:action] == 'index' %>
diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml
index c7a607f9..18da4440 100644
--- a/config/locales/i18n.yml
+++ b/config/locales/i18n.yml
@@ -24,7 +24,10 @@ en: &en
thanks: Thanks
private: Private
select: Select
+ enabled: Enabled
+ disabled: Disabled
party: Party
+ click_here_to_send_a_reset_password_email_html: ' to send a reset password email.'
edit_order: Edit Order
expirable_file_download_links: Expirable file download links
invite_form_fields: Invite form fields
@@ -44,7 +47,7 @@ en: &en
pending_by_me: Pending by me
partially_completed: Partially completed
require_phone_2fa_to_open: Require phone 2FA to open
- the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: The sender has requested a two factor authentication via one time password sent to your %{phone} phone number.
+ the_sender_has_requested_a_two_factor_authentication_via_one_time_password_sent_to_your_html: The sender has requested two-factor authentication via a one-time password sent to your %{phone} phone number.
send_verification_code: Send verification code
code_has_been_resent: Code has been re-sent
invalid_code: Invalid code
@@ -796,6 +799,12 @@ en: &en
template_name_has_been_completed_by_submitters_html: '"{template.name}" has been completed by {submission.submitters}'
please_check_the_copy_of_your_template_name_in_the_email_attachments_html: 'Please check the copy of your "{template.name}" in the email attachments.'
you_have_been_invited_to_sign_the_template_name_html: 'You have been invited to sign the "{template.name}".'
+ reveal_api_key: Reveal API Key
+ enter_your_password_to_reveal_the_api_key: Enter your password to reveal the API key
+ wrong_password: Wrong password.
+ current_password: Current password
+ dont_remember_your_current_password_click_here_to_reset_it_html: 'Don''t remember your current password? to reset it.'
+ an_email_with_password_reset_instructions_has_been_sent: An email with password reset instructions has been sent.
submission_sources:
api: API
bulk: Bulk Send
@@ -902,6 +911,8 @@ en: &en
range_without_total: "%{from}-%{to} events"
es: &es
+ enabled: Habilitado
+ disabled: Deshabilitado
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
@@ -1678,6 +1689,12 @@ es: &es
template_name_has_been_completed_by_submitters_html: '"{template.name}" ha sido completado por {submission.submitters}'
please_check_the_copy_of_your_template_name_in_the_email_attachments_html: 'Por favor, revisa la copia de tu "{template.name}" en los archivos adjuntos del correo electrónico.'
you_have_been_invited_to_sign_the_template_name_html: 'Has sido invitado a firmar el "{template.name}".'
+ reveal_api_key: Revelar clave API
+ enter_your_password_to_reveal_the_api_key: Introduce tu contraseña para revelar la clave API
+ wrong_password: Contraseña incorrecta.
+ current_password: Contraseña actual
+ dont_remember_your_current_password_click_here_to_reset_it_html: '¿No recuerdas tu contraseña actual? para restablecerla.'
+ an_email_with_password_reset_instructions_has_been_sent: Se enviará un correo electrónico con las instrucciones para restablecer tu contraseña en unos minutos.
submission_sources:
api: API
bulk: Envío masivo
@@ -1784,6 +1801,9 @@ es: &es
range_without_total: "%{from}-%{to} eventos"
it: &it
+ click_here_to_send_a_reset_password_email_html: ' per inviare una email per reimpostare la password.'
+ enabled: Abilitato
+ disabled: Disabilitato
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
@@ -2560,6 +2580,12 @@ it: &it
template_name_has_been_completed_by_submitters_html: '"{template.name}" è stato completato da {submission.submitters}'
please_check_the_copy_of_your_template_name_in_the_email_attachments_html: 'Per favore, controlla la copia del tuo "{template.name}" negli allegati dell''email.'
you_have_been_invited_to_sign_the_template_name_html: 'Sei stato invitato a firmare il "{template.name}".'
+ reveal_api_key: Mostra chiave API
+ enter_your_password_to_reveal_the_api_key: Inserisci la tua password per mostrare la chiave API
+ wrong_password: Password errata.
+ current_password: Password attuale
+ dont_remember_your_current_password_click_here_to_reset_it_html: 'Non ricordi la tua password attuale? per reimpostarla.'
+ an_email_with_password_reset_instructions_has_been_sent: Un'email con le istruzioni per reimpostare la password ti è stata inviata e arriverà entro pochi minuti.
submission_sources:
api: API
bulk: Invio massivo
@@ -2666,6 +2692,9 @@ it: &it
range_without_total: "%{from}-%{to} eventi"
fr: &fr
+ click_here_to_send_a_reset_password_email_html: ' pour envoyer un e-mail de réinitialisation du mot de passe.'
+ enabled: Activé
+ disabled: Désactivé
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
@@ -3445,6 +3474,12 @@ fr: &fr
template_name_has_been_completed_by_submitters_html: '"{template.name}" a été complété par {submission.submitters}'
please_check_the_copy_of_your_template_name_in_the_email_attachments_html: 'Veuillez vérifier la copie de votre "{template.name}" dans les pièces jointes de l’e-mail.'
you_have_been_invited_to_sign_the_template_name_html: 'Vous avez été invité à signer le "{template.name}".'
+ reveal_api_key: Révéler la clé API
+ enter_your_password_to_reveal_the_api_key: Entrez votre mot de passe pour révéler la clé API
+ wrong_password: Mot de passe incorrect.
+ current_password: Mot de passe actuel
+ dont_remember_your_current_password_click_here_to_reset_it_html: 'Vous ne vous souvenez plus de votre mot de passe actuel ? pour le réinitialiser.'
+ an_email_with_password_reset_instructions_has_been_sent: Un e-mail contenant les instructions pour réinitialiser votre mot de passe vous sera envoyé dans quelques minutes.
submission_sources:
api: API
bulk: Envoi en masse
@@ -3551,6 +3586,9 @@ fr: &fr
range_without_total: "%{from} à %{to} événements"
pt: &pt
+ click_here_to_send_a_reset_password_email_html: ' para enviar um e-mail de redefinição de senha.'
+ enabled: Ativado
+ disabled: Desativado
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
@@ -4328,6 +4366,12 @@ pt: &pt
template_name_has_been_completed_by_submitters_html: '"{template.name}" foi concluído por {submission.submitters}'
please_check_the_copy_of_your_template_name_in_the_email_attachments_html: 'Por favor, verifique a cópia do seu "{template.name}" nos anexos do e-mail.'
you_have_been_invited_to_sign_the_template_name_html: 'Você foi convidado a assinar o "{template.name}".'
+ reveal_api_key: Revelar chave API
+ enter_your_password_to_reveal_the_api_key: Insira sua senha para revelar a chave API
+ wrong_password: Senha incorreta.
+ current_password: Senha atual
+ dont_remember_your_current_password_click_here_to_reset_it_html: 'Não se lembra da sua senha atual? para redefini-la.'
+ an_email_with_password_reset_instructions_has_been_sent: Um e-mail com instruções para redefinir sua senha será enviado em alguns minutos.
submission_sources:
api: API
bulk: Envio em massa
@@ -4434,6 +4478,9 @@ pt: &pt
range_without_total: "%{from}-%{to} eventos"
de: &de
+ click_here_to_send_a_reset_password_email_html: ', um eine E-Mail zum Zurücksetzen des Passworts zu senden.'
+ enabled: Aktiviert
+ disabled: Deaktiviert
expirable_file_download_links: Ablaufbare Datei-Download-Links
create_templates_with_private_access_by_default: Vorlagen standardmäßig mit privatem Zugriff erstellen
party: Partei
@@ -5211,6 +5258,12 @@ de: &de
template_name_has_been_completed_by_submitters_html: '"{template.name}" wurde von {submission.submitters} abgeschlossen'
please_check_the_copy_of_your_template_name_in_the_email_attachments_html: 'Bitte prüfen Sie die Kopie Ihres "{template.name}" in den E-Mail-Anhängen.'
you_have_been_invited_to_sign_the_template_name_html: 'Du wurdest eingeladen, "{template.name}" zu unterschreiben.'
+ reveal_api_key: API-Schlüssel anzeigen
+ enter_your_password_to_reveal_the_api_key: Gib dein Passwort ein, um den API-Schlüssel anzuzeigen
+ wrong_password: Falsches Passwort.
+ current_password: Aktuelles Passwort
+ dont_remember_your_current_password_click_here_to_reset_it_html: 'Sie erinnern sich nicht an Ihr aktuelles Passwort? , um es zurückzusetzen.'
+ an_email_with_password_reset_instructions_has_been_sent: Eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts wurde Ihnen in wenigen Minuten zugesendet.
submission_sources:
api: API
bulk: Massenversand
diff --git a/config/routes.rb b/config/routes.rb
index 76ed7af9..1947b4b4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -65,7 +65,9 @@ Rails.application.routes.draw do
resources :setup, only: %i[index create]
resource :newsletter, only: %i[show update]
resources :enquiries, only: %i[create]
- resources :users, only: %i[new create edit update destroy]
+ resources :users, only: %i[new create edit update destroy] do
+ resource :send_reset_password, only: %i[update], controller: 'users_send_reset_password'
+ end
resource :user_signature, only: %i[edit update destroy]
resource :user_initials, only: %i[edit update destroy]
resources :submissions_archived, only: %i[index], path: 'submissions/archived'
@@ -179,6 +181,7 @@ Rails.application.routes.draw do
defaults: { status: :integration }
resource :personalization, only: %i[show create], controller: 'personalization_settings'
resources :api, only: %i[index create], controller: 'api_settings'
+ resource :reveal_access_token, only: %i[show create], controller: 'reveal_access_token'
resources :webhooks, only: %i[index show new create update destroy], controller: 'webhook_settings' do
post :resend
diff --git a/db/migrate/20250901110606_add_verification_method_to_completed_submitters.rb b/db/migrate/20250901110606_add_verification_method_to_completed_submitters.rb
new file mode 100644
index 00000000..31d48047
--- /dev/null
+++ b/db/migrate/20250901110606_add_verification_method_to_completed_submitters.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddVerificationMethodToCompletedSubmitters < ActiveRecord::Migration[8.0]
+ def change
+ add_column :completed_submitters, :verification_method, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index bb59b018..9b5ee95b 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_08_31_125322) do
+ActiveRecord::Schema[8.0].define(version: 2025_09_01_110606) do
# These are extensions that must be enabled in order to support this database
enable_extension "btree_gin"
enable_extension "plpgsql"
@@ -117,6 +117,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_08_31_125322) do
t.datetime "completed_at", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.string "verification_method"
t.index ["account_id"], name: "index_completed_submitters_on_account_id"
t.index ["submitter_id"], name: "index_completed_submitters_on_submitter_id", unique: true
end
diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb
index 4cad84bf..ae85f346 100644
--- a/lib/submissions/generate_result_attachments.rb
+++ b/lib/submissions/generate_result_attachments.rb
@@ -226,7 +226,7 @@ module Submissions
page[:Annots] ||= []
page[:Annots] = page[:Annots].try(:reject) do |e|
- next if e.is_a?(Integer)
+ next if e.is_a?(Integer) || e.is_a?(Symbol)
e.present? && e[:A] && e[:A][:URI].to_s.starts_with?('file:///docuseal_field')
end || page[:Annots]
diff --git a/lib/templates/build_annotations.rb b/lib/templates/build_annotations.rb
index b654198b..9a933f99 100644
--- a/lib/templates/build_annotations.rb
+++ b/lib/templates/build_annotations.rb
@@ -10,6 +10,7 @@ module Templates
pdf.pages.flat_map.with_index do |page, index|
(page[:Annots] || []).filter_map do |annot|
next if annot.blank?
+ next if annot.is_a?(Integer) || annot.is_a?(Symbol)
next if annot[:A].blank? || annot[:A][:URI].blank?
next unless annot[:Subtype] == :Link
next if !annot[:A][:URI].starts_with?('https://') && !annot[:A][:URI].starts_with?('http://')
diff --git a/spec/system/api_settings_spec.rb b/spec/system/api_settings_spec.rb
index a14ef36d..6f796add 100644
--- a/spec/system/api_settings_spec.rb
+++ b/spec/system/api_settings_spec.rb
@@ -14,4 +14,26 @@ RSpec.describe 'API Settings' do
token = user.access_token.token
expect(page).to have_field('X-Auth-Token', with: token.sub(token[5..], '*' * token[5..].size))
end
+
+ it 'reveals API key with correct password' do
+ find('#api_key').click
+
+ within('.modal') do
+ fill_in 'password', with: user.password
+ click_button 'Submit'
+ end
+
+ expect(page).to have_field('X-Auth-Token', with: user.access_token.token)
+ end
+
+ it 'shows error with incorrect password' do
+ find('#api_key').click
+
+ within('.modal') do
+ fill_in 'password', with: 'wrong_password'
+ click_button 'Submit'
+ end
+
+ expect(page).to have_content('Wrong password')
+ end
end
diff --git a/spec/system/profile_settings_spec.rb b/spec/system/profile_settings_spec.rb
index 3c05a664..9cce6088 100644
--- a/spec/system/profile_settings_spec.rb
+++ b/spec/system/profile_settings_spec.rb
@@ -5,6 +5,9 @@ RSpec.describe 'Profile Settings' do
before do
sign_in(user)
+
+ allow(Accounts).to receive(:can_send_emails?).and_return(true)
+
visit settings_profile_index_path
end
@@ -16,7 +19,6 @@ RSpec.describe 'Profile Settings' do
expect(page).to have_content('Change Password')
expect(page).to have_field('user[password]')
- expect(page).to have_field('user[password_confirmation]')
end
context 'when changes contact information' do
@@ -47,6 +49,7 @@ RSpec.describe 'Profile Settings' do
it 'updates password' do
fill_in 'New password', with: 'newpassword'
fill_in 'Confirm new password', with: 'newpassword'
+ fill_in 'Current password', with: 'password'
all(:button, 'Update')[1].click
@@ -56,10 +59,51 @@ RSpec.describe 'Profile Settings' do
it 'does not update if password confirmation does not match' do
fill_in 'New password', with: 'newpassword'
fill_in 'Confirm new password', with: 'newpassword1'
+ fill_in 'Current password', with: 'password'
all(:button, 'Update')[1].click
expect(page).to have_content("Password confirmation doesn't match Password")
end
+
+ it 'does not update if current password is incorrect' do
+ fill_in 'New password', with: 'newpassword'
+ fill_in 'Confirm new password', with: 'newpassword'
+ fill_in 'Current password', with: 'wrongpassword'
+
+ all(:button, 'Update')[1].click
+
+ expect(page).to have_content('Current password is invalid')
+ end
+
+ it 'resets password and signs in with new password', sidekiq: :inline do
+ fill_in 'New password', with: 'newpassword'
+ accept_confirm('Are you sure?') do
+ find('label', text: 'Click here').click
+ end
+
+ expect(page).to have_content('An email with password reset instructions has been sent.')
+
+ email = ActionMailer::Base.deliveries.last
+ reset_password_url = email.body
+ .encoded[/href="([^"]+)"/, 1]
+ .sub(%r{https?://(.*?)/}, "#{Capybara.current_session.server.base_url}/")
+
+ visit reset_password_url
+
+ fill_in 'New password', with: 'new_strong_password'
+ fill_in 'Confirm new password', with: 'new_strong_password'
+ click_button 'Change my password'
+
+ expect(page).to have_content('Your password has been changed successfully. You are now signed in.')
+
+ visit new_user_session_path
+
+ fill_in 'Email', with: user.email
+ fill_in 'Password', with: 'new_strong_password'
+ click_button 'Sign In'
+
+ expect(page).to have_content('Signed in successfully')
+ end
end
end
diff --git a/spec/system/team_settings_spec.rb b/spec/system/team_settings_spec.rb
index 01ce568f..693f31df 100644
--- a/spec/system/team_settings_spec.rb
+++ b/spec/system/team_settings_spec.rb
@@ -115,7 +115,6 @@ RSpec.describe 'Team Settings' do
fill_in 'First name', with: 'Adam'
fill_in 'Last name', with: 'Meier'
fill_in 'Email', with: 'adam.meier@example.com'
- fill_in 'Password', with: 'new_password'
expect do
click_button 'Submit'