gitlab-org--gitlab-foss/app/assets/javascripts/captcha/captcha_modal.vue

121 lines
4.1 KiB
Vue

<script>
// NOTE 1: This modal directly uses the reCAPTCHA Javascript API
// (https://developers.google.com/recaptcha/docs/display#js_api) and gl-modal,
// rather than relying form-based reCAPTCHA HTML being pre-rendered by the backend.
// 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();
}
},
},
mounted() {
// 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 (this.needsCaptchaResponse) {
this.$refs.modal.show();
}
},
methods: {
emitReceivedCaptchaResponse(captchaResponse) {
this.$refs.modal.hide();
this.$emit('receivedCaptchaResponse', captchaResponse);
},
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="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
text: __('Cancel'),
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
@shown="shown"
@hide="hide"
@hidden="$emit('hidden')"
>
<div ref="captcha"></div>
<p>{{ __('We want to be sure it is you, please confirm you are not a robot.') }}</p>
</gl-modal>
</template>