From 19f81b594b8473881090428254c3b4fbcc3cd837 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 13 Oct 2021 09:23:40 +0200 Subject: [PATCH] merge with an existing account by using the password --- app/controllers/application_controller.rb | 4 + .../france_connect/particulier_controller.rb | 44 +++++++++- app/javascript/new_design/fc-fusion.js | 5 ++ app/javascript/packs/application.js | 4 + app/models/france_connect_information.rb | 4 + .../particulier/merge.html.haml | 21 +++++ config/routes.rb | 1 + .../particulier_controller_spec.rb | 84 ++++++++++++++++--- 8 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 app/javascript/new_design/fc-fusion.js diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9553e307f..072c3f483 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -85,6 +85,10 @@ class ApplicationController < ActionController::Base end end + def ajax_redirect(path) + "window.location.href='#{path}'" + end + protected def feature_enabled?(feature_name) diff --git a/app/controllers/france_connect/particulier_controller.rb b/app/controllers/france_connect/particulier_controller.rb index ac99880bb..e856a84fa 100644 --- a/app/controllers/france_connect/particulier_controller.rb +++ b/app/controllers/france_connect/particulier_controller.rb @@ -1,6 +1,6 @@ class FranceConnect::ParticulierController < ApplicationController before_action :redirect_to_login_if_fc_aborted, only: [:callback] - before_action :securely_retrieve_fci, only: [:merge] + before_action :securely_retrieve_fci, only: [:merge, :merge_with_existing_account] def login if FranceConnectService.enabled? @@ -41,6 +41,28 @@ class FranceConnect::ParticulierController < ApplicationController def merge end + def merge_with_existing_account + user = User.find_by(email: sanitized_email_params) + + if user.valid_for_authentication? { user.valid_password?(password_params) } + if !user.can_france_connect? + flash.alert = "#{user.email} ne peut utiliser FranceConnect" + + render js: ajax_redirect(root_path) + else + @fci.update(user: user) + @fci.delete_merge_token! + + flash.notice = "Les comptes FranceConnect et #{APPLICATION_NAME} sont à présent fusionnés" + connect_france_connect_particulier(user) + end + else + flash.alert = 'Mauvais mot de passe' + + render js: helpers.render_flash + end + end + private def securely_retrieve_fci @@ -49,7 +71,10 @@ class FranceConnect::ParticulierController < ApplicationController if @fci.nil? || !@fci.valid_for_merge? flash.alert = 'Votre compte FranceConnect a expiré, veuillez recommencer.' - redirect_to root_path + respond_to do |format| + format.html { redirect_to root_path } + format.js { render js: ajax_redirect(root_path) } + end end end @@ -68,7 +93,12 @@ class FranceConnect::ParticulierController < ApplicationController 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) + redirection_location = stored_location_for(current_user) || root_path(current_user) + + respond_to do |format| + format.html { redirect_to redirection_location } + format.js { render js: ajax_redirect(root_path) } + end end def redirect_france_connect_error_connection @@ -79,4 +109,12 @@ class FranceConnect::ParticulierController < ApplicationController def merge_token_params params[:merge_token] end + + def password_params + params[:password] + end + + def sanitized_email_params + params[:email]&.gsub(/[[:space:]]/, ' ')&.strip&.downcase + end end diff --git a/app/javascript/new_design/fc-fusion.js b/app/javascript/new_design/fc-fusion.js new file mode 100644 index 000000000..bb1c904c3 --- /dev/null +++ b/app/javascript/new_design/fc-fusion.js @@ -0,0 +1,5 @@ +import { show, hide } from '@utils'; + +export function showFusion() { + show(document.querySelector('.fusion')); +} diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 472d6ebc9..7a529b41e 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -41,6 +41,9 @@ import { acceptEmailSuggestion, discardEmailSuggestionBox } from '../new_design/user-sign_up'; +import { + showFusion +} from '../new_design/fc-fusion'; // This is the global application namespace where we expose helpers used from rails views const DS = { @@ -49,6 +52,7 @@ const DS = { showMotivation, motivationCancel, showImportJustificatif, + showFusion, replaceSemicolonByComma, acceptEmailSuggestion, discardEmailSuggestionBox diff --git a/app/models/france_connect_information.rb b/app/models/france_connect_information.rb index 49f6fd7d9..06180a43a 100644 --- a/app/models/france_connect_information.rb +++ b/app/models/france_connect_information.rb @@ -52,4 +52,8 @@ class FranceConnectInformation < ApplicationRecord def valid_for_merge? (MERGE_VALIDITY.ago < merge_token_created_at) && user_id.nil? end + + def delete_merge_token! + update(merge_token: nil, merge_token_created_at: nil) + end end diff --git a/app/views/france_connect/particulier/merge.html.haml b/app/views/france_connect/particulier/merge.html.haml index 6ec910cdb..1ee48aa00 100644 --- a/app/views/france_connect/particulier/merge.html.haml +++ b/app/views/france_connect/particulier/merge.html.haml @@ -10,3 +10,24 @@ Votre compte FranceConnect utilise #{@fci.email_france_connect} comme email de contact. %br Or il existe un compte sur #{APPLICATION_NAME} avec cet email. + + .form.mt-2 + %label Ce compte #{@fci.email_france_connect} vous appartient-il ? + %fieldset.radios + %label{ onclick: "DS.showFusion(event);" } + = radio_button_tag :value, true, false, autocomplete: "off" + Oui + + %label + = radio_button_tag :value, false, false, autocomplete: "off" + Non + + .fusion.hidden + %p Pour les fusionner, entrez votre mot de passe + + = form_tag france_connect_particulier_merge_with_existing_account_path, remote: true, class: 'mt-2 form' do + = hidden_field_tag :merge_token, @fci.merge_token + = hidden_field_tag :email, @fci.email_france_connect + = label_tag :password, 'Mot de passe (8 caractères minimum)' + = password_field_tag :password, nil, autocomplete: 'current-password' + = submit_tag 'Fusionner les comptes', class: 'button primary' diff --git a/config/routes.rb b/config/routes.rb index 3560cc97a..8d27d54db 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -125,6 +125,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 + post 'particulier/merge_with_existing_account' => 'particulier#merge_with_existing_account' end namespace :champs do diff --git a/spec/controllers/france_connect/particulier_controller_spec.rb b/spec/controllers/france_connect/particulier_controller_spec.rb index a67eadbac..172bb9f4e 100644 --- a/spec/controllers/france_connect/particulier_controller_spec.rb +++ b/spec/controllers/france_connect/particulier_controller_spec.rb @@ -136,16 +136,7 @@ describe FranceConnect::ParticulierController, type: :controller do end end - describe '#merge' do - let(:fci) { FranceConnectInformation.create!(user_info) } - let(:merge_token) { fci.create_merge_token! } - - 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 - + RSpec.shared_examples "a method that needs a valid merge token" do context 'when the merge token is invalid' do before do merge_token @@ -153,10 +144,29 @@ describe FranceConnect::ParticulierController, type: :controller do end it do - expect(subject).to redirect_to root_path + if format == :js + subject + expect(response.body).to eq("window.location.href='/'") + else + expect(subject).to redirect_to root_path + end expect(flash.alert).to eq('Votre compte FranceConnect a expiré, veuillez recommencer.') end 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' } @@ -167,4 +177,56 @@ describe FranceConnect::ParticulierController, type: :controller do end end end + + describe '#merge_with_existing_account' do + let(:fci) { FranceConnectInformation.create!(user_info) } + let(:merge_token) { fci.create_merge_token! } + let(:email) { 'EXISTING_account@a.com ' } + let(:password) { 'my-s3cure-p4ssword' } + let(:format) { :js } + + subject { post :merge_with_existing_account, params: { merge_token: merge_token, email: email, password: password }, format: format } + + it_behaves_like "a method that needs a valid merge token" + + context 'when the credentials are ok' do + let!(:user) { create(:user, email: email, password: password) } + + it 'merges the account, signs in, and delete the merge token' do + subject + fci.reload + + expect(fci.user).to eq(user) + expect(fci.merge_token).to be_nil + expect(controller.current_user).to eq(user) + end + + context 'but the targeted user is an instructeur' do + let!(:user) { create(:instructeur, email: email, password: password).user } + + it 'redirects to the root page' 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 + end + end + end + + context 'when the credentials are not ok' do + let!(:user) { create(:user, email: email, password: 'another password #$21$%%') } + + it 'increases the failed attempts counter' do + subject + fci.reload + + expect(fci.user).to be_nil + expect(fci.merge_token).not_to be_nil + expect(controller.current_user).to be_nil + expect(user.reload.failed_attempts).to eq(1) + end + end + end end