MOBILE-3691 recaptcha: Fix recaptcha in Ionic 5
parent
972e1e2a81
commit
e59d180971
|
@ -1,42 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
(function () {
|
||||
var url = location.href;
|
||||
|
||||
if (!url.match(/^https?:\/\//i) || !url.match(/\/webservice\/recaptcha\.php/i)) {
|
||||
// Not the recaptcha script, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
// Define recaptcha callbacks.
|
||||
window.recaptchacallback = function(value) {
|
||||
window.parent.postMessage({
|
||||
environment: 'moodleapp',
|
||||
context: 'recaptcha',
|
||||
action: 'callback',
|
||||
frameUrl: location.href,
|
||||
value: value,
|
||||
}, '*');
|
||||
};
|
||||
|
||||
window.recaptchaexpiredcallback = function() {
|
||||
window.parent.postMessage({
|
||||
environment: 'moodleapp',
|
||||
context: 'recaptcha',
|
||||
action: 'expired',
|
||||
frameUrl: location.href,
|
||||
}, '*');
|
||||
};
|
||||
})();
|
|
@ -44,7 +44,6 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
|
|||
import { CoreNavigationBarComponent } from './navigation-bar/navigation-bar';
|
||||
import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
||||
import { CoreRecaptchaComponent } from './recaptcha/recaptcha';
|
||||
import { CoreRecaptchaModalComponent } from './recaptcha/recaptcha-modal';
|
||||
import { CoreSendMessageFormComponent } from './send-message-form/send-message-form';
|
||||
import { CoreShowPasswordComponent } from './show-password/show-password';
|
||||
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||
|
@ -84,7 +83,6 @@ import { CoreHorizontalScrollControlsComponent } from './horizontal-scroll-contr
|
|||
CoreNavigationBarComponent,
|
||||
CoreProgressBarComponent,
|
||||
CoreRecaptchaComponent,
|
||||
CoreRecaptchaModalComponent,
|
||||
CoreSendMessageFormComponent,
|
||||
CoreShowPasswordComponent,
|
||||
CoreSitePickerComponent,
|
||||
|
@ -131,7 +129,6 @@ import { CoreHorizontalScrollControlsComponent } from './horizontal-scroll-contr
|
|||
CoreNavigationBarComponent,
|
||||
CoreProgressBarComponent,
|
||||
CoreRecaptchaComponent,
|
||||
CoreRecaptchaModalComponent,
|
||||
CoreSendMessageFormComponent,
|
||||
CoreShowPasswordComponent,
|
||||
CoreSitePickerComponent,
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>{{ 'core.login.security_question' | translate }}</ion-title>
|
||||
|
||||
<ion-buttons slot="end">
|
||||
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||
<ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-iframe [src]="recaptchaUrl" (loaded)="loaded($event)"></core-iframe>
|
||||
</ion-content>
|
|
@ -1,126 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, OnDestroy } from '@angular/core';
|
||||
|
||||
import { ModalController } from '@singletons';
|
||||
|
||||
/**
|
||||
* Component to display a the recaptcha in a modal.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-recaptcha-modal',
|
||||
templateUrl: 'core-recaptcha-modal.html',
|
||||
})
|
||||
export class CoreRecaptchaModalComponent implements OnDestroy {
|
||||
|
||||
@Input() recaptchaUrl?: string;
|
||||
|
||||
expired = false;
|
||||
value = '';
|
||||
|
||||
protected messageListenerFunction: (event: MessageEvent) => Promise<void>;
|
||||
|
||||
constructor() {
|
||||
// Listen for messages from the iframe.
|
||||
this.messageListenerFunction = this.onIframeMessage.bind(this);
|
||||
window.addEventListener('message', this.messageListenerFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close modal.
|
||||
*/
|
||||
closeModal(): void {
|
||||
ModalController.dismiss(<CoreRecaptchaModalReturn>{
|
||||
expired: this.expired,
|
||||
value: this.value,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The iframe with the recaptcha was loaded.
|
||||
*
|
||||
* @param iframe Iframe element.
|
||||
*/
|
||||
loaded(iframe: HTMLIFrameElement): void {
|
||||
// Search the iframe content.
|
||||
const contentWindow = iframe?.contentWindow;
|
||||
|
||||
if (contentWindow) {
|
||||
try {
|
||||
// Set the callbacks we're interested in.
|
||||
contentWindow['recaptchacallback'] = this.onRecaptchaCallback.bind(this);
|
||||
contentWindow['recaptchaexpiredcallback'] = this.onRecaptchaExpiredCallback.bind(this);
|
||||
} catch (error) {
|
||||
// Cannot access the window.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat an iframe message event.
|
||||
*
|
||||
* @param event Event.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async onIframeMessage(event: MessageEvent): Promise<void> {
|
||||
if (!event.data || event.data.environment != 'moodleapp' || event.data.context != 'recaptcha') {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data.action) {
|
||||
case 'callback':
|
||||
this.onRecaptchaCallback(event.data.value);
|
||||
break;
|
||||
case 'expired':
|
||||
this.onRecaptchaExpiredCallback();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recapcha callback called.
|
||||
*
|
||||
* @param value Value received.
|
||||
*/
|
||||
protected onRecaptchaCallback(value: string): void {
|
||||
this.expired = false;
|
||||
this.value = value;
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recapcha expired callback called.
|
||||
*/
|
||||
protected onRecaptchaExpiredCallback(): void {
|
||||
this.expired = true;
|
||||
this.value = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
window.removeEventListener('message', this.messageListenerFunction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type CoreRecaptchaModalReturn = {
|
||||
expired: boolean;
|
||||
value: string;
|
||||
};
|
|
@ -16,9 +16,8 @@ import { Component, Input, OnInit } from '@angular/core';
|
|||
|
||||
import { CoreLang } from '@services/lang';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreRecaptchaModalComponent, CoreRecaptchaModalReturn } from './recaptcha-modal';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Component that allows answering a recaptcha.
|
||||
|
@ -57,27 +56,49 @@ export class CoreRecaptchaComponent implements OnInit {
|
|||
}
|
||||
|
||||
/**
|
||||
* Open the recaptcha modal.
|
||||
* Let the user answer the recaptcha.
|
||||
*/
|
||||
async answerRecaptcha(): Promise<void> {
|
||||
// Set the iframe src. We use an iframe because reCaptcha V2 doesn't work with file:// protocol.
|
||||
// Open the recaptcha challenge in an InAppBrowser.
|
||||
// The app used to use an iframe for this, but the app can no longer access the iframe to create the required callbacks.
|
||||
// The app cannot render the recaptcha directly because it has problems with the local protocols and domains.
|
||||
const src = CoreTextUtils.concatenatePaths(this.siteUrl!, 'webservice/recaptcha.php?lang=' + this.lang);
|
||||
|
||||
// Modal to answer the recaptcha.
|
||||
// This is because the size of the recaptcha is dynamic, so it could cause problems if it was displayed inline.
|
||||
const inAppBrowserWindow = CoreUtils.openInApp(src);
|
||||
if (!inAppBrowserWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modalData = await CoreDomUtils.openModal<CoreRecaptchaModalReturn>({
|
||||
component: CoreRecaptchaModalComponent,
|
||||
cssClass: 'core-modal-fullscreen',
|
||||
componentProps: {
|
||||
recaptchaUrl: src,
|
||||
},
|
||||
// Set the callbacks once the page is loaded.
|
||||
const loadStopSubscription = inAppBrowserWindow.on('loadstop').subscribe(() => {
|
||||
inAppBrowserWindow.executeScript({
|
||||
code:
|
||||
'window.recaptchacallback = (value) => webkit.messageHandlers.cordova_iab.postMessage(' +
|
||||
'JSON.stringify({ action: "callback", value }));' +
|
||||
'window.recaptchaexpiredcallback = () => webkit.messageHandlers.cordova_iab.postMessage(' +
|
||||
'JSON.stringify({ action: "expired" }));',
|
||||
});
|
||||
});
|
||||
|
||||
if (modalData) {
|
||||
this.expired = modalData.expired;
|
||||
this.model![this.modelValueName] = modalData.value;
|
||||
// Listen for events.
|
||||
const messageSubscription = inAppBrowserWindow.on('message').subscribe((event) => {
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.action == 'expired') {
|
||||
this.expired = true;
|
||||
this.model![this.modelValueName] = '';
|
||||
} else if (event.data.action == 'callback') {
|
||||
this.expired = false;
|
||||
this.model![this.modelValueName] = event.data.value;
|
||||
|
||||
// Close the InAppBrowser now.
|
||||
inAppBrowserWindow.close();
|
||||
messageSubscription.unsubscribe();
|
||||
loadStopSubscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -516,14 +516,8 @@ export class CoreIframeUtilsProvider {
|
|||
private injectiOSScripts(userScriptWindow: WKUserScriptWindow) {
|
||||
const wwwPath = CoreFile.getWWWAbsolutePath();
|
||||
const linksPath = CoreTextUtils.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js');
|
||||
const recaptchaPath = CoreTextUtils.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js');
|
||||
|
||||
userScriptWindow.WKUserScript?.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath });
|
||||
userScriptWindow.WKUserScript?.addScript({
|
||||
id: 'CoreIframeUtilsRecaptchaScript',
|
||||
file: recaptchaPath,
|
||||
injectionTime: userScriptWindow.WKUserScript?.InjectionTime.END,
|
||||
});
|
||||
|
||||
// Handle post messages received by iframes.
|
||||
window.addEventListener('message', this.handleIframeMessage.bind(this));
|
||||
|
|
Loading…
Reference in New Issue