require 'digest/sha1' module Clearance module User # Hook for all Clearance::User modules. # # If you need to override parts of Clearance::User, # extend and include à la carte. # # @example # extend ClassMethods # include InstanceMethods # include AttrAccessor # include Callbacks # # @see ClassMethods # @see InstanceMethods # @see AttrAccessible # @see AttrAccessor # @see Validations # @see Callbacks def self.included(model) model.extend(ClassMethods) model.send(:include, InstanceMethods) model.send(:include, AttrAccessible) model.send(:include, AttrAccessor) model.send(:include, Validations) model.send(:include, Callbacks) end module AttrAccessible # Hook for attr_accessible white list. # # :email, :password, :password_confirmation # # Append other attributes that must be mass-assigned in your model. # # @example # include Clearance::User # attr_accessible :location, :gender def self.included(model) model.class_eval do attr_accessible :email, :password, :password_confirmation end end end module AttrAccessor # Hook for attr_accessor virtual attributes. # # :password, :password_confirmation def self.included(model) model.class_eval do attr_accessor :password, :password_confirmation end end end module Validations # Hook for validations. # # :email must be present, unique, formatted # # If password is required, # :password must be present, confirmed def self.included(model) model.class_eval do validates_presence_of :email validates_uniqueness_of :email, :case_sensitive => false validates_format_of :email, :with => %r{.+@.+\..+} validates_presence_of :password, :if => :password_required? validates_confirmation_of :password, :if => :password_required? end end end module Callbacks # Hook for callbacks. # # salt, token, password encryption are handled before_save. def self.included(model) model.class_eval do before_save :initialize_salt, :encrypt_password, :initialize_confirmation_token end end end module InstanceMethods # Am I authenticated with given password? # # @param [String] plain-text password # @return [true, false] # @example # user.authenticated?('password') def authenticated?(password) encrypted_password == encrypt(password) end # Am I remembered? # # @return [true, false] # @example # user.remember? def remember? remember_token && remember_token_expires_at && Time.now.utc < remember_token_expires_at end # Remember me for a year. # # @example # user.remember_me! # cookies[:remember_token] = { # :value => user.remember_token, # :expires => user.remember_token_expires_at # } def remember_me! remember_me_until! 1.year.from_now.utc end # Forget me. # # @example # user.forget_me! def forget_me! self.remember_token = nil self.remember_token_expires_at = nil save(false) end # Confirm my email. # # @example # user.confirm_email! def confirm_email! self.email_confirmed = true self.confirmation_token = nil save(false) end # Mark my account as forgotten password. # # @example # user.forgot_password! def forgot_password! generate_confirmation_token save(false) end # Update my password. # # @param [String, String] password and password confirmation # @return [true, false] password was updated or not # @example # user.update_password('new-password', 'new-password') def update_password(new_password, new_password_confirmation) self.password = new_password self.password_confirmation = new_password_confirmation if valid? self.confirmation_token = nil end save end protected def generate_hash(string) Digest::SHA1.hexdigest(string) end def initialize_salt if new_record? self.salt = generate_hash("--#{Time.now.utc}--#{password}--") end end def encrypt_password return if password.blank? self.encrypted_password = encrypt(password) end def encrypt(string) generate_hash("--#{salt}--#{string}--") end def generate_confirmation_token self.confirmation_token = encrypt("--#{Time.now.utc}--#{password}--") end def initialize_confirmation_token generate_confirmation_token if new_record? end def password_required? encrypted_password.blank? || !password.blank? end def remember_me_until!(time) self.remember_token_expires_at = time self.remember_token = encrypt("--#{time}--#{password}--") save(false) end end module ClassMethods # Authenticate with email and password. # # @param [String, String] email and password # @return [User, nil] authenticated user or nil # @example # User.authenticate("email@example.com", "password") def authenticate(email, password) return nil unless user = find_by_email(email) return user if user.authenticated?(password) end end end end