I'm having trouble with devise and password confirmation.
I've created a model test in rails4 which looks like this:
test "user requires a password_confirmation" do
user = FactoryGirl.build(:user)
assert user.valid?, "FactoryGirl should return a valid user"
user.password_confirmation = nil
# These are just some outputs to verify what's going on.
# This is the condition in devises's password_required? method
puts !user.persisted? || !user.password.nil? || !user.password_confirmation.nil? #=> true
puts user.valid? #=> true
assert user.invalid?, "Password confirmation should be required" # Fails here
assert_equal 1, user.errors.size, "Only one error should have occured. #{user.errors.full_messages}"
end
This test fails at second assert ("Password confirmation should be required").
Here's my full User model :
class User < ActiveRecord::Base
has_one :user_entity
has_one :manager,through: :user_entity, source: :entity, source_type: 'Manager'
has_one :client, through: :user_entity, source: :entity, source_type: 'Client'
# Adds default behavior. See: https://github.com/stanislaw/simple_roles#many-strategy
simple_roles
validates :username,
presence: true,
length: { minimum: 4, allow_blank: true, if: :username_changed? },
uniqueness: { allow_blank: true, if: :username_changed? },
format: { with: /\A[A-z_0-9]+\z/, allow_blank: true, if: :username_changed? }
# Include default devise modules. Others available are:
# :token_authenticatable, :timeoutable, :omniauthable, :registerable
devise :database_authenticatable, :recoverable, :rememberable,
:trackable, :validatable, :confirmable, :lockable
# Virtual attribute for authenticating by either username or email
# This is in addition to a real persisted field like 'username'
attr_accessor :login
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
# User need to be active
conditions[:active] = true
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 entity
self.user_entity.try(:entity)
end
def entity=(newEntity)
self.build_user_entity(entity: newEntity)
end
end
I've tried adding in my user model :
validates :password, confirmation: true
After that the test passes but i get an error on the next one:
1) Failure:
UserTest#test_user_requires_a_password [/my/project/test/models/user_test.rb:52]:
Two errors should have occured. ["Password confirmation doesn't match confirmation", "Password confirmation doesn't match confirmation", "Password can't be blank"].
Expected: 2
Actual: 3
As you can see, it's like the confirmation validation occurs two times and fails both time.
I'm using rails4 (edge) and the rails4 branch for devise.
EDIT:
I tried setting
user.password_confirmation = "#{user.password}_diff"
And the test passes. So why is confirmation ignored when nil is set ? As the object is not yet persisted i would assume a password confirmation has to be provided.
I know this is an old question but I was having the same problem.
First, remove this from your model so you don't get duplicate validation checks:
validates :password, confirmation: true
Then add the following. I changed it to only work on create:
validates :password_confirmation, presence: true, on: :create
See the validates_confirmation_of section on this apidock page:
NOTE: This check is performed only if password_confirmation is not nil. To require confirmation, make sure to add a presence check for the confirmation attribute:
validates_presence_of :password_confirmation, if: :password_changed?
Related
Everything was set up fine and seemed to be working. Suddenly I have an issue where if I log out and then log back in again with a different username it just logs me back in always as the first user.
Example:
user one - first#domain.com / password1
user two - second#domain.com / password2
Even if I log out and then log back in again as user two (verified as signed up correctly) it will log me in as user one.
Here is my user.rb file
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
acts_as_voter
has_many :links
has_many :comments
# Virtual attribute for authenticating by either username or email
# This is in addition to a real persisted field like 'username'
attr_accessor :login
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions.to_h).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
elsif conditions.has_key?(:username) || conditions.has_key?(:email)
where(conditions.to_h).first
end
conditions[:email].downcase! if conditions[:email]
where(conditions.to_h).first
end
validates :username, presence: :true, uniqueness: { case_sensitive: false }
validates_format_of :username, with: /^[a-zA-Z0-9_\.]*$/, :multiline => true
validate :validate_username
def validate_username
if User.where(email: username).exists?
errors.add(:username, :invalid)
end
end
end
Application Controller
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
added_attrs = [:username, :email, :password, :password_confirmation, :remember_me]
devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
devise_parameter_sanitizer.permit :account_update, keys: added_attrs
end
end
You might want to check your controllers. If you're determining the logged in user by using anything other than current_user, you may have used the wrong query or perhaps were using a specific user for testing purposes.
Check your User model. You're using :rememberable and might be passing a session cookie without realizing.
I have three models user (author), which is incorporating devise logic:
app/models/user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :questions
has_many :answers
end
question:
app/models/question.rb
# Model for Question
class Question < ActiveRecord::Base
has_many :answers, dependent: :destroy
belongs_to :author, class_name: 'User', foreign_key: 'user_id'
validates :title, presence: true, length: { maximum: 100 }
validates :body, presence: true, length: { minimum: 10 }
validates :author, presence: true
end
and answer:
app/models/answer.rb
# Model for Answer
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :author, class_name: 'User', foreign_key: 'user_id'
validates :body, presence: true, length: { minimum: 10 }
validates :question_id, presence: true
validates :author, presence: true
end
and their factories:
spec/factories/users.rb
FactoryGirl.define do
sequence :email do |n|
"email-#{n}#example.com"
end
sequence :password do |n|
"testpassword#{n}"
end
factory :user, aliases: [:author] do
email
# tried sequence generator and fixed password - both have no impact on result
# password '1234567890'
# password_confirmation '1234567890'
password
end
end
spec/factories/answers.rb
FactoryGirl.define do
factory :answer do
body 'Answer Body'
author
question
end
factory :nil_answer, class: 'Answer' do
question
body nil
end
end
spec/factories/questions.rb
FactoryGirl.define do
factory :question do
title 'Question Title'
body 'Question Body'
author
factory :question_with_answers do
after(:create) do |question|
# changing create_list to create has no impact on result
# create_list(:answer, 2, question: question)
create(:answer, question: question)
end
end
end
end
test code:
spec/features/delete_answer_spec.rb
require 'rails_helper'
feature 'Delete answer', %q{
By some reason
As an authenticated user
I want to delete answer
} do
given(:question) { create(:question_with_answers) }
given(:user) { create(:user) }
given(:ans) { create(:answer) }
scenario 'Answer author password should not be nil' do
expect(question.answers.first.author.password).to_not be_nil
# question.author.password and ans.author.password return not nil
# I need password to do:
# visit new_user_session_path
# fill_in 'Email', with: user.email
# fill_in 'Password', with: user.password
# click_on 'Log in'
end
end
Can anyone explain why the following given statement:
given(:question) { create(:question_with_answers) }
creates question object that:
question.author.password #=> '1234567890'
but:
question.answers.first.author.password #=> nil
why method "create" instantiates author of question properly (field password is set), but "create_list" inside "after" callback creates author in answer with nil fields?
rails 4.2.5, ruby 2.3.0, devise 3.5.6, warden 1.2.6, factory_girls_rails 4.6.0 (4.5.0)
Devise (and most authentication libraries) encrypt the password and don't allow you to access passwords from models retrieved from the database. The password may be temporarily available through an in-memory reader method, but won't be available if you retrieve the record from the database.
If you do:
user = User.new(password: "example")
p user.password
I'm guessing you'll see "example".
But if you do:
user = User.first
p user.password
I bet you'll see nil (assuming you have user records in your database).
When you query an association proxy like question.answers.first.author, it's going to the database again to find the answer and author. That means you're using a different instance, which no longer has the password available.
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"
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.
I am making a simple social network with Ruby on Rails. I wanted to add a restriction of certain characters for the profile name when signing up. So, in my User.rb file, I have the following:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
:first_name, :last_name, :profile_name
# attr_accessible :title, :body
validates :first_name, presence: true
validates :last_name, presence: true
validates :profile_name, presence: true,
uniqueness: true,
format: {
with: /^[a-zA-Z0-9_-]+$/,
message: "must be formatted correctly."
}
has_many :statuses
def full_name
first_name + " " + last_name
end
end
I set up a test to validate that it works, and this is what the test is:
test "user can have a correctly formatted profile name" do
user = User.new(first_name: '******', last_name: '****', email: '********#gmail.com')
user.password = user.password_confirmation = '**********'
user.profile_name = '******'
assert user.valid?
end
When I run the test, I keep getting the error saying that something is wrong with my assert user.valid? line. So I am thinking I messed up some syntax in my with: /^[a-zA-Z0-9_-]+$/.
The error I am getting is 1) Failure:
test_user_can_have_a_correctly_formatted_profile_name(UserTest) [test/unit/user_test.rb:40]:
But on line 40, it has this piece of code assert user.valid?
Any help is appreciated :)
So I am thinking I messed up some syntax in my with regexp.
Your syntax is fine.
However, your error message clearly shows you're using a profile name that doesn't match.
Are you using some other character in a profile name, such as a space? Or period?
Try it like this:
/^[a-zA-Z0-9_-]+$/.match "foobar" #=> #<MatchData "foobar">
If the data fails to match, you'll get nil:
/^[a-zA-Z0-9_-]+$/.match "foo bar" #=> nil