How to send reset password instead of confirmation email in Devise? - ruby-on-rails

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!

Related

Devise ignoring customized create method

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.

Which controller and action are being accessed with Rails, Devise, and Omniauth

I'm using Devise and Omniauth in my Rails 4 application. In my view I have a button to "Login With Facebook" that directs the user to user_omniauth_authorize_path(:facebook). This works as expected and sends the user to /users/auth/facebook. Then, that URL appears to be correctly redirecting the user to the Facebook login page.
How can I determine which controller/action are being used for this redirect? I would like to set a return_url before redirecting to Facebook. I was able to do this in my regular Session and Registration controllers by subclassing the Devise version and adding my code, like this:
class Users::SessionsController < Devise::SessionsController
def new
return_url = params[:return_url]
store_location_for(:user, return_url) unless return_url.nil?
super
end
end
Then, I tell devise to use my new controllers:
devise_for :users, controllers: { sessions: 'users/sessions' }
I'm trying to accomplish the same sort of thing with the omniauth functionality? Is there some way to find out which controller is being accessed when the user visits /users/auth/facebook? If so, can I subclass it and add functionality in the same way I did above?
UPDATE
I found the controller/action by running 'rake routes'. Here's what it says:
user_omniauth_authorize GET|POST /users/auth/:provider(.:format) users/omniauth_callbacks#passthru {:provider=>/facebook/}
So I was able to find the passthru method that is being used, but that has left me even more confused. It's in Devise::OmniauthCallbacksController and says:
def passthru
render status: 404, text: "Not found. Authentication passthru."
end
I'm confused how this method is resulting in the user being redirected to Facebook.
It looks like you can pass parameters to the user_omniauth_authorize_path, and then retrieve them in the callback URL using request.env["omniauth.params"]. So, I changed my link to: user_omniauth_authorize_path(:facebook, { return_url: '/test_redirect_path' }
Then in my OmniauthCallbacksController I added:
store_location_for(:user, request.env["omniauth.params"]["return_url"])
before the call to sign_in_and_redirect #user.
This seems to have done the trick.

Rails devise disable password recovery for certain user types

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

Integrating Wicked, Devise and Omniauth-Facebook

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.

Devise: user has to login again each time he closes the browser

I'm using Devise and Omniauth for user authentication.
I want the users to stay signed in for 2 weeks after the authentication. However, whenever the user closes the browser window and reopen it, he gets the login screen again.
What is the correct configuration for the user to stay connected even after the browser was closed and re-opened?
Check out this page on Devise's wiki:
https://github.com/plataformatec/devise/wiki/Omniauthable,-sign-out-action-and-rememberable
Basically, Devise does not call rememberable by default when using omniauth. If you wish to do so, simple call remember_me(#user) on your omniauth callback and Devise will do the hardwork for you. It will also use all the configuration options set in your devise initializer.
You have to extend the devise SessionsController to add cookies on log in and log out, so the controller will look like this one:
class SessionsController < Devise::SessionsController
# POST /resource/sign_in
def create
cookies[:sign_in] = "Sign in info you want to store"
# add this for expiration { :expires => 2.weeks.from_now }
super
end
# GET /resource/sign_out
def destroy
cookies[:sign_out] = "Sign out info you want to store"
super
end
end
Then you would have to add the following to your routes.rb:
devise_for :users, :controllers => { :sessions => "sessions" }
You may also set the cookie expiration time for 2 weeks.

Resources