Merge pull request #10570 from demarches-simplifiees/feat/10425
ETQ Usager se connectant par FC, je dois confirmer mon mail
This commit is contained in:
commit
9fd53b182a
26 changed files with 660 additions and 332 deletions
|
@ -2,8 +2,9 @@
|
|||
|
||||
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, :resend_and_renew_merge_confirmation]
|
||||
before_action :securely_retrieve_fci_from_email_merge_token, only: [:mail_merge_with_existing_account]
|
||||
before_action :securely_retrieve_fci, only: [:merge_using_fc_email, :merge_using_password, :send_email_merge_request]
|
||||
before_action :securely_retrieve_fci_from_email_merge_token, only: [:merge_using_email_link]
|
||||
before_action :set_user_by_confirmation_token, only: [:confirm_email]
|
||||
|
||||
def login
|
||||
if FranceConnectService.enabled?
|
||||
|
@ -14,112 +15,136 @@ class FranceConnect::ParticulierController < ApplicationController
|
|||
end
|
||||
|
||||
def callback
|
||||
fci = FranceConnectService.find_or_retrieve_france_connect_information(params[:code])
|
||||
@fci = FranceConnectService.find_or_retrieve_france_connect_information(params[:code])
|
||||
|
||||
if fci.user.nil?
|
||||
preexisting_unlinked_user = User.find_by(email: sanitize(fci.email_france_connect))
|
||||
if @fci.user.nil?
|
||||
preexisting_unlinked_user = User.find_by(email: sanitize(@fci.email_france_connect))
|
||||
|
||||
if preexisting_unlinked_user.nil?
|
||||
fci.associate_user!(fci.email_france_connect)
|
||||
connect_france_connect_particulier(fci.user)
|
||||
elsif !preexisting_unlinked_user.can_france_connect?
|
||||
fci.destroy
|
||||
redirect_to new_user_session_path, alert: t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path)
|
||||
@fci.create_merge_token!
|
||||
render :choose_email
|
||||
elsif preexisting_unlinked_user.can_france_connect?
|
||||
@fci.create_merge_token!
|
||||
render :merge
|
||||
else
|
||||
merge_token = fci.create_merge_token!
|
||||
redirect_to france_connect_particulier_merge_path(merge_token)
|
||||
destroy_fci_and_redirect_to_login(@fci)
|
||||
end
|
||||
else
|
||||
user = fci.user
|
||||
|
||||
if user.can_france_connect?
|
||||
fci.update(updated_at: Time.zone.now)
|
||||
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)
|
||||
if @fci.user.can_france_connect?
|
||||
@fci.update(updated_at: Time.zone.now)
|
||||
connect_france_connect_particulier(@fci.user)
|
||||
else
|
||||
destroy_fci_and_redirect_to_login(@fci)
|
||||
end
|
||||
end
|
||||
|
||||
rescue Rack::OAuth2::Client::Error => e
|
||||
Rails.logger.error e.message
|
||||
redirect_france_connect_error_connection
|
||||
redirect_to(new_user_session_path, alert: t('errors.messages.france_connect.connexion'))
|
||||
end
|
||||
|
||||
def merge
|
||||
end
|
||||
def send_email_merge_request
|
||||
@fci.update(requested_email: sanitized_email_params)
|
||||
|
||||
def merge_with_existing_account
|
||||
user = User.find_by(email: sanitized_email_params)
|
||||
|
||||
if user.present? && user.valid_for_authentication? { user.valid_password?(password_params) }
|
||||
if !user.can_france_connect?
|
||||
flash.alert = t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path)
|
||||
|
||||
redirect_to root_path
|
||||
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: Current.application_name)
|
||||
connect_france_connect_particulier(user)
|
||||
end
|
||||
else
|
||||
flash.alert = t('france_connect.particulier.flash.invalid_password')
|
||||
end
|
||||
end
|
||||
|
||||
def mail_merge_with_existing_account
|
||||
user = User.find_by(email: sanitize(@fci.email_france_connect.downcase))
|
||||
if user.can_france_connect?
|
||||
@fci.update(user: user)
|
||||
@fci.delete_merge_token!
|
||||
|
||||
flash.notice = t('france_connect.particulier.flash.connection_done', application_name: Current.application_name)
|
||||
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
|
||||
user = User.find_by(email: sanitized_email_params)
|
||||
|
||||
if user.nil?
|
||||
@fci.associate_user!(sanitized_email_params)
|
||||
@fci.delete_merge_token!
|
||||
|
||||
flash.notice = t('france_connect.particulier.flash.connection_done', application_name: Current.application_name)
|
||||
connect_france_connect_particulier(@fci.user)
|
||||
else
|
||||
@email = sanitized_email_params
|
||||
@merge_token = merge_token_params
|
||||
end
|
||||
end
|
||||
|
||||
def resend_and_renew_merge_confirmation
|
||||
@fci.create_email_merge_token!
|
||||
UserMailer.france_connect_merge_confirmation(
|
||||
@fci.email_france_connect,
|
||||
sanitized_email_params,
|
||||
@fci.email_merge_token,
|
||||
@fci.email_merge_token_created_at
|
||||
)
|
||||
.deliver_later
|
||||
|
||||
merge_token = @fci.create_merge_token!
|
||||
redirect_to france_connect_particulier_merge_path(merge_token),
|
||||
notice: t('france_connect.particulier.flash.confirmation_mail_sent')
|
||||
redirect_to root_path, notice: t('france_connect.particulier.flash.confirmation_mail_sent')
|
||||
end
|
||||
|
||||
def merge_using_fc_email
|
||||
@fci.safely_associate_user!(@fci.email_france_connect)
|
||||
|
||||
sign_in(@fci.user)
|
||||
|
||||
@fci.send_custom_confirmation_instructions
|
||||
|
||||
render :confirmation_sent, locals: { email: @fci.email_france_connect, destination_path: destination_path(@fci.user) }
|
||||
end
|
||||
|
||||
def merge_using_password
|
||||
user = User.find_by(email: sanitize(@fci.email_france_connect))
|
||||
|
||||
if user.present? && !user.can_france_connect?
|
||||
return destroy_fci_and_redirect_to_login(@fci)
|
||||
end
|
||||
|
||||
if user.present? && user.valid_for_authentication? { user.valid_password?(params[:password]) }
|
||||
@fci.safely_update_user(user:)
|
||||
|
||||
flash.notice = t('france_connect.particulier.flash.connection_done', application_name: Current.application_name)
|
||||
connect_france_connect_particulier(user)
|
||||
else
|
||||
flash.alert = t('france_connect.particulier.flash.invalid_password')
|
||||
end
|
||||
end
|
||||
|
||||
def merge_using_email_link
|
||||
user = User.find_by(email: @fci.requested_email)
|
||||
|
||||
if user.present? && !user.can_france_connect?
|
||||
return destroy_fci_and_redirect_to_login(@fci)
|
||||
end
|
||||
|
||||
if user.nil?
|
||||
@fci.safely_associate_user!(@fci.requested_email)
|
||||
else
|
||||
@fci.safely_update_user(user:)
|
||||
end
|
||||
|
||||
@fci.user.update(email_verified_at: Time.zone.now)
|
||||
|
||||
flash.notice = t('france_connect.particulier.flash.connection_done', application_name: Current.application_name)
|
||||
connect_france_connect_particulier(@fci.user)
|
||||
end
|
||||
|
||||
# TODO mutualiser avec le controller Users::ActivateController
|
||||
# pour toute la partie de confirmation de compte
|
||||
def confirm_email
|
||||
if @user.confirmation_sent_at && 2.days.ago < @user.confirmation_sent_at
|
||||
@user.update(email_verified_at: Time.zone.now, confirmation_token: nil)
|
||||
@user.after_confirmation
|
||||
redirect_to destination_path(@user), notice: I18n.t('france_connect.particulier.flash.email_confirmed')
|
||||
return
|
||||
end
|
||||
|
||||
fci = FranceConnectInformation.find_by(user: @user)
|
||||
|
||||
if fci
|
||||
fci.send_custom_confirmation_instructions
|
||||
redirect_to root_path, notice: I18n.t('france_connect.particulier.flash.confirmation_mail_resent')
|
||||
else
|
||||
redirect_to root_path, alert: I18n.t('france_connect.particulier.flash.confirmation_mail_resent_error')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user_by_confirmation_token
|
||||
@user = User.find_by(confirmation_token: params[:token])
|
||||
|
||||
if @user.nil?
|
||||
return redirect_to root_path, alert: I18n.t('france_connect.particulier.flash.user_not_found')
|
||||
end
|
||||
|
||||
if user_signed_in? && current_user != @user
|
||||
sign_out :user
|
||||
redirect_to new_user_session_path, alert: I18n.t('france_connect.particulier.flash.redirect_new_user_session')
|
||||
end
|
||||
end
|
||||
|
||||
def destination_path(user) = stored_location_for(user) || root_path(user)
|
||||
|
||||
def securely_retrieve_fci_from_email_merge_token
|
||||
@fci = FranceConnectInformation.find_by(email_merge_token: email_merge_token_params)
|
||||
@fci = FranceConnectInformation.find_by(email_merge_token: params[:email_merge_token])
|
||||
|
||||
if @fci.nil? || !@fci.valid_for_email_merge?
|
||||
flash.alert = t('france_connect.particulier.flash.merger_token_expired', application_name: Current.application_name)
|
||||
flash.alert = I18n.t('france_connect.particulier.flash.merger_token_expired', application_name: Current.application_name)
|
||||
|
||||
redirect_to root_path
|
||||
else
|
||||
|
@ -128,10 +153,10 @@ class FranceConnect::ParticulierController < ApplicationController
|
|||
end
|
||||
|
||||
def securely_retrieve_fci
|
||||
@fci = FranceConnectInformation.find_by(merge_token: merge_token_params)
|
||||
@fci = FranceConnectInformation.find_by(merge_token: params[:merge_token])
|
||||
|
||||
if @fci.nil? || !@fci.valid_for_merge?
|
||||
flash.alert = t('france_connect.particulier.flash.merger_token_expired', application_name: Current.application_name)
|
||||
flash.alert = I18n.t('france_connect.particulier.flash.merger_token_expired', application_name: Current.application_name)
|
||||
|
||||
redirect_to root_path
|
||||
end
|
||||
|
@ -143,33 +168,18 @@ class FranceConnect::ParticulierController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def connect_france_connect_particulier(user)
|
||||
if user_signed_in?
|
||||
sign_out :user
|
||||
end
|
||||
def destroy_fci_and_redirect_to_login(fci)
|
||||
fci.destroy
|
||||
redirect_to new_user_session_path, alert: t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path)
|
||||
end
|
||||
|
||||
def connect_france_connect_particulier(user)
|
||||
sign_out :user if user_signed_in?
|
||||
sign_in user
|
||||
|
||||
user.update_attribute('loged_in_with_france_connect', User.loged_in_with_france_connects.fetch(:particulier))
|
||||
|
||||
redirect_to stored_location_for(current_user) || root_path(current_user)
|
||||
end
|
||||
|
||||
def redirect_france_connect_error_connection
|
||||
flash.alert = t('errors.messages.france_connect.connexion')
|
||||
redirect_to(new_user_session_path)
|
||||
end
|
||||
|
||||
def merge_token_params
|
||||
params[:merge_token]
|
||||
end
|
||||
|
||||
def email_merge_token_params
|
||||
params[:email_merge_token]
|
||||
end
|
||||
|
||||
def password_params
|
||||
params[:password]
|
||||
redirect_to destination_path(current_user)
|
||||
end
|
||||
|
||||
def sanitized_email_params
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class EmailFranceConnectController extends ApplicationController {
|
||||
static targets = ['useFranceConnectEmail', 'emailField'];
|
||||
|
||||
emailFieldTarget!: HTMLElement;
|
||||
useFranceConnectEmailTargets!: HTMLInputElement[];
|
||||
|
||||
connect() {
|
||||
this.triggerEmailField();
|
||||
}
|
||||
|
||||
triggerEmailField() {
|
||||
const checkedTarget = this.useFranceConnectEmailTargets.find(
|
||||
(target) => target.checked
|
||||
);
|
||||
|
||||
const inputElement = this.emailFieldTarget.querySelector(
|
||||
'input[type="email"]'
|
||||
) as HTMLInputElement;
|
||||
|
||||
if (checkedTarget && checkedTarget.value === 'false') {
|
||||
this.emailFieldTarget.classList.remove('hidden');
|
||||
this.emailFieldTarget.setAttribute('aria-hidden', 'false');
|
||||
inputElement.setAttribute('required', '');
|
||||
} else {
|
||||
this.emailFieldTarget.classList.add('hidden');
|
||||
this.emailFieldTarget.setAttribute('aria-hidden', 'true');
|
||||
inputElement.removeAttribute('required');
|
||||
inputElement.value = '';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,12 @@ class UserMailer < ApplicationMailer
|
|||
mail(to: email, subject: @subject)
|
||||
end
|
||||
|
||||
def custom_confirmation_instructions(user, token)
|
||||
@user = user
|
||||
@token = token
|
||||
mail(to: @user.email, subject: 'Confirmez votre email')
|
||||
end
|
||||
|
||||
def invite_instructeur(user, reset_password_token)
|
||||
@reset_password_token = reset_password_token
|
||||
@user = user
|
||||
|
@ -139,7 +145,8 @@ class UserMailer < ApplicationMailer
|
|||
'france_connect_merge_confirmation',
|
||||
"new_account_warning",
|
||||
"ask_for_merge",
|
||||
"invite_instructeur"
|
||||
"invite_instructeur",
|
||||
"custom_confirmation_instructions"
|
||||
].include?(action_name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,28 +2,41 @@
|
|||
|
||||
class FranceConnectInformation < ApplicationRecord
|
||||
MERGE_VALIDITY = 15.minutes
|
||||
CONFIRMATION_EMAIL_VALIDITY = 2.days
|
||||
|
||||
belongs_to :user, optional: true
|
||||
|
||||
validates :france_connect_particulier_id, presence: true, allow_blank: false, allow_nil: false
|
||||
|
||||
def associate_user!(email)
|
||||
def safely_associate_user!(email)
|
||||
begin
|
||||
user = User.create!(
|
||||
email: email.downcase,
|
||||
password: Devise.friendly_token[0, 20],
|
||||
confirmed_at: Time.zone.now
|
||||
)
|
||||
user.after_confirmation
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
# ignore this exception because we check before is user is nil.
|
||||
# ignore this exception because we check before if user is nil.
|
||||
# exception can be raised in race conditions, when FranceConnect calls callback 2 times.
|
||||
# At the 2nd call, user is nil but exception is raised at the creation of the user
|
||||
# because the first call has already created a user
|
||||
end
|
||||
|
||||
clean_tokens_and_requested_email
|
||||
update_attribute('user_id', user.id)
|
||||
touch # needed to update updated_at column
|
||||
save!
|
||||
end
|
||||
|
||||
def safely_update_user(user:)
|
||||
self.user = user
|
||||
clean_tokens_and_requested_email
|
||||
save!
|
||||
end
|
||||
|
||||
def send_custom_confirmation_instructions
|
||||
token = SecureRandom.hex(10)
|
||||
user.update!(confirmation_token: token, confirmation_sent_at: Time.zone.now)
|
||||
UserMailer.custom_confirmation_instructions(user, token).deliver_later
|
||||
end
|
||||
|
||||
def create_merge_token!
|
||||
|
@ -48,14 +61,18 @@ class FranceConnectInformation < ApplicationRecord
|
|||
(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 clean_tokens_and_requested_email
|
||||
self.merge_token = nil
|
||||
self.merge_token_created_at = nil
|
||||
self.email_merge_token = nil
|
||||
self.email_merge_token_created_at = nil
|
||||
self.requested_email = nil
|
||||
end
|
||||
|
||||
def full_name
|
||||
[given_name, family_name].compact.join(" ")
|
||||
end
|
||||
|
|
|
@ -65,7 +65,6 @@ class User < ApplicationRecord
|
|||
|
||||
# Callback provided by Devise
|
||||
def after_confirmation
|
||||
update!(email_verified_at: Time.zone.now)
|
||||
link_invites!
|
||||
end
|
||||
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
%p
|
||||
= t('.already_exists', email: email, application_name: Current.application_name)
|
||||
%br
|
||||
= t('.fill_in_password')
|
||||
= form_tag france_connect_particulier_merge_using_password_path, data: { turbo: true }, class: 'mt-2 form fconnect-form', id: 'merge_using_password' do
|
||||
= hidden_field_tag :merge_token, fci.merge_token, id: dom_id(fci, :fusion_merge_token)
|
||||
.fr-input-group{ class: class_names('fr-input-group--error': wrong_password) }
|
||||
= label_tag :password, t('views.registrations.new.password_label', min_length: 8), class: 'fr-label'
|
||||
= password_field_tag :password, nil, autocomplete: 'current-password', class: 'mb-1 fr-input'
|
||||
|
||||
= form_tag france_connect_particulier_merge_with_existing_account_path, data: { turbo: true, turbo_force: :server }, class: 'mt-2 form fconnect-form' do
|
||||
= hidden_field_tag :merge_token, merge_token
|
||||
= hidden_field_tag :email, email
|
||||
= label_tag :password, t('views.registrations.new.password_label', min_length: 8)
|
||||
= password_field_tag :password, nil, autocomplete: 'current-password', id: 'password-for-another-account'
|
||||
.mb-2
|
||||
= t('views.users.sessions.new.reset_password')
|
||||
= link_to france_connect_particulier_resend_and_renew_merge_confirmation_path(merge_token: merge_token), method: :post do
|
||||
= t('france_connect.particulier.merge.link_confirm_by_email')
|
||||
= button_tag t('.back'), type: 'button', class: 'button secondary', onclick: 'DS.showNewAccount(event);'
|
||||
= submit_tag t('france_connect.particulier.merge.button_merge'), class: 'button primary'
|
||||
= submit_tag t('france_connect.particulier.merge.button_merge'), class: 'fr-btn'
|
||||
|
|
40
app/views/france_connect/particulier/choose_email.html.haml
Normal file
40
app/views/france_connect/particulier/choose_email.html.haml
Normal file
|
@ -0,0 +1,40 @@
|
|||
.fr-container
|
||||
%h1.text-center.mt-1= t('.choose_email_contact')
|
||||
|
||||
%p= t('.intro_html', email: @fci.email_france_connect)
|
||||
|
||||
%p= t('.use_email_for_notifications')
|
||||
|
||||
.fr-fieldset.fr-w-30v.fr-mt-2w
|
||||
= form_with url: france_connect_particulier_merge_using_fc_email_path(merge_token: @fci.merge_token), method: :post, data: { controller: 'email-france-connect' } do |f|
|
||||
= hidden_field_tag :merge_token, @fci.merge_token
|
||||
|
||||
%fieldset.fr-fieldset
|
||||
%legend.fr-fieldset__legend
|
||||
.fr-fieldset__element
|
||||
.fr-radio-group
|
||||
= f.radio_button :use_france_connect_email, true, id: 'use_france_connect_email_yes', class: 'fr-radio', required: true, data: { action: "email-france-connect#triggerEmailField", email_france_connect_target: "useFranceConnectEmail" }
|
||||
%label.fr-label.fr-text--wrap{ for: 'use_france_connect_email_yes' }
|
||||
= t('.keep_fc_email_html', email: h(@fci.email_france_connect)).html_safe
|
||||
.fr-fieldset__element
|
||||
.fr-radio-group
|
||||
= f.radio_button :use_france_connect_email, false, id: 'use_france_connect_email_no', class: 'fr-radio', required: true, data: { action: "email-france-connect#triggerEmailField", email_france_connect_target: "useFranceConnectEmail" }
|
||||
%label.fr-label.fr-text--wrap{ for: 'use_france_connect_email_no' }
|
||||
= t('.use_another_email')
|
||||
|
||||
.fr-fieldset__element.fr-fieldset__element--inline.hidden{ aria: { hidden: true }, data: { email_france_connect_target: "emailField", controller: 'email-input', email_input_url_value: show_email_suggestions_path } }
|
||||
= f.label :email, t('.alternative_email'), class: "fr-label"
|
||||
%span.fr-hint-text.mb-1= t('activerecord.attributes.user.hints.email')
|
||||
= f.email_field :email, class: "fr-input"
|
||||
|
||||
.suspect-email.hidden{ data: { "email-input-target": 'ariaRegion'}, aria: { live: 'off' } }
|
||||
= render Dsfr::AlertComponent.new(title: t('utils.email_suggest.wanna_say'), state: :info, heading_level: :div) do |c|
|
||||
- c.with_body do
|
||||
%p{ data: { "email-input-target": 'suggestion'} } exemple@gmail.com ?
|
||||
%p
|
||||
= button_tag type: 'button', class: 'fr-btn fr-btn--sm fr-mr-3w', data: { action: 'click->email-input#accept'} do
|
||||
= t('utils.yes')
|
||||
= button_tag type: 'button', class: 'fr-btn fr-btn--sm', data: { action: 'click->email-input#discard'} do
|
||||
= t('utils.no')
|
||||
%div
|
||||
= f.submit t('.confirm'), class: 'fr-btn'
|
|
@ -0,0 +1,12 @@
|
|||
.fr-container
|
||||
.fr-col-12.fr-col-md-6.fr-col-offset-md-3
|
||||
%h1.fr-mt-6w.fr-h2.center= t('.confirmation_sent_by_email')
|
||||
|
||||
%p.center{ aria: { hidden: true } }= image_tag("user/confirmation-email.svg", alt: t('views.confirmation.new.image_alt'))
|
||||
|
||||
= render Dsfr::AlertComponent.new(title: '', state: :info, heading_level: 'h2', extra_class_names: 'fr-mt-6w fr-mb-3w') do |c|
|
||||
- c.with_body do
|
||||
%p= t('.intro_html', email: h(email)).html_safe
|
||||
%p= t('.click_the_link_in_the_email')
|
||||
|
||||
%p.center= link_to t('.continue'), destination_path, class: 'fr-btn'
|
|
@ -1,46 +1,42 @@
|
|||
= content_for :title, "Fusion des comptes FC et #{Current.application_name}"
|
||||
|
||||
.container
|
||||
.fr-container
|
||||
%h1.page-title= t('.title', application_name: Current.application_name)
|
||||
|
||||
%p= t('.subtitle_html', email: @fci.email_france_connect, application_name: Current.application_name)
|
||||
|
||||
.form.mt-2
|
||||
%label= t('.label_select_merge_flow', email: @fci.email_france_connect)
|
||||
%fieldset.radios
|
||||
%label{ onclick: "DS.showFusion(event);" }
|
||||
= radio_button_tag :value, true, false, autocomplete: "off", id: 'it-is-mine'
|
||||
= t('utils.yes')
|
||||
%fieldset.fr-fieldset{ aria: { labelledby: 'merge-account' } }
|
||||
%legend.fr-fieldset__legend#merge-account= t('.label_select_merge_flow', email: @fci.email_france_connect)
|
||||
.fr-fieldset__element.fr-fieldset__element--inline
|
||||
.fr-radio-group
|
||||
%input{ type: 'radio', id: 'it-is-mine', name: 'value', value: 'true', autocomplete: "off", onclick: "DS.showFusion(event);" }
|
||||
%label{ for: 'it-is-mine' }= t('utils.yes')
|
||||
.fr-fieldset__element.fr-fieldset__element--inline
|
||||
.fr-radio-group
|
||||
%input{ type: 'radio', id: 'it-is-not-mine', name: 'value', value: 'false', autocomplete: "off", onclick: "DS.showNewAccount(event);" }
|
||||
%label{ for: 'it-is-not-mine' }= t('utils.no')
|
||||
|
||||
%label{ onclick: "DS.showNewAccount(event);" }
|
||||
= radio_button_tag :value, false, false, autocomplete: "off", id: 'it-is-not-mine'
|
||||
= t('utils.no')
|
||||
|
||||
.fusion.hidden
|
||||
%p= t('.title_fill_in_password')
|
||||
|
||||
= form_tag france_connect_particulier_merge_with_existing_account_path, data: { turbo: true }, class: 'mt-2 form fconnect-form' do
|
||||
= hidden_field_tag :merge_token, @fci.merge_token, id: dom_id(@fci, :fusion_merge_token)
|
||||
= hidden_field_tag :email, @fci.email_france_connect, id: dom_id(@fci, :fusion_email)
|
||||
.fr-input-group
|
||||
= label_tag :password, t('views.registrations.new.password_label', min_length: 8), class: 'fr-label'
|
||||
= password_field_tag :password, nil, autocomplete: 'current-password', class: 'mb-1 fr-input'
|
||||
.mb-2
|
||||
= t('views.users.sessions.new.reset_password')
|
||||
= link_to france_connect_particulier_resend_and_renew_merge_confirmation_path(merge_token: @fci.merge_token), method: :post do
|
||||
= t('.link_confirm_by_email')
|
||||
= render partial: 'password_confirmation', locals: { fci: @fci, wrong_password: @wrong_password }
|
||||
|
||||
= submit_tag t('.button_merge'), class: 'fr-btn'
|
||||
.mt-2
|
||||
= button_to t('.link_confirm_by_email'),
|
||||
france_connect_particulier_send_email_merge_request_path,
|
||||
params: { email: @fci.email_france_connect, merge_token: @fci.merge_token },
|
||||
class: 'fr-btn fr-btn--secondary'
|
||||
|
||||
|
||||
.new-account.hidden
|
||||
%p= t('.title_fill_in_email', application_name: Current.application_name)
|
||||
|
||||
= form_tag france_connect_particulier_merge_with_new_account_path, data: { turbo: true }, class: 'mt-2 form' do
|
||||
= form_tag france_connect_particulier_send_email_merge_request_path, class: 'mt-2 form' do
|
||||
= hidden_field_tag :merge_token, @fci.merge_token, id: dom_id(@fci, :new_account_merge_token)
|
||||
= label_tag :email, t('views.registrations.new.email_label'), for: dom_id(@fci, :new_account_email)
|
||||
= email_field_tag :email, "", required: true, id: dom_id(@fci, :new_account_email)
|
||||
= submit_tag t('.button_use_this_email'), class: 'button primary'
|
||||
= label_tag :email, t('views.registrations.new.email_label'), for: dom_id(@fci, :new_account_email), class: 'fr-label'
|
||||
= email_field_tag :email, "", required: true, id: dom_id(@fci, :new_account_email), class: 'mb-1 fr-input'
|
||||
= submit_tag t('.button_use_this_email'), class: 'fr-btn'
|
||||
|
||||
|
||||
#new-account-password-confirmation.hidden
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
= turbo_stream.replace('merge_using_password', partial: 'password_confirmation', locals: { fci: @fci, wrong_password: true })
|
|
@ -1,4 +0,0 @@
|
|||
= turbo_stream.update 'new-account-password-confirmation', partial: 'password_confirmation', locals: { email: @email, merge_token: @merge_token }
|
||||
= turbo_stream.hide_all '.fusion'
|
||||
= turbo_stream.hide_all '.new-account'
|
||||
= turbo_stream.show 'new-account-password-confirmation'
|
|
@ -0,0 +1,22 @@
|
|||
- content_for(:title, 'Confirmez votre email')
|
||||
%p
|
||||
Bonjour
|
||||
= @user.email
|
||||
!
|
||||
|
||||
%p
|
||||
Veuillez confirmer votre email en cliquant sur le lien ci-dessous:
|
||||
= round_button 'Je confirme', france_connect_confirm_email_url(@token), :primary
|
||||
|
||||
|
||||
%p Ce lien est valide #{distance_of_time_in_words(FranceConnectInformation::CONFIRMATION_EMAIL_VALIDITY)}.
|
||||
|
||||
%p
|
||||
Tant que vous n'aurez pas confirmé votre email, vous ne recevrez aucune notification sur l'avancement de vos dossiers.
|
||||
|
||||
%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"
|
|
@ -1,5 +1,5 @@
|
|||
- content_for(:title, @subject)
|
||||
- merge_link = france_connect_particulier_mail_merge_with_existing_account_url(email_merge_token: @email_merge_token)
|
||||
- merge_link = france_connect_particulier_merge_using_email_link_url(email_merge_token: @email_merge_token)
|
||||
|
||||
%p
|
||||
Bonjour,
|
||||
|
|
|
@ -47,6 +47,9 @@ en:
|
|||
utils:
|
||||
'yes': 'Yes'
|
||||
'no': 'No'
|
||||
email_suggest:
|
||||
wanna_say: 'Do you mean to say ?'
|
||||
|
||||
deconnexion: "Log out"
|
||||
pj: "Attachments"
|
||||
asterisk_html: "Fields marked by an asterisk ( <svg aria-label='required' class='icon mandatory' height='10' role='img' viewBox='0 0 1200 1200' width='10' xml:space='preserve' xmlns='http://www.w3.org/2000/svg'><desc>required</desc><path d='M489.838 29.354v443.603L68.032 335.894 0 545.285l421.829 137.086-260.743 358.876 178.219 129.398L600.048 811.84l260.673 358.806 178.146-129.398-260.766-358.783L1200 545.379l-68.032-209.403-421.899 137.07V29.443H489.84l-.002-.089z'></path></svg> ) are mandatory."
|
||||
|
@ -150,6 +153,8 @@ en:
|
|||
subtitle_two: "Additional notes"
|
||||
content_html: "<p class=\"fr-mb-2w\">The documentation pages are managed by a third-party tool. They are not fully accessible.</p>
|
||||
<p class=\"fr-mb-2w\">FAQ management was delegated to a third-party tool. It was reintegrated into the platform in May 2024 and has not yet been audited.</p>"
|
||||
|
||||
|
||||
preparation:
|
||||
title: "Preparation of this accessibility declaration"
|
||||
intro: "This declaration was drawn up on 27 April 2022. It was updated on 14 June 2024."
|
||||
|
@ -746,6 +751,7 @@ en:
|
|||
# # etablissement_fail: 'Désolé, nous n’avons pas réussi à enregistrer l’établissement correspondant à ce numéro SIRET'
|
||||
france_connect:
|
||||
connexion: "Error trying to connect to France Connect."
|
||||
forbidden_html: "Only citizen can use FranceConnect. As an instructor or administrator, you should <a href='%{reset_link}'>reset your password</a>."
|
||||
evil_regexp: The regular expression you have entered is potentially dangerous and could lead to performance issues.
|
||||
mismatch_regexp: The provided example must match the regular expression
|
||||
syntax_error_regexp: The syntax of the regular expression is invalid
|
||||
|
@ -879,10 +885,19 @@ en:
|
|||
to_follow: to follow
|
||||
france_connect:
|
||||
particulier:
|
||||
password_confirmation:
|
||||
back: 'back to previous step'
|
||||
already_exists: An account with %{email} already existis on %{application_name}
|
||||
fill_in_password: fill in your password to merge your accounts
|
||||
choose_email:
|
||||
intro_html: "Your FranceConnect account uses <span class='fr-badge fr-badge--info fr-badge--sm'>%{email}</span> as the contact email."
|
||||
use_email_for_notifications: "Would you like to use it to receive notifications regarding the progress of your cases?"
|
||||
confirm: "Confirm"
|
||||
choose_email_contact: "Choose your contact email"
|
||||
alternative_email: "Please provide the email to use for contacting you."
|
||||
keep_fc_email_html: Yes, use <b class='bold'>%{email}</b> as contact email.
|
||||
use_another_email: No, use another address.
|
||||
confirmation_sent:
|
||||
confirmation_sent_by_email: "Confirm your email"
|
||||
intro_html: "A confirmation email has been sent to your address <span class='fr-badge fr-badge--info fr-badge--sm'>%{email}</span>"
|
||||
click_the_link_in_the_email: "Please click the link in the email to confirm your account and connect with France Connect in the future."
|
||||
continue: "Continue"
|
||||
merge:
|
||||
title: "Merge your account FranceConnect and %{application_name}"
|
||||
subtitle_html: "Hello,<br /><br />Your account FranceConnect uses <b class='bold'>%{email}</b> as contact email.<br />But there is an existing %{application_name} account using this email."
|
||||
|
@ -894,6 +909,11 @@ en:
|
|||
link_confirm_by_email: Confirm by receiving an email
|
||||
flash:
|
||||
confirmation_mail_sent: "An email with the confirmation link has been sent, please click on the link."
|
||||
confirmation_mail_resent: "Confirmation link expired. A new link has been sent by email."
|
||||
confirmation_mail_resent_error: "An unexpected error has occurred. Please contact support if the problem persists."
|
||||
redirect_new_user_session: "You have been disconnected from your previous account. Please click on the confirmation link again."
|
||||
email_confirmed: "Your email is confirmed"
|
||||
user_not_found: "User not found"
|
||||
invalid_password: "The password is not correct."
|
||||
connection_done: "The accounts for FranceConnect and %{application_name} are now merged."
|
||||
merger_token_expired: "Le delay to merge your FranceConnect and %{application_name} accounts is expired. Please retry."
|
||||
|
|
|
@ -37,6 +37,9 @@ fr:
|
|||
utils:
|
||||
'yes': Oui
|
||||
'no': Non
|
||||
email_suggest:
|
||||
wanna_say: 'Voulez-vous dire ?'
|
||||
|
||||
i_dont_know: Je ne sais pas
|
||||
deconnexion: "Déconnexion"
|
||||
pj: "Pièces jointes"
|
||||
|
@ -935,10 +938,21 @@ fr:
|
|||
ministeres: Ministères
|
||||
france_connect:
|
||||
particulier:
|
||||
password_confirmation:
|
||||
back: 'revenir en arrière'
|
||||
already_exists: Le compte %{email} existe déjà sur %{application_name}
|
||||
fill_in_password: entrez votre mot de passe pour fusionner les comptes
|
||||
choose_email:
|
||||
intro_html: "Votre compte FranceConnect utilise <span class='fr-badge fr-badge--info fr-badge--sm'>%{email}</span> comme email de contact."
|
||||
use_email_for_notifications: Souhaitez-vous l'utiliser pour recevoir les notifications concernant l'avancement de vos dossiers ?
|
||||
confirm: Confirmer
|
||||
choose_email_contact: Choisissez votre email de contact pour finaliser votre connexion
|
||||
alternative_email: Veuillez nous fournir l'email à utiliser pour vous contacter.
|
||||
keep_fc_email_html: "Oui, utiliser %{email} comme email de contact."
|
||||
use_another_email: Non, utiliser une autre adresse.
|
||||
email_suggest:
|
||||
wanna_say: 'Voulez-vous dire ?'
|
||||
confirmation_sent:
|
||||
confirmation_sent_by_email: Confirmez votre email
|
||||
intro_html: "Un mail de confirmation a été envoyé à votre adresse <span class='fr-badge fr-badge--info fr-badge--sm'>%{email}</span>"
|
||||
click_the_link_in_the_email: Vous devez impérativement cliquer sur le lien du mail pour activer votre adresse et recevoir les notifications sur l'avancement de vos dossiers.
|
||||
continue: Continuer
|
||||
merge:
|
||||
title: "Fusion des comptes FranceConnect et %{application_name}"
|
||||
subtitle_html: "Bonjour,<br /><br />Votre compte FranceConnect utilise <b class='bold'>%{email}</b> comme email de contact.<br />Or il existe un compte sur %{application_name} avec cet email."
|
||||
|
@ -950,7 +964,12 @@ fr:
|
|||
link_confirm_by_email: Confirmer mon compte par email
|
||||
flash:
|
||||
confirmation_mail_sent: "Nous venons de vous envoyer le mail de confirmation, veuillez cliquer sur le lien contenu dans ce mail pour fusionner vos comptes"
|
||||
invalid_password: "Mauvais mot de passe"
|
||||
confirmation_mail_resent: "Le lien de confirmation a expiré. Un nouveau lien de confirmation vous a été envoyé par email."
|
||||
confirmation_mail_resent_error: "Une erreur inattendue est survenue. Veuillez contacter le support si le problème persiste."
|
||||
redirect_new_user_session: "Vous avez été déconnecté de votre précédent compte. Veuillez cliquer à nouveau sur le lien de confirmation."
|
||||
email_confirmed: 'Votre email est bien vérifié'
|
||||
user_not_found: 'Utilisateur non trouvé'
|
||||
invalid_password: "Mot de passe incorrect"
|
||||
connection_done: "Les comptes FranceConnect et %{application_name} sont à présent fusionnés"
|
||||
merger_token_expired: "Le délai pour fusionner les comptes FranceConnect et %{application_name} est expiré. Veuillez recommencer la procédure pour vous fusionner les comptes."
|
||||
groupe_gestionnaires:
|
||||
|
|
|
@ -188,11 +188,14 @@ Rails.application.routes.draw do
|
|||
namespace :france_connect 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/: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'
|
||||
|
||||
post 'particulier/send_email_merge_request'
|
||||
|
||||
post 'particulier/merge_using_fc_email'
|
||||
post 'particulier/merge_using_password'
|
||||
get 'particulier/merge_using_email_link/:email_merge_token' => 'particulier#merge_using_email_link', as: :particulier_merge_using_email_link
|
||||
|
||||
get 'confirm_email/:token', to: 'particulier#confirm_email', as: :confirm_email
|
||||
end
|
||||
|
||||
namespace :agent_connect do
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAlternativeEmailColumnToFranceConnectInformationTable < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :france_connect_informations, :requested_email, :string
|
||||
end
|
||||
end
|
|
@ -712,6 +712,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_08_29_141049) do
|
|||
t.string "given_name"
|
||||
t.string "merge_token"
|
||||
t.datetime "merge_token_created_at", precision: nil
|
||||
t.string "requested_email"
|
||||
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"
|
||||
|
|
|
@ -89,39 +89,25 @@ describe FranceConnect::ParticulierController, type: :controller do
|
|||
let(:fc_user) { nil }
|
||||
|
||||
context 'and no user with the same email exists' do
|
||||
it 'creates an user with the same email and log in' do
|
||||
expect { subject }.to change { User.count }.by(1)
|
||||
|
||||
user = User.last
|
||||
|
||||
expect(user.email).to eq(email.downcase)
|
||||
expect(controller.current_user).to eq(user)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
|
||||
context 'when invites are pending' do
|
||||
let!(:invite) { create(:invite, email: email, user: nil) }
|
||||
it 'links pending invites' do
|
||||
expect(invite.reload.user).to eq(nil)
|
||||
subject
|
||||
expect(invite.reload.user).to eq(User.last)
|
||||
end
|
||||
it 'render the choose email template to select good email' do
|
||||
expect { subject }.to change { User.count }.by(0)
|
||||
expect(subject).to render_template(:choose_email)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and an user with the same email exists' do
|
||||
let!(:preexisting_user) { create(:user, email: email) }
|
||||
|
||||
it 'redirects to the merge process' do
|
||||
it 'renders the merge page' do
|
||||
expect { subject }.not_to change { User.count }
|
||||
|
||||
expect(response).to redirect_to(france_connect_particulier_merge_path(fci.reload.merge_token))
|
||||
expect(response).to render_template(:merge)
|
||||
end
|
||||
end
|
||||
context 'and an instructeur with the same email exists' do
|
||||
let!(:preexisting_user) { create(:instructeur, email: email) }
|
||||
|
||||
it 'redirects to the merge process' do
|
||||
it 'redirects to the login path' do
|
||||
expect { subject }.not_to change { User.count }
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
|
@ -134,15 +120,7 @@ describe FranceConnect::ParticulierController, type: :controller do
|
|||
context 'when france_connect_particulier_id does not exist in database' do
|
||||
it { expect { subject }.to change { FranceConnectInformation.count }.by(1) }
|
||||
|
||||
describe 'FranceConnectInformation attributs' do
|
||||
let(:stored_fci) { FranceConnectInformation.last }
|
||||
|
||||
before { subject }
|
||||
|
||||
it { expect(stored_fci).to have_attributes(user_info.merge(birthdate: Time.zone.parse(birthdate).to_datetime)) }
|
||||
end
|
||||
|
||||
it { is_expected.to redirect_to(root_path) }
|
||||
it { is_expected.to render_template(:choose_email) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -158,6 +136,167 @@ describe FranceConnect::ParticulierController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#merge_using_fc_email' do
|
||||
subject { post :merge_using_fc_email, params: { merge_token: merge_token } }
|
||||
|
||||
let!(:fci) { FranceConnectInformation.create!(user_info) }
|
||||
let(:merge_token) { fci.create_merge_token! }
|
||||
|
||||
before do
|
||||
allow(UserMailer).to receive_message_chain(:custom_confirmation_instructions, :deliver_later)
|
||||
end
|
||||
|
||||
context 'when the merge token is valid' do
|
||||
it do
|
||||
expect(User.last.email).not_to eq(email.downcase)
|
||||
|
||||
subject
|
||||
|
||||
user = User.last
|
||||
|
||||
expect(user.email).to eq(email.downcase)
|
||||
expect(UserMailer).to have_received(:custom_confirmation_instructions).with(user, user.confirmation_token)
|
||||
expect(user.email_verified_at).to be_nil
|
||||
expect(fci.reload.merge_token).to be_nil
|
||||
expect(response).to render_template(:confirmation_sent)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the merge token is invalid' do
|
||||
let(:merge_token) { 'invalid_token' }
|
||||
|
||||
it 'redirects to root_path with an alert' do
|
||||
subject
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq("Le délai pour fusionner les comptes FranceConnect et demarches-simplifiees.fr est expiré. Veuillez recommencer la procédure pour vous fusionner les comptes.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when @fci is not valid for merge' do
|
||||
before do
|
||||
merge_token
|
||||
fci.update!(merge_token_created_at: 2.years.ago)
|
||||
end
|
||||
|
||||
it 'redirects to root_path with an alert' do
|
||||
subject
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq('Le délai pour fusionner les comptes FranceConnect et demarches-simplifiees.fr est expiré. Veuillez recommencer la procédure pour vous fusionner les comptes.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#confirm_email' do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:fci) { create(:france_connect_information, user: user) }
|
||||
|
||||
before { fci.send_custom_confirmation_instructions }
|
||||
|
||||
context 'when the confirmation token is valid' do
|
||||
before do
|
||||
get :confirm_email, params: { token: user.confirmation_token }
|
||||
user.reload
|
||||
end
|
||||
|
||||
it do
|
||||
expect(user.email_verified_at).to be_present
|
||||
expect(user.confirmation_token).to be_nil
|
||||
|
||||
expect(response).to redirect_to(root_path(user))
|
||||
expect(flash[:notice]).to eq('Votre email est bien vérifié')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invites are pending' do
|
||||
let!(:invite) { create(:invite, email: user.email, user: nil) }
|
||||
|
||||
it 'links pending invites' do
|
||||
get :confirm_email, params: { token: user.confirmation_token }
|
||||
invite.reload
|
||||
expect(invite.user).to eq(user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the confirmation token is expired' do
|
||||
let!(:expired_user_confirmation) do
|
||||
create(:user, confirmation_token: 'expired_token', confirmation_sent_at: 3.days.ago)
|
||||
end
|
||||
|
||||
it 'redirects to root path with an alert when FranceConnectInformation is not found' do
|
||||
get :confirm_email, params: { token: 'expired_token' }
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq(I18n.t('france_connect.particulier.flash.confirmation_mail_resent_error'))
|
||||
end
|
||||
|
||||
context 'when FranceConnectInformation exists' do
|
||||
let!(:france_connect_information) do
|
||||
create(:france_connect_information, user: expired_user_confirmation)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(UserMailer).to receive_message_chain(:custom_confirmation_instructions, :deliver_later)
|
||||
end
|
||||
|
||||
it 'resends the confirmation email and redirects to root path with a notice' do
|
||||
get :confirm_email, params: { token: 'expired_token' }
|
||||
|
||||
expect(UserMailer).to have_received(:custom_confirmation_instructions)
|
||||
.with(expired_user_confirmation, expired_user_confirmation.reload.confirmation_token)
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:notice]).to eq(I18n.t('france_connect.particulier.flash.confirmation_mail_resent'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a different user is signed in' do
|
||||
let!(:expired_user_confirmation) do
|
||||
create(:user, confirmation_token: 'expired_token', confirmation_sent_at: 3.days.ago)
|
||||
end
|
||||
|
||||
let(:another_user) { create(:user) }
|
||||
|
||||
before { sign_in(another_user) }
|
||||
|
||||
it 'signs out the current user and redirects to sign in path' do
|
||||
expect_any_instance_of(FranceConnectInformation).not_to receive(:send_custom_confirmation_instructions)
|
||||
expect(controller).to receive(:sign_out).with(:user)
|
||||
|
||||
get :confirm_email, params: { token: 'expired_token' }
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
expect(flash[:alert]).to eq(I18n.t('france_connect.particulier.flash.redirect_new_user_session'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set_user_by_confirmation_token' do
|
||||
let(:current_user) { create(:user) }
|
||||
let!(:confirmation_user) { create(:user, confirmation_token: 'valid_token') }
|
||||
|
||||
before { sign_in current_user }
|
||||
|
||||
it 'signs out current user and redirects to new session path when users do not match' do
|
||||
expect(controller).to receive(:sign_out).with(:user)
|
||||
|
||||
get :confirm_email, params: { token: 'valid_token' }
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
expect(flash[:alert]).to eq(I18n.t('france_connect.particulier.flash.redirect_new_user_session'))
|
||||
end
|
||||
|
||||
context 'when user is not found' do
|
||||
it 'redirects to root path with user not found alert' do
|
||||
get :confirm_email, params: { token: 'invalid_token' }
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq(I18n.t('france_connect.particulier.flash.user_not_found'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples "a method that needs a valid merge token" do
|
||||
context 'when the merge token is invalid' do
|
||||
before do
|
||||
|
@ -178,41 +317,14 @@ describe FranceConnect::ParticulierController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#merge' do
|
||||
let(:fci) { FranceConnectInformation.create!(user_info) }
|
||||
let(:merge_token) { fci.create_merge_token! }
|
||||
let(:format) { :html }
|
||||
|
||||
subject { get :merge, params: { merge_token: merge_token } }
|
||||
|
||||
context 'when the merge token is valid' do
|
||||
it { expect(subject).to have_http_status(:ok) }
|
||||
end
|
||||
|
||||
it_behaves_like "a method that needs a valid merge token"
|
||||
|
||||
context 'when the merge token does not exist' do
|
||||
let(:merge_token) { 'i do not exist' }
|
||||
|
||||
before do
|
||||
allow(Current).to receive(:application_name).and_return('demarches-simplifiees.fr')
|
||||
end
|
||||
|
||||
it do
|
||||
expect(subject).to redirect_to root_path
|
||||
expect(flash.alert).to eq("Le délai pour fusionner les comptes FranceConnect et demarches-simplifiees.fr est expiré. Veuillez recommencer la procédure pour vous fusionner les comptes.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#merge_with_existing_account' do
|
||||
describe '#merge_using_password' do
|
||||
let(:fci) { FranceConnectInformation.create!(user_info) }
|
||||
let(:merge_token) { fci.create_merge_token! }
|
||||
let(:email) { 'EXISTING_account@a.com ' }
|
||||
let(:password) { SECURE_PASSWORD }
|
||||
let(:format) { :turbo_stream }
|
||||
|
||||
subject { post :merge_with_existing_account, params: { merge_token: merge_token, email: email, password: password }, format: format }
|
||||
subject { post :merge_using_password, params: { merge_token: merge_token, password: password }, format: format }
|
||||
|
||||
it_behaves_like "a method that needs a valid merge token"
|
||||
|
||||
|
@ -244,9 +356,9 @@ describe FranceConnect::ParticulierController, type: :controller do
|
|||
|
||||
it 'redirects to the root page' do
|
||||
subject
|
||||
fci.reload
|
||||
|
||||
expect(fci.user).to be_nil
|
||||
expect { fci.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
|
||||
expect(fci.merge_token).not_to be_nil
|
||||
expect(controller.current_user).to be_nil
|
||||
end
|
||||
|
@ -268,18 +380,21 @@ describe FranceConnect::ParticulierController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#mail_merge_with_existing_account' do
|
||||
describe '#merge_using_email_link' do
|
||||
let(:fci) { FranceConnectInformation.create!(user_info) }
|
||||
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: { email_merge_token: } }
|
||||
subject do
|
||||
post :merge_using_email_link, params: { email_merge_token: }
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Current).to receive(:application_name).and_return('demarches-simplifiees.fr')
|
||||
fci.update!(requested_email: email.downcase)
|
||||
end
|
||||
|
||||
let!(:user) { create(:user, email: email, password: 'abcdefgh') }
|
||||
let!(:user) { create(:user, email:, password: 'abcdefgh') }
|
||||
|
||||
it 'merges the account, signs in, and delete the merge token' do
|
||||
subject
|
||||
|
@ -304,82 +419,26 @@ describe FranceConnect::ParticulierController, type: :controller do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
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) }
|
||||
|
||||
it 'increases the failed attempts counter' do
|
||||
subject
|
||||
fci.reload
|
||||
|
||||
expect(fci.user).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
|
||||
end
|
||||
end
|
||||
|
||||
describe '#merge_with_new_account' do
|
||||
describe '#send_email_merge_request' do
|
||||
let(:fci) { FranceConnectInformation.create!(user_info) }
|
||||
let(:merge_token) { fci.create_merge_token! }
|
||||
let(:email) { ' Account@a.com ' }
|
||||
let(:format) { :turbo_stream }
|
||||
let(:email) { 'requested_email@.a.com' }
|
||||
|
||||
subject { post :merge_with_new_account, params: { merge_token: merge_token, email: email }, format: format }
|
||||
subject { post :send_email_merge_request, params: { merge_token: merge_token, email: } }
|
||||
|
||||
it_behaves_like "a method that needs a valid merge token"
|
||||
|
||||
context 'when the email does not belong to any user' do
|
||||
it 'creates the account, signs in, and delete the merge token' do
|
||||
subject
|
||||
fci.reload
|
||||
|
||||
expect(fci.user.email).to eq(email.downcase.strip)
|
||||
expect(fci.merge_token).to be_nil
|
||||
expect(controller.current_user).to eq(fci.user)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
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.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
|
||||
|
||||
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 }
|
||||
allow(UserMailer).to receive_message_chain(:france_connect_merge_confirmation, :deliver_later)
|
||||
subject
|
||||
|
||||
fci.reload
|
||||
expect(fci.requested_email).to eq(email)
|
||||
expect(fci.email_merge_token).to be_present
|
||||
expect(response).to redirect_to(france_connect_particulier_merge_path(fci.reload.merge_token))
|
||||
|
||||
expect(UserMailer).to have_received(:france_connect_merge_confirmation).with(email, fci.email_merge_token, fci.email_merge_token_created_at)
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -81,7 +81,7 @@ RSpec.describe UserMailer, type: :mailer do
|
|||
|
||||
it 'sends to correct email with merge link' do
|
||||
expect(subject.to).to eq([email])
|
||||
expect(subject.body).to include(france_connect_particulier_mail_merge_with_existing_account_url(email_merge_token: code))
|
||||
expect(subject.body).to include(france_connect_particulier_merge_using_email_link_url(email_merge_token: code))
|
||||
end
|
||||
|
||||
context 'without SafeMailer configured' do
|
||||
|
@ -101,6 +101,31 @@ RSpec.describe UserMailer, type: :mailer do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#custom_confirmation_instructions' do
|
||||
let(:user) { create(:user, email: 'user@example.com') }
|
||||
let(:token) { 'confirmation_token_123' }
|
||||
let(:mail) { UserMailer.custom_confirmation_instructions(user, token) }
|
||||
|
||||
it 'renders the headers' do
|
||||
expect(mail.subject).to eq('Confirmez votre email')
|
||||
expect(mail.to).to eq([user.email])
|
||||
expect(mail.from).to eq(['contact@demarches-simplifiees.fr'])
|
||||
end
|
||||
|
||||
it 'renders the body' do
|
||||
expect(mail.body.encoded).to match(user.email)
|
||||
expect(mail.body.encoded).to match(token)
|
||||
end
|
||||
|
||||
it 'assigns @user' do
|
||||
expect(mail.body.encoded).to match(user.email)
|
||||
end
|
||||
|
||||
it 'assigns @token' do
|
||||
expect(mail.body.encoded).to include(token)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.send_archive' do
|
||||
let(:procedure) { create(:procedure) }
|
||||
let(:archive) { create(:archive) }
|
||||
|
|
|
@ -9,19 +9,67 @@ describe FranceConnectInformation, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'associate_user!' do
|
||||
context 'when there is no user with same email' do
|
||||
let(:email) { 'A@email.com' }
|
||||
let(:fci) { build(:france_connect_information) }
|
||||
describe 'safely_associate_user!' do
|
||||
let(:email) { 'A@email.com' }
|
||||
let(:fci) { build(:france_connect_information) }
|
||||
|
||||
subject { fci.associate_user!(email) }
|
||||
subject { fci.safely_associate_user!(email) }
|
||||
|
||||
it { expect { subject }.to change(User, :count).by(1) }
|
||||
context 'when there is no user with the same email' do
|
||||
it 'creates a new user' do
|
||||
expect { subject }.to change(User, :count).by(1)
|
||||
end
|
||||
|
||||
it do
|
||||
it 'sets the correct attributes on the user' do
|
||||
subject
|
||||
expect(fci.user.email).to eq('a@email.com')
|
||||
expect(fci.user.email_verified_at).to be_present
|
||||
user = User.find_by(email: email.downcase)
|
||||
expect(user).not_to be_nil
|
||||
expect(user.confirmed_at).to be_present
|
||||
end
|
||||
|
||||
it 'associates the user with the FranceConnectInformation' do
|
||||
subject
|
||||
expect(fci.reload.user.email).to eq(email.downcase)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user with the same email already exists due to race condition' do
|
||||
let!(:existing_user) { create(:user, email: email.downcase) }
|
||||
let!(:fci) { create(:france_connect_information) } # Assurez-vous que fci est créé et sauvegardé
|
||||
|
||||
before do
|
||||
call_count = 0
|
||||
allow(User).to receive(:create!).and_wrap_original do
|
||||
call_count += 1
|
||||
if call_count == 1
|
||||
raise ActiveRecord::RecordNotUnique
|
||||
else
|
||||
existing_user
|
||||
end
|
||||
end
|
||||
allow(fci).to receive(:send_custom_confirmation_instructions)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { fci.safely_associate_user!(email) }.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it 'does not create a new user' do
|
||||
expect {
|
||||
begin
|
||||
fci.safely_associate_user!(email)
|
||||
rescue NoMethodError
|
||||
end
|
||||
}.to_not change(User, :count)
|
||||
end
|
||||
|
||||
it 'does not associate with any user' do
|
||||
expect(fci.user).to be_nil
|
||||
begin
|
||||
fci.safely_associate_user!(email)
|
||||
rescue NoMethodError
|
||||
end
|
||||
expect(fci.reload.user).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,12 +18,6 @@ describe User, type: :model do
|
|||
user.confirm
|
||||
expect(user.reload.invites.size).to eq(2)
|
||||
end
|
||||
|
||||
it 'verifies its email' do
|
||||
expect(user.email_verified_at).to be_nil
|
||||
user.confirm
|
||||
expect(user.email_verified_at).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
describe '#owns?' do
|
||||
|
|
|
@ -43,9 +43,25 @@ describe 'France Connect Particulier Connexion' do
|
|||
before { page.find('.fr-connect').click }
|
||||
|
||||
scenario 'he is redirected to user dossiers page' do
|
||||
expect(page).to have_content('Dossiers')
|
||||
expect(page).to have_content("Choisissez votre email de contact pour finaliser votre connexion")
|
||||
find("#use_france_connect_email_no").click
|
||||
fill_in("email", with: "exemple@email.com")
|
||||
page.find("input[type='submit'][name='commit'][value='Confirmer']").click
|
||||
expect(page).to have_content("Confirmez votre email")
|
||||
click_on 'Continuer'
|
||||
expect(User.find_by(email: email)).not_to be nil
|
||||
end
|
||||
|
||||
scenario 'he can choose not to use FranceConnect email and input an alternative email' do
|
||||
expect(page).to have_content("Choisissez votre email de contact pour finaliser votre connexion")
|
||||
|
||||
expect(page).to have_selector("input[name='email']", visible: true, wait: 10)
|
||||
|
||||
fill_in 'email', with: 'alternative@example.com'
|
||||
click_on 'Confirmer'
|
||||
|
||||
expect(page).to have_content("Confirmez votre email")
|
||||
end
|
||||
end
|
||||
|
||||
context 'and an user exists with the same email' do
|
||||
|
@ -72,25 +88,23 @@ describe 'France Connect Particulier Connexion' do
|
|||
fill_in 'email', with: 'new_email@a.com'
|
||||
click_on 'Utiliser ce mail'
|
||||
|
||||
expect(page).to have_content('Dossiers')
|
||||
expect(page).to have_content('Nous venons de vous envoyer le mail de confirmation')
|
||||
end
|
||||
|
||||
context 'and the user wants an email that belongs to another account', js: true do
|
||||
let!(:another_user) { create(:user, email: 'an_existing_email@a.com', password: SECURE_PASSWORD) }
|
||||
|
||||
scenario 'it uses another email that belongs to another account' do
|
||||
page.find('#it-is-not-mine').click
|
||||
fill_in 'email', with: 'an_existing_email@a.com'
|
||||
click_on 'Utiliser ce mail'
|
||||
find('label[for="it-is-not-mine"]').click
|
||||
|
||||
expect(page).to have_css('#password-for-another-account', visible: true)
|
||||
expect(page).to have_css('.new-account', visible: true)
|
||||
|
||||
within '#new-account-password-confirmation' do
|
||||
fill_in 'password', with: SECURE_PASSWORD
|
||||
click_on 'Fusionner les comptes'
|
||||
within '.new-account' do
|
||||
fill_in 'email', with: 'an_existing_email@a.com'
|
||||
click_on 'Utiliser ce mail'
|
||||
end
|
||||
|
||||
expect(page).to have_content('Dossiers')
|
||||
expect(page).to have_content('Nous venons de vous envoyer le mail de confirmation')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -191,7 +191,15 @@ describe 'Prefilling a dossier (with a GET request):', js: true do
|
|||
|
||||
page.find('.fr-connect').click
|
||||
|
||||
click_on "Poursuivre mon dossier prérempli"
|
||||
expect(page).to have_content("Choisissez votre email de contact pour finaliser votre connexion")
|
||||
expect(page).to have_selector("#use_france_connect_email_no", visible: false, wait: 10)
|
||||
page.execute_script('document.getElementById("use_france_connect_email_no").click()')
|
||||
fill_in("email", with: "exemple@email.com")
|
||||
page.find("input[type='submit'][name='commit'][value='Confirmer']").click
|
||||
expect(page).to have_content("Confirmez votre email")
|
||||
click_on 'Continuer'
|
||||
expect(page).to have_content('Vous avez un dossier prérempli')
|
||||
find('.fr-btn.fr-mb-2w', text: 'Poursuivre mon dossier prérempli', wait: 10).click
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -142,9 +142,15 @@ describe 'Prefilling a dossier (with a POST request):', js: true do
|
|||
allow(FranceConnectService).to receive(:retrieve_user_informations_particulier).and_return(build(:france_connect_information))
|
||||
|
||||
page.find('.fr-connect').click
|
||||
expect(page).to have_content("Choisissez votre email de contact pour finaliser votre connexion")
|
||||
expect(page).to have_selector("#use_france_connect_email_yes", visible: false, wait: 10)
|
||||
page.execute_script('document.getElementById("use_france_connect_email_yes").click()')
|
||||
|
||||
click_on 'Confirmer'
|
||||
expect(page).to have_content("Confirmez votre email")
|
||||
click_on 'Continuer'
|
||||
expect(page).to have_content('Vous avez un dossier prérempli')
|
||||
click_on 'Poursuivre mon dossier prérempli'
|
||||
find('.fr-btn.fr-mb-2w', text: 'Poursuivre mon dossier prérempli', wait: 10).click
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue