Revoke refresh token on logout (cherrypicked)

Change-Id: I30504a15e36d60a832d3a9b8fcd8286ff8400464
diff --git a/Changes b/Changes
index 7d28cfa..ef8de58 100755
--- a/Changes
+++ b/Changes
@@ -10,6 +10,7 @@
         - Fix treatment of legacy "collection" parameter.
         - Fix pagination by not repeating page value in URL.
         - Added auto-refresh of OAuth tokens.
+        - Added token revocation on logout.
 
         WARNING: This requires relogin for all users!
 
diff --git a/lib/Kalamar/Plugin/Auth.pm b/lib/Kalamar/Plugin/Auth.pm
index 6c6a043..7072f9f 100644
--- a/lib/Kalamar/Plugin/Auth.pm
+++ b/lib/Kalamar/Plugin/Auth.pm
@@ -208,7 +208,7 @@
         # Get OAuth access token
         state $r_url = Mojo::URL->new($c->korap->api)->path('oauth2/token');
 
-        $c->app->log->debug("Refresh at " . $r_url);
+        $c->app->log->debug("Refresh at $r_url");
 
         return $c->kalamar_ua->post_p($r_url, {} => form => {
           grant_type => 'refresh_token',
@@ -510,13 +510,73 @@
 
 
     # Log out of the session
-    # $r->get('/user/logout')->to(
-    #  cb => sub {
-    #
-    #    # TODO!
-    #    return shift->redirect_to('index');
-    #  }
-    #)->name('logout');
+    $r->get('/user/logout')->to(
+      cb => sub {
+        my $c = shift;
+
+        # TODO: csrf-protection!
+
+        my $refresh_token = $c->session('auth_r');
+
+        # Revoke the token
+        state $url = Mojo::URL->new($c->korap->api)->path('oauth2/revoke');
+
+        $c->kalamar_ua->post_p($url => {} => form => {
+          client_id => $client_id,
+          client_secret => $client_secret,
+          token => $refresh_token,
+          token_type => 'refresh_token'
+        })->then(
+          sub {
+            my $tx = shift;
+            my $json = $tx->result->json;
+
+            my $promise;
+
+            # Response is fine
+            if ($tx->res->is_success) {
+              $c->app->log->info("Revocation was successful");
+              $c->notify(success => $c->loc('Auth_logoutSuccess'));
+
+              $c->stash(auth => undef);
+              $c->stash(auth_exp => undef);
+              $c->flash(handle_or_email => delete $c->session->{user});
+              delete $c->session->{auth};
+              delete $c->session->{auth_r};
+              delete $c->session->{auth_exp};
+              return Mojo::Promise->resolve;
+            }
+
+            # Token may be invalid
+            $c->notify('error', $c->loc('Auth_logoutFail'));
+
+            # There is a client error - refresh fails
+            if ($tx->res->is_client_error && $json) {
+
+              return Mojo::Promise->reject(
+                $json->{error_description}
+              );
+            };
+
+            # Resource may not be found (404)
+            return Mojo::Promise->reject
+
+          }
+        )->catch(
+          sub {
+            my $err = shift;
+
+            # Server may be irresponsible
+            $c->notify('error', $c->loc('Auth_logoutFail'));
+            return Mojo::Promise->reject($err);
+          }
+        )->finally(
+          sub {
+            return $c->redirect_to('index');
+          }
+        )->wait;
+      }
+    )->name('logout');
   }
 
   # Use JWT login
@@ -668,67 +728,63 @@
         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
+            # ?? Necesseary
+            # $c->chi('user')->remove($c->auth->token);
+
+            # TODO:
+            #   Revoke refresh token!
+            #   based on auth token!
+            # my $refresh_token = $c->chi('user')->get('refr_' . $c->auth->token);
+            # $c->auth->revoke_token($refresh_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');
   };
-
-
-  # Log out of the session
-  $r->get('/user/logout')->to(
-    cb => sub {
-      my $c = shift;
-
-      # TODO: csrf-protection!
-
-      # TODO:
-      #   Revoke refresh token!
-
-      # 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
-          # ?? Necesseary
-          # $c->chi('user')->remove($c->auth->token);
-
-          # TODO:
-          #   Revoke refresh token!
-          #   based on auth token!
-          # my $refresh_token = $c->chi('user')->get('refr_' . $c->auth->token);
-          # $c->auth->revoke_token($refresh_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;
 
 __DATA__
diff --git a/t/plugin/auth-oauth.t b/t/plugin/auth-oauth.t
index 4ce999a..f03f61e 100644
--- a/t/plugin/auth-oauth.t
+++ b/t/plugin/auth-oauth.t
@@ -200,6 +200,10 @@
 # search with authorization
 $t->get_ok('/?q=Baum')
   ->status_is(200)
+  ->session_has('/auth')
+  ->session_is('/auth', 'Bearer ' . $access_token)
+  ->session_is('/auth_r', $refresh_token)
+  ->session_is('/user', 'test')
   ->text_like('h1 span', qr/KorAP: Find .Baum./i)
   ->text_like('#total-results', qr/\d+$/)
   ->element_exists_not('div.notify-error')
@@ -211,6 +215,9 @@
 # Logout
 $t->get_ok('/user/logout')
   ->status_is(302)
+  ->session_hasnt('/auth')
+  ->session_hasnt('/auth_r')
+  ->session_hasnt('/user')
   ->header_is('Location' => '/');
 
 $t->get_ok('/')
@@ -218,6 +225,8 @@
   ->element_exists_not('div.notify-error')
   ->element_exists('div.notify-success')
   ->text_is('div.notify-success', 'Logout successful')
+  ->element_exists("input[name=handle_or_email]")
+  ->element_exists("input[name=handle_or_email][value=test]")
   ;
 
 $t->get_ok('/?q=Baum')
diff --git a/t/server/mock.pl b/t/server/mock.pl
index f8e6e62..2e14035 100644
--- a/t/server/mock.pl
+++ b/t/server/mock.pl
@@ -452,6 +452,26 @@
   }
 };
 
+# Revoke API token
+post '/v1.0/oauth2/revoke' => sub {
+  my $c = shift;
+
+  my $refresh_token = $c->param('token');
+
+  if ($c->param('client_secret') ne 'k414m4r-s3cr3t') {
+    return $c->render(
+      json => {
+        "error_description" => "Invalid client credentials",
+        "error" => "invalid_client"
+      },
+      status => 401
+    );
+  };
+
+  return $c->render(
+    text => ''
+  )
+};
 
 
 app->start;