From 97d58568562d3df987494b688e1ed63b28092343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Inge=20J=C3=B8rgensen?= Date: Tue, 30 Jan 2024 22:12:05 +0100 Subject: [PATCH] PagesCore::AuthenticableUser --- .../concerns/pages_core/authenticable_user.rb | 65 +++++++++++++++++++ app/models/concerns/pages_core/has_otp.rb | 27 -------- app/models/user.rb | 34 +--------- 3 files changed, 66 insertions(+), 60 deletions(-) create mode 100644 app/models/concerns/pages_core/authenticable_user.rb delete mode 100644 app/models/concerns/pages_core/has_otp.rb diff --git a/app/models/concerns/pages_core/authenticable_user.rb b/app/models/concerns/pages_core/authenticable_user.rb new file mode 100644 index 00000000..9da1738c --- /dev/null +++ b/app/models/concerns/pages_core/authenticable_user.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module PagesCore + module AuthenticableUser + extend ActiveSupport::Concern + + included do + has_secure_password + + validates(:otp_secret, presence: true, if: :otp_enabled?) + validates( + :password, + length: { + minimum: 8, + maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED, + allow_blank: true + } + ) + + before_save :update_session_token + end + + module ClassMethods + def authenticate(email, password:) + User.find_by_email(email).try(:authenticate, password) + end + end + + def authenticate!(password) + return false unless can_login? && valid_password?(password) + + rehash_password!(password) if password_needs_rehash? + true + end + + def can_login? + activated? + end + + def recovery_codes=(codes) + self.hashed_recovery_codes = codes.map do |c| + BCrypt::Password.create(c, cost: 8) + end + end + + def use_recovery_code!(code) + valid_hashes = hashed_recovery_codes.select do |c| + BCrypt::Password.new(c) == code + end + return false unless valid_hashes.any? + + update(hashed_recovery_codes: hashed_recovery_codes - valid_hashes) + true + end + + private + + def update_session_token + return unless !session_token? || password_digest_changed? || + otp_enabled_changed? + + self.session_token = SecureRandom.hex(32) + end + end +end diff --git a/app/models/concerns/pages_core/has_otp.rb b/app/models/concerns/pages_core/has_otp.rb deleted file mode 100644 index 25f69297..00000000 --- a/app/models/concerns/pages_core/has_otp.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module PagesCore - module HasOtp - extend ActiveSupport::Concern - - included do - validates :otp_secret, presence: true, if: :otp_enabled? - end - - def recovery_codes=(codes) - self.hashed_recovery_codes = codes.map do |c| - BCrypt::Password.create(c, cost: 8) - end - end - - def use_recovery_code!(code) - valid_hashes = hashed_recovery_codes.select do |c| - BCrypt::Password.new(c) == code - end - return false unless valid_hashes.any? - - update(hashed_recovery_codes: hashed_recovery_codes - valid_hashes) - true - end - end -end diff --git a/app/models/user.rb b/app/models/user.rb index a1db79d6..27af1bc1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true class User < ApplicationRecord - include PagesCore::HasOtp + include PagesCore::AuthenticableUser include PagesCore::HasRoles - has_secure_password - belongs_to(:creator, class_name: "User", foreign_key: "created_by", @@ -30,14 +28,6 @@ class User < ApplicationRecord format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }, uniqueness: { case_sensitive: false } - validates :password, - length: { - minimum: 8, - maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED, - allow_blank: true - } - - before_save :update_session_token before_create :ensure_first_user_has_all_roles scope :by_name, -> { order("name ASC") } @@ -45,26 +35,11 @@ class User < ApplicationRecord scope :deactivated, -> { by_name.includes(:roles).where(activated: false) } class << self - def authenticate(email, password:) - User.find_by_email(email).try(:authenticate, password) - end - def find_by_email(str) find_by("LOWER(email) = ?", str.to_s.downcase.strip) end end - def authenticate!(password) - return false unless can_login? && valid_password?(password) - - rehash_password!(password) if password_needs_rehash? - true - end - - def can_login? - activated? - end - def mark_active! return if last_login_at && last_login_at > 10.minutes.ago @@ -93,11 +68,4 @@ def ensure_first_user_has_all_roles roles.new(name: r.name) unless role?(r.name) end end - - def update_session_token - return unless !session_token? || password_digest_changed? || - otp_enabled_changed? - - self.session_token = SecureRandom.hex(32) - end end