Commit 375b5743 authored by Robert Knight's avatar Robert Knight

Reload profile information when API tokens are changed by another client.

With this change, logins are automatically synced across different tabs
in the same browser session.
parent a23437ec
...@@ -15,6 +15,11 @@ module.exports = { ...@@ -15,6 +15,11 @@ module.exports = {
GROUPS_CHANGED: 'groupsChanged', GROUPS_CHANGED: 'groupsChanged',
/** The logged-in user changed */ /** The logged-in user changed */
USER_CHANGED: 'userChanged', USER_CHANGED: 'userChanged',
/**
* API tokens were fetched and saved to local storage by another client
* instance.
*/
OAUTH_TOKENS_CHANGED: 'oauthTokensChanged',
// UI state changes // UI state changes
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
var queryString = require('query-string'); var queryString = require('query-string');
var events = require('./events');
var resolve = require('./util/url-util').resolve; var resolve = require('./util/url-util').resolve;
var serviceConfig = require('./service-config'); var serviceConfig = require('./service-config');
...@@ -27,7 +28,7 @@ var serviceConfig = require('./service-config'); ...@@ -27,7 +28,7 @@ var serviceConfig = require('./service-config');
* an opaque access token. * an opaque access token.
*/ */
// @ngInject // @ngInject
function auth($http, $window, flash, localStorage, random, settings) { function auth($http, $rootScope, $window, flash, localStorage, random, settings) {
/** /**
* Authorization code from auth popup window. * Authorization code from auth popup window.
...@@ -214,6 +215,7 @@ function auth($http, $window, flash, localStorage, random, settings) { ...@@ -214,6 +215,7 @@ function auth($http, $window, flash, localStorage, random, settings) {
// Reset cached token information. Tokens will be reloaded from storage // Reset cached token information. Tokens will be reloaded from storage
// on the next call to `tokenGetter()`. // on the next call to `tokenGetter()`.
tokenInfoPromise = null; tokenInfoPromise = null;
$rootScope.$broadcast(events.OAUTH_TOKENS_CHANGED);
} }
}); });
} }
......
...@@ -258,6 +258,10 @@ function session($http, $q, $resource, $rootScope, analytics, annotationUI, auth ...@@ -258,6 +258,10 @@ function session($http, $q, $resource, $rootScope, analytics, annotationUI, auth
return resource.load(); return resource.load();
} }
$rootScope.$on(events.OAUTH_TOKENS_CHANGED, () => {
reload();
});
return { return {
dismissSidebarTutorial: dismissSidebarTutorial, dismissSidebarTutorial: dismissSidebarTutorial,
load: resource.load, load: resource.load,
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
var angular = require('angular'); var angular = require('angular');
var { stringify } = require('query-string'); var { stringify } = require('query-string');
var events = require('../events');
var DEFAULT_TOKEN_EXPIRES_IN_SECS = 1000; var DEFAULT_TOKEN_EXPIRES_IN_SECS = 1000;
var TOKEN_KEY = 'hypothesis.oauth.hypothes%2Eis.token'; var TOKEN_KEY = 'hypothesis.oauth.hypothes%2Eis.token';
...@@ -51,6 +53,7 @@ class FakeWindow { ...@@ -51,6 +53,7 @@ class FakeWindow {
describe('sidebar.oauth-auth', function () { describe('sidebar.oauth-auth', function () {
var $rootScope;
var auth; var auth;
var nowStub; var nowStub;
var fakeHttp; var fakeHttp;
...@@ -137,8 +140,9 @@ describe('sidebar.oauth-auth', function () { ...@@ -137,8 +140,9 @@ describe('sidebar.oauth-auth', function () {
settings: fakeSettings, settings: fakeSettings,
}); });
angular.mock.inject((_auth_) => { angular.mock.inject((_auth_, _$rootScope_) => {
auth = _auth_; auth = _auth_;
$rootScope = _$rootScope_;
}); });
}); });
...@@ -445,12 +449,8 @@ describe('sidebar.oauth-auth', function () { ...@@ -445,12 +449,8 @@ describe('sidebar.oauth-auth', function () {
}); });
}); });
it('reloads tokens when refreshed by another client instance', () => { context('when another client instance saves new tokens', () => {
return login().then(() => { function notifyStoredTokenChange() {
return auth.tokenGetter();
}).then(token => {
assert.equal(token, 'firstAccessToken');
// Trigger "storage" event as if another client refreshed the token. // Trigger "storage" event as if another client refreshed the token.
var storageEvent = new Event('storage'); var storageEvent = new Event('storage');
storageEvent.key = TOKEN_KEY; storageEvent.key = TOKEN_KEY;
...@@ -462,12 +462,31 @@ describe('sidebar.oauth-auth', function () { ...@@ -462,12 +462,31 @@ describe('sidebar.oauth-auth', function () {
}); });
fakeWindow.trigger(storageEvent); fakeWindow.trigger(storageEvent);
}
it('reloads tokens from storage', () => {
return login().then(() => {
return auth.tokenGetter();
}).then(token => {
assert.equal(token, 'firstAccessToken');
notifyStoredTokenChange();
return auth.tokenGetter(); return auth.tokenGetter();
}).then(token => { }).then(token => {
assert.equal(token, 'storedAccessToken'); assert.equal(token, 'storedAccessToken');
}); });
}); });
it('notifies other services about the change', () => {
var onTokenChange = sinon.stub();
$rootScope.$on(events.OAUTH_TOKENS_CHANGED, onTokenChange);
notifyStoredTokenChange();
assert.called(onTokenChange);
});
});
}); });
describe('#login', () => { describe('#login', () => {
......
...@@ -6,7 +6,7 @@ var events = require('../events'); ...@@ -6,7 +6,7 @@ var events = require('../events');
var mock = angular.mock; var mock = angular.mock;
describe('session', function () { describe('sidebar.session', function () {
var $httpBackend; var $httpBackend;
var $rootScope; var $rootScope;
...@@ -426,4 +426,25 @@ describe('session', function () { ...@@ -426,4 +426,25 @@ describe('session', function () {
}); });
}); });
}); });
context('when another client changes the current login', () => {
it('reloads the profile', () => {
fakeAuth.login = sinon.stub().returns(Promise.resolve());
fakeStore.profile.read.returns(Promise.resolve({
userid: 'acct:initial_user@hypothes.is',
}));
return session.load().then(() => {
// Simulate login change happening in a different tab.
fakeStore.profile.read.returns(Promise.resolve({
userid: 'acct:different_user@hypothes.is',
}));
$rootScope.$broadcast(events.OAUTH_TOKENS_CHANGED);
}).then(() => {
assert.equal(session.state.userid, 'acct:different_user@hypothes.is');
});
});
});
}); });
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