Tried to use Devise 1.0.6 in a Rails 2.3.5 project (I know it is bit old version). I have used instructions here to setup devise. Sign up is working fine but when sign in, the password field is cleared up and nothing happened. There is no error showing in log. admin_signed_in? gives not signed in.
when sign in, controller create action as follows
def create
if resource = authenticate(resource_name)
set_flash_message :notice, :signed_in
sign_in_and_redirect(resource_name, resource, true)
elsif [:custom, :redirect].include?(warden.result)
throw :warden, :scope => resource_name
else
set_now_flash_message :alert, (warden.message || :invalid)
clean_up_passwords(build_resource)
render_with_scope :new
end
end
authenticate() method in model as follows
def authenticate(attributes={})
return unless authentication_keys.all? { |k| attributes[k].present? }
conditions = attributes.slice(*authentication_keys)
resource = find_for_authentication(conditions)
resource if resource.try(:valid_for_authentication?, attributes)
end
inspected resource in model and it returns a valid record. but its not reaching to controller. in controller authenticate(resource_name) returns nil
helper.rb
def authenticate(scope)
warden.authenticate(:scope => scope)
end
scope is valid and warden.authenticate(:scope => scope) returns null .
Related
I'm setting up a CRUD feature and I only want a few people (entire email addresses) to be able to register through devise.
I've gone through countless posts but they mostly use email domains or single person login. I've also tried creating my own validation method in my User.rb but I can't seem to get it.
validate :check_email
private
def check_email
#users = User.all
if #users == '123.example#gmail.com')
events_path
else
errors.add(:email, 'is not authorized')
end
end
I don't get an explicit error but the app seems to skip my 'if' condition and outputs the 'else' condition.
To allow only users with certain email addresses you could simply add an inclusion validation.
class User < ApplicationRecord
ALLOWED_EMAILS = %w[
123.example#gmail.com
456.example#gmail.com
789.example#gmail.com
].freeze
validates :email, inclusion: { in: ALLOWED_EMAILS, message: :invalid }
# ...
end
You could also opt to load the ALLOWED_EMAILS from the settings or a file.
To load from the Rails config you have to define the email addresses in a config file.
config.allowed_user_emails = %w[
123.example#gmail.com
456.example#gmail.com
789.example#gmail.com
]
Then load them in the controller using:
ALLOWED_EMAILS = Rails.configuration.allowed_user_emails.freeze
To load from for example a yaml file you could do something like:
ALLOWED_EMAILS = YAML.load_file(Rails.root.join('config', 'allowed_user_emails.yml')).freeze
Having the following in the file:
- 123.example#gmail.com
- 456.example#gmail.com
- 789.example#gmail.com
The easiest way to accomplish this would be to create a custom validation like you did originally. In your user model you could create a validation that looks something like this:
validate :is_email_valid?
def is_email_valid
if ["example#gmail.com", "example#yahoo.com", "123.example#gmail.com"].include?(self.email)
errors.add :base, "Your email is not authorized for use!"
end
end
This code will work when saving or creating a new record.
The problem with your code is that you are attempting to validate on a ActiveRecord_Relation object which cannot directly access validations for class instances. It's like trying to call an instance method from a class level; you have to validate one user at a time. You will want to execute validations on your object instance with a reference to self. So, looping through your users and then validating would work. Here is an example:
User.all.each do |i|
if i.valid?
puts "VALID"
else
puts "INVALID"
end
end
Sarah,
The first step that I'd take in accomplishing this would be to work inside devise's registrations_controller. This is the controller where the signed up user will be served to and here you will want to over-write some of the Devise code.
Ensure the controllers are created first by running Devise's controller generator:
rails generate devise:controllers users
Then you'll want to find the user's registrations_controller.rb and override the code in there under the create action.
Here's an example of some code I wrote to override Devise's admin controller:
def create
build_resource(sign_up_params)
# Below - If admin is coming from an email invite or shared invite link, grabs both token and workplace id from params.
#token = params[:invite_token] if params[:invite_token]
#workplace_id = params[:workplace_id] if params[:workplace_id]
#workplace = Workplace.find(params[:workplace_id]) if params[:workplace_id] # Finds the workplace from the workplace_id, works for both invite email and shared link.
#institute = #workplace.institute if params[:workplace_id]
if #institute && #institute.has_super_admins?
resource.super_admin = false
end
if resource.save
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
# Below - If admin came from a shared workpalce invite link or email workplace invite
if #token != nil # Admin signed up via a workplace invite email or the shared link
# Below - Checks Payment plan for admins joining an institute to make sure the institute didn't exceed user count subscription permission.
unless #institute.plan.unlimited_users?
if #institute.plan.user_count <= #institute.admins.count # Admin doesn't join workplace since institute has excited user allowance
set_flash_message! :alert, :signed_up_no_workplace, :workplace => #workplace.name, :institute => #institute.name, :workplace_owner => #institute.subscription.admin.name
sign_up(resource_name, resource)
respond_with resource, location: institute_admin_path(resource.institute, resource)
else # Admin successfully signs up and joins the workplace. Method is below in protected
join_workplace_and_redirect
end
else # Admin successfully signs up and joins the workplace. Method is below in protected
join_workplace_and_redirect
end
else # Fresh admin signup
sign_up(resource_name, resource)
if resource.super_admin? # Checks if the admin is a super_admin and set as true, if so redirects to another page
set_flash_message! :notice, :super_admin_signed_up, :name => resource.first_name
respond_with resource, location: new_institute_path()
else # Admin is not a super_admin and not first one who signed up inside a city.
set_flash_message! :notice, :admin_signed_up, :link => edit_institute_admin_path(resource.institute, resource), :city => resource.institute.name
respond_with resource, location: after_sign_up_path_for(resource)
end
end
else
set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
flash[:alert] = "Your profile could not be created. See why below!"
set_flash_message! :alert, :"signed_up_but_#{resource.inactive_message}"
redirect_to request.referrer
end
else # Failed to save
clean_up_passwords resource
respond_with resource, location: new_admin_registration_path(workplace: #workplace)
end
end
To apply it to your case, what you may want to do is use a case statement to see if the email matches ones you want it to. For example, below you'll want to check the resource (the single user in this case, the user who is signing up) email attribute to determine whether it's a success or failure:
# Checks emails that are allowed
case resource.email
when "123.example#gmail.com"
if resource.save # Success
set_flash_message! :notice, :signup_succeed
respond_with resource, location: home_page(resource)
else # For some reason the allowed email didn't go through due to other validations
set_flash_message! :alert, :signup_failure
respond_with resource, location: new_user_path(resource)
end
else # Entered an email that is not allowed
set_flash_message! :alert, :email_invalid
respond_with resource, location: new_user_path(resource)
end
The set_flash_message! is Devises custom messages which can be edited in config/locales/devise.en.yaml. The second keyword refers to the name of the yaml key, and you can customize the error or success message in there. respond_with is the redirection and location. You can use as many when statements as needed. This is just one way to do it.
Hope this helps.
I am trying to override Devise's error message 'reset_password_token is invalid' entirely. I would like it to read "password reset link has already been used." How can I do this? There does not seen to be a field or keyword for this in devise.en.yml.
A simpler solution than overwriting the passwords_controller, is simply to modify the view:
In app/views/devise/passwords/edit.html.haml (or your erb equivalent),
Just put this conditional inside the form:
- if resource.errors[:reset_password_token].present?
.alert.alert-danger
This password reset URL has expired. You may have requested to reset your password more than once. Follow the link in the most recent email or
= link_to 'request to reset your password again.', new_user_password_path
And you may want to remove these two lines:
= f.error_notification
= f.full_error :reset_password_token
Reset password token is invalid message is a validation error thrown while updating password, and is not a devise specific error ( for which the messages stored in devise.en.yml).
This validation happens in the devise/passwords_controller#update method.
Code included below:
# PUT /resource/password
def update
self.resource = resource_class.reset_password_by_token(resource_params)
yield resource if block_given?
if resource.errors.empty?
resource.unlock_access! if unlockable?(resource)
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
set_flash_message(:notice, flash_message) if is_flashing_format?
sign_in(resource_name, resource)
respond_with resource, location: after_resetting_password_path_for(resource)
else
respond_with resource
end
end
The self.resource = resource_class.reset_password_by_token(resource_params) line sets the resource.errors object with the error message related to reset_password_token being invalid.
Inspecting the value of resource.errors after this line will show a big hash ending with ... #messages={:reset_password_token=>["is invalid"]}
The devise_error_messages method reformats this to say "Reset Password Token is invalid".
To change this message, the passwords controller should be customized and the update method changed to have a different error message hash.
Steps would be as follows:
1) Customize the routes for passwords controller
# config/routes.rb
devise_for :users, :controllers => { :passwords => "passwords" }
2) Create the customized passwords controller
# app/controllers/passwords_controller.rb
class PasswordsController < Devise::PasswordsController
end
3) Customize the update method to change the error message:
# app/controllers/passwords_controller.rb
# Include the update method as it is fully, with the following changes in the else block:
def update
...
if resource.errors.empty?
...
else
if resource.errors.messages[:reset_password_token]
resource.errors.messages.delete(:reset_password_token)
resource.errors.messages[:password_reset_link] = ["has already been used"]
end
respond_with resource
end
More about Customizing Devise controllers
Need to do ajax side-registration with devise. Created RegistrationsController:
class RegistrationsController < Devise::RegistrationsController
def create
if request.xhr?
build_resource(sign_up_params)
if resource.save
...
end
else
super
end
end
end
Noticed, that model does not validate confirmation of password, if I don't pass that field in XHR, or the field is empty, and Angular does not gather it before doing $http.post
Hacked it for a while in Angular, hardcoding initial $scope.reg = {'password_confirmation': ''} but wonder how do I fix this a normal way?
It's unlikely that your model's password confirmation is not validated once you've properly set up Devise. You'll know for sure if validation isn't being run if registration actually creates a new user. If it doesn't create a new user, then validation works fine.
The problem seems to lie in your create action. It looks like your if resource.save doesn't have an else block which is what is called when resource.save fails due to validation errors. Your create action should look like:
def create
# seems unnecessary to check if request is xhr
build_resource(sign_up_params)
if resource.save
render json: resource
else
render :json => { :errors => #model.errors.full_messages }, :status => 422
end
end
Then just have code in Angular that handles errors by notifying the user.
Here is my user session controller create action that the login goes through
# POST /resource/sign_in
def create
resource = User.find_by_email(params[:user][:email])
# check for inactive
redirect_to(new_user_session_path, :notice => 'Invalid Email Address or Password.') and return if resource.try(:active) == false
# check to see if user is AD user
if ad_resource?(resource)
if !ActiveDirectory.new.authenticate!(params[:user][:email], params[:user][:password])
redirect_to new_user_session_path, :notice => 'Invalid Email Address or Password.'
return
end
else
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
end
set_flash_message :notice, :signed_in
sign_in_and_redirect(resource_name, resource)
end
this line
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
How can that possible login anyone...I am not as familiar to devise and i am trying to login and i know there is an active user in the db but i cant log in and looking at the warden.authenticate line confuses me because it doensnt pass in the email and password...any help would be great to help me understand what is happening in the authentication
There is a warden initializer file its run as middleware that picks up the correct params when used with devise...
Have a look at the warden gem to understand how it works fully.
Personally i find devise gets in the way very quickly and you might be better understanding how it all works if you 'roll' your own authentication system.
I currently use Devise for user registration/authentication in a Rails project.
When a user wants to cancel their account, the user object is soft deleted in a way like the following.
How to "soft delete" user with Devise
My implmenetation has a small difference this way.
User model has an attribute 'deleted_flag'.
And, soft_delete method executes "update_attribtue(:deleted_flag, true)"
But, I have to implment sign_in action.
In my implmenetation is the following.
class SessionsController < Devise::SessionsController
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
if resource.deleted_flag
p "deleted account : " + resource.deleted_flag.to_s
sign_out(resource)
render :controller => :users, :action => :index
else
if is_navigational_format?
if resource.sign_in_count == 1
set_flash_message(:notice, :signed_in_first_time)
else
set_flash_message(:notice, :signed_in)
end
end
sign_in(resource_name, resource)
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
end
I think this code has strange points.
If deleted user tries to sing in,
the system permit logging and make log out immediately.
And, the system cann't display flash[:alert] message...
I want to know two points.
How do I implement to prohibit deleted users to login?
How do I implement to display flash[:alert] when deleted user tries to login?
To stop a user that has been 'soft deleted', the best way is to overwrite the find_for_authentication class method on the user model. Such as:
Class User < ActiveRecord::Base
def self.find_for_authentication(conditions)
super(conditions.merge(:deleted_flag => false))
end
This will generate a invalid email or password flash message by devise (because it cannot find the user to authenticate)
As far as your second question though, you'll need some for of method in your controller to add a particular flash message. However, in my opinion you should treat users that are 'soft' deleted the same as if they didn't exist in the database at all. Thus if they tried to log in, they should just get an valid email or password message.
See my solution here: https://stackoverflow.com/a/24365051/556388
Basically you need to override the active_for_authentication? method on the devise model (User).
I haven't tried anything like that but it seems if you want to catch the user before authentication you'll either have to write a Devise authentication strategy or a before_filter to be run before authenticate_user!. Something like:
before_filter :no_deleted_users
def no_deleted_users
if User.find(params[:email]).deleted?
redirect_to root_path, :flash => { :error => "Your user was deleted. You cannot log in." }
end
end
Although it might be more complex to get the user than that. I haven't played with Devise pre-authentication.
The modern and correct answer is this:
class User < ApplicationRecord
def active_for_authentication?
super && !discarded? # or whatever...
end
end
See the documentation here.