app: add a password_complexity component
This component will replace the previous `password_field` component.
This commit is contained in:
parent
586f8ec543
commit
428ca8755f
10 changed files with 172 additions and 0 deletions
40
app/assets/stylesheets/password_complexity.scss
Normal file
40
app/assets/stylesheets/password_complexity.scss
Normal file
|
@ -0,0 +1,40 @@
|
|||
@import "colors";
|
||||
@import "constants";
|
||||
|
||||
$complexity-bg: #EEEEEE;
|
||||
$complexity-color-0: $lighter-red;
|
||||
$complexity-color-1: #FF5000;
|
||||
$complexity-color-2: $orange;
|
||||
$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;
|
||||
|
||||
&.complexity-0 {
|
||||
background: linear-gradient(to right, $complexity-color-0 00%, $complexity-bg 20%);
|
||||
}
|
||||
|
||||
&.complexity-1 {
|
||||
background: linear-gradient(to right, $complexity-color-1 20%, $complexity-bg 40%);
|
||||
}
|
||||
|
||||
&.complexity-2 {
|
||||
background: linear-gradient(to right, $complexity-color-2 40%, $complexity-bg 60%);
|
||||
}
|
||||
|
||||
&.complexity-3 {
|
||||
background: linear-gradient(to right, $complexity-color-3 60%, $complexity-bg 80%);
|
||||
}
|
||||
|
||||
&.complexity-4 {
|
||||
background: $complexity-color-4;
|
||||
}
|
||||
}
|
13
app/controllers/concerns/devise_populated_resource.rb
Normal file
13
app/controllers/concerns/devise_populated_resource.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
module DevisePopulatedResource
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# During a GET /password/edit, the resource is a brand new object.
|
||||
# This method gives access to the actual resource record, complete with email, relationships, etc.
|
||||
def populated_resource
|
||||
resource_class.with_reset_password_token(resource.reset_password_token)
|
||||
end
|
||||
|
||||
included do
|
||||
helper_method :populated_resource
|
||||
end
|
||||
end
|
15
app/controllers/password_complexity_controller.rb
Normal file
15
app/controllers/password_complexity_controller.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class PasswordComplexityController < ApplicationController
|
||||
def show
|
||||
@score, @words, @length = ZxcvbnService.new(password_param).complexity
|
||||
@min_length = PASSWORD_MIN_LENGTH
|
||||
@min_complexity = PASSWORD_COMPLEXITY_FOR_ADMIN
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def password_param
|
||||
params
|
||||
.transform_keys! { |k| params[k].try(:has_key?, :password) ? 'resource' : k }
|
||||
.dig(:resource, :password)
|
||||
end
|
||||
end
|
1
app/views/password_complexity/_bar.html.haml
Normal file
1
app/views/password_complexity/_bar.html.haml
Normal file
|
@ -0,0 +1 @@
|
|||
#complexity-bar.password-complexity{ class: "complexity-#{@length < @min_length ? @score/2 : @score}" }
|
9
app/views/password_complexity/_field.html.haml
Normal file
9
app/views/password_complexity/_field.html.haml
Normal file
|
@ -0,0 +1,9 @@
|
|||
= form.password_field :password, autofocus: true, autocomplete: 'off', placeholder: 'Mot de passe', data: { remote: test_complexity, url: 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é.
|
16
app/views/password_complexity/_label.html.haml
Normal file
16
app/views/password_complexity/_label.html.haml
Normal file
|
@ -0,0 +1,16 @@
|
|||
#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.
|
3
app/views/password_complexity/show.js.erb
Normal file
3
app/views/password_complexity/show.js.erb
Normal file
|
@ -0,0 +1,3 @@
|
|||
<%= render_to_element('#complexity-label', partial: 'label', outer: true) %>
|
||||
<%= render_to_element('#complexity-bar', partial: 'bar', outer: true) %>
|
||||
<%= raw("document.querySelector('#submit-password').disabled = #{@score < @min_complexity || @length < @min_length};") %>
|
|
@ -114,6 +114,8 @@ Rails.application.routes.draw do
|
|||
get '/administrateurs/password/test_strength' => 'administrateurs/passwords#test_strength'
|
||||
end
|
||||
|
||||
get 'password_complexity' => 'password_complexity#show', as: 'show_password_complexity'
|
||||
|
||||
#
|
||||
# Main routes
|
||||
#
|
||||
|
|
38
spec/controllers/concerns/devise_populated_resource_spec.rb
Normal file
38
spec/controllers/concerns/devise_populated_resource_spec.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
describe DevisePopulatedResource, type: :controller do
|
||||
controller(Devise::PasswordsController) do
|
||||
include DevisePopulatedResource
|
||||
end
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
routes.draw do
|
||||
get 'edit' => 'devise/passwords#edit'
|
||||
put 'update' => 'devise/passwords#update'
|
||||
end
|
||||
|
||||
@request.env["devise.mapping"] = Devise.mappings[:user]
|
||||
|
||||
@token = user.send_reset_password_instructions
|
||||
end
|
||||
|
||||
context 'when initiating a password reset' do
|
||||
subject { get :edit, params: { reset_password_token: @token } }
|
||||
|
||||
it 'returns the fully populated resource' do
|
||||
subject
|
||||
expect(controller.populated_resource.id).to eq(user.id)
|
||||
expect(controller.populated_resource.email).to eq(user.email)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when submitting a password reset' do
|
||||
subject { put :update, params: { user: { reset_password_token: @token } } }
|
||||
|
||||
it 'returns the fully populated resource' do
|
||||
subject
|
||||
expect(controller.populated_resource.id).to eq(user.id)
|
||||
expect(controller.populated_resource.email).to eq(user.email)
|
||||
end
|
||||
end
|
||||
end
|
35
spec/controllers/password_complexity_controller_spec.rb
Normal file
35
spec/controllers/password_complexity_controller_spec.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
describe PasswordComplexityController, type: :controller do
|
||||
describe '#show' do
|
||||
let(:params) do
|
||||
{ user: { password: 'moderately complex password' } }
|
||||
end
|
||||
|
||||
subject { get :show, format: :js, params: params, xhr: true }
|
||||
|
||||
it 'computes a password score' do
|
||||
subject
|
||||
expect(assigns(:score)).to eq(3)
|
||||
end
|
||||
|
||||
context 'with a different resource name' do
|
||||
let(:params) do
|
||||
{ super_admin: { password: 'moderately complex password' } }
|
||||
end
|
||||
|
||||
it 'computes a password score' do
|
||||
subject
|
||||
expect(assigns(:score)).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when rendering the view' do
|
||||
render_views
|
||||
|
||||
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')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue