Refresh expired credentials

Show error toast with a link to sign-in child window. Close child window
on successful login and refresh credentials without reloading page.
Listen to window focus (using Page Visibility API) to refresh
credentials immediately after window/tab focus.

Feature: Issue 4097
Change-Id: If5c798b6ac3b4cb97f2ac537c9f5b4b58cf58f9a
This commit is contained in:
Viktar Donich
2016-07-12 12:09:26 -07:00
committed by Andrew Bonventre
parent 28bb25d510
commit a48ddd7aae
5 changed files with 151 additions and 36 deletions

View File

@@ -14,16 +14,17 @@
(function() {
'use strict';
var HIDE_ALERT_TIMEOUT_MS = 5000;
var CHECK_SIGN_IN_INTERVAL_MS = 60000;
var SIGN_IN_WIDTH_PX = 690;
var SIGN_IN_HEIGHT_PX = 500;
Polymer({
is: 'gr-error-manager',
properties: {
_alertElement: Element,
_hideAlertHandle: Number,
_hideAlertTimeout: {
type: Number,
value: 5000,
},
},
attached: function() {
@@ -67,7 +68,7 @@
this._clearHideAlertHandle();
this._hideAlertHandle =
this.async(this._hideAlert.bind(this), this._hideAlertTimeout);
this.async(this._hideAlert, HIDE_ALERT_TIMEOUT_MS);
var el = this._createToastAlert();
el.show(text);
this._alertElement = el;
@@ -88,12 +89,17 @@
},
_showAuthErrorAlert: function() {
// TODO(viktard): close alert if it's not for auth error.
if (this._alertElement) { return; }
var el = this._createToastAlert();
el.addEventListener('action', this._refreshPage.bind(this));
el.show('Auth error', 'Refresh page');
this._alertElement = el;
this._alertElement = this._createToastAlert();
this._alertElement.show('Auth error', 'Refresh credentials.');
this.listen(this._alertElement, 'action', '_createLoginPopup');
if (typeof document.hidden !== undefined) {
this.listen(document, 'visibilitychange', '_handleVisibilityChange');
}
this._requestCheckLoggedIn();
},
_createToastAlert: function() {
@@ -102,8 +108,44 @@
return el;
},
_refreshPage: function() {
window.location.reload();
_handleVisibilityChange: function() {
if (!document.hidden) {
this.flushDebouncer('checkLoggedIn');
}
},
_requestCheckLoggedIn: function() {
this.debounce(
'checkLoggedIn', this._checkSignedIn, CHECK_SIGN_IN_INTERVAL_MS);
},
_checkSignedIn: function() {
this.$.restAPI.refreshCredentials().then(function(isLoggedIn) {
if (isLoggedIn) {
this._handleCredentialRefresh();
} else {
this._requestCheckLoggedIn();
}
}.bind(this));
},
_createLoginPopup: function(e) {
var left = window.screenLeft + (window.outerWidth - SIGN_IN_WIDTH_PX) / 2;
var top = window.screenTop + (window.outerHeight - SIGN_IN_HEIGHT_PX) / 2;
var options = [
'width=' + SIGN_IN_WIDTH_PX,
'height=' + SIGN_IN_HEIGHT_PX,
'left=' + left,
'top=' + top,
];
window.open('/login/%3FcloseAfterLogin', '_blank', options.join(','));
},
_handleCredentialRefresh: function() {
this.unlisten(document, 'visibilitychange', '_handleVisibilityChange');
this.unlisten(this._alertElement, 'action', '_createLoginPopup');
this._hideAlert();
this._showAlert('Credentials refreshed.');
},
});
})();

View File

@@ -33,27 +33,32 @@ limitations under the License.
<script>
suite('gr-error-manager tests', function() {
var element;
var sandbox;
setup(function() {
sandbox = sinon.sandbox.create();
stub('gr-rest-api-interface', {
getLoggedIn: function() { return Promise.resolve(true); },
});
element = fixture('basic');
});
teardown(function() {
sandbox.restore();
});
test('show auth error', function(done) {
var showAuthErrorStub = sinon.stub(element, '_showAuthErrorAlert');
var showAuthErrorStub = sandbox.stub(element, '_showAuthErrorAlert');
element.fire('server-error', {response: {status: 403}});
flush(function() {
assert.isTrue(showAuthErrorStub.calledOnce);
showAuthErrorStub.restore();
done();
});
});
test('show normal server error', function(done) {
var showAlertStub = sinon.stub(element, '_showAlert');
var textSpy = sinon.spy(function() { return Promise.resolve('ZOMG'); });
var showAlertStub = sandbox.stub(element, '_showAlert');
var textSpy = sandbox.spy(function() { return Promise.resolve('ZOMG'); });
element.fire('server-error', {response: {status: 500, text: textSpy}});
assert.isTrue(textSpy.called);
@@ -61,14 +66,13 @@ limitations under the License.
assert.isTrue(showAlertStub.calledOnce);
assert.isTrue(showAlertStub.lastCall.calledWithExactly(
'Server error: ZOMG'));
showAlertStub.restore();
done();
});
});
test('show network error', function(done) {
var consoleErrorStub = sinon.stub(console, 'error');
var showAlertStub = sinon.stub(element, '_showAlert');
var consoleErrorStub = sandbox.stub(console, 'error');
var showAlertStub = sandbox.stub(element, '_showAlert');
element.fire('network-error', {error: new Error('ZOMG')});
flush(function() {
assert.isTrue(showAlertStub.calledOnce);
@@ -76,10 +80,45 @@ limitations under the License.
'Server unavailable'));
assert.isTrue(consoleErrorStub.calledOnce);
assert.isTrue(consoleErrorStub.lastCall.calledWithExactly('ZOMG'));
showAlertStub.restore();
consoleErrorStub.restore();
done();
});
});
test('show auth refresh toast', function(done) {
var refreshStub = sandbox.stub(element.$.restAPI, 'refreshCredentials',
function() { return Promise.resolve(true); });
var toastSpy = sandbox.spy(element, '_createToastAlert');
var windowOpen = sandbox.stub(window, 'open');
element.fire('server-error', {response: {status: 403}});
flush(function() {
assert.isTrue(toastSpy.called);
var toast = toastSpy.lastCall.returnValue;
assert.isOk(toast);
assert.include(
Polymer.dom(toast.root).textContent, 'Auth error');
assert.include(
Polymer.dom(toast.root).textContent, 'Refresh credentials.');
assert.isFalse(windowOpen.called);
toast.fire('action');
assert.isTrue(windowOpen.called);
var hideToastSpy = sandbox.spy(toast, 'hide');
assert.isFalse(refreshStub.called);
element.flushDebouncer('checkLoggedIn');
flush(function() {
assert.isTrue(refreshStub.called);
assert.isTrue(hideToastSpy.called);
assert.notStrictEqual(toastSpy.lastCall.returnValue, toast);
toast = toastSpy.lastCall.returnValue;
assert.isOk(toast);
assert.include(
Polymer.dom(toast.root).textContent, 'Credentials refreshed');
done();
});
});
});
});
</script>

View File

@@ -40,6 +40,10 @@
// Routes.
page('/', loadUser, function(data) {
if (data.querystring.match(/^closeAfterLogin/)) {
// Close child window on redirect after login.
window.close();
}
// For backward compatibility with GWT links.
if (data.hash) {
page.redirect(data.hash);