Merge pull request #443 from firezone/backlog/261/password_strength_requirements

Increase password length requirements to 12
This commit is contained in:
Jamil
2022-02-07 16:46:11 -08:00
committed by GitHub
23 changed files with 166 additions and 81 deletions

View File

@@ -1,5 +1,5 @@
import hljs from "highlight.js"
import {FormatTimestamp} from "./util.js"
import {FormatTimestamp,PasswordStrength} from "./util.js"
import {renderQrCode} from "./qrcode.js"
const highlightCode = function () {
@@ -11,6 +11,46 @@ const formatTimestamp = function () {
this.el.innerHTML = FormatTimestamp(t)
}
const passwordStrength = function () {
const field = this.el
const fieldClasses = "password input "
const progress = document.getElementById(field.dataset.target)
const reset = function () {
field.className = fieldClasses
progress.className = "is-hidden"
progress.setAttribute("value", "0")
progress.innerHTML = "0%"
}
field.addEventListener("input", () => {
if (field.value === "") return reset()
const score = PasswordStrength(field.value)
switch (score) {
case 0:
case 1:
field.className = fieldClasses + "is-danger"
progress.className = "progress is-small is-danger"
progress.setAttribute("value", "33")
progress.innerHTML = "33%"
break
case 2:
case 3:
field.className = fieldClasses + "is-warning"
progress.className = "progress is-small is-warning"
progress.setAttribute("value", "67")
progress.innerHTML = "67%"
break
case 4:
field.className = fieldClasses + "is-success"
progress.className = "progress is-small is-success"
progress.setAttribute("value", "100")
progress.innerHTML = "100%"
break
default:
reset()
}
})
}
const clipboardCopy = function () {
let button = this.el
let data = button.dataset.clipboard
@@ -37,5 +77,9 @@ Hooks.FormatTimestamp = {
mounted: formatTimestamp,
updated: formatTimestamp
}
Hooks.PasswordStrength = {
mounted: passwordStrength,
updated: passwordStrength
}
export default Hooks

View File

@@ -1,4 +1,5 @@
import moment from "moment"
import zxcvbn from "zxcvbn"
const FormatTimestamp = function (timestamp) {
if (timestamp) {
@@ -8,4 +9,9 @@ const FormatTimestamp = function (timestamp) {
}
}
export { FormatTimestamp }
const PasswordStrength = function (password) {
const result = zxcvbn(password)
return result.score
}
export { PasswordStrength, FormatTimestamp }

View File

@@ -43,7 +43,8 @@
"source-map": "^0.7.3",
"url-loader": "^4.1.1",
"webpack": "5.36.0",
"webpack-cli": "^4.9.2"
"webpack-cli": "^4.9.2",
"zxcvbn": "^4.4.2"
},
"engines": {
"node": ">= 14.0.0"
@@ -8061,6 +8062,12 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zxcvbn": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
"integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA=",
"dev": true
}
},
"dependencies": {
@@ -13904,6 +13911,12 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true
},
"zxcvbn": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
"integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA=",
"dev": true
}
}
}

View File

@@ -48,6 +48,7 @@
"source-map": "^0.7.3",
"url-loader": "^4.1.1",
"webpack": "5.36.0",
"webpack-cli": "^4.9.2"
"webpack-cli": "^4.9.2",
"zxcvbn": "^4.4.2"
}
}

View File

@@ -3,7 +3,7 @@ defmodule FzHttp.Users.User do
Represents a User.
"""
@min_password_length 8
@min_password_length 12
@max_password_length 64
use Ecto.Schema
@@ -173,4 +173,6 @@ defmodule FzHttp.Users.User do
{:error, error_msg} -> changeset |> add_error(:current_password, error_msg)
end
end
defp verify_current_password(changeset, _user), do: changeset
end

View File

@@ -29,6 +29,19 @@ defmodule FzHttpWeb.ErrorHelpers do
end)
end
@doc """
Adds "is-danger" to input elements that have errors
"""
def input_error_class(form, field) do
case Keyword.get_values(form.errors, field) do
[] ->
""
_ ->
"is-danger"
end
end
@doc """
Translates an error message using gettext.
"""

View File

@@ -3,7 +3,7 @@
<div class="field">
<%= label f, :name, class: "label" %>
<div class="control">
<%= text_input f, :name, class: "input" %>
<%= text_input f, :name, class: "input #{input_error_class(f, :name)}" %>
</div>
<p class="help is-danger">
<%= error_tag f, :name %>
@@ -30,7 +30,8 @@
<div class="field">
<%= label f, :allowed_ips, "Allowed IPs", class: "label" %>
<div class="control">
<%= text_input f, :allowed_ips, class: "input", disabled: @use_default_allowed_ips %>
<%= text_input f, :allowed_ips, class: "input #{input_error_class(f, :allowed_ips)}",
disabled: @use_default_allowed_ips %>
</div>
<p class="help is-danger">
<%= error_tag f, :allowed_ips %>
@@ -57,7 +58,8 @@
<div class="field">
<%= label f, :dns, "DNS Servers", class: "label" %>
<div class="control">
<%= text_input f, :dns, class: "input", disabled: @use_default_dns %>
<%= text_input f, :dns, class: "input #{input_error_class(f, :dns)}",
disabled: @use_default_dns %>
</div>
<p class="help is-danger">
<%= error_tag f, :dns %>
@@ -85,7 +87,8 @@
<%= label f, :endpoint, "Server Endpoint", class: "label" %>
<p>The IP of the server this device should connect to.</p>
<div class="control">
<%= text_input f, :endpoint, class: "input", disabled: @use_default_endpoint %>
<%= text_input f, :endpoint, class: "input #{input_error_class(f, :endpoint)}",
disabled: @use_default_endpoint %>
</div>
<p class="help is-danger">
<%= error_tag f, :endpoint %>
@@ -113,7 +116,7 @@
<%= label f, :mtu, "Interface MTU", class: "label" %>
<p>The WireGuard interface MTU for this Device.</p>
<div class="contro">
<%= text_input f, :mtu, class: "input", disabled: @use_default_mtu %>
<%= text_input f, :mtu, class: "input #{input_error_class(f, :mtu)}", disabled: @use_default_mtu %>
</div>
<p class="help is-danger">
<%= error_tag f, :mtu %>
@@ -146,7 +149,8 @@
unless you're experiencing NAT or firewall traversal problems.
</p>
<div class="control">
<%= text_input f, :persistent_keepalive, class: "input", disabled: @use_default_persistent_keepalive %>
<%= text_input f, :persistent_keepalive, class: "input #{input_error_class(f, :persistent_keepalive)}",
disabled: @use_default_persistent_keepalive %>
</div>
<p class="help is-danger">
<%= error_tag f, :persistent_keepalive %>
@@ -157,7 +161,7 @@
<%= label f, :ipv4, "IPv4 Address", class: "label" %>
<div class="control">
<%= text_input f, :ipv4, class: "input" %>
<%= text_input f, :ipv4, class: "input #{input_error_class(f, :ipv4)}" %>
</div>
<p class="help is-danger">
<%= error_tag f, :ipv4 %>
@@ -168,7 +172,7 @@
<%= label f, :ipv6, "IPv6 Address", class: "label" %>
<div class="control">
<%= text_input f, :ipv6, class: "input" %>
<%= text_input f, :ipv6, class: "input #{input_error_class(f, :ipv6)}" %>
</div>
<p class="help is-danger">
<%= error_tag f, :ipv6 %>

View File

@@ -12,7 +12,7 @@
<div class="control is-expanded">
<%= text_input f,
:destination,
class: "input",
class: "input #{input_error_class(f, :destination)}",
placeholder: "IPv4/6 CIDR range or address" %>
</div>
<div class="control">

View File

@@ -8,32 +8,24 @@
<%= label f, :email, class: "label" %>
<div class="control">
<%= text_input f, :email, class: "input" %>
<%= text_input f, :email, class: "input #{input_error_class(f, :email)}" %>
</div>
<p class="help is-danger">
<%= error_tag f, :email %>
</p>
</div>
<div class="field">
<%= label f, :password, "New password", class: "label" %>
<div class="control">
<%= password_input f, :password, class: "input password" %>
</div>
<p class="help is-danger">
<%= error_tag f, :password %>
</p>
</div>
<%= render FzHttpWeb.SharedView,
"password_field.html",
context: f,
field: :password,
label: "Password" %>
<div class="field">
<%= label f, :password_confirmation, "New password confirmation", class: "label" %>
<div class="control">
<%= password_input f, :password_confirmation, class: "input password" %>
</div>
<p class="help is-danger">
<%= error_tag f, :password_confirmation %>
</p>
</div>
<%= render FzHttpWeb.SharedView,
"password_field.html",
context: f,
field: :password_confirmation,
label: "Password Confirmation" %>
<hr>

View File

@@ -10,34 +10,24 @@
<%= label f, :email, class: "label" %>
<div class="control">
<%= text_input f, :email, class: "input" %>
<%= text_input f, :email, class: "input #{input_error_class(f, :email)}" %>
</div>
<p class="help is-danger">
<%= error_tag f, :email %>
</p>
</div>
<div class="field">
<%= label f, :password, "New Password", class: "label" %>
<%= render FzHttpWeb.SharedView,
"password_field.html",
context: f,
field: :password,
label: "New Password" %>
<div class="control">
<%= password_input f, :password, class: "input password" %>
</div>
<p class="help is-danger">
<%= error_tag f, :password %>
</p>
</div>
<div class="field">
<%= label f, :password_confirmation, "New Password Confirmation", class: "label" %>
<div class="control">
<%= password_input f, :password_confirmation, class: "input password" %>
</div>
<p class="help is-danger">
<%= error_tag f, :password_confirmation %>
</p>
</div>
<%= render FzHttpWeb.SharedView,
"password_field.html",
context: f,
field: :password_confirmation,
label: "New Password Confirmation" %>
<div class="field">
<div class="control">

View File

@@ -1,4 +1,3 @@
<!-- Use https://admin-one.justboil.me/?style=light-dark#/lock-screen for inspiration -->
<h3 class="is-3 title">Sign In</h3>
<hr>
@@ -13,7 +12,7 @@
<div class="field">
<%= label(:session, :email, class: "label") %>
<div class="control">
<%= text_input(f, :email, class: "input") %>
<%= text_input(f, :email, class: "input #{input_error_class(f, :email)}") %>
</div>
<p class="help is-danger">
<%= error_tag f, :email %>
@@ -23,7 +22,7 @@
<div class="field">
<%= label(:session, :password, class: "label") %>
<div class="control">
<%= password_input(f, :password, class: "input") %>
<%= password_input(f, :password, class: "input #{input_error_class(f, :password)}") %>
</div>
<p class="help is-danger">
<%= error_tag f, :password %>

View File

@@ -0,0 +1,21 @@
<div class="field">
<%= label @context, @field, @label, class: "label" %>
<div class="control">
<%= password_input @context,
@field,
class: "input password",
id: "#{@field}-field",
data_target: "#{@field}-progress",
phx_hook: "PasswordStrength" %>
</div>
<p class="help is-danger">
<%= error_tag @context, @field %>
</p>
<progress
id={"#{@field}-progress"}
class="is-hidden"
value="0"
max="100">0%</progress>
</div>

View File

@@ -31,7 +31,7 @@ defmodule FzHttp.ReleaseTest do
test "reset admin password when user exists" do
{:ok, first_user} = Release.create_admin_user()
{:ok, new_first_user} = Release.change_password(first_user.email, "newpassword")
{:ok, new_first_user} = Release.change_password(first_user.email, "newpassword1234")
{:ok, second_user} = Release.create_admin_user()
assert second_user.password_hash != new_first_user.password_hash

View File

@@ -36,7 +36,7 @@ defmodule FzHttp.SessionsTest do
describe "create_session/2" do
setup [:create_user]
@password_params %{password: "testtest"}
@password_params %{password: "password1234"}
@invalid_params %{password: "invalid"}
test "creates session (updates existing record)", %{user: user} do

View File

@@ -61,23 +61,23 @@ defmodule FzHttp.UsersTest do
describe "create_user/1" do
@valid_attrs_map %{
email: "valid@test",
password: "password",
password_confirmation: "password"
password: "password1234",
password_confirmation: "password1234"
}
@valid_attrs_list [
email: "valid@test",
password: "password",
password_confirmation: "password"
password: "password1234",
password_confirmation: "password1234"
]
@invalid_attrs_map %{
email: "invalid_email",
password: "password",
password_confirmation: "password"
password: "password1234",
password_confirmation: "password1234"
}
@invalid_attrs_list [
email: "valid@test",
password: "password",
password_confirmation: "different_password"
password: "password1234",
password_confirmation: "different_password1234"
]
@too_short_password [
email: "valid@test",
@@ -95,7 +95,7 @@ defmodule FzHttp.UsersTest do
assert changeset.errors[:password] == {
"should be at least %{count} character(s)",
[count: 8, validation: :length, kind: :min, type: :string]
[count: 12, validation: :length, kind: :min, type: :string]
}
end
@@ -140,7 +140,7 @@ defmodule FzHttp.UsersTest do
@change_password_valid_params %{
"password" => "new_password",
"password_confirmation" => "new_password",
"current_password" => "testtest"
"current_password" => "password1234"
}
@change_password_invalid_params %{
"password" => "new_password",

View File

@@ -54,7 +54,7 @@ defmodule FzHttpWeb.SessionControllerTest do
params = %{
"session" => %{
"email" => user.email,
"password" => "testtest"
"password" => "password1234"
}
}

View File

@@ -261,7 +261,7 @@ defmodule FzHttpWeb.DeviceLive.ShowTest do
|> render_change(@default_allowed_ips_change)
assert test_view =~ """
<input class="input" id="edit-device_allowed_ips" name="device[allowed_ips]" type="text"/>\
<input class="input " id="edit-device_allowed_ips" name="device[allowed_ips]" type="text"/>\
"""
end
@@ -275,7 +275,7 @@ defmodule FzHttpWeb.DeviceLive.ShowTest do
|> render_change(@default_dns_change)
assert test_view =~ """
<input class="input" id="edit-device_dns" name="device[dns]" type="text"/>\
<input class="input " id="edit-device_dns" name="device[dns]" type="text"/>\
"""
end
@@ -289,7 +289,7 @@ defmodule FzHttpWeb.DeviceLive.ShowTest do
|> render_change(@default_endpoint_change)
assert test_view =~ """
<input class="input" id="edit-device_endpoint" name="device[endpoint]" type="text"/>\
<input class="input " id="edit-device_endpoint" name="device[endpoint]" type="text"/>\
"""
end
@@ -303,7 +303,7 @@ defmodule FzHttpWeb.DeviceLive.ShowTest do
|> render_change(@default_mtu_change)
assert test_view =~ """
<input class="input" id="edit-device_mtu" name="device[mtu]" type="text"/>\
<input class="input " id="edit-device_mtu" name="device[mtu]" type="text"/>\
"""
end
@@ -317,7 +317,7 @@ defmodule FzHttpWeb.DeviceLive.ShowTest do
|> render_change(@default_persistent_keepalive_change)
assert test_view =~ """
<input class="input" id="edit-device_persistent_keepalive" name="device[persistent_keepalive]" type="text"/>\
<input class="input " id="edit-device_persistent_keepalive" name="device[persistent_keepalive]" type="text"/>\
"""
end
end

View File

@@ -98,7 +98,7 @@ defmodule FzHttpWeb.UserLive.IndexTest do
|> render_submit(@invalid_user_attrs)
assert new_view =~ "has invalid format"
assert new_view =~ "should be at least 8 character(s)"
assert new_view =~ "should be at least 12 character(s)"
end
end

View File

@@ -139,7 +139,7 @@ defmodule FzHttpWeb.UserLive.ShowTest do
|> render_submit(@invalid_attrs)
assert new_view =~ "has invalid format"
assert new_view =~ "should be at least 8 character(s)"
assert new_view =~ "should be at least 12 character(s)"
end
end

View File

@@ -8,7 +8,7 @@ defmodule FzHttp.SessionsFixtures do
def session(_attrs \\ %{}) do
email = UsersFixtures.user().email
record = Sessions.get_session!(email: email)
create_params = %{email: email, password: "testtest"}
create_params = %{email: email, password: "password1234"}
{:ok, session} = Sessions.create_session(record, create_params)
session
end

View File

@@ -15,7 +15,7 @@ defmodule FzHttp.UsersFixtures do
case Repo.get_by(User, email: email) do
nil ->
{:ok, user} =
%{email: email, password: "testtest", password_confirmation: "testtest"}
%{email: email, password: "password1234", password_confirmation: "password1234"}
|> Map.merge(attrs)
|> Users.create_admin_user()

View File

@@ -65,7 +65,7 @@ config :fz_http,
cookie_signing_salt: "Z9eq8iof",
ecto_repos: [FzHttp.Repo],
admin_email: "firezone@localhost",
default_admin_password: "firezone",
default_admin_password: "firezone1234",
events_module: FzHttpWeb.Events,
server_process_opts: [name: {:global, :fz_http_server}]

View File

@@ -115,7 +115,7 @@ class Firezone
'database_encryption_key' => node['firezone'] && node['firezone']['database_encryption_key'] || \
SecureRandom.base64(32),
'default_admin_password' => node['firezone'] && node['firezone']['default_admin_password'] || \
SecureRandom.base64(8)
SecureRandom.base64(12)
}
end
# rubocop:enable Metrics/PerceivedComplexity