Merge pull request #10040 from colinux/cross-domain-component

Prépare la bannière informant du changement de nom de domaine, avec redirection automatique le cas échéant
This commit is contained in:
Colin Darie 2024-03-21 14:58:29 +00:00 committed by GitHub
commit 17f9992722
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 218 additions and 16 deletions

1
.env.test Normal file
View file

@ -0,0 +1 @@
APP_HOST="test.host" # must match host defined in spec/rails_helper.rb

View file

@ -10,7 +10,6 @@ ol.fr-ol-content--override {
}
}
// with Marianne font, weight of font is less bolder, so bold it up
.button.primary {
font-weight: bold;
@ -20,10 +19,17 @@ trix-editor.fr-input {
max-height: none;
}
.fr-label + .fr-ds-combobox { // same as .fr-label + .fr-input
margin-top: 0.5rem;
.fr-header {
.fr-notice {
// get back link underlined in notices, because they are usually hidden in headers
--underline-img: linear-gradient(0deg, currentColor, currentColor);
}
}
.fr-label + .fr-ds-combobox {
// same as .fr-label + .fr-input
margin-top: 0.5rem;
}
.fr-ds-combobox {
.fr-menu {

View file

@ -2,11 +2,18 @@
class Dsfr::NoticeComponent < ApplicationComponent
renders_one :title
def initialize(closable: false)
attr_reader :data_attributes
def initialize(closable: false, data_attributes: {})
@closable = closable
@data_attributes = data_attributes
end
def closable?
!!@closable
end
def notice_data_attributes
{ "data-dsfr-header-target": "notice" }.merge(data_attributes)
end
end

View file

@ -1,4 +1,4 @@
.fr-notice.fr-notice--info{ "data-dsfr-header-target": "notice" }
.fr-notice.fr-notice--info{ **notice_data_attributes }
.fr-container
.fr-notice__body
%p.fr-notice__title

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
class SwitchDomainBannerComponent < ApplicationComponent
attr_reader :user
def initialize(user:)
@user = user
end
def render?
return false unless helpers.switch_domain_enabled?(request)
return false if user&.preferred_domain_demarches_gouv_fr? && requested_from_new_domain?
true
end
def auto_switch?
helpers.auto_switch_domain?(request, user.present?)
end
def manual_switch?
helpers.app_host_legacy?(request) && user.present?
end
def new_host_url
helpers.url_for(url_options)
end
def requested_from_new_domain?
Current.host == ApplicationHelper::APP_HOST
end
private
def url_options
request.params.except(:switch_domain).merge(host: ApplicationHelper::APP_HOST)
end
end

View file

@ -0,0 +1,5 @@
---
en:
message_new_domain: "demarches-simplifiees.fr is now called"
follow_link: Follow this link to connect to the new address.
detected_error: We have detected a network error in your access. Contact your IT department to authorize connection to demarches.gouv.fr.

View file

@ -0,0 +1,5 @@
---
fr:
message_new_domain: "demarches-simplifiees.fr sappelle maintenant"
follow_link: Suivez ce lien pour vous connecter à la nouvelle adresse.
detected_error: Nous avons détecté une erreur réseau pour vous y accéder. Contactez votre service informatique pour autoriser la connexion sur demarches.gouv.fr.

View file

@ -0,0 +1,29 @@
- if auto_switch?
:javascript
const hintUrl = "http://#{ApplicationHelper::APP_HOST}/favicon.ico"
fetch(hintUrl, { mode: 'cors' }).then(() => {
window.location = '#{new_host_url}';
}).catch((e) => {
const error = new Error("Connection test on new host failed: " + e);
const event = new CustomEvent("sentry:capture-exception", { detail: error });
setTimeout(() => dispatchEvent(event), 100); // listener is not immediately enabled
})
= render Dsfr::NoticeComponent.new(closable: true, data_attributes: { "data-switch-domain-notice" => true }) do |c|
- c.with_title do
= t(".message_new_domain")
= "#{helpers.link_to APPLICATION_NAME, new_host_url}."
- if manual_switch?
= t(".follow_link")
- elsif auto_switch? || true
= t(".detected_error")
- if user && !user.preferred_domain_demarches_gouv_fr? && requested_from_new_domain?
= form_tag(helpers.preferred_domain_path, method: :patch, remote: true, name: "update-preferred-domain")
:javascript
document.addEventListener('noticeClosed', function(e) {
document.forms['update-preferred-domain'].submit();
});

View file

@ -63,6 +63,12 @@ module Users
redirect_to profil_path
end
def preferred_domain
current_user.update_preferred_domain(request.host_with_port)
head :no_content
end
private
def find_transfers

View file

@ -39,7 +39,9 @@ class Users::RegistrationsController < Devise::RegistrationsController
return redirect_to after_inactive_sign_up_path_for(existing_user)
end
super
super do
resource.update_preferred_domain(Current.host) if resource.valid?
end
end
# GET /resource/edit

View file

@ -13,6 +13,7 @@ class Users::SessionsController < Devise::SessionsController
if user&.valid_password?(params[:user][:password])
user.update(loged_in_with_france_connect: nil)
user.update_preferred_domain(Current.host)
end
super

View file

@ -9,5 +9,8 @@ export class DSFRHeaderController extends ApplicationController {
this.noticeTarget.parentNode?.removeChild(this.noticeTarget);
this.element.classList.remove('fr-header__with-notice-info');
const event = new CustomEvent('noticeClosed', { bubbles: true });
this.element.dispatchEvent(event);
}
}

View file

@ -0,0 +1,18 @@
module DomainMigratableConcern
extend ActiveSupport::Concern
included do
enum preferred_domain: { demarches_gouv_fr: 0, demarches_simplifiees_fr: 1 }, _prefix: true
validates :preferred_domain, inclusion: { in: User.preferred_domains.keys, allow_nil: true }
def update_preferred_domain(host)
case host
when ApplicationHelper::APP_HOST
preferred_domain_demarches_gouv_fr!
when ApplicationHelper::APP_HOST_LEGACY
preferred_domain_demarches_simplifiees_fr!
end
end
end
end

View file

@ -1,4 +1,5 @@
class User < ApplicationRecord
include DomainMigratableConcern
include EmailSanitizableConcern
include PasswordComplexityConcern

View file

@ -70,6 +70,8 @@
- if is_expert_context
= render partial: 'layouts/search_dossiers_form'
= render SwitchDomainBannerComponent.new(user: current_user)
#modal-header__menu.fr-header__menu.fr-modal{ "aria-labelledby": "navbar-burger-button" }
.fr-container
%button.fr-btn--close.fr-btn{ "aria-controls" => "modal-header__menu", title: t('close_modal', scope: [:layouts, :header]) }= t('close_modal', scope: [:layouts, :header])

View file

@ -1,7 +0,0 @@
- if auto_switch_domain?(request, user_signed_in?)
:javascript
const hintUrl = "#{image_url(FAVICONS_SRC["16px"])}"
fetch(hintUrl)
.then(function(){
window.location = window.location.href.replace("#{ApplicationHelper::APP_HOST_LEGACY}", "#{ApplicationHelper::APP_HOST}")
})

View file

@ -36,7 +36,6 @@
= yield(:invisible_captcha_styles)
= render partial: 'layouts/setup_theme'
= render partial: 'layouts/switch_domain_banner'
%body{ { id: content_for(:page_id), class: browser.platform.ios? ? 'ios' : nil, data: { controller: 'turbo number-input' } }.compact }
= render partial: 'layouts/skiplinks'

View file

@ -386,6 +386,7 @@ Rails.application.routes.draw do
post 'accept_merge' => 'profil#accept_merge'
post 'refuse_merge' => 'profil#refuse_merge'
delete 'france_connect_information' => 'profil#destroy_fci'
patch 'preferred_domain', to: 'profil#preferred_domain'
get 'fermeture/:path', to: 'commencer#closing_details', as: :closing_details
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddPreferredDomainToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :preferred_domain, :integer, default: nil
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: 2024_03_18_134256) do
ActiveRecord::Schema[7.0].define(version: 2024_03_18_152314) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
@ -1136,6 +1136,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_03_18_134256) do
t.string "locale"
t.datetime "locked_at", precision: nil
t.string "loged_in_with_france_connect", default: "false"
t.integer "preferred_domain"
t.datetime "remember_created_at", precision: nil
t.bigint "requested_merge_into_id"
t.datetime "reset_password_sent_at", precision: nil

View file

@ -3,7 +3,7 @@ if defined?(HamlLint)
class Linter::ApplicationNameLinter < Linter
include LinterRegistry
FORBIDDEN = 'demarches-simplifiees.fr'
FORBIDDEN = 'demarches.gouv.fr'
REPLACEMENT = "APPLICATION_NAME"
MSG = 'Hardcoding %s is forbidden, use %s instead'

View file

@ -0,0 +1,59 @@
# frozen_string_literal: true
require "rails_helper"
RSpec.describe SwitchDomainBannerComponent, type: :component do
let(:app_host_legacy) { "demarches-simplifiees.fr" }
let(:app_host) { "demarches.gouv.fr" }
let(:user) { create(:user) }
let(:request_host) { app_host_legacy }
let(:path) { "/" }
before do
allow(Current).to receive(:host).and_return(app_host)
stub_const("ApplicationHelper::APP_HOST_LEGACY", app_host_legacy)
stub_const("ApplicationHelper::APP_HOST", app_host)
Flipper.enable(:switch_domain)
end
after do
Flipper.disable(:switch_domain)
end
subject(:rendered) do
with_request_url path, host: request_host, format: nil do
render_inline(described_class.new(user: user))
end
end
context "when request is already on APP_HOST" do
let(:request_host) { app_host }
it "notify about names change" do
expect(rendered.to_html).to include("demarches-simplifiees.fr")
expect(rendered.to_html).to include(app_host)
expect(rendered.to_html).not_to include("window.location")
expect(rendered.to_html).not_to include("Suivez ce lien")
end
context "when user has already set preferred domain" do
let(:user) { create(:user, preferred_domain: :demarches_gouv_fr) }
it "does not render the banner" do
expect(rendered.to_html).to be_empty
end
end
end
describe "URL generation" do
let(:path) { "/admin/procedures" }
it "generate an url to the new domain" do
expect(rendered.to_html).to have_link(APPLICATION_NAME, href: "http://demarches.gouv.fr/admin/procedures")
expect(rendered.to_html).not_to include("window.location")
expect(rendered.to_html).to include("Suivez ce lien")
end
end
end

View file

@ -42,6 +42,10 @@ describe Users::RegistrationsController, type: :controller do
post :create, params: { user: user }
end
before do
allow(Current).to receive(:host).and_return(ENV.fetch("APP_HOST"))
end
context 'when user is correct' do
it 'sends confirmation instruction' do
message = double()
@ -49,6 +53,8 @@ describe Users::RegistrationsController, type: :controller do
expect(message).to receive(:deliver_later)
subject
expect(User.last.preferred_domain_demarches_gouv_fr?).to be_truthy
end
end

View file

@ -65,6 +65,18 @@ describe Users::SessionsController, type: :controller do
expect(flash.alert).to eq("Adresse électronique ou mot de passe incorrect.")
end
end
context 'when user has not yet a preferred domain' do
before do
allow(Current).to receive(:host).and_return(ENV.fetch("APP_HOST"))
end
it 'update preferred domain' do
subject
expect(user.reload.preferred_domain_demarches_gouv_fr?).to be_truthy
end
end
end
context 'when the credentials are wrong' do