Is it possible to force logout for SOME of the users through Devise.
In my setup, I am using rails 4.1.15 with Devise 1.5.4. Sessions are persisted in db and there is no direct mapping with user ids. Is there a Devise way to logout against some of the users NOT ALL.
I tried resetting password as a proxy which logs out immediately but not always.
user_obj.update_attributes(:password => "some_random_string")
I can propose you next solution.
Add a new column for admin that called force_logout:boolean
In any your controller add a new action to set force_logout to true. Ex.:
# in admins_controller.rb
def force_logout
admin = Admin.find(params[:id])
admin.update_column(:force_logout, true)
redirect_to :back
end
In application_controller.rb add before_action to logout user if force_logout is true
before_action :check_force_logout
def check_force_logout
if current_user && current_user.force_logout?
current_user.update_column(:force_logout, false)
sign_out(current_user)
end
end
Too you need reset force_logout column after admin will be signed in. Usually you can do it session_controller.rb in action create.
Time to exhume this one. The only way I was able to solve this issue was by accessing the sessions. It is slow but it works.
def sign_out_different_user
#user = User.find(params[:id])
sessions = ActiveRecord::SessionStore::Session.where("updated_at > ?", Time.now - 480.minutes).all
sessions.each do |s|
if s.data['user_email'] == #user.email
s.delete and return
end
end
end
I needed to change the session expiration on the user object for my purposes, so I added this into the method, as well. YMMV
#user.session_expires_at = Time.now
#user.save
Related
I'm trying to find a way to display logged in active users on my web app. I'm not using any gem for authentication like Devise. I have a list of users and wanted to show an icon or some type of indicator next to a users name if they are currently on the site.
I'm not sure how to go about this. Possibly I could add a column called currently_logged_in to my User model and could set the value to true when the session is created and then to false when the user session is destroyed?
class SessionsController < ApplicationController
def new
end
def create
if user = User.authenticate(params[:email], params[:password])
session[:user_id] = user.id #session id created off of the
redirect_to(session[:intended_url] || user)
session[:intended_url] = nil #removes url from the sessions
else
flash.now[:error] = "Invalid email/password combination"
render :new
end
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
end
User model
# tries to find an existing user in the database so that they can be authenticated.
def self.authenticate(email, password)
user = User.find_by(email: email) # returns user or nil value
user && user.authenticate(password) # if user exists validate that password is correct
end
It depends what you mean by "currently on the site".
Adding a currently_logged_in column like you described works IF you want to mark users that are currently logged in. However most users don't log out when leaving a website these days so that probably won't do what you want.
A better solution would be to add a last_active_at column which you can update with the current time whenever a user performs some action. Then determine a threshold that makes sense for your website, let's say 15 minutes, and only mark users in your list that have a last_active_at value less than 15 minutes in the past.
Assuming the definition of "active user" for your website involves hitting authenticated endpoints it would be as simple as changing your authenticate method to:
def self.authenticate(email, password)
user = User.find_by(email: email) # returns user or nil value
if user && user.authenticate(password)
user.update!(last_active_at: Time.now)
true
else
false
end
end
Firstly You need to find current user on site.
You may b call current_user method which is in application helper and you should display all current user in wherever you want.
For Example,
module ApplicationHelper
def current_user
#current_user ||= session[:user_id] && User.find_by_id(session[:user_id])
end
end
And you call this method in Session controller as #current_user.
I have a Rails 5 app with Devise. Each user has a role_id where they are assigned a role upon creation. I'm trying to use the after_sign_in_path_for method that Devise gives to redirect to a specific page on login based on the role.
Below is what I have so far, but it doesn't work when trying to sign out a disabled user.
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
case resource.role_id
when Role.find_by(name: "admin").id
root_path
when Role.find_by(name: "disabled").id
destroy_user_session_path
else
super
end
end
end
I'm able to sign in when I'm an admin user and it redirects. But if I try to sign in as a user whose role is disabled, it tries to tear down the session then raises an exception of No route matches [GET] "/users/sign_out". I know the method destroy_user_session_path expects a delete method but how can I pass this in the application controller?
What am I doing wrong here?
Update
I tried the sign_out(resource) as suggested in the first answer, and it raises an exception undefined methodto_model' for true:TrueClassin mymy_sessions_controller.rb` which I use to override the create method to set a login token and limit concurrent sessions. Here is the controller.
class MySessionsController < Devise::SessionsController
skip_before_action :check_concurrent_session
def create
super
set_login_token
end
private
def set_login_token
token = Devise.friendly_token
session[:token] = token
current_user.login_token = token
current_user.save(validate: false)
end
end
You can check roles inside MySessionsController#create and prevent logging if the role not valid instead of allowing user to login then logout
def create
unless current_user.role_id == Role.find_by(name: "disabled").id
super set_login_token
else
redirect_to new_user_session_path, alert: "You can't log in"
end
end
You can also use active_for_authentication? and inactive_message methods in user model to prevent him from login. in /app/models/user.rb:
def active_for_authentication?
super and self.role_id != Role.find_by(name: "disabled").id
end
def inactive_message
"You can't log in"
end
destroy_user_session_path is making [GET] "/users/sign_out" request.
You can use sign_out or reset_session function to delete session directly.
Hope this answer works for you.
Use devise's sign_out(resource) method instead of destroy_user_session_path. This method will destroy the user session.
I want to know how I can make specific user(not current_user) sign out.
I saw this http://www.rubydoc.info/github/plataformatec/devise/master/Devise/Controllers/SignInOut#sign_out-instance_method and maked this code.
def kick_admin
user = User.find params[:user_id]
user.admin = false
user.save
sign_out user #want to kick him.
end
But it does not make that user sign out but make me(current_user) signed out.
What is the right way to use the sign_out method?
I checked this answer(Sign out specific user with Devise in Rails) but it was not helpful.
One way you could do this is create a new attribute in the User table, call it force_sign_out.
def kick_admin
user = User.find params[:user_id]
user.update_attributes(admin: false, force_sign_out: true)
end
And have a before action in ApplicatonController so that if the user attempts any activity he's signed out
class ApplicationController < ActionController::Base
before_action :check_if_force_sign_out
def check_if_force_sign_out
return unless current_user.force_sign_out
current_user.update_attributes(force_sign_out: false) # reset for non-admin log in
sign_out
redirect_to root_path
end
end
I'm trying to set up gradual engagement in my utility app which people can use without registering e.g. notepad.cc and jsfiddle.net and I plan to create a guest user (with Devise) for the user when he 'writes' to the app.
I found this guide on the Devise wiki https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user which shows how to create a guest user for the duration of the browser session. What I want is for the user to continue using the same guest account in subsequent visits, until he signs up, maybe when I introduce subscription plans for more features.
How can I modify what's in the guide to make this possible?
Code in the guide linked above:
# file: app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
# if user is logged in, return current_user, else return guest_user
def current_or_guest_user
if current_user
if session[:guest_user_id]
logging_in
guest_user.destroy
session[:guest_user_id] = nil
end
current_user
else
guest_user
end
end
# find guest_user object associated with the current session,
# creating one as needed
def guest_user
User.find(session[:guest_user_id].nil? ? session[:guest_user_id] = create_guest_user.id : session[:guest_user_id])
end
# called (once) when the user logs in, insert any code your application needs
# to hand off from guest_user to current_user.
def logging_in
end
private
def create_guest_user
u = User.create(:name => "guest", :email => "guest_#{Time.now.to_i}#{rand(99)}#email_address.com")
u.save(false)
u
end
end
And using it in the controller:
#thing.user = current_or_guest_user
#thing.save
After some yak-shaving I've managed to get it to work. Here's the working code:
class ApplicationController < ActionController::Base
protect_from_forgery
# if user is logged in, return current_user, else return guest_user
def current_or_guest_user
if current_user
if cookies[:uuid]
logging_in # Look at this method to see how handing over works
guest_user.destroy # Stuff have been handed over. Guest isn't needed anymore.
cookies.delete :uuid # The cookie is also irrelevant now
end
current_user
else
guest_user
end
end
# find guest_user object associated with the current session,
# creating one as needed
def guest_user
User.find_by_lazy_id(cookies[:uuid].nil? ? create_guest_user.lazy_id : cookies[:uuid])
end
# called (once) when the user logs in, insert any code your application needs
# to hand off from guest_user to current_user.
def logging_in
# What should be done here is take all that belongs to user with lazy_id matching current_user's uuid cookie... then associate them with current_user
end
private
def create_guest_user
uuid = rand(36**64).to_s(36)
temp_email = "guest_#{uuid}#email_address.com"
u = User.create(:email => temp_email, :lazy_id => uuid)
u.save(:validate => false)
cookies[:uuid] = { :value => uuid, :path => '/', :expires => 5.years.from_now }
u
end
end
I will accept another answer if you can show me a better way to do this.
The above solution works great.
Don't forget to setuphelper_method :current_or_guest_user to make the method accessible in views. Took me some time to figure out.
In my app I set Devise to timeout a session after 30 minutes. Which works fine... the user has to log in again after that time. My only problem is that devise apparently doesn't use the destroy action in the session controller after a session has timed out. So the attribute :signed_in for the user isn't set to 'false'.
So even after a session has timed out this user is still displayed as online.
Is there a way to destroy the session after the timeout or set the signed_in attribute to false after a certain time and on browser close?
My destroy action in session controller:
def destroy
current_user.try("signed_in=", false); current_user.save
signed_in = signed_in?(resource_name)
sign_out_and_redirect(resource_name)
set_flash_message :notice, :signed_out if signed_in
end
I'm not exactly an expert on Devise but I suspect this is because of the stateless nature of HTTP. Devise only knows a session is timed out when the user tries to access a page again after your timeout length and will likely only call the destroy method when a user actually logs out and the destroy method is called on the session controller.
In order to achieve what you are likely looking for, you would need to run a background process that sweeps for old sessions and then manually calls the destroy method (or does something similar).
With the new Devise version it works as follows to have an accurate online/offline status:
put this in your application_controller:
before_filter :last_request
def last_request
if user_signed_in?
if current_user.last_request_at < 1.minutes.ago
current_user.update_attribute(:last_request_at, Time.now)
end
if current_user.currently_signed_in = false
current_user.update_attribute(:currently_signed_in, true)
end
end
end
With every action the app checks if the last request was over 1min ago, if yes, he updates the user attribute.
put this in user.rb:
before_save :set_last_request
Warden::Manager.after_authentication do |user,auth,opts|
user.update_attribute(:currently_signed_in, true)
end
Warden::Manager.before_logout do |user,auth,opts|
user.update_attribute(:currently_signed_in, false)
end
def set_last_request
self.last_request_at = Time.now
end
def signed_in?
if self.currently_signed_in
if self.timedout?(self.last_request_at.localtime)
return false
else
return true
end
else
false
end
end
you can then use the signed_in? method to determine a users online status.