diff --git a/config/config.example.toml b/config/config.example.toml index e7ebc586..802573b4 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -288,6 +288,16 @@ reactions = [] banners = [] avatars = [] +# For bridge software, such as lysand-org/activitypub +# Bridges must be hosted separately from the main Lysand process +[federation.bridge] +enabled = false +# Only lysand-ap exists for now +software = "lysand-ap" +# WARNING: These IPs will have signature checks disabled. +# Only use the bridge software if you trust it. +allowed_ips = ["192.168.1.0/24"] + [instance] name = "Lysand" description = "A Lysand instance" diff --git a/packages/config-manager/config.type.ts b/packages/config-manager/config.type.ts index 493c3253..ee7f1718 100644 --- a/packages/config-manager/config.type.ts +++ b/packages/config-manager/config.type.ts @@ -430,6 +430,17 @@ export const configValidator = z.object({ banners: z.array(zUrl).default([]), avatars: z.array(zUrl).default([]), }), + bridge: z + .object({ + enabled: z.boolean().default(false), + software: z.enum(["lysand-ap"]).or(z.string()), + allowed_ips: z.array(z.string().trim()).default([]), + }) + .default({ + enabled: false, + software: "lysand-ap", + allowed_ips: [], + }), }) .default({ blocked: [], @@ -445,6 +456,11 @@ export const configValidator = z.object({ banners: [], avatars: [], }, + bridge: { + enabled: false, + software: "lysand-ap", + allowed_ips: [], + }, }), instance: z .object({ diff --git a/server/api/users/:uuid/inbox/index.ts b/server/api/users/:uuid/inbox/index.ts index 37e54abe..afb212f4 100644 --- a/server/api/users/:uuid/inbox/index.ts +++ b/server/api/users/:uuid/inbox/index.ts @@ -3,8 +3,10 @@ import { zValidator } from "@hono/zod-validator"; import { dualLogger } from "@loggers"; import { EntityValidator, SignatureValidator } from "@lysand-org/federation"; import { errorResponse, jsonResponse, response } from "@response"; +import type { SocketAddress } from "bun"; import { eq } from "drizzle-orm"; import type { Hono } from "hono"; +import { matches } from "ip-matching"; import { z } from "zod"; import { isValidationError } from "zod-validation-error"; import { resolveNote } from "~database/entities/Status"; @@ -14,6 +16,7 @@ import { } from "~database/entities/User"; import { db } from "~drizzle/db"; import { Notifications, Relationships } from "~drizzle/schema"; +import { config } from "~packages/config-manager"; import { User } from "~packages/database-interface/user"; import { LogLevel } from "~packages/log-manager"; @@ -57,11 +60,27 @@ export default (app: Hono) => return errorResponse("User not found", 404); } + // @ts-expect-error IP attribute is not in types + const request_ip = context.env?.ip as + | SocketAddress + | undefined + | null; + + let checkSignature = true; + + if (request_ip?.address && config.federation.bridge.enabled) { + for (const ip of config.federation.bridge.allowed_ips) { + if (matches(ip, request_ip?.address)) { + checkSignature = false; + break; + } + } + } + // Verify request signature // TODO: Check if instance is defederated // TODO: Reverse DNS lookup with Origin header - // biome-ignore lint/correctness/noConstantCondition: Temporary - if (true) { + if (checkSignature) { if (!signature) { return errorResponse("Missing Signature header", 400); }