require 'devise/strategies/database_authenticatable' module Devise module Models # Authenticable Module, responsible for encrypting password and validating # authenticity of a user while signing in. # # Configuration: # # You can overwrite configuration values by setting in globally in Devise, # using devise method or overwriting the respective instance method. # # pepper: encryption key used for creating encrypted password. Each time # password changes, it's gonna be encrypted again, and this key # is added to the password and salt to create a secure hash. # Always use `rake secret' to generate a new key. # # stretches: defines how many times the password will be encrypted. # # encryptor: the encryptor going to be used. By default :sha1. # # authentication_keys: parameters used for authentication. By default [:email] # # Examples: # # User.authenticate('email@test.com', 'password123') # returns authenticated user or nil # User.find(1).valid_password?('password123') # returns true/false # module DatabaseAuthenticatable def self.included(base) base.class_eval do extend ClassMethods attr_reader :password, :current_password attr_accessor :password_confirmation end end # TODO Remove me in next release def old_password ActiveSupport::Deprecation.warn "old_password is deprecated, please use current_password instead", caller @old_password end # Regenerates password salt and encrypted password each time password is set, # and then trigger any "after_changed_password"-callbacks. def password=(new_password) @password = new_password if @password.present? self.password_salt = self.class.encryptor_class.salt self.encrypted_password = password_digest(@password) end end # Verifies whether an incoming_password (ie from sign in) is the user password. def valid_password?(incoming_password) Devise.secure_compare(password_digest(incoming_password), self.encrypted_password) end # Checks if a resource is valid upon authentication. def valid_for_authentication?(attributes) valid_password?(attributes[:password]) end # Set password and password confirmation to nil def clean_up_passwords self.password = self.password_confirmation = nil end # Update record attributes when :current_password matches, otherwise returns # error on :current_password. It also automatically rejects :password and # :password_confirmation if they are blank. def update_with_password(params={}) current_password = params.delete(:current_password) if params[:password].blank? params.delete(:password) params.delete(:password_confirmation) if params[:password_confirmation].blank? end result = if valid_password?(current_password) update_attributes(params) else message = current_password.blank? ? :blank : :invalid self.class.add_error_on(self, :current_password, message, false) self.attributes = params false end clean_up_passwords unless result result end protected # Checks whether a password is needed or not. For validations only. # Passwords are always required if it's a new record, or if the password # or confirmation are being set somewhere. def password_required? new_record? || !password.nil? || !password_confirmation.nil? end # Digests the password using the configured encryptor. def password_digest(password) self.class.encryptor_class.digest(password, self.class.stretches, self.password_salt, self.class.pepper) end module ClassMethods Devise::Models.config(self, :pepper, :stretches, :encryptor, :authentication_keys) # Authenticate a user based on configured attribute keys. Returns the # authenticated user if it's valid or nil. def authenticate(attributes={}) return unless authentication_keys.all? { |k| attributes[k].present? } conditions = attributes.slice(*authentication_keys) resource = find_for_authentication(conditions) resource if resource.try(:valid_for_authentication?, attributes) end # Returns the class for the configured encryptor. def encryptor_class @encryptor_class ||= ::Devise::Encryptors.const_get(encryptor.to_s.classify) end protected # Find first record based on conditions given (ie by the sign in form). # Overwrite to add customized conditions, create a join, or maybe use a # namedscope to filter records while authenticating. # Example: # # def self.find_for_authentication(conditions={}) # conditions[:active] = true # find(:first, :conditions => conditions) # end # def find_for_authentication(conditions) find(:first, :conditions => conditions) end end end end end