diff options
| author | Acid Chicken (硫酸鶏) <root@acid-chicken.com> | 2020-05-05 10:22:49 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-05-05 10:22:49 +0900 |
| commit | 4cccdb8a98ffd69584c7139545ecd17bfb3b7a3e (patch) | |
| tree | 8bad0d079776b8fd3e20cfe65b44f205a0763287 /src/client/components | |
| parent | chore: Update dependencies :rocket: (diff) | |
| parent | Update hcaptcha.d.ts (diff) | |
| download | sharkey-4cccdb8a98ffd69584c7139545ecd17bfb3b7a3e.tar.gz sharkey-4cccdb8a98ffd69584c7139545ecd17bfb3b7a3e.tar.bz2 sharkey-4cccdb8a98ffd69584c7139545ecd17bfb3b7a3e.zip | |
Merge pull request #6303 from syuilo/features/hcaptcha
Add support for hCaptcha
Diffstat (limited to 'src/client/components')
| -rw-r--r-- | src/client/components/captcha.vue | 119 | ||||
| -rw-r--r-- | src/client/components/signup-dialog.vue | 2 | ||||
| -rw-r--r-- | src/client/components/signup.vue | 44 | ||||
| -rw-r--r-- | src/client/components/url-preview.vue | 2 |
4 files changed, 150 insertions, 17 deletions
diff --git a/src/client/components/captcha.vue b/src/client/components/captcha.vue new file mode 100644 index 0000000000..6b1ee6f0b2 --- /dev/null +++ b/src/client/components/captcha.vue @@ -0,0 +1,119 @@ +<template> +<div> + <span v-if="!available">{{ $t('waiting') }}<mk-ellipsis/></span> + <div ref="captcha"></div> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import i18n from '../i18n'; + +type Captcha = { + render(container: string | Node, options: { + readonly [_ in 'sitekey' | 'theme' | 'type' | 'size' | 'tabindex' | 'callback' | 'expired' | 'expired-callback' | 'error-callback' | 'endpoint']?: unknown; + }): string; + remove(id: string): void; + execute(id: string): void; + reset(id: string): void; + getResponse(id: string): string; +}; + +type CaptchaProvider = 'hcaptcha' | 'grecaptcha'; + +type CaptchaContainer = { + readonly [_ in CaptchaProvider]?: Captcha; +}; + +declare global { + interface Window extends CaptchaContainer { + } +} + +export default Vue.extend({ + i18n, + props: { + provider: { + type: String, + required: true, + }, + sitekey: { + type: String, + required: true, + }, + value: { + type: String, + }, + }, + + data() { + return { + available: false, + }; + }, + + computed: { + loaded() { + return !!window[this.provider as CaptchaProvider]; + }, + src() { + const endpoint = ({ + hcaptcha: 'https://hcaptcha.com/1', + grecaptcha: 'https://www.google.com/recaptcha', + } as Record<PropertyKey, unknown>)[this.provider]; + + return `${typeof endpoint == 'string' ? endpoint : 'about:invalid'}/api.js?render=explicit`; + }, + captcha() { + return window[this.provider as CaptchaProvider] || {} as unknown as Captcha; + }, + }, + + created() { + if (this.loaded) { + this.available = true; + } else { + (document.getElementById(this.provider) || document.head.appendChild(Object.assign(document.createElement('script'), { + async: true, + id: this.provider, + src: this.src, + }))) + .addEventListener('load', () => this.available = true); + } + }, + + mounted() { + if (this.available) { + this.requestRender(); + } else { + this.$watch('available', this.requestRender); + } + }, + + beforeDestroy() { + this.reset(); + }, + + methods: { + reset() { + this.captcha?.reset(); + }, + requestRender() { + if (this.captcha.render && this.$refs.captcha instanceof Element) { + this.captcha.render(this.$refs.captcha, { + sitekey: this.sitekey, + theme: this.$store.state.device.darkMode ? 'dark' : 'light', + callback: this.callback, + 'expired-callback': this.callback, + 'error-callback': this.callback, + }); + } else { + setTimeout(this.requestRender.bind(this), 1); + } + }, + callback(response?: string) { + this.$emit('input', typeof response == 'string' ? response : null); + }, + }, +}); +</script> diff --git a/src/client/components/signup-dialog.vue b/src/client/components/signup-dialog.vue index 10cdf3a567..4db79af512 100644 --- a/src/client/components/signup-dialog.vue +++ b/src/client/components/signup-dialog.vue @@ -1,5 +1,5 @@ <template> -<x-window ref="window" @closed="() => { $emit('closed'); destroyDom(); }"> +<x-window ref="window" :width="366" :height="506" @closed="() => { $emit('closed'); destroyDom(); }"> <template #header>{{ $t('signup') }}</template> <x-signup :auto-set="autoSet" @signup="onSignup"/> </x-window> diff --git a/src/client/components/signup.vue b/src/client/components/signup.vue index 9f3ae8db28..6452afc886 100644 --- a/src/client/components/signup.vue +++ b/src/client/components/signup.vue @@ -41,8 +41,9 @@ <a :href="meta.tosUrl" class="_link" target="_blank">{{ $t('tos') }}</a> </i18n> </mk-switch> - <div v-if="meta.enableRecaptcha" class="g-recaptcha" :data-sitekey="meta.recaptchaSiteKey" style="margin: 16px 0;"></div> - <mk-button type="submit" :disabled=" submitting || !(meta.tosUrl ? ToSAgreement : true) || passwordRetypeState == 'not-match'" primary>{{ $t('start') }}</mk-button> + <captcha v-if="meta.enableHcaptcha" class="captcha" provider="hcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :sitekey="meta.hcaptchaSiteKey"/> + <captcha v-if="meta.enableRecaptcha" class="captcha" provider="grecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :sitekey="meta.recaptchaSiteKey"/> + <mk-button type="submit" :disabled="shouldDisableSubmitting" primary>{{ $t('start') }}</mk-button> </template> </form> </template> @@ -65,6 +66,7 @@ export default Vue.extend({ MkButton, MkInput, MkSwitch, + captcha: () => import('./captcha.vue').then(x => x.default), }, data() { @@ -80,6 +82,8 @@ export default Vue.extend({ passwordRetypeState: null, submitting: false, ToSAgreement: false, + hCaptchaResponse: null, + reCaptchaResponse: null, faLock, faExclamationTriangle, faSpinner, faCheck, faKey } }, @@ -96,7 +100,15 @@ export default Vue.extend({ meta() { return this.$store.state.instance.meta; }, - + + shouldDisableSubmitting(): boolean { + return this.submitting || + this.meta.tosUrl && !this.ToSAgreement || + this.meta.enableHcaptcha && !this.hCaptchaResponse || + this.meta.enableRecaptcha && !this.reCaptchaResponse || + this.passwordRetypeState == 'not-match'; + }, + shouldShowProfileUrl(): boolean { return (this.username != '' && this.usernameState != 'invalid-format' && @@ -114,13 +126,6 @@ export default Vue.extend({ } }, - mounted() { - const head = document.getElementsByTagName('head')[0]; - const script = document.createElement('script'); - script.setAttribute('src', 'https://www.google.com/recaptcha/api.js'); - head.appendChild(script); - }, - methods: { onChangeUsername() { if (this.username == '') { @@ -177,7 +182,8 @@ export default Vue.extend({ username: this.username, password: this.password, invitationCode: this.invitationCode, - 'g-recaptcha-response': this.meta.enableRecaptcha ? (window as any).grecaptcha.getResponse() : null + 'hcaptcha-response': this.hCaptchaResponse, + 'g-recaptcha-response': this.meta.reCaptchaResponse, }).then(() => { this.$root.api('signin', { username: this.username, @@ -187,17 +193,25 @@ export default Vue.extend({ }); }).catch(() => { this.submitting = false; + this.$refs.hcaptcha?.reset?.(); + this.$refs.recaptcha?.reset?.(); this.$root.dialog({ type: 'error', text: this.$t('error') }); - - if (this.meta.enableRecaptcha) { - (window as any).grecaptcha.reset(); - } }); } } }); </script> + +<style lang="scss" scoped> +.mk-signup { + padding: 32px 0 0; + + .captcha { + margin: 16px 0; + } +} +</style> diff --git a/src/client/components/url-preview.vue b/src/client/components/url-preview.vue index 94d07cbaed..c2dd0038be 100644 --- a/src/client/components/url-preview.vue +++ b/src/client/components/url-preview.vue @@ -4,7 +4,7 @@ <iframe :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen /> </div> <div v-else-if="tweetUrl && detail" class="twitter"> - <blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null"> + <blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkMode ? 'dark' : null"> <a :href="url"></a> </blockquote> </div> |