Rails Session Timeout/Warning - ruby-on-rails

I've been trying to research this all day and haven't really been able to find anything that fits my needs. We need to create a session timeout (simple) for our application, but we also need a warning message to display x minutes before the timeout of the session will occur.
I have my app set up with the activerecord_session gem and have done the necessary configuration changes. I CAN write up something like this:
before_action :determine_if_session_timed_out
in the ApplicationController. My main concern with doing it this way is db hits and the fact that it seems like there should be a rails way to do it, similar to how the above linked gem has expire_after: xx minutes, but for a warning.
My two questions are--has anyone seen any apps that:
have a built-in rails way of displaying a warning when the session is about to time out?
automatically redirect after the session times out? It looks like the page doesn't redirect with the expire_after implementation for the above gem. It stays on the same page until another action is called and then redirects.

Ajax requests in your application can be caught by this piece of code,.
$(document).ajaxError(function(e, error) { switch(error.status) {
case 401: {
// page redirect unauthorized access.
location.reload();
alert(xhr.responseText);
} }
You can have look at this approach with jquery (https://codedecoder.wordpress.com/2014/05/01/jquery-inactivity-warning-logout-timer-setinterval-clearinterval/)

Related

How to get the myshopify_domain from a ShopifyApp::Authenticated controller without an API call

I am trying to reduce unnecessary calls to the Shopify API from a controller that inherits from ShopifyApp::AuthenticatedController, for example to get the myshopify_domain:
myshopify_domain = ShopifyAPI::Shop.current.myshopify_domain
Is there some method in ShopifyApp::SessionRepository or somewhere else in the ShopifyApp that I can call to retrieve Shop.current.myshopify_domain without making an actual call to the Shopify API webservice? If not, can I store the myshopify_domain, once retrieved, in the ShopifyApp::SessionRepository?
If you are in the AuthenticatedController, dump the following to the console:
session.to_json
You will see that you can access all sorts of stuff about the current session, such as:
session["shopify_domain"]
session["shop_id"]
I had the same problem with a muli-store app, where I needed to pull data tied to a specific store. ShopifyAPI::Shop.current.myshopify_domain is redundant in that you are slowing down the controller waiting for Shopify's response, and you are tinking down the api bucket limit. The session object is the superior method to avoid all of that, and should be accessible from any controller which inherits the ShopifyApp::Authenticated controller.
Your question is confusing. At the point where you are doing calls to the API, you clearly already know the myshopify_domain, as you cannot do API calls with that the shops name and token.
So now we're past that point, and you are asking how you can somehow have the myshopify_domain be more convenient for you to use? Just make yourself a little helper so that when you open a session, you have access to shop_name or whatever you want.
Shopify always sends you shop name in their requests, so you're covered there as it's a param, and your own interface code and calls will also be setting up the shop name too, so you're really now into some pretty esoteric territory to need anything else.
Seem like you're caught in a classic "the dog chasing its own tail", but why?
The myshopify_domain is usually available in the session parameters:
if !session[:myshopify_domain].nil? && !session[:myshopify_domain].empty?
session[:myshopify_domain]
else
session[:myshopify_domain] = ShopifyAPI::Shop.current.myshopify_domain
end

Testing HTTP Requests that require a cookie

I am trying to setup unit tests for all of my HTTP requests. Every request requires authentication, and with my app it requires authentication via cookie & DB query.
I have a preDispatch method in a parent controller that looks like this:
$this->cookie = Cookie::readCookie();
if (is_null($this->cookie))
{
return $this->failResponseView();
}
$this->dm = $this->getServiceLocator()->get('doctrine.documentmanager.odm_default');
//Does not have authoriziation
if (!$this->hasAppAccess())
{
return $this->failResponseView();
}
This has been working fine as far as the app is concerned. But running phpunit fails everytime because the cookie can't be read, or the response is being written before it is read.
This is me mirroring what I do in the regular app, in my test setup method:
$this->_cookie = new Cookie(array('access_token' => $profile['token']));
$this->_cookie->setCookie();
However, I receive this Exception when the code reaches this point. My question is, how can I fake, or bypass my cookie authentication when running phpunit to make sure all of these authenticated requests work?
Cannot modify header information - headers already sent by (output started at D:
\www\app\vendor\phpunit\phpunit\PHPUnit\Util\Printer.php:172)
UPDATE
It looks like since the PHPUnit\Util\Printer is outputting to STDOUT (see above), it's not liking that I am trying to write a cookie. Running this allowed full execution
phpunit --stderr
So I am able to call the setCookie() method, and it executes fine. But when I get to the point where it does Cookie::readCookie(), even though it's already been set, it can't read it. It returns null.
So question is still pretty much the same. What do I do to test this app if it uses cookie authentication?
Ugh, it's always something simple. In my setup method, I can just do this...
$_COOKIE[$name]= $this->_cookie->getData();

Disable Session Lookup for a single controller action

I want to disable sessions completely for a controller action, because I want this single controller action (it's an autocomplete action on thousands of values, so speed matters) to be blazingly fast.
I tried using session_off, but it just sets the session variable to nil, an still looks up the users session in the database.
Is it possible to completely disable the Rails::SessionStore middleware, but only for a single controller action or URL?
I am running rails 3.2.17.
The answer is: handle this endpoint in a Rack middleware of your own, and insert it into the stack as early as possible!
You can achieve this in config/routes.rb just by routing to the middleware object:
match 'my_autocomplete_endpoint', to: AutocompleteMiddleware
then just return a response from the middleware and don't go up the stack.
You can put this wherever you want in the stack in config/application.rb with:
config.middleware.insert_before(SomeOtherMiddleware, AutocompleteMiddleware)
e.g., perhaps insert it before Rails::SessionStore.
Rails 5+ solution (maybe before, not sure when this became available).
Add this to your controller. You can specify which actions should not touch/update the session using the only: option.
after_action -> { request.session_options[:skip] = true }, only: :my_action_name
This will make the response not include the set_cookie response header. I found this particularly useful when dealing with a race condition in multiple AJAX requests, whereas one contained a very important session (cookie data) update and the other the session was not used, but Rails still sent back an updated cookie for the session. The race condition could cause the updated session data from the important action to be overwritten from the one I didn't care about.

Ember.js session cookie based authentication with Rails and devise

I'm looking to satisfy 3 goals with my Ember.js app authentication using rails, devise and a cookie based session.
Redirected to #/sessions/new if they're not logged in.
Always show the current user's information in the application template.
If the user is logged in and they go to #/some/route directly. The current user should be loaded on load.
I've watched these embercast videos: Client-side Authentication Part 1 & Client-side Authentication Part 2. They're a little out of date but helpful.
But still can't full solution. Anyone have full Rails 4, Devise, Emberjs 1.0.0 example?
Biggest problem is having a strategy to load the current user on page load and setting the current user when the sign in form is submitted.
Right now this is my strategy:
App.User = Em.Object.extend();
App.User.reopenClass({
current: function() {
return Ember.$.getJSON("/users/current").then(function(data) {
return data
})
}
});
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return App.User.current();
}
});
App.SessionsNewController = Ember.ObjectController.extend({
actions: {
save: function(data) {
var self = this, data = this.getProperties('email', 'password');
$.post("/sessions", { session: data }).always(function(response, status, data) {
if (status == "success") {
self.transitionToRoute('index');
} else {
self.set('errorMessage', data);
}
})
},
}
});
I would not say this is not doable. But you will do lots of extra and unnecessary works to get the authentication working, which can all be done with a simple page redirect.
I've collected some opinions from Derick, the author of Backbone.Marionette. Though these are for Backbone but not Ember.js, the situation of client side authentication is same.
I find it painful and unnecessary to try and make Backbone/Marionette handle the authentication and re-loading of the authorized site stuff. Once they log in, redirect them to a different URL that the server handles, and have the server send down all the stuff that they need, as an authenticated user. https://stackoverflow.com/a/18151935
Another quote from Derick as well:
Right. And there’s a lot of cases where I just flat out say, “Do not do single-page applications,” as well. And a login screen is the biggest example of that. In all of the clients that I’ve had in the last couple of years, they’ve all asked me, “Hey, I’m having this problem. I’m trying to get my login screen to give me the current user information back from the server and redo all of this stuff on the screen without refreshing everything.” My answer every single time is, “Don’t do that." http://javascriptjabber.com/056-jsj-marionette-js-with-derick-bailey/
Also think about other cases, say Gmail. You won't get a smooth transition after click "Sign in" button on Gmail's sign in page. There will be redirect with rather big data loading as well :)
From users' perspective, they won't say Gmail is not great just because there is a redirect after signing in. After all signing/sign up is much much less frequent than daily mail operations.
So my suggestion is, reload all resources after user session changed. Let Rails and Devise do these dirty jobs in traditional fashion.

Providing updates during a long Rails controller action

I have an action that takes a long time. I want to be able to provide updates during the process so the user is not confused as to whether he lost the connection or something. Can I do something like this:
class HeavyLiftingController < ApplicationController
def data_mine
render_update :js=>"alert('Just starting!')"
# do some complicated find etc.
render_update :js=>"alert('Found the records!')"
# do some processing ...
render_update :js=>"alert('Done processig')"
# send #results to view
end
end
No, you can only issue ONE render within a controller action. The render does NOTHING until the controller terminates. When data_mine terminates, there will be THREE renders, which will result in an error.
UPDATE:
You'll likely have to set up a JavaScript (jquery) timer in the browser, then periodically send an AJAX request to the server to determine the current status of your long running task.
For example the long running task could write a log as it progresses, and the periodic AJAX request would read that log and create some kind of status display, and return that to the browser for display.
It is impossible to handle the request that way. For each request, you have just one answer.
If your action takes a long time, then maybe it should be performed asynchronously. You could send user e-mails during the process to notify him of the progress.
I suggest that you to take a look on DelayedJob gem:
http://rubygems.org/gems/delayed_job
It will handle most difficult parts of dealing with assync stuff for you (serializing / deserializing your objects, storage, so on...).
Hope it helps you!

Resources