Skip to content

Instantly share code, notes, and snippets.

@stympy
Forked from KieranP/1 app-models-session.rb
Created March 23, 2017 13:13
Show Gist options
  • Save stympy/afface9dbf17e2455b01f7362b01a364 to your computer and use it in GitHub Desktop.
Save stympy/afface9dbf17e2455b01f7362b01a364 to your computer and use it in GitHub Desktop.
A Ruby on Rails implementation of a Database and Cookie hybrid session storage for Devise that supports session revocation and storage of session ip and user agent for security (similar to Github)
# A user session class, a simplified mix of the following code samples:
# * https://github.com/blog/1661-modeling-your-app-s-user-session
# * http://www.jonathanleighton.com/articles/2013/revocable-sessions-with-devise/
class Session < ActiveRecord::Base
# Uncomment if you use Hobo Fields, else add these yourself
# fields do
# session_id :string, :index => true, :unique => true
# accessed_at :datetime
# user_ip :string
# user_agent :string
# timestamps
# end
belongs_to :user
validates_presence_of :user, :session_id
def self.exists?(user, session_id)
self.where(:user => user, :session_id => session_id).exists?
end
def self.fetch(user, session_id)
if user
user.sessions.find_by_session_id(session_id)
else
Session.find_by_session_id(session_id)
end
end
def self.activate(user)
self.purge_old(user) # Clear old sessions before creating one
user.sessions.create!(:session_id => SecureRandom.hex)
end
def self.deactivate(user, session_id)
self.fetch(user, session_id).try(:delete)
end
def self.purge_old(user)
user.sessions.where { accessed_at < 2.weeks.ago }.destroy_all
end
def accessed!(request)
self.accessed_at = Time.zone.now
self.user_ip = request.remote_ip
self.user_agent = request.user_agent
self.save!
end
end
class User < ActiveRecord::Base
has_many :sessions, :dependent => :destroy
end
Warden::Manager.after_set_user(:except => :fetch) do |user, warden, opts|
unless Rails.env.test?
Session.deactivate(user, warden.cookies.signed["_session_id"])
warden.cookies.signed["_session_id"] = {
:value => Session.activate(user).session_id,
:expires => 1.year.from_now,
:httponly => true
}
end
end
Warden::Manager.after_fetch do |user, warden, opts|
unless Rails.env.test?
if Session.exists?(user, warden.cookies.signed["_session_id"])
warden.cookies.signed["_session_id"] = {
:value => warden.cookies.signed["_session_id"],
:expires => 1.year.from_now,
:httponly => true
}
else
warden.logout
throw(:warden, :message => :unauthenticated)
end
end
end
Warden::Manager.before_logout do |user, warden, opts|
unless Rails.env.test?
Session.deactivate(user, warden.cookies.signed["_session_id"])
warden.cookies.delete("_session_id")
end
end
class ApplicationController < ActionController::Base
before_filter :authenticate_user!, :update_current_session
def current_session
return unless current_user
@current_session ||= current_user.sessions.find_by_session_id(cookies.signed[:_session_id])
end
def update_current_session
return unless current_session
current_session.accessed!(request)
end
end
# We use a hybrid database/cookie based session storage
[APP_CONSTANT]::Application.config.session_store :cookie_store, key: APP_CONFIG[:session_key]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment