Add confirmation by email when merging DC/FC accounts

feat(fci.confirmation_code): add confirmation code to france_connect_informations

feat(user_mailer.france_connect_confirmation_code): add confirmation by email mail method/preview/spec, pointing to merge_mail_with_existing_account (reuse existing method)

feat(mail_merge): mail merge

feat(merge.cannot_use_france_connect): same behaviour as callback

clean(fci.confirmation_code): use same token for mail validation as merge

feat(resend_france_connect/particulier/merge_confirmation): resend email with link. also enhance some trads, cleanup halfy finished refacto

clean(tech): finalize story by plugging merge_with_new_account to email validation

fix(deadspec): was removed

fix(spec): broken after last refactoring

lint(rubocop): space before parenthesis

lint(haml-lint): yoohoooo space before =

fix(lint): scss now :D

Update app/assets/stylesheets/buttons.scss

cleanup

feat(france_connect): re-add confirm by email, with an option for confirmation by email instead of only confirmation by email

fixup! Add confirmation by email when merging DC/FC accounts

fix(lint): haml_spec failure
This commit is contained in:
Martin 2021-11-17 16:21:55 +01:00
parent f86e202738
commit ff073f8884
11 changed files with 152 additions and 11 deletions

View file

@ -556,3 +556,9 @@
[data-reach-combobox-popover] { [data-reach-combobox-popover] {
z-index: 20; z-index: 20;
} }
.fconnect-form {
input[type=password] {
margin-bottom: 16px;
}
}

View file

@ -1,6 +1,6 @@
class FranceConnect::ParticulierController < ApplicationController class FranceConnect::ParticulierController < ApplicationController
before_action :redirect_to_login_if_fc_aborted, only: [:callback] before_action :redirect_to_login_if_fc_aborted, only: [:callback]
before_action :securely_retrieve_fci, only: [:merge, :merge_with_existing_account, :merge_with_new_account] before_action :securely_retrieve_fci, only: [:merge, :merge_with_existing_account, :merge_with_new_account, :mail_merge_with_existing_account, :resend_and_renew_merge_confirmation]
def login def login
if FranceConnectService.enabled? if FranceConnectService.enabled?
@ -20,7 +20,8 @@ class FranceConnect::ParticulierController < ApplicationController
fci.associate_user!(fci.email_france_connect) fci.associate_user!(fci.email_france_connect)
connect_france_connect_particulier(fci.user) connect_france_connect_particulier(fci.user)
else else
redirect_to france_connect_particulier_merge_path(fci.create_merge_token!) merge_token = fci.create_merge_token!
redirect_to france_connect_particulier_merge_path(merge_token)
end end
else else
user = fci.user user = fci.user
@ -28,7 +29,7 @@ class FranceConnect::ParticulierController < ApplicationController
if user.can_france_connect? if user.can_france_connect?
fci.update(updated_at: Time.zone.now) fci.update(updated_at: Time.zone.now)
connect_france_connect_particulier(user) connect_france_connect_particulier(user)
else else # same behaviour as redirect nicely with message when instructeur/administrateur
fci.destroy fci.destroy
redirect_to new_user_session_path, alert: t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path) redirect_to new_user_session_path, alert: t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path)
end end
@ -64,6 +65,20 @@ class FranceConnect::ParticulierController < ApplicationController
end end
end end
def mail_merge_with_existing_account
user = User.find_by(email: @fci.email_france_connect.downcase)
if user.can_france_connect?
@fci.update(user: user)
@fci.delete_merge_token!
flash.notice = "Les comptes FranceConnect et #{APPLICATION_NAME} sont à présent fusionnés"
connect_france_connect_particulier(user)
else # same behaviour as redirect nicely with message when instructeur/administrateur
@fci.destroy
redirect_to new_user_session_path, alert: t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path)
end
end
def merge_with_new_account def merge_with_new_account
user = User.find_by(email: sanitized_email_params) user = User.find_by(email: sanitized_email_params)
@ -79,13 +94,20 @@ class FranceConnect::ParticulierController < ApplicationController
end end
end end
def resend_and_renew_merge_confirmation
merge_token = @fci.create_merge_token!
UserMailer.france_connect_merge_confirmation(@fci.email_france_connect, merge_token).deliver_later
redirect_to france_connect_particulier_merge_path(merge_token),
notice: "Nous venons de vous envoyer le mail de confirmation, veuillez cliquer sur le lien contenu dans ce mail pour fusionner vos comptes"
end
private private
def securely_retrieve_fci def securely_retrieve_fci
@fci = FranceConnectInformation.find_by(merge_token: merge_token_params) @fci = FranceConnectInformation.find_by(merge_token: merge_token_params)
if @fci.nil? || !@fci.valid_for_merge? if @fci.nil? || !@fci.valid_for_merge?
flash.alert = 'Votre compte FranceConnect a expiré, veuillez recommencer.' flash.alert = "Le délai pour fusionner les comptes FranceConnect et #{APPLICATION_NAME} est expirée. Veuillez recommencer la procédure pour vous fusionner les comptes."
respond_to do |format| respond_to do |format|
format.html { redirect_to root_path } format.html { redirect_to root_path }

View file

@ -20,6 +20,13 @@ class UserMailer < ApplicationMailer
mail(to: requested_email, subject: @subject) mail(to: requested_email, subject: @subject)
end end
def france_connect_merge_confirmation(email, merge_token)
@merge_token = merge_token
@subject = "Veuillez confirmer la fusion de compte"
mail(to: email, subject: @subject)
end
def invite_instructeur(user, reset_password_token) def invite_instructeur(user, reset_password_token)
@reset_password_token = reset_password_token @reset_password_token = reset_password_token
@user = user @user = user

View file

@ -3,10 +3,14 @@
%br %br
entrez votre mot de passe pour fusionner les comptes entrez votre mot de passe pour fusionner les comptes
= form_tag france_connect_particulier_merge_with_existing_account_path, remote: true, class: 'mt-2 form' do = form_tag france_connect_particulier_merge_with_existing_account_path, remote: true, class: 'mt-2 form fconnect-form' do
= hidden_field_tag :merge_token, merge_token = hidden_field_tag :merge_token, merge_token
= hidden_field_tag :email, email = hidden_field_tag :email, email
= label_tag :password, 'Mot de passe (8 caractères minimum)' = label_tag :password, 'Mot de passe (8 caractères minimum)'
= password_field_tag :password, nil, autocomplete: 'current-password', id: 'password-for-another-account' = password_field_tag :password, nil, autocomplete: 'current-password', id: 'password-for-another-account'
.mb-2
Mot de passe oublié ?
= link_to france_connect_particulier_resend_and_renew_merge_confirmation_path(merge_token: merge_token), method: :post do
Confirmer mon compte par mail
= button_tag 'revenir en arrière', type: 'button', class: 'button secondary', onclick: 'DS.showNewAccount(event);' = button_tag 'revenir en arrière', type: 'button', class: 'button secondary', onclick: 'DS.showNewAccount(event);'
= submit_tag 'Fusionner les comptes', class: 'button primary' = submit_tag 'Fusionner les comptes', class: 'button primary'

View file

@ -23,22 +23,30 @@
Non Non
.fusion.hidden .fusion.hidden
%p Pour les fusionner, entrez votre mot de passe %p Pour fusionner ces comptes, veuillez cliquer sur le lien présent dans le mail que nous venons de vous envoyer.
= form_tag france_connect_particulier_merge_with_existing_account_path, remote: true, class: 'mt-2 form' do = form_tag france_connect_particulier_merge_with_existing_account_path, remote: true, class: 'mt-2 form fconnect-form' do
= hidden_field_tag :merge_token, @fci.merge_token = hidden_field_tag :merge_token, @fci.merge_token
= hidden_field_tag :email, @fci.email_france_connect = hidden_field_tag :email, @fci.email_france_connect
= label_tag :password, 'Mot de passe (8 caractères minimum)' = label_tag :password, 'Mot de passe (8 caractères minimum)'
= password_field_tag :password, nil, autocomplete: 'current-password' = password_field_tag :password, nil, autocomplete: 'current-password', class: 'mb-1'
.mb-2
Mot de passe oublié ?
= link_to france_connect_particulier_resend_and_renew_merge_confirmation_path(merge_token: @fci.merge_token), method: :post do
Confirmer mon compte par mail
= submit_tag 'Fusionner les comptes', class: 'button primary' = submit_tag 'Fusionner les comptes', class: 'button primary'
.new-account.hidden .new-account.hidden
%p Donnez-nous alors le mail que #{APPLICATION_NAME} utilisera pour vous contacter %p Donnez-nous alors le mail que #{APPLICATION_NAME} utilisera pour vous contacter
= form_tag france_connect_particulier_merge_with_new_account_path, remote: true, class: 'mt-2 form' do = form_tag france_connect_particulier_merge_with_new_account_path, remote: true, class: 'mt-2 form' do
= hidden_field_tag :merge_token, @fci.merge_token = hidden_field_tag :merge_token, @fci.merge_token
= label_tag :email, 'Email (nom@site.com)' = label_tag :email, 'Email (nom@site.com)'
= email_field_tag :email = email_field_tag :email, "", required: true
= submit_tag 'Utiliser ce mail', class: 'button primary' = submit_tag 'Utiliser ce mail', class: 'button primary'
.new-account-password-confirmation.hidden .new-account-password-confirmation.hidden

View file

@ -0,0 +1,20 @@
- content_for(:title, @subject)
%p
Bonjour,
%p
Pour confirmer la fusion de votre compte, veuillez cliquer sur le lien suivant :
= round_button 'Je confirme', france_connect_particulier_mail_merge_with_existing_account_url(merge_token: @merge_token), :primary
%p
Vous pouvez aussi visiter ce lien : #{link_to france_connect_particulier_mail_merge_with_existing_account_url(merge_token: @merge_token), france_connect_particulier_mail_merge_with_existing_account_url(merge_token: @merge_token)}
%p Ce lien est valide #{distance_of_time_in_words(FranceConnectInformation::MERGE_VALIDITY)}.
%p
Si vous nêtes pas à lorigine de cette demande, vous pouvez ignorer ce message. Et si vous avez besoin dassistance, nhésitez pas à nous contacter à
= succeed '.' do
= mail_to CONTACT_EMAIL
= render partial: "layouts/mailers/signature"

View file

@ -125,6 +125,8 @@ Rails.application.routes.draw do
get 'particulier' => 'particulier#login' get 'particulier' => 'particulier#login'
get 'particulier/callback' => 'particulier#callback' get 'particulier/callback' => 'particulier#callback'
get 'particulier/merge/:merge_token' => 'particulier#merge', as: :particulier_merge get 'particulier/merge/:merge_token' => 'particulier#merge', as: :particulier_merge
get 'particulier/mail_merge_with_existing_account/:merge_token' => 'particulier#mail_merge_with_existing_account', as: :particulier_mail_merge_with_existing_account
post 'particulier/resend_and_renew_merge_confirmation' => 'particulier#resend_and_renew_merge_confirmation', as: :particulier_resend_and_renew_merge_confirmation
post 'particulier/merge_with_existing_account' => 'particulier#merge_with_existing_account' post 'particulier/merge_with_existing_account' => 'particulier#merge_with_existing_account'
post 'particulier/merge_with_new_account' => 'particulier#merge_with_new_account' post 'particulier/merge_with_new_account' => 'particulier#merge_with_new_account'
end end

View file

@ -150,7 +150,7 @@ describe FranceConnect::ParticulierController, type: :controller do
else else
expect(subject).to redirect_to root_path expect(subject).to redirect_to root_path
end end
expect(flash.alert).to eq('Votre compte FranceConnect a expiré, veuillez recommencer.') expect(flash.alert).to eq('Le délai pour fusionner les comptes FranceConnect et demarches-simplifiees.fr est expirée. Veuillez recommencer la procédure pour vous fusionner les comptes.')
end end
end end
end end
@ -173,7 +173,7 @@ describe FranceConnect::ParticulierController, type: :controller do
it do it do
expect(subject).to redirect_to root_path expect(subject).to redirect_to root_path
expect(flash.alert).to eq('Votre compte FranceConnect a expiré, veuillez recommencer.') expect(flash.alert).to eq("Le délai pour fusionner les comptes FranceConnect et demarches-simplifiees.fr est expirée. Veuillez recommencer la procédure pour vous fusionner les comptes.")
end end
end end
end end
@ -241,6 +241,54 @@ describe FranceConnect::ParticulierController, type: :controller do
end end
end end
describe '#mail_merge_with_existing_account' do
let(:fci) { FranceConnectInformation.create!(user_info) }
let!(:merge_token) { fci.create_merge_token! }
context 'when the merge_token is ok and the user is found' do
subject { post :mail_merge_with_existing_account, params: { merge_token: fci.merge_token } }
let!(:user) { create(:user, email: email, password: 'abcdefgh') }
it 'merges the account, signs in, and delete the merge token' do
subject
fci.reload
expect(fci.user).to eq(user)
expect(fci.merge_token).to be_nil
expect(controller.current_user).to eq(user)
end
context 'but the targeted user is an instructeur' do
let!(:user) { create(:instructeur, email: email, password: 'abcdefgh').user }
it 'redirects to the new session' do
subject
expect(FranceConnectInformation.exists?(fci.id)).to be_falsey
expect(controller.current_user).to be_nil
expect(response).to redirect_to(new_user_session_path)
expect(flash[:alert]).to eq(I18n.t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path))
end
end
end
context 'when the merge_token is not ok' do
subject { post :mail_merge_with_existing_account, params: { merge_token: 'ko' } }
let!(:user) { create(:user, email: email) }
it 'increases the failed attempts counter' do
subject
fci.reload
expect(fci.user).to be_nil
expect(fci.merge_token).not_to be_nil
expect(controller.current_user).to be_nil
expect(response).to redirect_to(root_path)
end
end
end
describe '#merge_with_new_account' do describe '#merge_with_new_account' do
let(:fci) { FranceConnectInformation.create!(user_info) } let(:fci) { FranceConnectInformation.create!(user_info) }
let(:merge_token) { fci.create_merge_token! } let(:merge_token) { fci.create_merge_token! }
@ -280,4 +328,13 @@ describe FranceConnect::ParticulierController, type: :controller do
end end
end end
end end
describe '#resend_and_renew_merge_confirmation' do
let(:fci) { FranceConnectInformation.create!(user_info) }
let(:merge_token) { fci.create_merge_token! }
it 'renew token' do
expect { post :resend_and_renew_merge_confirmation, params: { merge_token: merge_token } }.to change { fci.reload.merge_token }
expect(response).to redirect_to(france_connect_particulier_merge_path(fci.reload.merge_token))
end
end
end end

View file

@ -12,6 +12,10 @@ class UserMailerPreview < ActionMailer::Preview
UserMailer.ask_for_merge(user, 'dircab@territoires.gouv.fr') UserMailer.ask_for_merge(user, 'dircab@territoires.gouv.fr')
end end
def france_connect_merge_confirmation
UserMailer.france_connect_merge_confirmation('new.exemple.fr', '123456')
end
private private
def user def user

View file

@ -25,4 +25,14 @@ RSpec.describe UserMailer, type: :mailer do
it { expect(subject.to).to eq([requested_email]) } it { expect(subject.to).to eq([requested_email]) }
it { expect(subject.body).to include(requested_email) } it { expect(subject.body).to include(requested_email) }
end end
describe '.france_connect_merge_confirmation' do
let(:email) { 'new.exemple.fr' }
let(:code) { '123456' }
subject { described_class.france_connect_merge_confirmation(email, code) }
it { expect(subject.to).to eq([email]) }
it { expect(subject.body).to include(france_connect_particulier_mail_merge_with_existing_account_url(merge_token: code)) }
end
end end

View file

@ -140,3 +140,4 @@
height: 500px; height: 500px;
margin: 60px auto 0 auto; margin: 60px auto 0 auto;
} }