Can't disable validation of email in devise-auth-token - ruby-on-rails

I am using devise-auth-token and want to sign in with field another than email.
So I used phone_number instead and everything is working properly but I can't remove validation on email, here are my codes
user.rb
class User < ActiveRecord::Base
include DeviseTokenAuth::Concerns::User
devise :database_authenticatable, :confirmable, authentication_keys: [:phone_number]
mount_uploader :avatar, AvatarUploader
validates :first_name, presence: true,format: {with: /[[:word:]]/}
validates :last_name , presence: true,format: {with: /[[:word:]]/}
validates :email , format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }, uniqueness: true
validates :phone_number, presence: true, uniqueness: true
validate :birthdate_in_the_future_invalid
validates :country_code, presence: true
validates :birthdate, presence: true
validates :gender, presence: true
# validates :file, presence: true
validates :phone_number, phone: { possible: true, types: [:mobile], country_specifier: -> phone { phone.country_code.try(:upcase) } }
validate :e164_phone_number
after_create :set_provider_uid
def email_required?
false
end
def email_changed?
false
end
def password_required?
super if confirmed?
end
def password_match(password, password_confirmation)
self.errors[:password] << "can't be blank" if password.blank?
self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
password == password_confirmation && !password.blank?
end
private
def birthdate_in_the_future_invalid
begin
if Date.strptime(birthdate, '%d-%m-%Y') > Date.today
errors.add(:birthdate, "in_the_future")
end
rescue
errors.add(:birthdate, "invalid format")
end
end
def e164_phone_number
if phone_number[0] != '+'
errors.add(:phone_number, "invalid format")
end
end
def set_provider_uid
self.update_column(:uid, phone_number)
self.update_column(:provider, "phone_number")
end
end
and this is my initializers/devise.rb
Devise.setup do |config|
config.mailer_sender = 'aliabdelrahmanweka74#gmail.com'
require 'devise/orm/active_record'
config.authentication_keys = [:phone_number]
config.case_insensitive_keys = [:phone_number]
config.strip_whitespace_keys = [:phone_number]
config.skip_session_storage = [:http_auth]
config.stretches = Rails.env.test? ? 1 : 11
config.reconfirmable = false
config.expire_all_remember_me_on_sign_out = true
config.password_length = 6..128
config.email_regexp = /\A[^#\s]+#[^#\s]+\z/
config.reset_password_within = 6.hours
config.sign_out_via = :delete
end
I used funciton email_required? and make it return false but nothing happens
I call localhost:3000/users , Method Post
params:
{
"user": {
"first_name": "ali",
"last_name": "weka",
"gender": "male",
"birthdate": "25-06-2016",
"email": "",
"phone_number": "+201118574649",
"country_code": "EG",
"uid": "+201118574649",
"provider": "phone_number"
}
}
and here's the response
{"status":"error","data":{"id":null,"first_name":"ali","last_name":"ali","country_code":"EG","phone_number":"+201119574649","gender":"male","birthdate":"25-06-2016","created_at":null,"updated_at":null,"provider":"email","uid":"+201119574649","avatar":{"url":null},"email":""},"errors":{"email":["can't be blank","is invalid"],"full_messages":["Email can't be blank","Email is invalid"]}}
so any help, please?

I had the same problem. I wanted some users with email, and some without.
Solution
You should first remove the null: false restriction from users table with a migration. Should you want to remove the default "", do it to. Don't remove the index, so it still validates that emails are unique (in case you still use emails sometimes). Null values won't have a problem with the unique constraint.
If you are using devise validatable, add this method in your User model:
def email_required?
false
end
After doing the previous part, you should do one of the following:
Option 1:
Going deep into devise_token_auth gem, at least in my case, I realized that this concern was being included.
It's included by default unless you modify the DeviseTokenAuth initializer with
config.default_callbacks = false
But maybe you don't want that.
Option 2:
If you check the code in the concern I linked, you can see that it validates email in case the provider of the user is 'email'.
The fix in my case was to change the provider field to something else for the users that didn't have email.
With any of those 2 changes, your email won't be validated anymore.
For somebody who needs email sometimes:
Of course, do what Devise wiki says
apart from the other field include email in case_insensitive_keys, strip_whitespace_keys
and you can have a method in the User model with:
def email_required?
true unless phone_number?
end

Related

Rails Validation with two attributes of a variable

I really need your help, because I can't find a solution on my own since 2 hours. I tried many different approaches, but I just don't get how I can validate an attribute of a variable by using another attribute as a condition.
This was my last try:
class User < ApplicationRecord
validates :admin, absence: true,
if: ["student?", :email]
def student?(user)
user.email.include? 'stud'
end
end
I want to validate, that a user with the attribute "admin" can't have an email address, which includes 'stud'. Even better would be a validation, that 'stud' users can't be admin.
When I run db:seed I get this error, which stays if I put the 'stud' in brackets:
rails aborted!
ArgumentError: wrong number of arguments (given 0, expected 1)
/home/studi/Bachelorarbeitsapp/sample_app/app/models/user.rb:22:in `student?'
Please have a look at this issue!
I am willing to share any other code, which is necessary to solve the problem. Thanks!
Here is the full helper code:
class User < ApplicationRecord
has_many :preferences, dependent: :destroy
accepts_nested_attributes_for :preferences
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
has_secure_password
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#(stud.uni-hannover.de|wiwi.uni-hannover.de)+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: {case_sensitive: false}
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
validates :mat_number, presence: true, length: { minimum: 7, maximum: 8 }, numericality: { only_integer: true }
validates :admin, absence: true,
if: ["student?", :email]
def student?(user)
user.email.include? 'stud'
end
validates :ects, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 170, only_integer: true }, allow_nil: true
validates :grade_avg, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 4 }, allow_nil: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Activates an account.
def activate
update_columns(activated: true, activated_at: Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
Based on the Active Record Validations Guide section 6.2 Custom Methods, it seems like you should be able to do something like:
class User < ApplicationRecord
validate :admin_cant_be_student
def admin_cant_be_student
if admin and email.include?('stud')
errors.add(:admin, "can't have a student email address")
end
end
end
You don’t need a custom validation:
Could use validates_exclusion_of
validates_exclusion_of :email, in: %w( stud ), if: -> { |user| user.email.include? “admin” }, message: “admins can’t have stud in email”
validates_exclusion_of :email, in: %w( admin ), if: -> { |user|user.email.include? “stud” }, message: “students can’t have admin in email”

Single Table Inheritance Errors - ActiveRecord::SubclassNotFound

My intent is to implement STI with two types: Staff and Clinician. My previous implementation was using roles with enums, and after doing my best to follow answers to similar questions, take out all references in tests etc. to enum roles and replace with references to types , I am getting many versions of the following error when I run my testing suite:
ERROR["test_valid_signup_information_with_account_activation", UsersSignupTest, 1.01794000000001]
test_valid_signup_information_with_account_activation#UsersSignupTest (1.02s)
ActiveRecord::SubclassNotFound: ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'Staff'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite User.inheritance_column to use another column for that information.
app/controllers/users_controller.rb:19:in `create'
test/integration/users_signup_test.rb:27:in `block (2 levels) in <class:UsersSignupTest>'
test/integration/users_signup_test.rb:26:in `block in <class:UsersSignupTest>'
Here are a couple areas where I am confused that could potentially be hiding issues:
In my user model user.rb, I think I am defining the sub Classes correctly (Staff and Clinician), but I'm unsure if I'm wrapping everything correctly. Does all the other code have to be contained in one of these classes? Am I misusing "end"?
class User < ApplicationRecord
end
class Staff < User
end
class Clinician < User
end
belongs_to :university
has_many :referral_requests
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :type, presence: true
validates :university_id, presence: true, if: lambda { self.type == 'Staff' }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
def feed
ReferralRequest.where("user_id = ?", id)
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
Here's the specific test code that is failing (one of many in the test suite that are failing - all the user parameters are defined similarly though). Am I passing the staff parameter appropriately?
test "valid signup information with account activation" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, params: { user: { name: "Example User",
email: "user#example.com",
university_id: 1 ,
type: "Staff",
password: "password",
password_confirmation: "password" } }
Here is my users table schema:
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "remember_digest"
t.string "activation_digest"
t.boolean "activated", default: false
t.datetime "activated_at"
t.string "reset_digest"
t.datetime "reset_sent_at"
t.integer "university_id"
t.integer "role"
t.string "type"
t.index ["email"], name: "index_users_on_email", unique: true
end
Thanks very much for any ideas! I ask a lot of questions on here but it's only after trying to work through similar answers for quite a while.
Assuming that the code sample above is accurate, you are seeing this error because the user.rb file is invalid Ruby and failing to parse. You should also be seeing an interpreter error about that.
class User < ApplicationRecord
belongs_to :university
has_many :referral_requests
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :type, presence: true
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
# etc...
end
class Staff < User
validates :university_id, presence: true
end
class Clinician < User
end
Standard class-inheritance practices apply, so if there is code in there that is only appropriate for a specific subclass it should move there (e.g. university_id validation moving to Staff).
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
These should be written as
def self.digest(string)
# ...
end
def self.new_token
# ...
end
or, alternatively,
class << self
def digest(string)
# ...
end
def new_token
# ...
end
end

Device cannot login

I have a simple user model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,:confirmable, :validatable
include BCrypt
#attr_accessor :password, :password_confirmation
has_many :deals
has_many :charges
has_one :menu
has_many :vouchers
has_one :authentication
has_many :restaurant_tags
has_many :restaurant_hours
#validates :login, uniqueness: true
#validates :login, presence: true
validates :password, presence: { on: :create }
validates :password, confirmation: true
#validates :login, format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create, message: "must be a valid email address."}
validates :role, inclusion: { in: ["admin", "client", "restaurant"] }
#before_save :encrypt_password
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.crypted_password = BCrypt::Engine.hash_secret(password, salt)
end
end
def self.authenticate(login, password)
user = find_by_login(login)
if user && user.crypted_password == BCrypt::Engine.hash_secret(password, user.salt)
user
else
nil
end
end
def restaurant?
role == "restaurant"
end
def client?
role == "client"
end
def admin?
role == "admin"
end
end
and devise.rb
Devise.setup do |config|
config.mailer_sender = "please-change-me-at-config-initializers-devise#example.com"
require 'devise/orm/active_record'
config.authentication_keys = [ :email, :login]
config.case_insensitive_keys = [ :email, login]
config.strip_whitespace_keys = [ :email, login ]
config.skip_session_storage = [:http_auth]
config.stretches = Rails.env.test? ? 1 : 10
config.reconfirmable = true
config.password_length = 6..128
config.reset_password_within = 6.hours
config.sign_out_via = :delete
end
I can sign up successfully and after confirmation I can see I am logged in but when I sign out and then try to login I am face error Completed 401 Unauthorized in 2ms. I have already spent whole day on this please help me out Thanks
I have solved the problem the devise was checking the login field for authenticate while I was using email in my views i just updated
config.authentication_keys = [:email]
config.case_insensitive_keys = [:email]
config.strip_whitespace_keys = [:email]
and this solve my problem thanks for helping
please update this method
def self.authenticate(login, password)
user = self.find_by_login(login)
if user && user.crypted_password == BCrypt::Engine.hash_secret(password, user.salt)
user
else
nil
end
end

Ruby on rails update client with and without new password

I have a edit form for a client. It is also possible to change the password, but of course you don't want to change(and/or reenter) your password every time you change on of the other settings. To avoid updating the password I tried this:
def client_update_params
if admin? == true
params.require(:client).permit(:name, :email,:company_name,
:address_street,:address_number,:address_city,
:address_zip,:address_country,:billing_informations)
else
if params[:client][:password].blank?
params[:client].delete("password")
params[:client].delete("password_confirmation")
params.require(:client).permit(:name, :email,:company_name,
:address_street,:address_number,:address_city,
:address_zip,:address_country)
else
params.require(:client).permit(:name, :email,:company_name,
:address_street,:address_number,:address_city,
:address_zip,:address_country,
:password,:password_confirmation)
end
end
end
So the idea is to check if the password field is set or not. If it is set, update with new password, else do not update the password. But every time I hit submit(and leave the password field empty), the form validation says the password is to short....
Is there maybe a working/more elegant solution for this problem ?
EDIT:VALIDATIONS ON MODEL:
attr_accessor :is_admin_applying_update
attr_accessor :plain_password
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :company_name,presence:true
validates :address_street,presence:true
validates :address_number,presence:true
validates :address_city,presence:true
validates :address_zip,presence:true
validates :address_country,presence:true
validates :billing_informations,presence:true
has_secure_password
validates :password, length: { minimum: 6 }, :unless => :is_admin_applying_update
def Client.new_remember_token
SecureRandom.urlsafe_base64
end
def Client.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = Client.encrypt(Client.new_remember_token)
end
Remember that you don't really have a password attribute in your model. The password is stored encrypted in a field named password_digest.
You should only be validating the password attribute when a password and a password_confirmation is given. You can do this in your model validation rather than deleting things from the params hash.
Also, you should validate the existence of a password_confirmation if password is present.
Add a virtual attribute skip_password like the devise uses
In model
attr_accessible :skip_password
validates :password, length: { minimum: 6 }, :unless => :skip_password
def skip_password=(value)
#skip = value
end
def skip_password
#skip
end
In controller
#client.skip_password = true

Devise and password_confirmation validation

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?

Resources