tags:

views:

390

answers:

5

I'm writing a Catalyst application that's required to have a fairly short session expiration (15 minutes). I'm using the standard Catalyst framework authentication modules, so the user data is stored in the session -- i.e., when your session expires, you get logged out.

Many of the uses of this application will require >15 minutes to complete, so users will frequently submit a form only to find their session state is gone and they're required to log back in.

If this happens I want to preserve the original form submission, and if they log in successfully, continue on and carry out the form submission just as if the session had not expired.

I've got the authentication stuff being handled by an auto() method in the controller -- if you request an action that requires authentication and you're not currently logged in, you get redirected to the login() method, which displays the login form and then processes it once it's submitted. It seems like it should be possible to store the request and any form parameters when the auto method redirects to the login(), and then pull them back out if the login() succeeds -- but I'm not entirely sure of the best way to grab or store this information in a generic/standard/reusable way. (I'm figuring on storing it in the session and then deleting it once it's pulled back out; if that seems like a bad idea, that's something else to address.)

Is there a standard "best practices" or cookbook way to do this?

(One wrinkle: these forms are being submitted via POST.)

+3  A: 

I can't help thinking that there's a fundamental flaw in mandating a 15 minute timeout in an app that routinely requires >15 minutes between actions.

Be that as it may, I would look at over-riding the Catalyst::Plugin::Session->delete_session method so that any contents of $c->request->body_parameters are serialised and saved (presumably to the database) for later recovery. You would probably want some rudimentary check of the POST arguments to ensure they're what you're expecting.

Similarly, create_session needs to take responsibility for pulling this data back out of the database and making it available to the original form action.

It does seem like a messy situation, and I'm inclined to repeat my first sentence...

UPDATE:

Whether you use delete_session or auto, the paradoxical issue remains: you can't store this info in the session because the time-out event will destroy the session. You've got to store it somewhere more permanent so it survives the session re-initialization. Catalyst::Plugin::Session itself is using Storable, and you should be able to with something along these lines:

use Storable;
...
sub auto {
    ...
    unless (...) { #ie don't do this if processing the login action
        my $formitems = freeze $c->request->body_parameters;
        my $freezer = $rs->update_or_create(
              {user => $c->user, formitems => $formitems} );
        # Don't quote me on the exact syntax, I don't use DBIx::Class
    }
    ...
    my $formitems = $c->request->body_parameters
                  || thaw $rs->find({$user => $c->user})->formitems
                  || {} ;
    # use formitems instead of $c->request->body_parameters from here on in

The underlying table probably has (user CHAR(x), formitems TEXT) or similar. Perhaps a timestamp so that nothing too stale gets recovered. You might also want to store the action you were processing, to be sure the retrieved form items belong to the right form. You know the issues for your app better than me.

RET
as far as the fundamental flaw, these are the challenges that one faces when working for governments -- i really should say no more.messing with the session stuff seems like the wrong thing to me, though, and i'll revise the question with my reasoning.
genehack
I also work for Government, I hear you ;-)If you can embed the logic I described in the MyApp->auto action, so much the better. I envisaged this logic being sprinkled around many controllers and actions, hence the suggestion to over-ride the default session handling. I've revised by answer too.
RET
That revision took longer than I expected...
RET
+1  A: 

I would store the form data as some sort of per user data in the model.

Catalyst::Plugin::Session::PerUser is one way of doing that (albeit somewhat hackishly). I would reccomend using the session plugin only for authentication and storing all the state info in the model that stores your user data instead.

And I totally agree with RET's opinion that the 15 minute limit seems really counter productive in this context.

nothingmuch
A: 

You could always have some javascript on the client that keeps the session from expiring by making a small request every few minutes.

Or you could have AJAX check for an active session before posting the form and presenting the user with a new login box at that time if needed.

Chuck Phillips
Like that PC mini-app that moves the mouse in idle-time to keep the screen saver from triggering?Somehow I don't think the customer would find that acceptable.
RET
I don't see why not. Adding some AJAX to the page sounds simpler than over-riding Catalyst methods and storing the data. Even if you still have to enforce the short timeout, the code behind the submit button could check if you have been logged out and present you with a login form if you have.
Chuck Phillips
A: 

I came across this whilst searching CPAN for something entirely unrelated.

Catalyst::Plugin::Wizard purports to do exactly what you need. The documentation suggests it can redirect to a login page whilst retaining the state of the previous action.

NB: I haven't used it, so can't vouch for its effectiveness.

RET
A: 

In the end, we ended up grabbing the pending request (URL+params) in the auto(), serializing and encrypting it, and passing it via a hidden form element on the login page. If we got a login request with the hidden element populated, we decrypted and deserialized it and then redirected appropriately (making sure to pass through the standard "can this user do this thing" code paths).

genehack
Did you just override the session expiration completely?
Kenny Evitt