Finding last active time for logged in user - ruby-on-rails

I am using ruby on rails session to store a user cookie to keep them logged in, so I can't just update a last_seen column after they login. I'm trying to figure out the best way to find out if a user has been active in the last day. This is what I have so far but I'm wondering if there's a better way:
class ApplicationController
before_filter :update_last_seen
private
def update_last_seen
if (DateTime.now - 1.day) < current_user.last_seen
current_user.last_seen = DateTime.now
current_user.save
end
end
end
The problem with this is that it is going to get called with every request. Is there any way to avoid this? Does the session cookie get updated somehow when a user is active?

No, that is exactly how you should go about it. before_filters are a great way to keep track of things like that, and are how authlogic implements it's authentication system as well. There's no way to do processing on one request "per day". If you use Authlogic as your authentication method, it has last-login time built in. You can see an example of it here.

Related

Increment a users login count

I have an existing application where a user can log in etc. This does not use devise works fine. I wanted to create a feature that counts the users login attempts and as such increments this by one each time they log in. I am aware of active record and increment as i have put below.
def increment_login_count!
update_attribute(:login_count, login_count + 1)
end
Could anyone offer any other advice as to how to do this logic. I wanted to start with a controller spec and work from this but i am a little unsure.
Try this
def increment_login_count
increment! :login_count
end
And you can call this method whenever a new session is created for the user
You can refer to docs for more information

Ruby on Rails - login with cookie or username / password - always hitting the db

UPDATE: All, thanks for the responses - here is some more significant info.
I'm using a Neo4J graph database for the back-end, ROR 3 (MRI), and one single Neo4J database server accessed via REST.
If you don't know much about Neo4j, to use more than one database server (master/master) for data costs $26,000, which means I have to code for optimization now, not later, or come up with $26k...I'm sure you can guess which way I'm going with this..And I'm using it via rest, not locally etc, so performance matters...
I have one database server that has to handle all of the database work, and yes 1 ms counts under this scenario where some other queries take up to 40 ms. So no, I don't want to hit the database unnecessarily as it will simply add unnecessary work to it.
It might be easy to say "don't code for optimizations or problems you don't have yet" yet given the bottleneck and steep costs - and the fact I already have what I need done except for the authentication piece, it really doesn't apply.
What I simply wanted to know, was if the #current_user ||= is valid across pages.. The answer is that it's valid in a request, and not across them or pages. Yes this is a simple question, but sometimes they have to be asked in the midst of R&D and advanced stuff. Hence my gut feeling to stick with sessions to hold the id of the user logged in.
Thanks for your help!
I'm trying to allow a user to login to my site either by cookies or by username and password. The username/password part works fine, but having a problem with introducing cookies....
I've ready plenty of "how tos" including: http://ruby.railstutorial.org/chapters/sign-in-sign-out
I do not want to use a gem, devise, etc.
PROBLEM:
I notice that requests from the page to read current_user (a helper in the application controller) results in database reads, even though I'm using ||=..
I've tried the #current_user ||= User.find by blah blah blah
and it ALWAYS hits the database. This shoudn't be the case right? It should hit once and then that's it, correct?
But before you suggest any tutorial - I've seen tons of them - here is my question.. is #current_user saved across pages, or just for the current page? The link above mentions it only saves it for the current page...
You see, I don't want to keep hitting the database needlessly to find out the same person previously is logged in.. I can do that with session variables.
I really just want to check for a cookie,and would be happy to keep on using session[:user_id] after that.. for performance reasons, I do not want to keep hitting the database.
Any help?
My NEW code is below (and this too always hits the database as it should in this instance). I removed the typical #current_user ||= find_by.. because it was useless - it was always hitting the db.
.. I already tried https://github.com/cliftonm/basic-auth and http://ruby.railstutorial.org/chapters/sign-in-sign-out etc..
(ROR 3.2...)
class ApplicationController < ActionController::Base
protect_from_forgery
#before_filter :set_var
helper_method :current_user
private
def current_user
#current_user = User.find_by_id(session[:user_id]) #if session[:user_id]
if (#current_user)
return #current_user
end
#current_user =User.find_by_remember_token(cookies[:auth_token]) if cookies[:auth_token]
if (#current_user)
return #current_user
end
end
User.find will always hit the database unless you have some kind of cache extension loaded. Rails.cache can be configured several ways, but the most popular, if this sort of thing is required, is Memcached.
Unless you're dealing with massive scaling issues, the time required to fetch a user record should be less than 1ms, so it's hardly worth fussing about. Check your log/development.log to see the execution times of your various queries and focus first on the slowest ones.
Instance variables like #current_user persist only for the duration of the request. Remember that the HTTP protocol is stateless, each request exists independent of the others, and the only way to communicate state is via cookies, a persistent store like a database, a temporary store like an in-memory cache, or by parameters sent in with the request itself via GET or POST.
If you want to save something across pages, add it to the session, but be careful. You should only be persisting things like strings, numbers, or booleans. You should not be adding models. Further, using the default cookie store, each thing you put in the session increases the overhead on all requests made to your application from that point forward until you remove that from your session store.
Don't sweat the little things until you've got all the other problems solved. Business logic first, optimization second.
Neither of these should be slamming the database every time. Please let me know.
if (#current_user)
#current_user
else
#current_user = User.find_by_id(session[:user_id])
end
or
#current_user ||= User.find_by(id: session[:user_id])
Have you tried using a before_filter (instead of a helper) to load current_user only once? You would then store it / access it using an instance variable.
class ApplicationController < ActionController::Base
before_filter :load_current_user
def load_current_user
#current_user = # your code goes here.
end
end
Edit:
My bad. How about storing a server-side encrypted current_user record in the cookie, and keeping the hashsum in the session for later checkup?

How can I find a devise user by it's session id?

Given session["session_id"] is it possible to find the logged in User to which that session belongs to?
At least on my system (rails 3.2, devise 2.0.4), you can do it like this:
session is:
{"session_id"=>"be02f27d504672bab3408a0ccf5c1db5", "_csrf_token"=>"DKaCNX3/DMloaCHbVSNq33NJjYIg51X0z/p2T1VRzfY=", "warden.user.user.key"=>["User", [3], "$2a$10$5HFWNuz5p6fT3Z4ZvJfQq."]}
session["warden.user.user.key"][1][0], then is 3.
So, I'd find it as:
User.find(session["warden.user.user.key"][1][0])
I'm not sure what you are trying to accomplish but will try to answer
If you want only the User from the current session, a simple way would be to store his id on session, for example:
def login(username, pass)
# do the usual authentication stuff and get user
if logedin
session[:user_id] = user.id
end
end
Then get the current user would be something like
current_user =User.find(session[:user_id])
If what you want is finding all the users that are currently logged in, I think you need to config your app to save session at DB, because predefined is that session data is store in cookies in the browser. For more information on this check this answer
How to track online users in Rails?
EDIT: Just noticed you are using devise so the first way is actually there for you. You just need to call current_user method at any controller.
For the second way check this answer "Who's Online" using Devise in Rails
And i might add this, as i was trying to do it the other way, if you are using ActiveRecord session_store you can find all stored sessions of a user like so:
ActiveRecord::SessionStore::Session.select{ |s| s.data["warden.user.user.key"] && s.data["warden.user.user.key"][0][0] == user_id }

How to handle the expiration of login cookies if a user is inactive for more than a set time using Ruby on Rails?

I am trying to manage the login session of users that navigate in my RoR3 application.
I set a cookie when they sign in and I would like that their authentication expires after a few minutes of inactivity, for example 15 minutes.
How to achieve that?
This doesn't directly answer your question, but I strongly suggest using something like Devise for authentication instead of rolling your own.
Devise provides a timeoutable configuration flag, as well as a timeout value covering how long user sessions can be inactive before being logged out.
You could setup a property in your session. Something like
session[:expire_time] = 15.minutes.since
Then, in your applicationController you can check if your user has been away enough time to be logged out, if not then you renew his expiration time, something like:
class ApplicationController < ActionController::Base
before_filter :check_expire
def check_expire
if session[:expire_time] and session[:expire_time] < Time.now
#your code to logout the user
else
session[:expire_time] = 15.minutes.since
end
return true
end
end
You can also set the expiration time of the cookie that sets their session. In your configuration :
env["rack.session.options"][:expire_after] = 15.minutes
This works perfectly for the use case you described, because it will be reset every time the server responds, but it gives you a little less control.
Here's a good article about it :
http://augustl.com/blog/2010/dynamic_session_expiration_time_in_rails_3

Steps to create my own authentication system, need some guidance

I want to learn how to create my own authentication system, please provide some guidance if am doing this wrong.
I will create a Module in my /lib folder /lib/auth.rb
I will require this module in my ApplicationController.
when a user enters their email + password, I will call a method that will do a lookup in the user's table for a user with the same email, I will then compare the passwords. (i'll add encryption with salt later).
If the user entered the correct credentials, I will create a row in the Sessions table, and then write the session GUID to a cookie.
Now whenever I need to check if the user is logged in, or I need the user object, I will check if the cookie exists, if it does, I will lookup the session table for a row with the same guid, if it exists, I will return the session row and then load the User object.
I realize there are many suggestions one can give, but in a nutshell does this sound like a workable solution?
Now to make this usable, I will have to make some helper methods in my ApplicationController right?
How will I access the current_user from within my views?
P.S I know of other authentication systems, I just want to learn how to create my own.
The basic logic you're following is correct. Of course you can always expand on this with features that you need. For instance, you'll need helper methods for things like "logged_in?" and "current_user". Also, you might want to add session expiry, or session retention as a "remember me" feature.
Go for it, you won't learn authentication systems better than building your own then figuring what's wrong with it.
You should really check out the authlogic gem on github.
http://github.com/binarylogic/authlogic
It also has great instructions on how to set up your users.
After Faisal said what I would say, I only give you answer to the last part of your question:
"How will I access the current_user from within my views?"
try something like this:
class User < ...
def self.current=(u)
#current = u
end
def self.current
#current
end
end
In your views (or any part of your code) you can call User.current. Your controller has to assign a validated user to User.current. Your filters can react to "if User.current.nil?" and so on.
If you want to be thread safe, you may use a thread variable instead of #current:
Thread.current[:current_user] = u

Resources