Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
a72e992
Don't link to staging
IanCal Dec 23, 2025
fd6228f
Simplify setup to require a single session room for all comms
IanCal Dec 23, 2025
d920670
Add realm url to indexing events
IanCal Dec 24, 2025
505c1e5
Merge branch 'main' into cs-9552-refactor-realm-event-sending-to-use-…
IanCal Jan 27, 2026
7bc0935
Remove username from publishing realm
IanCal Jan 27, 2026
7cd147b
Remove console logging
IanCal Jan 27, 2026
789ce5c
Merge branch 'main' into cs-9552-refactor-realm-event-sending-to-use-…
IanCal Feb 3, 2026
02f9b25
Pass through matrix client in tests setup too
IanCal Feb 4, 2026
45d3741
Merge branch 'main' into cs-9552-refactor-realm-event-sending-to-use-…
IanCal Feb 9, 2026
0d138a1
Update scheme and move migration to be the last
IanCal Feb 9, 2026
2854b71
Explicitly expect two files to be attached
IanCal Feb 9, 2026
cfbbae5
Don't auto-login to realm
IanCal Feb 9, 2026
10aaba6
Add the hard coded realm user to have read permissions
IanCal Feb 10, 2026
0740430
Session rooms are created automatically, do not create them in tests
IanCal Feb 10, 2026
01b7c9f
Update permissions in tests
IanCal Feb 10, 2026
6a2a5bd
Use the realm user id to get the session rooms for sending events
IanCal Feb 10, 2026
e811ac5
Info overwriting removed, correct realm url
IanCal Feb 10, 2026
cc60f93
Remove accidental test.only
IanCal Feb 11, 2026
036d8e3
Merge branch 'main' of github.com:cardstack/boxel
IanCal Feb 11, 2026
79409bc
Merge branch 'main' into cs-9552-refactor-realm-event-sending-to-use-…
IanCal Feb 11, 2026
1afb5ae
Fix syntax
IanCal Feb 11, 2026
5cb89d1
node test realm is the owner
IanCal Feb 11, 2026
801d6d1
fix syntax error
IanCal Feb 11, 2026
eea7e1f
syntax error
IanCal Feb 11, 2026
b3473df
JWTs need to have matching permissions, add test realm user as owner
IanCal Feb 11, 2026
f501fe0
Remove extra user, remove unnecessary filesystem content
IanCal Feb 11, 2026
86664a9
No longer creating a session room for creating a realm
IanCal Feb 11, 2026
f966aab
Linting and type fixes
IanCal Feb 11, 2026
a94a476
Improve where we get userId to not require login
IanCal Feb 11, 2026
c467c04
Use test realm info for id
IanCal Feb 11, 2026
a495154
Import fix
IanCal Feb 11, 2026
fb458c4
Restore import as it was earlier
IanCal Feb 11, 2026
a231047
Index events contain realm url
IanCal Feb 11, 2026
1974d26
Realm server is realm user
IanCal Feb 11, 2026
6a5402e
Lint
IanCal Feb 11, 2026
5ff6b80
Move the session room to the users table now that it is 1:1
IanCal Feb 16, 2026
5ada8be
Restore realm bot user for published realms
IanCal Feb 16, 2026
d676caa
Require realm urls in events, add new ones for file watching
IanCal Feb 16, 2026
ce8f3d3
Event test updates
IanCal Feb 16, 2026
1a4c9dc
Update session room work in tests
IanCal Feb 16, 2026
9c7ef14
Require that sessions are only created for users that exist and that …
IanCal Feb 16, 2026
ed163c7
Update schema
IanCal Feb 16, 2026
1fc0b8c
Can't have too many 0s in your migration filename
IanCal Feb 16, 2026
f0ed61b
Don't login without the user existing
IanCal Feb 16, 2026
822c36c
Make sure users exist at the correct points in tests
IanCal Feb 16, 2026
5418d39
Add user in CLI tests
IanCal Feb 16, 2026
c088fa7
Specify username
IanCal Feb 16, 2026
18a8d61
Linting
IanCal Feb 16, 2026
abfe929
Only create dm rooms for users that exist in the table already
IanCal Feb 17, 2026
289cf14
Linting
IanCal Feb 17, 2026
9086d4f
Only create session rooms when users exist for now
IanCal Feb 17, 2026
5b3d296
Don't remove the sessions table
IanCal Feb 19, 2026
daa217a
Delete packages/postgres/migrations/1770648743000_add-realm-user-id-t…
IanCal Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/base/matrix-event.gts
Original file line number Diff line number Diff line change
Expand Up @@ -401,23 +401,27 @@ export interface IncrementalIndexEventContent {
indexType: 'incremental';
invalidations: string[];
clientRequestId?: string | null;
realmURL: string;
}

interface FullIndexEventContent {
eventName: 'index';
indexType: 'full';
realmURL: string;
}

interface CopiedIndexEventContent {
eventName: 'index';
indexType: 'copy';
sourceRealmURL: string;
realmURL: string;
}

export interface IncrementalIndexInitiationContent {
eventName: 'index';
indexType: 'incremental-index-initiation';
updatedFile: string;
realmURL: string;
}

export type UpdateRealmEventContent =
Expand All @@ -428,18 +432,27 @@ export type UpdateRealmEventContent =
export interface FileAddedEventContent {
eventName: 'update';
added: string;
realmURL: string;
}

export interface FileUpdatedEventContent {
eventName: 'update';
updated: string;
realmURL: string;
}

export interface FileRemovedEventContent {
eventName: 'update';
removed: string;
realmURL: string;
}

// File watcher events don't include realmURL - it gets added by the Realm
export type FileWatcherEventContent =
| Omit<FileAddedEventContent, 'realmURL'>
| Omit<FileUpdatedEventContent, 'realmURL'>
| Omit<FileRemovedEventContent, 'realmURL'>;

export interface StopGeneratingEvent extends BaseMatrixEvent {
type: typeof APP_BOXEL_STOP_GENERATING_EVENT_TYPE;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/billing/billing-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export async function getUserById(
stripeCustomerId: results[0].stripe_customer_id,
stripeCustomerEmail: results[0].stripe_customer_email,
matrixRegistrationToken: results[0].matrix_registration_token,
sessionRoomId: results[0].session_room_id ?? null,
} as User;
}

Expand All @@ -159,6 +160,7 @@ export async function getUserByStripeId(
matrixUserId: results[0].matrix_user_id,
stripeCustomerId: results[0].stripe_customer_id,
matrixRegistrationToken: results[0].matrix_registration_token,
sessionRoomId: results[0].session_room_id ?? null,
} as User;
}

Expand All @@ -181,6 +183,7 @@ export async function getUserByMatrixUserId(
stripeCustomerId: results[0].stripe_customer_id,
stripeCustomerEmail: results[0].stripe_customer_email,
matrixRegistrationToken: results[0].matrix_registration_token,
sessionRoomId: results[0].session_room_id ?? null,
} as User;
}

Expand Down
25 changes: 7 additions & 18 deletions packages/host/app/services/matrix-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1895,28 +1895,17 @@ export default class MatrixService extends Service {
return;
}

let realmResourceForEvent = this.realm.realmForSessionRoomId(
event.room_id!,
);
if (!realmResourceForEvent) {
const content = event.content as RealmEventContent;
if (!content.realmURL) {
realmEventsLogger.debug(
'Ignoring realm event because no realm found',
'Ignoring realm event because no realm URL was provided',
event,
);
} else {
if (realmResourceForEvent.info?.realmUserId !== event.sender) {
realmEventsLogger.warn(
`Realm event sender ${event.sender} is not the realm user ${realmResourceForEvent.info?.realmUserId}`,
event,
);
}

(event.content as any).origin_server_ts = event.origin_server_ts;
this.messageService.relayRealmEvent(
realmResourceForEvent.url,
event.content as RealmEventContent,
);
return;
}

(content as any).origin_server_ts = event.origin_server_ts;
this.messageService.relayRealmEvent(content);
Comment on lines +1898 to +1908

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject realm events from non-session rooms

processDecryptedEventFromAuthRoom now relays any APP_BOXEL_REALM_EVENT_TYPE event as long as content.realmURL exists, but this path is reached for every non-AI room event. That means a participant in any unrelated DM/group room can send a crafted realm event and trigger invalidations for whatever realm URL the client is subscribed to. Previously this code first resolved event.room_id to a known session room before relaying, so removing that check introduces spoofable cross-room realm updates.

Useful? React with 👍 / 👎.

}
}

Expand Down
6 changes: 5 additions & 1 deletion packages/host/app/services/message-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ export default class MessageService extends Service {
}
}

relayRealmEvent(realmURL: string, event: RealmEventContent) {
relayRealmEvent(event: RealmEventContent) {
const realmURL = event.realmURL;
if (!realmURL) {
return;
}
this.listenerCallbacks.get(realmURL)?.forEach((cb) => {
cb(event);
});
Expand Down
8 changes: 5 additions & 3 deletions packages/host/tests/acceptance/query-fields-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { module, test } from 'qunit';

import type { Loader } from '@cardstack/runtime-common';
import { SupportedMimeType } from '@cardstack/runtime-common';
import { testRealmURLToUsername } from '@cardstack/runtime-common/helpers/const';
import { testRealmInfo } from '@cardstack/runtime-common/helpers/const';
import { APP_BOXEL_REALM_EVENT_TYPE } from '@cardstack/runtime-common/matrix-constants';

import type NetworkService from '@cardstack/host/services/network';
Expand Down Expand Up @@ -361,7 +361,7 @@ module(
'no query runs while hydrating server-provided results',
);

let realmMatrixUsername = testRealmURLToUsername(testRealmURL);
let realmMatrixUsername = testRealmInfo.realmUserId!;
let realmRoomId = mockMatrixUtils.getRoomIdForRealmAndUser(
testRealmURL,
'@testuser:localhost',
Expand All @@ -379,6 +379,7 @@ module(
eventName: 'index',
indexType: 'incremental',
invalidations: [`${testRealmURL}Person/new-match`],
realmURL: testRealmURL,
},
{ type: APP_BOXEL_REALM_EVENT_TYPE },
);
Expand Down Expand Up @@ -476,7 +477,7 @@ module(
(store as any).store.sweep(cardAPI);
await settled();

let realmMatrixUsername = testRealmURLToUsername(testRealmURL);
let realmMatrixUsername = testRealmInfo.realmUserId!;
let realmRoomId = mockMatrixUtils.getRoomIdForRealmAndUser(
testRealmURL,
'@testuser:localhost',
Expand All @@ -494,6 +495,7 @@ module(
eventName: 'index',
indexType: 'incremental',
invalidations: [`${testRealmURL}Person/new-match`],
realmURL: testRealmURL,
},
{ type: APP_BOXEL_REALM_EVENT_TYPE },
);
Expand Down
19 changes: 11 additions & 8 deletions packages/host/tests/helpers/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ import type {
} from '@cardstack/runtime-common/realm';

import type {
FileAddedEventContent,
FileUpdatedEventContent,
FileWatcherEventContent,
RealmEventContent,
UpdateRealmEventContent,
} from 'https://cardstack.com/base/matrix-event';

import { WebMessageStream, messageCloseHandler } from './stream';
Expand Down Expand Up @@ -66,7 +64,7 @@ export class TestRealmAdapter implements RealmAdapter {
#files: Dir = { kind: 'directory', contents: {} };
#lastModified: Map<string, number> = new Map();
#paths: RealmPaths;
#subscriber: ((message: UpdateRealmEventContent) => void) | undefined;
#subscriber: ((message: FileWatcherEventContent) => void) | undefined;
#loader: Loader | undefined; // Will be set in the realm's constructor - needed for openFile for shimming purposes
#ready = new Deferred<void>();
#potentialModulesAndInstances: { content: any; url: URL }[] = [];
Expand Down Expand Up @@ -128,8 +126,13 @@ export class TestRealmAdapter implements RealmAdapter {
rid.replace('test-session-room-realm-', '').startsWith(realmUrl),
);

const eventWithRealmURL: RealmEventContent = {
...event,
realmURL: realmUrl,
};

for (let roomId of targetRoomIds) {
simulateRemoteMessage(roomId, realmMatrixUsername, event, {
simulateRemoteMessage(roomId, realmMatrixUsername, eventWithRealmURL, {
type: APP_BOXEL_REALM_EVENT_TYPE,
});
}
Expand Down Expand Up @@ -308,7 +311,7 @@ export class TestRealmAdapter implements RealmAdapter {
);
}

let updateEvent: FileAddedEventContent | FileUpdatedEventContent;
let updateEvent: FileWatcherEventContent;

let lastModified = unixTime(Date.now());
this.#lastModified.set(this.#paths.fileURL(path).href, lastModified);
Expand Down Expand Up @@ -343,7 +346,7 @@ export class TestRealmAdapter implements RealmAdapter {
};
}

postUpdateEvent(data: UpdateRealmEventContent) {
postUpdateEvent(data: FileWatcherEventContent) {
this.#subscriber?.(data);
}

Expand Down Expand Up @@ -439,7 +442,7 @@ export class TestRealmAdapter implements RealmAdapter {
}

async subscribe(
cb: (message: UpdateRealmEventContent) => void,
cb: (message: FileWatcherEventContent) => void,
): Promise<void> {
this.#subscriber = cb;
}
Expand Down
7 changes: 1 addition & 6 deletions packages/host/tests/helpers/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
testHostModeRealmURL,
testRealmInfo,
testRealmURL,
testRealmURLToUsername,
Worker,
DEFAULT_CARD_SIZE_LIMIT_BYTES,
type DefinitionLookup,
Expand Down Expand Up @@ -788,15 +787,11 @@ async function setupTestRealm({
realm = new Realm({
url: realmURL,
adapter,
matrix: {
...baseTestMatrix,
username: testRealmURLToUsername(realmURL),
},
secretSeed: testRealmSecretSeed,
virtualNetwork,
dbAdapter,
queue,
realmServerMatrixClient: new MatrixClient({
matrixClient: new MatrixClient({
matrixURL: baseTestMatrix.url,
username: testRealmServerMatrixUsername,
seed: testRealmSecretSeed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getService } from '@universal-ember/test-support';
import { module, test } from 'qunit';

import { baseRealm } from '@cardstack/runtime-common';
import { testRealmURLToUsername } from '@cardstack/runtime-common/helpers/const';
import { testRealmInfo } from '@cardstack/runtime-common/helpers/const';
import type { Loader } from '@cardstack/runtime-common/loader';
import { APP_BOXEL_REALM_EVENT_TYPE } from '@cardstack/runtime-common/matrix-constants';

Expand Down Expand Up @@ -45,7 +45,7 @@ module('Integration | message service subscription', function (hooks) {
autostart: true,
});

let realmMatrixUsername = testRealmURLToUsername(testRealmURL);
let realmMatrixUsername = testRealmInfo.realmUserId!;

let realmRoomId = mockMatrixUtils.getRoomIdForRealmAndUser(
testRealmURL,
Expand Down Expand Up @@ -107,6 +107,7 @@ module('Integration | message service subscription', function (hooks) {
eventName: 'index',
indexType: 'incremental-index-initiation',
updatedFile: 'index.json',
realmURL: testRealmURL,
},
});

Expand Down
6 changes: 3 additions & 3 deletions packages/host/tests/integration/realm-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ module('Integration | realm', function (hooks) {
backgroundURL:
'https://i.postimg.cc/tgRHRV8C/pawel-czerwinski-h-Nrd99q5pe-I-unsplash.jpg',
iconURL: 'https://boxel-images.boxel.ai/icons/cardstack.png',
realmUserId: '@test_realm:localhost',
realmUserId: '@realm_server:localhost',
showAsCatalog: null,
visibility: 'public',
publishable: null,
Expand Down Expand Up @@ -3288,7 +3288,7 @@ module('Integration | realm', function (hooks) {
backgroundURL:
'https://i.postimg.cc/tgRHRV8C/pawel-czerwinski-h-Nrd99q5pe-I-unsplash.jpg',
iconURL: 'https://boxel-images.boxel.ai/icons/cardstack.png',
realmUserId: '@test_realm:localhost',
realmUserId: '@realm_server:localhost',
showAsCatalog: null,
visibility: 'public',
publishable: null,
Expand Down Expand Up @@ -3575,7 +3575,7 @@ posts/ignore-me.gts
name: 'Example Workspace',
backgroundURL: 'https://example-background-url.com',
iconURL: 'https://example-icon-url.com',
realmUserId: '@realm/test-realm-test:localhost',
realmUserId: '@realm_server:localhost',
showAsCatalog: null,
visibility: 'public',
publishable: null,
Expand Down
25 changes: 0 additions & 25 deletions packages/matrix/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,31 +120,6 @@ export async function setRealmRedirects(page: Page) {
}

export async function registerRealmUsers(synapse: SynapseInstance) {
await registerUser(
synapse,
'base_realm',
await realmPassword('base_realm', realmSecretSeed),
);
await registerUser(
synapse,
'experiments_realm',
await realmPassword('experiments_realm', realmSecretSeed),
);
await registerUser(
synapse,
'catalog_realm',
await realmPassword('catalog_realm', realmSecretSeed),
);
await registerUser(
synapse,
'skills_realm',
await realmPassword('skills_realm', realmSecretSeed),
);
await registerUser(
synapse,
'test_realm',
await realmPassword('test_realm', realmSecretSeed),
);
await registerUser(
synapse,
'node-test_realm',
Expand Down
2 changes: 1 addition & 1 deletion packages/matrix/tests/messages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ test.describe('Room messages', () => {
await expect(
page.locator(`[data-test-attached-card="${appURL}/hassan"]`),
).toHaveCount(1);
await expect(page.locator(`[data-test-attached-file]`)).toHaveCount(1);
await expect(page.locator(`[data-test-attached-file]`)).toHaveCount(2);
await expect(
page.locator(`[data-test-attached-file="${appURL}/person.gts"]`),
).toHaveCount(1);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
exports.up = (pgm) => {
pgm.addColumns('users', {
session_room_id: { type: 'varchar' },
});

pgm.sql(`
UPDATE users
SET session_room_id = sr.room_id
FROM session_rooms sr
WHERE users.matrix_user_id = sr.matrix_user_id
AND sr.realm_url = '__realm-server__'
`);

pgm.dropTable('session_rooms');
};

exports.down = (pgm) => {
pgm.dropColumns('users', ['session_room_id']);
};
Loading