Devise - uninitialized constant User::PasswordHistory - ruby-on-rails

I have a rails 3 application that I am working on and have implemented devise. I have it working, and now I wish to extend it so that a user is unable to use a old password more than once. Found this functionality on github which to my suprise was good. Disallow previously passwords - Git Hub
I thought this would straight forward but it is clearly not. My code looks like the following:
create_passwrod_histories.rb
class CreatePasswordHistories < ActiveRecord::Migration
def self.up
create_table(:password_histories) do |t|
t.integer :user_id
t.string :encrypted_password
t.timestamps
end
end
def self.down
drop_table :password_histories
end
end
User.rb
class User < ActiveRecord::Base
include ActiveModel::Validations
has_many :roles_users
has_many :roles, :through => :roles_users
has_many :projects
has_many :password_histories
after_save :store_digest
# authorization include this in whichever model that will use ACL9
acts_as_authorization_subject
def has_role?(role_name, object=nil)
!! if object.nil?
self.roles.find_by_name(role_name.to_s) ||
self.roles.member?(get_role(role_name, nil))
else
method = "is_#{role_name.to_s}?".to_sym
object.respond_to?(method) && object.send(method, self)
end
end
def login(user)
post_via_redirect user_session_path, 'user[username]' => user.username, 'user[password]' => user.password
end
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable #:registerable,
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable, :timeoutable
acts_as_authorization_subject :association_name => :roles
attr_accessor :login
# Setup accessible (or protected) attributes for your model
attr_accessible :id, :login, :username, :full_name, :email, :password, :password_confirmation, :remember_me, :role_ids
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates_presence_of :username, :full_name
validates_format_of :username, :with => /^[-\w\._#]+$/i, :allow_blank => true, :message => "should only contain letters, numbers, or . - _ #"
validates_length_of :username, :minimum => 1, :allow_blank => true
validates_uniqueness_of :username, :email
validates :email, :presence => true,
:format => { :with => email_regex }
validates :password, :unique_password => true
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
end
private
def store_digest
if encrypted_password_changed?
PasswordHistory.create(:user => self, :encrypted_password => encrypted_password)
end
end
end
unique_password_validator.rb
require 'bcrypt'
class UniquePasswordValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.password_histories.each do |ph|
bcrypt = ::BCrypt::Password.new(ph.encrypted_password)
hashed_value = ::BCrypt::Engine.hash_secret([value, Devise.pepper].join, bcrypt.salt)
record.errors[attribute] << "has been used previously." and return if hashed_value == ph.encrypted_password
end
end
end
I then run my app and try to use the same password. It then throws up the follwoing error uninitialized constant User::PasswordHistory

The only way that I can see from your code why that would be happening is if you didn't have the PasswordHistory model object. That code from Github doesn't actually explicitly tell you to do it, but you certainly need it. So, maybe you created and ran the migration but forgot to create the model, as in:
class PasswordHistory < ActiveRecord::Base
...
end

Related

Before validation method breaking spec with 'undefined method downcase'

In my User model I have a before_validation method called normalize_params that uses downcase.
class User < ApplicationRecord
before_validation :normalize_params, on: [:create, :update]
validates :email, :presence => true
validates :email, :uniqueness => true
validates :username, :presence => true
validates :username, :uniqueness => true
validates_confirmation_of :password
scope :all_users_except, -> (current_user) {
where.not(id: current_user)
}
has_attached_file :avatar, styles: { medium: "300x300>", thumb:
"100x100>" }, default_url: "/images/missing.png"
validates_attachment_content_type :avatar, content_type:
/\Aimage\/.*\z/
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
private
def normalize_params
self.name = name.downcase.titleize
self.email = email.downcase
self.home = home.downcase.titleize
end
end
All of that works perfectly in my app but my tests break when they hit the downcase with this error...
NoMethodError:
undefined method `downcase' for nil:NilClass
Here are my tests...
require 'rails_helper'
describe User, 'validation' do
it { should validate_presence_of :email }
it { should validate_presence_of :username }
it { should validate_presence_of :password }
end
If I take before_validation and normalize_params out then my tests pass.
As per the documentation exemplifies, you could use attribute_present? before:
class User < ApplicationRecord
before_validation :normalize_params, on: %i[create update]
validates :email, presence: true, uniqueness: true
validates :username, presence: true, uniqueness: true
private
def normalize_params
titleize_name
downcase_email
# You can any other here.
end
def titleize_name
self.name = name.downcase.titleize if attribute_present? 'name'
end
def downcase_email
self.email = email.downcase if attribute_present? 'email'
end
end
Note you can:
Use %i[] to create an array of symbols.
Validates presence and uniqueness by separating them by comma.
Prefer the is_expected.to than should syntax (it { is_expected.to validate_presence_of :attribute })
Separate each of your attribute modifications to easily work and include them.
Avoid using hash rocket when not needed. See ruby-style-guide#hash-literals.
Either one of name, email or home may be nil. I'd recommend using the safe navigation operator:
def normalize_params
self.name = name&.downcase&.titleize
self.email = email&.downcase
self.home = home&.downcase&.titleize
end

Error with `check_validity!'

I'm getting this error when trying to migrate. I've looked into my user.rb but I don't see how validate_format_of is causing an error. Below is my user.rb and error log:
ArgumentError: Either :with or :without must be supplied (but not both)
/Users/admin/.rvm/gems/ruby-2.2.0/gems/activemodel-4.1.6/lib/active_model/validations/format.rb:17:in `check_validity!'
/Users/admin/.rvm/gems/ruby-2.2.0/gems/activemodel-4.1.6/lib/active_model/validator.rb:157:in `initialize'
/Users/admin/.rvm/gems/ruby-2.2.0/gems/activemodel-4.1.6/lib/active_model/validations/with.rb:89:in `new'
/Users/admin/.rvm/gems/ruby-2.2.0/gems/activemodel-4.1.6/lib/active_model/validations/with.rb:89:in `block in validates_with'
/Users/admin/.rvm/gems/ruby-2.2.0/gems/activemodel-4.1.6/lib/active_model/validations/with.rb:88:in `each'
/Users/admin/.rvm/gems/ruby-2.2.0/gems/activemodel-4.1.6/lib/active_model/validations/with.rb:88:in `validates_with'
/Users/admin/.rvm/gems/ruby-2.2.0/gems/activemodel-4.1.6/lib/active_model/validations/format.rb:109:in `validates_format_of'
/Users/admin/Documents/workspace/ruby_on_rails/zoan/app/models/user.rb:19:in `<class:User>'
/Users/admin/Documents/workspace/ruby_on_rails/zoan/app/models/user.rb:1:in `<top (required)>'
Model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
TEMP_EMAIL_PREFIX = 'main#gmail'
TEMP_EMAIL_REGEX = /\Amain#gmail/
attr_accessor :login
self.per_page = 20
extend FriendlyId
friendly_id :username, use: [:slugged, :finders]
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
validates :name, presence: true, length: { maximum: 100 }
validates :username, presence: true, length: { maximum: 20 }, :uniqueness => { case_sensitive: false }
validates_format_of :email, on: :update
has_many :tweets
has_many :relationships
has_many :friends, through: :relationships
has_many :inverse_relationships, class_name: "Relationship", foreign_key: "friend_id"
has_many :inverse_friends, through: :inverse_relationships, source: :user
has_many :favorites
has_many :votes
has_many :retweets, foreign_key: "retweeter_id"
mount_uploader :avatar, AvatarUploader
mount_uploader :cover, CoverUploader
# def self.find_for_database_authentication(warden_conditions)
# conditions = warden_conditions.dup
# if login = conditions.delete(:login)
# where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
# else
# where(conditions).first
# end
# end
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
# Create the user if needed
if user.nil?
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via UsersController.finish_signup
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email if email_is_verified
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
user = User.new(
name: auth.extra.raw_info.name,
#username: auth.info.nickname || auth.uid,
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0,20]
)
user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && self.email !~ TEMP_EMAIL_REGEX
end
end
Check out the example on the docs: http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates_format_of
Basically you need to provide validates_format_of with a regular expression so it has something to compare your string with.
You can provide the regular expression by using the :with option.
In your example, to validate for an email upon updating, do this:
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :update
That regular expression takes care of most email formats: http://rubular.com/r/YEPtKO3j5L
validates_format_of :email, on: :update
You have to specify how to check email. Use "with"

Creating an empty profile on devise registration

I have used devise for authentication in a rails app and I want a user to be able to sign up and once signed up be able to edit their own profile. I've followed along with other answers on stack but when I try and register using the default devise registration form I'm getting this error.
NoMethodError in Devise::RegistrationsController#create
undefined method `create' for nil:NilClass
app/models/user.rb:17:in `create_profile'
My User.rb is as follows
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile
attr_accessible :login, :username, :email, :password, :password_confirmation, :remember_me, :profile_attributes
attr_accessor :login
accepts_nested_attributes_for :profile
validates :username, :uniqueness => { :case_sensitive => false }, :presence => true
after_create :create_profile
def create_profile
self.profile.create
end
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where(["username = :value OR lower(email) = lower(:value)", { :value => login }]).first
else
where(conditions).first
end
end
end
Profile.rb is
class Profile < ActiveRecord::Base
attr_accessible :fax, :phone_1, :phone_2, :url
belongs_to :user
end
profiles controller is the norm apart from the edit action
def edit
#profile = current_user.profile
end
You need to use self.create_profile instead of self.profile.create for has_one association.
Example
An Account class declares has_one :beneficiary, which will add:
Account#beneficiary (similar to Beneficiary.where(account_id: id).first)
Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save)
Account#build_beneficiary (similar to Beneficiary.new("account_id" => id))
Account#create_beneficiary (similar to b = Beneficiary.new("account_id" => id); b.save; b)
Account#create_beneficiary! (similar to b = Beneficiary.new("account_id" => id); b.save!; b)
More docs here

Devise before_save filter not applying

Before my User's can register I need to authenticate them via api first to see if their information is valid. Anyhow I have my validate_api method working as it needs to be I have tested this however, I'm not sure as to why when i try to register with a faulty api it still saves the user.
I put my method in a controller and called it with a valid api and it returned true and then with faulty api and it returned false.
So if the method is working it's either being ignored or something overrides it.
My User model
class User < ActiveRecord::Base
attr_accessor :login
before_save :validate_api
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :authentication_keys => [:login]
validates :username, :presence => true, :length => { :minimum => 6, :maximum => 255 }
validates :apiid, :presence => true, :numericality => { :only_integer => true }
validates :vcode, :presence => true, :length => { :minimum => 20, :maximum => 255 }
# Setup accessible (or protected) attributes for your model
attr_accessible :login, :username, :group, :apiid, :vcode, :email, :password, :password_confirmation, :remember_me
# Check if user is banned before login
def active_for_authentication?
super && self.banned == 0
end
# Redefine authentication procedure to allow login with username or email
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login).downcase
#where(conditions).where('$or' => [ {:username => /^#{Regexp.escape(login)}$/i}, {:email => /^#{Regexp.escape(login)}$/i} ]).first
where(conditions).where("username = '#{login}' OR email = '#{login}'").first
else
where(conditions).first
end
end
# Validate API information
private
def validate_api
require 'nokogiri'
require 'open-uri'
uri = "https://*******?keyID=#{self.apiid}&vCode=#{self.vcode}"
xml = Nokogiri::XML(open(uri))
xml.xpath("//row").each do |row|
if row['****'].downcase == '****'
return true
else
return false
end
end
end
end
Instead of using before_save :validate_api you should be using validate :check_api, and then adding an error message (eg: errors[:apiid] << "must be a valid API id.") if the api check fails.

Admin role isn't being assigned in my seed data when I have roles in my model?

In my seed file I am trying to create 3 users, 1 admin and 2 default users but it keeps assigning all 3 users to the default role before creation. Here is my code:
User.rb
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me, :username
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
has_many :user_prices
has_many :products, :through => :user_prices
validates_presence_of :username, :email, :password, :password_confirmation
validates_format_of :username, :with => /\A[a-z0-9]{5,20}\z/i
validates_uniqueness_of :username, :email
before_create :setup_default_role_for_new_users
ROLES = %w[admin default]
private
def setup_default_role_for_new_users
if self.role.blank?
self.role = "default"
end
end
end
Seed.rb
puts 'Loading seed data now....'
user1 = User.create(:email => 'admin#email.com', :role => 'admin')
user2 = User.create(:email => 'user1#email.com')
user3 = User.create(:email => 'user2#email.com')
puts 'Users added'
I know user2 and user3 will have the default role but user1 should be admin. How is this done?
since :role isnt in your accessible attributes, its protected from mass assignment, which is what you are doing in your seed file.
so in order to set role, you can use something like this
user1 = User.create(:email => 'admin#email.com')
user1.update_attribute(:role, 'admin')
Use if not unless:
def setup_default_role_for_new_users
if self.role.blank? # if not unless
self.role = "default"
end
end

Resources