require 'devise/strategies/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 Authenticatable extend ActiveSupport::Concern included do attr_reader :password, :current_password attr_accessor :password_confirmation 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) password_digest(incoming_password) == self.encrypted_password end # Verifies whether an +incoming_authentication_token+ (i.e. from single access URL) # is the user authentication token. def valid_authentication_token?(incoming_auth_token) incoming_auth_token == self.authentication_token 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) params.delete(:password) if params[:password].blank? params.delete(:password_confirmation) if params[:password_confirmation].blank? result = if valid_password?(current_password) update_attributes(params) else self.errors.add(:current_password, current_password.blank? ? :blank : :invalid) self.attributes = params false end clean_up_passwords unless result result end protected # 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