What's the best action persistence technique for a Catalyst application? - perl

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.)

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.

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.

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.

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).

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.

Related

Dancer Hooks on a per-request method basis?

I'm working on a CRUD application using Dancer. One of the things I need to do is check a user is authorized to perform POST (create) and PUT (update) / DELETE (remove) operations.
I've read up on before hooks in the Dancer documentation, but have been unable to figure out the best way to do varying types of authorization.
For a POST operation, all I want to do is check that a valid API key has been submitted with the request, but for a PUT/DELETE operation, I want to check that the API key submitted matches the user who is attached to the record to be updated or deleted.
I understand how to do the logic behind checking the API keys, but I'm wondering if hooks (or something else) would allow me to call that logic without having to add the same boilerplate function call to every single PUT/POST/DELETE function on every route.
like I told the poster on IRC, I think a combination of https://metacpan.org/pod/Dancer#request (the Dancer request object) and its HTTP verbs querying things should do the trick. See for example: https://metacpan.org/pod/Dancer::Request#is_post .
I'm not sure if it's a very elegant solution, but I think it should work.
Here's another take on this issue, based on experience:
Since Dancer has not had the opportunity to parse your input parameters when the 'before' hook executes, you may not have a consistent way to read in your authentication credentials, if your application allows them to be provided in a variety of ways.
In particular, if you're using input parameters to pass a nonce to prevent CSRF attacks (which you should definitely consider!), you won't have a consistent way to obtain that nonce. You could do your own mini-parameter-parsing within 'before', but that could be messy too.
I ran into this problem when I worked on an application some time ago, and remember having to add the dreaded boilerplate authentication function to every PUT/POST/DELETE route. Then, if you're doing that, it becomes irrelevant to check request->is_post because you're already deciding whether to place the boilerplate authentication function within the route.
I haven't tried this yet, but it may be possible to handle the pre-requisite action in your Route base class, then pass upon success. This will leave your specific packages to handle the request as normal once your base class has verified authentication.
An action can choose not to serve the current request and ask Dancer to process the request with the next matching route. This is done with the pass keyword, like in the following example
get '/say/:word' => sub {
return pass if (params->{word} =~ /^\d+$/);
"I say a word: ".params->{word};
};
get '/say/:number' => sub {
"I say a number: ".params->{number};
};

Is sending the value through POST for each input-change on a form wrong?

I am developing a webshop, where it would be nice if the customer's input is not lost during the checkout process.
I am making a form, and to make sure the data is kept after a refresh, I now send the input-value through post at the onchange event. I store this in a session-object that represents the form. I use this object to fill the form on page-load/refresh.
This does result in a lot of post-requests, one for each input filled instead of just one for the whole form. I can imagine this would impact performance. Is this something I should worry about, and if so, how can I perform the same type of thing without all the requests?
It depends, if you expect enormous count of customers:)
Instead of posting after every change, you can also store these data in cookies on client side and ignore posting at all before complete submit. Or not post after each change but rather after some meaningfull timeout when some changes has been performed...

Perl Catalyst; configuring session expire time and flash behaviour

I just discovered that when I configure the session plugin of a Catalyst app (Catalyst::Plugin::Session) to expire, it screws with the flash data. More specifically, I'm finding that flash data no longer carries over with a new request.
Does this sound normal? How might I cope with this?
Perfectly normal. The whole point of sessions is to be able to associated data from one request with data in another request. When you let the session for some request expire, you are saying that that request's data shouldn't have anything to do with any future request.
More specifically, the flash data is a part of the session data -- see the _save_flash method in the Catalyst/Plugin/Session.pm file, for instance. Also see the big warning for the delete_session method:
NOTE: This method will also delete your flash data.
How to cope with it? You need to persist data from a request using any scheme other than the Session plugin. Without knowing more about your app, what data you are trying to persist, and how you will associate data from an old session with a new request, I couldn't begin to make a more specific recommendation than that.
When configuring the session for example with a database backend you'll have to add flash_to_stash as an option:
<session>
dbi_dbh DB
dbi_table sessions
dbi_id_field id
dbi_data_field session_data
dbi_expires_field expires
flash_to_stash 1
expires 3600
</session>

Post/Redirect/Get pattern for HTTP Responses with application/excel MIME Type

I want to post some data to the server, and in response, I want to create a CSV file, with application/excel as the MIME Type (recently recognized as Internet Media Type), to force the browser to open the generated CSV file in Microsoft Excel. However, I also want to prevent user from re-submitting the same info (re-posting the form) by any accident as the result of refreshing the page.
With simple CRUD operations, I use Post/Redirect/Get pattern, so that any further refreshing will only send HTTP Get Request to the server, without any parameter, thus not changing server's state (Idempotence).
What is the recognized pattern for stopping user from re-submitting (re-posting) the same info to the server, when the response is not a page, but a file?
Any idea?
The Post/Redirect/Get pattern is an answer to a browsing event.
Here, there is no browsing action (the form submission only open a 3rd party app, i.e excel), and so any kind of browsing related pattern will be useless.
I suggest you use both a server side trace of the initial submission (with a unique token maybe), so you can prevent the file generation, and an easy to write client side script like <form onsubmit="this.onsubmit = function(){ return false ; }">
I can offer you one other solution.
Take hash (MD5/SHA256 ..) of your submitted data. The hash will be [fairly] unique.
Put it in list in a session with a time limit, say 5 minutes.
Even your user submit same data. Hash will be same and you can give error message to your user.
If different users can post same data, you can also hold user information in the list. And give error message according to user.

Strategies for preserving form data ( on tab/browser close )

I have an issue with a task management application where occasionally users close their browsers/tabs and the information which they type goes away because they accidentally close a browser/tab, resulting in the loss of the text which they've entered ( and some can spend half an hour entering in text ).
So I have to provide a solution, I have a couple ideas but wanted input on the best to go with, or if you have a better solution let me hear ya.
Option 1:
On the window.onunload or possibly window.onbeforeunload event invoke a confirm() dialog and first test whether the task logging area has any text in it and is not blank. If it's not blank, invoke window.confirm() and ask whether the user wants to close the tab/window without saving a log.
My concern with option #1 is that it may be user intrusive.
Option 2:
On the same event, don't invoke any confirm() but instead forcefully save the text in the task logging area in a cookie. Then possibly offer a button that tries to restore any saved task information from the cookie on the same page, so hitting that button would make it parse the cookies and retrieve the information.
The window.onbeforeunload event works a little strangely. If you define a handler for it, the browser will display a generic message about losing data by navigating away from the page, with the string you return from the handler function inserted into the middle of the message. See here:
alt text http://img291.imageshack.us/img291/8724/windowonbeforeunload.png
So what we do: when we know something on the page is unsaved, we set:
window.onbeforeunload = function(){
return "[SOME CUSTOM MESSAGE FROM THE APP]";
}
and once the user saves, and we know we don't need to show them the message, we set:
window.onbeforeunload = null;
It is a little intrusive, but it's better than your users losing data accidentally.
If the user is daft enough to navigate away before submitting what they have been doing, then they shouldn't mind an intrusion to ask if they mean to do something that is apparently stupid.
Also, SO uses a confirmation dialog on navigating away, and most (some) users here are pretty smart.
This is the easiest to use, and will probably help the users more.
If someone writes a long piece of text, then closes the browser without submitting it, they might be more pleased to sort the problem there and then rather than finding out the next morning they didn't do it...
I would research AJAX frameworks for the particular web server/languages you are using. AJAX would allow you to save form data as it is typed (for example, this is how Google Docs works).