Merge pull request #9424 from mfo/US/ETQ-instructeur-je-peux-redemander-un-lien-de-connexion-securise
amelioration(instructeur.connexion): ETQ instructeur, je peux redemander un lien de connexion securisé
This commit is contained in:
commit
f5d9cf015c
17 changed files with 135 additions and 29 deletions
|
@ -0,0 +1,6 @@
|
||||||
|
class Instructeurs::ActivateAccountFormComponent < ApplicationComponent
|
||||||
|
attr_reader :user
|
||||||
|
def initialize(user:)
|
||||||
|
@user = user
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
en:
|
||||||
|
title: Create your account for %{application_name}
|
||||||
|
activate: Activate your account %{email}
|
||||||
|
email_disabled: Instructor email address not changeable
|
||||||
|
submit: Choose password
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
fr:
|
||||||
|
title: Création de compte sur %{application_name}
|
||||||
|
activate: Se créer un compte pour %{email} en choissant un mot de passe
|
||||||
|
email_disabled: Adresse instructeur non modifiable
|
||||||
|
submit: Définir le mot de passe
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
= form_for user, url: { controller: 'users/activate', action: :create }, html: { class: "fr-py-5w" } do |f|
|
||||||
|
|
||||||
|
%h1.fr-h2.fr-mb-7w= t('.title', application_name: APPLICATION_NAME)
|
||||||
|
|
||||||
|
.fr-background-alt--grey.fr-px-12w.fr-py-7w
|
||||||
|
%fieldset.fr-mb-0.fr-fieldset{ aria: { labelledby: 'activate-account-legend' } }
|
||||||
|
|
||||||
|
%legend.fr-fieldset__legend#activate-account-legend
|
||||||
|
%h2.fr-h6.fr-mb-0= t('.activate', email: user.email)
|
||||||
|
|
||||||
|
.fr-fieldset__element
|
||||||
|
%p.fr-text--sm= t('utils.mandatory_champs')
|
||||||
|
|
||||||
|
.fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :email, input_type: :email_field, opts: { disabled: :disabled, class: 'fr-input-group--disabled', value: t('.email_disabled') })
|
||||||
|
|
||||||
|
.fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, opts: { autocomplete: 'current-password' })
|
||||||
|
|
||||||
|
= f.hidden_field :reset_password_token, value: params[:token]
|
||||||
|
|
||||||
|
.fr-fieldset__element
|
||||||
|
.fr-btns-group--right.fr-btns-group.fr-btns-group--inline.fr-btns-group.fr-btns-group--inline
|
||||||
|
%ul
|
||||||
|
%li= f.submit t('.submit'), class: 'fr-mt-2v fr-btn fr-btn'
|
|
@ -6,7 +6,7 @@ class Users::SessionsController < Devise::SessionsController
|
||||||
layout 'login', only: [:new, :create]
|
layout 'login', only: [:new, :create]
|
||||||
|
|
||||||
before_action :restore_procedure_context, only: [:new, :create]
|
before_action :restore_procedure_context, only: [:new, :create]
|
||||||
|
skip_before_action :redirect_if_untrusted, only: [:reset_link_sent]
|
||||||
# POST /resource/sign_in
|
# POST /resource/sign_in
|
||||||
def create
|
def create
|
||||||
user = User.find_by(email: params[:user][:email])
|
user = User.find_by(email: params[:user][:email])
|
||||||
|
@ -18,6 +18,13 @@ class Users::SessionsController < Devise::SessionsController
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reset_link_sent
|
||||||
|
if send_login_token_or_bufferize(current_instructeur)
|
||||||
|
flash[:notice] = "Nous venons de vous renvoyer un nouveau lien de connexion sécurisée à #{APPLICATION_NAME}"
|
||||||
|
end
|
||||||
|
redirect_to link_sent_path(email: current_instructeur.email)
|
||||||
|
end
|
||||||
|
|
||||||
def link_sent
|
def link_sent
|
||||||
if Devise.email_regexp.match?(params[:email])
|
if Devise.email_regexp.match?(params[:email])
|
||||||
@email = params[:email]
|
@email = params[:email]
|
||||||
|
|
|
@ -19,8 +19,11 @@ module TrustedDeviceConcern
|
||||||
|
|
||||||
def send_login_token_or_bufferize(instructeur)
|
def send_login_token_or_bufferize(instructeur)
|
||||||
if !instructeur.young_login_token?
|
if !instructeur.young_login_token?
|
||||||
login_token = instructeur.create_trusted_device_token
|
token = instructeur.create_trusted_device_token
|
||||||
InstructeurMailer.send_login_token(instructeur, login_token).deliver_later
|
InstructeurMailer.send_login_token(instructeur, token).deliver_later
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Bonjour,
|
Bonjour,
|
||||||
|
|
||||||
%p
|
%p
|
||||||
Veuillez cliquer sur le lien suivant pour vous connecter sur le site #{APPLICATION_NAME} :
|
Veuillez cliquer sur le lien sécurisé suivant pour vous connecter à #{APPLICATION_NAME} :
|
||||||
= link_to(sign_in_by_link_url(@instructeur_id, jeton: @login_token), sign_in_by_link_url(@instructeur_id, jeton: @login_token))
|
= link_to(sign_in_by_link_url(@instructeur_id, jeton: @login_token), sign_in_by_link_url(@instructeur_id, jeton: @login_token))
|
||||||
|
|
||||||
%p
|
%p
|
||||||
|
|
|
@ -7,11 +7,18 @@
|
||||||
Vous venez d’être nommé instructeur sur #{APPLICATION_NAME}.
|
Vous venez d’être nommé instructeur sur #{APPLICATION_NAME}.
|
||||||
|
|
||||||
%p
|
%p
|
||||||
Votre compte a été créé pour l'adresse email #{@user.email}. Pour l’activer, nous vous invitons à cliquer sur le lien suivant :
|
Votre compte a été créé pour l'adresse email
|
||||||
|
%strong #{@user.email}.
|
||||||
|
|
||||||
|
%p
|
||||||
|
Pour l’activer, cliquez sur le lien suivant :
|
||||||
= link_to(users_activate_url(token: @reset_password_token), users_activate_url(token: @reset_password_token))
|
= link_to(users_activate_url(token: @reset_password_token), users_activate_url(token: @reset_password_token))
|
||||||
|
|
||||||
%p
|
%p
|
||||||
Par ailleurs, nous vous invitons à prendre quelques minutes pour consulter notre tutoriel à destination des nouveaux instructeurs :
|
Lors de vos prochaines connexions sur #{APPLICATION_NAME} cliquez sur le bouton « Se connecter » positionné sur le haut de page ou bien sur ce lien :
|
||||||
|
= link_to new_user_session_url, new_user_session_url
|
||||||
|
%p
|
||||||
|
Nous vous invitons aussi à consulter notre tutoriel à destination des nouveaux instructeurs :
|
||||||
= link_to(INSTRUCTEUR_TUTORIAL_URL, INSTRUCTEUR_TUTORIAL_URL)
|
= link_to(INSTRUCTEUR_TUTORIAL_URL, INSTRUCTEUR_TUTORIAL_URL)
|
||||||
|
|
||||||
= render partial: "layouts/mailers/signature"
|
= render partial: "layouts/mailers/signature"
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
.container
|
= content_for(:page_id, 'activate_account')
|
||||||
= form_for @user, url: { controller: 'users/activate', action: :create }, html: { class: "form" } do |f|
|
= content_for(:title, t('metas.users.activate.title', application_name: APPLICATION_NAME))
|
||||||
%br
|
|
||||||
%h1= @user.email
|
.fr-container.fr-my-5w
|
||||||
= f.password_field :password, placeholder: 'Mot de passe'
|
.fr-grid-row
|
||||||
= f.hidden_field :reset_password_token, value: params[:token]
|
.fr-col-12.fr-col-offset-md-2.fr-col-md-8
|
||||||
= f.submit 'Définir le mot de passe', class: 'button large primary expand'
|
= render Instructeurs::ActivateAccountFormComponent.new(user: @user)
|
||||||
|
|
||||||
|
= render partial: 'users/dossiers/index_footer'
|
||||||
|
|
|
@ -3,18 +3,27 @@
|
||||||
- content_for :footer do
|
- content_for :footer do
|
||||||
= render partial: 'root/footer'
|
= render partial: 'root/footer'
|
||||||
|
|
||||||
#link-sent.container
|
.fr-container.fr-my-5w
|
||||||
= image_tag('user/confirmation-email.svg', "aria-hidden": true)
|
.fr-grid-row
|
||||||
%h1 Encore une petite étape :)
|
.fr-col-12.fr-col-offset-md-1.fr-col-md-7
|
||||||
|
%h1.fr-mt-6w Encore une petite étape !
|
||||||
|
|
||||||
%section.link-sent-info
|
%section
|
||||||
%p
|
%p.fr-text--lead
|
||||||
Ouvrez votre boite email <strong>#{@email}</strong> puis cliquez sur le lien d’activation du message <strong>Connexion sécurisée à #{APPLICATION_NAME}</strong>.
|
Nous venons de vous envoyer un courriel sur votre boite email <strong>#{@email}</strong>.
|
||||||
%p
|
Veuillez l’ouvrir et cliquer sur le lien de <strong>connexion sécurisée à #{APPLICATION_NAME}</strong>.
|
||||||
= t('views.users.shared.email_can_take_a_while_html')
|
|
||||||
|
|
||||||
%section.link-sent-help
|
%p.fr-text--lead
|
||||||
%p
|
Ce lien est <strong>valide une semaine</strong> et peut être réutilisé <strong>plusieurs fois</strong>.
|
||||||
Si vous voyez cette page trop souvent, consultez notre aide : #{link_to t("links.common.faq.confirmer_compte_chaque_connexion_url"), **external_link_attributes}
|
|
||||||
%p
|
%p.fr-text--sm.fr-text-mention--grey
|
||||||
= t('views.users.shared.contact_us_if_any_trouble_html', href: contact_admin_url)
|
Ce courriel peut mettre jusqu’à <strong>15 minutes</strong> pour arriver. Si vous n’avez pas reçu de courriel (n’hésitez pas à vérifier dans les indésirables), cliquez sur le bouton ci-dessous.
|
||||||
|
|
||||||
|
= button_to instructeurs_reset_link_sent_path, class: 'fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-mail-line', method: 'POST' do
|
||||||
|
Renvoyer le courriel
|
||||||
|
|
||||||
|
%section
|
||||||
|
%p.fr-mt-3w
|
||||||
|
Si vous voyez cette page trop souvent, consultez notre aide : #{link_to t("links.common.faq.confirmer_compte_chaque_connexion_url"), **external_link_attributes}
|
||||||
|
%p.fr-mt-3w
|
||||||
|
= t('views.users.shared.contact_us_if_any_trouble_html', href: contact_admin_url)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
= content_for(:page_id, 'auth')
|
= content_for(:page_id, 'auth')
|
||||||
= content_for(:title, t('metas.signin.title'))
|
= content_for(:title, t('metas.signin.title'))
|
||||||
|
|
||||||
|
|
||||||
#session-new.auth-form.sign-in-form
|
#session-new.auth-form.sign-in-form
|
||||||
= form_for resource, url: user_session_path, html: { class: "fr-py-5w" } do |f|
|
= form_for resource, url: user_session_path, html: { class: "fr-py-5w" } do |f|
|
||||||
|
|
||||||
|
|
|
@ -515,7 +515,7 @@ en:
|
||||||
check_france_connect_html: Have you once logged in with France Connect? If yes, <a href=\"%{href}\">try again with France Connect</a>.
|
check_france_connect_html: Have you once logged in with France Connect? If yes, <a href=\"%{href}\">try again with France Connect</a>.
|
||||||
check_gpdr: "The account may have been deleted in the event of prolonged inactivity and no current file. In this case you will have to recreate an account from a procedure."
|
check_gpdr: "The account may have been deleted in the event of prolonged inactivity and no current file. In this case you will have to recreate an account from a procedure."
|
||||||
shared:
|
shared:
|
||||||
email_can_take_a_while_html: <strong>Please note</strong> that this message can take up to 15 minutes to arrive.
|
email_can_take_a_while_html: <strong>Please note</strong> that this email can take up to 15 minutes to arrive.
|
||||||
contact_us_if_any_trouble_html: 'You can contact us <a href="%{href}">through this form</a> if a problem still exists.'
|
contact_us_if_any_trouble_html: 'You can contact us <a href="%{href}">through this form</a> if a problem still exists.'
|
||||||
modal:
|
modal:
|
||||||
publish:
|
publish:
|
||||||
|
|
|
@ -519,7 +519,7 @@ fr:
|
||||||
open_your_mailbox: "Maintenant ouvrez votre boite email."
|
open_your_mailbox: "Maintenant ouvrez votre boite email."
|
||||||
title: "Lien de réinitialisation du mot de passe envoyé"
|
title: "Lien de réinitialisation du mot de passe envoyé"
|
||||||
shared:
|
shared:
|
||||||
email_can_take_a_while_html: "<strong>Attention</strong>, ce message peut mettre jusqu’à <strong>15 minutes</strong> pour arriver."
|
email_can_take_a_while_html: "<strong>Attention</strong>, ce courriel peut mettre jusqu’à <strong>15 minutes</strong> pour arriver."
|
||||||
contact_us_if_any_trouble_html: 'En cas de difficultés, nous restons joignables <a href="%{href}">via ce formulaire</a>.'
|
contact_us_if_any_trouble_html: 'En cas de difficultés, nous restons joignables <a href="%{href}">via ce formulaire</a>.'
|
||||||
modal:
|
modal:
|
||||||
publish:
|
publish:
|
||||||
|
|
|
@ -17,3 +17,5 @@ en:
|
||||||
title: "Modification of draft nº %{number} (%{procedure_label})"
|
title: "Modification of draft nº %{number} (%{procedure_label})"
|
||||||
merci:
|
merci:
|
||||||
title: "File submitted (%{procedure_label})"
|
title: "File submitted (%{procedure_label})"
|
||||||
|
activate:
|
||||||
|
title: Activate my account on %{application_name}
|
||||||
|
|
|
@ -17,3 +17,5 @@ fr:
|
||||||
title: "Modification du brouillon nº %{number} (%{procedure_label})"
|
title: "Modification du brouillon nº %{number} (%{procedure_label})"
|
||||||
merci:
|
merci:
|
||||||
title: "Dossier envoyé (%{procedure_label})"
|
title: "Dossier envoyé (%{procedure_label})"
|
||||||
|
activate:
|
||||||
|
title: Activer mon compte sur %{application_name}
|
||||||
|
|
|
@ -125,6 +125,7 @@ Rails.application.routes.draw do
|
||||||
get '/users/no_procedure' => 'users/sessions#no_procedure'
|
get '/users/no_procedure' => 'users/sessions#no_procedure'
|
||||||
get 'connexion-par-jeton/:id' => 'users/sessions#sign_in_by_link', as: 'sign_in_by_link'
|
get 'connexion-par-jeton/:id' => 'users/sessions#sign_in_by_link', as: 'sign_in_by_link'
|
||||||
get 'lien-envoye' => 'users/sessions#link_sent', as: 'link_sent'
|
get 'lien-envoye' => 'users/sessions#link_sent', as: 'link_sent'
|
||||||
|
post '/instructeurs/reset-link-sent' => 'users/sessions#reset_link_sent'
|
||||||
get '/users/password/reset-link-sent' => 'users/passwords#reset_link_sent'
|
get '/users/password/reset-link-sent' => 'users/passwords#reset_link_sent'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -244,4 +244,36 @@ describe Users::SessionsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#reset_link_sent' do
|
||||||
|
let(:instructeur) { create(:instructeur, user: user) }
|
||||||
|
before { sign_in(user) }
|
||||||
|
subject { post :reset_link_sent }
|
||||||
|
|
||||||
|
context 'when the instructeur is signed without trust_device_token' do
|
||||||
|
it 'send InstructeurMailer.send_login_token' do
|
||||||
|
expect(InstructeurMailer).to receive(:send_login_token).with(instructeur, anything).and_return(double(deliver_later: true))
|
||||||
|
expect { subject }.to change { instructeur.trusted_device_tokens.count }.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the instructeur is signed with an young trust_device_token' do
|
||||||
|
before { instructeur.create_trusted_device_token }
|
||||||
|
it 'doesnot send InstructeurMailer.send_login_token' do
|
||||||
|
expect(InstructeurMailer).not_to receive(:send_login_token)
|
||||||
|
expect { subject }.to change { instructeur.trusted_device_tokens.count }.by(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the instructeur is signed with an old trust_device_token' do
|
||||||
|
let(:token) { instructeur.create_trusted_device_token }
|
||||||
|
before do
|
||||||
|
travel_to 15.minutes.from_now
|
||||||
|
end
|
||||||
|
it 'send InstructeurMailer.send_login_token' do
|
||||||
|
expect(InstructeurMailer).to receive(:send_login_token).with(instructeur, anything).and_return(double(deliver_later: true))
|
||||||
|
expect { subject }.to change { instructeur.trusted_device_tokens.count }.by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue