From 9dbf9d4a162e48d505ca7e28eff8fac0c540a34d Mon Sep 17 00:00:00 2001 From: Laszlo Vadasz Date: Tue, 26 Nov 2024 15:45:22 +0000 Subject: [PATCH 1/3] fix: too fast polling - skip 1 interval in polling after the server respond with 'slow_down' - prevent emitting events from previous polling sessions - refactored timeout logic issue: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-586223 --- .../src/authorization.js | 105 +++++-- .../test/unit/spec/authorization.js | 278 +++++++++++------- 2 files changed, 256 insertions(+), 127 deletions(-) diff --git a/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js b/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js index cc65a772edd..e1e5532e919 100644 --- a/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js +++ b/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js @@ -14,6 +14,7 @@ import {cloneDeep, isEmpty, omit} from 'lodash'; import uuid from 'uuid'; import base64url from 'crypto-js/enc-base64url'; import CryptoJS from 'crypto-js'; +import {interfaceExtends} from '@babel/types'; // Necessary to require lodash this way in order to stub // methods in the unit test @@ -67,17 +68,49 @@ const Authorization = WebexPlugin.extend({ namespace: 'Credentials', + /** + * EventEmitter for authorization events + * @instance + * @memberof AuthorizationBrowserFirstParty + * @type {EventEmitter} + * @public + */ + eventEmitter: new EventEmitter(), /** - * Stores the interval ID for QR code polling + * Stores the timer ID for QR code polling * @instance * @memberof AuthorizationBrowserFirstParty * @type {?number} * @private */ - pollingRequest: null, + pollingTimer: null, + /** + * Stores the expiration timer ID for QR code polling + * @instance + * @memberof AuthorizationBrowserFirstParty + * @type {?number} + * @private + */ + pollingExpirationTimer: null, - eventEmitter: new EventEmitter(), + /** + * Monotonically increasing id to identify the current polling request + * @instance + * @memberof AuthorizationBrowserFirstParty + * @type {number} + * @private + */ + pollingId: 0, + + /** + * Identifier for the current polling request + * @instance + * @memberof AuthorizationBrowserFirstParty + * @type {?number} + * @private + */ + currentPollingId: null, /** * Initializer @@ -260,7 +293,7 @@ const Authorization = WebexPlugin.extend({ * @emits #qRCodeLogin */ initQRCodeLogin() { - if (this.pollingRequest) { + if (this.pollingTimer) { this.eventEmitter.emit('qRCodeLogin', { eventType: 'getUserCodeFailure', data: {message: 'There is already a polling request'}, @@ -291,7 +324,7 @@ const Authorization = WebexPlugin.extend({ userCode: user_code, verificationUri: verification_uri, verificationUriComplete: verification_uri_complete, - } + }, }); // if device authorization success, then start to poll server to check whether the user has completed authorization this._startQRCodePolling(res.body); @@ -320,7 +353,7 @@ const Authorization = WebexPlugin.extend({ return; } - if (this.pollingRequest) { + if (this.pollingTimer) { this.eventEmitter.emit('qRCodeLogin', { eventType: 'authorizationFailure', data: {message: 'There is already a polling request'}, @@ -328,15 +361,21 @@ const Authorization = WebexPlugin.extend({ return; } - const {device_code: deviceCode, interval = 2, expires_in: expiresIn = 300} = options; + const {device_code: deviceCode, expires_in: expiresIn = 300} = options; + let interval = options.interval ?? 2; - let attempts = 0; - const maxAttempts = expiresIn / interval; + this.pollingExpirationTimer = setTimeout(() => { + this.cancelQRCodePolling(false); + this.eventEmitter.emit('qRCodeLogin', { + eventType: 'authorizationFailure', + data: {message: 'Authorization timed out'}, + }); + }, expiresIn * 1000); - this.pollingRequest = setInterval(() => { - attempts += 1; + const polling = () => { + this.pollingId += 1; + this.currentPollingId = this.pollingId; - const currentAttempts = attempts; this.webex .request({ method: 'POST', @@ -354,7 +393,8 @@ const Authorization = WebexPlugin.extend({ }, }) .then((res) => { - if (this.pollingRequest === null) return; + // if the pollingId has changed, it means that the polling request has been canceled + if (this.currentPollingId !== this.pollingId) return; this.eventEmitter.emit('qRCodeLogin', { eventType: 'authorizationSuccess', @@ -363,23 +403,24 @@ const Authorization = WebexPlugin.extend({ this.cancelQRCodePolling(); }) .catch((res) => { - if (this.pollingRequest === null) return; + // if the pollingId has changed, it means that the polling request has been canceled + if (this.currentPollingId !== this.pollingId) return; - if (currentAttempts >= maxAttempts) { - this.eventEmitter.emit('qRCodeLogin', { - eventType: 'authorizationFailure', - data: {message: 'Authorization timed out'} - }); - this.cancelQRCodePolling(); + // When server sends 400 status code with message 'slow_down', it means that last request happened too soon. + // So, skip one interval and then poll again. + if (res.statusCode === 400 && res.body.message === 'slow_down') { + schedulePolling(interval * 2); return; } + // if the statusCode is 428 which means that the authorization request is still pending // as the end user hasn't yet completed the user-interaction steps. So keep polling. if (res.statusCode === 428) { this.eventEmitter.emit('qRCodeLogin', { eventType: 'authorizationPending', - data: res.body + data: res.body, }); + schedulePolling(interval); return; } @@ -387,10 +428,15 @@ const Authorization = WebexPlugin.extend({ this.eventEmitter.emit('qRCodeLogin', { eventType: 'authorizationFailure', - data: res.body + data: res.body, }); }); - }, interval * 1000); + }; + + const schedulePolling = (interval) => + (this.pollingTimer = setTimeout(polling, interval * 1000)); + + schedulePolling(interval); }, /** @@ -399,14 +445,19 @@ const Authorization = WebexPlugin.extend({ * @memberof AuthorizationBrowserFirstParty * @returns {void} */ - cancelQRCodePolling() { - if (this.pollingRequest) { - clearInterval(this.pollingRequest); + cancelQRCodePolling(withCancelEvent = true) { + if (this.pollingTimer && withCancelEvent) { this.eventEmitter.emit('qRCodeLogin', { eventType: 'pollingCanceled', }); - this.pollingRequest = null; } + + this.currentPollingId = null; + + clearTimeout(this.pollingExpirationTimer); + this.pollingExpirationTimer = null; + clearTimeout(this.pollingTimer); + this.pollingTimer = null; }, /** diff --git a/packages/@webex/plugin-authorization-browser-first-party/test/unit/spec/authorization.js b/packages/@webex/plugin-authorization-browser-first-party/test/unit/spec/authorization.js index 5bf294485fb..26709ca3da2 100644 --- a/packages/@webex/plugin-authorization-browser-first-party/test/unit/spec/authorization.js +++ b/packages/@webex/plugin-authorization-browser-first-party/test/unit/spec/authorization.js @@ -18,7 +18,6 @@ import Authorization from '@webex/plugin-authorization-browser-first-party'; // Necessary to require lodash this way in order to stub the method const lodash = require('lodash'); - describe('plugin-authorization-browser-first-party', () => { describe('Authorization', () => { function makeWebex( @@ -187,14 +186,16 @@ describe('plugin-authorization-browser-first-party', () => { const webex = makeWebex( `http://example.com/?code=${code}&state=${base64.encode( JSON.stringify({emailhash: 'someemailhash'}) - )}`, + )}` ); const requestAuthorizationCodeGrantStub = sinon.stub( Authorization.prototype, 'requestAuthorizationCodeGrant' ); - const collectPreauthCatalogStub = sinon.stub(Services.prototype, 'collectPreauthCatalog').resolves(); + const collectPreauthCatalogStub = sinon + .stub(Services.prototype, 'collectPreauthCatalog') + .resolves(); await webex.authorization.when('change:ready'); @@ -206,9 +207,7 @@ describe('plugin-authorization-browser-first-party', () => { it('collects the preauth catalog no emailhash is present in the state', async () => { const code = 'authcode_clusterid_theOrgId'; - const webex = makeWebex( - `http://example.com/?code=${code}` - ); + const webex = makeWebex(`http://example.com/?code=${code}`); const requestAuthorizationCodeGrantStub = sinon.stub( Authorization.prototype, @@ -271,12 +270,13 @@ describe('plugin-authorization-browser-first-party', () => { it('throws a grant error', () => { let err = null; try { - makeWebex('http://127.0.0.1:8000/?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid.'); - } - catch (e) { + makeWebex( + 'http://127.0.0.1:8000/?error=invalid_scope&error_description=The%20requested%20scope%20is%20invalid.' + ); + } catch (e) { err = e; } - expect(err?.message).toBe('Cannot convert object to primitive value') + expect(err?.message).toBe('Cannot convert object to primitive value'); }); }); @@ -446,14 +446,14 @@ describe('plugin-authorization-browser-first-party', () => { describe('#initQRCodeLogin()', () => { it('should prevent concurrent request if there is already a polling request', async () => { const webex = makeWebex('http://example.com'); - - webex.authorization.pollingRequest = 1; + + webex.authorization.pollingTimer = 1; const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit'); webex.authorization.initQRCodeLogin(); - + assert.calledOnce(emitSpy); - assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeFailure'); - webex.authorization.pollingRequest = null; + assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeFailure'); + webex.authorization.pollingTimer = null; }); it('should send correct request parameters to the API', async () => { @@ -461,19 +461,19 @@ describe('plugin-authorization-browser-first-party', () => { const testClientId = 'test-client-id'; const testScope = 'test-scope'; const sampleData = { - device_code: "test123", + device_code: 'test123', expires_in: 300, - user_code: "421175", - verification_uri: "http://example.com", - verification_uri_complete: "http://example.com", - interval: 2 + user_code: '421175', + verification_uri: 'http://example.com', + verification_uri_complete: 'http://example.com', + interval: 2, }; const webex = makeWebex('http://example.com', undefined, undefined, { credentials: { client_id: testClientId, scope: testScope, - } + }, }); webex.request.onFirstCall().resolves({statusCode: 200, body: sampleData}); sinon.spy(webex.authorization, '_startQRCodePolling'); @@ -481,11 +481,11 @@ describe('plugin-authorization-browser-first-party', () => { webex.authorization.initQRCodeLogin(); clock.tick(2000); - await clock.runAllAsync() + await clock.runAllAsync(); assert.calledTwice(webex.request); assert.calledOnce(webex.authorization._startQRCodePolling); - assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeSuccess'); + assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeSuccess'); const request = webex.request.getCall(0); @@ -498,18 +498,18 @@ describe('plugin-authorization-browser-first-party', () => { const clock = sinon.useFakeTimers(); const webex = makeWebex('http://example.com'); const sampleData = { - device_code: "test123", + device_code: 'test123', expires_in: 300, - user_code: "421175", - verification_uri: "http://example.com", - verification_uri_complete: "http://example.com", - interval: 2 + user_code: '421175', + verification_uri: 'http://example.com', + verification_uri_complete: 'http://example.com', + interval: 2, }; webex.request.resolves().resolves({statusCode: 200, body: sampleData}); webex.authorization.initQRCodeLogin(); clock.tick(2000); - await clock.runAllAsync() + await clock.runAllAsync(); const request = webex.request.getCall(0); assert.equal(request.args[0].method, 'POST'); @@ -517,7 +517,7 @@ describe('plugin-authorization-browser-first-party', () => { assert.equal(request.args[0].resource, '/actions/device/authorize'); clock.restore(); }); - + it('should emit getUserCodeFailure event', async () => { const clock = sinon.useFakeTimers(); const webex = makeWebex('http://example.com'); @@ -526,7 +526,7 @@ describe('plugin-authorization-browser-first-party', () => { webex.authorization.initQRCodeLogin(); - await clock.runAllAsync() + await clock.runAllAsync(); assert.calledOnce(emitSpy); assert.equal(emitSpy.getCall(0).args[1].eventType, 'getUserCodeFailure'); @@ -537,15 +537,15 @@ describe('plugin-authorization-browser-first-party', () => { describe('#_startQRCodePolling()', () => { it('requires a deviceCode', () => { const webex = makeWebex('http://example.com'); - + const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit'); webex.authorization._startQRCodePolling({}); assert.calledOnce(emitSpy); - assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationFailure'); + assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationFailure'); }); - + it('should send correct request parameters to the API', async () => { const clock = sinon.useFakeTimers(); const testClientId = 'test-client-id'; @@ -560,7 +560,7 @@ describe('plugin-authorization-browser-first-party', () => { const webex = makeWebex('http://example.com', undefined, undefined, { credentials: { client_id: testClientId, - } + }, }); webex.request.onFirstCall().resolves({statusCode: 200, body: {access_token: 'token'}}); @@ -569,7 +569,7 @@ describe('plugin-authorization-browser-first-party', () => { webex.authorization._startQRCodePolling(options); clock.tick(2000); - await clock.runAllAsync() + await clock.runAllAsync(); assert.calledOnce(webex.request); @@ -577,7 +577,10 @@ describe('plugin-authorization-browser-first-party', () => { assert.equal(request.args[0].form.client_id, testClientId); assert.equal(request.args[0].form.device_code, testDeviceCode); - assert.equal(request.args[0].form.grant_type, 'urn:ietf:params:oauth:grant-type:device_code'); + assert.equal( + request.args[0].form.grant_type, + 'urn:ietf:params:oauth:grant-type:device_code' + ); assert.calledOnce(webex.authorization.cancelQRCodePolling); assert.calledTwice(emitSpy); @@ -593,23 +596,26 @@ describe('plugin-authorization-browser-first-party', () => { const options = { device_code: 'test-device-code', interval: 2, - expires_in: 300 + expires_in: 300, }; - - webex.request.onFirstCall().rejects({statusCode: 428, body: {message: 'authorization_pending'}}); + + webex.request + .onFirstCall() + .rejects({statusCode: 428, body: {message: 'authorization_pending'}}); webex.request.onSecondCall().resolves({statusCode: 200, body: {access_token: 'token'}}); sinon.spy(webex.authorization, 'cancelQRCodePolling'); const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit'); - + webex.authorization._startQRCodePolling(options); - clock.tick(4000); - await clock.runAllAsync() - + await clock.tickAsync(4000); + //await clock.runAllAsync() + assert.calledTwice(webex.request); assert.calledOnce(webex.authorization.cancelQRCodePolling); - assert.calledTwice(emitSpy); - assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationSuccess'); - assert.equal(emitSpy.getCall(1).args[1].eventType, 'pollingCanceled'); + assert.calledThrice(emitSpy); + assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationPending'); + assert.equal(emitSpy.getCall(1).args[1].eventType, 'authorizationSuccess'); + assert.equal(emitSpy.getCall(2).args[1].eventType, 'pollingCanceled'); clock.restore(); }); @@ -619,23 +625,21 @@ describe('plugin-authorization-browser-first-party', () => { const options = { device_code: 'test-device-code', interval: 5, - expires_in: 10 + expires_in: 9, }; - + webex.request.rejects({statusCode: 428, body: {message: 'authorizationPending'}}); sinon.spy(webex.authorization, 'cancelQRCodePolling'); const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit'); webex.authorization._startQRCodePolling(options); - clock.tick(10000); - await clock.runAllAsync() - - assert.calledTwice(webex.request); + await clock.tickAsync(10_000); + + assert.calledOnce(webex.request); assert.calledOnce(webex.authorization.cancelQRCodePolling); - assert.calledThrice(emitSpy); - assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationPending'); + assert.calledTwice(emitSpy); + assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationPending'); assert.equal(emitSpy.getCall(1).args[1].eventType, 'authorizationFailure'); - assert.equal(emitSpy.getCall(2).args[1].eventType, 'pollingCanceled'); clock.restore(); }); @@ -644,54 +648,129 @@ describe('plugin-authorization-browser-first-party', () => { const options = { device_code: 'test-device-code', interval: 2, - expires_in: 300 + expires_in: 300, }; - - webex.authorization.pollingRequest = 1; + + webex.authorization.pollingTimer = 1; const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit'); webex.authorization._startQRCodePolling(options); assert.calledOnce(emitSpy); - assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationFailure'); - webex.authorization.pollingRequest = null; + assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationFailure'); + webex.authorization.pollingTimer = null; + }); + + it('should skip a interval when server ask for slow_down', async () => { + const clock = sinon.useFakeTimers(); + const webex = makeWebex('http://example.com'); + const options = { + device_code: 'test-device-code', + interval: 2, + expires_in: 300, + }; + + webex.request.onFirstCall().rejects({statusCode: 400, body: {message: 'slow_down'}}); + webex.request.onSecondCall().resolves({statusCode: 200, body: {access_token: 'token'}}); + sinon.spy(webex.authorization, 'cancelQRCodePolling'); + const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit'); + + webex.authorization._startQRCodePolling(options); + await clock.tickAsync(4000); + + // Request only once because of slow_down + assert.calledOnce(webex.request); + + // Wait for next interval + await clock.tickAsync(2000); + + assert.calledTwice(webex.request); + assert.calledOnce(webex.authorization.cancelQRCodePolling); + assert.calledTwice(emitSpy); + assert.equal(emitSpy.getCall(0).args[1].eventType, 'authorizationSuccess'); + assert.equal(emitSpy.getCall(1).args[1].eventType, 'pollingCanceled'); + clock.restore(); + }); + + it('should ignore the response from the previous polling', async () => { + const clock = sinon.useFakeTimers(); + const webex = makeWebex('http://example.com'); + const options = { + device_code: 'test-device-code', + interval: 2, + expires_in: 300, + }; + + webex.request.onFirstCall().callsFake(() => { + return new Promise((resolve) => { + setTimeout(() => { + resolve({statusCode: 200, body: {access_token: 'token'}}); + }, 1000); + }); + }); + + webex.request + .onSecondCall() + .rejects({statusCode: 428, body: {message: 'authorizationPending'}}); + sinon.spy(webex.authorization, 'cancelQRCodePolling'); + const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit'); + + webex.authorization._startQRCodePolling(options); + await clock.tickAsync(2500); + + webex.authorization.cancelQRCodePolling(); + + // Start new polling + + webex.authorization._startQRCodePolling(options); + + // Wait for next interval + await clock.tickAsync(3000); + + assert.calledTwice(webex.request); + assert.calledOnce(webex.authorization.cancelQRCodePolling); + assert.calledTwice(emitSpy); + // authorizationSuccess event should not be emitted + assert.equal(emitSpy.getCall(0).args[1].eventType, 'pollingCanceled'); + assert.equal(emitSpy.getCall(1).args[1].eventType, 'authorizationPending'); + clock.restore(); }); }); describe('#cancelQRCodePolling()', () => { it('should stop polling after cancellation', async () => { - const clock = sinon.useFakeTimers(); - const webex = makeWebex('http://example.com'); - const options = { - device_code: 'test-device-code', - interval: 2, - expires_in: 300 - }; - - webex.request.rejects({statusCode: 428, body: {message: 'authorizationPending'}}); - const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit'); - - webex.authorization._startQRCodePolling(options); - // First poll - clock.tick(2000); - assert.calledOnce(webex.request); - - webex.authorization.cancelQRCodePolling(); - // Wait for next interval - clock.tick(2000); - - const eventArgs = emitSpy.getCall(0).args; - - // Verify no additional requests were made - assert.calledOnce(webex.request); - assert.calledOnce(emitSpy); - assert.equal(eventArgs[1].eventType, 'pollingCanceled'); - clock.restore(); - }); + const clock = sinon.useFakeTimers(); + const webex = makeWebex('http://example.com'); + const options = { + device_code: 'test-device-code', + interval: 2, + expires_in: 300, + }; + + webex.request.rejects({statusCode: 428, body: {message: 'authorizationPending'}}); + const emitSpy = sinon.spy(webex.authorization.eventEmitter, 'emit'); + + webex.authorization._startQRCodePolling(options); + // First poll + clock.tick(2000); + assert.calledOnce(webex.request); + + webex.authorization.cancelQRCodePolling(); + // Wait for next interval + clock.tick(2000); + + const eventArgs = emitSpy.getCall(0).args; + + // Verify no additional requests were made + assert.calledOnce(webex.request); + assert.calledOnce(emitSpy); + assert.equal(eventArgs[1].eventType, 'pollingCanceled'); + clock.restore(); + }); it('should clear interval and reset polling request', () => { const clock = sinon.useFakeTimers(); const webex = makeWebex('http://example.com'); - + const options = { device_code: 'test_device_code', interval: 2, @@ -699,22 +778,21 @@ describe('plugin-authorization-browser-first-party', () => { }; webex.authorization._startQRCodePolling(options); - assert.isDefined(webex.authorization.pollingRequest); - + assert.isDefined(webex.authorization.pollingTimer); + webex.authorization.cancelQRCodePolling(); - assert.isNull(webex.authorization.pollingRequest); - + assert.isNull(webex.authorization.pollingTimer); + clock.restore(); }); - + it('should handle cancellation when no polling is in progress', () => { const webex = makeWebex('http://example.com'); - assert.isNull(webex.authorization.pollingRequest); - + assert.isNull(webex.authorization.pollingTimer); + webex.authorization.cancelQRCodePolling(); - assert.isNull(webex.authorization.pollingRequest); + assert.isNull(webex.authorization.pollingTimer); }); - }); describe('#_generateCodeChallenge', () => { @@ -836,7 +914,7 @@ describe('plugin-authorization-browser-first-party', () => { const orgId = webex.authorization._extractOrgIdFromCode(code); assert.isUndefined(orgId); - }) + }); }); }); }); From 53045fd6bb4d915d4a4b5b6cb009728a60e9ddbf Mon Sep 17 00:00:00 2001 From: Laszlo Vadasz Date: Wed, 27 Nov 2024 13:48:37 +0000 Subject: [PATCH 2/3] fix: too fast polling - Removed unused import --- .../src/authorization.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js b/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js index e1e5532e919..9d37846e34a 100644 --- a/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js +++ b/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js @@ -14,7 +14,6 @@ import {cloneDeep, isEmpty, omit} from 'lodash'; import uuid from 'uuid'; import base64url from 'crypto-js/enc-base64url'; import CryptoJS from 'crypto-js'; -import {interfaceExtends} from '@babel/types'; // Necessary to require lodash this way in order to stub // methods in the unit test From 6b0a33ec989d6848862556d4db8c0010b50745cc Mon Sep 17 00:00:00 2001 From: Laszlo Vadasz Date: Fri, 29 Nov 2024 15:28:54 +0000 Subject: [PATCH 3/3] fix: too fast polling - added exported Events object for better consumer side API --- .../src/authorization.js | 30 ++++++++++++------- .../src/index.js | 2 +- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js b/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js index 9d37846e34a..381b63c037d 100644 --- a/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js +++ b/packages/@webex/plugin-authorization-browser-first-party/src/authorization.js @@ -22,6 +22,16 @@ const lodash = require('lodash'); const OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token'; const OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier'; +/** + * Authorization plugin events + */ +export const Events = { + /** + * QR code login events + */ + qRCodeLogin: 'qRCodeLogin', +}; + /** * Browser support for OAuth2. Automatically parses the URL query for an * authorization code @@ -293,7 +303,7 @@ const Authorization = WebexPlugin.extend({ */ initQRCodeLogin() { if (this.pollingTimer) { - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'getUserCodeFailure', data: {message: 'There is already a polling request'}, }); @@ -317,7 +327,7 @@ const Authorization = WebexPlugin.extend({ }) .then((res) => { const {user_code, verification_uri, verification_uri_complete} = res.body; - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'getUserCodeSuccess', userData: { userCode: user_code, @@ -329,7 +339,7 @@ const Authorization = WebexPlugin.extend({ this._startQRCodePolling(res.body); }) .catch((res) => { - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'getUserCodeFailure', data: res.body, }); @@ -345,7 +355,7 @@ const Authorization = WebexPlugin.extend({ */ _startQRCodePolling(options = {}) { if (!options.device_code) { - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'authorizationFailure', data: {message: 'A deviceCode is required'}, }); @@ -353,7 +363,7 @@ const Authorization = WebexPlugin.extend({ } if (this.pollingTimer) { - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'authorizationFailure', data: {message: 'There is already a polling request'}, }); @@ -365,7 +375,7 @@ const Authorization = WebexPlugin.extend({ this.pollingExpirationTimer = setTimeout(() => { this.cancelQRCodePolling(false); - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'authorizationFailure', data: {message: 'Authorization timed out'}, }); @@ -395,7 +405,7 @@ const Authorization = WebexPlugin.extend({ // if the pollingId has changed, it means that the polling request has been canceled if (this.currentPollingId !== this.pollingId) return; - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'authorizationSuccess', data: res.body, }); @@ -415,7 +425,7 @@ const Authorization = WebexPlugin.extend({ // if the statusCode is 428 which means that the authorization request is still pending // as the end user hasn't yet completed the user-interaction steps. So keep polling. if (res.statusCode === 428) { - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'authorizationPending', data: res.body, }); @@ -425,7 +435,7 @@ const Authorization = WebexPlugin.extend({ this.cancelQRCodePolling(); - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'authorizationFailure', data: res.body, }); @@ -446,7 +456,7 @@ const Authorization = WebexPlugin.extend({ */ cancelQRCodePolling(withCancelEvent = true) { if (this.pollingTimer && withCancelEvent) { - this.eventEmitter.emit('qRCodeLogin', { + this.eventEmitter.emit(Events.qRCodeLogin, { eventType: 'pollingCanceled', }); } diff --git a/packages/@webex/plugin-authorization-browser-first-party/src/index.js b/packages/@webex/plugin-authorization-browser-first-party/src/index.js index 02ca4c2bbe8..4870be6d0ed 100644 --- a/packages/@webex/plugin-authorization-browser-first-party/src/index.js +++ b/packages/@webex/plugin-authorization-browser-first-party/src/index.js @@ -14,5 +14,5 @@ registerPlugin('authorization', Authorization, { proxies, }); -export {default} from './authorization'; +export {default, Events} from './authorization'; export {default as config} from './config';