Commit e05ba783 authored by Robert Knight's avatar Robert Knight

Get OAuth authorization and revocation endpoints from /api/links

Use the "apiRoutes" service to get the URLs of the `/oauth/authorize`
and `/oauth/revoke` endpoints from `/api/links` instead of from
"oauthAuthorizeUrl" and "oauthRevokeUrl" in app settings.

This makes the client's behavior more consistent in terms of getting all
links to pages within the service from the `/api/links` route.

It also paves the way to enabling the client to use multiple annotation
services, each of which is defined by a single entrypoint (the `/api`
route) from which all other API routes and links into the service are
obtained.
parent ba2de7c8
......@@ -28,7 +28,8 @@ var serviceConfig = require('./service-config');
* an opaque access token.
*/
// @ngInject
function auth($http, $rootScope, $window, flash, localStorage, random, settings) {
function auth($http, $rootScope, $window,
apiRoutes, flash, localStorage, random, settings) {
/**
* Authorization code from auth popup window.
......@@ -336,23 +337,25 @@ function auth($http, $rootScope, $window, flash, localStorage, random, settings)
var left = $window.screen.width / 2 - width / 2;
var top = $window.screen.height /2 - height / 2;
var authUrl = settings.oauthAuthorizeUrl;
authUrl += '?' + queryString.stringify({
client_id: settings.oauthClientId,
origin: $window.location.origin,
response_mode: 'web_message',
response_type: 'code',
state: state,
});
var authWindowSettings = queryString.stringify({
left: left,
top: top,
width: width,
height: height,
}).replace(/&/g, ',');
$window.open(authUrl, 'Login to Hypothesis', authWindowSettings);
return authResponse.then((resp) => {
return apiRoutes.links().then(links => {
var authUrl = links['oauth.authorize'];
authUrl += '?' + queryString.stringify({
client_id: settings.oauthClientId,
origin: $window.location.origin,
response_mode: 'web_message',
response_type: 'code',
state: state,
});
var authWindowSettings = queryString.stringify({
left: left,
top: top,
width: width,
height: height,
}).replace(/&/g, ',');
$window.open(authUrl, 'Login to Hypothesis', authWindowSettings);
return authResponse;
}).then((resp) => {
// Save the auth code. It will be exchanged for an access token when the
// next API request is made.
authCode = resp.code;
......@@ -366,13 +369,15 @@ function auth($http, $rootScope, $window, flash, localStorage, random, settings)
* This revokes and then forgets any OAuth credentials that the user has.
*/
function logout() {
return tokenInfoPromise.then(token => {
return formPost(settings.oauthRevokeUrl, {
token: token.accessToken,
return Promise.all([tokenInfoPromise, apiRoutes.links()])
.then(([token, links]) => {
var revokeUrl = links['oauth.revoke'];
return formPost(revokeUrl, {
token: token.accessToken,
});
}).then(() => {
clearCache();
});
}).then(() => {
clearCache();
});
}
listenForTokenStorageEvents();
......
......@@ -56,6 +56,7 @@ describe('sidebar.oauth-auth', function () {
var $rootScope;
var auth;
var nowStub;
var fakeApiRoutes;
var fakeHttp;
var fakeFlash;
var fakeLocalStorage;
......@@ -104,6 +105,13 @@ describe('sidebar.oauth-auth', function () {
post: sinon.stub().returns(successfulFirstAccessTokenPromise),
};
fakeApiRoutes = {
links: sinon.stub().returns(Promise.resolve({
'oauth.authorize': 'https://hypothes.is/oauth/authorize/',
'oauth.revoke': 'https://hypothes.is/oauth/revoke/',
})),
};
fakeFlash = {
error: sinon.stub(),
};
......@@ -114,9 +122,7 @@ describe('sidebar.oauth-auth', function () {
fakeSettings = {
apiUrl: 'https://hypothes.is/api/',
oauthAuthorizeUrl: 'https://hypothes.is/oauth/authorize/',
oauthClientId: 'the-client-id',
oauthRevokeUrl: 'https://hypothes.is/oauth/revoke/',
services: [{
authority: 'publisher.org',
grantToken: 'a.jwt.token',
......@@ -134,6 +140,7 @@ describe('sidebar.oauth-auth', function () {
angular.mock.module('app', {
$http: fakeHttp,
$window: fakeWindow,
apiRoutes: fakeApiRoutes,
flash: fakeFlash,
localStorage: fakeLocalStorage,
random: fakeRandom,
......@@ -500,20 +507,23 @@ describe('sidebar.oauth-auth', function () {
it('opens the auth endpoint in a popup window', () => {
auth.login();
var params = {
client_id: fakeSettings.oauthClientId,
origin: 'client.hypothes.is',
response_mode: 'web_message',
response_type: 'code',
state: 'notrandom',
};
var expectedAuthUrl = `${fakeSettings.oauthAuthorizeUrl}?${stringify(params)}`;
assert.calledWith(
fakeWindow.open,
expectedAuthUrl,
'Login to Hypothesis',
'height=400,left=312,top=184,width=400'
);
return fakeApiRoutes.links().then((links) => {
var authUrl = links['oauth.authorize'];
var params = {
client_id: fakeSettings.oauthClientId,
origin: 'client.hypothes.is',
response_mode: 'web_message',
response_type: 'code',
state: 'notrandom',
};
var expectedAuthUrl = `${authUrl}?${stringify(params)}`;
assert.calledWith(
fakeWindow.open,
expectedAuthUrl,
'Login to Hypothesis',
'height=400,left=312,top=184,width=400'
);
});
});
it('ignores auth responses if the state does not match', () => {
......
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