indicate which users are currently logged in - ruby-on-rails

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.

Related

Session in Action Mailer - how to pass it?

Let's say I have a website where people can get a free ebook if they will sign up for a newsletter - after they've done it, I will create a User model and I will show them Edit Form to add some extra details about them.
I don't want to force them to add a password or any other details on the first page because it would decrease conversions and I don't require the additional information either. Also, I don't want them to have forever access to the Edit page so I solved it by assigned a session to them and recognize it through it on the Edit page. This is my controller:
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
UserWorker.perform_in(5.minutes, 'new_user', user.id)
redirect to edit form...
end
end
def edit
#user = User.find(session[:user_id])
end
def update
#user = User.find(session[:user_id])
#user.update!(user_edit_params)
redirect_to user_thank_you_path
end
end
But if they won't add extra information within 10 mins, I will send them an email via ActiveMailer with a link to the Edit form and ask them to do so.
Th question is how could I identify the user through the session and show them the form - how could I do User.find(session[:user_id] via ActionMailer)? Is it actually a correct way or would you recommend a different approach?
One way could be to set a background job to run in 10 minutes.
Inside that job, you would check if they're still "unregistered". You deliver the email if they've yet to complete the registration.
Something like this;
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
RegistrationCompletionReminderWorker.perform_in(10.minutes, user.id)
# redirect to edit form...
end
end
end
class RegistrationCompletionReminderWorker
def perform(user_id)
user = User.find(user_id)
if user.password.nil? # or whatever your logic for registration completion is
UserMailer.registration_reminder(user_id).deliver_now
end
end
end

Devise force logout for some users from Rails App

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

How can I make specific user(not current_user) sign out on rails-devise website?

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

Current User in Rails

I have a question I am really new in Rails and I'm not sure what is correct.
def current_user
return unless params[:user_id]
#current_user ||= User.find(params[:user_id])
end
So like this I get the current user. Rails check the params for user_id.
But what if I something like that the route /users/:id
Then the params is id and not user_id this is why it fails sometimes with to set the time zone correctly.
How to handle something like this ?
def set_time_zone(&block)
if params[:user]
Time.use_zone(params[:user][:time_zone], &block)
else
time_zone = current_user.try(:time_zone) || 'UTC'
Time.use_zone(time_zone, &block)
end
end
you could override the current_user inside the UsersController so that it looks at the id param instead of user_id. or you can use the existing method with a fallback like so:
#current_user ||= User.find_by_id(params[:user_id]) || User.find_by_id(params[:id])
i'd caution you though not to take that params[:id] for granted. if my user id is 5 there's nothing to stop me from visiting /users/6!
In all the rails apps i've seen so far current_user represents the currently logged in user.
The id of the currently logged in user MUST NEVER be sent via params but via session (because params can be manipulated by everybody).
User submits username/password via form
controller verifies username/password matches and if correct
sets id of user to session
Then you can have
def current_user
#current_user ||= begin
User.find(session[:user_id]) if session[:user_id]
end
end
in your application controller.

Rails + iOS authentication without devise

I have a very simple rails app that I rolled my own authentication for, based on the rails tutorial book by Michael Hartl (http://www.railstutorial.org/book/modeling_users). The app is a content management system for an equally simple iOS app. I am aware that devise is very popular, but I really do not think it is necessary for this project. I want to be able to link my iOS app to my rails app, but everywhere I look the only advice I can find is how to do it with devise. All I want to do is have the user be presented with a login screen so they can establish a session and then I can handle all of the permission logic on the rails end of things. Here are a few things to give you an idea of my current authentication scheme:
My session controller:
class SessionsController < ApplicationController
def new
end
##
#Use the email in the nested hash to find the right user
#Check to make sure that the user authenticates with the given password
##
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_back_or root_url
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
current_user.update_attribute(:remember_token,User.digest(User.new_remember_token))
cookies.delete(:remember_token)
self.current_user = nil
session.delete(:return_to) #not sure if this should really be here or if better way to fix bug
redirect_and_alert(root_url, "User Successfully Logged Out!",:success)
end
end
Sessions helper:
module SessionsHelper
##
#set the remember token for the user
#make the cookie reflect that token
#update the users remember token column
#set the user being passed in as the current user
##
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.digest(remember_token))
self.current_user = user
end
#set the current user
def current_user=(user)
#current_user = user
end
#Helper current user method
def current_user
remember_token = User.digest(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
#Is the requesting user the current user
def current_user?(user)
user == current_user
end
#Is the user signed in?
def signed_in?
!current_user.nil?
end
#Store user request info for friendly forwarding
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
#Store user request info for friendly forwarding
def store_location
session[:return_to] = request.url if request.get?
end
#Authorization
def signed_in_user
store_location
redirect_to signin_url, notice: "Please sign in." unless signed_in?
end
def super_user
redirect_and_alert(root_url,
"You are not allowed to do that. Contact the admin for this account.",
:error) unless (current_user.role.id == 1)
end
def super_user_or_admin
redirect_and_alert(root_url,
"You are not allowed to do that. Contact the admin for this account.",
:error) unless (current_user.role.id == 1 || current_user.role.id == 2)
end
def is_super_user
current_user.role.id == 1
end
def is_admin
current_user.role.id == 2
end
def is_regular_user
current_user.role.id == 3
end
end
You could go for a token-based system rather than a session-based system.
Create an attribute called authentication token for every user. This token can be generated and assigned during sign-up. The token itself can be generated using simple techniques such as SecureRandom.hex(n), where n is the length of the random hex number generated.
After sign-in/sign-up from the app, send the authentication token in the response from the server. You can then have the iOS app send the token along with every subsequent request to the server.
Have the server check the token every time a controller is hit with a request. This can be achieved using the before_filter. So a sample controller could look like this:
before_filter :authenticate_user
def authenticate_user
# assuming the parameter sent from the app is called auth_token
auth_token = params[:auth_token]
user = User.find_by_authentication_token(auth_token)
if user.nil?
# what to do if user does not exist
else
# what to do if user exists
end
end
Typically for mobile applications tokens are used instead of sessions. The token is essentially just a string that your Rails app will give each mobile device to verify it has access.
Step 1 is to have your mobile app authenticate against the Rails app (this can be through a custom email/password login or through a 3rd party like Facebook).
Have your Rails app send a token to the mobile application
Store this token in UserDefaults on iOS (help link: http://www.learnswiftonline.com/objective-c-to-swift-guides/nsuserdefaults-swift/)
Whenever your app makes a request (or websocket connection, etc.) to the Rails app, send the token (usually as a header).
Whenever the iOS app opens, check for the token in UserDefaults. If it is there, then you skip having the user authenticate again.
If you want to get really fancy, the Rails app should only accept a token once and issue new-tokens on each request. So your iOS app would send a token on each request and then grab a new one with each response. This is a more secure practice so someone does not grab a token and mess with your Rails app.
This diagram covers the high-level process (it uses FB login for initial authentication, but you can plug yours/an alternate system in instead): http://www.eggie5.com/57-ios-rails-oauth-flow.

Resources