Merge pull request #8632 from colinux/password-edit-show
ETQ utilisateur je peux voir le mot de passe que je choisis
This commit is contained in:
commit
69383659f1
18 changed files with 168 additions and 77 deletions
|
@ -9,12 +9,10 @@ $complexity-color-3: #FFD000;
|
|||
$complexity-color-4: $green;
|
||||
|
||||
.password-complexity {
|
||||
margin-top: -24px;
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
background: $complexity-bg;
|
||||
display: block;
|
||||
margin-bottom: $default-spacer;
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ class Dsfr::InputComponent < ApplicationComponent
|
|||
# it uses aria-describedby on input and link it to yielded content
|
||||
renders_one :describedby
|
||||
|
||||
def initialize(form:, attribute:, input_type:, opts: {}, required: true)
|
||||
def initialize(form:, attribute:, input_type: :text_field, opts: {}, required: true)
|
||||
@form = form
|
||||
@attribute = attribute
|
||||
@input_type = input_type
|
||||
|
@ -40,19 +40,21 @@ class Dsfr::InputComponent < ApplicationComponent
|
|||
'fr-mb-0': true,
|
||||
'fr-input--error': errors_on_attribute?))
|
||||
|
||||
if errors_on_attribute? || describedby
|
||||
@opts = @opts.deep_merge(aria: {
|
||||
describedby: error_message_id,
|
||||
invalid: errors_on_attribute?
|
||||
if errors_on_attribute? || describedby?
|
||||
@opts.deep_merge!(aria: {
|
||||
describedby: describedby_id,
|
||||
invalid: errors_on_attribute?
|
||||
})
|
||||
end
|
||||
|
||||
if @required
|
||||
@opts[:required] = true
|
||||
end
|
||||
|
||||
if email?
|
||||
@opts = @opts.deep_merge(data: {
|
||||
@opts.deep_merge!(data: {
|
||||
action: "blur->email-input#checkEmail",
|
||||
'email-input-target': 'input'
|
||||
'email-input-target': 'input'
|
||||
})
|
||||
end
|
||||
@opts
|
||||
|
@ -63,14 +65,14 @@ class Dsfr::InputComponent < ApplicationComponent
|
|||
errors.has_key?(attribute_or_rich_body)
|
||||
end
|
||||
|
||||
def error_message_id
|
||||
dom_id(object, @attribute)
|
||||
end
|
||||
|
||||
def error_messages
|
||||
errors.full_messages_for(attribute_or_rich_body)
|
||||
end
|
||||
|
||||
def describedby_id
|
||||
dom_id(object, "#{@attribute}-messages")
|
||||
end
|
||||
|
||||
# i18n lookups
|
||||
def label
|
||||
object.class.human_attribute_name(@attribute)
|
||||
|
@ -89,6 +91,10 @@ class Dsfr::InputComponent < ApplicationComponent
|
|||
@input_type == :email_field
|
||||
end
|
||||
|
||||
def show_password_id
|
||||
dom_id(object, "#{@attribute}_show_password")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hint?
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
- if hint?
|
||||
%span.fr-hint-text= hint
|
||||
|
||||
= @form.send(@input_type, @attribute, input_opts)
|
||||
= @form.public_send(@input_type, @attribute, input_opts)
|
||||
|
||||
- if errors_on_attribute?
|
||||
- if error_messages.size == 1
|
||||
%p.fr-error-text{ id: error_message_id }= error_messages.first
|
||||
%p.fr-error-text{ id: describedby_id }= error_messages.first
|
||||
- else
|
||||
.fr-error-text{ id: error_message_id }
|
||||
.fr-error-text{ id: describedby_id }
|
||||
%ul.list-style-type-none.fr-pl-0
|
||||
- error_messages.map do |error_message|
|
||||
%li= error_message
|
||||
|
@ -23,8 +23,8 @@
|
|||
|
||||
- if password?
|
||||
.fr-password__checkbox.fr-checkbox-group.fr-checkbox-group--sm
|
||||
%input#show_password{ "aria-label" => t('.show_password.aria_label'), type: "checkbox" }/
|
||||
%label.fr--password__checkbox.fr-label{ for: "show_password" }= t('.show_password.label')
|
||||
%input{ id: show_password_id, "aria-label" => t('.show_password.aria_label'), type: "checkbox" }/
|
||||
%label.fr--password__checkbox.fr-label{ for: show_password_id }= t('.show_password.label')
|
||||
|
||||
- if email?
|
||||
.suspect-email.hidden{ data: { "email-input-target": 'ariaRegion'}, aria: { live: 'off' } }
|
||||
|
|
53
app/components/password_complexity_component.rb
Normal file
53
app/components/password_complexity_component.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
class PasswordComplexityComponent < ApplicationComponent
|
||||
def initialize(length: nil, min_length: nil, score: nil, min_complexity: nil)
|
||||
@length = length
|
||||
@min_length = min_length
|
||||
@score = score
|
||||
@min_complexity = min_complexity
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filled?
|
||||
!@length.nil? || !@score.nil?
|
||||
end
|
||||
|
||||
def alert_classes
|
||||
class_names(
|
||||
"fr-alert": true,
|
||||
"fr-alert--sm": true,
|
||||
"fr-alert--info": !success?,
|
||||
"fr-alert--success": success?
|
||||
)
|
||||
end
|
||||
|
||||
def success?
|
||||
return false if !filled?
|
||||
|
||||
@length >= @min_length && @score >= @min_complexity
|
||||
end
|
||||
|
||||
def complexity_classes
|
||||
[
|
||||
"password-complexity fr-mt-2w fr-mb-1w",
|
||||
filled? ? "complexity-#{@length < @min_length ? @score / 2 : @score}" : nil
|
||||
]
|
||||
end
|
||||
|
||||
def title
|
||||
return t(".title.empty") if !filled?
|
||||
|
||||
return t(".title.too_short", min_length: @min_length) if @length < @min_length
|
||||
|
||||
case @score
|
||||
when 0..1
|
||||
return t(".title.weakest")
|
||||
when 2...@min_complexity
|
||||
return t(".title.weak")
|
||||
when @min_complexity...4
|
||||
return t(".title.passable")
|
||||
else
|
||||
return t(".title.strong")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
en:
|
||||
title:
|
||||
empty: Enter a password.
|
||||
too_short: Password must be at least %{min_length} characters long.
|
||||
passable: Password is acceptable. You can validate… or improve your password.
|
||||
strong: Congratulations! Password is strong and secure enough.
|
||||
weak: Vulnerable password.
|
||||
weakest: Very vulnerable password.
|
||||
hint: A short sentence with punctuation can be a very secure password.
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
fr:
|
||||
title:
|
||||
empty: Inscrivez un mot de passe.
|
||||
too_short: Le mot de passe doit faire au moins %{min_length} caractères.
|
||||
passable: Mot de passe acceptable. Vous pouvez valider… ou améliorer votre mot de passe.
|
||||
strong: Félicitations ! Mot de passe suffisamment fort et sécurisé.
|
||||
weak: Mot de passe vulnérable.
|
||||
weakest: Mot de passe très vulnérable.
|
||||
hint: Une courte phrase avec ponctuation peut être un mot de passe très sécurisé.
|
|
@ -0,0 +1,6 @@
|
|||
%div{ class: complexity_classes }
|
||||
|
||||
%div{ class: alert_classes }
|
||||
%h3.fr-alert__title= title
|
||||
- if !success?
|
||||
%p= t(".hint")
|
|
@ -1,21 +1,26 @@
|
|||
- content_for(:title, "Choix du mot de passe")
|
||||
- content_for(:title, t('.title'))
|
||||
|
||||
- content_for :footer do
|
||||
= render partial: "root/footer"
|
||||
|
||||
.container.devise-container
|
||||
.one-column-centered
|
||||
= form_for @administrateur, url: { controller: 'administrateurs/activate', action: :create }, html: { class: "form" } do |f|
|
||||
%br
|
||||
%h1
|
||||
Choix du mot de passe
|
||||
.fr-container.fr-my-5w
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
.fr-col-lg-6
|
||||
= form_for @administrateur, url: { controller: 'administrateurs/activate', action: :create } do |f|
|
||||
= f.hidden_field :reset_password_token, value: @token
|
||||
|
||||
= f.hidden_field :reset_password_token, value: @token
|
||||
= f.label :email, "Email"
|
||||
= f.text_field :email, disabled: true
|
||||
%fieldset.fr-mb-0.fr-fieldset{ aria: { labelledby: 'edit-password-legend' } }
|
||||
%legend.fr-fieldset__legend#edit-password-legend
|
||||
%h1.fr-h2= t('.title')
|
||||
|
||||
= f.label :password do
|
||||
Mot de passe
|
||||
= render 'password_complexity/field', { form: f, test_complexity: true }
|
||||
.fr-fieldset__element
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :email, opts: { disabled: true })
|
||||
|
||||
= f.submit 'Continuer', class: 'button large primary expand', id: "submit-password", data: { disable_with: "Envoi..." }
|
||||
.fr-fieldset__element
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field,
|
||||
opts: { autofocus: 'true', autocomplete: 'new-password', data: { controller: 'turbo-input', turbo_input_url_value: show_password_complexity_path }})
|
||||
|
||||
#password_complexity
|
||||
= render PasswordComplexityComponent.new
|
||||
|
||||
= f.submit t('.continue'), id: 'submit-password', class: "fr-btn fr-btn--lg fr-mt-2w", data: { disable_with: t('views.users.passwords.edit.submit_loading') }
|
||||
|
|
3
app/views/devise/_password_rules.html.haml
Normal file
3
app/views/devise/_password_rules.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
|||
.fr-messages-group{ "aria-live" => "off", id: id }
|
||||
%p.fr-message= t('views.registrations.new.password_message')
|
||||
%p.fr-message.fr-message--info= t('views.registrations.new.password_placeholder', min_length: PASSWORD_MIN_LENGTH)
|
|
@ -3,20 +3,31 @@
|
|||
- content_for :footer do
|
||||
= render partial: 'root/footer'
|
||||
|
||||
.container.devise-container
|
||||
.one-column-centered
|
||||
= devise_error_messages!
|
||||
.fr-container.fr-my-5w
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
.fr-col-lg-6
|
||||
= devise_error_messages!
|
||||
|
||||
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :patch, class: 'form' }) do |f|
|
||||
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :patch, class: '' }) do |f|
|
||||
= f.hidden_field :reset_password_token
|
||||
|
||||
%h1 Changement de mot de passe
|
||||
|
||||
= f.hidden_field :reset_password_token
|
||||
%fieldset.fr-mb-0.fr-fieldset{ aria: { labelledby: 'edit-password-legend' } }
|
||||
%legend.fr-fieldset__legend#edit-password-legend
|
||||
%h1.fr-h2= I18n.t('views.users.passwords.edit.subtitle')
|
||||
|
||||
= f.label 'Nouveau mot de passe'
|
||||
= render 'password_complexity/field', { form: f, test_complexity: populated_resource.validate_password_complexity? }
|
||||
.fr-fieldset__element
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field,
|
||||
opts: { autofocus: 'true', autocomplete: 'new-password', minlength: PASSWORD_MIN_LENGTH, data: { controller: populated_resource.validate_password_complexity? ? 'turbo-input' : false, turbo_input_url_value: show_password_complexity_path }}) do |c|
|
||||
- c.describedby do
|
||||
- if populated_resource.validate_password_complexity?
|
||||
%div{ id: c.describedby_id }
|
||||
#password_complexity
|
||||
= render PasswordComplexityComponent.new
|
||||
- else
|
||||
= render partial: "devise/password_rules", locals: { id: c.describedby_id }
|
||||
|
||||
= f.label 'Confirmez le nouveau mot de passe'
|
||||
= f.password_field :password_confirmation, autocomplete: 'off'
|
||||
.fr-fieldset__element
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :password_confirmation, input_type: :password_field, opts: { autocomplete: 'new-password' })
|
||||
|
||||
= f.submit 'Changer le mot de passe', class: 'button large primary expand', id: "submit-password", data: { disable_with: "Envoi…" }
|
||||
= f.submit t('views.users.passwords.edit.submit'), id: 'submit-password', class: "fr-btn fr-btn--lg fr-mt-2w", data: { disable_with: t('views.users.passwords.edit.submit_loading') }
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
#complexity-bar.password-complexity{ class: "complexity-#{@length < @min_length ? @score/2 : @score}" }
|
|
@ -1,9 +0,0 @@
|
|||
= form.password_field :password, autofocus: true, autocomplete: 'off', placeholder: 'Mot de passe', data: { controller: test_complexity ? 'turbo-input' : false, turbo_input_url_value: show_password_complexity_path }
|
||||
|
||||
- if test_complexity
|
||||
#complexity-bar.password-complexity
|
||||
|
||||
.explication
|
||||
#complexity-label{ style: 'font-weight: bold' }
|
||||
Inscrivez un mot de passe.
|
||||
Une courte phrase avec ponctuation peut être un mot de passe très sécurisé.
|
|
@ -1,16 +0,0 @@
|
|||
#complexity-label{ style: 'font-weight: bold' }
|
||||
- if @length > 0
|
||||
- if @length < @min_length
|
||||
Le mot de passe doit faire au moins #{@min_length} caractères.
|
||||
- else
|
||||
- case @score
|
||||
- when 0..1
|
||||
Mot de passe très vulnérable.
|
||||
- when 2...@min_complexity
|
||||
Mot de passe vulnérable.
|
||||
- when @min_complexity...4
|
||||
Mot de passe acceptable. Vous pouvez valider...<br> ou améliorer votre mot de passe.
|
||||
- else
|
||||
Félicitations ! Mot de passe suffisamment fort et sécurisé.
|
||||
- else
|
||||
Inscrivez un mot de passe.
|
|
@ -1,5 +1,6 @@
|
|||
= turbo_stream.replace 'complexity-label', partial: 'label'
|
||||
= turbo_stream.replace 'complexity-bar', partial: 'bar'
|
||||
= turbo_stream.update 'password_complexity' do
|
||||
= render PasswordComplexityComponent.new(length: @length, min_length: @min_length, score: @score, min_complexity: @min_complexity)
|
||||
|
||||
- if @score < @min_complexity || @length < @min_length
|
||||
= turbo_stream.disable 'submit-password'
|
||||
- else
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
.fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :email, input_type: :email_field, opts: { autocomplete: 'email', autofocus: true })
|
||||
|
||||
.fr-fieldset__element
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, opts: { autocomplete: 'new-password', min_length: PASSWORD_MIN_LENGTH }) do |c|
|
||||
= render Dsfr::InputComponent.new(form: f, attribute: :password, input_type: :password_field, opts: { autocomplete: 'new-password', minlength: PASSWORD_MIN_LENGTH }) do |c|
|
||||
- c.describedby do
|
||||
#password-input-messages.fr-messages-group{ "aria-live" => "off" }
|
||||
%p#password-input-message.fr-message= t('views.registrations.new.password_message')
|
||||
%p#password-input-message-info.fr-message.fr-message--info= t('views.registrations.new.password_placeholder', min_length: PASSWORD_MIN_LENGTH)
|
||||
= render partial: "devise/password_rules", locals: { id: c.describedby_id }
|
||||
|
||||
= f.submit t('views.shared.account.create'), class: "fr-btn fr-btn--lg"
|
||||
|
|
|
@ -351,6 +351,10 @@ en:
|
|||
connect_with_agent_connect: Visit our dedicated page
|
||||
subtitle: "Sign in with my account"
|
||||
passwords:
|
||||
edit:
|
||||
subtitle: Change password
|
||||
submit: Change password
|
||||
submit_loading: Sending…
|
||||
reset_link_sent:
|
||||
got_it: Got it!
|
||||
open_your_mailbox: Now open your mailbox.
|
||||
|
@ -576,6 +580,11 @@ en:
|
|||
deleted:
|
||||
one: Deleted
|
||||
other: Deleted
|
||||
administrateurs:
|
||||
activate:
|
||||
new:
|
||||
title: Pick a password
|
||||
continue: Continue
|
||||
users:
|
||||
dossiers:
|
||||
test_procedure: "This file is submitted on a test procedure. Any modification of the procedure by the administrator (addition of a field, publication of the procedure, etc.) will result in the removal of the file."
|
||||
|
|
|
@ -347,6 +347,10 @@ fr:
|
|||
connect_with_agent_connect: Accédez à notre page dédiée
|
||||
subtitle: "Se connecter avec son compte"
|
||||
passwords:
|
||||
edit:
|
||||
subtitle: Changement de mot de passe
|
||||
submit: Changer le mot de passe
|
||||
submit_loading: Envoi…
|
||||
reset_link_sent:
|
||||
email_sent_html: "Nous vous avons envoyé un email à l’adresse <strong>%{email}</strong>."
|
||||
click_link_to_reset_password: "Cliquez sur le lien contenu dans l’email pour changer votre mot de passe."
|
||||
|
@ -625,6 +629,10 @@ fr:
|
|||
to_follow: à suivre
|
||||
total: dossiers
|
||||
administrateurs:
|
||||
activate:
|
||||
new:
|
||||
title: Choix du mot de passe
|
||||
continue: Continuer
|
||||
index:
|
||||
restored: La démarche %{procedure_id} a été restaurée
|
||||
dropdown_actions:
|
||||
|
|
|
@ -27,8 +27,7 @@ describe PasswordComplexityController, type: :controller do
|
|||
|
||||
it 'renders Javascript that updates the password complexity meter' do
|
||||
subject
|
||||
expect(response.body).to include('complexity-label')
|
||||
expect(response.body).to include('complexity-bar')
|
||||
expect(response.body).to include('Mot de passe vulnérable')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue