From bc5691a7fc51c95f310e34995b934d01e7a34fd2 Mon Sep 17 00:00:00 2001 From: "Kevin J. Sung" Date: Tue, 9 Mar 2021 16:14:18 -0500 Subject: [PATCH] load CAPTCHA to enable phone number verification --- _locales/en/messages.json | 4 ++ config/default.json | 2 + js/views/standalone_registration_view.js | 42 ++++++++++++++++++-- main.js | 49 ++++++++++++++++++++++++ preload.js | 10 +++++ ts/textsecure/AccountManager.ts | 8 ++-- ts/textsecure/WebAPI.ts | 32 +++++++++++++--- ts/window.d.ts | 1 + 8 files changed, 134 insertions(+), 14 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 03007fa355c..9869f055771 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -715,6 +715,10 @@ "message": "Sharing screen", "description": "Title for screen sharing window" }, + "captchaResponseRequired": { + "message": "CAPTCHA Response Required", + "description": "Title of the window that pops up when a CAPTCHA response is required" + }, "speech": { "message": "Speech", "description": "Item under the Edit menu, with 'start/stop speaking' items below it" diff --git a/config/default.json b/config/default.json index d87e2e7babf..cc59cb56866 100644 --- a/config/default.json +++ b/config/default.json @@ -8,6 +8,8 @@ "0": "https://cdn-staging.signal.org", "2": "https://cdn2-staging.signal.org" }, + "captchaUrl": "https://signalcaptchas.org/registration/generate.html", + "captchaScheme": "signalcaptcha://", "contentProxyUrl": "http://contentproxy.signal.org:443", "updatesUrl": "https://updates2.signal.org/desktop", "updatesPublicKey": "05fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401", diff --git a/js/views/standalone_registration_view.js b/js/views/standalone_registration_view.js index d5dfe9251fb..47d595c3ef5 100644 --- a/js/views/standalone_registration_view.js +++ b/js/views/standalone_registration_view.js @@ -17,6 +17,9 @@ this.accountManager = getAccountManager(); + this.pendingVoiceVerification = false; + this.pendingSMSVerification = false; + this.render(); const number = textsecure.storage.user.getNumber(); @@ -77,30 +80,61 @@ } }, requestVoice() { + this.pendingVoiceVerification = true; + this.pendingSMSVerification = false; window.removeSetupMenuItems(); this.$('#error').hide(); const number = this.phoneView.validateNumber(); + const token = window.textsecure.storage.get('captchaToken'); if (number) { this.accountManager - .requestVoiceVerification(number) - .catch(this.displayError.bind(this)); + .requestVoiceVerification(number, token) + .then(() => { + this.pendingVoiceVerification = false; + }) + .catch(e => { + if (e.code === 402) { + window.captchaRequired(); + } else { + this.displayError(e); + } + }); this.$('#step2').addClass('in').fadeIn(); } else { this.$('#number-container').addClass('invalid'); } }, requestSMSVerification() { + this.pendingSMSVerification = true; + this.pendingVoiceVerification = false; window.removeSetupMenuItems(); $('#error').hide(); const number = this.phoneView.validateNumber(); + const token = window.textsecure.storage.get('captchaToken'); if (number) { this.accountManager - .requestSMSVerification(number) - .catch(this.displayError.bind(this)); + .requestSMSVerification(number, token) + .then(() => { + this.pendingSMSVerification = false; + }) + .catch(e => { + if (e.code === 402) { + window.captchaRequired(); + } else { + this.displayError(e); + } + }); this.$('#step2').addClass('in').fadeIn(); } else { this.$('#number-container').addClass('invalid'); } }, + requestPendingVerification() { + if (this.pendingVoiceVerification) { + this.requestVoice(); + } else if (this.pendingSMSVerification) { + this.requestSMSVerification(); + } + }, }); })(); diff --git a/main.js b/main.js index c56ff42d33b..7175768b761 100644 --- a/main.js +++ b/main.js @@ -294,6 +294,14 @@ async function handleUrl(event, target) { return; } + if (target.startsWith(config.get('captchaScheme'))) { + if (captchaWindow) { + captchaWindow.close(); + } + const token = target.slice(config.get('captchaScheme').length); + mainWindow.webContents.send('captcha-response', token); + } + if ((protocol === 'http:' || protocol === 'https:') && !isDevServer) { try { await shell.openExternal(target); @@ -1113,6 +1121,47 @@ function showPermissionsPopupWindow(forCalling, forCamera) { }); } +let captchaWindow; +function showCaptchaWindow() { + if (captchaWindow) { + captchaWindow.show(); + return; + } + + const options = { + resizable: false, + title: locale.messages.captchaResponseRequired.message, + autoHideMenuBar: true, + show: false, + webPreferences: { + ...defaultWebPrefs, + nodeIntegration: false, + nodeIntegrationInWorker: false, + contextIsolation: false, + nativeWindowOpen: true, + }, + }; + + captchaWindow = new BrowserWindow(options); + + handleCommonWindowEvents(captchaWindow); + + captchaWindow.loadURL(prepareUrl(new URL(config.get('captchaUrl')))); + + captchaWindow.on('closed', () => { + captchaWindow = null; + }); + + captchaWindow.once('ready-to-show', () => { + captchaWindow.show(); + }); +} + +// IPC call to load CAPTCHA +ipc.on('captcha-required', () => { + showCaptchaWindow(); +}); + async function initializeSQL() { const userDataPath = await getRealPath(app.getPath('userData')); diff --git a/preload.js b/preload.js index cf631655ce2..8445abd9db3 100644 --- a/preload.js +++ b/preload.js @@ -124,6 +124,11 @@ try { // eslint-disable-next-line no-eval, no-multi-assign window.eval = global.eval = () => null; + window.captchaRequired = () => { + window.log.info('CAPTCHA required'); + ipc.send('captcha-required'); + }; + window.drawAttention = () => { window.log.info('draw attention'); ipc.send('draw-attention'); @@ -200,6 +205,11 @@ try { }); }); + ipc.on('captcha-response', (_event, token) => { + window.textsecure.storage.put('captchaToken', token); + window.owsDesktopApp.appView.standaloneView.requestPendingVerification(); + }); + ipc.on('set-up-as-new-device', () => { Whisper.events.trigger('setupAsNewDevice'); }); diff --git a/ts/textsecure/AccountManager.ts b/ts/textsecure/AccountManager.ts index 021d86d39b7..64a845fd1bf 100644 --- a/ts/textsecure/AccountManager.ts +++ b/ts/textsecure/AccountManager.ts @@ -81,12 +81,12 @@ export default class AccountManager extends EventTarget { this.pending = Promise.resolve(); } - async requestVoiceVerification(number: string) { - return this.server.requestVerificationVoice(number); + async requestVoiceVerification(number: string, captchaToken?: string) { + return this.server.requestVerificationVoice(number, captchaToken); } - async requestSMSVerification(number: string) { - return this.server.requestVerificationSMS(number); + async requestSMSVerification(number: string, captchaToken?: string) { + return this.server.requestVerificationSMS(number, captchaToken); } async encryptDeviceName(name: string, providedIdentityKey?: KeyPairType) { diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 60cea8be6cb..b69c2f5eeb7 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -924,8 +924,14 @@ export type WebAPIType = { registerKeys: (genKeys: KeysType) => Promise; registerSupportForUnauthenticatedDelivery: () => Promise; reportMessage: (senderE164: string, serverGuid: string) => Promise; - requestVerificationSMS: (number: string) => Promise; - requestVerificationVoice: (number: string) => Promise; + requestVerificationSMS: ( + number: string, + captchaToken?: string + ) => Promise; + requestVerificationVoice: ( + number: string, + captchaToken?: string + ) => Promise; sendMessages: ( destination: string, messageArray: Array, @@ -1476,19 +1482,33 @@ export function initialize({ }); } - async function requestVerificationSMS(number: string) { + async function requestVerificationSMS( + number: string, + captchaToken?: string + ) { + let urlParameters = `/sms/code/${number}?client=desktop`; + if (captchaToken) { + urlParameters += `&captcha=${captchaToken}`; + } return _ajax({ call: 'accounts', httpType: 'GET', - urlParameters: `/sms/code/${number}`, + urlParameters, }); } - async function requestVerificationVoice(number: string) { + async function requestVerificationVoice( + number: string, + captchaToken?: string + ) { + let urlParameters = `/voice/code/${number}?client=desktop`; + if (captchaToken) { + urlParameters += `&captcha=${captchaToken}`; + } return _ajax({ call: 'accounts', httpType: 'GET', - urlParameters: `/voice/code/${number}`, + urlParameters, }); } diff --git a/ts/window.d.ts b/ts/window.d.ts index e3b14e76ab8..4dc058ea3e7 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -200,6 +200,7 @@ declare global { baseAttachmentsPath: string; baseStickersPath: string; baseTempPath: string; + captchaRequired: () => void; enterKeyboardMode: () => void; enterMouseMode: () => void; getAccountManager: () => AccountManager;