how to hide info that email is already registered with devise rails - ruby-on-rails

I have set confirmable in my devise user model but when I try to register email, which is already registered, I get error that email is registered or waiting for confirmation.
This is correct but for security reasons I want to always show something like "confirmation email has been sent to email address" so nobody can figure out which emails are already registered in the app.
Is this somewhere in devise config? Or do I have to manually modify registration controller to not throw any error if email exists in db?

The best way is way is to manually modify from registration controller (https://github.com/heartcombo/devise#configuring-controllers), you can rescue the error and changes the flash message.

I borrowed has_duplicate_email? from this post , checked devise source code and then modified create in registration controller and routes to use it. I hope it helps somebody
def has_duplicate_email?
return false unless resource.errors.has_key?(:email)
resource.errors.details[:email].any? do |hash|
hash[:error] == :taken
end
end
def create
super do |resource|
if has_duplicate_email?
set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in!
redirect_to root_path and return
end
end
end
devise_for :users, :controllers => {:registrations => "users/registrations"}

Related

How can I configure my app to allow only specific emails to register with devise?

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.

Rails Devise Send confirmation email after Facebook signup

Problem: How do I send the user an email after they sign up for the first time with Facebook? I'm using device and omniauth.
I have confirmation emails working for regular signup with devise. I need to send an email when the user gets added to my database for the first time after signing in with Facebook. Where in the code is this happening?
I tried adding a line of code sending the email in my omniauth_callbacks_controller.
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# omniauth_callbacks_controller
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
facebook = "www.facebook.com"
if #user.persisted?
print "User persisted"
sign_in #user, :event => :authentication
set_flash_message(:notice,:success,:kind => "Facebook") if is_navigational_format?
# I SENT THE EMAIL HERE
else
session["device.facebook_data"] = request.env["omniauth.auth"]
redirect_to root_path
end
end
However, this just sends the user a confirmation email EVERY time they log in with Facebook, which is not what I want. I want to simply send the email the first time they log in.
The email should be sent in the registrations_controller. However, when users are signing up with Facebook, this controller is never used.
class RegistrationsController < Devise::RegistrationsController
def create
build_resource(sign_up_params)
if resource.save
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_up(resource_name, resource)
# Tell the UserMailer to send a welcome email after save
UserMailer.welcome_email(current_user).deliver_later
return render :json => {:success => true}
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format?
expire_session_data_after_sign_in!
return render :json => {:success => true}
end
else
clean_up_passwords resource
invalid_signin_attempt
end
end
Would like to know the right way to send a confirmation to the user after signing up with Facebook.
Problem
It looks like your User.from_omniauth function behaves like a find_or_create call. That means that the controller has no knowledge of whether the user was just created or is being fetched from an existing identity in the database.
If the user is created as part of this from_omniauth call, then you should be able to just rely on the Devise :confirmable module. Otherwise, the user is created before you get back the OAuth credentials, so you need to handle it manually.
The code in the from_omniauth function likely looks something like this:
def self.from_omniauth(token)
user = User.find(token: token)
if user.nil?
user = User.create(token: token, ...)
# ...
end
# ...
end
There might be an intermediary Token, Identity, or other such class, but the logic should be the same.
Fix
There are two easy ways to fix this:
Include a created boolean as part of the from_omniauth return value, which the controller can then use to gate the confirmation email on.
Move the "create" part of the "find or create" logic out into the controller, so that the email can be sent as part of the "create" path.
Aside
Also, I'd suggest using the Devise resource.send_confirmation_instructions function and piggybacking your email off that. That way, all welcome emails share the same code and you're not maintaining a separate module just for Facebook/OAuth login.

Devise sending forgot password instructions to any email id

I am looking for a customization in devise where if we click on forgot password it should send the mail to any e-mail id . Something like it happens in Gmail, irrespective of the email id exists or not.
Screen 1
Screen 2
Currently what i have is this in which it tries to validate with the valid users in the system.
The Devise, recoverable module takes care of this
def send_reset_password_instructions(attributes={})
recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
recoverable.send_reset_password_instructions if recoverable.persisted?
recoverable
end
How can i remove this validation and have email sent to any Email id?
There is a Devise configuration called paranoid that, when set to true, would change the message in a way to avoid e-mail enumeration. Just set config.paranoid = true in your Devise configuration.
My solution would be to extend/override Devise's passwords controller. To do this, create a controller (let's call it passwords) that inherits from Devise's passwords controller, like this:
class PasswordsController < Devise::PasswordsController
Then edit your routes file so this change takes effect:
devise_for :users, :controllers => { :passwords => 'passwords' }
Now, you'll want to override the create action. There are several ways you could do this but since I'm not sure of what you want to do, I'll show you 2 things you could do:
You only want to prevent the "Email not found" error so that people can't find which emails exist or not in your database:
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
end
You really want to send emails to any entered email:
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
unless successfully_sent?(resource)
Devise::Mailer.reset_password_instructions(resource).deliver
end
respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
end
Now, the problem with this last solution is that you are sending an email to a user that doesn't exist... And so when the user comes back, he won't be able to enter his new password since his user account can't be found. But if you really want to do this, hopefully I set you on the right track.

Devise: When resending confirmation email, change flash message

I'm setting up Devise such that users can log in and use the site without having confirmed their email address, similar to this question. But there are a couple of features on the site that users can't use unless they've confirmed.
OK, that's fine. I can check for current_user.confirmed?. If they're not confirmed, I can put a button on the page to have them request the confirmation be sent again.
The issue I'm having is that when they do this while logged in, the flash message they see on the result page is "You are already signed in." Which isn't ideal - I just want to put up the message that the confirmation was sent.
I'm starting down the path of trying to figure out which method of the Devise::ConfirmationController to override, and to what, but I'm hoping someone has done this already.
The reason the flash says "You are already signed in" is because the user is being redirected to new_session_path from the after_resending_confirmation_instructions_path_for method. I would override this method to check if they are logged in. If they are, then don't redirect to new_session_path, set your flash message and redirect to another page.
Override the confirmations controller by putting it in controllers/users/confirmations_controller.rb
class Users::ConfirmationsController < Devise::ConfirmationsController
protected
def after_resending_confirmation_instructions_path_for(resource_name)
if signed_in?
flash[:notice] = "New message here" #this is optional since devise already sets the flash message
root_path
else
new_session_path(resource_name)
end
end
end
Add your confirmationsController to routes->
devise_for :users, :controllers => {:confirmations => 'users/confirmations' }
I think it should look something like this:
module Devise
module ConfirmationsController
extend ActiveSupport::Concern
included do
alias_method_chain :show, :new_flash
end
def show_with_new_flash
# do some stuff
flash[:notice] = "New message goes here"
end
end
end
Could edit
config/locales/devise.en.yml to be more relavent at line:
failure:
already_authenticated: 'You are already signed in.'
or you could do this in your view where flash message has been added
<%=content_tag :div, msg, id: "flash_#{name}" unless msg.blank? or msg == "You are already signed in."%>
I'm using Devise 3.1.0, there's a different method for this scenario instead of after_resending_confirmation_instructions_path_for described in the top voted answer. I modified mine like so:
class Users::ConfirmationsController < Devise::ConfirmationsController
protected
def after_confirmation_path_for(resource_name, resource)
if signed_in?
set_flash_message(:notice, :confirmed)
root_path
elsif Devise.allow_insecure_sign_in_after_confirmation
after_sign_in_path_for(resource)
else
new_session_path(resource_name)
end
end
end

How to stop soft deleted user's login with Devise

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.

Resources