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;