Rails 6 How sign_in and creating sessions works in Devise? - ruby-on-rails

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

Related

Ruby on Rails: Edit Sessions controller create action to check if user is active

I need to alter the Sessions controller create method to first check if the user has a column filled out for deactivation_date.
If that column has a value, the user IS deactivated and the sign in authentication should fail in the same way as if the user doesn't exist.
class Users::SessionsController < Devise::SessionsController
def create
logger.debug('create')
self.resource = warden.authenticate!(auth_options)
if self.resource.deactivation_date?
logger.debug('user has been deactivated')
flash.now[:notice] = "Sorry, this account has been deactivated."
else
set_flash_message!(:notice, :signed_in)
sign_in(resource_name, resource)
respond_with resource, location: after_sign_in_path_for(resource)
end
end
end
I've tried overriding the sessions controller as well as the create method.
My code above is only preventing a redirect. Once I refresh the page, the user is authenticated no matter what.
Could someone help point me in the right direction?
The most simple way to do this is really just add a custom valid_for_authentication? method in the resource devise is using, so in Users.rb:
def valid_for_authentication?
super && !deactivation_date?
end
Thanks to this question: Check if user is active before allowing user to sign in with devise (rails)
I was able to simply add a method to my User model and devise picked it up automatically
In my user model:
def active_for_authentication?
super && !self.deactivation_date
end
I didn't know at first the method name was specific to devise. has to be exactly active_for_authentication?

How to add condition before sign in in devise gem

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

Devise signs in an user even though it (in theory) never reached the sign_in method call

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)

Rails Devise and skip_confirmation!, how to avoid autologin

When I use a link from confirmation email to confirm my account, I'm redirected to login path, which is fine.
But for some users I want to skip confirmation, so I've applied a code like below in before validation collback:
if foo?
self.skip_confirmation!
end
Problem is that when I register as that user I'm auto logged, is it any way to prevent it ? I want to skip confirmation but not autologin.
You could try overriding devise's RegistrationsController and only override the sign_up method. The comment for that method in the devise source says "Signs in a user on sign up. You can overwrite this method in your own RegistrationsController". You could then override it like so:
def sign_up(resource_name, resource)
if !foo? # or you could use "if !resource.confirmed?" instead.
super # The current implementation just calls sign_in(resource_name, resource)
end
end
I haven't actually done this myself, but hopefully this helps.

Using a deleted scope with Devise authentication

I'm using Devise to handle authentication in a Rails app, and I'm using permanent_records to soft delete users. The default scope for my User model is the undeleted users. If a user deletes (deactivates) his account, I want him to be able to reactivate his account by logging in, similar to the way Facebook does it. Problem is, since Devise doesn't know to look for deleted users, no account is found. I thought about overriding the sessions#create method
def create
self.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
But since this is handled by Warden, it seems I'm out of luck. I'm afraid that if I start digging too deep I'm going to start breaking things.
Any ideas?
Thanks!
You need:
Overwrite find_for_authentication method in User model to allow finding for any users https://github.com/plataformatec/devise/blob/master/lib/devise/models/authenticatable.rb#L229
Redefine after_database_authentication method in your model to remove deleted flag here https://github.com/plataformatec/devise/blob/master/lib/devise/models/database_authenticatable.rb#L98
That is all, I believe. No need to touch controller actions.
this works with the paranoia gem:
class << self
def find_for_authentication(conditions)
User.unscoped do
user = super(conditions)
user.restore!(recursive: true) if user.deleted?
user
end
end
end

Resources