From 8d233fa1cf8c01d1cdaad381c94d512a42571749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Tue, 28 Apr 2020 14:29:33 +0900 Subject: [PATCH] Add support for hCaptcha --- locales/ja-JP.yml | 4 + migration/1588044505511-hCaptcha.ts | 18 +++++ package.json | 1 + src/@types/hcaptcha.d.ts | 9 +++ src/client/components/hcaptcha.vue | 76 +++++++++++++++++++ src/client/components/signup.vue | 24 ++++-- src/client/components/url-preview.vue | 2 +- src/client/pages/instance/settings.vue | 67 +++++++++++++--- src/models/entities/meta.ts | 17 +++++ src/server/api/endpoints/admin/update-meta.ts | 33 ++++++++ src/server/api/endpoints/meta.ts | 4 + src/server/api/private/signup.ts | 13 +++- src/server/nodeinfo.ts | 1 + src/server/web/views/info.pug | 3 + yarn.lock | 5 ++ 15 files changed, 257 insertions(+), 20 deletions(-) create mode 100644 migration/1588044505511-hCaptcha.ts create mode 100644 src/@types/hcaptcha.d.ts create mode 100644 src/client/components/hcaptcha.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3db383e37..de44d89a0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -299,6 +299,10 @@ bannerUrl: "バナー画像のURL" basicInfo: "基本情報" pinnedUsers: "ピン留めユーザー" pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。" +hcaptcha: "hCaptcha" +enableHcaptcha: "hCaptchaを有効にする" +hcaptchaSiteKey: "サイトキー" +hcaptchaSecretKey: "シークレットキー" recaptcha: "reCAPTCHA" enableRecaptcha: "reCAPTCHAを有効にする" recaptchaSiteKey: "サイトキー" diff --git a/migration/1588044505511-hCaptcha.ts b/migration/1588044505511-hCaptcha.ts new file mode 100644 index 000000000..a3f4e9367 --- /dev/null +++ b/migration/1588044505511-hCaptcha.ts @@ -0,0 +1,18 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class hCaptcha1588044505511 implements MigrationInterface { + name = 'hCaptcha1588044505511' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableHcaptcha" boolean NOT NULL DEFAULT false`, undefined); + await queryRunner.query(`ALTER TABLE "meta" ADD "hcaptchaSiteKey" character varying(64)`, undefined); + await queryRunner.query(`ALTER TABLE "meta" ADD "hcaptchaSecretKey" character varying(64)`, undefined); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "hcaptchaSecretKey"`, undefined); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "hcaptchaSiteKey"`, undefined); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableHcaptcha"`, undefined); + } + +} diff --git a/package.json b/package.json index 07d88db0e..de669ee48 100644 --- a/package.json +++ b/package.json @@ -144,6 +144,7 @@ "gulp-tslint": "8.1.4", "gulp-typescript": "6.0.0-alpha.1", "hard-source-webpack-plugin": "0.13.1", + "hcaptcha": "0.0.1", "html-minifier": "4.0.0", "http-proxy-agent": "4.0.1", "http-signature": "1.3.4", diff --git a/src/@types/hcaptcha.d.ts b/src/@types/hcaptcha.d.ts new file mode 100644 index 000000000..ef3d44256 --- /dev/null +++ b/src/@types/hcaptcha.d.ts @@ -0,0 +1,9 @@ +declare module 'hcaptcha' { + export function verify(secret: string, token: string): Promise<{ + success: boolean; + challenge_ts: string; + hostname: string; + credit?: boolean; + 'error-codes'?: unknown[]; + }>; +} diff --git a/src/client/components/hcaptcha.vue b/src/client/components/hcaptcha.vue new file mode 100644 index 000000000..e54eb314a --- /dev/null +++ b/src/client/components/hcaptcha.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/client/components/signup.vue b/src/client/components/signup.vue index 9f3ae8db2..5d16a82ba 100644 --- a/src/client/components/signup.vue +++ b/src/client/components/signup.vue @@ -42,7 +42,8 @@
- {{ $t('start') }} + + {{ $t('start') }} @@ -65,6 +66,7 @@ export default Vue.extend({ MkButton, MkInput, MkSwitch, + hCaptcha: () => import('./hcaptcha.vue').then(x => x.default), }, data() { @@ -80,6 +82,7 @@ export default Vue.extend({ passwordRetypeState: null, submitting: false, ToSAgreement: false, + hCaptchaResponse: null, faLock, faExclamationTriangle, faSpinner, faCheck, faKey } }, @@ -96,7 +99,14 @@ 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.passwordRetypeState == 'not-match'; + }, + shouldShowProfileUrl(): boolean { return (this.username != '' && this.usernameState != 'invalid-format' && @@ -115,10 +125,11 @@ 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); + if (this.meta.enableRecaptcha) { + const script = document.createElement('script'); + script.setAttribute('src', 'https://www.google.com/recaptcha/api.js'); + document.head.appendChild(script); + } }, methods: { @@ -177,6 +188,7 @@ export default Vue.extend({ username: this.username, password: this.password, invitationCode: this.invitationCode, + 'hcaptcha-response': this.hCaptchaResponse, 'g-recaptcha-response': this.meta.enableRecaptcha ? (window as any).grecaptcha.getResponse() : null }).then(() => { this.$root.api('signin', { diff --git a/src/client/components/url-preview.vue b/src/client/components/url-preview.vue index 94d07cbae..c2dd0038b 100644 --- a/src/client/components/url-preview.vue +++ b/src/client/components/url-preview.vue @@ -4,7 +4,7 @@