I want to add a feature that will redirect a user with an expired password to the reset password page.
My controller looks like this
class Users::SessionsController < Devise::SessionsController
def create
user = User.find_by(email: params[:user][:email].downcase)
if user.password_expire?
raw, enc = Devise.token_generator.generate(current_user.class,
:reset_password_token)
user.reset_password_token = enc
user.reset_password_sent_at = Time.now.utc
user.save(validate: false)
redirect_to edit_password_url(user, reset_password_token: raw)
else
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in)
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
end
end
When I debug with binding.pry at the top of the action, I find that current_user exists and user_signed_in? is true. How is it possible I'm signed in before the create method completes?
If you are using the security extension you don't need at all to take care of the implementation of password expiration.
And if you are not using it, you should check it out - devise_security_extension.
Devise remembers the current user using cookies until they log out.
Just because they've hit your sign in route doesn't mean they're signed out.
Diving into Devise
Tracing the code in Devise to understand what happens we see:
1. Devise::SessionsController#create
class Devise::SessionsController < ApplicationController
# ...
def create
# ...
sign_in(resource_name, resource)
# ...
end
end
2. Devise::Controllers::Helpers#sign_in
def sign_in(resource_or_scope, *args)
# ...
if options[:bypass]
warden.session_serializer.store(resource, scope)
elsif warden.user(scope) == resource && !options.delete(:force)
# Do nothing. User already signed in and we are not forcing it.
true # <=== Here's the moment of truth
else
# ...
end
Conclusion
The user can hit your sessions#create when they're already logged in
In this case the default Devise behaviour is to do nothing
Not quite sure what you want to achieve above, but calling super might come in handy somewhere to resume the default Devise behaviour
Related
in my project I'm using devise and I want to create custom sign_in. I decided to override create action in SessionsController. Right now my SessionsController looks like that:
class SessionsController < Devise::SessionsController
def create
user_signed_in? # -> gives true if correct email and password
if !current_user.new_one? # -> I have access to current_user
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
# even if condition is true, user is signed_in
end
end
I'm a little confused because even if I remove whole code from there then user will be signed_in anyway. Can someone explain me why user_signed_in? gives me true before sign_in(resource_name, resource)? It looks like the user is already logged in? In that case, how can specific users not be allowed to log in? I know there is method active_for_authentication?, but I don't want to override it, because I want to allow some users to log in to only part of the application, this method will not allow it and overriding this method will not allow me to do it.
When overriding Devise::SessionsController, you still expect some authentication over the params you have parametered (usually email/password).
user_signed_in? method does not the authentication, it's just a helper and it can check if the formerly processed authentication has succeded in the past or not.
The authentication is achieved with use of 'super' at some point of your code.
See :
https://rubydoc.info/github/heartcombo/devise/main/Devise/Controllers/SignInOut#signed_in%3F-instance_method
to find an answer to your first question
https://github.com/heartcombo/devise#controller-filters-and-helpers
https://github.com/heartcombo/devise#configuring-controllers (use of
super)
Hope it helps
I'm adding 2FA to an existing Rails app using Devise, using the active_model_otp gem to handle the authenticator code. Based on my unit testing, it seems to work. But given my limited knowledge about devise, I'd like to double check if there is any security issues with the implementation, and the sequencing of the calls.
For simplicity, I will only include the controller code for login:
# /app/controllers/users/sessions_controller.rb
class SessionsController < Devise::SessionsController
def create
self.resource = resource_class.find_for_authentication(sign_in_params.except(:password, :otp_response_code, :remember_me))
if resource
if resource.active_for_authentication?
if resource && !resource.user_2fa_activation_date.present?
super
elsif resource && resource.user_2fa_activation_date.present?
if params[:user][:otp_response_code].present?
if resource.authenticate_otp(params[:user][:otp_response_code])
super
else
sign_out(resource)
back_to_login_page("Invalid 2fa token value.")
end
else
sign_out(resource)
back_to_login_page("Your account requires 2-factor authentication.")
end
end
else
super
end
else
# case when email is invalid.
super
end
end
def back_to_login_page(alert_message)
redirect_to new_user_session_path, :alert => alert_message
return false
end
end
I am using the devise gem in rails project for user authentication. Now I have a problem.
I want to add a condition before the default sign in process, for example:
if admin?
sign in
else
error
end
And also this condition should not impact the method sign_in_and_redirect, because I use this method to allow user sign in with another tunnel.
I tried to override the method active_for_authentication?, but it also impacted the sign_in_and_redirect
How to implement this?
You could override default sessions_controller.rb and recreate it with custom condition. Here is how to override Registration controller and it's similiar for Sessions controller: https://gist.github.com/kinopyo/2343176
Then in custom controller recreate default create action but with condition.
This is the default sessions_controller.rb https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb
This is create session action with condition
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
if [CONDITION]
set_flash_message!(:notice, :signed_in)
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
else
[ERROR]
end
end
I have an attribute (approved) on the user that i wish for devise to consider before allowing sign in.
I've overriden the session controller and its currently as follows:
class SessionsController < Devise::SessionsController
skip_after_filter :verify_authorized
def create
user = User.find_by_email(params[:user].try(:[], :email))
unless user.approved?
flash[:alert] = "Login fail. Account currently pending approval."
redirect_to :back and return
end
resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
def failure
head 403
end
end
Looking at the logic i see that there is a "redirect_to :back" happening, but then it seems devise takes over, logs in the user regardless of that and redirects to root (which is what i've setup to redirect to upon login)
I've never worked with devise before and this is driving me mad. I should return before reaching the 'sign_in' part but something is happening that it still signs in the user regardless of my unless block.
How could i go about intercepting that login if user.approved == false ?
thanks in advance!
Although not the same question, the top answer in this question applies here and fixed the issue for me.
Check if user is active before allowing user to sign in with devise (rails)
I am using devise and created a User field called :active which is either true or false. I have to manually make the user active (true) before the user is allowed to log in. At least that is the intent. I have tried this...
class SessionsController < Devise::SessionsController
# POST /resource/sign_in
def create
"resource/signin CREATE"
self.resource = warden.authenticate!(auth_options)
unless resource.active?
sign_out
redirect_to :sorry_not_active_url
return
end
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
end
However this does not catch all the places where a user can log in, for example, when a user changes their password, the site automatically logs them in automatically after. However, if the user is not active, I do not want them to be allowed to log in, but rather be redirected to a sorry_not_active_url.
What would be the best way to prevent the user from signing in if the user is not active?
Thank you.
Add these two methods to your user model, devise should pick them up automatically - you should NOT need to extend Devise::SessionsController
def active_for_authentication?
super && self.your_method_for_checking_active # i.e. super && self.is_active
end
def inactive_message
"Sorry, this account has been deactivated."
end
Devise (If you have devise 3.2+) now support block parameter in (session) create
# assuming this is your session controller
class SessionsController < Devise::SessionsController
def create
super do |resource|
unless resource.active?
sign_out
# you can set flash message as well.
redirect_to :sorry_not_active_url
return
end
end
end