diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3db383e378..f22a76917f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -107,6 +107,7 @@ customEmojis: "カスタム絵文字" emojiName: "絵文字名" emojiUrl: "絵文字画像URL" addEmoji: "絵文字を追加" +settingGuide: "おすすめ設定" cacheRemoteFiles: "リモートのファイルをキャッシュする" cacheRemoteFilesDescription: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。サーバーのストレージを節約できますが、サムネイルが生成されないので通信量が増加します。" flagAsBot: "Botとして設定" @@ -299,10 +300,15 @@ bannerUrl: "バナー画像のURL" basicInfo: "基本情報" pinnedUsers: "ピン留めユーザー" pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。" +hcaptcha: "hCaptcha" +enableHcaptcha: "hCaptchaを有効にする" +hcaptchaSiteKey: "サイトキー" +hcaptchaSecretKey: "シークレットキー" recaptcha: "reCAPTCHA" enableRecaptcha: "reCAPTCHAを有効にする" recaptchaSiteKey: "サイトキー" recaptchaSecretKey: "シークレットキー" +avoidMultiCaptchaConfirm: "複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。" antennas: "アンテナ" manageAntennas: "アンテナの管理" name: "名前" 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 9764155e71..39219260f7 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..afed587560 --- /dev/null +++ b/src/@types/hcaptcha.d.ts @@ -0,0 +1,11 @@ +declare module 'hcaptcha' { + interface IVerifyResponse { + success: boolean; + challenge_ts: string; + hostname: string; + credit?: boolean; + 'error-codes'?: unknown[]; + } + + export function verify(secret: string, token: string): Promise; +} 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 @@ + + + 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 @@ @@ -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(); - } }); } } }); + + 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 @@