users: refactor password reset flow.

The old password reset flow:

* User requests a password reset.
* Danbooru generates a password reset nonce.
* Danbooru emails user a password reset confirmation link.
* User follows link to password reset confirmation page.
* The link contains a nonce authenticating the user.
* User confirms password reset.
* Danbooru resets user's password to a random string.
* Danbooru emails user their new password in plaintext.

The new password reset flow:

* User requests a password reset.
* Danbooru emails user a password reset link.
* User follows link to password edit page.
* The link contains a signed_user_id param authenticating the user.
* User changes their own password.
This commit is contained in:
evazion
2020-03-08 21:03:36 -05:00
parent f25bace766
commit 5625458f69
30 changed files with 133 additions and 395 deletions

View File

@@ -121,8 +121,7 @@ class Dmail < ApplicationRecord
end
def valid_key?(key)
decoded_id = verifier.verified(key)
id == decoded_id
id == verifier.verify(key)
end
def visible_to?(user, key)

View File

@@ -68,7 +68,7 @@ class User < ApplicationRecord
has_bit_flags BOOLEAN_ATTRIBUTES, :field => "bit_prefs"
attr_accessor :password, :old_password
attr_accessor :password, :old_password, :signed_user_id
after_initialize :initialize_attributes, if: :new_record?
validates :name, user_name: true, on: :create
@@ -185,36 +185,15 @@ class User < ApplicationRecord
def encrypt_password_on_update
return if password.blank?
return if old_password.blank?
if bcrypt_password == User.sha1(old_password)
if signed_user_id.present? && id == Danbooru::MessageVerifier.new(:login).verify(signed_user_id)
self.bcrypt_password_hash = User.bcrypt(password)
elsif old_password.present? && bcrypt_password == User.sha1(old_password)
self.bcrypt_password_hash = User.bcrypt(password)
return true
else
errors[:old_password] << "is incorrect"
return false
end
end
def reset_password
consonants = "bcdfghjklmnpqrstvqxyz"
vowels = "aeiou"
pass = ""
6.times do
pass << consonants[rand(21), 1]
pass << vowels[rand(5), 1]
end
pass << rand(100).to_s
update_column(:bcrypt_password_hash, User.bcrypt(pass))
pass
end
def reset_password_and_deliver_notice
new_password = reset_password
Maintenance::User::PasswordResetMailer.confirmation(self, new_password).deliver_now
end
end
module AuthenticationMethods
@@ -637,14 +616,6 @@ class User < ApplicationRecord
end
module SearchMethods
def with_email(email)
if email.blank?
where("FALSE")
else
where("email = ?", email)
end
end
def search(params)
q = super

View File

@@ -1,28 +0,0 @@
class UserPasswordResetNonce < ApplicationRecord
has_secure_token :key
validates_presence_of :email
validate :validate_existence_of_email
after_create :deliver_notice
def self.prune!
where("created_at < ?", 1.week.ago).destroy_all
end
def deliver_notice
Maintenance::User::PasswordResetMailer.reset_request(user, self).deliver_now
end
def validate_existence_of_email
if !User.with_email(email).exists?
errors[:email] << "is invalid"
end
end
def reset_user!
user.reset_password_and_deliver_notice
end
def user
@user ||= User.with_email(email).first
end
end