Users have to confirm their email account via a link sent to them after registration through devise's confirmable.
Right now if a user who has not confirmed their account gets the unhelpful error:
Your email or password is incorrect.
I have tried adding the below to the SessionsController:
def create
user = User.where(email: params[:user][:email]).take
unless user.confirmed?
super
end
flash[:alert] = "Your account has not been confirmed."
end
My routes are
devise_for :users, path: 'users', controllers: {
sessions: "users/sessions"
}
However, signing in as any user (confirmed or not), seems to bypass this entire method. (Even if I put in binding.pry.)
UPDATE:
Devise only ignores the unique create method. If I put in a binding.pry in any other method (new or delete), devise will catch it.
The sessions create doesn't get called if the user isn't confirmed, so this is why you're not able to reach the create method.
Try this
class User < ActiveRecord
protected
def confirmation_required?
false
end
end
This will allow the user to proceed to the create action in sessions_controller and you can test there if the user is actually confirmed.
Related
I am trying to make it so that if a one type of User is created - then instead of sending the confirmations email, it sends the reset password link (I have already achieved so that the reset password link serves as both confirmation and reset password). This is basically a try to avoid duplication of emails since I allow creating a user without password so that the user then can request his first password via reset password.
I am trying to override the Confirmations Controller like so:
class Users::ConfirmationsController < Devise::ConfirmationsController
def create
if self.resource.encrypted_password.empty? then
Users::PasswordsController.create
else
super
end
end
end
And in routes:
devise_for :users, controllers: {
passwords: 'users/passwords',
sessions: 'users/sessions',
confirmations: 'users/confirmations',
omniauth_callbacks: 'omniauth_callbacks'
}
The overriding works with other models. But the create action in confirmations controller is never called. I put a lot of byebug statements all around the Gem itself, and it seems even the original create action is not called, but the email is sent anyway. I am using sidekiq for delayed emails. The only place that is run seems to the mailer:
def confirmation_instructions(record, token, opts={})
byebug # <- it does stop here
#token = token
devise_mail(record, :confirmation_instructions, opts)
end
But when I check the stack trace, it looks like it's called from some labda function.
The question: why isn't devise using ConfirmationsController and how do I achieve the task at hand?
Thanks a lot!
In my Rails project I have different types of users one of which has the user_status :admin, which has full rights to edit content unlike the rest of the users. For obvious reasons I want to add additional security for these types of users, in particular, completely disable password recovery.
What is the correct way of overriding standard Devise password recovery (:recoverable Devise module) methods so that when a user tries to get a reset password link for a user which is an admin user (user_status == "admin") the system gives back the "standard email not found" message?
This is somewhat like the unanswered question: Restrict Devise password recovery to only certain users
Thank you in advance.
The method I chose and that worked for me was overriding the send_reset_password_instructions method of the User model by adding the following to models/user.rb:
def send_reset_password_instructions
return false if self.user_status == 'admin'
super
end
This makes Devise not do anything in case the email belongs to an admin account.
For any future viewers, here's another way to do it. Vitaly's example did work for me, but I was still getting the "Your password email has been sent." notice (I wanted a separate alert to flash), so I went another route.
Extending the Devise::PasswordsController was the easiest solution for me:
class Devise::Extends::PasswordsController < Devise::PasswordsController
def create
if some_condition?
redirect_to :root
flash[:alert] = 'You cannot reset your password, buddy.'
else
super
end
end
Then, in routes.rb:
devise_for :users, controllers: { passwords: 'devise/extends/passwords' }
That will direct your app to the extended controller, then hit the devise controller ("super") if your condition is not met.
Not tested, but I think you can overwrite the reset_password! in the User model as follows:
def reset_password!(new_password, new_password_confirmation)
return false if user_status == 'admin'
super
end
This prevents the password from being reset if the user is an admin.
I don't know if this is the best method to override, there are more devise recoverable methods that are candidate to be overwritten in your User model, ie send_reset_password_instructions. Check the manual for all the interesting methods.
Snippet above from Keller Martin works pretty well!
Some minor issues I faced are the following:
If you got uninitialized constant Devise::Extends (NameError) (probably it's just due to old ruby version?) then you can just use nested modules definition.
If you need to allow some action to run for non authenticated user then you can skip the filter.
Below is updated snippet.
module Devise
module Extends
class PasswordsController < Devise::PasswordsController
skip_before_filter :authenticate_user!, :only => [ :edit ]
def edit
redirect_to "https://www.google.com/"
end
end
end
end
I am developing an application using these three gem I wrote in the title of this post. I set up devise with the confirmable module(?), so when a user creates an account with its email/password, it receives a confirmation email. If the user sign up with facebook (using omniauth-facebook gem) devise skips the confirmation step.
In user.rb
"Of course the :confirmable is active in the model"
...
# Omniauth-facebook
def self.find_for_facebook_oauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.skip_confirmation!
# user.image = auth.info.image # assuming the user model has an image
end
end
...
The thing comes when I added the wicked gem for the wizard. I configured the routes file
in routes.rb
MyApp::Application.routes.draw do
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks",
:registrations => "registrations" }
root 'home#index'
# Registration wizard routes
resources :after_register
end
The I created a registration_controller to override the devise registration methods
class RegistrationsController < Devise::RegistrationsController
def create
super
end
protected
def after_sign_in_path_for(resource)
puts "<<<<<<<<<<<<<<<< SIGN IN"
after_register_path(:import_contacts)
end
def after_sign_up_path_for(resource)
puts "<<<<<<<<<<<<<<<< SIGN UP ACTIVE"
after_register_path(:import_contacts)
end
def after_inactive_sign_up_path_for(resource)
puts "<<<<<<<<<<<<<<<< SIGN IN INACTIVE"
after_register_path(:import_contacts)
end
end
And then, I created a new controller to handle the steps of the wizard with wicked.
class AfterRegisterController < ApplicationController
include Wicked::Wizard
before_filter :authenticate_user!
steps :import_contacts, :select_agents, :wish_form
def show
#user = current_user
render_wizard
end
def update
#user = current_user
#user.attributes = params[:user]
render_wizard #user
end
end
When I create a user rith email/password, the wizard comes and everything works fine, but when I try to sign up with facebook, the wizars never comes.
Any hint???
Thank you!!
If everything is configured in the usual way (and what I can see looks pretty standard), then signing in using Facebook won't go via the RegistrationsController at all. It will go via the OmniauthCallbacks controller which you haven't posted the code for. It depends what you do in there when they log in via Facebook. I assume that you have a facebook() method which calls User.find_for_facebook_oauth(auth). Are you then just signing them in rather than going to the RegistrationsController? If so, then the RegistrationsController won't be touched, so its overridden after_sign_in_path_for won't have any effect.
If you want to have your overridden after_sign_in_path_for take effect throughout your app, you can define it in your ApplicationController. If you only want it to take effect in your OmniauthCallbacksController, you could define it there. In either case, they'll be hitting it every time they log in using Facebook (and if you put it in the ApplicationController, every time anyone logs in using any method), so you'd need to keep track of the fact that they have already been through the wizard, assuming you want to make sure that it only happens the first time they sign in. If you're using the devise :trackable module, perhaps checking the user.sign_in_count would be appropriate, or perhaps you have some other way to easily check if they have been through the wizard already.
UPDATE FOR COMMENT QUESTIONS:
Your first question: "Assuming I put the after_sign_in_path_fot in ApplicationController, I should remove the after_inactive_sign_up_path_for method in RegistrationsController, right?" It depends on what behaviour you want. With the RegistrationsController as in your question it will go to the wizard after they've signed up and before they've confirmed their email (because of after_inactive_sign_up_path_for which will be called in this case). When they confirm and sign in, the after_sign_in_path_for in ApplicationController will send them to the wizard again. So yes, remove inactive from RegistrationsController if you just want wizard after sign in. Then probably after_sign_up_path_for(resource) in RegistrationsController is unnecessary because the default implementation in devise just calls after_sign_in_path_for(resource) which you will have in your ApplicationController. Anyway, after_sign_up_path_for() won't be called if you always require confirmation because of the logic in the default implementation of RegistrationsController.create() - requiring confirmation will result in resource.active_for_authentication? returning false which causes after_inactive_sign_up_path_for(resource) to be called.
For the question in your second comment, you said "If I remove the after_sign_in_path_for in ApplicationController" - I assume you meant RegistrationsController? If that's right, then yes, there would be no methods needed in your overridden version of RegistrationsController (if that's the whole controller you pasted in your question) because your create() just calls super, the after_sign_in_path_for would be in ApplicationController and you probably don't want either of the after_(inactive)_sign_up_path_for methods as discussed above. So yes, there would be no need for your RegistrationsController. You could remove it completely and remove the :registrations => "registrations" in routes.rb - the devise implementation of RegistrationsController will then be used again.
Then you say "just override the methods of RegistrationsController in ApplicationController?". The only method you will have left from your RegistrationsController is the after_sign_in_path_for(resource) in ApplicationController, so I don't think there will be any other methods from your RegistrationsController that you need in ApplicationController. Let me know if I've missed one of your requirements or made an incorrect assumption.
I have a Rails 3.2.x application using Devise.
As per standard Devise feature, a user can delete his account (there is a button to click on the change passsword screen).
I want to prevent a user of deleting his account if he has made previous transactions in the system.
So I did the following in the user model
before_destroy do
# Only delete if no financial transactions exist
return false if Trxhistory.where(:user_id=>self.id).select(:id).count() != 0
end
It works, the user gets deleted if no transactions exist and the user does not get deleted if a transaction exists. BUT: in case the user is not deleted, the screen still shows the deletion success message rather than the error message.
To have such customization. I believe that you will have to overwrite the devise registrations controller.
The good news is that is not hard. all you have to do is generate a new controller on your app that will be the new registration controller for that user. for example.
bundle exec rails generate controller users/registrations
It will have to inherit from Devise:RegistrationsController
class Users::RegistrationsController < Devise::RegistrationsController
end
lastly you will have to reference this controller as the new registration controller at your routes.rb file just like the devise page says
devise_for :users, controllers: { registrations: "users/registrations"}
now you can implement your custom rule for that resource. i.e
class Users::RegistrationsController < Devise::RegistrationsController
def destroy
if resource.didsomething
redirect_to root_path, flash: { error: "You can't delete your account" }
else
super
end
end
end
the method above is overwriting the default destruction method of devise and calling the original one only if the condition is not met.
With the Confirmable module enabled, Devise will not allow an unconfirmed user to sign in after a predefined period of time has elapsed. Instead the user is redirected back to the sign in page with the flash message "You have to confirm your account before continuing".
This is an undesirable interaction model, as a flash notice does not provide adequate space to properly explain to the user why access has been denied, what "confirm your account" means, provide a link to resend the confirmation, and instructions on how to check your spam folder and so on.
Is there a way I can change this behaviour to redirect to a specific URL instead?
Sorry at first I thought you meant after Sign Up not Sign In. So the down below works for how to direct users after Sign Up and what you need to do for Sign In is to create a custom Devise::FailureApp
See the wiki page: https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated
Then within your custom FailureApp overwrite redirect_url method from https://github.com/plataformatec/devise/blob/master/lib/devise/failure_app.rb:
def redirect_url
if warden_message == :unconfirmed
custom_redirect_path
else
super
end
end
For custom redirect after Sign Up:
There is a controller method after_inactive_sign_up_path_for within the RegistrationsController that you can overwrite to accomplish this.
First in your Routes you will need to specify to use your custom controller:
config/routes.rb:
devise_for :users, :controllers => { :registrations => "users/registrations" }
Second you create your custom controller that inherits from the normal controller in order to overwrite the method:
app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
protected
def after_inactive_sign_up_path_for(resource)
signed_up_path
end
end
In this case for my App my Devise model is User so you may want to change that namespace if your model is named differently. I wanted my users to be redirected to the signed_up_path, but you can change that to your desired path.
I just did this, but took a different approach.
in app/controllers/sessions_controller.rb:
class SessionsController < Devise::SessionsController
before_filter :check_user_confirmation, only: :create
#
# other code here not relevant to the example
#
private
def check_user_confirmation
user = User.find_by_email(params[:email])
redirect_to new_confirmation_path(:user) unless user && user.confirmed?
end
end
This worked for me and seemed minimally invasive. In my app new sessions always have to go through sessions#create and users always sign in with their email address, so this may be a simpler case than yours.
You can of course redirect_to any location you desire in the check_user_confirmation method. new_confirmation_path was the logical choice for me because it provides users with the resources to get confirmed.
This is my solution you need to add :unconfirmed message on devise locales below the sessions.
in app/controllers/sessions_controller.rb
def check_user_confirmation
user = User.where(email: params[:user][:email]).take
unless user && user.confirmed?
set_flash_message! :alert, :unconfirmed
expire_data_after_sign_in!
respond_with user, location: after_inactive_sign_up_path_for(user)
end
end
protected
def after_inactive_sign_up_path_for(resource)
new_user_session_path
end