diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3db383e378..de44d89a05 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 0000000000..a3f4e93670 --- /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 07d88db0e2..de669ee48f 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 0000000000..ef3d44256c --- /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 0000000000..e54eb314a3 --- /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 9f3ae8db28..5d16a82bac 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 94d07cbaed..c2dd0038be 100644 --- a/src/client/components/url-preview.vue +++ b/src/client/components/url-preview.vue @@ -4,7 +4,7 @@