diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /app/assets/javascripts/captcha | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/captcha')
-rw-r--r-- | app/assets/javascripts/captcha/captcha_modal.vue | 110 | ||||
-rw-r--r-- | app/assets/javascripts/captcha/init_recaptcha_script.js | 48 |
2 files changed, 158 insertions, 0 deletions
diff --git a/app/assets/javascripts/captcha/captcha_modal.vue b/app/assets/javascripts/captcha/captcha_modal.vue new file mode 100644 index 00000000000..e6c73bc9643 --- /dev/null +++ b/app/assets/javascripts/captcha/captcha_modal.vue @@ -0,0 +1,110 @@ +<script> +// NOTE 1: This is similar to recaptcha_modal.vue, but it directly uses the reCAPTCHA Javascript API +// (https://developers.google.com/recaptcha/docs/display#js_api) and gl-modal, rather than relying +// on the form-based ReCAPTCHA HTML being pre-rendered by the backend and using deprecated-modal. + +// NOTE 2: Even though this modal currently only supports reCAPTCHA, we use 'captcha' instead +// of 'recaptcha' throughout the code, so that we can easily add support for future alternative +// captcha implementations other than reCAPTCHA (e.g. FriendlyCaptcha) without having to +// change the references in the code or API. + +import { GlModal } from '@gitlab/ui'; +import { uniqueId } from 'lodash'; +import { initRecaptchaScript } from '~/captcha/init_recaptcha_script'; + +export default { + components: { + GlModal, + }, + props: { + needsCaptchaResponse: { + type: Boolean, + required: false, + default: false, + }, + captchaSiteKey: { + type: String, + required: true, + }, + }, + data() { + return { + modalId: uniqueId('captcha-modal-'), + }; + }, + watch: { + needsCaptchaResponse(newNeedsCaptchaResponse) { + // If this is true, we need to present the captcha modal to the user. + // When the modal is shown we will also initialize and render the form. + if (newNeedsCaptchaResponse) { + this.$refs.modal.show(); + } + }, + }, + methods: { + emitReceivedCaptchaResponse(captchaResponse) { + this.$emit('receivedCaptchaResponse', captchaResponse); + this.$refs.modal.hide(); + }, + emitNullReceivedCaptchaResponse() { + this.emitReceivedCaptchaResponse(null); + }, + /** + * handler for when modal is shown + */ + shown() { + const containerRef = this.$refs.captcha; + + // NOTE: This is the only bit that is specific to Google's reCAPTCHA captcha implementation. + initRecaptchaScript() + .then((grecaptcha) => { + grecaptcha.render(containerRef, { + sitekey: this.captchaSiteKey, + // This callback will emit and let the parent handle the response + callback: this.emitReceivedCaptchaResponse, + // TODO: Also need to handle expired-callback and error-callback + // See https://gitlab.com/gitlab-org/gitlab/-/issues/217722#future-follow-on-issuesmrs + }); + }) + .catch((e) => { + // TODO: flash the error or notify the user some other way + // See https://gitlab.com/gitlab-org/gitlab/-/issues/217722#future-follow-on-issuesmrs + this.emitNullReceivedCaptchaResponse(); + this.$refs.modal.hide(); + + // eslint-disable-next-line no-console + console.error( + '[gitlab] an unexpected exception was caught while initializing captcha', + e, + ); + }); + }, + /** + * handler for when modal is about to hide + */ + hide(bvModalEvent) { + // If hide() was called without any argument, the value of trigger will be null. + // See https://bootstrap-vue.org/docs/components/modal#prevent-closing + if (bvModalEvent.trigger) { + this.emitNullReceivedCaptchaResponse(); + } + }, + }, +}; +</script> +<template> + <!-- Note: The action-cancel button isn't necessary for the functionality of the modal, but --> + <!-- there must be at least one button or focusable element, or the gl-modal fails to render. --> + <!-- We could modify gl-model to remove this requirement. --> + <gl-modal + ref="modal" + :modal-id="modalId" + :title="__('Please solve the captcha')" + :action-cancel="{ text: __('Cancel') }" + @shown="shown" + @hide="hide" + > + <div ref="captcha"></div> + <p>{{ __('We want to be sure it is you, please confirm you are not a robot.') }}</p> + </gl-modal> +</template> diff --git a/app/assets/javascripts/captcha/init_recaptcha_script.js b/app/assets/javascripts/captcha/init_recaptcha_script.js new file mode 100644 index 00000000000..f546eef7d84 --- /dev/null +++ b/app/assets/javascripts/captcha/init_recaptcha_script.js @@ -0,0 +1,48 @@ +// NOTE: This module will be used in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52044 +import { memoize } from 'lodash'; + +export const RECAPTCHA_API_URL_PREFIX = 'https://www.google.com/recaptcha/api.js'; +export const RECAPTCHA_ONLOAD_CALLBACK_NAME = 'recaptchaOnloadCallback'; + +/** + * Adds the Google reCAPTCHA script tag to the head of the document, and + * returns a promise of the grecaptcha object + * (https://developers.google.com/recaptcha/docs/display#js_api). + * + * It is memoized, so there will only be one instance of the script tag ever + * added to the document. + * + * See the reCAPTCHA documentation for more details: + * + * https://developers.google.com/recaptcha/docs/display#explicit_render + * + */ +export const initRecaptchaScript = memoize(() => { + // Appends the the reCAPTCHA script tag to the head of document + const appendRecaptchaScript = () => { + const script = document.createElement('script'); + script.src = `${RECAPTCHA_API_URL_PREFIX}?onload=${RECAPTCHA_ONLOAD_CALLBACK_NAME}&render=explicit`; + script.classList.add('js-recaptcha-script'); + document.head.appendChild(script); + }; + + return new Promise((resolve) => { + // This global callback resolves the Promise and is passed by name to the reCAPTCHA script. + window[RECAPTCHA_ONLOAD_CALLBACK_NAME] = () => { + // Let's clean up after ourselves. This is also important for testing, because `window` is NOT cleared between tests. + // https://github.com/facebook/jest/issues/1224#issuecomment-444586798. + delete window[RECAPTCHA_ONLOAD_CALLBACK_NAME]; + resolve(window.grecaptcha); + }; + appendRecaptchaScript(); + }); +}); + +/** + * Clears the cached memoization of the default manager. + * + * This is needed for determinism in tests. + */ +export const clearMemoizeCache = () => { + initRecaptchaScript.cache.clear(); +}; |