diff --git a/docs/setup.en.md b/docs/setup.en.md index 72da57a9a..83392d0d9 100644 --- a/docs/setup.en.md +++ b/docs/setup.en.md @@ -47,11 +47,6 @@ In root : 4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) 5. `npm install` Install misskey dependencies. -*(optional)* reCAPTCHA tokens ----------------------------------------------------------------- -If you want to enable reCAPTCHA, you need to generate reCAPTCHA tokens: -Please visit https://www.google.com/recaptcha/intro/ and generate keys. - *(optional)* Generating VAPID keys ---------------------------------------------------------------- If you want to enable ServiceWorker, you need to generate VAPID keys: diff --git a/docs/setup.ja.md b/docs/setup.ja.md index 606857219..79be1fb88 100644 --- a/docs/setup.ja.md +++ b/docs/setup.ja.md @@ -53,11 +53,6 @@ adduser --disabled-password --disabled-login misskey 4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認 5. `npm install` Misskeyの依存パッケージをインストール -*(オプション)* reCAPTCHAトークン ----------------------------------------------------------------- -reCAPTCHAを有効にする場合、reCAPTCHAトークンを取得する必要があります。 -https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してください。 - *(オプション)* VAPIDキーペアの生成 ---------------------------------------------------------------- ServiceWorkerを有効にする場合、VAPIDキーペアを生成する必要があります: diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 22e621260..840dce573 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1085,6 +1085,11 @@ admin/views/instance.vue: local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量" remote-drive-capacity-mb: "リモートユーザーひとりあたりのドライブ容量" mb: "メガバイト単位" + recaptcha-config: "reCAPTCHAの設定" + recaptcha-info: "reCAPTCHAを有効にする場合、reCAPTCHAトークンを取得する必要があります。https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してください。" + enable-recaptcha: "reCAPTCHAを有効にする" + recaptcha-site-key: "reCAPTCHA site key" + recaptcha-secret-key: "reCAPTCHA secret key" max-note-text-length: "投稿の最大文字数" disable-registration: "ユーザー登録の受付を停止する" disable-local-timeline: "ローカルタイムラインを無効にする" diff --git a/src/client/app/admin/views/instance.vue b/src/client/app/admin/views/instance.vue index 050fb6617..47ab9e615 100644 --- a/src/client/app/admin/views/instance.vue +++ b/src/client/app/admin/views/instance.vue @@ -16,6 +16,13 @@ %i18n:@local-drive-capacity-mb%%i18n:@mb%MB %i18n:@remote-drive-capacity-mb%%i18n:@mb%MB +
+
%i18n:@recaptcha-config%
+ %i18n:@enable-recaptcha% + %i18n:@recaptcha-info% + %i18n:@recaptcha-site-key% + %i18n:@recaptcha-secret-key% +
%i18n:@save%
@@ -54,6 +61,9 @@ export default Vue.extend({ localDriveCapacityMb: null, remoteDriveCapacityMb: null, maxNoteTextLength: null, + enableRecaptcha: false, + recaptchaSiteKey: null, + recaptchaSecretKey: null, inviteCode: null, }; }, @@ -67,6 +77,9 @@ export default Vue.extend({ this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; this.maxNoteTextLength = meta.maxNoteTextLength; + this.enableRecaptcha = meta.enableRecaptcha; + this.recaptchaSiteKey = meta.recaptchaSiteKey; + this.recaptchaSecretKey = meta.recaptchaSecretKey; }); }, @@ -92,7 +105,10 @@ export default Vue.extend({ cacheRemoteFiles: this.cacheRemoteFiles, localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), - maxNoteTextLength: parseInt(this.maxNoteTextLength, 10) + maxNoteTextLength: parseInt(this.maxNoteTextLength, 10), + enableRecaptcha: this.enableRecaptcha, + recaptchaSiteKey: this.recaptchaSiteKey, + recaptchaSecretKey: this.recaptchaSecretKey }).then(() => { this.$swal({ type: 'success', diff --git a/src/client/app/common/views/components/signup.vue b/src/client/app/common/views/components/signup.vue index fa26eabc9..91a09e14f 100644 --- a/src/client/app/common/views/components/signup.vue +++ b/src/client/app/common/views/components/signup.vue @@ -35,7 +35,7 @@

%i18n:@password-not-matched%

-
+
%i18n:@create% @@ -130,7 +130,7 @@ export default Vue.extend({ username: this.username, password: this.password, invitationCode: this.invitationCode, - 'g-recaptcha-response': this.meta.recaptchaSitekey != null ? (window as any).grecaptcha.getResponse() : null + 'g-recaptcha-response': this.meta.recaptchaSiteKey != null ? (window as any).grecaptcha.getResponse() : null }, true).then(() => { (this as any).api('signin', { username: this.username, @@ -141,7 +141,7 @@ export default Vue.extend({ }).catch(() => { alert('%i18n:@some-error%'); - if (this.meta.recaptchaSitekey != null) { + if (this.meta.recaptchaSiteKey != null) { (window as any).grecaptcha.reset(); } }); diff --git a/src/config/types.ts b/src/config/types.ts index ff19af27c..07d2ec318 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -40,11 +40,6 @@ export type Source = { port: number; pass: string; }; - recaptcha?: { - site_key: string; - secret_key: string; - }; - drive?: { storage: string; bucket?: string; diff --git a/src/models/meta.ts b/src/models/meta.ts index 073be7de8..3eb73681a 100644 --- a/src/models/meta.ts +++ b/src/models/meta.ts @@ -61,6 +61,19 @@ if ((config as any).preventCacheRemoteFiles) { } }); } +if ((config as any).recaptcha) { + Meta.findOne({}).then(m => { + if (m != null && m.enableRecaptcha == null) { + Meta.update({}, { + $set: { + enableRecaptcha: (config as any).recaptcha != null, + recaptchaSiteKey: (config as any).recaptcha.site_key, + recaptchaSecretKey: (config as any).recaptcha.secret_key, + } + }); + } + }); +} export type IMeta = { name?: string; @@ -79,6 +92,10 @@ export type IMeta = { cacheRemoteFiles?: boolean; + enableRecaptcha?: boolean; + recaptchaSiteKey?: string; + recaptchaSecretKey?: string; + /** * Drive capacity of a local user (MB) */ diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index b4b2b231a..85266b47c 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -88,6 +88,27 @@ export const meta = { desc: { 'ja-JP': 'リモートのファイルをキャッシュするか否か' } + }, + + enableRecaptcha: { + validator: $.bool.optional, + desc: { + 'ja-JP': 'reCAPTCHAを使用するか否か' + } + }, + + recaptchaSiteKey: { + validator: $.str.optional, + desc: { + 'ja-JP': 'reCAPTCHA site key' + } + }, + + recaptchaSecretKey: { + validator: $.str.optional, + desc: { + 'ja-JP': 'reCAPTCHA secret key' + } } } }; @@ -139,6 +160,18 @@ export default define(meta, (ps) => new Promise(async (res, rej) => { set.cacheRemoteFiles = ps.cacheRemoteFiles; } + if (ps.enableRecaptcha !== undefined) { + set.enableRecaptcha = ps.enableRecaptcha; + } + + if (ps.recaptchaSiteKey !== undefined) { + set.recaptchaSiteKey = ps.recaptchaSiteKey; + } + + if (ps.recaptchaSecretKey !== undefined) { + set.recaptchaSecretKey = ps.recaptchaSecretKey; + } + await Meta.update({}, { $set: set }, { upsert: true }); diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index e3d3ad520..3ed225cc5 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -35,7 +35,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => { } }); - res({ + const response: any = { maintainer: config.maintainer, version: pkg.version, @@ -60,24 +60,32 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => { driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, cacheRemoteFiles: instance.cacheRemoteFiles, - recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null, + recaptchaSiteKey: instance.enableRecaptcha ? instance.recaptchaSiteKey : null, swPublickey: config.sw ? config.sw.public_key : null, - hidedTags: (me && me.isAdmin) ? instance.hidedTags : undefined, bannerUrl: instance.bannerUrl, maxNoteTextLength: instance.maxNoteTextLength, emojis: emojis, + }; - features: ps.detail ? { + if (ps.detail) { + response.features = { registration: !instance.disableRegistration, localTimeLine: !instance.disableLocalTimeline, elasticsearch: config.elasticsearch ? true : false, - recaptcha: config.recaptcha ? true : false, + recaptcha: instance.enableRecaptcha, objectStorage: config.drive && config.drive.storage === 'minio', twitter: config.twitter ? true : false, github: config.github ? true : false, serviceWorker: config.sw ? true : false, userRecommendation: config.user_recommendation ? config.user_recommendation : {} - } : undefined - }); + }; + } + + if (me && me.isAdmin) { + response.hidedTags = instance.hidedTags; + response.recaptchaSecretKey = instance.recaptchaSecretKey; + } + + res(response); })); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index eefffd855..3a367ff11 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -1,7 +1,6 @@ import * as Koa from 'koa'; import * as bcrypt from 'bcryptjs'; import { generate as generateKeypair } from '../../../crypto_key'; -const recaptcha = require('recaptcha-promise'); import User, { IUser, validateUsername, validatePassword, pack } from '../../../models/user'; import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; @@ -10,18 +9,20 @@ import RegistrationTicket from '../../../models/registration-tickets'; import usersChart from '../../../chart/users'; import fetchMeta from '../../../misc/fetch-meta'; -if (config.recaptcha) { - recaptcha.init({ - secret_key: config.recaptcha.secret_key - }); -} - export default async (ctx: Koa.Context) => { const body = ctx.request.body as any; + const instance = await fetchMeta(); + + const recaptcha = require('recaptcha-promise'); + // Verify recaptcha // ただしテスト時はこの機構は障害となるため無効にする - if (process.env.NODE_ENV !== 'test' && config.recaptcha != null) { + if (process.env.NODE_ENV !== 'test' && instance.enableRecaptcha) { + recaptcha.init({ + secret_key: instance.recaptchaSecretKey + }); + const success = await recaptcha(body['g-recaptcha-response']); if (!success) { @@ -34,8 +35,6 @@ export default async (ctx: Koa.Context) => { const password = body['password']; const invitationCode = body['invitationCode']; - const instance = await fetchMeta(); - if (instance && instance.disableRegistration) { if (invitationCode == null || typeof invitationCode != 'string') { ctx.status = 400;