module NaClPassword::Concern::ClassMethods
Public Instance Methods
Adds methods to set and authenticate against an Argon2 password. This mechanism requires you to have a XXX_digest
attribute. Where XXX
is the attribute name of your desired password. the digest
attribute to use can be set by passing `digest_attribute: non_standard_attribute` to `nacl_password`
The following validations are added automatically:
-
Password must be present on creation
-
Password length should be less than or equal to 1024 bytes
-
Confirmation of password (using a
XXX_confirmation
attribute)
If confirmation validation is not needed, simply leave out the value for XXX_confirmation
(i.e. don't provide a form field for it). When this attribute has a nil
value, the validation will not be triggered.
It is also possible to suppress the default validations completely by passing skip_validations: true as an argument.
Add rbnacl (~> 7.1) to Gemfile to use nacl_password
:
gem "rbnacl", "~> 7.1"
Example:
# Schema: User(name:string, password_digest:string, recovery_password_digest:string) class User < ActiveRecord::Base include NaClPassword::Concern nacl_password nacl_password :recovery_password, validations: false end user = User.new(name: 'david', password: '', password_confirmation: 'nomatch') user.save # => false, password required user.password = 'mUc3m00RsqyRe' user.save # => false, confirmation doesn't match user.password_confirmation = 'mUc3m00RsqyRe' user.save # => true user.recovery_password = "42password" user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC" user.save # => true user.authenticate('notright') # => false user.authenticate('mUc3m00RsqyRe') # => user user.authenticate_recovery_password('42password') # => user User.find_by(name: 'david')&.authenticate('notright') # => false User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user
# File lib/nacl_password/concern.rb, line 57 def nacl_password(attribute = :password, digest_attribute: nil, **opts) NaClPassword.setup digest_attribute ||= "#{attribute}_digest" attribute = attribute.to_sym digest_attribute = digest_attribute.to_sym if digest_attribute.to_s == attribute.to_s raise ArgumentError, "Digest Attribute Name can't be the same as Password Attribute Name" end skip_validations = CoerceBoolean.from(opts[:skip_validations]) && (opts[:skip_validations] != :blank) length_options = skip_validations ? {} : { maximum: NaClPassword::MAX_PASSWORD_LENGTH }. merge( opts[:min_length] == :none \ ? {} \ : { minimum: opts[:min_length].presence&.to_i || 8 } ) include InstanceMethodsOnActivation.new(attribute.to_sym, digest_attribute, **length_options) unless skip_validations include ActiveModel::Validations # This ensures the model has a password by checking whether the password_digest # is present, so that this works with both new and existing records. However, # when there is an error, the message is added to the password attribute instead # so that the error message will make sense to the end-user. unless opts[:skip_validations] == :blank validate do |record| unless record.__send__(digest_attribute).present? record.errors.add(attribute, :blank) end end end validates_length_of attribute, **length_options, allow_blank: true validates_confirmation_of attribute, allow_blank: true end end