From bc66ddc6331ec5628b043563e4d4fd87f20c0498 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:35:34 +0200 Subject: [PATCH] fix(server): canonicalize spaceInstanceOrigin before storing payload Empty-string (local) origin is now stored as the absolute home origin so relayed DM space-invite cards carry the correct value to remote recipients instead of resolving against the wrong instance. --- .../server/src/routes/dm.spaceInvite.test.ts | 36 +++++++++++++++++++ packages/server/src/routes/dm.ts | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/server/src/routes/dm.spaceInvite.test.ts b/packages/server/src/routes/dm.spaceInvite.test.ts index d694fa96..8bf4de3f 100644 --- a/packages/server/src/routes/dm.spaceInvite.test.ts +++ b/packages/server/src/routes/dm.spaceInvite.test.ts @@ -248,6 +248,11 @@ describe('POST /api/dm/space-invite', () => { expect(parsed.snapshot.avatarColor).toBe('mint'); expect(parsed.snapshot.description).toBe('desc'); expect(parsed.snapshot.instanceName).toBe('Backspace'); + + // Canonicalization: empty origin (local) must be stored as the absolute home + // origin so relayed messages carry the correct value to remote recipients. + expect(parsed.spaceInstanceOrigin).toBe('https://local.test'); + expect(parsed.spaceInstanceOrigin).not.toBe(''); }); it('reuses an existing 1-on-1 DM rather than creating a new one', async () => { @@ -386,4 +391,35 @@ describe('POST /api/dm/space-invite', () => { expect(fetchSpaceInviteSnapshot).toHaveBeenCalledWith('https://remote.example', 'abc'); expect(getLocalInviteSnapshot).not.toHaveBeenCalled(); }); + + it('preserves explicit remote spaceInstanceOrigin in stored payload', async () => { + (fetchSpaceInviteSnapshot as unknown as ReturnType).mockResolvedValueOnce({ + spaceId: 'S1', + spaceName: 'Remote', + description: null, + icon: null, + avatarColor: null, + memberCount: 5, + instanceName: 'OtherHost', + }); + + const res = await app.inject({ + method: 'POST', + url: '/api/dm/space-invite', + payload: { + target: { userId: 'bob' }, + spaceId: 'S1', + spaceInstanceOrigin: 'https://other.example', + inviteCode: 'abc', + }, + }); + expect(res.statusCode).toBe(200); + const body = JSON.parse(res.body) as { messageId: string }; + const stored = testDb.select() + .from(schema.dmMessages) + .where(eq(schema.dmMessages.id, body.messageId)) + .get(); + const parsed = JSON.parse(stored!.content!); + expect(parsed.spaceInstanceOrigin).toBe('https://other.example'); + }); }); diff --git a/packages/server/src/routes/dm.ts b/packages/server/src/routes/dm.ts index 6a8074aa..75ef571b 100644 --- a/packages/server/src/routes/dm.ts +++ b/packages/server/src/routes/dm.ts @@ -1757,7 +1757,7 @@ export async function dmRoutes(app: FastifyInstance): Promise { const payload: SpaceInviteSystemPayload = { event: 'space_invite', spaceId: body.spaceId, - spaceInstanceOrigin: body.spaceInstanceOrigin, + spaceInstanceOrigin: spaceOrigin, inviteCode: body.inviteCode, snapshot: { spaceName: snapshot.spaceName,