Implement instance blocking (#4182)

* Implement instance blocking

* Add missing text

* Delete unnecessary file

* Covert Punycode to Unicode
This commit is contained in:
syuilo 2019-02-08 04:26:43 +09:00 committed by GitHub
parent 2ce9e8fabc
commit dd0871dd59
7 changed files with 105 additions and 4 deletions

View file

@ -1383,6 +1383,7 @@ admin/views/federation.vue:
latest-request-received-at: "直近のリクエスト受信"
remove-all-following: "フォローを全解除"
remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
block: "ブロック"
lookup: "照会"
instances: "インスタンス"
instance-not-registered: "そのインスタンスは登録されていません"
@ -1398,6 +1399,11 @@ admin/views/federation.vue:
followingDesc: "フォローが多い順"
followersAsc: "フォロワーが少ない順"
followersDesc: "フォロワーが多い順"
state: "状態"
states:
all: "すべて"
blocked: "ブロック"
result-is-truncated: "上位{n}件を表示しています。"
desktop/views/pages/welcome.vue:
about: "詳しく..."

View file

@ -39,6 +39,7 @@
<ui-input :value="instance.latestRequestReceivedAt" type="text" readonly>
<span>{{ $t('latest-request-received-at') }}</span>
</ui-input>
<ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch>
<section>
<ui-button @click="removeAllFollowing()"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button>
<ui-info warn>{{ $t('remove-all-following-info', { host: instance.host }) }}</ui-info>
@ -64,6 +65,11 @@
<option value="-followers">{{ $t('sorts.followersAsc') }}</option>
<option value="+followers">{{ $t('sorts.followersDesc') }}</option>
</ui-select>
<ui-select v-model="state">
<span slot="label">{{ $t('state') }}</span>
<option value="all">{{ $t('states.all') }}</option>
<option value="blocked">{{ $t('states.blocked') }}</option>
</ui-select>
</ui-horizon-group>
<div class="instances">
@ -84,6 +90,8 @@
<span>{{ instance.latestStatus }}</span>
</div>
</div>
<ui-info v-if="instances.length == limit">{{ $t('result-is-truncated', { n: limit }) }}</ui-info>
</section>
</ui-card>
</div>
@ -102,6 +110,7 @@ export default Vue.extend({
instance: null,
target: null,
sort: '+caughtAt',
state: 'all',
limit: 50,
instances: [],
faGlobe, faTerminal, faSearch, faMinusCircle
@ -110,7 +119,10 @@ export default Vue.extend({
watch: {
sort() {
this.instances = [];
this.fetchInstances();
},
state() {
this.fetchInstances();
},
},
@ -137,9 +149,11 @@ export default Vue.extend({
},
fetchInstances() {
this.instances = [];
this.$root.api('federation/instances', {
state: this.state,
sort: this.sort,
limit: 50
limit: this.limit
}).then(instances => {
this.instances = instances;
});
@ -154,7 +168,14 @@ export default Vue.extend({
splash: true
});
});
}
},
updateInstance() {
this.$root.api('admin/federation/update-instance', {
host: this.instance.host,
isBlocked: this.instance.isBlocked,
});
},
}
});
</script>

View file

@ -57,4 +57,9 @@ export interface IInstance {
*
*/
latestRequestReceivedAt?: Date;
/**
*
*/
isBlocked: boolean;
}

View file

@ -45,6 +45,15 @@ export default async (job: bq.Job, done: any): Promise<void> => {
return;
}
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const instance = await Instance.findOne({ host: host.toLowerCase() });
if (instance && instance.isBlocked) {
logger.warn(`Blocked request: ${host}`);
done();
return;
}
user = await User.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser;
} else {
// アクティビティ内のホストの検証
@ -57,6 +66,15 @@ export default async (job: bq.Job, done: any): Promise<void> => {
return;
}
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const instance = await Instance.findOne({ host: host.toLowerCase() });
if (instance && instance.isBlocked) {
logger.warn(`Blocked request: ${host}`);
done();
return;
}
user = await User.findOne({
host: { $ne: null },
'publicKey.id': signature.keyId

View file

@ -4,11 +4,13 @@ import { URL } from 'url';
import * as crypto from 'crypto';
import { lookup, IRunOptions } from 'lookup-dns-cache';
import * as promiseAny from 'promise-any';
import { toUnicode } from 'punycode';
import config from '../../config';
import { ILocalUser } from '../../models/user';
import { publishApLogStream } from '../../services/stream';
import { apLogger } from './logger';
import Instance from '../../models/instance';
export const logger = apLogger.createSubLogger('deliver');
@ -19,6 +21,11 @@ export default (user: ILocalUser, url: string, object: any) => new Promise(async
const { protocol, host, hostname, port, pathname, search } = new URL(url);
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const instance = await Instance.findOne({ host: toUnicode(host) });
if (instance && instance.isBlocked) return;
const data = JSON.stringify(object);
const sha256 = crypto.createHash('sha256');

View file

@ -0,0 +1,34 @@
import $ from 'cafy';
import define from '../../../define';
import Instance from '../../../../../models/instance';
export const meta = {
requireCredential: true,
requireModerator: true,
params: {
host: {
validator: $.str
},
isBlocked: {
validator: $.bool
},
}
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
const instance = await Instance.findOne({ host: ps.host });
if (instance == null) {
return rej('instance not found');
}
Instance.update({ host: ps.host }, {
$set: {
isBlocked: ps.isBlocked
}
});
res();
}));

View file

@ -6,6 +6,10 @@ export const meta = {
requireCredential: false,
params: {
state: {
validator: $.str.optional,
},
limit: {
validator: $.num.optional.range(1, 100),
default: 30
@ -73,8 +77,14 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
};
}
const q = {} as any;
if (ps.state === 'blocked') {
q.isBlocked = true;
}
const instances = await Instance
.find({}, {
.find(q, {
limit: ps.limit,
sort: sort,
skip: ps.offset