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.
Related
I am looking to set a secondary password by which I can authenticate a user for a login as from admin. The reason for this work around is the front end is a single page application.
Each user has been given a unique login_as string. now I need to configure Devise to compare the login_as if the password fails.
Any help is appreciated. I am of course open to an alternative solution if there is a better way.
Thanks.
This post from Duncan Robertson was very helpful in solving my issue. I essentially created an override strategy and called it in the devise.rb file. I had some concern regarding tampering with a large user base but it has proved successful. By adding a column to users named ":signin_as" and then setting it to a default unique string with a rake I then had what I needed to fallback on if the initial sign in failed.
the override strategy (config/initializers/auth_override.rb)
module Devise
module Strategies
class AuthOverride < Authenticatable
def custom_auth(user, signin_as)
if user[:signin_as] == signin_as
return true
else
return false
end
end
def authenticate!
user = User.find_by_email(email)
if user
if user.valid_password?(params[:password])
success!(user)
elsif custom_auth(user, params[:password])
success!(user)
else
fail
end
else
fail
end
end
end
end
end
including the strategy in devise (config/initializers/devise.rb)
config.warden do |manager|
manager.default_strategies(:scope => :user).unshift :auth_override
end
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
In the devise source code listed here:
https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb
What exactly does line 56 do? In other words, I am not sure how devise determines whether a user is signed in.
It looks like it calls warden.authenticate with the scope of user(provided that user is what the model is)
Would I have to dive into the Warden codebase as well?
Devise delegates the work to warden. Warden checks if the username and password are valid.
Warden::Strategies.add(:my_strategy) do
def valid?
params[:username] && params[:password]
end
def authenticate!
u = User.find_by_username_and_password(
params[:username],
params[:password] # you should encrypt this. ;)
)
u.nil? ? fail!("Couldn't log in") : success!(u)
end
end
In my app I need to have a database authentication but without a password. Just by entering your phone number. When the user sings up he enter phone number, adress and name and no password.
Is it real? Just can't figure out how to make it.
Thanks for help in advance!
Figured out like this:
In devise_no_pass.rb initializer add:
require 'devise/strategies/authenticatable'
module Devise
module Strategies
class DeviseNoPass < Authenticatable
def authenticate!
return super unless params[:customer_sign_in]
customer = Customer.find_by_phone(params[:customer_sign_in]
customer ? success!(customer) : raise
end
end
end
end
Warden::Strategies.add(:devise_no_pass, Devise::Strategies::DeviseNoPass)
In devise.rb:
config.warden do |manager|
manager.default_strategies(:scope => :customer).unshift :devise_no_pass
end
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])