Allowlist implementation
This commit is contained in:
parent
d1e8653449
commit
9dcc7645ea
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');
|
||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue