I'm having a problem matching user password using devise gem in rails. User password stored on my db which is encrypted_password and i am trying to find user by password, but I don't understand how to match password from form and encrypted_password in my db.
User.find_by_email_and_password(params[:user][:email], params[:user][:password])
I think this is a better, and more elegant way of doing it:
user = User.find_by_email(params[:user][:email])
user.valid_password?(params[:user][:password])
The other method where you generate the digest from the user instance was giving me protected method errors.
Use Devise Methods
Devise provides you with built-in methods to verify a user's password:
user = User.find_for_authentication(email: params[:user][:email])
user.valid_password?(params[:user][:password])
For Rails 4+ with Strong Params, you can do something like this:
def login
user = User.find_for_authentication(email: login_params[:email])
if user.valid_password?(login_params[:password])
user.remember_me = login_params[:remember_me]
sign_in_and_redirect(user, event: :authentication)
end
end
private
def login_params
params.require(:user).permit(:email, :password, :remember_me)
end
I think the better one will be this
valid_password = User.find_by_email(params[:user][:email]).valid_password?(params[:user][:password])
I would suggest this.
user = User.where("email=? OR username=?", email_or_username, email_or_username).first.valid_password?(user_password)
For 2022, devise is required to add :database_authenticatable to use valid_password? method
class User < ApplicationRecord
devise :xxxable, :yyyable, :database_authenticatable
But, if you need only to verify the entering password just go like this
class User < ApplicationRecord
devise :xxxable, :yyyable#, :database_authenticatable
def valid_password?(verifying_word)
password_digest_instance = BCrypt::Password.new(self.password_digest)
current_password_salt = password_digest_instance.salt
hashed_verifying_word_with_same_salt = BCrypt::Engine.hash_secret(verifying_word, current_password_salt)
Devise.secure_compare(hashed_verifying_word_with_same_salt, self.password_digest)
Then
user = User.find_by(email: params[:user][:email])
user = nil unless user.try(:valid_password?, params[:user][:password])
Related
In my application I have users and sites (think of it like users and organizations). There's a lookup table the sits between them called SiteUser. In SiteUser:
belongs_to :site
belongs_to :user
before_validation :set_user_id, if: ->() { email != nil }
def set_user_id
existing_user = User.find_by(email: email)
self.user = if existing_user.present?
UserMailer.notify_existing_user(site, existing_user).deliver_now unless Rails.env.test?
existing_user
else
User.invite!(email: email)
end
end
I need the subject of the email that gets generated to include the site name. "Example Company has invite you to join their site!"
And in the email body I also need the site title.
I am confused on how to get the site parameter over to devise invitable. As you can see in the code above, if the user who is being invited to the site already exists in our system, I use my own mailer in which I pass in the site and the existing_user so I have access to it in my mailer view.
class UserMailer < ApplicationMailer
def notify_existing_user(site, user)
#site = site
#user = user
mail to: #user.email, subject: "You've been given access to #{#site.title} in the Dashboard."
end
end
I cannot figure out how to accomplish this similarly with the devise invitable mailer, which is used when the user doesn't already exist in the system.
Any help you could provide would be incredibly appreciated!
The way I went about accomplishing this was to add a temporary/virtual attribute on User called invite_site_name. Because the problem was that site wasn't an attribute of User, so I could not send site in to User.invite!.
class User < ActiveRecord::Base
attr_accessor :invite_site_name
and then in my CustomDeviseMailer I had access to it:
class CustomDeviseMailer < Devise::Mailer
helper :application # gives access to all helpers defined within `application_helper`.
include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
default template_path: 'devise/mailer' # to make sure that your mailer uses the devise views
def invitation_instructions(record, token, opts={})
opts[:subject] = "#{record.invite_site_name} has invited you to their Dashboard! Instructions inside!"
super
end
end
You could always override devise's subject_for method. Found this on the gems issues, that also suggests another way:
https://github.com/scambra/devise_invitable/issues/660#issuecomment-277242853
Hope this helps!
I'd like to build a web app based on ruby on rails. For authentication I am using the devise gem. Everything is good : I can create accounts, login, logout etc.
But here I have an issue. I'd like to be able to sign up without providing password but still have the ability to register with password for another account.
I've set the password lenght from 0 to 128 on config/initializers/devise.rb
config.password_length = 0..128
But what are the next steps to do what I want?
Thank you
Ok, I reply here as answer.
Thanks to Ammar Shah i figured out how to get users with password and users without password.
First of all create a file in lib/devise/strategies (create the folders) named database_authenticatable.rb with following code :
require 'devise/strategies/authenticatable'
module Devise
module Strategies
# Default strategy for signing in a user, based on their email and password in the database.
class DatabaseAuthenticatable < Authenticatable
def authenticate!
if password.blank?
authentication_hash[:encrypted_password] = ''
end
resource = mapping.to.find_for_database_authentication(authentication_hash)
hashed = false
if validate(resource){ hashed = true; resource.valid_password?(password) }
remember_me(resource)
resource.after_database_authentication
success!(resource)
end
mapping.to.new.password = password if !hashed && Devise.paranoid
fail(:not_found_in_database) unless resource
end
end
end
end
Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable)
Then in the devise_create_user.rb migration add :
t.string :remember_token
Finally in user.rb model :
before_create :remember_value
def valid_password?(password)
if password.blank?
true
else
super
end
end
def password_required?
new_record? ? false : super
end
def remember_value
self.remember_token ||= Devise.friendly_token
end
Thank you Ammar Shah for helping me !
Reset password length in config/initializers/devise.rb to the default value and use this answer to make password optional.
Also, here is a complete implementation of gradual engagement feature in devise wiki. It depends what exactly you want you achieve.
I am using devise gem. Devise send reset password token in mail when user clicks on forget password link. User follow the link and reset his password by entering new password and confirm new password.
When I follow the same mail link again, it again allow the user to reset password in the same way as above.
Now, I want the reset password token to clear once it is used. So that when your follow the previously used send link from old mail, he must get message that "Invalid token"
How can I do this?
Thanks in advance.
Easier and safer solution than what was proposed:
Create your own passwords controller, I chose to place it under controllers/auth
controllers/auth/passwords_controller.rb
class Auth::PasswordsController < Devise::PasswordsController
def update
super do |resource|
if resource.reset_password_token_changed? and resource.reset_password_token_was.nil?
resource.reset_password_token = nil
end
end
end
end
This fix many problem with papertrail for example, and anyhow save one access to the DB
You can try either of the following methods
# reset_password_within = 1.day and reset_password_sent_at = today
reset_password_period_valid? # returns true
# reset_password_within = 5.days and reset_password_sent_at = 4.days.ago
reset_password_period_valid? # returns true
# reset_password_within = 5.days and reset_password_sent_at = 5.days.ago
reset_password_period_valid? # returns false
# reset_password_within = 0.days
reset_password_period_valid? # will always return false
or you can call the instance methods like clear reset password token or by calling
clear_reset_password_token
or after_password_reset methods.
I think this hack should be more easier if you do the following in your user model or the model that has been used by devise.
class YourModel < ActiveRecord::Base
...
def after_password_reset
self.clear_reset_password_token if not (self.reset_password_token.nil? and self.reset_password_sent_at.nil?)
end
end
I suggest not to use your controller to perform business operation. This after_password_reset password is used to called after clear_reset_password token in devise. Here is the reference:
https://github.com/plataformatec/devise/blob/master/lib/devise/models/recoverable.rb#L39
Hope this will help.
I have achieved above by overriding Devise::PasswordsController in application.
Devise handle reset password on PasswordController#edit action.
On edit, I have checked if the reset password token is valid or not. If its valid I allow user to reset password otherwise redirect user to sign in page with "Password token is invalid message".
For devise 3.0
class Users::PasswordsController < Devise::PasswordsController
def edit
self.resource = resource_class.find_or_initialize_with_error_by(:reset_password_token, params[:reset_password_token])
if !resource.errors.empty?
flash[:alert] = "Password token is invalid"
redirect_to new_session_path(resource_name)
end
end
end
For devise 3.1
class Users::PasswordsController < Devise::PasswordsController
def edit
original_token = params[:reset_password_token]
reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token)
self.resource = resource_class.find_or_initialize_with_error_by(:reset_password_token, reset_password_token)
if !resource.errors.empty?
flash[:alert] = "Password token is invalid"
redirect_to new_session_path(resource_name)
end
end
end
For my small Rails application, I am using bcrypt to hash users' passwords when they are stored. However, when loading the new user form, I was hit with "invalid hash" for the password, as my new action was
def new
#user = User.new
end
which does not make a new password, which is thus invalid. To remedy this, I tried using
<%= form_for :user, url: users_path do |f| %>
which does not require a user object, allowing me to make that in the create action. However, error handling still needs the User object and throws a nil error
I feel that there should be a "right" way to do this. Can anyone enlighten me?
My user model is as such:
require 'bcrypt'
class User < ActiveRecord::Base
# For user of user.password_hash. Thanks, bcrypt!
include BCrypt
before_save { self.email = email.downcase }
# Validates uniqueness of email
validates_uniqueness_of :email
# Set relationship to lists
has_many :lists
def make_new_password
new_password = Array.new(10).map { (65 + rand(58)).chr }.join
self.password_hash = Password.create(new_password)
end
def password
#password ||= Password.new(password_hash)
end
def password=(new_password)
#password = Password.create(new_password)
self.password_hash = #password
end
end
I feel like this book can help you find the right way to do user authentication. (sorry that is the best I can do with the information you have provided).
Hope this helps :)
I am using devise to manage user authentication in my rails app. Devise is really great for that.
However I have a special requirement for my application: A user must be whitelisted before he can register as a User.
So there is a admin which creates a list of allowed emails. A user registers with a email and if the email is in the whitelist table he will be registered. If however, the mail is not in the whitelist, the registration should be aborted with a message like "You are not yet invited".
Do you have an idea how that could be solved with devise?
Thanks in advance.
I would just use model validation. I'm assuming your User class has the devise method
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable #etc
before_validation :whitelisted
def whitelisted
unless celebrityemail.include? email
errors.add :email, "#{email} is not on our invitation list"
end
end
end
What you can do is create your own registrations controller and extend the device one like:
class MyRegistrationController < Devise::RegistrationsController
def create
# do your checks
super
end
end
see: https://github.com/plataformatec/devise/blob/master/app/controllers/devise/registrations_controller.rb
And: https://github.com/plataformatec/devise/wiki/How-to:-Customize-routes-to-user-registration-pages
Good luck!
I did create my own controller as suggested:
class Users::RegistrationsController < Devise::RegistrationsController
def create
email = params[:user][:email]
if Admin::Whitelist.find_by_email(email) != nil
super
else
build_resource
set_flash_message :error, "You are not permitted to sign up yet. If you have already payed your registration fee, try again later."
render_with_scope :new
end
end
end
I placed it in app/users/registrations_controller.rb. Then I had to copy the devise registration views into app/views/users/registrations because the default views were not used.
It is working now, thanks for your help