From db608102725a87e560ca1e229775d1ac9b553eae Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Wed, 1 Jan 2025 14:38:24 +0200 Subject: [PATCH] add email OTP --- .../devise/sessions/_hidden_fields.html.erb | 1 + app/views/devise/sessions/_otp_form.html.erb | 4 + app/views/devise/sessions/new.html.erb | 4 +- app/views/devise/sessions/otp.html.erb | 7 +- config/locales/i18n.yml | 95 +++++++++++++++++++ spec/rails_helper.rb | 4 + spec/system/sign_in_spec.rb | 60 ++++++++++++ 7 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 app/views/devise/sessions/_hidden_fields.html.erb create mode 100644 app/views/devise/sessions/_otp_form.html.erb create mode 100644 spec/system/sign_in_spec.rb diff --git a/app/views/devise/sessions/_hidden_fields.html.erb b/app/views/devise/sessions/_hidden_fields.html.erb new file mode 100644 index 00000000..bde27b7e --- /dev/null +++ b/app/views/devise/sessions/_hidden_fields.html.erb @@ -0,0 +1 @@ +<%= hidden_field_tag :redir, params[:redir] if params[:redir].present? %> diff --git a/app/views/devise/sessions/_otp_form.html.erb b/app/views/devise/sessions/_otp_form.html.erb new file mode 100644 index 00000000..bf1959bf --- /dev/null +++ b/app/views/devise/sessions/_otp_form.html.erb @@ -0,0 +1,4 @@ +
+ <%= label_tag 'user[otp_attempt]', t('two_factor_code_from_authenticator_app'), class: 'label' %> + <%= text_field_tag 'user[otp_attempt]', nil, autofocus: true, autocomplete: 'off', placeholder: 'XXX-XXX', required: true, class: 'base-input' %> +
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 0cbd57e8..af589843 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -3,9 +3,7 @@ <%= render 'devise/shared/select_server' if Docuseal.multitenant? %>

<%= t('sign_in') %>

<%= form_for(resource, as: resource_name, html: { class: 'space-y-6' }, data: { turbo: params[:redir].blank? }, url: session_path(resource_name)) do |f| %> - <% if params[:redir].present? %> - <%= hidden_field_tag :redir, params[:redir] %> - <% end %> + <%= render 'hidden_fields' %>
<%= f.label :email, t(:email), class: 'label' %> diff --git a/app/views/devise/sessions/otp.html.erb b/app/views/devise/sessions/otp.html.erb index 57775ff9..8e8a07e7 100644 --- a/app/views/devise/sessions/otp.html.erb +++ b/app/views/devise/sessions/otp.html.erb @@ -8,12 +8,7 @@ <% if params[:redir].present? %> <%= hidden_field_tag :redir, params[:redir] %> <% end %> -
-
- <%= f.label :otp_attempt, t('two_factor_code_from_authenticator_app'), class: 'label' %> - <%= f.text_field :otp_attempt, autofocus: true, placeholder: 'XXX-XXX', required: true, class: 'base-input' %> -
-
+ <%= render 'otp_form', **local_assigns %>
<%= f.button button_title(title: t('sign_in'), disabled_with: t('signing_in')), class: 'base-button' %>
diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index a257b84d..896961fe 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -661,6 +661,16 @@ en: &en policy_links: Policy Links markdown_content_e_g: Markdown content, e.g. privacy_policy: Privacy Policy + the_code_has_been_sent_to_your_email: The code has been sent to your email. + enter_the_verification_code_from_your_email: Enter the verification code from your email. + too_many_attempts: Too many attempts. + verification_code: Verification Code + resend_code: Resend Code + verify_new_sign_in: Verify new sign in + use_otp_code_to_sign_in_or_click_the_link_below_html: 'Use %{code} code to sign in or click the link below:' + complete_sign_in: Complete Sign In + please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Please reply to this email if you don't recognize this sign in attempt. + docuseal_support: DocuSeal Support use_the_edit_form_to_move_it_to_another_team: Use the edit form to move it to another team. too_many_sms_attempts_try_again_in_a_few_seconds: Too many SMS attempts. Try again in a few seconds. number_phone_number_is_invalid: "+%{number} phone number is invalid" @@ -1345,6 +1355,16 @@ es: &es policy_links: Enlaces de Políticas markdown_content_e_g: Contenido Markdown, por ej. privacy_policy: Política de Privacidad + the_code_has_been_sent_to_your_email: El código ha sido enviado a tu correo electrónico. + enter_the_verification_code_from_your_email: Ingresa el código de verificación de tu correo electrónico. + too_many_attempts: Demasiados intentos. + verification_code: Código de Verificación + resend_code: Reenviar Código + verify_new_sign_in: Verificar nuevo inicio de sesión + use_otp_code_to_sign_in_or_click_the_link_below_html: 'Usa el código %{code} para iniciar sesión o haz clic en el enlace a continuación:' + complete_sign_in: Completar Inicio de Sesión + please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Responde a este correo electrónico si no reconoces este intento de inicio de sesión. + docuseal_support: Soporte DocuSeal use_the_edit_form_to_move_it_to_another_team: Usa el formulario de edición para moverlo a otro equipo. too_many_sms_attempts_try_again_in_a_few_seconds: Demasiados intentos de SMS. Intenta de nuevo en unos segundos. number_phone_number_is_invalid: "El número de teléfono +%{number} no es válido" @@ -2028,6 +2048,16 @@ it: &it policy_links: Collegamenti alle Politiche markdown_content_e_g: Contenuto Markdown, ad es. privacy_policy: Politica sulla Privacy + the_code_has_been_sent_to_your_email: Il codice è stato inviato alla tua e-mail. + enter_the_verification_code_from_your_email: Inserisci il codice di verifica dalla tua e-mail. + too_many_attempts: Troppe tentativi. + verification_code: Codice di Verifica + resend_code: Reinvia Codice + verify_new_sign_in: Verifica nuovo accesso + use_otp_code_to_sign_in_or_click_the_link_below_html: 'Usa il codice %{code} per accedere o clicca sul link qui sotto:' + complete_sign_in: "Completa l'Accesso" + please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Rispondi a questa e-mail se non riconosci questo tentativo di accesso. + docuseal_support: Supporto DocuSeal use_the_edit_form_to_move_it_to_another_team: Usa il modulo di modifica per spostarlo in un altro team. too_many_sms_attempts_try_again_in_a_few_seconds: Troppi tentativi di SMS. Riprova tra qualche secondo. number_phone_number_is_invalid: "Il numero di telefono +%{number} non è valido" @@ -2713,6 +2743,16 @@ fr: &fr policy_links: Liens des Politiques markdown_content_e_g: Contenu Markdown, par ex. privacy_policy: Politique de Confidentialité + the_code_has_been_sent_to_your_email: Le code a été envoyé à votre e-mail. + enter_the_verification_code_from_your_email: Entrez le code de vérification de votre e-mail. + too_many_attempts: Trop de tentatives. + verification_code: Code de Vérification + resend_code: Renvoyer le Code + verify_new_sign_in: Vérifier la nouvelle connexion + use_otp_code_to_sign_in_or_click_the_link_below_html: 'Utilisez le code %{code} pour vous connecter ou cliquez sur le lien ci-dessous :' + complete_sign_in: Terminer la Connexion + please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Veuillez répondre à cet e-mail si vous ne reconnaissez pas cette tentative de connexion. + docuseal_support: Support DocuSeal use_the_edit_form_to_move_it_to_another_team: Utilisez le formulaire de modification pour le déplacer vers une autre équipe. too_many_sms_attempts_try_again_in_a_few_seconds: Trop de tentatives de SMS. Réessayez dans quelques secondes. number_phone_number_is_invalid: "Le numéro de téléphone +%{number} est invalide" @@ -3397,6 +3437,16 @@ pt: &pt policy_links: Links de Políticas markdown_content_e_g: Conteúdo Markdown, ex. privacy_policy: Política de Privacidade + the_code_has_been_sent_to_your_email: O código foi enviado para seu e-mail. + enter_the_verification_code_from_your_email: Insira o código de verificação do seu e-mail. + too_many_attempts: Muitas tentativas. + verification_code: Código de Verificação + resend_code: Reenviar Código + verify_new_sign_in: Verificar novo login + use_otp_code_to_sign_in_or_click_the_link_below_html: 'Use o código %{code} para entrar ou clique no link abaixo:' + complete_sign_in: Concluir Login + please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Responda a este e-mail se você não reconhecer esta tentativa de login. + docuseal_support: Suporte DocuSeal use_the_edit_form_to_move_it_to_another_team: Use o formulário de edição para movê-lo para outra equipe. too_many_sms_attempts_try_again_in_a_few_seconds: Muitas tentativas de SMS. Tente novamente em alguns segundos. number_phone_number_is_invalid: "O número de telefone +%{number} é inválido" @@ -4081,6 +4131,16 @@ de: &de policy_links: Richtlinien-Links markdown_content_e_g: Markdown-Inhalt, z. B. privacy_policy: Datenschutzrichtlinie + the_code_has_been_sent_to_your_email: Der Code wurde an Ihre E-Mail gesendet. + enter_the_verification_code_from_your_email: Geben Sie den Verifizierungscode aus Ihrer E-Mail ein. + too_many_attempts: Zu viele Versuche. + verification_code: Verifizierungscode + resend_code: Code erneut senden + verify_new_sign_in: Neue Anmeldung überprüfen + use_otp_code_to_sign_in_or_click_the_link_below_html: 'Verwenden Sie den Code %{code}, um sich anzumelden, oder klicken Sie auf den untenstehenden Link:' + complete_sign_in: Anmeldung abschließen + please_reply_to_this_email_if_you_dont_recognize_this_sign_in_attempt: Bitte antworten Sie auf diese E-Mail, wenn Sie diesen Anmeldeversuch nicht erkennen. + docuseal_support: DocuSeal Support use_the_edit_form_to_move_it_to_another_team: Verwenden Sie das Bearbeitungsformular, um ihn in ein anderes Team zu verschieben. too_many_sms_attempts_try_again_in_a_few_seconds: Zu viele SMS-Versuche. Versuchen Sie es in ein paar Sekunden erneut. number_phone_number_is_invalid: "Die Telefonnummer +%{number} ist ungültig" @@ -4174,6 +4234,11 @@ pl: sign_up_with_microsoft: Zarejestruj się przez Microsoft by_creating_an_account_you_agree_to_our_html: 'Tworząc konto, akceptujesz naszą Politykę Prywatności i Regulamin.' enter_email_to_continue: Wprowadź e-mail, aby kontynuować + the_code_has_been_sent_to_your_email: Kod został wysłany na Twój e-mail. + enter_the_verification_code_from_your_email: Wprowadź kod weryfikacyjny z Twojego e-maila. + too_many_attempts: Zbyt wiele prób. + verification_code: Kod Weryfikacyjny + resend_code: Wyślij Kod Ponownie uk: awaiting_completion_by_the_other_party: "Очікує завершення з боку іншої сторони" @@ -4228,6 +4293,11 @@ uk: sign_up_with_microsoft: Зареєструватися через Microsoft by_creating_an_account_you_agree_to_our_html: 'Створюючи акаунт, ви погоджуєтесь з нашою Політикою конфіденційності і Умовами надання послуг.' enter_email_to_continue: Введіть електронну пошту, щоб продовжити + the_code_has_been_sent_to_your_email: Код було надіслано на вашу електронну пошту. + enter_the_verification_code_from_your_email: Введіть код перевірки з вашої електронної пошти. + too_many_attempts: Забагато спроб. + verification_code: Код перевірки + resend_code: Відправити код знову cs: awaiting_completion_by_the_other_party: "Čeká se na dokončení druhou stranou" @@ -4282,6 +4352,11 @@ cs: sign_up_with_microsoft: Zaregistrovat se pomocí Microsoftu by_creating_an_account_you_agree_to_our_html: 'Vytvořením účtu souhlasíte s našimi Zásadami ochrany osobních údajů a Podmínkami služby.' enter_email_to_continue: Zadejte e-mail pro pokračování + the_code_has_been_sent_to_your_email: Kód byl odeslán na váš e-mail. + enter_the_verification_code_from_your_email: Zadejte ověřovací kód z vašeho e-mailu. + too_many_attempts: Příliš mnoho pokusů. + verification_code: Ověřovací Kód + resend_code: Znovu Odeslat Kód he: awaiting_completion_by_the_other_party: "המתנה להשלמה מצד הצד השני" @@ -4336,6 +4411,11 @@ he: sign_up_with_microsoft: הירשם עם מיקרוסופט by_creating_an_account_you_agree_to_our_html: 'על ידי יצירת חשבון, אתה מסכים למדיניות הפרטיות ולתנאי השירות שלנו.' enter_email_to_continue: הכנס דוא"ל כדי להמשיך + the_code_has_been_sent_to_your_email: הקוד נשלח לדוא"ל שלך. + enter_the_verification_code_from_your_email: הזן את קוד האימות מדוא"ל שלך. + too_many_attempts: יותר מדי ניסיונות. + verification_code: קוד אימות + resend_code: שלח קוד מחדש nl: awaiting_completion_by_the_other_party: "In afwachting van voltooiing door de andere partij" @@ -4390,6 +4470,11 @@ nl: sign_up_with_microsoft: Aanmelden met Microsoft by_creating_an_account_you_agree_to_our_html: 'Door een account aan te maken, ga je akkoord met ons Privacybeleid en onze Gebruiksvoorwaarden.' enter_email_to_continue: Voer e-mail in om door te gaan + the_code_has_been_sent_to_your_email: De code is naar uw e-mail verzonden. + enter_the_verification_code_from_your_email: Voer de verificatiecode uit uw e-mail in. + too_many_attempts: Te veel pogingen. + verification_code: Verificatiecode + resend_code: Code Opnieuw Verzenden ar: awaiting_completion_by_the_other_party: "في انتظار إكتمال الطرف الآخر" @@ -4444,6 +4529,11 @@ ar: sign_up_with_microsoft: الاشتراك باستخدام مايكروسوفت by_creating_an_account_you_agree_to_our_html: 'من خلال إنشاء حساب، فإنك توافق على سياسة الخصوصية وشروط الخدمة الخاصة بنا.' enter_email_to_continue: أدخل البريد الإلكتروني للمتابعة + the_code_has_been_sent_to_your_email: تم إرسال الرمز إلى بريدك الإلكتروني. + enter_the_verification_code_from_your_email: أدخل رمز التحقق من بريدك الإلكتروني. + too_many_attempts: عدد المحاولات كبير جدًا. + verification_code: رمز التحقق + resend_code: إعادة إرسال الرمز ko: awaiting_completion_by_the_other_party: "다른 당사자의 완료를 기다리고 있습니다" @@ -4498,6 +4588,11 @@ ko: sign_up_with_microsoft: Microsoft로 가입 by_creating_an_account_you_agree_to_our_html: '계정을 생성함으로써, 귀하는 우리의 개인정보 보호정책서비스 약관에 동의하는 것입니다.' enter_email_to_continue: 계속하려면 이메일을 입력하세요 + the_code_has_been_sent_to_your_email: 코드가 이메일로 전송되었습니다. + enter_the_verification_code_from_your_email: 이메일에서 인증 코드를 입력하세요. + too_many_attempts: 시도 횟수가 너무 많습니다. + verification_code: 인증 코드 + resend_code: 코드 재전송 en-US: <<: *en diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 60e8abc0..db91da56 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -71,6 +71,10 @@ RSpec.configure do |config| Sidekiq::Testing.inline! if example.metadata[:sidekiq] == :inline end + config.after do |example| + Sidekiq::Testing.fake! if example.metadata[:sidekiq] == :inline + end + config.before(multitenant: true) do allow(Docuseal).to receive(:multitenant?).and_return(true) end diff --git a/spec/system/sign_in_spec.rb b/spec/system/sign_in_spec.rb new file mode 100644 index 00000000..e9e2f4d1 --- /dev/null +++ b/spec/system/sign_in_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Sign In', type: :system do + let(:account) { create(:account) } + let!(:user) { create(:user, account:, email: 'john.dou@example.com', password: 'strong_password') } + + before do + visit new_user_session_path + end + + context 'when only with email and password' do + it 'signs in successfully with valid email and password' do + fill_in 'Email', with: 'john.dou@example.com' + fill_in 'Password', with: 'strong_password' + click_button 'Sign In' + + expect(page).to have_content('Signed in successfully') + expect(page).to have_content('Document Templates') + end + + it "doesn't sign in if the email or password are incorrect" do + fill_in 'Email', with: 'john.dou@example.com' + fill_in 'Password', with: 'wrong_password' + click_button 'Sign In' + + expect(page).to have_content('Invalid Email or password') + expect(page).not_to have_content('Document Templates') + end + end + + context 'when 2FA is required' do + before do + user.update(otp_required_for_login: true, otp_secret: User.generate_otp_secret) + end + + it 'signs in successfully with valid OTP code' do + fill_in 'Email', with: 'john.dou@example.com' + fill_in 'Password', with: 'strong_password' + click_button 'Sign In' + fill_in 'Two-Factor Code from Authenticator App', with: user.current_otp + click_button 'Sign In' + + expect(page).to have_content('Signed in successfully') + expect(page).to have_content('Document Templates') + end + + it 'fails to sign in with invalid OTP code' do + fill_in 'Email', with: 'john.dou@example.com' + fill_in 'Password', with: 'strong_password' + click_button 'Sign In' + fill_in 'Two-Factor Code from Authenticator App', with: '123456' + click_button 'Sign In' + + expect(page).to have_content('Invalid Email or password') + expect(page).not_to have_content('Document Templates') + end + end +end