diff --git a/addon/mixins/ui-service-mixin.js b/addon/mixins/ui-service-mixin.js index b58a7284..604dd12c 100644 --- a/addon/mixins/ui-service-mixin.js +++ b/addon/mixins/ui-service-mixin.js @@ -38,6 +38,7 @@ var ServicesMixin = Mixin.create({ let service = this; let lastRemote = this.remote; let storageToriiEventHandler; + let messageToriiEventHandler; return new EmberPromise(function (resolve, reject) { if (lastRemote) { @@ -55,7 +56,34 @@ var ServicesMixin = Mixin.create({ }); } }; + + // Using postMessage as an alternative to localStorage/storageEvent + // for case of web site embedded in iframe + messageToriiEventHandler = function (messageEvent) { + if (messageEvent.data === 'getPendingRequestKey') { + messageEvent.source.postMessage( + JSON.stringify({ pendingRequestKey: service.pendingRequestKey }), + window.location.origin + ); + } else { + const msg = JSON.parse(messageEvent.data); + const key = Object.keys(msg)[0]; + var remoteIdFromEvent = PopupIdSerializer.deserialize( + decodeURIComponent(key) + ); + if (remoteId === remoteIdFromEvent) { + var data = parseMessage(msg[key], keys); + localStorage.removeItem(key); + run(function () { + resolve(data); + }); + } + } + }; + window.addEventListener('message', messageToriiEventHandler); + var pendingRequestKey = PopupIdSerializer.serialize(remoteId); + service.pendingRequestKey = pendingRequestKey; localStorage.setItem(CURRENT_REQUEST_KEY, pendingRequestKey); localStorage.removeItem(WARNING_KEY); @@ -115,6 +143,7 @@ var ServicesMixin = Mixin.create({ }).finally(function () { // didClose will reject this same promise, but it has already resolved. service.close(); + window.removeEventListener('message', messageToriiEventHandler); window.removeEventListener('storage', storageToriiEventHandler); }); }, diff --git a/package.json b/package.json index 9e6cccc9..ff47b7ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "torii", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "description": "A set of clean abstractions for authentication in Ember.js", "keywords": [ "authentication", diff --git a/public/redirect.html b/public/redirect.html index b58b7a26..cda41a69 100644 --- a/public/redirect.html +++ b/public/redirect.html @@ -4,16 +4,49 @@ Torii OAuth Redirect diff --git a/tests/unit/mixins/ui-service-mixin-test.js b/tests/unit/mixins/ui-service-mixin-test.js new file mode 100644 index 00000000..178f50ce --- /dev/null +++ b/tests/unit/mixins/ui-service-mixin-test.js @@ -0,0 +1,129 @@ +/* eslint-disable ember/no-mixins, ember/no-new-mixins, ember/no-classic-classes */ + +import EmberObject from '@ember/object'; +import Evented from '@ember/object/evented'; +import UiServiceMixin, { + CURRENT_REQUEST_KEY, +} from 'torii/mixins/ui-service-mixin'; +import PopupIdSerializer from 'torii/lib/popup-id-serializer'; +import { module, test } from 'qunit'; + +module('Unit | Mixin | ui-service-mixin', function (hooks) { + const originalWindowOpen = window.open; + + const popupId = '09123-asdf'; + const expectedUrl = 'http://authServer'; + const expectedRedirectUrl = 'http://localserver?code=fr'; + const expectedMessage = 'getPendingRequestKey'; + + const mockWindowListener = (event) => { + let msg; + try { + msg = JSON.parse(event.data); + } catch { + // allow + } + if (msg && Object.keys(msg)[0] === 'pendingRequestKey') { + const obj = {}; + const key = PopupIdSerializer.serialize(encodeURIComponent(popupId)); + obj[key] = `${expectedUrl}?redirect_url=${expectedRedirectUrl}`; + window.dispatchEvent( + new MessageEvent('message', { + data: JSON.stringify(obj), + source: window, + }) + ); + } + }; + + const buildMockWindow = function (windowName) { + windowName = windowName || ''; + window.addEventListener('message', mockWindowListener); + return { + name: windowName, + focus() {}, + close() {}, + open() { + this.postMessage(expectedMessage); + }, + postMessage(msg) { + window.dispatchEvent( + new MessageEvent('message', { data: msg, source: window }) + ); + }, + }; + }; + + const buildPopupIdGenerator = function (popupId) { + return { + generate() { + return popupId; + }, + }; + }; + + let Popup = EmberObject.extend(Evented, UiServiceMixin, { + // Open a popup window. + openRemote(_url, pendingRequestKey) { + this.remote = buildMockWindow(pendingRequestKey); + this.remote.open(); + }, + + closeRemote() { + this.remote.closed = true; + }, + + pollRemote() { + if (!this.remote) { + return; + } + }, + }); + + let popup; + + hooks.beforeEach(function () { + popup = Popup.create({ remoteIdGenerator: buildPopupIdGenerator(popupId) }); + localStorage.removeItem(CURRENT_REQUEST_KEY); + }); + + hooks.afterEach(async function () { + localStorage.removeItem(CURRENT_REQUEST_KEY); + window.open = originalWindowOpen; + window.removeEventListener('message', mockWindowListener); + popup.destroy(); + }); + + test('requests pending request key', function (assert) { + assert.expect(1); + + let resultMessage; + const resultMessageListener = (event) => { + resultMessage = event.data; + }; + try { + window.addEventListener('message', resultMessageListener); + + popup.openRemote(expectedUrl, CURRENT_REQUEST_KEY); + + assert.strictEqual( + resultMessage, + expectedMessage, + 'requests pendingRequestKey' + ); + } finally { + window.removeEventListener('message', resultMessageListener); + } + }); + + test('returns data after receiving key', async function (assert) { + const keys = ['redirect_url']; + const result = await popup.open(expectedUrl, keys); + + assert.strictEqual( + result.redirect_url, + expectedRedirectUrl, + 'returns data successfully' + ); + }); +});