Remember application state on login (issue #20)

Change-Id: Ie72bb05ee53080986749e74b87fc4f71b343c270
diff --git a/Changes b/Changes
index d1e1eb2..a632a27 100755
--- a/Changes
+++ b/Changes
@@ -1,4 +1,4 @@
-0.26 2018-02-14
+0.26 2018-03-19
         - Added meta data view.
         - Attach reference line to match bottom.
         - Separate match views and integrate relation menu
@@ -9,6 +9,9 @@
         - Generalized Poliqarp tutorials and made
           example queries corpus independent.
         - Improve README.
+        - Protect login for csrf attacks.
+        - Remember app state after login (issue #20).
+        - Fixed DRuKoLA annotation assistant data.
 
 0.25 2018-01-31
         - Make annotation helper configurable.
diff --git a/Makefile.PL b/Makefile.PL
index 992ef8f..1781435 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -17,6 +17,7 @@
     'Mojolicious' => '7.66',
     'Mojolicious::Plugin::TagHelpers::Pagination' => 0.06,
     'Mojolicious::Plugin::TagHelpers::MailToChiffre' => 0.09,
+    'Mojolicious::Plugin::ClosedRedirect' => 0.13,
     'Mojolicious::Plugin::Notifications' => 1.01,
     'Mojolicious::Plugin::MailException' => 0.20,
     'Mojolicious::Plugin::CHI' => 0.15,
diff --git a/kalamar.dict b/kalamar.dict
index dcf75ff..b9c805d 100644
--- a/kalamar.dict
+++ b/kalamar.dict
@@ -59,7 +59,8 @@
       loginFail => 'Anmeldung fehlgeschlagen',
       logoutSuccess => 'Abmeldung erfolgreich',
       logoutFail => 'Abmeldung fehlgeschlagen',
-      csrfFail => 'Fehlerhafter CSRF Token'
+      csrfFail => 'Fehlerhafter CSRF Token',
+      openRedirectFail => 'Weiterleitungsfehler'
     },
     Template => {
       intro => 'de/intro',
@@ -153,7 +154,8 @@
       loginFail => 'Access denied',
       logoutSuccess => 'Logout successful',
       logoutFail => 'Logout failed',
-      csrfFail => 'Bad CSRF token'
+      csrfFail => 'Bad CSRF token',
+      openRedirectFail => 'Redirect failure'
     },
     Template => {
       intro => 'intro',
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index 43e98b6..7eec66c 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -8,11 +8,9 @@
 # Minor version - may be patched from package.json
 our $VERSION = '0.26';
 
-# TODO: Use CSRF!!!
 # TODO: The FAQ-Page has a contact form for new questions
 # TODO: Embed query serialization
 # TODO: Embed collection statistics
-# TODO: Show further meta data per click
 # TODO: Implement tab opener for matches and the tutorial
 # TODO: Implement a "projects" system
 
@@ -130,7 +128,8 @@
     'TagHelpers::MailToChiffre', # Obfuscate email addresses
     'KalamarHelpers',            # Specific Helpers for Kalamar
     'KalamarUser',               # Specific Helpers for Kalamar
-    'ClientIP'                   # Get client IP from X-Forwarded-For
+    'ClientIP',                  # Get client IP from X-Forwarded-For
+    'ClosedRedirect'             # Redirect with OpenRedirect protection
   ) {
     $self->plugin($_);
   };
diff --git a/lib/Kalamar/Controller/User.pm b/lib/Kalamar/Controller/User.pm
index a77062d..6eb0372 100644
--- a/lib/Kalamar/Controller/User.pm
+++ b/lib/Kalamar/Controller/User.pm
@@ -10,9 +10,13 @@
   $v->required('handle_or_email', 'trim');
   $v->required('pwd', 'trim');
   $v->csrf_protect;
+  $v->optional('fwd')->closed_redirect;
 
   if ($v->has_error) {
-    if ($v->has_error('csrf_token')) {
+    if ($v->has_error('fwd')) {
+      $c->notify(error => $c->loc('Auth_openRedirectFail'));
+    }
+    elsif ($v->has_error('csrf_token')) {
       $c->notify(error => $c->loc('Auth_csrfFail'));
     }
     else {
@@ -36,7 +40,7 @@
   $c->flash(handle_or_email => $v->param('handle_or_email'));
 
   # Redirect to slash
-  return $c->redirect_to('index');
+  return $c->relative_redirect_to($v->param('fwd') // 'index');
 };
 
 
diff --git a/package.json b/package.json
index 6e75732..451ae0d 100755
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "Kalamar",
   "description": "Mojolicious-based Frontend for KorAP",
   "license" : "BSD-2-Clause",
-  "version": "0.26.1",
+  "version": "0.26.2",
   "repository" : {
     "type": "git",
     "url": "https://github.com/KorAP/Kalamar.git"
diff --git a/t/remote_user.t b/t/remote_user.t
index cb9a7b6..4ac8a8e 100644
--- a/t/remote_user.t
+++ b/t/remote_user.t
@@ -82,6 +82,43 @@
   ->content_like(qr/\"authorized\"\:null/)
   ;
 
+# Get redirect
+my $fwd = $t->get_ok('/?q=Baum&ql=poliqarp')
+  ->status_is(200)
+  ->element_exists_not('div.notify-error')
+  ->tx->res->dom->at('input[name=fwd]')->attr('value')
+  ;
+
+is($fwd, '/?q=Baum&ql=poliqarp', 'Redirect is valid');
+
+$t->post_ok('/user/login' => form => {
+  handle_or_email => 'test',
+  pwd => 'pass',
+  csrf_token => $csrf,
+  fwd => 'http://bad.example.com/test'
+})
+  ->status_is(302)
+  ->header_is('Location' => '/');
+
+$t->get_ok('/')
+  ->status_is(200)
+  ->element_exists('div.notify-error')
+  ->element_exists_not('div.notify-success')
+  ->text_is('div.notify-error', 'Redirect failure')
+  ;
+
+$t->post_ok('/user/login' => form => {
+  handle_or_email => 'test',
+  pwd => 'pass',
+  csrf_token => $csrf,
+  fwd => $fwd
+})
+  ->status_is(302)
+  ->header_is('Location' => '/?q=Baum&ql=poliqarp');
+
+
+
+
 done_testing;
 __END__
 
diff --git a/templates/layouts/main.html.ep b/templates/layouts/main.html.ep
index 78ed722..f1e993a 100644
--- a/templates/layouts/main.html.ep
+++ b/templates/layouts/main.html.ep
@@ -35,6 +35,7 @@
   <legend><span><%= loc 'login' %></span></legend>
     %= csrf_field
     %= text_field 'handle_or_email', placeholder => loc('userormail')
+    %= hidden_field fwd => $c->url_with
     <div>
       %= password_field 'pwd', placeholder => loc('pwd')
       <button type="submit"><span><%= loc 'go' %></span></button>