stimulus-password-strength 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +41 -0
- data/AGENTS.md +71 -0
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +11 -0
- data/LICENSE.txt +21 -0
- data/PUBLISH_CHECKLIST.md +82 -0
- data/README.md +182 -0
- data/Rakefile +8 -0
- data/app/javascript/stimulus_password_strength_controller.js +193 -0
- data/app/views/stimulus_password_strength/_field.html.erb +114 -0
- data/config/importmap.rb +2 -0
- data/config/locales/en.yml +8 -0
- data/config/locales/pl.yml +8 -0
- data/lib/generators/stimulus_password_strength/install/install_generator.rb +55 -0
- data/lib/generators/stimulus_password_strength/install/templates/password_policy.rb.tt +20 -0
- data/lib/generators/stimulus_password_strength/install/templates/stimulus_password_strength.rb.tt +19 -0
- data/lib/stimulus/password/strength.rb +3 -0
- data/lib/stimulus_password_strength/configuration.rb +63 -0
- data/lib/stimulus_password_strength/engine.rb +24 -0
- data/lib/stimulus_password_strength/helper.rb +121 -0
- data/lib/stimulus_password_strength/version.rb +5 -0
- data/lib/stimulus_password_strength.rb +18 -0
- data/vendor/javascript/zxcvbn.js +4 -0
- metadata +88 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<div class="<%= container_class %>"
|
|
2
|
+
data-controller="password-strength"
|
|
3
|
+
data-password-strength-weak-label="<%= labels[:weak] %>"
|
|
4
|
+
data-password-strength-fair-label="<%= labels[:fair] %>"
|
|
5
|
+
data-password-strength-good-label="<%= labels[:good] %>"
|
|
6
|
+
data-password-strength-strong-label="<%= labels[:strong] %>"
|
|
7
|
+
data-password-strength-bar-base-class="<%= bar_base_class %>"
|
|
8
|
+
data-password-strength-text-base-class="<%= text_base_class %>"
|
|
9
|
+
data-password-strength-weak-bar-color="<%= bar_colors[:weak] %>"
|
|
10
|
+
data-password-strength-fair-bar-color="<%= bar_colors[:fair] %>"
|
|
11
|
+
data-password-strength-good-bar-color="<%= bar_colors[:good] %>"
|
|
12
|
+
data-password-strength-strong-bar-color="<%= bar_colors[:strong] %>"
|
|
13
|
+
data-password-strength-weak-text-color="<%= text_colors[:weak] %>"
|
|
14
|
+
data-password-strength-fair-text-color="<%= text_colors[:fair] %>"
|
|
15
|
+
data-password-strength-good-text-color="<%= text_colors[:good] %>"
|
|
16
|
+
data-password-strength-strong-text-color="<%= text_colors[:strong] %>">
|
|
17
|
+
<% if label.present? %>
|
|
18
|
+
<div class="<%= label_row_class %>">
|
|
19
|
+
<%= form.label attribute, label, class: label_class %>
|
|
20
|
+
|
|
21
|
+
<div class="<%= header_aux_class %>">
|
|
22
|
+
<% if requirements.any? %>
|
|
23
|
+
<div class="<%= requirements_class %>" style="<%= requirements_style %>">
|
|
24
|
+
<% requirements.each do |requirement| %>
|
|
25
|
+
<p data-password-strength-target="requirement"
|
|
26
|
+
data-rule="<%= requirement[:rule] %>"
|
|
27
|
+
data-value="<%= requirement[:value] %>"
|
|
28
|
+
data-label="<%= requirement[:label] %>"
|
|
29
|
+
data-remaining-singular="<%= requirement[:remaining_singular] %>"
|
|
30
|
+
data-remaining-plural="<%= requirement[:remaining_plural] %>"
|
|
31
|
+
data-met-label="<%= requirement[:met_label] %>"
|
|
32
|
+
data-pending-style="<%= requirement_pending_style %>"
|
|
33
|
+
data-met-style="<%= requirement_met_style %>"
|
|
34
|
+
data-unmet-style="<%= requirement_unmet_style %>"
|
|
35
|
+
class="<%= requirement_class %>"
|
|
36
|
+
style="<%= requirement_pending_style %>"><%= requirement[:label] %></p>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
39
|
+
<% end %>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div data-password-strength-target="statusRow" class="<%= status_row_class %>" style="<%= status_row_style %>; visibility: hidden;">
|
|
44
|
+
<div data-password-strength-target="strengthTrack" class="<%= bar_track_class %>" style="visibility: hidden;">
|
|
45
|
+
<div data-password-strength-target="strengthBar" class="<%= bar_base_class %>" style="width: 0%"></div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<p data-password-strength-target="strengthText" class="<%= text_base_class %>" style="<%= text_style %>" aria-live="polite"></p>
|
|
49
|
+
</div>
|
|
50
|
+
<% else %>
|
|
51
|
+
<% if requirements.any? %>
|
|
52
|
+
<div class="<%= requirements_class %>" style="<%= requirements_style %>">
|
|
53
|
+
<% requirements.each do |requirement| %>
|
|
54
|
+
<p data-password-strength-target="requirement"
|
|
55
|
+
data-rule="<%= requirement[:rule] %>"
|
|
56
|
+
data-value="<%= requirement[:value] %>"
|
|
57
|
+
data-label="<%= requirement[:label] %>"
|
|
58
|
+
data-remaining-singular="<%= requirement[:remaining_singular] %>"
|
|
59
|
+
data-remaining-plural="<%= requirement[:remaining_plural] %>"
|
|
60
|
+
data-met-label="<%= requirement[:met_label] %>"
|
|
61
|
+
data-pending-style="<%= requirement_pending_style %>"
|
|
62
|
+
data-met-style="<%= requirement_met_style %>"
|
|
63
|
+
data-unmet-style="<%= requirement_unmet_style %>"
|
|
64
|
+
class="<%= requirement_class %>"
|
|
65
|
+
style="<%= requirement_pending_style %>"><%= requirement[:label] %></p>
|
|
66
|
+
<% end %>
|
|
67
|
+
</div>
|
|
68
|
+
<% end %>
|
|
69
|
+
|
|
70
|
+
<div data-password-strength-target="statusRow" class="<%= status_row_class %>" style="<%= status_row_style %>; visibility: hidden;">
|
|
71
|
+
<div data-password-strength-target="strengthTrack" class="<%= bar_track_class %>" style="visibility: hidden;">
|
|
72
|
+
<div data-password-strength-target="strengthBar" class="<%= bar_base_class %>" style="width: 0%"></div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<p data-password-strength-target="strengthText" class="<%= text_base_class %>" style="<%= text_style %>" aria-live="polite"></p>
|
|
76
|
+
</div>
|
|
77
|
+
<% end %>
|
|
78
|
+
|
|
79
|
+
<div class="relative">
|
|
80
|
+
<%= form.password_field attribute,
|
|
81
|
+
required: required,
|
|
82
|
+
autocomplete: autocomplete,
|
|
83
|
+
placeholder: placeholder,
|
|
84
|
+
class: input_class,
|
|
85
|
+
data: {
|
|
86
|
+
password_strength_target: "input",
|
|
87
|
+
action: "input->password-strength#evaluate"
|
|
88
|
+
} %>
|
|
89
|
+
|
|
90
|
+
<button type="button"
|
|
91
|
+
data-password-strength-target="toggle"
|
|
92
|
+
aria-label="<%= toggle_labels[:show] %>"
|
|
93
|
+
title="<%= toggle_labels[:show] %>"
|
|
94
|
+
data-show-label="<%= toggle_labels[:show] %>"
|
|
95
|
+
data-hide-label="<%= toggle_labels[:hide] %>"
|
|
96
|
+
data-action="click->password-strength#toggle"
|
|
97
|
+
class="<%= toggle_class %>">
|
|
98
|
+
<svg data-password-strength-target="showIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" style="width: 1rem; height: 1rem;" aria-hidden="true">
|
|
99
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.27 2.943 9.542 7-1.273 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7Z" />
|
|
100
|
+
<circle cx="12" cy="12" r="3" />
|
|
101
|
+
</svg>
|
|
102
|
+
<svg data-password-strength-target="hideIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" style="width: 1rem; height: 1rem;" aria-hidden="true" hidden>
|
|
103
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3 3l18 18" />
|
|
104
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M10.584 10.587A2 2 0 0 0 13.414 13.4" />
|
|
105
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9.364 5.365A10.45 10.45 0 0 1 12 5c4.478 0 8.27 2.943 9.542 7a10.46 10.46 0 0 1-4.132 5.411" />
|
|
106
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6.657 6.658C4.798 7.936 3.423 9.79 2.458 12c1.273 4.057 5.064 7 9.542 7 1.536 0 2.992-.346 4.293-.965" />
|
|
107
|
+
</svg>
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<% if hint.present? %>
|
|
112
|
+
<p class="<%= hint_class %>"><%= hint %></p>
|
|
113
|
+
<% end %>
|
|
114
|
+
</div>
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPasswordStrength
|
|
4
|
+
module Generators
|
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
|
7
|
+
|
|
8
|
+
def add_importmap_pins
|
|
9
|
+
importmap_path = "config/importmap.rb"
|
|
10
|
+
return unless destination_file?(importmap_path)
|
|
11
|
+
|
|
12
|
+
add_pin(importmap_path, 'pin "stimulus-password-strength", to: "stimulus_password_strength_controller.js", preload: false')
|
|
13
|
+
add_pin(importmap_path, 'pin "zxcvbn", to: "zxcvbn.js", preload: false')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def register_stimulus_controller
|
|
17
|
+
index_path = "app/javascript/controllers/index.js"
|
|
18
|
+
return unless destination_file?(index_path)
|
|
19
|
+
|
|
20
|
+
content = read_destination(index_path)
|
|
21
|
+
import_line = 'import PasswordStrengthController from "stimulus-password-strength"'
|
|
22
|
+
register_line = 'application.register("password-strength", PasswordStrengthController)'
|
|
23
|
+
|
|
24
|
+
append_to_file(index_path, "\n#{import_line}\n") unless content.include?(import_line)
|
|
25
|
+
append_to_file(index_path, "#{register_line}\n") unless read_destination(index_path).include?(register_line)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_initializer
|
|
29
|
+
template "stimulus_password_strength.rb.tt", "config/initializers/stimulus_password_strength.rb"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create_password_policy
|
|
33
|
+
policy_path = "app/lib/password_policy.rb"
|
|
34
|
+
return if destination_file?(policy_path)
|
|
35
|
+
|
|
36
|
+
template "password_policy.rb.tt", policy_path
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def add_pin(path, line)
|
|
42
|
+
content = read_destination(path)
|
|
43
|
+
append_to_file(path, "#{line}\n") unless content.include?(line)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def destination_file?(path)
|
|
47
|
+
File.exist?(File.join(destination_root, path))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def read_destination(path)
|
|
51
|
+
File.read(File.join(destination_root, path))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PasswordPolicy
|
|
4
|
+
MIN_LENGTH = 12
|
|
5
|
+
|
|
6
|
+
REQUIREMENTS = [
|
|
7
|
+
{
|
|
8
|
+
rule: :min_length,
|
|
9
|
+
value: MIN_LENGTH,
|
|
10
|
+
label: "At least #{MIN_LENGTH} characters",
|
|
11
|
+
remaining_singular: "Type 1 more character",
|
|
12
|
+
remaining_plural: "Type %{count} more characters",
|
|
13
|
+
met_label: "#{MIN_LENGTH}+ chars"
|
|
14
|
+
}
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
def self.requirements
|
|
18
|
+
REQUIREMENTS
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/generators/stimulus_password_strength/install/templates/stimulus_password_strength.rb.tt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
StimulusPasswordStrength.configure do |config|
|
|
4
|
+
# Example customization:
|
|
5
|
+
# config.input_class = "w-full rounded-md border px-3 py-2 pr-16"
|
|
6
|
+
# config.text_style = "min-width: 2.5rem; text-align: right; white-space: nowrap;"
|
|
7
|
+
# config.status_row_class = "flex min-h-5 items-center gap-2"
|
|
8
|
+
# config.status_row_style = "min-height: 1rem;"
|
|
9
|
+
# config.requirements_style = "min-height: 1rem;"
|
|
10
|
+
# config.requirement_pending_style = "color: #6b7280;"
|
|
11
|
+
# config.requirement_met_style = "color: #047857;"
|
|
12
|
+
# config.requirement_unmet_style = "color: #b91c1c;"
|
|
13
|
+
# config.bar_colors = {
|
|
14
|
+
# weak: "#f87171",
|
|
15
|
+
# fair: "#fbbf24",
|
|
16
|
+
# good: "#22c55e",
|
|
17
|
+
# strong: "#059669"
|
|
18
|
+
# }
|
|
19
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPasswordStrength
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :container_class,
|
|
6
|
+
:label_row_class,
|
|
7
|
+
:label_class,
|
|
8
|
+
:header_aux_class,
|
|
9
|
+
:status_row_class,
|
|
10
|
+
:status_row_style,
|
|
11
|
+
:requirements_class,
|
|
12
|
+
:requirements_style,
|
|
13
|
+
:requirement_class,
|
|
14
|
+
:input_class,
|
|
15
|
+
:toggle_class,
|
|
16
|
+
:bar_track_class,
|
|
17
|
+
:bar_base_class,
|
|
18
|
+
:text_base_class,
|
|
19
|
+
:text_style,
|
|
20
|
+
:hint_class,
|
|
21
|
+
:requirement_pending_style,
|
|
22
|
+
:requirement_met_style,
|
|
23
|
+
:requirement_unmet_style,
|
|
24
|
+
:bar_colors,
|
|
25
|
+
:text_colors
|
|
26
|
+
|
|
27
|
+
def initialize
|
|
28
|
+
@container_class = "space-y-1"
|
|
29
|
+
@label_row_class = "flex items-end justify-between gap-3"
|
|
30
|
+
@label_class = "block text-sm font-medium text-gray-700"
|
|
31
|
+
@header_aux_class = "flex justify-end"
|
|
32
|
+
@status_row_class = "flex min-h-5 items-center gap-2"
|
|
33
|
+
@status_row_style = "min-height: 1rem;"
|
|
34
|
+
@requirements_class = "flex justify-end gap-2"
|
|
35
|
+
@requirements_style = "min-height: 1rem;"
|
|
36
|
+
@requirement_class = "text-xs text-right leading-tight"
|
|
37
|
+
@input_class = "w-full rounded-xl border border-gray-300 px-4 py-3 pr-16 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
|
38
|
+
@toggle_class = "absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer text-xs font-medium text-gray-500 hover:text-gray-700"
|
|
39
|
+
@bar_track_class = "h-1.5 w-20 overflow-hidden rounded-full bg-gray-100"
|
|
40
|
+
@bar_base_class = "h-full rounded-full transition-all duration-300"
|
|
41
|
+
@text_base_class = "text-xs"
|
|
42
|
+
@text_style = "min-width: 2.5rem; text-align: right; white-space: nowrap;"
|
|
43
|
+
@hint_class = "mt-1 text-xs text-gray-500"
|
|
44
|
+
@requirement_pending_style = "color: #6b7280;"
|
|
45
|
+
@requirement_met_style = "color: #047857;"
|
|
46
|
+
@requirement_unmet_style = "color: #b91c1c;"
|
|
47
|
+
|
|
48
|
+
@bar_colors = {
|
|
49
|
+
weak: "#f87171",
|
|
50
|
+
fair: "#fbbf24",
|
|
51
|
+
good: "#22c55e",
|
|
52
|
+
strong: "#059669"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@text_colors = {
|
|
56
|
+
weak: "#ef4444",
|
|
57
|
+
fair: "#d97706",
|
|
58
|
+
good: "#16a34a",
|
|
59
|
+
strong: "#047857"
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPasswordStrength
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
initializer "stimulus_password_strength.assets" do |app|
|
|
6
|
+
next unless app.config.respond_to?(:assets)
|
|
7
|
+
|
|
8
|
+
app.config.assets.paths << root.join("app/javascript")
|
|
9
|
+
app.config.assets.paths << root.join("vendor/javascript")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
initializer "stimulus_password_strength.importmap", before: "importmap" do |app|
|
|
13
|
+
next unless app.config.respond_to?(:importmap)
|
|
14
|
+
|
|
15
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
initializer "stimulus_password_strength.helpers" do
|
|
19
|
+
ActiveSupport.on_load(:action_view) do
|
|
20
|
+
include StimulusPasswordStrength::Helper
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StimulusPasswordStrength
|
|
4
|
+
module Helper
|
|
5
|
+
def password_strength_field(form, attribute, **options)
|
|
6
|
+
config = StimulusPasswordStrength.configuration
|
|
7
|
+
|
|
8
|
+
placeholder = options.delete(:placeholder)
|
|
9
|
+
hint = options.delete(:hint)
|
|
10
|
+
label = options.delete(:label)
|
|
11
|
+
required = options.delete(:required)
|
|
12
|
+
autocomplete = options.delete(:autocomplete) || "new-password"
|
|
13
|
+
requirements = normalize_requirements(options.delete(:requirements) || [])
|
|
14
|
+
input_class = options.delete(:input_class) || config.input_class
|
|
15
|
+
container_class = options.delete(:container_class) || config.container_class
|
|
16
|
+
label_row_class = options.delete(:label_row_class) || config.label_row_class
|
|
17
|
+
label_class = options.delete(:label_class) || config.label_class
|
|
18
|
+
header_aux_class = options.delete(:header_aux_class) || config.header_aux_class
|
|
19
|
+
status_row_class = options.delete(:status_row_class) || config.status_row_class
|
|
20
|
+
status_row_style = options.delete(:status_row_style) || config.status_row_style
|
|
21
|
+
requirements_class = options.delete(:requirements_class) || config.requirements_class
|
|
22
|
+
requirements_style = options.delete(:requirements_style) || config.requirements_style
|
|
23
|
+
requirement_class = options.delete(:requirement_class) || config.requirement_class
|
|
24
|
+
toggle_class = options.delete(:toggle_class) || config.toggle_class
|
|
25
|
+
bar_track_class = options.delete(:bar_track_class) || config.bar_track_class
|
|
26
|
+
bar_base_class = options.delete(:bar_base_class) || config.bar_base_class
|
|
27
|
+
text_base_class = options.delete(:text_base_class) || config.text_base_class
|
|
28
|
+
text_style = options.delete(:text_style) || config.text_style
|
|
29
|
+
hint_class = options.delete(:hint_class) || config.hint_class
|
|
30
|
+
|
|
31
|
+
labels = i18n_labels(options.delete(:strength_labels) || {})
|
|
32
|
+
toggle_labels = i18n_toggle_labels(options.delete(:toggle_labels) || {})
|
|
33
|
+
|
|
34
|
+
render(
|
|
35
|
+
"stimulus_password_strength/field",
|
|
36
|
+
form: form,
|
|
37
|
+
attribute: attribute,
|
|
38
|
+
label: label,
|
|
39
|
+
placeholder: placeholder,
|
|
40
|
+
hint: hint,
|
|
41
|
+
required: required,
|
|
42
|
+
autocomplete: autocomplete,
|
|
43
|
+
requirements: requirements,
|
|
44
|
+
input_class: input_class,
|
|
45
|
+
container_class: container_class,
|
|
46
|
+
label_row_class: label_row_class,
|
|
47
|
+
label_class: label_class,
|
|
48
|
+
header_aux_class: header_aux_class,
|
|
49
|
+
status_row_class: status_row_class,
|
|
50
|
+
status_row_style: status_row_style,
|
|
51
|
+
requirements_class: requirements_class,
|
|
52
|
+
requirements_style: requirements_style,
|
|
53
|
+
requirement_class: requirement_class,
|
|
54
|
+
toggle_class: toggle_class,
|
|
55
|
+
bar_track_class: bar_track_class,
|
|
56
|
+
bar_base_class: bar_base_class,
|
|
57
|
+
text_base_class: text_base_class,
|
|
58
|
+
text_style: text_style,
|
|
59
|
+
hint_class: hint_class,
|
|
60
|
+
requirement_pending_style: config.requirement_pending_style,
|
|
61
|
+
requirement_met_style: config.requirement_met_style,
|
|
62
|
+
requirement_unmet_style: config.requirement_unmet_style,
|
|
63
|
+
labels: labels,
|
|
64
|
+
toggle_labels: toggle_labels,
|
|
65
|
+
bar_colors: config.bar_colors,
|
|
66
|
+
text_colors: config.text_colors
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def i18n_labels(override_labels)
|
|
73
|
+
{
|
|
74
|
+
weak: override_labels[:weak] || I18n.t("stimulus_password_strength.weak", default: "Weak"),
|
|
75
|
+
fair: override_labels[:fair] || I18n.t("stimulus_password_strength.fair", default: "Fair"),
|
|
76
|
+
good: override_labels[:good] || I18n.t("stimulus_password_strength.good", default: "Good"),
|
|
77
|
+
strong: override_labels[:strong] || I18n.t("stimulus_password_strength.strong", default: "Strong")
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def i18n_toggle_labels(override_labels)
|
|
82
|
+
{
|
|
83
|
+
show: override_labels[:show] || I18n.t("stimulus_password_strength.show", default: "Show"),
|
|
84
|
+
hide: override_labels[:hide] || I18n.t("stimulus_password_strength.hide", default: "Hide")
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def normalize_requirements(raw_requirements)
|
|
89
|
+
raw_requirements.map do |requirement|
|
|
90
|
+
item = requirement.to_h.transform_keys(&:to_sym)
|
|
91
|
+
rule = item.fetch(:rule).to_sym
|
|
92
|
+
label = item.fetch(:label).to_s
|
|
93
|
+
|
|
94
|
+
raise ArgumentError, "Requirement label must be present" if label.strip.empty?
|
|
95
|
+
|
|
96
|
+
case rule
|
|
97
|
+
when :min_length
|
|
98
|
+
{
|
|
99
|
+
rule: rule.to_s,
|
|
100
|
+
value: Integer(item.fetch(:value)),
|
|
101
|
+
label: label,
|
|
102
|
+
remaining_singular: item[:remaining_singular].to_s,
|
|
103
|
+
remaining_plural: item[:remaining_plural].to_s,
|
|
104
|
+
met_label: item[:met_label].to_s
|
|
105
|
+
}
|
|
106
|
+
when :uppercase
|
|
107
|
+
{
|
|
108
|
+
rule: rule.to_s,
|
|
109
|
+
value: "",
|
|
110
|
+
label: label,
|
|
111
|
+
remaining_singular: "",
|
|
112
|
+
remaining_plural: "",
|
|
113
|
+
met_label: item[:met_label].to_s
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
raise ArgumentError, "Unsupported requirement rule: #{rule}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "stimulus_password_strength/version"
|
|
4
|
+
require_relative "stimulus_password_strength/configuration"
|
|
5
|
+
require_relative "stimulus_password_strength/helper"
|
|
6
|
+
require_relative "stimulus_password_strength/engine" if defined?(Rails)
|
|
7
|
+
|
|
8
|
+
module StimulusPasswordStrength
|
|
9
|
+
class << self
|
|
10
|
+
def configuration
|
|
11
|
+
@configuration ||= Configuration.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def configure
|
|
15
|
+
yield(configuration)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|