diff --git a/api/users/:uuid/inbox/index.ts b/api/users/:uuid/inbox/index.ts index 6c2b520a..5fba0c54 100644 --- a/api/users/:uuid/inbox/index.ts +++ b/api/users/:uuid/inbox/index.ts @@ -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 "))) { diff --git a/classes/inbox/processor.ts b/classes/inbox/processor.ts index 1d9f6578..f33203b4 100644 --- a/classes/inbox/processor.ts +++ b/classes/inbox/processor.ts @@ -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} - Whether the signature is valid. */ private async isSignatureValid(): Promise { - 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