reCAPTCHAの設定をDBに保存するように

This commit is contained in:
syuilo 2018-11-07 00:08:21 +09:00
parent 22cf9b0b4c
commit 7f57bbfbbd
10 changed files with 99 additions and 36 deletions

View file

@ -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) 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. 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 *(optional)* Generating VAPID keys
---------------------------------------------------------------- ----------------------------------------------------------------
If you want to enable ServiceWorker, you need to generate VAPID keys: If you want to enable ServiceWorker, you need to generate VAPID keys:

View file

@ -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)を確認 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の依存パッケージをインストール 5. `npm install` Misskeyの依存パッケージをインストール
*(オプション)* reCAPTCHAトークン
----------------------------------------------------------------
reCAPTCHAを有効にする場合、reCAPTCHAトークンを取得する必要があります。
https://www.google.com/recaptcha/intro/ にアクセスしてトークンを取得してください。
*(オプション)* VAPIDキーペアの生成 *(オプション)* VAPIDキーペアの生成
---------------------------------------------------------------- ----------------------------------------------------------------
ServiceWorkerを有効にする場合、VAPIDキーペアを生成する必要があります: ServiceWorkerを有効にする場合、VAPIDキーペアを生成する必要があります:

View file

@ -1085,6 +1085,11 @@ admin/views/instance.vue:
local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量" local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量"
remote-drive-capacity-mb: "リモートユーザーひとりあたりのドライブ容量" remote-drive-capacity-mb: "リモートユーザーひとりあたりのドライブ容量"
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: "投稿の最大文字数" max-note-text-length: "投稿の最大文字数"
disable-registration: "ユーザー登録の受付を停止する" disable-registration: "ユーザー登録の受付を停止する"
disable-local-timeline: "ローカルタイムラインを無効にする" disable-local-timeline: "ローカルタイムラインを無効にする"

View file

@ -16,6 +16,13 @@
<ui-input v-model="localDriveCapacityMb">%i18n:@local-drive-capacity-mb%<span slot="desc">%i18n:@mb%</span><span slot="suffix">MB</span></ui-input> <ui-input v-model="localDriveCapacityMb">%i18n:@local-drive-capacity-mb%<span slot="desc">%i18n:@mb%</span><span slot="suffix">MB</span></ui-input>
<ui-input v-model="remoteDriveCapacityMb" :disabled="!cacheRemoteFiles">%i18n:@remote-drive-capacity-mb%<span slot="desc">%i18n:@mb%</span><span slot="suffix">MB</span></ui-input> <ui-input v-model="remoteDriveCapacityMb" :disabled="!cacheRemoteFiles">%i18n:@remote-drive-capacity-mb%<span slot="desc">%i18n:@mb%</span><span slot="suffix">MB</span></ui-input>
</section> </section>
<section class="fit-bottom">
<header><fa icon="shield-alt"/> %i18n:@recaptcha-config%</header>
<ui-switch v-model="enableRecaptcha">%i18n:@enable-recaptcha%</ui-switch>
<ui-info>%i18n:@recaptcha-info%</ui-info>
<ui-input v-model="recaptchaSiteKey" :disabled="!enableRecaptcha"><i slot="icon"><fa icon="key"/></i>%i18n:@recaptcha-site-key%</ui-input>
<ui-input v-model="recaptchaSecretKey" :disabled="!enableRecaptcha"><i slot="icon"><fa icon="key"/></i>%i18n:@recaptcha-secret-key%</ui-input>
</section>
<section> <section>
<ui-button @click="updateMeta">%i18n:@save%</ui-button> <ui-button @click="updateMeta">%i18n:@save%</ui-button>
</section> </section>
@ -54,6 +61,9 @@ export default Vue.extend({
localDriveCapacityMb: null, localDriveCapacityMb: null,
remoteDriveCapacityMb: null, remoteDriveCapacityMb: null,
maxNoteTextLength: null, maxNoteTextLength: null,
enableRecaptcha: false,
recaptchaSiteKey: null,
recaptchaSecretKey: null,
inviteCode: null, inviteCode: null,
}; };
}, },
@ -67,6 +77,9 @@ export default Vue.extend({
this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
this.maxNoteTextLength = meta.maxNoteTextLength; 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, cacheRemoteFiles: this.cacheRemoteFiles,
localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 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(() => { }).then(() => {
this.$swal({ this.$swal({
type: 'success', type: 'success',

View file

@ -35,7 +35,7 @@
<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@password-not-matched%</p> <p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@password-not-matched%</p>
</div> </div>
</ui-input> </ui-input>
<div v-if="meta.recaptchaSitekey != null" class="g-recaptcha" :data-sitekey="meta.recaptchaSitekey" style="margin: 16px 0;"></div> <div v-if="meta.recaptchaSiteKey != null" class="g-recaptcha" :data-sitekey="meta.recaptchaSiteKey" style="margin: 16px 0;"></div>
<ui-button type="submit">%i18n:@create%</ui-button> <ui-button type="submit">%i18n:@create%</ui-button>
</template> </template>
</form> </form>
@ -130,7 +130,7 @@ export default Vue.extend({
username: this.username, username: this.username,
password: this.password, password: this.password,
invitationCode: this.invitationCode, 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(() => { }, true).then(() => {
(this as any).api('signin', { (this as any).api('signin', {
username: this.username, username: this.username,
@ -141,7 +141,7 @@ export default Vue.extend({
}).catch(() => { }).catch(() => {
alert('%i18n:@some-error%'); alert('%i18n:@some-error%');
if (this.meta.recaptchaSitekey != null) { if (this.meta.recaptchaSiteKey != null) {
(window as any).grecaptcha.reset(); (window as any).grecaptcha.reset();
} }
}); });

View file

@ -40,11 +40,6 @@ export type Source = {
port: number; port: number;
pass: string; pass: string;
}; };
recaptcha?: {
site_key: string;
secret_key: string;
};
drive?: { drive?: {
storage: string; storage: string;
bucket?: string; bucket?: string;

View file

@ -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 = { export type IMeta = {
name?: string; name?: string;
@ -79,6 +92,10 @@ export type IMeta = {
cacheRemoteFiles?: boolean; cacheRemoteFiles?: boolean;
enableRecaptcha?: boolean;
recaptchaSiteKey?: string;
recaptchaSecretKey?: string;
/** /**
* Drive capacity of a local user (MB) * Drive capacity of a local user (MB)
*/ */

View file

@ -88,6 +88,27 @@ export const meta = {
desc: { desc: {
'ja-JP': 'リモートのファイルをキャッシュするか否か' '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; 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({}, { await Meta.update({}, {
$set: set $set: set
}, { upsert: true }); }, { upsert: true });

View file

@ -35,7 +35,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
} }
}); });
res({ const response: any = {
maintainer: config.maintainer, maintainer: config.maintainer,
version: pkg.version, version: pkg.version,
@ -60,24 +60,32 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
cacheRemoteFiles: instance.cacheRemoteFiles, 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, swPublickey: config.sw ? config.sw.public_key : null,
hidedTags: (me && me.isAdmin) ? instance.hidedTags : undefined,
bannerUrl: instance.bannerUrl, bannerUrl: instance.bannerUrl,
maxNoteTextLength: instance.maxNoteTextLength, maxNoteTextLength: instance.maxNoteTextLength,
emojis: emojis, emojis: emojis,
};
features: ps.detail ? { if (ps.detail) {
response.features = {
registration: !instance.disableRegistration, registration: !instance.disableRegistration,
localTimeLine: !instance.disableLocalTimeline, localTimeLine: !instance.disableLocalTimeline,
elasticsearch: config.elasticsearch ? true : false, elasticsearch: config.elasticsearch ? true : false,
recaptcha: config.recaptcha ? true : false, recaptcha: instance.enableRecaptcha,
objectStorage: config.drive && config.drive.storage === 'minio', objectStorage: config.drive && config.drive.storage === 'minio',
twitter: config.twitter ? true : false, twitter: config.twitter ? true : false,
github: config.github ? true : false, github: config.github ? true : false,
serviceWorker: config.sw ? true : false, serviceWorker: config.sw ? true : false,
userRecommendation: config.user_recommendation ? config.user_recommendation : {} userRecommendation: config.user_recommendation ? config.user_recommendation : {}
} : undefined };
}); }
if (me && me.isAdmin) {
response.hidedTags = instance.hidedTags;
response.recaptchaSecretKey = instance.recaptchaSecretKey;
}
res(response);
})); }));

View file

@ -1,7 +1,6 @@
import * as Koa from 'koa'; import * as Koa from 'koa';
import * as bcrypt from 'bcryptjs'; import * as bcrypt from 'bcryptjs';
import { generate as generateKeypair } from '../../../crypto_key'; import { generate as generateKeypair } from '../../../crypto_key';
const recaptcha = require('recaptcha-promise');
import User, { IUser, validateUsername, validatePassword, pack } from '../../../models/user'; import User, { IUser, validateUsername, validatePassword, pack } from '../../../models/user';
import generateUserToken from '../common/generate-native-user-token'; import generateUserToken from '../common/generate-native-user-token';
import config from '../../../config'; import config from '../../../config';
@ -10,18 +9,20 @@ import RegistrationTicket from '../../../models/registration-tickets';
import usersChart from '../../../chart/users'; import usersChart from '../../../chart/users';
import fetchMeta from '../../../misc/fetch-meta'; import fetchMeta from '../../../misc/fetch-meta';
if (config.recaptcha) {
recaptcha.init({
secret_key: config.recaptcha.secret_key
});
}
export default async (ctx: Koa.Context) => { export default async (ctx: Koa.Context) => {
const body = ctx.request.body as any; const body = ctx.request.body as any;
const instance = await fetchMeta();
const recaptcha = require('recaptcha-promise');
// Verify recaptcha // 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']); const success = await recaptcha(body['g-recaptcha-response']);
if (!success) { if (!success) {
@ -34,8 +35,6 @@ export default async (ctx: Koa.Context) => {
const password = body['password']; const password = body['password'];
const invitationCode = body['invitationCode']; const invitationCode = body['invitationCode'];
const instance = await fetchMeta();
if (instance && instance.disableRegistration) { if (instance && instance.disableRegistration) {
if (invitationCode == null || typeof invitationCode != 'string') { if (invitationCode == null || typeof invitationCode != 'string') {
ctx.status = 400; ctx.status = 400;