improve user password reset
This commit is contained in:
committed by
Pete Matsyburka
parent
aeea619059
commit
f7be74eb73
@@ -1,6 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PasswordsController < Devise::PasswordsController
|
class PasswordsController < Devise::PasswordsController
|
||||||
|
# rubocop:disable Rails/LexicallyScopedActionFilter
|
||||||
|
skip_before_action :require_no_authentication, only: %i[edit update]
|
||||||
|
# rubocop:enable Rails/LexicallyScopedActionFilter
|
||||||
|
|
||||||
class Current < ActiveSupport::CurrentAttributes
|
class Current < ActiveSupport::CurrentAttributes
|
||||||
attribute :user
|
attribute :user
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class ProfileController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update_password
|
def update_password
|
||||||
if current_user.update(password_params)
|
if current_user.update_with_password(password_params)
|
||||||
bypass_sign_in(current_user)
|
bypass_sign_in(current_user)
|
||||||
redirect_to settings_profile_index_path, notice: I18n.t('password_has_been_changed')
|
redirect_to settings_profile_index_path, notice: I18n.t('password_has_been_changed')
|
||||||
else
|
else
|
||||||
@@ -31,6 +31,6 @@ class ProfileController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def password_params
|
def password_params
|
||||||
params.require(:user).permit(:password, :password_confirmation)
|
params.require(:user).permit(:password, :password_confirmation, :current_password)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class UsersController < ApplicationController
|
class UsersController < ApplicationController
|
||||||
load_and_authorize_resource :user, only: %i[index edit update destroy]
|
load_and_authorize_resource :user, only: %i[index edit update destroy resend_reset_password]
|
||||||
|
|
||||||
before_action :build_user, only: %i[new create]
|
before_action :build_user, only: %i[new create]
|
||||||
authorize_resource :user, only: %i[new create]
|
authorize_resource :user, only: %i[new create]
|
||||||
@@ -71,6 +71,13 @@ class UsersController < ApplicationController
|
|||||||
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_removed')
|
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_removed')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def resend_reset_password
|
||||||
|
current_user.send_reset_password_instructions
|
||||||
|
|
||||||
|
redirect_back fallback_location: settings_users_path,
|
||||||
|
notice: I18n.t('you_will_receive_an_email_with_password_reset_instructions_in_a_few_minutes')
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def role_valid?(role)
|
def role_valid?(role)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import RequiredCheckboxGroup from './elements/required_checkbox_group'
|
|||||||
import PageContainer from './elements/page_container'
|
import PageContainer from './elements/page_container'
|
||||||
import EmailEditor from './elements/email_editor'
|
import EmailEditor from './elements/email_editor'
|
||||||
import MountOnClick from './elements/mount_on_click'
|
import MountOnClick from './elements/mount_on_click'
|
||||||
|
import VisibleOnInput from './elements/visible_on_input'
|
||||||
|
|
||||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
@@ -113,6 +114,7 @@ safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup)
|
|||||||
safeRegisterElement('page-container', PageContainer)
|
safeRegisterElement('page-container', PageContainer)
|
||||||
safeRegisterElement('email-editor', EmailEditor)
|
safeRegisterElement('email-editor', EmailEditor)
|
||||||
safeRegisterElement('mount-on-click', MountOnClick)
|
safeRegisterElement('mount-on-click', MountOnClick)
|
||||||
|
safeRegisterElement('visible-on-input', VisibleOnInput)
|
||||||
|
|
||||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.input = document.getElementById(this.dataset.inputId)
|
||||||
|
|
||||||
|
this.input.addEventListener('input', () => {
|
||||||
|
if (this.input.value.trim().length > 0) {
|
||||||
|
this.classList.remove('hidden')
|
||||||
|
} else {
|
||||||
|
this.classList.add('hidden')
|
||||||
|
this.querySelectorAll('input').forEach(input => { input.value = '' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,14 +59,24 @@
|
|||||||
<%= f.label :password, t('new_password'), class: 'label' %>
|
<%= f.label :password, t('new_password'), class: 'label' %>
|
||||||
<%= f.password_field :password, autocomplete: 'off', class: 'base-input' %>
|
<%= f.password_field :password, autocomplete: 'off', class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
|
<visible-on-input data-input-id="user_password" class="block space-y-4 <%= 'hidden' if f.object.errors.blank? %>">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.label :password_confirmation, t('confirm_password'), class: 'label' %>
|
<%= f.label :password_confirmation, t('confirm_password'), class: 'label' %>
|
||||||
<%= f.password_field :password_confirmation, autocomplete: 'off', class: 'base-input' %>
|
<%= f.password_field :password_confirmation, autocomplete: 'off', class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control pt-2">
|
<div class="form-control">
|
||||||
|
<%= f.label :current_password, t('current_password'), class: 'label' %>
|
||||||
|
<%= f.password_field :current_password, autocomplete: 'current-password', class: 'base-input' %>
|
||||||
|
<span class="label-text-alt mt-1">
|
||||||
|
<%= t('dont_remember_your_current_password_click_here_to_reset_it_html', link: new_user_password_url) %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
<%= f.button button_title(title: t('update'), disabled_with: t('updating')), class: 'base-button' %>
|
<%= f.button button_title(title: t('update'), disabled_with: t('updating')), class: 'base-button' %>
|
||||||
</div>
|
</div>
|
||||||
|
</visible-on-input>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= button_to nil, resend_reset_password_users_path, id: 'resend_password_button', class: 'hidden', data: { turbo_confirm: t('are_you_sure_') } %>
|
||||||
<p class="text-2xl font-bold mt-8 mb-4">
|
<p class="text-2xl font-bold mt-8 mb-4">
|
||||||
<%= t('two_factor_authentication') %>
|
<%= t('two_factor_authentication') %>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -801,6 +801,9 @@ en: &en
|
|||||||
reveal_api_key: Reveal API Key
|
reveal_api_key: Reveal API Key
|
||||||
enter_your_password_to_reveal_the_api_key: Enter your password to reveal the API key
|
enter_your_password_to_reveal_the_api_key: Enter your password to reveal the API key
|
||||||
wrong_password: Wrong password.
|
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? <label class="link" for="resend_password_button">Click here</label> to reset it.'
|
||||||
|
you_will_receive_an_email_with_password_reset_instructions_in_a_few_minutes: You will receive an email with password reset instructions in a few minutes.
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Bulk Send
|
bulk: Bulk Send
|
||||||
@@ -1688,6 +1691,9 @@ es: &es
|
|||||||
reveal_api_key: Revelar clave API
|
reveal_api_key: Revelar clave API
|
||||||
enter_your_password_to_reveal_the_api_key: Introduce tu contraseña para revelar la clave API
|
enter_your_password_to_reveal_the_api_key: Introduce tu contraseña para revelar la clave API
|
||||||
wrong_password: Contraseña incorrecta.
|
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? <label class="link" for="resend_password_button">Haz clic aquí</label> para restablecerla.'
|
||||||
|
you_will_receive_an_email_with_password_reset_instructions_in_a_few_minutes: Recibirás un correo electrónico con las instrucciones para restablecer tu contraseña en unos minutos.
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envío masivo
|
bulk: Envío masivo
|
||||||
@@ -2575,6 +2581,9 @@ it: &it
|
|||||||
reveal_api_key: Mostra chiave API
|
reveal_api_key: Mostra chiave API
|
||||||
enter_your_password_to_reveal_the_api_key: Inserisci la tua password per mostrare la chiave API
|
enter_your_password_to_reveal_the_api_key: Inserisci la tua password per mostrare la chiave API
|
||||||
wrong_password: Password errata.
|
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? <label class="link" for="resend_password_button">Clicca qui</label> per reimpostarla.'
|
||||||
|
you_will_receive_an_email_with_password_reset_instructions_in_a_few_minutes: Riceverai un'email con le istruzioni per reimpostare la password entro pochi minuti.
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Invio massivo
|
bulk: Invio massivo
|
||||||
@@ -3465,6 +3474,9 @@ fr: &fr
|
|||||||
reveal_api_key: Révéler la clé API
|
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
|
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.
|
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 ? <label class="link" for="resend_password_button">Cliquez ici</label> pour le réinitialiser.'
|
||||||
|
you_will_receive_an_email_with_password_reset_instructions_in_a_few_minutes: Vous recevrez un e-mail avec les instructions de réinitialisation de votre mot de passe dans quelques minutes.
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envoi en masse
|
bulk: Envoi en masse
|
||||||
@@ -4353,6 +4365,9 @@ pt: &pt
|
|||||||
reveal_api_key: Revelar chave API
|
reveal_api_key: Revelar chave API
|
||||||
enter_your_password_to_reveal_the_api_key: Insira sua senha para revelar a chave API
|
enter_your_password_to_reveal_the_api_key: Insira sua senha para revelar a chave API
|
||||||
wrong_password: Senha incorreta.
|
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? <label class="link" for="resend_password_button">Clique aqui</label> para redefini-la.'
|
||||||
|
you_will_receive_an_email_with_password_reset_instructions_in_a_few_minutes: Você receberá um e-mail com as instruções para redefinir sua senha em alguns minutos.
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Envio em massa
|
bulk: Envio em massa
|
||||||
@@ -5241,6 +5256,9 @@ de: &de
|
|||||||
reveal_api_key: API-Schlüssel anzeigen
|
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
|
enter_your_password_to_reveal_the_api_key: Gib dein Passwort ein, um den API-Schlüssel anzuzeigen
|
||||||
wrong_password: Falsches Passwort.
|
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? <label class="link" for="resend_password_button">Klicken Sie hier</label>, um es zurückzusetzen.'
|
||||||
|
you_will_receive_an_email_with_password_reset_instructions_in_a_few_minutes: Sie erhalten in wenigen Minuten eine E-Mail mit Anweisungen zum Zurücksetzen Ihres Passworts.
|
||||||
submission_sources:
|
submission_sources:
|
||||||
api: API
|
api: API
|
||||||
bulk: Massenversand
|
bulk: Massenversand
|
||||||
|
|||||||
+3
-1
@@ -65,7 +65,9 @@ Rails.application.routes.draw do
|
|||||||
resources :setup, only: %i[index create]
|
resources :setup, only: %i[index create]
|
||||||
resource :newsletter, only: %i[show update]
|
resource :newsletter, only: %i[show update]
|
||||||
resources :enquiries, only: %i[create]
|
resources :enquiries, only: %i[create]
|
||||||
resources :users, only: %i[new create edit update destroy]
|
resources :users, only: %i[new create edit update destroy] do
|
||||||
|
post :resend_reset_password, on: :collection
|
||||||
|
end
|
||||||
resource :user_signature, only: %i[edit update destroy]
|
resource :user_signature, only: %i[edit update destroy]
|
||||||
resource :user_initials, only: %i[edit update destroy]
|
resource :user_initials, only: %i[edit update destroy]
|
||||||
resources :submissions_archived, only: %i[index], path: 'submissions/archived'
|
resources :submissions_archived, only: %i[index], path: 'submissions/archived'
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ RSpec.describe 'Profile Settings' do
|
|||||||
|
|
||||||
expect(page).to have_content('Change Password')
|
expect(page).to have_content('Change Password')
|
||||||
expect(page).to have_field('user[password]')
|
expect(page).to have_field('user[password]')
|
||||||
expect(page).to have_field('user[password_confirmation]')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when changes contact information' do
|
context 'when changes contact information' do
|
||||||
@@ -47,6 +46,7 @@ RSpec.describe 'Profile Settings' do
|
|||||||
it 'updates password' do
|
it 'updates password' do
|
||||||
fill_in 'New password', with: 'newpassword'
|
fill_in 'New password', with: 'newpassword'
|
||||||
fill_in 'Confirm new password', with: 'newpassword'
|
fill_in 'Confirm new password', with: 'newpassword'
|
||||||
|
fill_in 'Current password', with: 'password'
|
||||||
|
|
||||||
all(:button, 'Update')[1].click
|
all(:button, 'Update')[1].click
|
||||||
|
|
||||||
@@ -56,10 +56,51 @@ RSpec.describe 'Profile Settings' do
|
|||||||
it 'does not update if password confirmation does not match' do
|
it 'does not update if password confirmation does not match' do
|
||||||
fill_in 'New password', with: 'newpassword'
|
fill_in 'New password', with: 'newpassword'
|
||||||
fill_in 'Confirm new password', with: 'newpassword1'
|
fill_in 'Confirm new password', with: 'newpassword1'
|
||||||
|
fill_in 'Current password', with: 'password'
|
||||||
|
|
||||||
all(:button, 'Update')[1].click
|
all(:button, 'Update')[1].click
|
||||||
|
|
||||||
expect(page).to have_content("Password confirmation doesn't match Password")
|
expect(page).to have_content("Password confirmation doesn't match Password")
|
||||||
end
|
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('You will receive an email with password reset instructions in a few minutes.')
|
||||||
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user