diff --git a/src/MeetingsSDKAdapter.js b/src/MeetingsSDKAdapter.js index 0b859bd1..9f84f916 100644 --- a/src/MeetingsSDKAdapter.js +++ b/src/MeetingsSDKAdapter.js @@ -562,6 +562,7 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter { remoteAudio: null, remoteVideo: null, remoteShare: null, + requiredCaptcha: {}, showRoster: null, settings: { visible: false, @@ -634,7 +635,26 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter { const sdkMeeting = this.fetchMeeting(ID); if (sdkMeeting.passwordStatus === 'REQUIRED') { - await sdkMeeting.verifyPassword(options.hostKey || options.password); + const res = await sdkMeeting + .verifyPassword(options.hostKey || options.password, options.captcha); + + if (!res.isPasswordValid) { + this.updateMeeting(ID, () => ( + { + failureReason: res.failureReason, + invalidPassword: true, + ...(res.requiredCaptcha && { + requiredCaptcha: { + captchaId: res.requiredCaptcha.captchaId, + refreshURL: res.requiredCaptcha.refreshURL, + verificationAudioURL: res.requiredCaptcha.verificationAudioURL, + verificationImageURL: res.requiredCaptcha.verificationImageURL, + }, + }), + })); + } else { + logger.info('MEETING', ID, 'joinMeeting()', 'Password successfully verified'); + } } sdkMeeting.meetingFiniteStateMachine.reset(); logger.debug('MEETING', ID, 'joinMeeting()', ['calling sdkMeeting.join() with', {pin: options.password, moderator: false, name: options.name}]); @@ -1353,6 +1373,7 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter { deepMerge(meeting, updates); logger.debug('MEETING', ID, 'updateMeeting()', ['meeting updated with', EVENT_MEETING_UPDATED, 'event', 'meeting object', {meeting}]); + sdkMeeting.emit(EVENT_MEETING_UPDATED, meeting); } @@ -1406,4 +1427,22 @@ export default class MeetingsSDKAdapter extends MeetingsAdapter { async clearInvalidHostKeyFlag(ID) { await this.updateMeeting(ID, async () => ({invalidHostKey: false})); } + + /** + * Refreshes the captcha code. + * + * @async + * @param {string} ID Id of the meeting + */ + async refreshCaptcha(ID) { + logger.debug('MEETING', ID, 'refreshCaptcha()', ['called with', {ID}]); + const sdkMeeting = this.fetchMeeting(ID); + + sdkMeeting.refreshCaptcha(); + await this.updateMeeting(ID, () => ( + { + requiredCaptcha: sdkMeeting.requiredCaptcha, + } + )); + } } diff --git a/src/MeetingsSDKAdapter.test.js b/src/MeetingsSDKAdapter.test.js index 7367eaf6..a1f69c91 100644 --- a/src/MeetingsSDKAdapter.test.js +++ b/src/MeetingsSDKAdapter.test.js @@ -1087,4 +1087,114 @@ describe('Meetings SDK Adapter', () => { expect(mockSDKMeeting.emit.mock.calls[0][1]).toMatchObject({invalidHostKey: false}); }); }); + + describe('refreshCaptcha()', () => { + const mockCaptcha = { + captchaId: 'Captcha_c57280d4-6baf-4a27-87e9-290757754bb6', + verificationImageURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_c57280d4-6baf-4a27-87e9-290757754bb6/verificationCodeImage', + refreshURL: 'https://cisco.webex.com/captchaservice/v1/captchas/refresh?siteurl=cisco&captchaID=Captcha_c57280d4-6baf-4a27-87e9-290757754bb6', + verificationAudioURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_c57280d4-6baf-4a27-87e9-290757754bb6/verificationCodeAudio', + }; + + it('refreshes the captcha', async () => { + meetingsSDKAdapter.meetings[meetingID].requiredCaptcha = { + captchaId: 'Captcha_b40798d4-6baf-4a27-bf77-2907577524d6', + verificationImageURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_b40798d4-6baf-4a27-bf77-2907577524d6/verificationCodeImage', + refreshURL: 'https://cisco.webex.com/captchaservice/v1/captchas/refresh?siteurl=cisco&captchaID=Captcha_b40798d4-6baf-4a27-bf77-2907577524d6', + verificationAudioURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_b40798d4-6baf-4a27-bf77-2907577524d6/verificationCodeAudio', + }; + + mockSDKMeeting.requiredCaptcha = mockCaptcha; + + await meetingsSDKAdapter.refreshCaptcha(meetingID); + + expect(mockSDKMeeting.refreshCaptcha).toHaveBeenCalledWith(); + expect(mockSDKMeeting.emit).toHaveBeenCalledTimes(1); + expect(mockSDKMeeting.emit.mock.calls[0][0]).toBe('adapter:meeting:updated'); + expect(mockSDKMeeting.emit.mock.calls[0][1]).toMatchObject({requiredCaptcha: mockCaptcha}); + }); + }); + + describe('joinMeeting()', () => { + const verifyPasswordRes1 = { + failureReason: 'WRONG_PASSWORD', + isPasswordValid: false, + requiredCaptcha: { + captchaId: 'Captcha_c3c3009f-c844-4305-b1c4-6aed2a251247', + refreshURL: 'https://cisco.webex.com/captchaservice/v1/captchas/refresh?siteurl=cisco&captchaID=Captcha_c3c3009f-c844-4305-b1c4-6aed2a251247', + verificationAudioURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_c3c3009f-c844-4305-b1c4-6aed2a251247/verificationCodeAudio', + verificationImageURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_c3c3009f-c844-4305-b1c4-6aed2a251247/verificationCodeImage', + }, + }; + + const mockResponse1 = { + failureReason: 'WRONG_PASSWORD', + invalidPassword: true, + requiredCaptcha: { + captchaId: 'Captcha_c3c3009f-c844-4305-b1c4-6aed2a251247', + refreshURL: 'https://cisco.webex.com/captchaservice/v1/captchas/refresh?siteurl=cisco&captchaID=Captcha_c3c3009f-c844-4305-b1c4-6aed2a251247', + verificationAudioURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_c3c3009f-c844-4305-b1c4-6aed2a251247/verificationCodeAudio', + verificationImageURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_c3c3009f-c844-4305-b1c4-6aed2a251247/verificationCodeImage', + }, + }; + + const verifyPasswordRes2 = { + failureReason: 'WRONG_CAPTCHA', + isPasswordValid: false, + requiredCaptcha: { + captchaId: 'Captcha_e34a56d8-c75e-4424-9a34-b1dd28badd68', + refreshURL: 'https://cisco.webex.com/captchaservice/v1/captchas/refresh?siteurl=cisco&captchaID=Captcha_e34a56d8-c75e-4424-9a34-b1dd28badd68', + verificationAudioURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_e34a56d8-c75e-4424-9a34-b1dd28badd68/verificationCodeAudio', + verificationImageURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_e34a56d8-c75e-4424-9a34-b1dd28badd68/verificationCodeImage', + }, + }; + + const mockResponse2 = { + failureReason: 'WRONG_CAPTCHA', + invalidPassword: true, + requiredCaptcha: { + captchaId: 'Captcha_e34a56d8-c75e-4424-9a34-b1dd28badd68', + refreshURL: 'https://cisco.webex.com/captchaservice/v1/captchas/refresh?siteurl=cisco&captchaID=Captcha_e34a56d8-c75e-4424-9a34-b1dd28badd68', + verificationAudioURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_e34a56d8-c75e-4424-9a34-b1dd28badd68/verificationCodeAudio', + verificationImageURL: 'https://cisco.webex.com/captchaservice/v1/captchas/Captcha_e34a56d8-c75e-4424-9a34-b1dd28badd68/verificationCodeImage', + }, + }; + + it('joinMeeting with correct password with no captcha needed', async () => { + mockSDKMeeting.verifyPassword = jest.fn(() => Promise.resolve({ + failureReason: 'NONE', + isPasswordValid: true, + requiredCaptcha: null, + })); + await meetingsSDKAdapter.joinMeeting(meetingID, {password: 'BJzbHDKd347'}); + + expect(mockSDKMeeting.verifyPassword).toHaveBeenCalledWith('BJzbHDKd347', undefined); + expect(mockSDKMeeting.emit).not.toHaveBeenCalled(); + expect(mockSDKMeeting.join).toHaveBeenCalledTimes(1); + }); + + it('joinMeeting with incorrect password multiple times and receiving captcha code to be entered', async () => { + mockSDKMeeting.verifyPassword = jest.fn(() => Promise.resolve(verifyPasswordRes1)); + mockSDKMeeting.requiredCaptcha = verifyPasswordRes1.requiredCaptcha; + + await meetingsSDKAdapter.joinMeeting(meetingID, {password: 'fahdkwlr'}); + + expect(mockSDKMeeting.verifyPassword).toHaveBeenCalledWith('fahdkwlr', undefined); + expect(mockSDKMeeting.emit).toHaveBeenCalledTimes(1); + expect(mockSDKMeeting.emit.mock.calls[0][0]).toBe('adapter:meeting:updated'); + expect(mockSDKMeeting.emit.mock.calls[0][1]).toMatchObject(mockResponse1); + }); + + it('joinMeeting with correct password and incorrect captcha', async () => { + mockSDKMeeting.verifyPassword = jest.fn(() => Promise.resolve(verifyPasswordRes2)); + mockSDKMeeting.requiredCaptcha = verifyPasswordRes2.requiredCaptcha; + + await meetingsSDKAdapter.joinMeeting(meetingID, {password: 'BJzbHDKd347', captcha: 'pfucip'}); + + expect(mockSDKMeeting.verifyPassword).toHaveBeenCalledWith('BJzbHDKd347', 'pfucip'); + expect(mockSDKMeeting.emit).toHaveBeenCalledTimes(1); + expect(mockSDKMeeting.emit.mock.calls[0][0]).toBe('adapter:meeting:updated'); + expect(mockSDKMeeting.emit.mock.calls[0][1]).toMatchObject(mockResponse2); + }); + }); }); diff --git a/src/MeetingsSDKAdapter/testHelper.js b/src/MeetingsSDKAdapter/testHelper.js index cb5c7f0a..8d45d4e3 100644 --- a/src/MeetingsSDKAdapter/testHelper.js +++ b/src/MeetingsSDKAdapter/testHelper.js @@ -25,6 +25,7 @@ export function createTestMeetingsSDKAdapter() { remoteAudio: null, remoteVideo: null, remoteShare: null, + requiredCaptcha: {}, showRoster: null, settings: { visible: false, diff --git a/src/mockSdk.js b/src/mockSdk.js index 639fd4b2..8a4c7033 100644 --- a/src/mockSdk.js +++ b/src/mockSdk.js @@ -83,6 +83,9 @@ export const createMockSDKMeeting = () => ({ meetingInfo: { isWebexScheduled: true, }, + meetingFiniteStateMachine: { + reset: jest.fn(), + }, members: { membersCollection: { members: { @@ -174,6 +177,7 @@ export const createMockSDKMeeting = () => ({ canUpdateMedia: jest.fn(() => true), updateShare: jest.fn(() => Promise.resolve()), changeVideoLayout: jest.fn(() => Promise.resolve()), + refreshCaptcha: jest.fn(), }); export const mockSDKMembership = {