Decoupled authentication from core and introduced as a plugin

Change-Id: I149e5f7f5ab2d833d812e6e381da8ad4b45c1ed7
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index 232e96b..8cf581f 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -4,7 +4,7 @@
 use Mojo::URL;
 use Mojo::File;
 use Mojo::JSON 'decode_json';
-use Mojo::Util qw/url_escape/;
+use Mojo::Util qw/url_escape deprecated/;
 use List::Util 'none';
 
 # Minor version - may be patched from package.json
@@ -109,11 +109,6 @@
     push @{$self->static->paths}, 'dev';
   };
 
-  # Check search configuration
-
-  # Set endpoint
-  $self->config('Search')->{api} //= $kalamar_conf->{api};
-
   # Client notifications
   $self->plugin(Notifications => {
     'Kalamar::Plugin::Notifications' => 1,
@@ -178,14 +173,22 @@
     };
   };
 
-  # Deprecated Legacy code -
-  # TODO: Remove 2019-02
+  # Deprecated Legacy code
   if ($self->config('Piwik') &&
         none { $_ eq 'Piwik' } @{$conf->{plugins} // []}) {
-    use Data::Dumper;
-    warn Dumper $self->config('Piwik');
-    $self->log->error('Piwik is no longer considered a mandatory plugin');
-    $self->plugin('Piwik');
+
+    # 2018-11-12
+    deprecated 'Piwik is no longer considered a mandatory plugin';
+    $self->plugin('Kalamar::Plugin::Piwik');
+  };
+
+  # Deprecated Legacy code
+  if ($self->config('Kalamar')->{auth_support} &&
+        none { $_ eq 'Auth' } @{$conf->{plugins} // []}) {
+
+    # 2018-11-16
+    deprecated 'auth_support configuration is deprecated in favor of Plugin loading';
+    $self->plugin('Kalamar::Plugin::Auth')
   };
 
   # Configure documentation navigation
@@ -197,26 +200,6 @@
   # Establish routes with authentification
   my $r = $self->routes;
 
-  # Check for auth support
-  $self->defaults(
-    auth_support => $self->config('Kalamar')->{auth_support}
-  );
-
-  # Support auth
-  if ($self->stash('auth_support')) {
-    $r = $r->under(
-      '/' => sub {
-        my $c = shift;
-
-        if ($c->session('auth')) {
-          $c->stash(auth => $c->session('auth'));
-          $c->stash(user => $c->session('user'));
-        };
-        return 1;
-      }
-    );
-  };
-
   # Set footer value
   $self->content_block(footer => {
     inline => '<%= doc_link_to "V ' . $Kalamar::VERSION . '", "korap", "kalamar" %>',
@@ -241,13 +224,6 @@
   my $doc    = $r->route('/corpus/:corpus_id/:doc_id');
   my $text   = $doc->get('/:text_id')->to('search#text_info')->name('text');
   my $match  = $doc->get('/:text_id/:match_id')->to('search#match_info')->name('match');
-
-  # User Management
-  my $user = $r->any('/user')->to(controller => 'User');
-  $user->post('/login')->to(action => 'login')->name('login');
-  $user->get('/logout')->to(action => 'logout')->name('logout');
-  # $r->any('/register')->to(action => 'register')->name('register');
-  # $r->any('/forgotten')->to(action => 'pwdforgotten')->name('pwdforgotten');
 };
 
 
diff --git a/lib/Kalamar/Controller/Search.pm b/lib/Kalamar/Controller/Search.pm
index 4fba8f8..ff11115 100644
--- a/lib/Kalamar/Controller/Search.pm
+++ b/lib/Kalamar/Controller/Search.pm
@@ -112,7 +112,7 @@
   if (!$cutoff && !$c->no_cache) {
 
     # Create cache string
-    my $user = $c->user->handle;
+    my $user = $c->user_handle;
     my $cache_url = $url->clone;
     $cache_url->query->remove('context')->remove('count')->remove('cutoff')->remove('offset');
     $total_cache_str = "total-$user-" . $cache_url->to_string;
diff --git a/lib/Kalamar/Controller/User.pm b/lib/Kalamar/Controller/User.pm
deleted file mode 100644
index af199a2..0000000
--- a/lib/Kalamar/Controller/User.pm
+++ /dev/null
@@ -1,82 +0,0 @@
-package Kalamar::Controller::User;
-use Mojo::Base 'Mojolicious::Controller';
-
-# Login action
-sub login {
-  my $c = shift;
-
-  # Validate input
-  my $v = $c->validation;
-  $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('fwd')) {
-      $c->notify(error => $c->loc('Auth_openRedirectFail'));
-    }
-    elsif ($v->has_error('csrf_token')) {
-      $c->notify(error => $c->loc('Auth_csrfFail'));
-    }
-    else {
-      $c->notify(error => $c->loc('Auth_loginFail'));
-    };
-  }
-
-  # Login user
-  elsif ($c->user->login(
-    $v->param('handle_or_email'),
-    $v->param('pwd')
-  )) {
-    $c->notify(success => $c->loc('Auth_loginSuccess'));
-  }
-
-  else {
-    $c->notify(error => $c->loc('Auth_loginFail'));
-  };
-
-  # Set flash for redirect
-  $c->flash(handle_or_email => $v->param('handle_or_email'));
-
-  # Redirect to slash
-  return $c->relative_redirect_to($v->param('fwd') // 'index');
-};
-
-
-# Logout of the session
-sub logout {
-  my $c = shift;
-
-  # Log out of the system
-  if ($c->user->logout) {
-    $c->notify(success => $c->loc('Auth_logoutSuccess'));
-  }
-
-  # Something went wrong
-  else {
-    $c->notify('error', $c->loc('Auth_logoutFail'));
-  };
-
-  return $c->redirect_to('index');
-};
-
-
-# Currently not in used
-sub register {
-  my $c = shift;
-  $c->render(json => {
-    response => 'register'
-  });
-};
-
-
-# Currently not in use
-sub pwdforgotten {
-  my $c = shift;
-  $c->render(json => {
-    response => 'pwdforgotten'
-  });
-};
-
-1;
diff --git a/lib/Kalamar/Plugin/Auth.pm b/lib/Kalamar/Plugin/Auth.pm
new file mode 100644
index 0000000..d61d77f
--- /dev/null
+++ b/lib/Kalamar/Plugin/Auth.pm
@@ -0,0 +1,271 @@
+package Kalamar::Plugin::Auth;
+use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::ByteStream 'b';
+
+# TODO:
+#   Get rid of auth_support for templates!
+# TODO:
+#   Make all authentification parts in templates
+#   content_block aware!
+
+# Register the plugin
+sub register {
+  my ($plugin, $app, $param) = @_;
+
+
+  # Load parameter from config file
+  if (my $config_param = $app->config('Kalamar-Auth')) {
+    $param = { %$param, %$config_param };
+  };
+
+
+  # Temp
+  $app->defaults(auth_support => 1);
+
+
+  # Load 'notifications' plugin
+  unless (exists $app->renderer->helpers->{notify}) {
+    $app->plugin(Notifications => {
+      HTML => 1
+    });
+  };
+
+
+  # unless ($param->{client_id} && $param->{client_secret}) {
+  #   $mojo->log->error('client_id or client_secret not defined');
+  #   return;
+  # };
+
+  # TODO:
+  #   Define user CHI cache
+
+  $app->plugin('Localize' => {
+    dict => {
+      Auth => {
+        _ => sub { $_->locale },
+        de => {
+          loginSuccess => 'Anmeldung erfolgreich',
+          loginFail => 'Anmeldung fehlgeschlagen',
+          logoutSuccess => 'Abmeldung erfolgreich',
+          logoutFail => 'Abmeldung fehlgeschlagen',
+          csrfFail => 'Fehlerhafter CSRF Token',
+          openRedirectFail => 'Weiterleitungsfehler'
+        },
+        -en => {
+          loginSuccess => 'Login successful',
+          loginFail => 'Access denied',
+          logoutSuccess => 'Logout successful',
+          logoutFail => 'Logout failed',
+          csrfFail => 'Bad CSRF token',
+          openRedirectFail => 'Redirect failure'
+        }
+      }
+    }
+  });
+
+
+  # Inject authorization to all korap requests
+  $app->hook(
+    before_korap_request => sub {
+      my ($c, $tx) = @_;
+      my $auth_token = $c->auth->token or return;
+      my $h = $tx->req->headers;
+      $h->header('Authorization' => $auth_token);
+    }
+  );
+
+
+  # Get the user token necessary for authorization
+  $app->helper(
+    'auth.token' => sub {
+      my $c = shift;
+
+      # Get token from stash
+      my $token = $c->stash('auth');
+
+      return $token if $token;
+
+      # Get auth from session
+      my $auth = $c->session('auth') or return;
+
+      # Set token to stash
+      $c->stash(auth => $auth);
+
+      return $auth;
+    }
+  );
+
+
+  # Log in to the system
+  my $r = $app->routes;
+  $r->post('/user/login')->to(
+    cb => sub {
+      my $c = shift;
+
+      # Validate input
+      my $v = $c->validation;
+      $v->required('handle_or_email', 'trim');
+      $v->required('pwd', 'trim');
+      $v->csrf_protect;
+      $v->optional('fwd')->closed_redirect;
+
+      my $user = $v->param('handle_or_email');
+      my $fwd = $v->param('fwd');
+
+      # Set flash for redirect
+      $c->flash(handle_or_email => $user);
+
+      if ($v->has_error || index($user, ':') >= 0) {
+        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 {
+          $c->notify(error => $c->loc('Auth_loginFail'));
+        };
+
+        return $c->relative_redirect_to($fwd // 'index');
+      }
+
+      my $pwd = $v->param('pwd');
+
+      $c->app->log->debug("Login from user $user:$pwd");
+
+      my $url = Mojo::URL->new($c->korap->api)->path('auth/apiToken');
+
+      # Korap request for login
+      $c->korap_request('get', $url, {
+
+        # Set authorization header
+        Authorization => 'Basic ' . b("$user:$pwd")->b64_encode->trim,
+
+      })->then(
+        sub {
+          my $tx = shift;
+
+          # Get the java token
+          my $jwt = $tx->result->json;
+
+          # No java web token
+          unless ($jwt) {
+            $c->notify(error => 'Response is no valid JWT (remote)');
+            return;
+          };
+
+          # There is an error here
+          # Dealing with errors here
+          if (my $error = $jwt->{error}) {
+            if (ref $error eq 'ARRAY') {
+              $c->notify(error => $c->dumper($_));
+            }
+            else {
+              $c->notify(error => 'There is an unknown JWT error');
+            };
+            return;
+          };
+
+          # TODO: Deal with user return values.
+          my $auth = $jwt->{token_type} . ' ' . $jwt->{token};
+
+          $c->app->log->debug(qq!Login successful: "$user" with "$auth"!);
+
+          $user = $jwt->{username} ? $jwt->{username} : $user;
+
+          # Set session info
+          $c->session(user => $user);
+          $c->session(auth => $auth);
+
+          # Set stash info
+          $c->stash(user => $user);
+          $c->stash(auth => $auth);
+
+          # Set cache
+          $c->chi('user')->set($auth => $user);
+          $c->notify(success => $c->loc('Auth_loginSuccess'));
+        }
+      )->catch(
+        sub {
+          my $e = shift;
+
+          # Notify the user
+          $c->notify(
+            error =>
+              ($e->{code} ? $e->{code} . ': ' : '') .
+              $e->{message} . ' for Login (remote)'
+            );
+
+          # Log failure
+          $c->app->log->debug(
+            ($e->{code} ? $e->{code} . ' - ' : '') .
+              $e->{message}
+            );
+
+          $c->app->log->debug(qq!Login fail: "$user"!);
+          $c->notify(error => $c->loc('Auth_loginFail'));
+        }
+      )->finally(
+        sub {
+
+          # Redirect to slash
+          return $c->relative_redirect_to($fwd // 'index');
+        }
+      )
+
+      # Start IOLoop
+      ->wait;
+
+      return 1;
+    }
+  )->name('login');
+
+
+  # Log out of the session
+  $r->get('/user/logout')->to(
+    cb => sub {
+      my $c = shift;
+
+      # TODO: csrf-protection!
+
+      # Log out of the system
+      my $url = Mojo::URL->new($c->korap->api)->path('auth/logout');
+
+      $c->korap_request(
+        'get', $url
+      )->then(
+        # Logged out
+        sub {
+          my $tx = shift;
+          # Clear cache
+          $c->chi('user')->remove($c->auth->token);
+
+          # Expire session
+          $c->session(user => undef);
+          $c->session(auth => undef);
+          $c->notify(success => $c->loc('Auth_logoutSuccess'));
+        }
+
+      )->catch(
+        # Something went wrong
+        sub {
+          # my $err_msg = shift;
+          $c->notify('error', $c->loc('Auth_logoutFail'));
+        }
+
+      )->finally(
+        # Redirect
+        sub {
+          return $c->redirect_to('index');
+        }
+      )
+
+      # Start IOLoop
+      ->wait;
+
+      return 1;
+    }
+  )->name('logout');
+};
+
+1;
diff --git a/lib/Kalamar/Plugin/KalamarHelpers.pm b/lib/Kalamar/Plugin/KalamarHelpers.pm
index 295a04f..f5e995b 100644
--- a/lib/Kalamar/Plugin/KalamarHelpers.pm
+++ b/lib/Kalamar/Plugin/KalamarHelpers.pm
@@ -2,7 +2,7 @@
 use Mojo::Base 'Mojolicious::Plugin';
 use Mojo::JSON qw/decode_json true false/;
 use Mojo::ByteStream 'b';
-use Mojo::Util qw/xml_escape/;
+use Mojo::Util qw/xml_escape deprecated/;
 
 sub register {
   my ($plugin, $mojo) = @_;
@@ -223,6 +223,9 @@
     kalamar_test_port => sub {
       my $c = shift;
 
+      # 2018-11-15
+      deprecated 'kalamar_test_port is deprecated and will be removed';
+
       # Test port is defined in the stash
       if (defined $c->stash('kalamar.test_port')) {
         return $c->stash('kalamar.test_port');
@@ -240,6 +243,7 @@
       return 0;
     });
 
+
   # Establish 'search_results' taghelper
   # This is based on Mojolicious::Plugin::Search
   $mojo->helper(
@@ -270,9 +274,11 @@
     }
   );
 
+
+  # Get the KorAP API endpoint
   $mojo->helper(
     'korap.api' => sub {
-      return shift->config('Search')->{api};
+      return shift->config('Kalamar')->{api};
     }
   );
 
@@ -284,14 +290,18 @@
 
       # In case the user is not known, it is assumed,
       # the user is not logged in
-      my $user = $c->user->handle;
+      # TODO:
+      #   Make this more general
+      my $user = $c->user_handle;
 
       # Set api request for debugging
       my $cache_str = "$method-$user-" . $url->to_string;
       $c->stash(api_request => $url->to_string);
 
+      # No cache request
       if ($c->no_cache) {
-        return $c->user->auth_request_p($method => $url)->then(
+
+        return $c->korap_request($method => $url)->then(
           sub {
             my $tx = shift;
             # Catch errors and warnings
@@ -306,6 +316,9 @@
 
       my $promise;
 
+      # TODO:
+      #   emit_hook(after_koral_fetch => $c)
+
       # Cache was found
       if ($koral) {
 
@@ -324,7 +337,8 @@
       };
 
       # Resolve request
-      return $c->user->auth_request_p($method => $url)->then(
+      # Before: user->auth_request_p
+      return $c->korap_request($method => $url)->then(
         sub {
           my $tx = shift;
           return ($c->catch_errors_and_warnings($tx) ||
@@ -340,7 +354,6 @@
       );
     }
   );
-
 };
 
 
diff --git a/lib/Kalamar/Plugin/KalamarUser.pm b/lib/Kalamar/Plugin/KalamarUser.pm
index 4cf7642..d0340e8 100644
--- a/lib/Kalamar/Plugin/KalamarUser.pm
+++ b/lib/Kalamar/Plugin/KalamarUser.pm
@@ -1,12 +1,15 @@
 package Kalamar::Plugin::KalamarUser;
 use Mojo::Base 'Mojolicious::Plugin';
+use Mojo::Util qw/deprecated/;
 use Mojo::Promise;
 use Mojo::ByteStream 'b';
 
 has 'api';
 has 'ua';
 
-# TODO: Merge with meta-button
+# TODO:
+#   This Plugin will be removed in favour of
+#   Kalamar::Plugin::Auth!
 
 sub register {
   my ($plugin, $mojo, $param) = @_;
@@ -34,11 +37,79 @@
   # Set app to server
   $plugin->ua->server->app($mojo);
 
+  # Get user handle
+  $mojo->helper(
+    'user_handle' => sub {
+      my $c = shift;
+
+      # Get from stash
+      my $user = $c->stash('user');
+      return $user if $user;
+
+      # Get from session
+      $user = $c->session('user');
+
+      # Set in stash
+      if ($user) {
+        $c->stash(user => $user);
+        return $user;
+      };
+
+      return 'not_logged_in';
+    }
+  );
+
+  # This is a new general korap_request helper,
+  # that can trigger some hooks for, e.g., authentication
+  # or analysis. It returns a promise.
+  $mojo->helper(
+    'korap_request' => sub {
+      my $c      = shift;
+      my $method = shift;
+      my $path   = shift;
+
+      # Get plugin user agent
+      my $ua = $plugin->ua;
+
+      my $url = Mojo::URL->new($path);
+      my $tx = $ua->build_tx(uc($method), $url, @_);
+
+      # Set X-Forwarded for
+      $tx->req->headers->header(
+        'X-Forwarded-For' => $c->client_ip
+      );
+
+
+      # Emit Hook to alter request
+      $c->app->plugins->emit_hook(
+        before_korap_request => ($c, $tx)
+      );
+
+      return $ua->start_p($tx);
+    }
+  );
+
+  #############################################
+  # WARNING!                                  #
+  # The following helpers are all deprecated: #
+  #############################################
+
+  $mojo->helper(
+    'user.handle' => sub {
+
+      # 2018-11-16
+      deprecated 'user.handle is deprecated in favour of user_handle!';
+      return shift->user_handle;
+    });
+
   # Get the user token necessary for authorization
   $mojo->helper(
     'user_auth' => sub {
       my $c = shift;
 
+      # 2018-11-16
+      deprecated 'user_auth is deprecated in favour of auth->token!';
+
       # Get token from stash
       my $token = $c->stash('auth');
 
@@ -56,6 +127,10 @@
 
   $mojo->helper(
     'user.ua' => sub {
+
+      # 2018-11-15
+      deprecated 'user->ua is deprecated!';
+
       my $c = shift;
 
       my $auth = $c->user_auth;
@@ -86,33 +161,15 @@
     }
   );
 
-  # Get user handle
-  $mojo->helper(
-    'user.handle' => sub {
-      my $c = shift;
-
-      # Get from stash
-      my $user = $c->stash('user');
-      return $user if $user;
-
-      # Get from session
-      $user = $c->session('user');
-
-      # Set in stash
-      if ($user) {
-        $c->stash(user => $user);
-        return $user;
-      };
-
-      return 'not_logged_in';
-    }
-  );
-
 
   # Request with authorization header
   $mojo->helper(
     'user.auth_request' => sub {
       my $c = shift;
+
+      # 2018-11-15
+      deprecated 'user->auth_request is deprecated!';
+
       my $method = shift;
       my $path = shift;
 
@@ -144,10 +201,14 @@
   # return a promise
   $mojo->helper(
     'user.auth_request_p' => sub {
-      my $c = shift;
+      my $c      = shift;
       my $method = shift;
-      my $path = shift;
+      my $path   = shift;
 
+      # 2018-11-16
+      deprecated 'user->auth_request_p is deprecated!';
+
+      # Get plugin user agent
       my $ua = $plugin->ua;
 
       my $tx;
@@ -175,6 +236,9 @@
       my $c = shift;
       my ($user, $pwd) = @_;
 
+      # 2018-11-16
+      deprecated 'user->login is deprecated!';
+
       return if (index($user, ':') >= 0);
 
       $c->app->log->debug("Login from user $user:$pwd");
@@ -350,6 +414,9 @@
     'user.logout' => sub {
       my $c = shift;
 
+      # 2018-11-16
+      deprecated 'user->logout is deprecated!';
+
       # TODO: csrf-protection!
 
       my $url = Mojo::URL->new($plugin->api)->path('auth/logout');