[Users] Rework login pages and increase password requirements (#825)

This commit is contained in:
Cinder 2024-12-14 17:37:53 -08:00 committed by GitHub
parent 1c84f551dc
commit a0b51e40bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 323 additions and 163 deletions

View File

@ -26,6 +26,7 @@ gem 'marcel'
gem 'sidekiq-unique-jobs' gem 'sidekiq-unique-jobs'
gem 'redis' gem 'redis'
gem 'request_store' gem 'request_store'
gem "zxcvbn-ruby", require: "zxcvbn"
gem "diffy" gem "diffy"
gem "rugged" gem "rugged"

View File

@ -376,6 +376,7 @@ GEM
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
zeitwerk (2.6.13) zeitwerk (2.6.13)
zxcvbn-ruby (1.2.0)
PLATFORMS PLATFORMS
ruby ruby
@ -426,6 +427,7 @@ DEPENDENCIES
streamio-ffmpeg streamio-ffmpeg
webmock webmock
webpacker (>= 4.0.x) webpacker (>= 4.0.x)
zxcvbn-ruby
BUNDLED WITH BUNDLED WITH
2.4.10 2.4.10

View File

@ -42,7 +42,7 @@
docker compose up docker compose up
``` ```
After running the commands once only `docker compose up` is needed to bring up the containers. After running the commands once only `docker compose up` is needed to bring up the containers.
1. To confirm the installation worked, open the web browser of your choice and enter `http://localhost:3000` into the address bar and see if the website loads correctly. An admin account has been created automatically, the username and password are `admin` and `qwerty` respectively. 1. To confirm the installation worked, open the web browser of your choice and enter `http://localhost:3000` into the address bar and see if the website loads correctly. An admin account has been created automatically, the username and password are `admin` and `hexerade` respectively.
1. By default, the site will lack any content. For testing purposes, you can generate some using the following command: 1. By default, the site will lack any content. For testing purposes, you can generate some using the following command:
``` ```
docker exec -it e621ng-e621-1 /app/bin/populate docker exec -it e621ng-e621-1 /app/bin/populate

View File

@ -0,0 +1,70 @@
import zxcvbn from "zxcvbn";
import Page from "./utility/page";
let Password = {};
Password.init_validation = function () {
if (Page.matches("users", "new") || Page.matches("users", "create"))
Password.bootstrap_input($("#user_password"), [$("#user_name"), $("#user_email")]);
if (Page.matches("maintenance-user-password-resets", "edit"))
Password.bootstrap_input($("#password"));
if (Page.matches("maintenance-user-passwords", "edit"))
Password.bootstrap_input($("#user_password"));
};
Password.bootstrap_input = function ($password, $inputs = []) {
// Set up the UI
$password.parent().addClass("password-input");
const hint = $("<div>")
.addClass("password-feedback")
.insertAfter($password);
const display = $("<div>")
.addClass("password-strength")
.insertAfter($password);
const progress = $("<div>")
.addClass("password-progress")
.css("width", 0)
.appendTo(display);
// Listen to secondary input changes
let extraData = getExtraData();
for (const one of $inputs)
one.on("input", () => {
extraData = getExtraData();
});
// Listen to main input changes
$password.on("input", () => {
const analysis = zxcvbn($password.val() + "", extraData);
progress.css("width", ((analysis.score * 25) + 10) + "%");
hint.html("");
if (analysis.feedback.warning)
$("<span>")
.text(analysis.feedback.warning)
.addClass("password-warning")
.appendTo(hint);
for (const one of analysis.feedback.suggestions)
$("<span>")
.text(one)
.appendTo(hint);
});
function getExtraData () {
const output = [];
for (const one of $inputs) {
const val = one.val() + "";
if (val) output.push(one.val() + "");
}
return output;
}
};
$(() => {
Password.init_validation();
});
export default Password;

View File

@ -54,7 +54,6 @@
@import "specific/iqdb_queries.scss"; @import "specific/iqdb_queries.scss";
@import "specific/keyboard_shortcuts.scss"; @import "specific/keyboard_shortcuts.scss";
@import "specific/lockdown.scss"; @import "specific/lockdown.scss";
@import "specific/maintenance.scss";
@import "specific/meta_searches.scss"; @import "specific/meta_searches.scss";
@import "specific/moderator_dashboard.scss"; @import "specific/moderator_dashboard.scss";
@import "specific/news_updates.scss"; @import "specific/news_updates.scss";

View File

@ -1,5 +0,0 @@
div#c-maintenance-user-login-reminders {
div#a-new {
width: 50em;
}
}

View File

@ -223,13 +223,83 @@ div#c-users {
color: themed("color-link-active"); color: themed("color-link-active");
} }
} }
}
div#a-new { // User signup and login
max-width: 60em; #c-users #a-new,
#c-sessions #a-new,
#c-maintenance-user-password-resets #a-new,
#c-maintenance-user-login-reminders #a-new {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(auto, 360px));
gap: 1em;
p { margin-bottom: 1em;
font-size: 1.2em; }
line-height: 1.4em;
.session_form {
box-sizing: border-box;
max-width: 360px;
margin: 0;
h1 {
margin-bottom: 0.5em;
text-align: center;
}
div.input {
input[type="text"], input[type="email"], input[type="password"], select {
width: 100%;
max-width: unset;
box-sizing: border-box;
} }
} }
} }
.session_info {
display: flex;
flex-flow: column;
justify-content: center;
box-sizing: border-box;
max-width: 360px;
padding: 0.5rem;
border-radius: 3px;
background-color: themed("color-section");
h3 { margin-bottom: 1em; }
}
// Password validation
.password-input {
input[type="password"] {
border-radius: 3px 3px 0 0;
}
.password-strength {
width: 100%;
height: 0.25rem;
border-radius: 0 0 3px 3px;
background: white;
overflow: hidden;
margin: 0;
.password-progress {
background: linear-gradient(to right, palette("text-red") 0%, palette("text-yellow") 25%, palette("text-green") 100%);
background-size: 360px 100%;
height: 100%;
transition: width 1s ease-in-out;
}
}
.password-feedback {
display: flex;
flex-flow: column;
padding-left: 1em;
margin-top: 0.5em;
span { display: list-item; }
.password-warning { font-weight: bold; }
}
}

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "zxcvbn"
class User < ApplicationRecord class User < ApplicationRecord
class Error < Exception ; end class Error < Exception ; end
class PrivilegeError < Exception class PrivilegeError < Exception
@ -71,7 +73,8 @@ class User < ApplicationRecord
validates :per_page, inclusion: { :in => 1..320 } validates :per_page, inclusion: { :in => 1..320 }
validates :comment_threshold, presence: true validates :comment_threshold, presence: true
validates :comment_threshold, numericality: { only_integer: true, less_than: 50_000, greater_than: -50_000 } validates :comment_threshold, numericality: { only_integer: true, less_than: 50_000, greater_than: -50_000 }
validates :password, length: { :minimum => 6, :if => ->(rec) { rec.new_record? || rec.password.present? || rec.old_password.present? } } validates :password, length: { minimum: 8, if: ->(rec) { rec.new_record? || rec.password.present? || rec.old_password.present? } }
validate :password_is_secure, if: ->(rec) { rec.new_record? || rec.password.present? || rec.old_password.present? }
validates :password, confirmation: true validates :password, confirmation: true
validates :password_confirmation, presence: { if: ->(rec) { rec.new_record? || rec.old_password.present? } } validates :password_confirmation, presence: { if: ->(rec) { rec.new_record? || rec.old_password.present? } }
validate :validate_ip_addr_is_not_banned, :on => :create validate :validate_ip_addr_is_not_banned, :on => :create
@ -227,6 +230,16 @@ class User < ApplicationRecord
def upgrade_password(pass) def upgrade_password(pass)
self.update_columns(password_hash: '', bcrypt_password_hash: User.bcrypt(pass)) self.update_columns(password_hash: '', bcrypt_password_hash: User.bcrypt(pass))
end end
def password_is_secure
analysis = Zxcvbn.test(password, [name, email])
return unless analysis.score < 2
if analysis.feedback.warning
errors.add(:password, "is insecure: #{analysis.feedback.warning}")
else
errors.add(:password, "is insecure")
end
end
end end
module AuthenticationMethods module AuthenticationMethods

View File

@ -1,21 +1,21 @@
<div id="c-maintenance-user-login-reminders"> <div id="c-maintenance-user-login-reminders"><div id="a-new">
<div id="a-new">
<%= form_tag(maintenance_user_login_reminder_path, :class => "simple_form session_form") do %>
<h1>Login Reminder</h1> <h1>Login Reminder</h1>
<div class="input email required">
<label for="user_email" class="required">Email</label>
<%= email_field(:user, :email) %>
</div>
<%= submit_tag "Submit" %>
<% end %>
<section class="session_info">
<p>If you supplied an email address when signing up, <%= Danbooru.config.app_name %> can email you your login information. Password details will not be provided and will not be changed.</p> <p>If you supplied an email address when signing up, <%= Danbooru.config.app_name %> can email you your login information. Password details will not be provided and will not be changed.</p>
<p>If you didn't supply a valid email address, you are out of luck.</p> <p>If you didn't supply a valid email address, you are out of luck.</p>
</section>
<%= form_tag(maintenance_user_login_reminder_path, :class => "simple_form") do %> </div></div>
<div class="input email required">
<label for="user_email" class="required">Email</label>
<%= email_field(:user, :email) %>
</div>
<%= submit_tag "Submit" %>
<% end %>
</div>
</div>
<%= render "sessions/secondary_links" %> <%= render "sessions/secondary_links" %>

View File

@ -1,26 +1,28 @@
<div id="c-maintenance-user-password-resets"> <div id="c-maintenance-user-password-resets"><div id="a-edit">
<div id="a-edit">
<h1>Reset Password</h1>
<% if @nonce %> <% if @nonce %>
<%= form_tag(maintenance_user_password_reset_path, :method => :put) do %> <%= form_tag(maintenance_user_password_reset_path, :method => :put, :class => "simple_form session_form") do %>
<%= hidden_field_tag :uid, params[:uid] %> <h1>Reset Password</h1>
<%= hidden_field_tag :key, params[:key] %>
<div class="input"> <%= hidden_field_tag :uid, params[:uid] %>
<label for="password">Password</label> <%= hidden_field_tag :key, params[:key] %>
<%= password_field_tag :password %>
</div> <div class="input">
<div class="input"> <label for="password">Password</label>
<label for="password_confirm">Confirm Password</label> <%= password_field_tag :password %>
<%= password_field_tag :password_confirm %> </div>
</div>
<%= submit_tag "Reset" %> <div class="input">
<% end %> <label for="password_confirm">Confirm Password</label>
<% else %> <%= password_field_tag :password_confirm %>
<p>Invalid reset</p> </div>
<%= submit_tag "Reset" %>
<% end %> <% end %>
</div> <% else %>
</div> <p>Invalid reset</p>
<% end %>
</div></div>
<%= render "sessions/secondary_links" %> <%= render "sessions/secondary_links" %>

View File

@ -1,20 +1,19 @@
<div id="c-maintenance-user-password-resets"> <div id="c-maintenance-user-password-resets"><div id="a-new">
<div id="a-new">
<%= form_tag(maintenance_user_password_reset_path, :class => "simple_form session_form") do %>
<h1>Reset Password</h1> <h1>Reset Password</h1>
<div class="input email required">
<label for="nonce_email" class="required">Email</label>
<%= text_field_tag :email %>
</div>
<%= submit_tag "Submit" %>
<% end %>
<section class="session_info">
<p>If you supplied an email address when signing up, <%= Danbooru.config.app_name %> can reset your password. You will receive an email confirming your request for a new password.</p> <p>If you supplied an email address when signing up, <%= Danbooru.config.app_name %> can reset your password. You will receive an email confirming your request for a new password.</p>
<p>If you didn't supply a valid email address, there is no way to recover your account.</p> <p>If you didn't supply a valid email address, there is no way to recover your account.</p>
</section>
<%= form_tag(maintenance_user_password_reset_path, :class => "simple_form") do %> </div></div>
<div class="input email required">
<label for="nonce_email" class="required">Email</label>
<%= text_field_tag :email %>
</div>
<%= submit_tag "Submit" %>
<% end %>
</div>
</div>
<%= render "sessions/secondary_links" %> <%= render "sessions/secondary_links" %>

View File

@ -1,15 +1,12 @@
<div id="c-maintenance-user-passwords"> <div id="c-maintenance-user-passwords"><div id="a-edit">
<div id="a-edit"> <%= custom_form_for(@user, html: { class: "session_form" }) do |f| %>
<h1>Change Password</h1> <h1>Change Password</h1>
<%= f.input :old_password, :as => :password, :input_html => {:autocomplete => "off"} %>
<%= custom_form_for @user do |f| %> <%= f.input :password, :label => "New password", :input_html => {:autocomplete => "off"} %>
<%= f.input :old_password, :as => :password, :input_html => {:autocomplete => "off"} %> <%= f.input :password_confirmation %>
<%= f.input :password, :label => "New password", :input_html => {:autocomplete => "off"} %> <%= f.button :submit, "Submit" %>
<%= f.input :password_confirmation %> <% end %>
<%= f.button :submit, "Submit" %> </div></div>
<% end %>
</div>
</div>
<% content_for(:page_title) do %> <% content_for(:page_title) do %>
Change Password Change Password

View File

@ -1,38 +1,40 @@
<div id="c-sessions"> <div id="c-sessions"><div id="a-new">
<div id="a-new"> <%= form_tag(session_path, :class => "simple_form session_form") do %>
<section> <h1>Sign in</h1>
<h1>Sign in</h1>
<%= form_tag(session_path, :class => "simple_form") do %>
<%= hidden_field_tag "url", params[:url] %>
<div class="input"> <%= hidden_field_tag "url", params[:url] %>
<label for="name">Username</label>
<%= text_field_tag :name %>
</div>
<div class="input"> <div class="input">
<label for="password">Password</label> <label for="name">Username</label>
<%= password_field_tag :password %> <%= text_field_tag :name %>
</div> </div>
<div class="input"> <div class="input">
<%= check_box_tag :remember, "1", true %> <label for="password">Password</label>
<label for="remember" id="remember-label">Remember</label> <%= password_field_tag :password %>
</div> </div>
<div class="input"> <div class="input">
<%= submit_tag "Submit", :data => { :disable_with => "Signing in..." } %> <%= check_box_tag :remember, "1", true %>
</div> <label for="remember" id="remember-label">Remember</label>
<% end %> </div>
</section>
<section class="box-section"> <div class="input">
<h2><%= link_to "Don't have an account? Sign up here.", new_user_path() %></h2> <%= submit_tag "Submit", :data => { :disable_with => "Signing in..." } %>
<h2><%= link_to "Reset Password", new_maintenance_user_password_reset_path %></h2> </div>
<h2><%= link_to "Login Reminder", new_maintenance_user_login_reminder_path %></h2> <% end %>
</section>
</div> <section class="session_info">
</div> <h3>
Don't have an account?<br />
<%= link_to "Sign up here.", new_user_path() %>
</h3>
<br />
<h3><%= link_to "Reset Password", new_maintenance_user_password_reset_path %></h2>
<h3><%= link_to "Login Reminder", new_maintenance_user_login_reminder_path %></h2>
</section>
</div></div>
<% content_for(:page_title) do %> <% content_for(:page_title) do %>
Sign in Sign in

View File

@ -1,34 +1,30 @@
<div id="c-users"> <div id="c-users"><div id="a-new">
<div id="a-new"> <%= custom_form_for(@user, html: { id: "signup-form", class: "session_form" }) do |f| %>
<h1>Sign Up</h1> <h1>Sign Up</h1>
<p>An account is <strong>free</strong> and lets you keep favorites, upload artwork, and write comments.</p> <%= f.input :name, label: "Username", as: :string %>
<%= f.input :email, :required => true, as: :email %>
<%= f.input :password %>
<%= f.input :password_confirmation %>
<div class="box-section background-red"> <%= f.input :time_zone, include_blank: false %>
<p>Make sure to read the <a href="/wiki_pages/e621:rules">site rules</a> before continuing.</p>
<p>You must confirm your email address, so use something you can receive email with.</p>
<p>This site is open to web crawlers so whatever name you choose will be public!</p>
<p>This includes favorites, uploads, and comments. Almost everything is public. So don't choose a name you don't want to be associated with.</p>
<p>Accounts are prefilled with the same blacklist as guests have. You can access your blacklist in your account settings.</p>
</div>
<div id="p3"> <% if Danbooru.config.enable_recaptcha? %>
<%= custom_form_for(@user, html: {id: "signup-form"}) do |f| %> <%= recaptcha_tags theme: "dark", nonce: content_security_policy_nonce %>
<%= f.input :name, :as => :string %> <% end %>
<%= f.input :email, :required => true, :as => :email %> <%= f.submit "Sign up", :data => { :disable_with => "Signing up..." } %>
<%= f.input :password %> <% end %>
<%= f.input :password_confirmation %> <section class="session_info">
<h3>
<%= f.input :time_zone, :include_blank => false %> Already have an account? <%= link_to "Sign In.", new_session_path %>
</h3>
<% if Danbooru.config.enable_recaptcha? %> <p>Please, read the <a href="/wiki_pages/e621:rules">site rules</a> before making an account.</p>
<%= recaptcha_tags theme: 'dark', nonce: content_security_policy_nonce %> <p>You must confirm your email address, so you should only use one that you have access to.</p>
<% end %> <p>This site is open to web crawlers, meaning that almost everything is public.</p>
<%= f.submit "Sign up", :data => { :disable_with => "Signing up..." } %> <p>This includes your account name, favorites, uploads, and comments. Do not choose a name you don't want to be associated with.</p>
<% end %> <p>Accounts have the same blacklist as guests by default. You will be able to modify your blacklist in the account settings.</p>
</div>
</div> </div>
</div> </div></div>
<%= render "secondary_links" %> <%= render "secondary_links" %>

View File

@ -37,7 +37,7 @@ POSTVOTES = presets[:postvotes]
POOLS = presets[:pools] POOLS = presets[:pools]
DISTRIBUTION = ENV.fetch("DISTRIBUTION", 10).to_i DISTRIBUTION = ENV.fetch("DISTRIBUTION", 10).to_i
DEFAULT_PASSWORD = ENV.fetch("PASSWORD", "qwerty") DEFAULT_PASSWORD = ENV.fetch("PASSWORD", "hexerade")
CurrentUser.user = User.system CurrentUser.user = User.system

View File

@ -9,8 +9,8 @@ require "tempfile"
admin = User.find_or_create_by!(name: "admin") do |user| admin = User.find_or_create_by!(name: "admin") do |user|
user.created_at = 2.weeks.ago user.created_at = 2.weeks.ago
user.password = "qwerty" user.password = "hexerade"
user.password_confirmation = "qwerty" user.password_confirmation = "hexerade"
user.password_hash = "" user.password_hash = ""
user.email = "admin@e621.local" user.email = "admin@e621.local"
user.can_upload_free = true user.can_upload_free = true

View File

@ -21,7 +21,8 @@
"vue": "^3.1.0", "vue": "^3.1.0",
"vue-loader": "^17.4.2", "vue-loader": "^17.4.2",
"webpack": "^5.51.1", "webpack": "^5.51.1",
"zingtouch": "^1.0.6" "zingtouch": "^1.0.6",
"zxcvbn": "^4.4.2"
}, },
"version": "0.1.0", "version": "0.1.0",
"devDependencies": { "devDependencies": {

View File

@ -5,14 +5,14 @@ FactoryBot.define do
sequence :name do |n| sequence :name do |n|
"user#{n}" "user#{n}"
end end
password { "password" } password { "6cQE!wbA" }
password_confirmation { "password" } password_confirmation { "6cQE!wbA" }
sequence(:email) { |n| "user_email_#{n}@example.com" } sequence(:email) { |n| "user_email_#{n}@example.com" }
default_image_size { "large" } default_image_size { "large" }
base_upload_limit { 10 } base_upload_limit { 10 }
level { 20 } level { 20 }
created_at {Time.now} created_at { Time.now }
last_logged_in_at {Time.now} last_logged_in_at { Time.now }
factory(:banned_user) do factory(:banned_user) do
transient { ban_duration { 3 } } transient { ban_duration { 3 } }

View File

@ -36,7 +36,7 @@ class ApplicationControllerTest < ActionDispatch::IntegrationTest
context "on api authentication" do context "on api authentication" do
setup do setup do
@user = create(:user, password: "password") @user = create(:user, password: "6cQE!wbA")
@api_key = ApiKey.generate!(@user) @api_key = ApiKey.generate!(@user)
ActionController::Base.allow_forgery_protection = true ActionController::Base.allow_forgery_protection = true
@ -108,7 +108,7 @@ class ApplicationControllerTest < ActionDispatch::IntegrationTest
token = css_select("form input[name=authenticity_token]").first["value"] token = css_select("form input[name=authenticity_token]").first["value"]
# login # login
post session_path, params: { authenticity_token: token, name: @user.name, password: "password" } post session_path, params: { authenticity_token: token, name: @user.name, password: "6cQE!wbA" }
assert_redirected_to posts_path assert_redirected_to posts_path
# try to submit a form with cookies but without the csrf token # try to submit a form with cookies but without the csrf token
@ -122,9 +122,9 @@ class ApplicationControllerTest < ActionDispatch::IntegrationTest
context "on session cookie authentication" do context "on session cookie authentication" do
should "succeed" do should "succeed" do
user = create(:user, password: "password") user = create(:user, password: "6cQE!wbA")
post session_path, params: { name: user.name, password: "password" } post session_path, params: { name: user.name, password: "6cQE!wbA" }
get edit_user_path(user) get edit_user_path(user)
assert_response :success assert_response :success

View File

@ -7,7 +7,7 @@ module Maintenance
class ApiKeysControllerTest < ActionDispatch::IntegrationTest class ApiKeysControllerTest < ActionDispatch::IntegrationTest
context "An api keys controller" do context "An api keys controller" do
setup do setup do
@user = create(:privileged_user, :password => "password") @user = create(:privileged_user, password: "6cQE!wbA")
ApiKey.generate!(@user) ApiKey.generate!(@user)
end end
@ -21,7 +21,7 @@ module Maintenance
context "#view" do context "#view" do
context "with a correct password" do context "with a correct password" do
should "succeed" do should "succeed" do
post_auth view_maintenance_user_api_key_path(user_id: @user.id), @user, params: {user: {password: "password"}} post_auth view_maintenance_user_api_key_path(user_id: @user.id), @user, params: { user: { password: "6cQE!wbA" } }
assert_response :success assert_response :success
end end
@ -37,7 +37,7 @@ module Maintenance
# ApiKey.expects(:generate!) # ApiKey.expects(:generate!)
# assert_difference("ApiKey.count", 1) do # assert_difference("ApiKey.count", 1) do
# post view_maintenance_user_api_key_path(user_id: @user.id), params: {user: {password: "password"}} # post view_maintenance_user_api_key_path(user_id: @user.id), params: { user: { password: "6cQE!wbA" } }
# end # end
# assert_not_nil(@user.reload.api_key) # assert_not_nil(@user.reload.api_key)
@ -46,7 +46,7 @@ module Maintenance
should "not generate another API key if the user already has one" do should "not generate another API key if the user already has one" do
assert_difference("ApiKey.count", 0) do assert_difference("ApiKey.count", 0) do
post_auth view_maintenance_user_api_key_path(user_id: @user.id), @user, params: {user: {password: "password"}} post_auth view_maintenance_user_api_key_path(user_id: @user.id), @user, params: { user: { password: "6cQE!wbA" } }
end end
end end
end end
@ -55,14 +55,14 @@ module Maintenance
context "#update" do context "#update" do
should "regenerate the API key" do should "regenerate the API key" do
old_key = @user.api_key old_key = @user.api_key
put_auth maintenance_user_api_key_path, @user, params: {user_id: @user.id, user: {password: "password"}} put_auth maintenance_user_api_key_path, @user, params: {user_id: @user.id, user: { password: "6cQE!wbA" } }
assert_not_equal(old_key.key, @user.reload.api_key.key) assert_not_equal(old_key.key, @user.reload.api_key.key)
end end
end end
context "#destroy" do context "#destroy" do
should "delete the API key" do should "delete the API key" do
delete_auth maintenance_user_api_key_path, @user, params: {user_id: @user.id, user: {password: "password"}} delete_auth maintenance_user_api_key_path, @user, params: {user_id: @user.id, user: { password: "6cQE!wbA" } }
assert_nil(@user.reload.api_key) assert_nil(@user.reload.api_key)
end end
end end

View File

@ -19,7 +19,7 @@ module Maintenance
context "#destroy" do context "#destroy" do
should "render" do should "render" do
delete_auth maintenance_user_deletion_path, @user, params: { password: "password" } delete_auth maintenance_user_deletion_path, @user, params: { password: "6cQE!wbA" }
assert_redirected_to(posts_path) assert_redirected_to(posts_path)
end end
end end

View File

@ -21,7 +21,7 @@ module Maintenance
context "#create" do context "#create" do
context "with the correct password" do context "with the correct password" do
should "work" do should "work" do
post_auth maintenance_user_email_change_path, @user, params: { email_change: { password: "password", email: "abc@ogres.net" } } post_auth maintenance_user_email_change_path, @user, params: { email_change: { password: "6cQE!wbA", email: "abc@ogres.net" } }
assert_redirected_to(home_users_path) assert_redirected_to(home_users_path)
@user.reload @user.reload
assert_equal("abc@ogres.net", @user.email) assert_equal("abc@ogres.net", @user.email)
@ -37,7 +37,7 @@ module Maintenance
end end
should "not work with an invalid email" do should "not work with an invalid email" do
post_auth maintenance_user_email_change_path, @user, params: { email_change: { password: "password", email: "" } } post_auth maintenance_user_email_change_path, @user, params: { email_change: { password: "6cQE!wbA", email: "" } }
@user.reload @user.reload
assert_not_equal("", @user.email) assert_not_equal("", @user.email)
assert_match(/Email can't be blank/, flash[:notice]) assert_match(/Email can't be blank/, flash[:notice])
@ -45,7 +45,7 @@ module Maintenance
should "work with a valid email when the users current email is invalid" do should "work with a valid email when the users current email is invalid" do
@user = create(:user, email: "") @user = create(:user, email: "")
post_auth maintenance_user_email_change_path, @user, params: { email_change: { password: "password", email: "abc@ogres.net" } } post_auth maintenance_user_email_change_path, @user, params: { email_change: { password: "6cQE!wbA", email: "abc@ogres.net" } }
@user.reload @user.reload
assert_equal("abc@ogres.net", @user.email) assert_equal("abc@ogres.net", @user.email)
end end

View File

@ -15,7 +15,7 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest
should "create a new session" do should "create a new session" do
user = create(:user) user = create(:user)
post session_path, params: { name: user.name, password: "password" } post session_path, params: { name: user.name, password: "6cQE!wbA" }
user.reload user.reload
assert_redirected_to(posts_path) assert_redirected_to(posts_path)
@ -33,7 +33,7 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest
end end
should "fail when provided an invalid password" do should "fail when provided an invalid password" do
user = create(:user, password: "xxxxxx", password_confirmation: "xxxxxx") user = create(:user, password: "6cQE!wbA", password_confirmation: "6cQE!wbA")
post session_path, params: { name: user.name, password: "yyy" } post session_path, params: { name: user.name, password: "yyy" }
assert_nil(session[:user_id]) assert_nil(session[:user_id])
@ -45,7 +45,7 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest
should "clear the session" do should "clear the session" do
user = create(:user) user = create(:user)
post session_path, params: { name: user.name, password: "password" } post session_path, params: { name: user.name, password: "6cQE!wbA" }
assert_not_nil(session[:user_id]) assert_not_nil(session[:user_id])
delete_auth(session_path, user) delete_auth(session_path, user)

View File

@ -76,7 +76,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
context "create action" do context "create action" do
should "create a user" do should "create a user" do
assert_difference(-> { User.count }, 1) do assert_difference(-> { User.count }, 1) do
post users_path, params: { user: { name: "xxx", password: "xxxxx1", password_confirmation: "xxxxx1" } } post users_path, params: { user: { name: "xxx", password: "nePD.3L4", password_confirmation: "nePD.3L4" } }
end end
created_user = User.find(session[:user_id]) created_user = User.find(session[:user_id])
assert_equal("xxx", created_user.name) assert_equal("xxx", created_user.name)

View File

@ -22,7 +22,7 @@ class UserDeletionTest < ActiveSupport::TestCase
setup do setup do
@user = create(:admin_user) @user = create(:admin_user)
CurrentUser.user = @user CurrentUser.user = @user
@deletion = UserDeletion.new(@user, "password") @deletion = UserDeletion.new(@user, "6cQE!wbA")
end end
should "fail" do should "fail" do
@ -43,7 +43,7 @@ class UserDeletionTest < ActiveSupport::TestCase
@user.update(email: "ted@danbooru.com") @user.update(email: "ted@danbooru.com")
@deletion = UserDeletion.new(@user, "password") @deletion = UserDeletion.new(@user, "6cQE!wbA")
with_inline_jobs { @deletion.delete! } with_inline_jobs { @deletion.delete! }
@user.reload @user.reload
end end
@ -58,7 +58,7 @@ class UserDeletionTest < ActiveSupport::TestCase
should "reset the password" do should "reset the password" do
assert_raises(BCrypt::Errors::InvalidHash) do assert_raises(BCrypt::Errors::InvalidHash) do
User.authenticate(@user.name, "password") User.authenticate(@user.name, "6cQE!wbA")
end end
end end

View File

@ -116,8 +116,8 @@ class UserTest < ActiveSupport::TestCase
end end
should "authenticate" do should "authenticate" do
assert(User.authenticate(@user.name, "password"), "Authentication should have succeeded") assert(User.authenticate(@user.name, "6cQE!wbA"), "Authentication should have succeeded")
assert(!User.authenticate(@user.name, "password2"), "Authentication should not have succeeded") assert_not(User.authenticate(@user.name, "password2"), "Authentication should not have succeeded")
end end
should "normalize its level" do should "normalize its level" do
@ -209,8 +209,8 @@ class UserTest < ActiveSupport::TestCase
should "fail if the confirmation does not match" do should "fail if the confirmation does not match" do
@user = create(:user) @user = create(:user)
@user.password = "zugzug6" @user.password = "6cQE!wbA"
@user.password_confirmation = "zugzug5" @user.password_confirmation = "7cQE!wbA"
@user.save @user.save
assert_equal(["Password confirmation doesn't match Password"], @user.errors.full_messages) assert_equal(["Password confirmation doesn't match Password"], @user.errors.full_messages)
end end
@ -220,7 +220,15 @@ class UserTest < ActiveSupport::TestCase
@user.password = "x5" @user.password = "x5"
@user.password_confirmation = "x5" @user.password_confirmation = "x5"
@user.save @user.save
assert_equal(["Password is too short (minimum is 6 characters)"], @user.errors.full_messages) assert_equal(["Password is too short (minimum is 8 characters)", "Password is insecure"], @user.errors.full_messages)
end
should "not be insecure" do
@user = create(:user)
@user.password = "qwerty123"
@user.password_confirmation = "qwerty123"
@user.save
assert_equal(["Password is insecure: This is similar to a commonly used password"], @user.errors.full_messages)
end end
# should "not change the password if the password and old password are blank" do # should "not change the password if the password and old password are blank" do

View File

@ -3716,3 +3716,8 @@ zingtouch@^1.0.6:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/zingtouch/-/zingtouch-1.0.6.tgz#456cf2b0a69f91a5ffbd8a83b18033c671f1096d" resolved "https://registry.yarnpkg.com/zingtouch/-/zingtouch-1.0.6.tgz#456cf2b0a69f91a5ffbd8a83b18033c671f1096d"
integrity sha512-S7jcR7cSRy28VmQBO0Tq7ZJV4pzfvvrTU9FrrL0K1QPpfBal9wm0oKhoCuifc+PPCq+hQMTJr5E9XKUQDm00VA== integrity sha512-S7jcR7cSRy28VmQBO0Tq7ZJV4pzfvvrTU9FrrL0K1QPpfBal9wm0oKhoCuifc+PPCq+hQMTJr5E9XKUQDm00VA==
zxcvbn@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30"
integrity sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ==