I am installing Kissmetrics on my rails app by storing events in a session variable and then passing them into the kissmetrics javascript code on the subsequent page. This method works great except for trying to track accounts getting created. It seems that when I store the account created event in my session variable it works fine, but by the time the next page loads, the session variable is gone. I put debugger in there to try to find where it is getting deleted but it seems there's nothing. km_log_event is a method that stores the string in a session variable called km_events. Here's my code:
accounts_controller/create -->
...
if #account.save
log_event("Account", "Created", #account.name)
km_log_event("Account Created")
redirect_to(welcome_url(:subdomain => #account.subdomain))
#user.activate!
#user.add_connection(params[:connect_to])
else
render(:action => 'new', :layout => 'signup')
end
...
sessions_controller/welcome -->
def welcome
if current_account.new?
# Create the session for the owner, the account is brand new
current_account.user_sessions.create(current_account.owner, true)
elsif current_account.users.last && current_account.users.last.created_at > 1.hour.ago
current_account.user_sessions.create(current_account.users.last, true)
end
redirect_to embedded_invitations_path
end
I'm just not sure where it is getting deleted so I can't record this event. It seems to be happening after #account.save in the accounts controller but before the welcome action.
UPDATE:
here is the accounts module where I believe (this isn't my codebase) current_account gets defined.
module Accounts
def self.included(controller)
controller.helper_method :current_account
end
protected
def current_account
return #current_account if defined?(#current_account)
#current_account = Account.find_by_subdomain!(current_subdomain)
end
end
An invalid csrf token will have the session be reset. Could this be happening?
You can test this easily by removing the following from your controller (usually in ApplicationController)
protect_from_forgery
I think this is happening as you are trying to share session between subdomains. To achieve this you have to do some configuration.
Refer Subdomain Session Not Working in Rails 2.3 and Rails 3 on Heroku with/without a Custom Domain?
Related
I was recently using Svbtle.com where they show a page immediately after logging out. It says "Goodbye.", along with link to go "Back to SVBTL".
I like the idea of a 'farewell' page, similar to how they did it, and would like to do something similar in a project I'm working on.
The 'farewell' page on Svbtle has a path of https://svbtle.com/notify?logout. When you reload the page or try to navigate to https://svbtle.com/notify?logout, it redirects you to the site landing page.
What is this magic?
How would I go about only showing a page upon user logout, but then prevent them from visiting it otherwise?
I'm using Rails 5.0.0.1 and Devise for authentication.
Create a static goodbye page with whatever content you want. Edit your routes.rb and give the goodbye page a route (we'll call it goodbye_page_path for illustrative purposes here).
Go into app/controllers/application_controller.rb and create a method called after_sign_out_path_for, which is a standard Devise helper. Set it up like this:
def after_sign_out_path_for(resource_or_scope)
goodbye_page_path
end
That should redirect users to your goodbye page whenever they log out.
To prevent access to the goodbye page, store a flag in the session object. In the controller method handling logout:
session[:goodbye] = true
In the controller method that handles displaying the goodbye page:
def goodbye_page
if session[:goodbye] && session[:goodbye] == true
render 'goodbye_page'
end
end
I started implementing this and wanted to share what I went with. I tried writing to session initially but ran into problems as session wasn't available after logout where the GoodbyeMessagesController scope lies. I ended up going with a cookie that is set immediately after sign out, then deleted in my goodbye controller:
ApplicationController
def after_sign_out_path_for(resource_or_scope)
cookies[:single_view_page] = true
goodbye_path
end
GoodbyeMessagesController
def show
if cookies[:single_view_page]
cookies.delete :single_view_page
# Other logic...
else
redirect_to root_path
end
end
It ended up being super easy, and it works great.
Edit - I misunderstood the exact nature of the problem, have reposted here with a better grasp of the issue: Rails / Devise - updating session variables between controller actions
I am using a session variable to pass data between three form submission controller actions in my Rails app, but I am having an issue where the process works the first time I run through but fails the second time because the session variable does not pass to a subsequent controller action - however, if I clear my browser history, it works fine (but will fail if I try to repeat without clearing history, etc.). The app uses Devise for user log in, and the problem seems to be related to the session being destroyed on log out.
The session variable in question here is session[:ids], and it seems that it is not being passed from the submission action to the approval action without first clearing the browser history.
Here are the routes:
post 'report/submission'
get 'report/approval'
get 'report/summary' => 'report#summary'
And the associated controller actions (added a few groups of puts to check the variable values in the console):
def submission
#incomingReport = ActiveSupport::JSON.decode(params[:report])
#newReportIDArray = Array.new
#incomingReport.each do |x|
#DATA PROCESSING STUFF HERE
#newReportIDArray.push(#new_report.id)
end
session[:ids] = #newReportIDArray
puts "submission_marker"
puts #newReportIDArray
puts "submission_marker_end"
respond_to do |format|
format.json do
render json: {
success: 200
}.to_json
end
end
end
#USER LOGIN OCCURS HERE
def approval
#reportIDs = session[:ids]
puts "approval_marker"
puts #reportIDs
puts "approval_marker_end"
#reportIDs.each do |x|
#new_report = Report.find(x)
#new_report.user_id = current_user.id
#new_report.save
end
redirect_to report_summary_path
end
def summary
#reportIDs = session[:ids]
puts "summary_marker"
puts #reportIDs
puts "summary_marker_end"
end
#USER LOGS OUT AFTER SUMMARY
So if I run this with clean history, my console looks like this:
submission_marker
766
submission_marker_end
approval_marker
766
approval_marker_end
summary_marker
766
summary_marker_end
but if I log out after the first summary and then I try to run through the process without clearing the history, I end up with
submission_marker
767
submission_marker_end
approval_marker
approval_marker_end
and the error undefined method 'each' for nil:NilClass, referring to #reportIDs.each in the approval action.
What am I missing here? Why is it that with a clean history, everything runs ok, but with a 'dirty' history, the session variable sets for the submission action but is dropped by/on its way to the approval action? I see that this has something to do with Devise and the destruction of the session, but I don't understand why I can't create a new session variable.
For some reason after some time on my website my session hash is turning into a string
undefined method `admin?' for "#<Visitor:0x000001071b7800>":String
is what I'm getting in my render_layout method
def render_layout
if session[:visitor].admin?
render layout: 'admin'
else
render layout: 'application'
end
end
the only two other times I ever call or use session[:visitor] is in my authenticate method, and my logged_in? method that i use to skip authenticate
def authenticate
uuid = params[:uuid]
#visitor ||= uuid && Visitor.find_by_uuid(uuid)
if !#visitor
authenticate_or_request_with_http_basic do |login, password|
#visitor = Visitor.find_by_uuid(ENV['ADMIN_UUID']) if login == 'test' && password == 'testpw'
end
session[:visitor] = #visitor
else
session[:visitor] = #visitor
end
end
def logged_in?
!!session[:visitor]
end
Why is this getting turned into a string? I used a project search in atom and I only ever called it in those places.
Edit:
I've added a binding.pry at the 4 locations I call session[:visitor] and it works the first time through everything. As soon as I follow a url for the first time and
before_action :authenticate, unless: :logged_in?
gets called for a second time the session[:visitor] is turned into a string
#=> "#<Visitor:0x00000106851bd0>"
From the docs, http://guides.rubyonrails.org/security.html#sessions
Do not store large objects in a session. Instead you should store them
in the database and save their id in the session. This will eliminate
synchronization headaches and it won't fill up your session storage
space (depending on what session storage you chose, see below). This
will also be a good idea, if you modify the structure of an object and
old versions of it are still in some user's cookies. With server-side
session storages you can clear out the sessions, but with client-side
storages, this is hard to mitigate.
Store your visitor's ID in the session
session[:visitor_id] = #visitor.id
and then retrieve it as needed
#visitor = User.find_by_id(session[:visitor_id])
I am new to Rails and experience a strange issue I don't understand.
I use ActiveRecord as a session store and need to add session id as a property of JSON responses for all the requests. I use Devise as well if it have some impact on the situation. The problem is that if a request is made by a user without cookies (or at least without session id in the cookie) the session.id is empty or - attention, please - not the same value that is set in the response cookie.
For debugging, I add this code as an after_filter to ApplicationController:
puts session.id
puts request.session_options[:id]
Both values are the same. They match the value in the cookie if it is present. Otherwise, if session id is not present in the cookie, the cookie set after that request has different value.
My opinion is that session_id gets new value after it is actually saved to the database, where it have to be unique. DB migration:
def change
create_table :sessions do |t|
t.string :session_id, :null => false
t.text :data
t.timestamps
end
add_index :sessions, :session_id, :unique => true
add_index :sessions, :updated_at
end
My question: How can I get the actual session.id value of a new session before the first response is rendered?
UPD:
I just created a new Rails app that uses ActiveRecord session store without Devise, and I can get session.id that is going to be set in cookie just before response with this code id application controller:
class ApplicationController < ActionController::Base
after_filter :show_session
def show_session
puts session.id
end
end
But in my existing app with Devise I get a value that really looks like a session id, but that doesn't match the value set in the cookie via Set-Cookie response header and the value actually saved to sessions table in database. Looks like Devise have a conflict with ActiveRecord session store in some way. Need to go deeper to figure it out.
UPD 2
Looks like I found the problem roots. As I said, I use Devise for authorization with Omniauth. According to the documentation, sign_in method resets session id for security reasons. But after that reset session.id returns the old value, that had been automatically set. I use this code as an Omniauth callback:
def facebook_access_token
sign_in #user
puts session.id
end
And in console I get session id different from the one set in the Set-Cookie response header. If I comment "sign_in" line, these values match. New question: how can I get the new session id value after it is been reset inside of sign_in method? Is it an internal Warden/Devise implementation or something?
Renewing is still important and you should not disable it
Also the new session id is generated after the execution of the controller, therefore after you have a chance to set the response to be sent to the client.
The solution is to manually trigger the renewing of the session id
In your ApplicationController add the method:
protected
def commit_session_now!
return unless session.options[:renew]
object = session.options.instance_variable_get('#by')
env = session.options.instance_variable_get('#env')
session_id = object.send(:destroy_session, env, session.id || object.generate_sid, session.options)
session_data = session.to_hash.delete_if { |k,v| v.nil? }
object.send(:set_session, env, session_id, session_data, session.options)
session.options[:renew] = false
session.options[:id] = session_id
end
Then in your controller you just call this method before getting the session id for your response
def my_action
...
commit_session_now!
render json: {session_id: session.id}, status: :ok
end
The code in commit_session_now! comes from Rack::Session::Abstract::ID#commit_session https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb#L327
The problem I experienced was caused by default Warden configuration. It renewed session id, but somehow the new id was not accessible via session.id.
The only way I found to stop this behavior was putting this code into config/initializers/devise.rb:
Warden::Manager.after_set_user do |user,auth,opts|
auth.env["rack.session.options"][:renew] = false
end
Probably this method is not really good for security reasons, but I have no other ideas in a week of searching and reading sources.
Without knowing the details of your application, my suggestion would be to use a before_filter in your ApplicationController:
class ApplicationController < ActionController::Base
before_filter :use_session_id
protected
def use_session_id
# Do something with session.id
# This will get called before any rendering happens
end
end
I am trying to create a session explicitly like this UserSession.create(#user, true) but the session is not getting created, current_user is nil.
But when I do this, I get < #UserSession: {:unauthorized_record=>""}>us = UserSession.create(#user, true)
RAILS_DEFAULT_LOGGER.info(us.inspect) #=> UserSession: {:unauthorized_record=>""}
I had a look at Authlogic::Session::UnauthorizedRecord here it says
Be careful with this, because Authlogic is assuming that you have already confirmed that the user is who he says he is. For example, this is the method used to persist the session internally. Authlogic finds the user with the persistence token. At this point we know the user is who he says he is, so Authlogic just creates a session with the record. This is particularly useful for 3rd party authentication methods, such as OpenID. Let that method verify the identity, once it’s verified, pass the object and create a session.
which is exactly what I am trying to do (i am authenticating using omniauth and creating session using authlogic).
How do I fix this, so that I can get a valid session in current_user ?
I had a similar issue caused by the persistence_token being nil on the user. Reset it before creating the UserSession. So...
#user.reset_persistence_token!
UserSession.create(#user, true)
I'm not sure about the .create(object, bool) method signature, but the following works using authlogic.
class Api::ApiBaseController < ApplicationController
protected
def verify_token
return false if params[:token].blank?
#session = UserSession.new(User.find_by_single_access_token(params[:token]))
#session.save
end
end
If that doesn't work for you -- I think the #user isn't being set correctly.
If you map the active_record_store to the authlogic user_sessions table your session information will be stored in the database, and you will be able to store larger sets of data.
Inside your config folder:
config/initializers/session_store.rb
Comment out App::Application.config.session_store :cookie_store, :key => '_App_session'
Add or uncomment App::Application.config.session_store :active_record_store
Inside of config/application.rb
At the end of the class for you application add:
ActiveRecord::SessionStore::Session.table_name = 'user_sessions'
Restart your app, and any information stored in the user session will be saved in the authlogic user_sessions table.
Goto: http://apidock.com/rails/ActiveRecord/SessionStore
For more information
For now you can replace
UserSession.create #user
to
UserSession.create :email => #user.email, :password => #user.password
not a big deal.
But that caught me other way. I forgot that my user got active? == false when created. I've set it to true and session is created.
I ran into this problem today. In my case it ended up being related to CSRF tokens.
We are creating a user and session in our app in response to an OAuth callback. It appears that if the CSRF token is invalid, which would be the case when coming from a third party, authlogic won't create the user session.
Can't verify CSRF token authenticity
The fix was simple:
class Oauth::UserSessionsController < ApplicationController
skip_before_action :verify_authenticity_token, only: :callback
def new
# code removed...
end
def callback
# code removed...
UserSession.create(#user)
redirect_to root_path
end
end