refactor (backend): port should-block-instance to backend-rs

This commit is contained in:
naskya 2024-04-22 08:31:28 +09:00
parent cbd15fb2ca
commit 0c1e7cdd72
No known key found for this signature in database
GPG key ID: 712D413B3A9FED5C
17 changed files with 117 additions and 102 deletions

View file

@ -193,6 +193,21 @@ export interface Acct {
}
export function stringToAcct(acct: string): Acct
export function acctToString(acct: Acct): string
/**
* @param host punycoded instance host
* @returns whether the given host should be blocked
*/
export function isBlockedServer(host: string): Promise<boolean>
/**
* @param host punycoded instance host
* @returns whether the given host should be limited
*/
export function isSilencedServer(host: string): Promise<boolean>
/**
* @param host punycoded instance host
* @returns whether the given host is allowlisted (this is always true if private mode is disabled)
*/
export function isAllowedServer(host: string): Promise<boolean>
/** TODO: handle name collisions better */
export interface NoteLikeForCheckWordMute {
fileIds: Array<string>
@ -557,7 +572,6 @@ export interface Meta {
recaptchaSecretKey: string | null
localDriveCapacityMb: number
remoteDriveCapacityMb: number
antennaLimit: number
summalyProxy: string | null
enableEmail: boolean
email: string | null
@ -620,6 +634,7 @@ export interface Meta {
donationLink: string | null
moreUrls: Json
markLocalFilesNsfwByDefault: boolean
antennaLimit: number
}
export interface Migrations {
id: number

View file

@ -310,12 +310,15 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { loadEnv, loadConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, addNoteToAntenna, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding
const { loadEnv, loadConfig, stringToAcct, acctToString, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, addNoteToAntenna, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding
module.exports.loadEnv = loadEnv
module.exports.loadConfig = loadConfig
module.exports.stringToAcct = stringToAcct
module.exports.acctToString = acctToString
module.exports.isBlockedServer = isBlockedServer
module.exports.isSilencedServer = isSilencedServer
module.exports.isAllowedServer = isAllowedServer
module.exports.checkWordMute = checkWordMute
module.exports.getFullApAccount = getFullApAccount
module.exports.isSelfHost = isSelfHost

View file

@ -0,0 +1,49 @@
use crate::misc::meta::fetch_meta;
use sea_orm::DbErr;
/**
* @param host punycoded instance host
* @returns whether the given host should be blocked
*/
#[crate::export]
pub async fn is_blocked_server(host: &str) -> Result<bool, DbErr> {
Ok(fetch_meta(true)
.await?
.blocked_hosts
.iter()
.any(|blocked_host| {
host == blocked_host || host.ends_with(format!(".{}", blocked_host).as_str())
}))
}
/**
* @param host punycoded instance host
* @returns whether the given host should be limited
*/
#[crate::export]
pub async fn is_silenced_server(host: &str) -> Result<bool, DbErr> {
Ok(fetch_meta(true)
.await?
.silenced_hosts
.iter()
.any(|silenced_host| {
host == silenced_host || host.ends_with(format!(".{}", silenced_host).as_str())
}))
}
/**
* @param host punycoded instance host
* @returns whether the given host is allowlisted (this is always true if private mode is disabled)
*/
#[crate::export]
pub async fn is_allowed_server(host: &str) -> Result<bool, DbErr> {
let meta = fetch_meta(true).await?;
if !meta.private_mode.unwrap_or(false) {
return Ok(true);
}
if let Some(allowed_hosts) = meta.allowed_hosts {
return Ok(allowed_hosts.contains(&host.to_string()));
}
Ok(false)
}

View file

@ -1,4 +1,5 @@
pub mod acct;
pub mod check_server_block;
pub mod check_word_mute;
pub mod convert_host;
pub mod emoji;

View file

@ -1,38 +0,0 @@
import { fetchMeta } from "backend-rs";
import type { Instance } from "@/models/entities/instance.js";
import type { Meta } from "@/models/entities/meta.js";
/**
* Returns whether a specific host (punycoded) should be blocked.
*
* @param host punycoded instance host
* @param meta a resolved Meta table
* @returns whether the given host should be blocked
*/
export async function shouldBlockInstance(
host: Instance["host"],
meta?: Meta,
): Promise<boolean> {
const { blockedHosts } = meta ?? (await fetchMeta(true));
return blockedHosts.some(
(blockedHost) => host === blockedHost || host.endsWith(`.${blockedHost}`),
);
}
/**
* Returns whether a specific host (punycoded) should be limited.
*
* @param host punycoded instance host
* @param meta a resolved Meta table
* @returns whether the given host should be limited
*/
export async function shouldSilenceInstance(
host: Instance["host"],
meta?: Meta,
): Promise<boolean> {
const { silencedHosts } = meta ?? (await fetchMeta(true));
return silencedHosts.some(
(silencedHost) =>
host === silencedHost || host.endsWith(`.${silencedHost}`),
);
}

View file

@ -1,9 +1,8 @@
import { Brackets } from "typeorm";
import { fetchMeta } from "backend-rs";
import { isBlockedServer } from "backend-rs";
import { Instances } from "@/models/index.js";
import type { Instance } from "@/models/entities/instance.js";
import { DAY } from "@/const.js";
import { shouldBlockInstance } from "./should-block-instance.js";
// Threshold from last contact after which an instance will be considered
// "dead" and should no longer get activities delivered to it.
@ -19,16 +18,16 @@ export async function skippedInstances(
hosts: Instance["host"][],
): Promise<Instance["host"][]> {
// first check for blocked instances since that info may already be in memory
const meta = await fetchMeta(true);
const shouldSkip = await Promise.all(
hosts.map((host) => shouldBlockInstance(host, meta)),
hosts.map((host) => isBlockedServer(host)),
);
const skipped = hosts.filter((_, i) => shouldSkip[i]);
// if possible return early and skip accessing the database
if (skipped.length === hosts.length) return hosts;
const deadTime = new Date(Date.now() - deadThreshold);
// FIXME: Use or remove this
// const deadTime = new Date(Date.now() - deadThreshold);
return skipped.concat(
await Instances.createQueryBuilder("instance")

View file

@ -1,10 +1,7 @@
import { db } from "@/db/postgre.js";
import { Instance } from "@/models/entities/instance.js";
import type { Packed } from "@/misc/schema.js";
import {
shouldBlockInstance,
shouldSilenceInstance,
} from "@/misc/should-block-instance.js";
import { isBlockedServer, isSilencedServer } from "backend-rs";
export const InstanceRepository = db.getRepository(Instance).extend({
async pack(instance: Instance): Promise<Packed<"FederationInstance">> {
@ -22,8 +19,8 @@ export const InstanceRepository = db.getRepository(Instance).extend({
lastCommunicatedAt: instance.lastCommunicatedAt.toISOString(),
isNotResponding: instance.isNotResponding,
isSuspended: instance.isSuspended,
isBlocked: await shouldBlockInstance(instance.host),
isSilenced: await shouldSilenceInstance(instance.host),
isBlocked: await isBlockedServer(instance.host),
isSilenced: await isSilencedServer(instance.host),
softwareName: instance.softwareName,
softwareVersion: instance.softwareVersion,
openRegistrations: instance.openRegistrations,

View file

@ -5,7 +5,7 @@ import perform from "@/remote/activitypub/perform.js";
import Logger from "@/services/logger.js";
import { registerOrFetchInstanceDoc } from "@/services/register-or-fetch-instance-doc.js";
import { Instances } from "@/models/index.js";
import { fetchMeta } from "backend-rs";
import { isAllowedServer, isBlockedServer } from "backend-rs";
import { toPuny, extractHost } from "backend-rs";
import { getApId } from "@/remote/activitypub/type.js";
import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js";
@ -16,7 +16,6 @@ import { LdSignature } from "@/remote/activitypub/misc/ld-signature.js";
import { StatusError } from "@/misc/fetch.js";
import type { CacheableRemoteUser } from "@/models/entities/user.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import { verifySignature } from "@/remote/activitypub/check-fetch.js";
import { inspect } from "node:util";
@ -41,13 +40,12 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
const host = toPuny(new URL(signature.keyId).hostname);
// interrupt if blocked
const meta = await fetchMeta(true);
if (await shouldBlockInstance(host, meta)) {
if (await isBlockedServer(host)) {
return `Blocked request: ${host}`;
}
// only whitelisted instances in private mode
if (meta.privateMode && !meta.allowedHosts.includes(host)) {
if (!isAllowedServer(host)) {
return `Blocked request: ${host}`;
}
@ -158,7 +156,7 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
// ブロックしてたら中断
const ldHost = extractHost(authUser.user.uri);
if (await shouldBlockInstance(ldHost, meta)) {
if (await isBlockedServer(ldHost)) {
return `Blocked request: ${ldHost}`;
}
} else {

View file

@ -1,11 +1,10 @@
import { URL } from "url";
import httpSignature, { IParsedSignature } from "@peertube/http-signature";
import httpSignature, { type IParsedSignature } from "@peertube/http-signature";
import { config } from "@/config.js";
import { fetchMeta } from "backend-rs";
import { fetchMeta, isAllowedServer, isBlockedServer } from "backend-rs";
import { toPuny } from "backend-rs";
import DbResolver from "@/remote/activitypub/db-resolver.js";
import { getApId } from "@/remote/activitypub/type.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import type { IncomingMessage } from "http";
import type { CacheableRemoteUser } from "@/models/entities/user.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js";
@ -44,15 +43,11 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
const keyId = new URL(signature.keyId);
const host = toPuny(keyId.hostname);
if (await shouldBlockInstance(host, meta)) {
if (await isBlockedServer(host)) {
return 403;
}
if (
meta.privateMode &&
host !== config.host &&
!meta.allowedHosts.includes(host)
) {
if (host !== config.host && !isAllowedServer(host)) {
return 403;
}

View file

@ -10,7 +10,7 @@ import { getApLock } from "@/misc/app-lock.js";
import { parseAudience } from "../../audience.js";
import { StatusError } from "@/misc/fetch.js";
import { Notes } from "@/models/index.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import { isBlockedServer } from "backend-rs";
import { inspect } from "node:util";
const logger = apLogger;
@ -31,7 +31,7 @@ export default async function (
}
// Interrupt if you block the announcement destination
if (await shouldBlockInstance(extractHost(uri))) return;
if (await isBlockedServer(extractHost(uri))) return;
const lock = await getApLock(uri);

View file

@ -38,8 +38,7 @@ import block from "./block/index.js";
import flag from "./flag/index.js";
import move from "./move/index.js";
import type { IObject, IActivity } from "../type.js";
import { extractHost } from "backend-rs";
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import { extractHost, isBlockedServer } from "backend-rs";
import { inspect } from "node:util";
export async function performActivity(
@ -71,7 +70,7 @@ async function performOneActivity(
if (typeof activity.id !== "undefined") {
const host = extractHost(getApId(activity));
if (await shouldBlockInstance(host)) return;
if (await isBlockedServer(host)) return;
}
if (isCreate(activity)) {

View file

@ -12,7 +12,7 @@ import { unique, toArray, toSingle } from "@/prelude/array.js";
import { extractPollFromQuestion } from "./question.js";
import vote from "@/services/note/polls/vote.js";
import { apLogger } from "../logger.js";
import { DriveFile } from "@/models/entities/drive-file.js";
import type { DriveFile } from "@/models/entities/drive-file.js";
import { extractHost, isSameOrigin, toPuny } from "backend-rs";
import {
Emojis,
@ -33,14 +33,13 @@ import {
getApType,
} from "../type.js";
import type { Emoji } from "@/models/entities/emoji.js";
import { genId } from "backend-rs";
import { genId, isBlockedServer } from "backend-rs";
import { getApLock } from "@/misc/app-lock.js";
import { createMessage } from "@/services/messages/create.js";
import { parseAudience } from "../audience.js";
import { extractApMentions } from "./mention.js";
import DbResolver from "../db-resolver.js";
import { StatusError } from "@/misc/fetch.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import { publishNoteStream } from "@/services/stream.js";
import { extractHashtags } from "@/misc/extract-hashtags.js";
import { UserProfiles } from "@/models/index.js";
@ -421,7 +420,7 @@ export async function resolveNote(
if (uri == null) throw new Error("missing uri");
// Abort if origin host is blocked
if (await shouldBlockInstance(extractHost(uri)))
if (await isBlockedServer(extractHost(uri)))
throw new StatusError(
"host blocked",
451,

View file

@ -1,8 +1,12 @@
import { config } from "@/config.js";
import type { ILocalUser } from "@/models/entities/user.js";
import { getInstanceActor } from "@/services/instance-actor.js";
import { fetchMeta } from "backend-rs";
import { extractHost, isSelfHost } from "backend-rs";
import {
extractHost,
isAllowedServer,
isBlockedServer,
isSelfHost,
} from "backend-rs";
import { apGet } from "./request.js";
import type { IObject, ICollection, IOrderedCollection } from "./type.js";
import { isCollectionOrOrderedCollection, getApId } from "./type.js";
@ -21,7 +25,6 @@ import renderQuestion from "@/remote/activitypub/renderer/question.js";
import renderCreate from "@/remote/activitypub/renderer/create.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import renderFollow from "@/remote/activitypub/renderer/follow.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import { apLogger } from "@/remote/activitypub/logger.js";
import { IsNull, Not } from "typeorm";
@ -69,7 +72,7 @@ export default class Resolver {
apLogger.debug("Object to resolve is not a string");
if (typeof value.id !== "undefined") {
const host = extractHost(getApId(value));
if (await shouldBlockInstance(host)) {
if (await isBlockedServer(host)) {
throw new Error("instance is blocked");
}
}
@ -100,17 +103,12 @@ export default class Resolver {
return await this.resolveLocal(value);
}
const meta = await fetchMeta(true);
if (await shouldBlockInstance(host, meta)) {
throw new Error("Instance is blocked");
if (await isBlockedServer(host)) {
throw new Error("This instance is blocked");
}
if (
meta.privateMode &&
config.host !== host &&
!meta.allowedHosts.includes(host)
) {
throw new Error("Instance is not allowed");
if (config.host !== host && !isAllowedServer(host)) {
throw new Error("This instance is not allowed");
}
if (!this.user) {

View file

@ -4,14 +4,13 @@ import { createNote } from "@/remote/activitypub/models/note.js";
import DbResolver from "@/remote/activitypub/db-resolver.js";
import Resolver from "@/remote/activitypub/resolver.js";
import { ApiError } from "@/server/api/error.js";
import { extractHost } from "backend-rs";
import { extractHost, isBlockedServer } from "backend-rs";
import { Users, Notes } from "@/models/index.js";
import type { Note } from "@/models/entities/note.js";
import type { CacheableLocalUser, User } from "@/models/entities/user.js";
import { isActor, isPost, getApId } from "@/remote/activitypub/type.js";
import type { SchemaType } from "@/misc/schema.js";
import { MINUTE } from "@/const.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import { updateQuestion } from "@/remote/activitypub/models/question.js";
import { populatePoll } from "@/models/repositories/note.js";
import { redisClient } from "@/db/redis.js";
@ -101,7 +100,7 @@ async function fetchAny(
me: CacheableLocalUser | null | undefined,
): Promise<SchemaType<(typeof meta)["res"]> | null> {
// Wait if blocked.
if (await shouldBlockInstance(extractHost(uri))) return null;
if (await isBlockedServer(extractHost(uri))) return null;
const dbResolver = new DbResolver();

View file

@ -8,11 +8,10 @@ import {
Users,
Followings,
} from "@/models/index.js";
import { genId } from "backend-rs";
import { genId, isSilencedServer } from "backend-rs";
import type { User } from "@/models/entities/user.js";
import type { Notification } from "@/models/entities/notification.js";
import { sendEmailNotification } from "./send-email-notification.js";
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
export async function createNotification(
notifieeId: User["id"],
@ -35,8 +34,8 @@ export async function createNotification(
if (
(notifier.isSilenced ||
(Users.isRemoteUser(notifier) &&
(await shouldSilenceInstance(notifier.host)))) &&
!(await Followings.exist({
(await isSilencedServer(notifier.host)))) &&
!(await Followings.exists({
where: { followerId: notifieeId, followeeId: data.notifierId },
}))
)

View file

@ -17,13 +17,12 @@ import {
Instances,
UserProfiles,
} from "@/models/index.js";
import { genId } from "backend-rs";
import { genId, isSilencedServer } from "backend-rs";
import { createNotification } from "@/services/create-notification.js";
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
import type { Packed } from "@/misc/schema.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { webhookDeliver } from "@/queue/index.js";
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
const logger = new Logger("following/create");
@ -231,7 +230,7 @@ export default async function (
(Users.isLocalUser(follower) && Users.isRemoteUser(followee)) ||
(Users.isRemoteUser(follower) &&
Users.isLocalUser(followee) &&
(await shouldSilenceInstance(follower.host)))
(await isSilencedServer(follower.host)))
) {
let autoAccept = false;

View file

@ -37,14 +37,18 @@ import type { DriveFile } from "@/models/entities/drive-file.js";
import type { App } from "@/models/entities/app.js";
import { Not, In } from "typeorm";
import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js";
import { genId } from "backend-rs";
import { activeUsersChart } from "@/services/chart/index.js";
import type { IPoll } from "@/models/entities/poll.js";
import { Poll } from "@/models/entities/poll.js";
import { createNotification } from "@/services/create-notification.js";
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
import { checkHitAntenna } from "@/misc/check-hit-antenna.js";
import { addNoteToAntenna, checkWordMute } from "backend-rs";
import {
addNoteToAntenna,
checkWordMute,
genId,
isSilencedServer,
} from "backend-rs";
import { countSameRenotes } from "@/misc/count-same-renotes.js";
import { deliverToRelays, getCachedRelays } from "../relay.js";
import type { Channel } from "@/models/entities/channel.js";
@ -56,7 +60,6 @@ import { Cache } from "@/misc/cache.js";
import type { UserProfile } from "@/models/entities/user-profile.js";
import { db } from "@/db/postgre.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
import { redisClient } from "@/db/redis.js";
import { Mutex } from "redis-semaphore";
import { langmap } from "@/misc/langmap.js";
@ -225,7 +228,7 @@ export default async (
if (
data.visibility === "public" &&
Users.isRemoteUser(user) &&
(await shouldSilenceInstance(user.host))
(await isSilencedServer(user.host))
) {
data.visibility = "home";
}