refactor(federation): ♻️ Allow ActivityPub bridge requests to omit all signature headers, including x-signed-by

This commit is contained in:
Jesse Wierzbinski 2024-11-24 16:40:23 +01:00
parent 80b5184d6a
commit b55237cdc8
No known key found for this signature in database
2 changed files with 39 additions and 6 deletions

View file

@ -25,7 +25,11 @@ export const schemas = {
header: z.object({
"x-signature": z.string().optional(),
"x-nonce": z.string().optional(),
"x-signed-by": z.string().url().or(z.string().startsWith("instance ")),
"x-signed-by": z
.string()
.url()
.or(z.string().startsWith("instance "))
.optional(),
authorization: z.string().optional(),
}),
body: z.any(),
@ -111,6 +115,32 @@ export default apiRoute((app) =>
const logger = getLogger(["federation", "inbox"]);
const body: Entity = await context.req.valid("json");
if (authorization) {
const processor = new InboxProcessor(
context,
body,
null,
{
signature,
nonce,
authorization,
},
logger,
);
return await processor.process();
}
// If not potentially from bridge, check for required headers
if (!(signature && nonce && signedBy)) {
return context.json(
{
error: "Missing required headers: x-signature, x-nonce, or x-signed-by",
},
400,
);
}
const sender = await User.resolve(signedBy);
if (!(sender || signedBy.startsWith("instance "))) {

View file

@ -70,7 +70,7 @@ export class InboxProcessor {
*
* @param context Hono request context.
* @param body Entity JSON body.
* @param senderInstance Sender of the request's instance (from X-Signed-By header).
* @param senderInstance Sender of the request's instance (from X-Signed-By header). Null if request is from a bridge.
* @param headers Various request headers.
* @param logger LogTape logger instance.
* @param requestIp Request IP address. Grabs it from the Hono context if not provided.
@ -78,7 +78,7 @@ export class InboxProcessor {
public constructor(
private context: Context,
private body: Entity,
private senderInstance: Instance,
private senderInstance: Instance | null,
private headers: {
signature?: string;
nonce?: string;
@ -94,9 +94,9 @@ export class InboxProcessor {
* @returns {Promise<boolean>} - Whether the signature is valid.
*/
private async isSignatureValid(): Promise<boolean> {
if (!this.senderInstance.data.publicKey?.key) {
if (!this.senderInstance?.data.publicKey?.key) {
throw new Error(
`Instance ${this.senderInstance.data.baseUrl} has no public key stored in database`,
`Instance ${this.senderInstance?.data.baseUrl} has no public key stored in database`,
);
}
@ -196,7 +196,10 @@ export class InboxProcessor {
public async process(): Promise<
(Response & TypedResponse<{ error: string }, 500, "json">) | Response
> {
if (isDefederated(this.senderInstance.data.baseUrl)) {
if (
this.senderInstance &&
isDefederated(this.senderInstance.data.baseUrl)
) {
// Return 201 to avoid
// 1. Leaking defederated instance information
// 2. Preventing the sender from thinking the message was not delivered and retrying