Merge pull request #9904 from demarches-simplifiees/use_email_merge_token

Use email merge token
This commit is contained in:
LeSim 2024-01-11 10:45:07 +00:00 committed by GitHub
commit 5f4aa4fc4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 91 additions and 20 deletions

View file

@ -1,6 +1,7 @@
class FranceConnect::ParticulierController < ApplicationController
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, :mail_merge_with_existing_account, :resend_and_renew_merge_confirmation]
before_action :securely_retrieve_fci, only: [:merge, :merge_with_existing_account, :merge_with_new_account, :resend_and_renew_merge_confirmation]
before_action :securely_retrieve_fci_from_email_merge_token, only: [:mail_merge_with_existing_account]
def login
if FranceConnectService.enabled?
@ -14,7 +15,7 @@ class FranceConnect::ParticulierController < ApplicationController
fci = FranceConnectService.find_or_retrieve_france_connect_information(params[:code])
if fci.user.nil?
preexisting_unlinked_user = User.find_by(email: fci.email_france_connect.downcase)
preexisting_unlinked_user = User.find_by(email: sanitize(fci.email_france_connect))
if preexisting_unlinked_user.nil?
fci.associate_user!(fci.email_france_connect)
@ -57,6 +58,7 @@ class FranceConnect::ParticulierController < ApplicationController
else
@fci.update(user: user)
@fci.delete_merge_token!
@fci.delete_email_merge_token!
flash.notice = t('france_connect.particulier.flash.connection_done', application_name: APPLICATION_NAME)
connect_france_connect_particulier(user)
@ -67,7 +69,7 @@ class FranceConnect::ParticulierController < ApplicationController
end
def mail_merge_with_existing_account
user = User.find_by(email: @fci.email_france_connect.downcase)
user = User.find_by(email: sanitize(@fci.email_france_connect.downcase))
if user.can_france_connect?
@fci.update(user: user)
@fci.delete_merge_token!
@ -96,14 +98,33 @@ class FranceConnect::ParticulierController < ApplicationController
end
def resend_and_renew_merge_confirmation
@fci.create_email_merge_token!
UserMailer.france_connect_merge_confirmation(
@fci.email_france_connect,
@fci.email_merge_token,
@fci.email_merge_token_created_at
)
.deliver_later
merge_token = @fci.create_merge_token!
UserMailer.france_connect_merge_confirmation(@fci.email_france_connect, merge_token, @fci.merge_token_created_at).deliver_later
redirect_to france_connect_particulier_merge_path(merge_token),
notice: t('france_connect.particulier.flash.confirmation_mail_sent')
end
private
def securely_retrieve_fci_from_email_merge_token
@fci = FranceConnectInformation.find_by(email_merge_token: email_merge_token_params)
if @fci.nil? || !@fci.valid_for_email_merge?
flash.alert = t('france_connect.particulier.flash.merger_token_expired', application_name: APPLICATION_NAME)
redirect_to root_path
else
@fci.delete_email_merge_token!
end
end
def securely_retrieve_fci
@fci = FranceConnectInformation.find_by(merge_token: merge_token_params)
@ -141,11 +162,19 @@ class FranceConnect::ParticulierController < ApplicationController
params[:merge_token]
end
def email_merge_token_params
params[:email_merge_token]
end
def password_params
params[:password]
end
def sanitized_email_params
params[:email]&.gsub(/[[:space:]]/, ' ')&.strip&.downcase
sanitize(params[:email])
end
def sanitize(string)
string&.gsub(/[[:space:]]/, ' ')&.strip&.downcase
end
end

View file

@ -20,9 +20,9 @@ class UserMailer < ApplicationMailer
mail(to: requested_email, subject: @subject)
end
def france_connect_merge_confirmation(email, merge_token, merge_token_created_at)
@merge_token = merge_token
@merge_token_created_at = merge_token_created_at
def france_connect_merge_confirmation(email, email_merge_token, email_merge_token_created_at)
@email_merge_token = email_merge_token
@email_merge_token_created_at = email_merge_token_created_at
@subject = "Veuillez confirmer la fusion de compte"
mail(to: email, subject: @subject)

View file

@ -26,19 +26,34 @@ class FranceConnectInformation < ApplicationRecord
def create_merge_token!
merge_token = SecureRandom.uuid
update(merge_token: merge_token, merge_token_created_at: Time.zone.now)
update(merge_token:, merge_token_created_at: Time.zone.now)
merge_token
end
def create_email_merge_token!
email_merge_token = SecureRandom.uuid
update(email_merge_token:, email_merge_token_created_at: Time.zone.now)
email_merge_token
end
def valid_for_merge?
(MERGE_VALIDITY.ago < merge_token_created_at) && user_id.nil?
end
def valid_for_email_merge?
(MERGE_VALIDITY.ago < email_merge_token_created_at) && user_id.nil?
end
def delete_merge_token!
update(merge_token: nil, merge_token_created_at: nil)
end
def delete_email_merge_token!
update(email_merge_token: nil, email_merge_token_created_at: nil)
end
def full_name
[given_name, family_name].compact.join(" ")
end

View file

@ -1,16 +1,17 @@
- content_for(:title, @subject)
- merge_link = france_connect_particulier_mail_merge_with_existing_account_url(email_merge_token: @email_merge_token)
%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
= round_button 'Je confirme', merge_link, :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)}
Vous pouvez aussi visiter ce lien : #{link_to merge_link, merge_link}
%p Ce lien est valide #{distance_of_time_in_words(FranceConnectInformation::MERGE_VALIDITY)}, jusqu'à #{@merge_token_created_at.strftime("%d-%m-%Y à %H:%M (%Z)")}
%p Ce lien est valide #{distance_of_time_in_words(FranceConnectInformation::MERGE_VALIDITY)}, jusqu'à #{@email_merge_token_created_at.strftime("%d-%m-%Y à %H:%M (%Z)")}
%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 à

View file

@ -177,7 +177,7 @@ Rails.application.routes.draw do
get 'particulier' => 'particulier#login'
get 'particulier/callback' => 'particulier#callback'
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
get 'particulier/mail_merge_with_existing_account/:email_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_new_account' => 'particulier#merge_with_new_account'

View file

@ -0,0 +1,10 @@
class AddEmailMergeTokenColumnToFranceConnectInformation < ActiveRecord::Migration[7.0]
disable_ddl_transaction!
def change
add_column :france_connect_informations, :email_merge_token, :string
add_column :france_connect_informations, :email_merge_token_created_at, :datetime
add_index :france_connect_informations, :email_merge_token, algorithm: :concurrently
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_12_21_142727) do
ActiveRecord::Schema[7.0].define(version: 2024_01_10_113623) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
@ -626,6 +626,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_21_142727) do
t.datetime "created_at", precision: nil, null: false
t.jsonb "data"
t.string "email_france_connect"
t.string "email_merge_token"
t.datetime "email_merge_token_created_at"
t.string "family_name"
t.string "france_connect_particulier_id"
t.string "gender"
@ -634,6 +636,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_21_142727) do
t.datetime "merge_token_created_at", precision: nil
t.datetime "updated_at", precision: nil, null: false
t.integer "user_id"
t.index ["email_merge_token"], name: "index_france_connect_informations_on_email_merge_token"
t.index ["merge_token"], name: "index_france_connect_informations_on_merge_token"
t.index ["user_id"], name: "index_france_connect_informations_on_user_id"
end

View file

@ -268,10 +268,10 @@ describe FranceConnect::ParticulierController, type: :controller do
describe '#mail_merge_with_existing_account' do
let(:fci) { FranceConnectInformation.create!(user_info) }
let!(:merge_token) { fci.create_merge_token! }
let!(:email_merge_token) { fci.create_email_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 } }
subject { post :mail_merge_with_existing_account, params: { email_merge_token: } }
let!(:user) { create(:user, email: email, password: 'abcdefgh') }
@ -281,6 +281,7 @@ describe FranceConnect::ParticulierController, type: :controller do
expect(fci.user).to eq(user)
expect(fci.merge_token).to be_nil
expect(fci.email_merge_token).to be_nil
expect(controller.current_user).to eq(user)
expect(flash[:notice]).to eq("Les comptes FranceConnect et #{APPLICATION_NAME} sont à présent fusionnés")
end
@ -298,8 +299,8 @@ describe FranceConnect::ParticulierController, type: :controller do
end
end
context 'when the merge_token is not ok' do
subject { post :mail_merge_with_existing_account, params: { merge_token: 'ko' } }
context 'when the email_merge_token is not ok' do
subject { post :mail_merge_with_existing_account, params: { email_merge_token: 'ko' } }
let!(:user) { create(:user, email: email) }
@ -308,7 +309,7 @@ describe FranceConnect::ParticulierController, type: :controller do
fci.reload
expect(fci.user).to be_nil
expect(fci.merge_token).not_to be_nil
expect(fci.email_merge_token).not_to be_nil
expect(controller.current_user).to be_nil
expect(response).to redirect_to(root_path)
end
@ -340,6 +341,8 @@ describe FranceConnect::ParticulierController, type: :controller do
context 'when an account with the same email exists' do
let!(:user) { create(:user, email: email) }
before { allow(controller).to receive(:sign_in).and_call_original }
render_views
it 'asks for the corresponding password' do
@ -352,6 +355,15 @@ describe FranceConnect::ParticulierController, type: :controller do
expect(response.body).to include('entrez votre mot de passe')
end
it 'cannot use the merge token in the email confirmation route' do
subject
fci.reload
get :mail_merge_with_existing_account, params: { email_merge_token: fci.merge_token }
expect(controller).not_to have_received(:sign_in)
expect(flash[:alert]).to be_present
end
end
end
@ -360,6 +372,7 @@ describe FranceConnect::ParticulierController, type: :controller do
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(fci.email_merge_token).to be_present
expect(response).to redirect_to(france_connect_particulier_merge_path(fci.reload.merge_token))
end
end

View file

@ -65,7 +65,7 @@ RSpec.describe UserMailer, type: :mailer do
subject { described_class.france_connect_merge_confirmation(email, code, 15.minutes.from_now) }
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)) }
it { expect(subject.body).to include(france_connect_particulier_mail_merge_with_existing_account_url(email_merge_token: code)) }
context 'without SafeMailer configured' do
it { expect(subject[BalancerDeliveryMethod::FORCE_DELIVERY_METHOD_HEADER]&.value).to eq(nil) }