Commit 750c666b authored by Robert Knight's avatar Robert Knight

Revoke tokens using /oauth/revoke endpoint when logging out.

Use the service's OAuth token revocation endpoint [1] to revoke access
and refresh tokens when the user logs out.

[1] https://github.com/hypothesis/h/pull/4620
parent 1a2c4aac
...@@ -81,14 +81,12 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -81,14 +81,12 @@ function auth($http, $window, flash, localStorage, random, settings) {
}; };
} }
// Post the given data to the tokenUrl endpoint as a form submission. function formPost(url, data) {
// Return a Promise for the access token response.
function postToTokenUrl(data) {
data = queryString.stringify(data); data = queryString.stringify(data);
var requestConfig = { var requestConfig = {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
}; };
return $http.post(tokenUrl, data, requestConfig); return $http.post(url, data, requestConfig);
} }
function grantTokenFromHostPage() { function grantTokenFromHostPage() {
...@@ -151,7 +149,7 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -151,7 +149,7 @@ function auth($http, $window, flash, localStorage, random, settings) {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: grantToken, assertion: grantToken,
}; };
return postToTokenUrl(data).then(function (response) { return formPost(tokenUrl, data).then(function (response) {
if (response.status !== 200) { if (response.status !== 200) {
throw new Error('Failed to retrieve access token'); throw new Error('Failed to retrieve access token');
} }
...@@ -169,7 +167,7 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -169,7 +167,7 @@ function auth($http, $window, flash, localStorage, random, settings) {
grant_type: 'authorization_code', grant_type: 'authorization_code',
code, code,
}; };
return postToTokenUrl(data).then((response) => { return formPost(tokenUrl, data).then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
throw new Error('Authorization code exchange failed'); throw new Error('Authorization code exchange failed');
} }
...@@ -187,7 +185,7 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -187,7 +185,7 @@ function auth($http, $window, flash, localStorage, random, settings) {
*/ */
function refreshAccessToken(refreshToken, options) { function refreshAccessToken(refreshToken, options) {
var data = { grant_type: 'refresh_token', refresh_token: refreshToken }; var data = { grant_type: 'refresh_token', refresh_token: refreshToken };
return postToTokenUrl(data).then((response) => { return formPost(tokenUrl, data).then((response) => {
var tokenInfo = tokenInfoFrom(response); var tokenInfo = tokenInfoFrom(response);
if (options.persist) { if (options.persist) {
...@@ -369,8 +367,13 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -369,8 +367,13 @@ function auth($http, $window, flash, localStorage, random, settings) {
* use it. * use it.
*/ */
function logout() { function logout() {
clearCache(); return accessTokenPromise.then(accessToken => {
return Promise.resolve(null); return formPost(settings.oauthRevokeUrl, {
token: accessToken,
});
}).then(() => {
clearCache();
});
} }
return { return {
......
...@@ -109,6 +109,7 @@ describe('sidebar.oauth-auth', function () { ...@@ -109,6 +109,7 @@ describe('sidebar.oauth-auth', function () {
apiUrl: 'https://hypothes.is/api/', apiUrl: 'https://hypothes.is/api/',
oauthAuthorizeUrl: 'https://hypothes.is/oauth/authorize/', oauthAuthorizeUrl: 'https://hypothes.is/oauth/authorize/',
oauthClientId: 'the-client-id', oauthClientId: 'the-client-id',
oauthRevokeUrl: 'https://hypothes.is/oauth/revoke/',
services: [{ services: [{
authority: 'publisher.org', authority: 'publisher.org',
grantToken: 'a.jwt.token', grantToken: 'a.jwt.token',
...@@ -564,6 +565,8 @@ describe('sidebar.oauth-auth', function () { ...@@ -564,6 +565,8 @@ describe('sidebar.oauth-auth', function () {
return auth.tokenGetter(); return auth.tokenGetter();
}).then(token => { }).then(token => {
assert.notEqual(token, null); assert.notEqual(token, null);
fakeHttp.post.reset();
}); });
}); });
...@@ -580,6 +583,15 @@ describe('sidebar.oauth-auth', function () { ...@@ -580,6 +583,15 @@ describe('sidebar.oauth-auth', function () {
assert.calledWith(fakeLocalStorage.removeItem, TOKEN_KEY); assert.calledWith(fakeLocalStorage.removeItem, TOKEN_KEY);
}); });
}); });
it('revokes tokens', () => {
return auth.logout().then(() => {
var expectedBody = 'token=firstAccessToken';
assert.calledWith(fakeHttp.post, 'https://hypothes.is/oauth/revoke/', expectedBody, {
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
});
});
});
}); });
// Advance time forward so that any current access tokens will have expired. // Advance time forward so that any current access tokens will have expired.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment