Implement instance blocking (#4182)
* Implement instance blocking * Add missing text * Delete unnecessary file * Covert Punycode to Unicode
This commit is contained in:
		
							parent
							
								
									5a28632af7
								
							
						
					
					
						commit
						e6612f610c
					
				
					 7 changed files with 105 additions and 4 deletions
				
			
		| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,4 +57,9 @@ export interface IInstance {
 | 
			
		|||
	 * 直近のリクエスト受信日時
 | 
			
		||||
	 */
 | 
			
		||||
	latestRequestReceivedAt?: Date;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * このインスタンスをブロックしているか
 | 
			
		||||
	 */
 | 
			
		||||
	isBlocked: boolean;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										34
									
								
								src/server/api/endpoints/admin/federation/update-instance.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/server/api/endpoints/admin/federation/update-instance.ts
									
										
									
									
									
										Normal 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();
 | 
			
		||||
}));
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue