Reputation: 1673
I'm trying to override the authenticate
method in this file: https://github.com/rails/rails/blob/f33d52c95217212cbacc8d5e44b5a8e3cdc6f5b3/activemodel/lib/active_model/secure_password.rb#L61
I have to make sure that if a user without password_digest
tries to authenticate, the method returns false instead of raising BCrypt::Errors::InvalidHash
I have to do this because, in the past, we had another authentication system, and many users have a null password_digest. the authenticate method is used in different places and we cannot put an empty password_digest
to the users without a password_digest.
I tried this:
# config/initializers/secure_password.rb
module ActiveModel
module SecurePassword
class InstanceMethodsOnActivation
def authenticate(unencrypted_password)
begin
BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
rescue BCrypt::Errors::InvalidHash => e
false
end
end
end
end
end
This is my test:
it 'should authenticate with false response if no digest (legacy password system)' do
user.activate!
user.update(password_digest: nil)
expect(user.authenticate('test')).to be false
end
And the error message:
BCrypt::Errors::InvalidHash: invalid hash
0) User should authenticate with false response if no digest (legacy password system)
Failure/Error: expect(user.authenticate('test')).to be false
BCrypt::Errors::InvalidHash:
invalid hash
I update our app to Rails 6 and SecurePassword has been rewritten, I can't change the method again. I think the problem is that the authenticate method is now defined within the InstanceMethodsOnActivation's initialize method. I find it strange... And I don't know how to modify it.
Please anyone could help me?
Upvotes: 0
Views: 167
Reputation: 6603
I think you don't need to monkey-patch the gem implementation, because I think the super-trick would already be sufficient enough in your case:
class User < ApplicationRecord
def authenticate(*args)
if password_digest.nil? # or `.blank?` (if you have '' as default instead of nil)
false
else
# else, proceed normally
super(*args)
# `super` alone also works, as it automatically pass in all arguments
end
end
end
user = User.new
user.password_digest = nil
puts user.authenticate('somepassword')
# => false
# app/models/concerns/your_own_named_concern.rb
module YourOwnNamedConcern
extend ActiveSupport::Concern
included do
def authenticate(*args)
if password_digest.nil?
false
else
super(*args)
end
end
end
end
# app/models/user.rb
class User < ApplicationRecord
include YourOwnNamedConcern
has_secure_password
end
# app/models/admin.rb
class Admin < ApplicationRecord
include YourOwnNamedConcern
end
user = User.new
user.password_digest = nil
puts user.authenticate('somepassword')
# => false
admin = Admin.new
admin.password_digest = nil
puts admin.authenticate('somepassword')
# => false
Upvotes: 1