In my Rails project I am using Devise and I've turned on allow_unconfirmed_access in the devise initializer like this:
config.allow_unconfirmed_access_for = 1.day
This works great. If a user has not confirmed their account via the confirmation email, they can still login for 24 hours. After this, if they attempt to login, Devise handles it and gives a flash message and returns a 401, disallowing the login.
I want to be able to hook into this and add a step to auto-resend the confirmation email but I can't figure out for the life of me where to do it.
You can extend the Devise::SessionsController to add this behavior:
class Users::SessionsController < Devise::SessionsController
before_action :resend_confirmation_email_if_unsent, on: :create
def resend_confirmation_email
#user = resource # resource is a devise controller helper method
unless #user.active_for_authentication?
#user.resend_confirmation_instructions # Resets token
# Or, if you don't want to reset the tokn
# #user.send_confirmation_instructions
end
# ....
end
I know this is an old question, but I thought I might as well answer it as I was dealing with the same situation.
Anthony E's answer is almost correct, but it missed the fact that resource is not defined before the create action starts, thus resource is nil at that moment. My solution was this:
class Users::SessionsController < Devise::SessionsController
before_action :resend_confirmation_email_if_needed, on: :create
def resend_confirmation_email_if_needed
#user = resource_class.find_by_email(resource_params[:email])
unless #user.nil? || #user.active_for_authentication?
#user.resend_confirmation_instructions
end
end
end
I'm not sure if it's a good idea to retrieve the user this way. It would be much easier if super do |resource| worked for this, but it only runs upon successful login, which is not the case.
Hope this helps!
Related
I'm trying to make signed up users to take some actions on my website, so I want to send them, by e-mail, a link directly to this action.
The problem is that I want them to be automatically logged in when clicking on this link.
I can do something obvious as creating an unique token and pass it through the url mysite.com/my_funky_action?login_bypass_token=af123fa127ba32 but this seems to me as a problem "solved many times before"
So, there is a simple way out there to do this using rails / devise? I've searched on devise documentation without success.
Using as basis the code from devise's recoverable, I did this
model:
class User < ActiveRecord::Base
def set_login_bypass_token
raw, enc = Devise.token_generator.generate(User, :login_bypass_token)
self.login_bypass_token = enc
self.login_bypass_token_set_at = Time.now.utc
self.save(validate: false)
raw
end
def self.by_bypass_token(token)
original_token = Devise.token_generator.digest(self, :login_bypass_token, token)
User.find_by(:login_bypass_token => original_token)
end
end
mailer:
class SomeMailer < ActionMailer::Base
def send_something
...
#login_bypass_token = #user.set_login_bypass_token
...
end
end
application_controller:
class ApplicationController < ActionController::Base
layout :application_layout
protect_from_forgery with: :exception
before_action :bypass_login
before_action :authenticate_user!
private
def bypass_login
if params[:login_bypass_token]
user = User.by_bypass_token(params[:login_bypass_token])
sign_in(user, :bypass => true) if user
redirect_to request.path
end
end
end
email template (in haml)
= link_to 'View this awesome page without login!', awesomeness_url(login_bypass_token: #login_bypass_token)
Generally not a good thing to do.
If you don't use a token, that means you'd need to build a path that includes the email address in clear, e.g.
http://my_app.com/special_action?email=john#sample.com
Given that, anybody would be able to sign on as any registered user simply by sending a url structured as the above, substituting whatever email they want.
Go for a token, make sure it expires when used or after the shortest time you can get away with.
EDIT: I use Devise 3.4.1, and after_remembered isn't available. Either apply this small patch or use a newer version released after 2014/11/09.
Alright, so I am rather new to the Rails environment, and I feel I am missing something.
I wish to execute a particular action after login, be it after login from a form or automatic login.
I found out that after_sign_in_path_for is executed after a "regular" login. I already use it in my Application controller.
class ApplicationController < ActionController::Base
[…]
def after_sign_in_path_for(resource)
myAction
end
However, it seems that this method isn't called when a user is logged in through the Devise Remember option.
I found out that after_remembered is executed after a user is automatically logged in, however I don't understand how to use it. I don't want to modify the Devise gem just to add my action to the method, and the following doesn't seem to work:
class ApplicationController < ActionController::Base
[…]
def after_remembered()
myAction
end
I am at a loss here. Either I don't understand how to use after_remembered, or I look at it the wrong way and there is another solution for that (should it cover either both cases or only the case with "remember me" automatic login, is fine for me). Does anyone have any idea to make it work?
PS: I am working on an app I haven't developed myself. If you feel you need more code in order to answer, tell me and I will edit my post.
It's part of the Rememberable module so it belongs in your model... assuming your resource is User the way you would use it is...
class User < ActiveRecord::Base
devise :rememberable
def after_remembered
# (your code here)
end
end
I'm not sure if Devise offers a controller-specific method but if not you could throw one togeter... create an attribute in your User model called, say, signed_in_via_remembered
class User < ActiveRecord::Base
devise :rememberable
def after_remembered
update_attribute(:signed_in_via_remember, true)
end
end
then in your application_controller.rb
class ApplicationController < ActionController::Base
before_action :handle_sign_in_via_rememberable
def handle_sign_in_via_rememberable
if current_user and current_user.signed_in_via_remember?
current_user.update_attribute(:signed_in_via_remember, false)
after_sign_in_path_for(User)
end
end
def after_sign_in_path_for(resource)
myAction
end
end
Someone may have a neater solution.
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'm using devise and have a quick question. How can I redirect the :authenticate_user! before_filter to the user sign up page instead of sign in? I've been going through https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb but haven't had much luck figuring out a solution.
I had a similar issue where I needed to redirect to the signup if the user was not logged in.
I fixed it by adding a method to the application_controller.rb and using it as a before filter in the other controllers.
Keep in mind that is is more of a temporary solution because it skips a bunch of devise´s abstractions.
before_filter :auth_user
def auth_user
redirect_to new_user_registration_url unless user_signed_in?
end
You're going to have to create a custom FailureApp that inherits from Devise's FailureApp as seen here: https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-when-the-user-can-not-be-authenticated
I added a wiki page showing the correct way to do this with a failure app (as Steven initially hinted at):
Redirect to new registration (sign up) path if unauthenticated
The key is to override the route method, like so:
# app/lib/my_failure_app.rb
class MyFailureApp < Devise::FailureApp
def route(scope)
:new_user_registration_url
end
end
and then have Devise use your failure app:
# config/initializers/devise.rb
config.warden do |manager|
manager.failure_app = MyFailureApp
end
This approach is preferable to overriding authenticate_user! in your controller because it won't clobber a lot of "behind the scenes" stuff Devise does, such as storing the attempted URL so the user can be redirected after successful sign in.
With multiple user types
If you have Admin and User Devise resources, you'll probably want to keep the default "new session" functionality for admins. You can do so quite easily by checking what type of scope is being processed:
# app/lib/my_failure_app.rb
class MyFailureApp < Devise::FailureApp
def route(scope)
scope.to_sym == :user ? :new_user_registration_url : super
end
end
I'm using Devise in a Rails application I'm writing, and I want to let users go back to where they were after signing in or signing up.
For example, if I have a "comments" Controller that is protected by:
before_filter :authenticate_user!
Then I want users who click a "Comment Now!" button (and are therefore redirected to the new action in CommentsController) to log in and then have Devise redirect them to the new action (or wherever they were) in CommentsController, not to the generic root of the application, or to a generic after_sign_in_path.
Looking through the RDOC for Devise, I found this method that makes it look as if Devise has at least the capability to do something like this on its own, but I can't figure out a way.
OK, so I've done some more experimentation, and working with Kormie's info, I've got a working solution.
From what I can determine, before_filter authenticate_user! does not save the route for returning the user. What I did was this:
First, I added an extra before_filter at the top of my controller
before_filter :store_location
before_filter :authenticate_user!
Then, I wrote the store_location method at the bottom of the controller
private
def store_location
session[:user_return_to] = any_old_route_path
end
I don't claim this is perfect, but it works for me. (The downside for anyone else wanting to use it, is that it only supports one return path per controller. This is all I need for myself, but it is only a slight improvement over the one return path per app that I was using previously.) I would really appreciate anyone else's insights and suggestions.
Devise should do this by itself. The authenticate_user! filter also did not want to work for me when the route to the action was set via PUT method. When I have changed this to GET in routes.rb devise started to work as expected.
The simple way to do this:
# Modified from https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
stored_location_for(resource) || your_defaut_path
end
end
I think by default Devise saves the route but you may be usinging
sign_in #user
this should redirect you
sign_in_and_redirect(#user) #assuming you are sigining in that resource
Have you tried after_sign_in_path_for? If you define that method in your ApplicationController it should override the default implementation on a per controller basis.
Adapted from Devise Wiki how to:
Redirect back to current page after sign in, sign out, sign up, update
Redirecting back to the "current page" involves saving the current url in the session and then retrieving the url from the session after the user is authenticated / signed out. This should only really be done for GET requests as the other http methods (POST, PUT, PATCH, DELETE) are not idempotent and should not be repeated automatically.
To store the location for your whole application use before_action to set a callback (Use before_filter in Rails versions before 4.0).
This example assumes that you have setup devise to authenticate a class named User.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :store_user_location!, if: :storable_location?
# The callback which stores the current location must be added
# before you authenticate the user as `authenticate_user!` (or
# whatever your resource is) will halt the filter chain
# and redirect before the location can be stored.
before_action :authenticate_user!
# To redirect to the stored location after the user signs
# signs in you would override the after_sign_in_path_for method:
def after_sign_in_path_for(resource_or_scope)
# *My note, not wiki*: you may need a fall back as
# stored_location_for can return nil. I've added root_path
stored_location_for(resource_or_scope) || root_path
end
private
# Its important that the location is NOT stored if:
# - The request method is not GET (non idempotent)
# - The request is handled by a Devise controller
# such as Devise::SessionsController as that could
# cause an infinite redirect loop.
# - The request is an Ajax request as this can lead
# to very unexpected behaviour.
def storable_location?
request.get? && is_navigational_format? &&
!devise_controller? && !request.xhr?
end
def store_user_location!
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
end
Reference
Devise How To: Redirect back to current page after sign in, sign out, sign up, update