Allowlist implementation
This commit is contained in:
parent
d1e8653449
commit
9dcc7645ea
22 changed files with 158 additions and 5 deletions
|
@ -142,6 +142,7 @@ unmute: "Unmute"
|
|||
renoteMute: "Mute Boosts"
|
||||
renoteUnmute: "Unmute Boosts"
|
||||
block: "Block"
|
||||
allow: "Allow"
|
||||
unblock: "Unblock"
|
||||
markAsNSFW: "Mark all media from user as NSFW"
|
||||
suspend: "Suspend"
|
||||
|
@ -210,6 +211,7 @@ perHour: "Per Hour"
|
|||
perDay: "Per Day"
|
||||
stopActivityDelivery: "Stop sending activities"
|
||||
blockThisInstance: "Block this instance"
|
||||
allowThisInstance: "Allow this instance"
|
||||
silenceThisInstance: "Silence this instance"
|
||||
operations: "Operations"
|
||||
software: "Software"
|
||||
|
@ -230,6 +232,9 @@ clearCachedFiles: "Clear cache"
|
|||
clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?"
|
||||
blockedInstances: "Blocked Instances"
|
||||
blockedInstancesDescription: "List the hostnames of the instances you want to block separated by linebreaks. Listed instances will no longer be able to communicate with this instance."
|
||||
allowedInstances: "Allowed instances"
|
||||
allowedInstancesDescription: "List the hostnames of the instances you want to allow separated by linebreaks. Listed instances will be able to communicate with this instance if they are not already blocked."
|
||||
allowlistModeDescription: "Enable allowlist mode, which will reject federation with any instances not on a preapproved list."
|
||||
silencedInstances: "Silenced instances"
|
||||
silencedInstancesDescription: "List the hostnames of the instances that you want to silence. All accounts of the listed instances will be treated as silenced, can only make follow requests, and cannot mention local accounts if not followed. This will not affect blocked instances."
|
||||
muteAndBlock: "Mutes and Blocks"
|
||||
|
@ -249,6 +254,7 @@ noCustomEmojis: "There are no emoji"
|
|||
noJobs: "There are no jobs"
|
||||
federating: "Federating"
|
||||
blocked: "Blocked"
|
||||
allowed: "Allowed"
|
||||
suspended: "Suspended"
|
||||
all: "All"
|
||||
subscribing: "Subscribing"
|
||||
|
|
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');
|
||||
|
|
|
@ -157,6 +157,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…
Reference in a new issue