From 27bdb9833851b2ac096e6b548fffe14a77549ef9 Mon Sep 17 00:00:00 2001
From: Arnaud Levy <arnaud.levy@noesya.coop>
Date: Fri, 23 Aug 2024 10:02:01 +0200
Subject: [PATCH] =?UTF-8?q?Am=C3=A9lioration=20des=20=C3=A9crans=20de=20co?=
 =?UTF-8?q?nnexion,=20notamment=20le=20code=20MFA=20(#2156)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Fix #2140

* better

* climate
---
 Gemfile.lock                                  |  80 +++++++-------
 README.md                                     |   2 +-
 app/assets/stylesheets/commons/_devise.sass   |  17 +++
 app/assets/stylesheets/commons/_forms.sass    |   5 -
 app/views/application/_footer.html.erb        |  42 ++++----
 app/views/application/_notice.html.erb        |  18 ++--
 app/views/devise/confirmations/new.html.erb   |  25 ++---
 app/views/devise/passwords/edit.html.erb      |  60 +++++------
 app/views/devise/passwords/new.html.erb       |  25 ++---
 app/views/devise/registrations/edit.html.erb  |   1 -
 app/views/devise/registrations/new.html.erb   |   2 +-
 app/views/devise/sessions/new.html.erb        |  14 ++-
 .../max_login_attempts_reached.html.erb       |   1 +
 .../two_factor_authentication/show.html.erb   | 101 ++++++++++--------
 app/views/devise/unlocks/new.html.erb         |  22 ++--
 app/views/extranet/layouts/devise.html.erb    |   1 +
 app/views/layouts/devise.html.erb             |  27 ++---
 config/locales/devise.en.yml                  |   4 +-
 config/locales/devise.fr.yml                  |   4 +-
 config/locales/en.yml                         |   6 +-
 config/locales/fr.yml                         |   6 +-
 21 files changed, 236 insertions(+), 227 deletions(-)
 create mode 100644 app/assets/stylesheets/commons/_devise.sass

diff --git a/Gemfile.lock b/Gemfile.lock
index fc5f7934c..f4f85a744 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -119,29 +119,29 @@ GEM
     annotate (3.2.0)
       activerecord (>= 3.2, < 8.0)
       rake (>= 10.4, < 14.0)
-    autoprefixer-rails (10.4.16.0)
+    autoprefixer-rails (10.4.19.0)
       execjs (~> 2)
     aws-eventstream (1.3.0)
-    aws-partitions (1.953.0)
-    aws-sdk-core (3.201.1)
+    aws-partitions (1.967.0)
+    aws-sdk-core (3.201.5)
       aws-eventstream (~> 1, >= 1.3.0)
       aws-partitions (~> 1, >= 1.651.0)
-      aws-sigv4 (~> 1.8)
+      aws-sigv4 (~> 1.9)
       jmespath (~> 1, >= 1.6.1)
     aws-sdk-kms (1.88.0)
       aws-sdk-core (~> 3, >= 3.201.0)
       aws-sigv4 (~> 1.5)
-    aws-sdk-s3 (1.156.0)
+    aws-sdk-s3 (1.159.0)
       aws-sdk-core (~> 3, >= 3.201.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.5)
-    aws-sigv4 (1.8.0)
+    aws-sigv4 (1.9.1)
       aws-eventstream (~> 1, >= 1.0.2)
     base64 (0.2.0)
     bcrypt (3.1.20)
     bigdecimal (3.1.8)
     bindex (0.8.1)
-    bootsnap (1.18.3)
+    bootsnap (1.18.4)
       msgpack (~> 1.2)
     bootstrap (5.3.3)
       autoprefixer-rails (>= 9.1.0)
@@ -179,7 +179,7 @@ GEM
       citeproc (~> 1.0, >= 1.0.9)
       csl (~> 2.0)
     cocoon (1.2.15)
-    concurrent-ruby (1.3.3)
+    concurrent-ruby (1.3.4)
     connection_pool (2.4.1)
     countries (6.0.1)
       unaccent (~> 0.3)
@@ -209,7 +209,7 @@ GEM
       warden (~> 1.2.3)
     devise-i18n (1.12.1)
       devise (>= 4.9.0)
-    docile (1.4.0)
+    docile (1.4.1)
     domain_name (0.6.20240107)
     drb (2.2.1)
     encryptor (3.0.0)
@@ -224,13 +224,13 @@ GEM
     faceted_search (3.6.2)
       font-awesome-sass
       rails (>= 5.2.0)
-    faraday (2.10.0)
+    faraday (2.10.1)
       faraday-net_http (>= 2.0, < 3.2)
       logger
     faraday-cookie_jar (0.0.7)
       faraday (>= 0.8.0)
       http-cookie (~> 1.0.0)
-    faraday-encoding (0.0.5)
+    faraday-encoding (0.0.6)
       faraday
     faraday-follow_redirects (0.3.0)
       faraday (>= 1, < 3)
@@ -241,7 +241,7 @@ GEM
       faraday (>= 0.8)
     faraday-multipart (1.0.4)
       multipart-post (~> 2)
-    faraday-net_http (3.1.0)
+    faraday-net_http (3.1.1)
       net-http
     faraday-retry (2.2.1)
       faraday (~> 2.0)
@@ -254,7 +254,7 @@ GEM
     font-awesome-sass (6.5.2)
       sassc (~> 2.0)
     front_matter_parser (1.0.1)
-    fugit (1.11.0)
+    fugit (1.11.1)
       et-orbi (~> 1, >= 1.2.11)
       raabro (~> 1.4)
     geo_calc (0.7.8)
@@ -279,20 +279,20 @@ GEM
       terminal-table (>= 1.5.1)
     globalid (1.2.1)
       activesupport (>= 6.1)
-    good_job (4.0.2)
+    good_job (4.2.0)
       activejob (>= 6.1.0)
       activerecord (>= 6.1.0)
       concurrent-ruby (>= 1.3.1)
       fugit (>= 1.11.0)
       railties (>= 6.1.0)
       thor (>= 1.0.0)
-    google-protobuf (4.27.2-arm64-darwin)
+    google-protobuf (4.27.3-arm64-darwin)
       bigdecimal
       rake (>= 13)
-    google-protobuf (4.27.2-x86_64-darwin)
+    google-protobuf (4.27.3-x86_64-darwin)
       bigdecimal
       rake (>= 13)
-    google-protobuf (4.27.2-x86_64-linux)
+    google-protobuf (4.27.3-x86_64-linux)
       bigdecimal
       rake (>= 13)
     hal_openscience (0.1.0)
@@ -303,10 +303,10 @@ GEM
       actionpack (>= 5.2)
       activesupport (>= 5.2)
     hash_dot (2.5.0)
-    hashdiff (1.1.0)
+    hashdiff (1.1.1)
     hashie (5.0.0)
     htmlentities (4.3.4)
-    http-cookie (1.0.6)
+    http-cookie (1.0.7)
       domain_name (~> 0.5)
     httparty (0.22.0)
       csv
@@ -319,7 +319,7 @@ GEM
     i18n_date_range (2.1.2)
       rails
       rails-i18n
-    image_processing (1.12.2)
+    image_processing (1.13.0)
       mini_magick (>= 4.9.5, < 5)
       ruby-vips (>= 2.0.17, < 3)
     io-console (0.7.2)
@@ -337,7 +337,7 @@ GEM
     json (2.7.2)
     jwt (2.8.2)
       base64
-    kamifusen (1.11.2)
+    kamifusen (1.12.0)
       image_processing
       rails
     kaminari (1.2.2)
@@ -352,7 +352,7 @@ GEM
       activerecord
       kaminari-core (= 1.2.2)
     kaminari-core (1.2.2)
-    leaflet-rails (1.9.4)
+    leaflet-rails (1.9.5)
       actionpack (>= 4.2.0)
       railties (>= 4.2.0)
     libretranslate (0.1.0)
@@ -387,7 +387,7 @@ GEM
       nokogiri (~> 1.13)
     mini_magick (4.13.2)
     mini_mime (1.1.5)
-    minitest (5.24.1)
+    minitest (5.25.1)
     msgpack (1.7.2)
     multi_xml (0.7.1)
       bigdecimal (~> 3.1)
@@ -408,11 +408,11 @@ GEM
     net-smtp (0.5.0)
       net-protocol
     nio4r (2.7.3)
-    nokogiri (1.16.6-arm64-darwin)
+    nokogiri (1.16.7-arm64-darwin)
       racc (~> 1.4)
-    nokogiri (1.16.6-x86_64-darwin)
+    nokogiri (1.16.7-x86_64-darwin)
       racc (~> 1.4)
-    nokogiri (1.16.6-x86_64-linux)
+    nokogiri (1.16.7-x86_64-linux)
       racc (~> 1.4)
     oauth2 (2.0.9)
       faraday (>= 0.17.3, < 3.0)
@@ -442,20 +442,20 @@ GEM
     orm_adapter (0.5.0)
     pexels (0.5.0)
       requests (~> 1.0.2)
-    pg (1.5.6)
+    pg (1.5.7)
     pg_query (5.1.0)
       google-protobuf (>= 3.22.3)
-    pghero (3.5.0)
-      activerecord (>= 6)
+    pghero (3.6.0)
+      activerecord (>= 6.1)
     popper_js (2.11.8)
     psych (5.1.2)
       stringio
-    public_suffix (6.0.0)
+    public_suffix (6.0.1)
     puma (6.4.2)
       nio4r (~> 2.0)
     raabro (1.4.0)
-    racc (1.8.0)
-    rack (3.1.6)
+    racc (1.8.1)
+    rack (3.1.7)
     rack-mini-profiler (2.3.4)
       rack (>= 1.2.0)
     rack-protection (4.0.0)
@@ -511,7 +511,7 @@ GEM
       ffi
     rdoc (6.7.0)
       psych (>= 4.0.0)
-    redis (5.2.0)
+    redis (5.3.0)
       redis-client (>= 0.22.0)
     redis-client (0.22.2)
       connection_pool
@@ -523,7 +523,7 @@ GEM
     responders (3.1.1)
       actionpack (>= 5.2)
       railties (>= 5.2)
-    rexml (3.3.1)
+    rexml (3.3.6)
       strscan
     roo (2.10.1)
       nokogiri (~> 1)
@@ -532,10 +532,11 @@ GEM
     ruby-saml (1.16.0)
       nokogiri (>= 1.13.10)
       rexml
-    ruby-vips (2.2.1)
+    ruby-vips (2.2.2)
       ffi (~> 1.12)
+      logger
     rubyzip (2.3.2)
-    sanitize (6.1.1)
+    sanitize (6.1.3)
       crass (~> 1.0.2)
       nokogiri (>= 1.12.0)
     sassc (2.4.0)
@@ -584,7 +585,7 @@ GEM
     sprockets (4.2.1)
       concurrent-ruby (~> 1.0)
       rack (>= 2.2.4, < 4)
-    sprockets-rails (3.5.1)
+    sprockets-rails (3.5.2)
       actionpack (>= 6.1)
       activesupport (>= 6.1)
       sprockets (>= 3.0.0)
@@ -614,7 +615,8 @@ GEM
       httparty (~> 0.20)
       oauth2 (>= 2.0.8)
     uri (0.13.0)
-    vcr (6.2.0)
+    vcr (6.3.1)
+      base64
     version_gem (1.1.4)
     warden (1.2.9)
       rack (>= 2.0.9)
@@ -638,7 +640,7 @@ GEM
     websocket-extensions (0.1.5)
     xpath (3.2.0)
       nokogiri (~> 1.8)
-    zeitwerk (2.6.16)
+    zeitwerk (2.6.17)
     zlib (2.1.1)
 
 PLATFORMS
diff --git a/README.md b/README.md
index 28f9f9968..c69f6bf7b 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Osuny
 
-Un commun numérique libre, sobre et accessible pour l'Enseignement Supérieur et la Recherche
+[osuny.org](https://www.osuny.org)
 
 [![Maintainability](https://api.codeclimate.com/v1/badges/32cf5551caac199ffad5/maintainability)](https://codeclimate.com/github/osunyorg/admin/maintainability)
 
diff --git a/app/assets/stylesheets/commons/_devise.sass b/app/assets/stylesheets/commons/_devise.sass
new file mode 100644
index 000000000..7e06cd960
--- /dev/null
+++ b/app/assets/stylesheets/commons/_devise.sass
@@ -0,0 +1,17 @@
+.layout-devise
+    display: flex
+    flex-direction: column
+    justify-content: space-between
+    min-height: 100vh
+    header
+        padding: 100px 0
+        text-align: center
+    main
+        flex-grow: 1
+        min-height: auto
+    footer
+        min-height: auto
+    .mfa-code
+        font-size: pxToRem(26)
+        @include media-breakpoint-up(lg)
+            font-size: pxToRem(32)
diff --git a/app/assets/stylesheets/commons/_forms.sass b/app/assets/stylesheets/commons/_forms.sass
index 2112cc485..900ce5936 100644
--- a/app/assets/stylesheets/commons/_forms.sass
+++ b/app/assets/stylesheets/commons/_forms.sass
@@ -23,11 +23,6 @@ textarea.form-control
     padding: 8px
     padding-top: 0 // Compensation de la hauteur des lignes
 
-.mfa-code
-    font-size: pxToRem(30) !important
-    @include media-breakpoint-up(lg)
-        font-size: pxToRem(40) !important
-
 .visibility_radio_buttons
     .form-check-items
         display: flex
diff --git a/app/views/application/_footer.html.erb b/app/views/application/_footer.html.erb
index abd05f1f8..85713a57f 100644
--- a/app/views/application/_footer.html.erb
+++ b/app/views/application/_footer.html.erb
@@ -1,24 +1,22 @@
-<footer class="mt-5 pt-5">
-  <div class="container">
-    <div class="mb-5 text-lg-center">
-      <%= image_tag 'osuny-black.svg', width: 80 %>
+<footer class="bg-black text-white py-5">
+  <div class="container-fluid">
+    <div class="d-md-flex justify-content-between mt-5 pt-5">
+      <%= link_to 'https://www.osuny.org', target: :_blank, class: 'text-white-50' do %>
+        <%= t('admin.footer.signature') %>
+      <% end %>
+      <ul class="list-unstyled d-md-flex">
+        <%
+          [
+            ['Aide à la contribution', 'https://support.osuny.org'],
+            [t('terms_of_service'), t('terms_of_service_url')],
+            ['Statut', 'https://status.osuny.org'],
+          ].each do |item| 
+        %>
+          <li>
+            <%= link_to item.first, item.last, target: :_blank, class: 'text-white-50' %>
+          </li>
+        <% end %>
+      </ul>
     </div>
-    <nav class="nav small d-block d-lg-flex justify-content-lg-center">
-      <%= link_to t('terms_of_service'),
-                  t('terms_of_service_url'),
-                  class: 'nav-link ps-0',
-                  target: '_blank',
-                  rel: 'noreferrer' %>
-      <%= link_to t('privacy_policy'),
-                  t('privacy_policy_url'),
-                  class: 'nav-link ps-0',
-                  target: '_blank',
-                  rel: 'noreferrer' %>
-      <%= link_to t('cookies_policy'),
-                  t('cookies_policy_url'),
-                  class: 'nav-link ps-0',
-                  target: '_blank',
-                  rel: 'noreferrer' %>
-    </nav>
   </div>
-</footer>
+</footer>
\ No newline at end of file
diff --git a/app/views/application/_notice.html.erb b/app/views/application/_notice.html.erb
index 3b4ff192c..0dad5bc23 100644
--- a/app/views/application/_notice.html.erb
+++ b/app/views/application/_notice.html.erb
@@ -1,12 +1,6 @@
-<div class="toasts-container" style="position: fixed; top: 20px; right: 20px; z-index: 100000;">
-  <% unless notice.nil? %>
-    <div class="js-notyf-notice d-none">
-      <%= notice %>
-    </div>
-  <% end %>
-  <% unless alert.nil? %>
-    <div class="js-notyf-alert d-none">
-      <%= alert %>
-    </div>
-  <% end %>
-</div>
\ No newline at end of file
+<% unless notice.blank? %>
+  <div class="alert alert-success mt-2" role="alert"><%= notice.html_safe %></div>
+<% end %>
+<% unless alert.blank? %>
+  <div class="alert alert-danger mt-2" role="alert"><%= alert.html_safe %></div>
+<% end %>
\ No newline at end of file
diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb
index 4d24f697b..13c1166c5 100644
--- a/app/views/devise/confirmations/new.html.erb
+++ b/app/views/devise/confirmations/new.html.erb
@@ -1,21 +1,16 @@
 <%= content_for :title, t('.title') %>
+<% @small_content = true %>
 
-<h2 class="mb-4"><%= t(".resend_confirmation_instructions") %></h2>
-
+<h1 class="h4 mb-4"><%= t(".resend_confirmation_instructions") %></h1>
 <%= simple_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
   <%= f.error_notification %>
   <%= f.full_error :confirmation_token %>
-  <div class="row">
-    <div class="col-md-6">
-      <%= f.input :email,
-                  required: true,
-                  autofocus: true,
-                  value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email),
-                  input_html: { autocomplete: "email" } %>
-    </div>
-    <div class="col-md-6">
-      <label class="form-label">&nbsp;</label><br>
-      <%= f.button :submit, t(".resend_confirmation_instructions"), class: 'btn btn-primary' %>
-    </div>
-  </div>
+  <%= f.input :email,
+              required: true,
+              placeholder: User.human_attribute_name(:email),
+              autofocus: true,
+              label: false,
+              value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email),
+              input_html: { autocomplete: "email" } %>
+  <%= f.button :submit, t(".resend_confirmation_instructions"), class: 'btn btn-primary' %>
 <% end %>
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb
index 44071e8c9..4dd136439 100644
--- a/app/views/devise/passwords/edit.html.erb
+++ b/app/views/devise/passwords/edit.html.erb
@@ -1,41 +1,31 @@
 <%= content_for :title, t('.title') %>
+<% @small_content = true %>
 
-<h2 class="mb-4"><%= t(".change_your_password") %></h2>
-
+<h1 class="h4 mb-4"><%= t(".change_your_password") %></h1>
 <%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
   <%= f.error_notification %>
-
   <%= f.input :reset_password_token, as: :hidden %>
   <%= f.full_error :reset_password_token %>
-
-  <div class="row">
-    <div class="col-md-6">
-      <%= f.input :password,
-                  as: :password_with_hints,
-                  label: t(".new_password"),
-                  required: true,
-                  autofocus: true,
-                  allow_password_uncloaking: true,
-                  validators: {
-                    length: Devise.password_length.first,
-                    uppercase_char: true,
-                    lowercase_char: true,
-                    numeric_char: true,
-                    special_char: Rails.application.config.allowed_special_chars
-                  },
-                  input_html: { autocomplete: "new-password" } %>
-    </div>
-    <div class="col-md-6">
-      <%= f.input :password_confirmation,
-                  as: :password_with_sync,
-                  label: t(".confirm_new_password"),
-                  required: true,
-                  allow_password_uncloaking: true,
-                  compare_with_field: :password,
-                  input_html: { autocomplete: "new-password" } %>
-    </div>
-    <div class="col-md-6">
-      <%= f.button :submit, t(".change_my_password"), class: 'btn btn-primary' %>
-    </div>
-  </div>
-<% end %>
+  <%= f.input :password,
+              as: :password_with_hints,
+              label: t(".new_password"),
+              required: true,
+              autofocus: true,
+              allow_password_uncloaking: true,
+              validators: {
+                length: Devise.password_length.first,
+                uppercase_char: true,
+                lowercase_char: true,
+                numeric_char: true,
+                special_char: Rails.application.config.allowed_special_chars
+              },
+              input_html: { autocomplete: "new-password" } %>
+  <%= f.input :password_confirmation,
+              as: :password_with_sync,
+              label: t(".confirm_new_password"),
+              required: true,
+              allow_password_uncloaking: true,
+              compare_with_field: :password,
+              input_html: { autocomplete: "new-password" } %>
+  <%= f.button :submit, t(".change_my_password"), class: 'btn btn-primary' %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb
index 0a10d6323..a96e5866d 100644
--- a/app/views/devise/passwords/new.html.erb
+++ b/app/views/devise/passwords/new.html.erb
@@ -1,20 +1,15 @@
 <%= content_for :title, t('.title') %>
+<% @small_content = true %>
 
-<h2 class="mb-4"><%= t(".forgot_your_password") %></h2>
+<h1 class="h4 mb-4"><%= t(".forgot_your_password") %></h1>
 
 <%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
   <%= f.error_notification %>
-
-  <div class="row">
-    <div class="col-md-6">
-      <%= f.input :email,
-                  required: true,
-                  autofocus: true,
-                  input_html: { autocomplete: "email" } %>
-    </div>
-    <div class="col-md-6">
-      <label class="form-label">&nbsp;</label><br>
-      <%= f.button :submit, t(".send_me_reset_password_instructions"), class: 'btn btn-primary' %>
-    </div>
-  </div>
-<% end %>
+  <%= f.input :email,
+              required: true,
+              autofocus: true,
+              placeholder: User.human_attribute_name(:email),
+              label: false,
+              input_html: { autocomplete: "email" } %>
+  <%= f.button :submit, t(".send_me_reset_password_instructions"), class: 'btn btn-primary' %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb
index 468f4f049..49e681395 100644
--- a/app/views/devise/registrations/edit.html.erb
+++ b/app/views/devise/registrations/edit.html.erb
@@ -16,7 +16,6 @@
       <%= render 'admin/application/i18n/form', f: f %>
     </div>
     <div class="col-lg-6">
-     
       <%= f.input :mobile_phone %>
       <%= f.input :picture,
                   as: :single_deletable_file,
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb
index 0617c8886..6da918858 100644
--- a/app/views/devise/registrations/new.html.erb
+++ b/app/views/devise/registrations/new.html.erb
@@ -1,6 +1,6 @@
 <%= content_for :title, t('.title', title: current_extranet.present? ? current_extranet.name : 'Osuny') %>
 
-<h2 class="mb-4"><%= t(".sign_up") %></h2>
+<h1 class="h4 mb-4"><%= t(".sign_up") %></h1>
 
 <%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
   <%= f.error_notification %>
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb
index afd0b0b68..c725701c7 100644
--- a/app/views/devise/sessions/new.html.erb
+++ b/app/views/devise/sessions/new.html.erb
@@ -1,8 +1,12 @@
 <%= content_for :title, t('.title', title: current_extranet.present? ? current_extranet.name : 'Osuny') %>
 
+<h1 class="h4 d-none">
+  <%= t('.title', title: current_extranet.present? ? current_extranet.name : 'Osuny') %>
+</h1>
 <div class="row">
-  <div class="col-md-6 mb-5">
-    <h2 class="mb-4"><%= t('login.already_registered') %></h2>
+  <div class="col-lg-6 mb-5">
+    <h2 class="h4 mb-4"><%= t('login.already_registered') %></h2>
+    <p><%= t('login.already_registered_details') %></p>
     <% if current_context.has_sso? %>
       <% button_label = current_context.sso_button_label.blank? ? t('login.sign_in_with_sso') : current_context.sso_button_label %>
       <p><%= link_to button_label, omniauth_authorize_path(resource_name, current_context.sso_provider), method: :post, class: 'btn btn-primary' %></p>
@@ -38,7 +42,7 @@
         </div>
       <% end %>
     </div>
-    <div class="mt-3">
+    <div class="mt-3 small">
       <% if devise_mapping.confirmable? %>
         <%= link_to t('devise.shared.links.didn_t_receive_confirmation_instructions'), new_confirmation_path(resource_name) %><br />
       <% end %>
@@ -48,8 +52,8 @@
       <% end %>
     </div>
   </div>
-  <div class="col-md-6">
-    <h2 class="mb-4"><%= t('login.not_registered_yet') %></h2>
+  <div class="col-lg-6">
+    <h2 class="h4 mb-4"><%= t('login.not_registered_yet') %></h2>
     <p><%= t('login.not_registered_yet_details') %></p>
     <div class="form-actions">
       <%= link_to t("devise.registrations.new.sign_up"),
diff --git a/app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb b/app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb
index 5e9459518..637515a4f 100644
--- a/app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb
+++ b/app/views/devise/two_factor_authentication/max_login_attempts_reached.html.erb
@@ -1,4 +1,5 @@
 <%= content_for :title, t('.title') %>
+<% @small_content = true %>
 
 <div class="alert alert-danger" role="alert">
   <%= t('devise.two_factor_authentication.max_login_attempts_reached').html_safe %>
diff --git a/app/views/devise/two_factor_authentication/show.html.erb b/app/views/devise/two_factor_authentication/show.html.erb
index e743ba9f3..05dd72c73 100644
--- a/app/views/devise/two_factor_authentication/show.html.erb
+++ b/app/views/devise/two_factor_authentication/show.html.erb
@@ -1,50 +1,65 @@
 <%= content_for :title, t('.title') %>
+<% @small_content = true %>
 
-<div class="text-lg-center">
-  <h1 class="h4 mb-5"><%= t('.title') %></h1>
+<h1 class="h4 mb-4"><%= t('.title') %></h1>
 
-  <p>
-    <% if resource.direct_otp %>
-      <% if resource.direct_otp_delivery_method == 'mobile_phone' %>
-        <%= t('devise.two_factor_authentication.enter_code_direct_otp_mobile_phone', phone: masked_phone(resource.mobile_phone)) %>
-      <% else %>
-      <%= t('devise.two_factor_authentication.enter_code_direct_otp_email', mail: masked_email(resource.email)) %>
-      <% end %>
+<p class="mb-4">
+  <% if resource.direct_otp %>
+    <% if resource.direct_otp_delivery_method == 'mobile_phone' %>
+      <%= t('devise.two_factor_authentication.enter_code_direct_otp_mobile_phone_html', phone: masked_phone(resource.mobile_phone)) %>
     <% else %>
-      <%= t('devise.two_factor_authentication.enter_code_totp') %>
+      <%= t('devise.two_factor_authentication.enter_code_direct_otp_email_html', mail: masked_email(resource.email)) %>
     <% end %>
-  </p>
-
-  <%= simple_form_for(resource, url: user_two_factor_authentication_path, html: { method: :put, class: 'my-3' }) do |f| %>
-    <div class="form-inputs">
-      <div class="input-group required mx-lg-auto mt-0" style="max-width: 220px">
-        <%= text_field_tag :code,
-                            '',
-                            type: 'tel',
-                            pattern: '\d*',
-                            required: true,
-                            autofocus: true,
-                            autocomplete: 'off',
-                            class: 'form-control form-control-lg mfa-code text-lg-center string required'%>
-      </div>
-      <%= f.button  :submit,
-                    t('devise.two_factor_authentication.validate'),
-                    class: "btn btn-primary mt-2" %>
-      <p class="mt-4 mb-5">
-        <% if resource.direct_otp %>
-          <%= link_to t('devise.two_factor_authentication.resend_code'), [:resend_code, resource_name, :two_factor_authentication] %>
-        <% else %>
-          <%= link_to t('devise.two_factor_authentication.send_code_instead'), [:resend_code, resource_name, :two_factor_authentication] %>
-        <% end %>
-        <% unless resource.mobile_phone.blank? # when phone is blank default code method is already :email so we don't need another link %>
-          • <%= link_to t('devise.two_factor_authentication.send_email_code'), [:resend_code, resource_name, :two_factor_authentication, delivery_method: :email] %>
-        <% end %>
-      </p>
-    </div>
+  <% else %>
+    <%= t('devise.two_factor_authentication.enter_code_totp') %>
   <% end %>
+</p>
 
-  <%= link_to t('devise.shared.links.sign_out'),
-              destroy_user_session_path,
-              method: :delete,
-              class: "btn btn-outline-danger" %>
-</div>
\ No newline at end of file
+<%= simple_form_for(resource, url: user_two_factor_authentication_path, html: { method: :put }) do |f| %>
+  <div class="form-inputs">
+    <div class="input-group">
+      <input  type="tel" 
+              name="code" 
+              id="code" 
+              value="" 
+              pattern="\d*" 
+              required="required" 
+              autofocus="autofocus" 
+              autocomplete="off" 
+              style="letter-spacing: 1rem"
+              placeholder="______" 
+              class="form-control form-control-lg mfa-code string required">
+      <input  type="submit" 
+              name="commit"
+              value="<%= t('devise.two_factor_authentication.validate') %>" 
+              class="btn btn-dark bg-black px-3 px-lg-5"
+              data-disable-with="<%= t('devise.two_factor_authentication.validate') %>">
+    </div>
+  </div>
+<% end %>
+<div class="my-5 pt-5 small">
+  <p class="mb-2">
+    <%= t('devise.two_factor_authentication.help') %>
+  </p>
+  <ul class="list-unstyled">
+    <li class="d-lg-inline-block">
+      <% key = resource.direct_otp ? 'resend_code' : 'send_code_instead' %>
+      <%= link_to t("devise.two_factor_authentication.#{key}"), 
+                  [:resend_code, resource_name, :two_factor_authentication],
+                  class: 'd-block pe-3 py-3' %>
+    </li>
+    <% unless resource.mobile_phone.blank? # when phone is blank default code method is already :email so we don't need another link %>
+      <li class="d-lg-inline-block">
+        <%= link_to t('devise.two_factor_authentication.send_email_code'), 
+                    [:resend_code, resource_name, :two_factor_authentication, delivery_method: :email],
+                    class: 'd-block pe-2 py-2' %>
+      </li>
+    <% end %>
+    <li class="d-lg-inline-block ">
+      <%= link_to t('devise.shared.links.sign_out'),
+                  destroy_user_session_path,
+                  method: :delete,
+                  class: 'd-block pe-2 py-2' %>
+    </li>
+  </ul>
+</div>
diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb
index 1035f065a..dd31c15ad 100644
--- a/app/views/devise/unlocks/new.html.erb
+++ b/app/views/devise/unlocks/new.html.erb
@@ -1,20 +1,16 @@
 <%= content_for :title, t('.title') %>
+<% @small_content = true %>
 
-<h2 class="mb-4"><%= t(".resend_unlock_instructions") %></h2>
+<h1 class="h4 mb-4"><%= t(".resend_unlock_instructions") %></h1>
 
 <%= simple_form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
   <%= f.error_notification %>
   <%= f.full_error :unlock_token %>
-  <div class="row">
-    <div class="col-md-6">
-      <%= f.input :email,
-                  required: true,
-                  autofocus: true,
-                  input_html: { autocomplete: "email" } %>
-    </div>
-    <div class="col-md-6">
-      <label class="form-label">&nbsp;</label><br>
-      <%= f.button :submit, t(".resend_unlock_instructions"), class: 'btn btn-primary' %>
-    </div>
-  </div>
+  <%= f.input :email,
+              placeholder: User.human_attribute_name(:email),
+              label: false,
+              required: true,
+              autofocus: true,
+              input_html: { autocomplete: "email" } %>
+  <%= f.button :submit, t(".resend_unlock_instructions"), class: 'btn btn-primary' %>
 <% end %>
diff --git a/app/views/extranet/layouts/devise.html.erb b/app/views/extranet/layouts/devise.html.erb
index 69ff30bdc..c7e0f4dd3 100644
--- a/app/views/extranet/layouts/devise.html.erb
+++ b/app/views/extranet/layouts/devise.html.erb
@@ -24,6 +24,7 @@
               <%= link_to t('terms_of_service'), terms_path, rel: 'noreferrer' if current_extranet.has_terms? %>
               <%= link_to t('privacy_policy'), privacy_policy_path, rel: 'noreferrer' if current_extranet.has_privacy_policy? %>
               <%= link_to t('cookies_policy'), cookies_policy_path, rel: 'noreferrer' if current_extranet.has_cookies_policy? %>
+            </nav>
           </footer>
         </div>
       </div>
diff --git a/app/views/layouts/devise.html.erb b/app/views/layouts/devise.html.erb
index 9e7d9b5f2..41244ffcd 100644
--- a/app/views/layouts/devise.html.erb
+++ b/app/views/layouts/devise.html.erb
@@ -10,21 +10,24 @@
     <%= javascript_include_tag 'devise' %>
     <%= favicon_link_tag 'favicon.png' %>
   </head>
-  <body class="<%= body_classes %>">
-    <div class="container">
-      <h1 class="my-5 py-5 text-center">
-        <%= link_to root_path do %>
-          <%= render 'logo' %>
-        <% end %>
-      </h1>
-      <% unless notice.blank? %>
-        <div class="alert alert-success mt-2" role="alert"><%= notice.html_safe %></div>
+  <body class="layout-devise <%= body_classes %>">
+    <header>
+      <%= link_to root_path do %>
+        <%= render 'logo' %>
       <% end %>
-      <% unless alert.blank? %>
-        <div class="alert alert-danger mt-2" role="alert"><%= alert.html_safe %></div>
+    </header>
+    <main class="container-fluid">
+      <% if @small_content %>
+        <div class="row">
+          <div class="offset-md-2 col-md-8 offset-lg-3 col-lg-6 offset-xxl-4 col-xxl-4">
       <% end %>
+      <%= render 'notice' %>
       <%= yield %>
-    </div>
+      <% if @small_content %>
+          </div>
+        </div>
+      <% end %>
+    </main>
     <%= render 'footer' %>
     <%= render 'bugsnag' %>
   </body>
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index e623f762d..b7e6c046d 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -12,10 +12,10 @@ en:
       edit:
         title: Account edition
       new: 
-        title: "Register - %{title}"
+        title: "Register on %{title}"
     sessions:
       new:
-        title: "Welcome - %{title}"
+        title: "Welcome to %{title}"
     two_factor_authentication:
       max_login_attempts_reached:
         title: Maximul login attemps reached
diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml
index 193656edf..e0f0196f1 100644
--- a/config/locales/devise.fr.yml
+++ b/config/locales/devise.fr.yml
@@ -12,10 +12,10 @@ fr:
       edit:
         title: Modification de votre compte
       new: 
-        title: "Inscription - %{title}"
+        title: "Inscription sur %{title}"
     sessions:
       new:
-        title: "Bienvenue - %{title}"
+        title: "Bienvenue sur %{title}"
     two_factor_authentication:
       max_login_attempts_reached:
         title: Maximum d'essais atteint
diff --git a/config/locales/en.yml b/config/locales/en.yml
index ce31f8767..9c83ecd5a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -205,9 +205,10 @@ en:
       admin_unlock: Unlock it now!
       attempt_failed: "Invalid Code"
       code_has_been_sent: "Your authentication code has been sent."
-      enter_code_direct_otp_email: "Enter the code that was sent to you on your email %{mail}"
-      enter_code_direct_otp_mobile_phone: "Enter the code that was sent to you on your mobile phone %{phone}"
+      enter_code_direct_otp_email_html: "To ensure a secure connection, we will send you a 6-digit, one-time code to your e-mail address %{mail}. Enter the code below."
+      enter_code_direct_otp_mobile_phone_html: "To ensure a secure connection, we will send you a 6-digit, one-time code to your mobile phone %{phone}. Enter the code below."
       enter_code_totp: "Enter the code from your authentication application"
+      help: "If you have a problem, you can..."
       max_login_attempts_reached: "You're account has been locked for security reasons.<br />Please contact an administrator."
       resend_code: "Resend Code"
       send_code_instead: "Send me a code instead"
@@ -272,6 +273,7 @@ en:
   loading: Loading...
   login:
     already_registered: Already registered?
+    already_registered_details: Sign in if you already have an account.
     not_registered_yet: Not registered yet?
     not_registered_yet_details: Register if you have no account yet.
     or: or
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 5c1d080f8..7ce90e599 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -205,9 +205,10 @@ fr:
       admin_unlock: Le débloquer !
       attempt_failed: "Code invalide"
       code_has_been_sent: "Un code d'authentification vient de vous être envoyé."
-      enter_code_direct_otp_email: "Entrez le code qui vous a été envoyé sur votre mail %{mail}"
-      enter_code_direct_otp_mobile_phone: "Entrez le code qui vous a été envoyé sur votre mobile %{phone}"
+      enter_code_direct_otp_email_html: "Pour assurer la sécurité de votre connexion, nous vous envoyons un code à 6 chiffres, à usage unique, sur votre mail %{mail}. Entrez ce code ci-dessous."
+      enter_code_direct_otp_mobile_phone_html: "Pour assurer la sécurité de votre connexion, nous vous envoyons un code à 6 chiffres, à usage unique, sur votre mobile %{phone}. Entrez ce code ci-dessous."
       enter_code_totp: "Entrez le code de votre application d'authentification"
+      help: "Si vous rencontrez un problème, vous pouvez..."
       max_login_attempts_reached: "Votre compte a été bloqué pour des raisons de sécurité.<br />Veuillez contacter un administrateur."
       resend_code: "Renvoyer le code"
       send_code_instead: "Envoyez-moi plutôt un code"
@@ -272,6 +273,7 @@ fr:
   loading: Chargement...
   login:
     already_registered: Déjà inscrit ?
+    already_registered_details: Connectez-vous si vous avez déjà un compte.
     not_registered_yet: Pas encore inscrit ?
     not_registered_yet_details: Inscrivez-vous si vous n'avez pas encore de compte.
     or: ou bien
-- 
GitLab