Merge branch 'feature-allowlist' into egirls-sharkey
This commit is contained in:
		
						commit
						74a62c2107
					
				
					 22 changed files with 158 additions and 5 deletions
				
			
		
							
								
								
									
										17
									
								
								packages/backend/migration/1635065559969-allowlist.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/backend/migration/1635065559969-allowlist.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
 | 
			
		||||
export class allowlist1635065559969 {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.name = 'allowlist1635065559969';
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async up(queryRunner) {
 | 
			
		||||
		await queryRunner.query(`ALTER TABLE "meta" ADD "allowedHosts" character varying(256) array NOT NULL DEFAULT '{}'::varchar[]`);
 | 
			
		||||
		await queryRunner.query(`ALTER TABLE "meta" ADD "allowlistMode" boolean NOT NULL DEFAULT false`);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async down(queryRunner) {
 | 
			
		||||
		await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "allowedHosts"`);
 | 
			
		||||
		await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "allowlistMode"`);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +42,12 @@ export class UtilityService {
 | 
			
		|||
		return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public isAllowedHost(allowedHosts: string[], host: string | null): boolean {
 | 
			
		||||
		if (host == null) return false;
 | 
			
		||||
		return allowedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
 | 
			
		||||
		if (sensitiveWords.length === 0) return false;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -269,7 +269,9 @@ export class ApInboxService {
 | 
			
		|||
 | 
			
		||||
		// アナウンス先をブロックしてたら中断
 | 
			
		||||
		const meta = await this.metaService.fetch();
 | 
			
		||||
		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) return;
 | 
			
		||||
		const dbHost = this.utilityService.extractDbHost(uri);
 | 
			
		||||
		if (this.utilityService.isBlockedHost(meta.blockedHosts, dbHost)) return;
 | 
			
		||||
		if (meta.allowlistMode && !this.utilityService.isAllowedHost(meta.allowedHosts, dbHost)) return;
 | 
			
		||||
 | 
			
		||||
		const unlock = await this.appLockService.getApLock(uri);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,6 +99,10 @@ export class Resolver {
 | 
			
		|||
			throw new Error('Instance is blocked');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (meta.allowlistMode && !this.utilityService.isAllowedHost(meta.allowedHosts, host)) {
 | 
			
		||||
			throw new Error('Instance is blocked');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (this.config.signToActivityPubGet && !this.user) {
 | 
			
		||||
			this.user = await this.instanceActorService.getInstanceActor();
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -532,7 +532,12 @@ export class ApNoteService {
 | 
			
		|||
 | 
			
		||||
		// ブロックしていたら中断
 | 
			
		||||
		const meta = await this.metaService.fetch();
 | 
			
		||||
		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) {
 | 
			
		||||
		const dbHost = this.utilityService.extractDbHost(uri);
 | 
			
		||||
		if (this.utilityService.isBlockedHost(meta.blockedHosts, dbHost)) {
 | 
			
		||||
			throw new StatusError('blocked host', 451);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (meta.allowlistMode && !this.utilityService.isAllowedHost(meta.allowedHosts, dbHost)) {
 | 
			
		||||
			throw new StatusError('blocked host', 451);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,11 +61,13 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 | 
			
		|||
			.select('f.followerHost')
 | 
			
		||||
			.where('f.followerHost IS NOT NULL');
 | 
			
		||||
 | 
			
		||||
		const skipAllowlistQuery = !meta.allowlistMode || meta.allowedHosts.length === 0;
 | 
			
		||||
		const [sub, pub, pubsub, subActive, pubActive] = await Promise.all([
 | 
			
		||||
			this.followingsRepository.createQueryBuilder('following')
 | 
			
		||||
				.select('COUNT(DISTINCT following.followeeHost)')
 | 
			
		||||
				.where('following.followeeHost IS NOT NULL')
 | 
			
		||||
				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere(skipAllowlistQuery ? '1=1' : 'following.followeeHost ILIKE ANY(ARRAY[:...allowed])', { allowed: meta.allowedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
 | 
			
		||||
				.getRawOne()
 | 
			
		||||
				.then(x => parseInt(x.count, 10)),
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +75,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 | 
			
		|||
				.select('COUNT(DISTINCT following.followerHost)')
 | 
			
		||||
				.where('following.followerHost IS NOT NULL')
 | 
			
		||||
				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere(skipAllowlistQuery ? '1=1' : 'following.followerHost ILIKE ANY(ARRAY[:...allowed])', { allowed: meta.allowedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
 | 
			
		||||
				.getRawOne()
 | 
			
		||||
				.then(x => parseInt(x.count, 10)),
 | 
			
		||||
| 
						 | 
				
			
			@ -80,6 +83,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 | 
			
		|||
				.select('COUNT(DISTINCT following.followeeHost)')
 | 
			
		||||
				.where('following.followeeHost IS NOT NULL')
 | 
			
		||||
				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere(skipAllowlistQuery ? '1=1' : 'following.followeeHost ILIKE ANY(ARRAY[:...allowed])', { allowed: meta.allowedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
 | 
			
		||||
				.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
 | 
			
		||||
				.setParameters(pubsubSubQuery.getParameters())
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +93,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 | 
			
		|||
				.select('COUNT(instance.id)')
 | 
			
		||||
				.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
 | 
			
		||||
				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere(skipAllowlistQuery ? '1=1' : 'instance.host ILIKE ANY(ARRAY[:...allowed])', { allowed: meta.allowedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere('instance.isSuspended = false')
 | 
			
		||||
				.andWhere('instance.isNotResponding = false')
 | 
			
		||||
				.getRawOne()
 | 
			
		||||
| 
						 | 
				
			
			@ -97,6 +102,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 | 
			
		|||
				.select('COUNT(instance.id)')
 | 
			
		||||
				.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
 | 
			
		||||
				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ANY(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere(skipAllowlistQuery ? '1=1' : 'instance.host ILIKE ANY(ARRAY[:...allowed])', { allowed: meta.allowedHosts.flatMap(x => [x, `%.${x}`]) })
 | 
			
		||||
				.andWhere('instance.isSuspended = false')
 | 
			
		||||
				.andWhere('instance.isNotResponding = false')
 | 
			
		||||
				.getRawOne()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,6 +35,7 @@ export class InstanceEntityService {
 | 
			
		|||
			isNotResponding: instance.isNotResponding,
 | 
			
		||||
			isSuspended: instance.isSuspended,
 | 
			
		||||
			isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host),
 | 
			
		||||
			isAllowed: meta.allowlistMode ? this.utilityService.isAllowedHost(meta.allowedHosts, instance.host) : null,
 | 
			
		||||
			softwareName: instance.softwareName,
 | 
			
		||||
			softwareVersion: instance.softwareVersion,
 | 
			
		||||
			openRegistrations: instance.openRegistrations,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,6 +71,16 @@ export class MiMeta {
 | 
			
		|||
	})
 | 
			
		||||
	public blockedHosts: string[];
 | 
			
		||||
 | 
			
		||||
	@Column('varchar', {
 | 
			
		||||
		length: 1024, array: true, default: '{}',
 | 
			
		||||
	})
 | 
			
		||||
	public allowedHosts: string[];
 | 
			
		||||
 | 
			
		||||
	@Column('boolean', {
 | 
			
		||||
		default: false,
 | 
			
		||||
	})
 | 
			
		||||
	public allowlistMode: boolean;
 | 
			
		||||
 | 
			
		||||
	@Column('varchar', {
 | 
			
		||||
		length: 1024, array: true, default: '{}',
 | 
			
		||||
	})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,6 +49,10 @@ export const packedFederationInstanceSchema = {
 | 
			
		|||
			type: 'boolean',
 | 
			
		||||
			optional: false, nullable: false,
 | 
			
		||||
		},
 | 
			
		||||
		isAllowed: {
 | 
			
		||||
			type: 'boolean',
 | 
			
		||||
			optional: false, nullable: true,
 | 
			
		||||
		},
 | 
			
		||||
		softwareName: {
 | 
			
		||||
			type: 'string',
 | 
			
		||||
			optional: false, nullable: true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,9 +53,13 @@ export class DeliverProcessorService {
 | 
			
		|||
 | 
			
		||||
		// ブロックしてたら中断
 | 
			
		||||
		const meta = await this.metaService.fetch();
 | 
			
		||||
		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.toPuny(host))) {
 | 
			
		||||
		const punyHost = this.utilityService.toPuny(host);
 | 
			
		||||
		if (this.utilityService.isBlockedHost(meta.blockedHosts, punyHost)) {
 | 
			
		||||
			return 'skip (blocked)';
 | 
			
		||||
		}
 | 
			
		||||
		if (meta.allowlistMode && !this.utilityService.isAllowedHost(meta.allowedHosts, punyHost)) {
 | 
			
		||||
    		return 'skip (not allowed)';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// isSuspendedなら中断
 | 
			
		||||
		let suspendedHosts = this.suspendedHostsCache.get();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,6 +67,10 @@ export class InboxProcessorService {
 | 
			
		|||
			return `Blocked request: ${host}`;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (meta.allowlistMode && !this.utilityService.isAllowedHost(meta.allowedHosts, host)) {
 | 
			
		||||
			return `Blocked request: ${host}`;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const keyIdLower = signature.keyId.toLowerCase();
 | 
			
		||||
		if (keyIdLower.startsWith('acct:')) {
 | 
			
		||||
			return `Old keyId is no longer supported. ${keyIdLower}`;
 | 
			
		||||
| 
						 | 
				
			
			@ -159,6 +163,9 @@ export class InboxProcessorService {
 | 
			
		|||
				if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) {
 | 
			
		||||
					throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
 | 
			
		||||
				}
 | 
			
		||||
				if (meta.allowlistMode && !this.utilityService.isAllowedHost(meta.allowedHosts, ldHost)) {
 | 
			
		||||
					throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				throw new Bull.UnrecoverableError(`skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`);
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -181,6 +181,11 @@ export class ActivityPubServerService {
 | 
			
		|||
			this.authlogger.warn(`${request.id} ${request.url} instance ${keyHost} is blocked: refuse`);
 | 
			
		||||
			reply.code(401);
 | 
			
		||||
			return true;
 | 
			
		||||
		} else if (meta.allowlistMode && !this.utilityService.isAllowedHost(meta.allowedHosts, keyHost)) {
 | 
			
		||||
			/* allowlist mode enabled and instance not on allowlist: refuse */
 | 
			
		||||
			this.authLogger.warn(`${request.id} ${request.url} instance ${keyHost} is not on allowlist: refuse`);
 | 
			
		||||
			reply.code(401);
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// do we know the signer already?
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -141,6 +141,17 @@ export const meta = {
 | 
			
		|||
					type: 'string',
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			allowedHosts: {
 | 
			
		||||
				type: 'array',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
				items: {
 | 
			
		||||
					type: 'string',
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			allowlistMode: {
 | 
			
		||||
				type: 'boolean',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
			},
 | 
			
		||||
			sensitiveWords: {
 | 
			
		||||
				type: 'array',
 | 
			
		||||
				optional: false, nullable: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -503,6 +514,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				pinnedUsers: instance.pinnedUsers,
 | 
			
		||||
				hiddenTags: instance.hiddenTags,
 | 
			
		||||
				blockedHosts: instance.blockedHosts,
 | 
			
		||||
				allowedHosts: instance.allowedHosts,
 | 
			
		||||
				allowlistMode: instance.allowlistMode,
 | 
			
		||||
				silencedHosts: instance.silencedHosts,
 | 
			
		||||
				sensitiveWords: instance.sensitiveWords,
 | 
			
		||||
				preservedUsernames: instance.preservedUsernames,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,12 @@ export const paramDef = {
 | 
			
		|||
				type: 'string',
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		allowedHosts: {
 | 
			
		||||
			type: 'array', nullable: true, items: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		allowlistMode: { type: 'boolean', nullable: true },
 | 
			
		||||
		sensitiveWords: {
 | 
			
		||||
			type: 'array', nullable: true, items: {
 | 
			
		||||
				type: 'string',
 | 
			
		||||
| 
						 | 
				
			
			@ -172,6 +178,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				set.blockedHosts = ps.blockedHosts.filter(Boolean).map(x => x.toLowerCase());
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (Array.isArray(ps.allowedHosts)) {
 | 
			
		||||
				set.allowedHosts = ps.allowedHosts.filter(Boolean).map(x => x.toLowerCase());
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (ps.allowlistMode !== undefined) {
 | 
			
		||||
				set.allowlistMode = ps.allowlistMode;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (Array.isArray(ps.sensitiveWords)) {
 | 
			
		||||
				set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -114,7 +114,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
	private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
 | 
			
		||||
	// ブロックしてたら中断
 | 
			
		||||
		const fetchedMeta = await this.metaService.fetch();
 | 
			
		||||
		if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, this.utilityService.extractDbHost(uri))) return null;
 | 
			
		||||
		const dbHost = this.utilityService.extractDbHost(uri);
 | 
			
		||||
		if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, dbHost)) return null;
 | 
			
		||||
		if (fetchedMeta.allowlistMode && !this.utilityService.isAllowedHost(fetchedMeta.allowedHosts, dbHost)) return null;
 | 
			
		||||
 | 
			
		||||
		let local = await this.mergePack(me, ...await Promise.all([
 | 
			
		||||
			this.apDbResolverService.getUserFromApId(uri),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,6 +34,7 @@ export const paramDef = {
 | 
			
		|||
	properties: {
 | 
			
		||||
		host: { type: 'string', nullable: true, description: 'Omit or use `null` to not filter by host.' },
 | 
			
		||||
		blocked: { type: 'boolean', nullable: true },
 | 
			
		||||
		allowed: { type: 'boolean', nullable: true },
 | 
			
		||||
		notResponding: { type: 'boolean', nullable: true },
 | 
			
		||||
		suspended: { type: 'boolean', nullable: true },
 | 
			
		||||
		silenced: { type: 'boolean', nullable: true },
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +108,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (typeof ps.allowed === 'boolean') {
 | 
			
		||||
				const meta = await this.metaService.fetch(true);
 | 
			
		||||
				if (ps.allowed) {
 | 
			
		||||
					query.andWhere(meta.allowedHosts.length === 0 ? '1=0' : 'instance.host IN (:...allows)', { allows: meta.allowedHosts });
 | 
			
		||||
				} else {
 | 
			
		||||
					query.andWhere(meta.allowedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...allows)', { allows: meta.allowedHosts });
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (typeof ps.notResponding === 'boolean') {
 | 
			
		||||
				if (ps.notResponding) {
 | 
			
		||||
					query.andWhere('instance.isNotResponding = TRUE');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -159,6 +159,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 | 
			
		|||
				if (note.user?.isSilenced && me && followings && note.userId !== me.id && !followings[note.userId]) return false;
 | 
			
		||||
				if (note.user?.isSuspended) return false;
 | 
			
		||||
				if (this.utilityService.isBlockedHost(meta.blockedHosts, note.userHost)) return false;
 | 
			
		||||
				if (meta.allowlistMode && !this.utilityService.isAllowedHost(meta.allowedHosts, note.userHost)) return false;
 | 
			
		||||
				if (this.utilityService.isSilencedHost(meta.silencedHosts, note.userHost)) return false;
 | 
			
		||||
				return true;
 | 
			
		||||
			});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
				<option value="suspended">{{ i18n.ts.suspended }}</option>
 | 
			
		||||
				<option value="silenced">{{ i18n.ts.silence }}</option>
 | 
			
		||||
				<option value="blocked">{{ i18n.ts.blocked }}</option>
 | 
			
		||||
				<option value="allowed">{{ i18n.ts.allowed }}</option>
 | 
			
		||||
				<option value="notResponding">{{ i18n.ts.notResponding }}</option>
 | 
			
		||||
			</MkSelect>
 | 
			
		||||
			<MkSelect v-model="sort">
 | 
			
		||||
| 
						 | 
				
			
			@ -77,6 +78,7 @@ const pagination = {
 | 
			
		|||
			state.value === 'publishing' ? { publishing: true } :
 | 
			
		||||
			state.value === 'suspended' ? { suspended: true } :
 | 
			
		||||
			state.value === 'blocked' ? { blocked: true } :
 | 
			
		||||
			state.value === 'allowed' ? { allowed: true } :
 | 
			
		||||
			state.value === 'silenced' ? { silenced: true } :
 | 
			
		||||
			state.value === 'notResponding' ? { notResponding: true } :
 | 
			
		||||
			state.value === 'nsfw' ? { nsfw: true } :
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
							<option value="nsfw">NSFW</option>
 | 
			
		||||
							<option value="suspended">{{ i18n.ts.suspended }}</option>
 | 
			
		||||
							<option value="blocked">{{ i18n.ts.blocked }}</option>
 | 
			
		||||
							<option value="allowed">{{ i18n.ts.allowed }}</option>
 | 
			
		||||
							<option value="silenced">{{ i18n.ts.silence }}</option>
 | 
			
		||||
							<option value="notResponding">{{ i18n.ts.notResponding }}</option>
 | 
			
		||||
						</MkSelect>
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +86,7 @@ const pagination = {
 | 
			
		|||
			state.value === 'publishing' ? { publishing: true } :
 | 
			
		||||
			state.value === 'suspended' ? { suspended: true } :
 | 
			
		||||
			state.value === 'blocked' ? { blocked: true } :
 | 
			
		||||
			state.value === 'allowed' ? { allowed: true } :
 | 
			
		||||
			state.value === 'silenced' ? { silenced: true } :
 | 
			
		||||
			state.value === 'notResponding' ? { notResponding: true } :
 | 
			
		||||
			state.value === 'nsfw' ? { nsfw: true } :
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +100,7 @@ function getStatus(instance) {
 | 
			
		|||
	if (instance.isSilenced) return 'Silenced';
 | 
			
		||||
	if (instance.isNotResponding) return 'Error';
 | 
			
		||||
	if (instance.isNSFW) return 'NSFW';
 | 
			
		||||
	if (instance.isAllowed) return 'Allowed';
 | 
			
		||||
	return 'Alive';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
				<span>{{ i18n.ts.silencedInstances }}</span>
 | 
			
		||||
				<template #caption>{{ i18n.ts.silencedInstancesDescription }}</template>
 | 
			
		||||
			</MkTextarea>
 | 
			
		||||
			<template v-else-if="tab === 'allow'">
 | 
			
		||||
				<MkSwitch v-model="allowlistMode">{{ i18n.ts.allowlistModeDescription }}</MkSwitch>
 | 
			
		||||
				<br />
 | 
			
		||||
				<MkTextarea v-model="allowedHosts" class="_formBlock">
 | 
			
		||||
					<span>{{ i18n.ts.allowedInstances }}</span>
 | 
			
		||||
					<template #caption>{{ i18n.ts.allowedInstancesDescription }}</template>
 | 
			
		||||
				</MkTextarea>
 | 
			
		||||
			</template>
 | 
			
		||||
			<MkButton primary @click="save"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
 | 
			
		||||
		</FormSuspense>
 | 
			
		||||
	</MkSpacer>
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +35,7 @@ import { ref, computed } from 'vue';
 | 
			
		|||
import XHeader from './_header_.vue';
 | 
			
		||||
import MkButton from '@/components/MkButton.vue';
 | 
			
		||||
import MkTextarea from '@/components/MkTextarea.vue';
 | 
			
		||||
import MkSwitch from '@/components/MkSwitch.vue';
 | 
			
		||||
import FormSuspense from '@/components/form/suspense.vue';
 | 
			
		||||
import * as os from '@/os.js';
 | 
			
		||||
import { fetchInstance } from '@/instance.js';
 | 
			
		||||
| 
						 | 
				
			
			@ -35,18 +44,24 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 | 
			
		|||
 | 
			
		||||
const blockedHosts = ref<string>('');
 | 
			
		||||
const silencedHosts = ref<string>('');
 | 
			
		||||
const tab = ref('block');
 | 
			
		||||
const allowedHosts = ref<string>('');
 | 
			
		||||
const allowlistMode = ref<boolean>(false);
 | 
			
		||||
const tab = ref('allow');
 | 
			
		||||
 | 
			
		||||
async function init() {
 | 
			
		||||
	const meta = await os.api('admin/meta');
 | 
			
		||||
	blockedHosts.value = meta.blockedHosts.join('\n');
 | 
			
		||||
	silencedHosts.value = meta.silencedHosts.join('\n');
 | 
			
		||||
	allowedHosts.value = meta.allowedHosts.join('\n');
 | 
			
		||||
	allowlistMode.value = meta.allowlistMode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function save() {
 | 
			
		||||
	os.apiWithDialog('admin/update-meta', {
 | 
			
		||||
		blockedHosts: blockedHosts.value.split('\n') || [],
 | 
			
		||||
		silencedHosts: silencedHosts.value.split('\n') || [],
 | 
			
		||||
		allowedHosts: allowedHosts.value.split('\n') || [],
 | 
			
		||||
		allowlistMode: allowlistMode.value || false,
 | 
			
		||||
 | 
			
		||||
	}).then(() => {
 | 
			
		||||
		fetchInstance();
 | 
			
		||||
| 
						 | 
				
			
			@ -63,6 +78,10 @@ const headerTabs = computed(() => [{
 | 
			
		|||
	key: 'silence',
 | 
			
		||||
	title: i18n.ts.silence,
 | 
			
		||||
	icon: 'ph-eye-closed ph-bold ph-lg',
 | 
			
		||||
}, {
 | 
			
		||||
	key: 'allow',
 | 
			
		||||
	title: i18n.ts.allow,
 | 
			
		||||
	icon: 'ph-prohibit ph-bold ph-lg',
 | 
			
		||||
}]);
 | 
			
		||||
 | 
			
		||||
definePageMetadata({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
			
		|||
				<div class="_gaps_s">
 | 
			
		||||
					<MkSwitch v-model="suspended" :disabled="!instance" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch>
 | 
			
		||||
					<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
 | 
			
		||||
					<MkSwitch v-model="isAllowed" :disabled="!meta || !instance" @update:modelValue="toggleAllow">{{ i18n.ts.allowThisInstance }}</MkSwitch>
 | 
			
		||||
					<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
 | 
			
		||||
					<MkSwitch v-model="isNSFW" :disabled="!instance" @update:modelValue="toggleNSFW">Mark as NSFW</MkSwitch>
 | 
			
		||||
					<MkButton @click="refreshMetadata"><i class="ph-arrows-counter-clockwise ph-bold ph-lg"></i> Refresh metadata</MkButton>
 | 
			
		||||
| 
						 | 
				
			
			@ -149,6 +150,7 @@ const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
 | 
			
		|||
const instance = ref<Misskey.entities.FederationInstance | null>(null);
 | 
			
		||||
const suspended = ref(false);
 | 
			
		||||
const isBlocked = ref(false);
 | 
			
		||||
const isAllowed = ref(false);
 | 
			
		||||
const isSilenced = ref(false);
 | 
			
		||||
const isNSFW = ref(false);
 | 
			
		||||
const faviconUrl = ref<string | null>(null);
 | 
			
		||||
| 
						 | 
				
			
			@ -173,6 +175,7 @@ async function fetch(): Promise<void> {
 | 
			
		|||
	});
 | 
			
		||||
	suspended.value = instance.value?.isSuspended ?? false;
 | 
			
		||||
	isBlocked.value = instance.value?.isBlocked ?? false;
 | 
			
		||||
	isAllowed.value = instance.value?.isAllowed ?? false;
 | 
			
		||||
	isSilenced.value = instance.value?.isSilenced ?? false;
 | 
			
		||||
	isNSFW.value = instance.value?.isNSFW ?? false;
 | 
			
		||||
	faviconUrl.value = getProxiedImageUrlNullable(instance.value?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.value?.iconUrl, 'preview');
 | 
			
		||||
| 
						 | 
				
			
			@ -187,6 +190,15 @@ async function toggleBlock(): Promise<void> {
 | 
			
		|||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function toggleAllow(): Promise<void> {
 | 
			
		||||
	if (!meta.value) throw new Error('No meta?');
 | 
			
		||||
	if (!instance.value) throw new Error('No instance?');
 | 
			
		||||
	const { host } = instance.value;
 | 
			
		||||
	await os.api('admin/update-meta', {
 | 
			
		||||
		allowedHosts: isAllowed.value ? meta.value.allowedHosts.concat([host]) : meta.value.allowedHosts.filter(x => x !== host),
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function toggleSilenced(): Promise<void> {
 | 
			
		||||
	if (!meta.value) throw new Error('No meta?');
 | 
			
		||||
	if (!instance.value) throw new Error('No instance?');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue