Merge pull request #6582 from betagouv/merge_2
Permet à un usager de merger son compte (#6550)
This commit is contained in:
commit
d73702ba3d
14 changed files with 190 additions and 44 deletions
|
@ -1,6 +1,9 @@
|
||||||
module Users
|
module Users
|
||||||
class ProfilController < UserController
|
class ProfilController < UserController
|
||||||
|
before_action :ensure_update_email_is_authorized, only: :update_email
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@waiting_merge_emails = waiting_merge_emails
|
||||||
@waiting_transfers = current_user.dossiers.joins(:transfer).group('dossier_transfers.email').count.to_a
|
@waiting_transfers = current_user.dossiers.joins(:transfer).group('dossier_transfers.email').count.to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -11,13 +14,16 @@ module Users
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_email
|
def update_email
|
||||||
if current_user.instructeur? && !target_email_allowed?
|
requested_user = User.find_by(email: requested_email)
|
||||||
flash.alert = t('.email_not_allowed', contact_email: CONTACT_EMAIL, requested_email: requested_email)
|
|
||||||
elsif current_user.update(update_email_params)
|
if requested_user.present?
|
||||||
|
current_user.ask_for_merge(requested_user)
|
||||||
|
current_user.update(unconfirmed_email: nil)
|
||||||
|
|
||||||
flash.notice = t('devise.registrations.update_needs_confirmation')
|
flash.notice = t('devise.registrations.update_needs_confirmation')
|
||||||
elsif current_user.errors&.details&.dig(:email)&.any? { |e| e[:error] == :taken }
|
elsif current_user.update(update_email_params)
|
||||||
UserMailer.account_already_taken(current_user, requested_email).deliver_later
|
current_user.update(requested_merge_into: nil)
|
||||||
# avoid leaking information about whether an account with this email exists or not
|
|
||||||
flash.notice = t('devise.registrations.update_needs_confirmation')
|
flash.notice = t('devise.registrations.update_needs_confirmation')
|
||||||
else
|
else
|
||||||
flash.alert = current_user.errors.full_messages
|
flash.alert = current_user.errors.full_messages
|
||||||
|
@ -32,8 +38,39 @@ module Users
|
||||||
redirect_to profil_path
|
redirect_to profil_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def accept_merge
|
||||||
|
users_requesting_merge.each { |user| current_user.merge(user) }
|
||||||
|
users_requesting_merge.update_all(requested_merge_into_id: nil)
|
||||||
|
|
||||||
|
flash.notice = "Vous avez absorbé le compte #{waiting_merge_emails.join(', ')}"
|
||||||
|
redirect_to profil_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def refuse_merge
|
||||||
|
users = users_requesting_merge
|
||||||
|
users.update_all(requested_merge_into_id: nil)
|
||||||
|
|
||||||
|
flash.notice = 'La fusion a été refusé'
|
||||||
|
redirect_to profil_path
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def waiting_merge_emails
|
||||||
|
users_requesting_merge.pluck(:email)
|
||||||
|
end
|
||||||
|
|
||||||
|
def users_requesting_merge
|
||||||
|
@requesting_merge ||= current_user.requested_merge_from
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_update_email_is_authorized
|
||||||
|
if current_user.instructeur? && !target_email_allowed?
|
||||||
|
flash.alert = t('users.profil.ensure_update_email_is_authorized.email_not_allowed', contact_email: CONTACT_EMAIL, requested_email: requested_email)
|
||||||
|
redirect_to profil_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update_email_params
|
def update_email_params
|
||||||
params.require(:user).permit(:email)
|
params.require(:user).permit(:email)
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,10 +12,10 @@ class UserMailer < ApplicationMailer
|
||||||
mail(to: user.email, subject: @subject, procedure: @procedure)
|
mail(to: user.email, subject: @subject, procedure: @procedure)
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_already_taken(user, requested_email)
|
def ask_for_merge(user, requested_email)
|
||||||
@user = user
|
@user = user
|
||||||
@requested_email = requested_email
|
@requested_email = requested_email
|
||||||
@subject = "Changement d’adresse email"
|
@subject = "Fusion de compte"
|
||||||
|
|
||||||
mail(to: requested_email, subject: @subject)
|
mail(to: requested_email, subject: @subject)
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
# administrateur_id :bigint
|
# administrateur_id :bigint
|
||||||
# expert_id :bigint
|
# expert_id :bigint
|
||||||
# instructeur_id :bigint
|
# instructeur_id :bigint
|
||||||
|
# requested_merge_into_id :bigint
|
||||||
#
|
#
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
include EmailSanitizableConcern
|
include EmailSanitizableConcern
|
||||||
|
@ -48,10 +49,13 @@ class User < ApplicationRecord
|
||||||
has_many :dossiers_invites, through: :invites, source: :dossier
|
has_many :dossiers_invites, through: :invites, source: :dossier
|
||||||
has_many :deleted_dossiers
|
has_many :deleted_dossiers
|
||||||
has_many :merge_logs, dependent: :destroy
|
has_many :merge_logs, dependent: :destroy
|
||||||
|
has_many :requested_merge_from, class_name: 'User', dependent: :nullify, inverse_of: :requested_merge_into, foreign_key: :requested_merge_into_id
|
||||||
|
|
||||||
has_one :france_connect_information, dependent: :destroy
|
has_one :france_connect_information, dependent: :destroy
|
||||||
belongs_to :instructeur, optional: true, dependent: :destroy
|
belongs_to :instructeur, optional: true, dependent: :destroy
|
||||||
belongs_to :administrateur, optional: true, dependent: :destroy
|
belongs_to :administrateur, optional: true, dependent: :destroy
|
||||||
belongs_to :expert, optional: true, dependent: :destroy
|
belongs_to :expert, optional: true, dependent: :destroy
|
||||||
|
belongs_to :requested_merge_into, class_name: 'User', optional: true
|
||||||
|
|
||||||
accepts_nested_attributes_for :france_connect_information
|
accepts_nested_attributes_for :france_connect_information
|
||||||
|
|
||||||
|
@ -218,6 +222,11 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ask_for_merge(requested_user)
|
||||||
|
update(requested_merge_into: requested_user)
|
||||||
|
UserMailer.ask_for_merge(self, requested_user.email).deliver_later
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def link_invites!
|
def link_invites!
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
- content_for(:title, @subject)
|
|
||||||
|
|
||||||
%p
|
|
||||||
Bonjour,
|
|
||||||
|
|
||||||
%p
|
|
||||||
L’utilisateur « #{@user.email} » a demandé le changement de son adresse vers « #{@requested_email} ».
|
|
||||||
|
|
||||||
%p
|
|
||||||
Malheureusement, votre compte « #{@requested_email} » existe déjà. Nous ne pouvons pas fusionner automatiquement vos comptes.
|
|
||||||
|
|
||||||
%p
|
|
||||||
%strong Nous ne pouvons donc pas effectuer le changement d’adresse email.
|
|
||||||
|
|
||||||
%p
|
|
||||||
Si vous n’êtes pas à l’origine de cette demande, vous pouvez ignorer ce message. Et si vous avez besoin d’assistance, n’hésitez pas à nous contacter à
|
|
||||||
= succeed '.' do
|
|
||||||
= mail_to CONTACT_EMAIL
|
|
||||||
|
|
||||||
= render partial: "layouts/mailers/signature"
|
|
22
app/views/user_mailer/ask_for_merge.haml
Normal file
22
app/views/user_mailer/ask_for_merge.haml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
- content_for(:title, @subject)
|
||||||
|
|
||||||
|
%p
|
||||||
|
Bonjour,
|
||||||
|
|
||||||
|
%p
|
||||||
|
L’utilisateur <b>« #{@user.email} »</b> a demandé la fusion de son compte avec le votre <b>« #{@requested_email} »</b>.
|
||||||
|
|
||||||
|
%p
|
||||||
|
Si vous désirez confirmer la fusion de ces comptes :
|
||||||
|
|
||||||
|
%ol
|
||||||
|
%li connectez-vous avec le compte #{@requested_email}
|
||||||
|
%li allez sur votre page profil
|
||||||
|
%li acceptez la fusion
|
||||||
|
|
||||||
|
%p
|
||||||
|
Si vous n’êtes pas à l’origine de cette demande, vous pouvez ignorer ce message. Et si vous avez besoin d’assistance, n’hésitez pas à nous contacter à
|
||||||
|
= succeed '.' do
|
||||||
|
= mail_to CONTACT_EMAIL
|
||||||
|
|
||||||
|
= render partial: "layouts/mailers/signature"
|
|
@ -5,18 +5,30 @@
|
||||||
#profil-page.container
|
#profil-page.container
|
||||||
%h1 Profil
|
%h1 Profil
|
||||||
|
|
||||||
|
- if @waiting_merge_emails.any?
|
||||||
|
.card
|
||||||
|
.card-title Demande de fusion de comptes
|
||||||
|
%p
|
||||||
|
Acceptez-vous d’absorber le compte de
|
||||||
|
%span.email-address= @waiting_merge_emails.join(', ')
|
||||||
|
|
||||||
|
= link_to 'Refuser la fusion', refuse_merge_path, method: :post, class: 'button', data: { confirm: "Confirmez-vous le refus ?" }
|
||||||
|
= link_to 'Accepter la fusion', accept_merge_path, method: :post, class: 'button', data: { confirm: "Confirmez-vous la fusion des comptes ?" }
|
||||||
|
|
||||||
.card
|
.card
|
||||||
.card-title Coordonnées
|
.card-title Coordonnées
|
||||||
%p
|
%p
|
||||||
Votre email est actuellement
|
Votre email est actuellement
|
||||||
%span.email-address= current_user.email
|
%span.email-address= current_user.email
|
||||||
- if current_user.unconfirmed_email.present?
|
|
||||||
.card.warning
|
- waiting_email = current_user.unconfirmed_email || current_user.requested_merge_into&.email
|
||||||
.card-title
|
|
||||||
Changement en attente :
|
- if waiting_email.present?
|
||||||
%span.email-address= current_user.unconfirmed_email
|
%p.mb-4
|
||||||
%p
|
Changement en attente :
|
||||||
Pour finaliser votre changement d’adresse, vérifiez vos emails et cliquez sur le lien de confirmation.
|
%span.email-address= waiting_email
|
||||||
|
%br
|
||||||
|
Pour finaliser votre changement d’adresse, vérifiez vos emails et cliquez sur le lien de confirmation.
|
||||||
|
|
||||||
- if current_user.instructeur?
|
- if current_user.instructeur?
|
||||||
%p.mb-4
|
%p.mb-4
|
||||||
|
|
|
@ -21,7 +21,7 @@ fr:
|
||||||
<br>
|
<br>
|
||||||
Si ce n'est pas votre cas, contactez le support :
|
Si ce n'est pas votre cas, contactez le support :
|
||||||
<a href="mailto:%{contact_email}">%{contact_email}</a>
|
<a href="mailto:%{contact_email}">%{contact_email}</a>
|
||||||
update_email:
|
ensure_update_email_is_authorized:
|
||||||
email_not_allowed: "L’email %{requested_email} ne peut être utilisé, contactez le support : <a href='mailto:%{contact_email}'>%{contact_email}</a>"
|
email_not_allowed: "L’email %{requested_email} ne peut être utilisé, contactez le support : <a href='mailto:%{contact_email}'>%{contact_email}</a>"
|
||||||
transfer_all_dossiers:
|
transfer_all_dossiers:
|
||||||
new_transfer:
|
new_transfer:
|
||||||
|
|
|
@ -280,6 +280,8 @@ Rails.application.routes.draw do
|
||||||
get 'renew-api-token' => redirect('/profil')
|
get 'renew-api-token' => redirect('/profil')
|
||||||
patch 'update_email' => 'profil#update_email'
|
patch 'update_email' => 'profil#update_email'
|
||||||
post 'transfer_all_dossiers' => 'profil#transfer_all_dossiers'
|
post 'transfer_all_dossiers' => 'profil#transfer_all_dossiers'
|
||||||
|
post 'accept_merge' => 'profil#accept_merge'
|
||||||
|
post 'refuse_merge' => 'profil#refuse_merge'
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddRequestedMergeIntoColumnToUsers < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_reference :users, :requested_merge_into, foreign_key: { to_table: :users }, null: true, index: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -794,11 +794,13 @@ ActiveRecord::Schema.define(version: 2021_10_26_131800) do
|
||||||
t.bigint "administrateur_id"
|
t.bigint "administrateur_id"
|
||||||
t.bigint "expert_id"
|
t.bigint "expert_id"
|
||||||
t.string "locale"
|
t.string "locale"
|
||||||
|
t.bigint "requested_merge_into_id"
|
||||||
t.index ["administrateur_id"], name: "index_users_on_administrateur_id"
|
t.index ["administrateur_id"], name: "index_users_on_administrateur_id"
|
||||||
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
|
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
|
||||||
t.index ["email"], name: "index_users_on_email", unique: true
|
t.index ["email"], name: "index_users_on_email", unique: true
|
||||||
t.index ["expert_id"], name: "index_users_on_expert_id"
|
t.index ["expert_id"], name: "index_users_on_expert_id"
|
||||||
t.index ["instructeur_id"], name: "index_users_on_instructeur_id"
|
t.index ["instructeur_id"], name: "index_users_on_instructeur_id"
|
||||||
|
t.index ["requested_merge_into_id"], name: "index_users_on_requested_merge_into_id"
|
||||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||||
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
|
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
|
||||||
end
|
end
|
||||||
|
@ -868,5 +870,6 @@ ActiveRecord::Schema.define(version: 2021_10_26_131800) do
|
||||||
add_foreign_key "users", "administrateurs"
|
add_foreign_key "users", "administrateurs"
|
||||||
add_foreign_key "users", "experts"
|
add_foreign_key "users", "experts"
|
||||||
add_foreign_key "users", "instructeurs"
|
add_foreign_key "users", "instructeurs"
|
||||||
|
add_foreign_key "users", "users", column: "requested_merge_into_id"
|
||||||
add_foreign_key "without_continuation_mails", "procedures"
|
add_foreign_key "without_continuation_mails", "procedures"
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,12 +49,16 @@ describe Users::ProfilController, type: :controller do
|
||||||
|
|
||||||
describe 'PATCH #update_email' do
|
describe 'PATCH #update_email' do
|
||||||
context 'when everything is fine' do
|
context 'when everything is fine' do
|
||||||
|
let(:previous_request) { create(:user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
user.update(requested_merge_into: previous_request)
|
||||||
patch :update_email, params: { user: { email: 'loulou@lou.com' } }
|
patch :update_email, params: { user: { email: 'loulou@lou.com' } }
|
||||||
user.reload
|
user.reload
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(user.unconfirmed_email).to eq('loulou@lou.com') }
|
it { expect(user.unconfirmed_email).to eq('loulou@lou.com') }
|
||||||
|
it { expect(user.requested_merge_into).to be_nil }
|
||||||
it { expect(response).to redirect_to(profil_path) }
|
it { expect(response).to redirect_to(profil_path) }
|
||||||
it { expect(flash.notice).to eq(I18n.t('devise.registrations.update_needs_confirmation')) }
|
it { expect(flash.notice).to eq(I18n.t('devise.registrations.update_needs_confirmation')) }
|
||||||
end
|
end
|
||||||
|
@ -63,16 +67,21 @@ describe Users::ProfilController, type: :controller do
|
||||||
let(:existing_user) { create(:user) }
|
let(:existing_user) { create(:user) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
user.update(unconfirmed_email: 'unconfirmed@mail.com')
|
||||||
|
|
||||||
|
expect_any_instance_of(User).to receive(:ask_for_merge).with(existing_user)
|
||||||
|
|
||||||
perform_enqueued_jobs do
|
perform_enqueued_jobs do
|
||||||
patch :update_email, params: { user: { email: existing_user.email } }
|
patch :update_email, params: { user: { email: existing_user.email } }
|
||||||
end
|
end
|
||||||
user.reload
|
user.reload
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(user.unconfirmed_email).to be_nil }
|
it 'launches the merge process' do
|
||||||
it { expect(ActionMailer::Base.deliveries.last.to).to eq([existing_user.email]) }
|
expect(user.unconfirmed_email).to be_nil
|
||||||
it { expect(response).to redirect_to(profil_path) }
|
expect(response).to redirect_to(profil_path)
|
||||||
it { expect(flash.notice).to eq(I18n.t('devise.registrations.update_needs_confirmation')) }
|
expect(flash.notice).to eq(I18n.t('devise.registrations.update_needs_confirmation'))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the mail is incorrect' do
|
context 'when the mail is incorrect' do
|
||||||
|
@ -126,4 +135,38 @@ describe Users::ProfilController, type: :controller do
|
||||||
expect(flash.notice).to eq("Le transfert de 3 dossiers à #{next_owner} est en cours")
|
expect(flash.notice).to eq("Le transfert de 3 dossiers à #{next_owner} est en cours")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'POST #accept_merge' do
|
||||||
|
let!(:requesting_user) { create(:user, requested_merge_into: user) }
|
||||||
|
|
||||||
|
subject { post :accept_merge }
|
||||||
|
|
||||||
|
it 'merges the account' do
|
||||||
|
expect_any_instance_of(User).to receive(:merge)
|
||||||
|
|
||||||
|
subject
|
||||||
|
requesting_user.reload
|
||||||
|
|
||||||
|
expect(requesting_user.requested_merge_into).to be_nil
|
||||||
|
expect(flash.notice).to include('Vous avez absorbé')
|
||||||
|
expect(response).to redirect_to(profil_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'POST #refuse_merge' do
|
||||||
|
let!(:requesting_user) { create(:user, requested_merge_into: user) }
|
||||||
|
|
||||||
|
subject { post :refuse_merge }
|
||||||
|
|
||||||
|
it 'merges the account' do
|
||||||
|
expect_any_instance_of(User).not_to receive(:merge)
|
||||||
|
|
||||||
|
subject
|
||||||
|
requesting_user.reload
|
||||||
|
|
||||||
|
expect(requesting_user.requested_merge_into).to be_nil
|
||||||
|
expect(flash.notice).to include('La fusion a été refusé')
|
||||||
|
expect(response).to redirect_to(profil_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,8 +8,8 @@ class UserMailerPreview < ActionMailer::Preview
|
||||||
UserMailer.new_account_warning(user, procedure)
|
UserMailer.new_account_warning(user, procedure)
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_already_taken
|
def ask_for_merge
|
||||||
UserMailer.account_already_taken(user, 'dircab@territoires.gouv.fr')
|
UserMailer.ask_for_merge(user, 'dircab@territoires.gouv.fr')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -17,10 +17,10 @@ RSpec.describe UserMailer, type: :mailer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.account_already_taken' do
|
describe '.ask_for_merge' do
|
||||||
let(:requested_email) { 'new@exemple.fr' }
|
let(:requested_email) { 'new@exemple.fr' }
|
||||||
|
|
||||||
subject { described_class.account_already_taken(user, requested_email) }
|
subject { described_class.ask_for_merge(user, requested_email) }
|
||||||
|
|
||||||
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) }
|
||||||
|
|
|
@ -29,3 +29,36 @@ describe 'Changing an email' do
|
||||||
expect(user.reload.email).to eq(new_email)
|
expect(user.reload.email).to eq(new_email)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'Merging account' do
|
||||||
|
let(:old_user) { create(:user) }
|
||||||
|
let(:new_user) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
login_as old_user, scope: :user
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'is easy' do
|
||||||
|
visit '/profil'
|
||||||
|
|
||||||
|
fill_in :user_email, with: new_user.email
|
||||||
|
|
||||||
|
perform_enqueued_jobs do
|
||||||
|
click_button 'Changer mon adresse'
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(page).to have_content(I18n.t('devise.registrations.update_needs_confirmation'))
|
||||||
|
expect(page).to have_content(old_user.email)
|
||||||
|
expect(page).to have_content(new_user.email)
|
||||||
|
|
||||||
|
login_as new_user, scope: :user
|
||||||
|
visit '/profil'
|
||||||
|
|
||||||
|
expect(page).to have_content("Acceptez-vous d’absorber le compte de #{old_user.email}")
|
||||||
|
click_on 'Accepter la fusion'
|
||||||
|
|
||||||
|
expect(page).not_to have_content(old_user.email)
|
||||||
|
expect(page).to have_content(new_user.email)
|
||||||
|
expect { old_user.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in a new issue