Skip to content

Commit d5d821d

Browse files
committed
fix(webapp): chat access is organization-scoped everywhere
Chats belong to (organization, user); several queries enforced only the user, so a user's own chat from another org could be opened, renamed, pinned or appended to through a different org's route.
1 parent 93c4a02 commit d5d821d

2 files changed

Lines changed: 66 additions & 17 deletions

File tree

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
153153
const chatId = searchParams.get("chatId");
154154
if (chatId) {
155155
const [messages, session] = await Promise.all([
156-
getChatMessages(dashboardAgentDb, { chatId, userId }),
157-
getSession(dashboardAgentDb, { chatId, userId }),
156+
getChatMessages(dashboardAgentDb, { chatId, userId, organizationId: project.organizationId }),
157+
getSession(dashboardAgentDb, { chatId, userId, organizationId: project.organizationId }),
158158
]);
159159
return json({ messages: messages ?? [], session });
160160
}
@@ -483,7 +483,12 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
483483
],
484484
};
485485

486-
await appendChatMessage(dashboardAgentDb, { chatId: targetChatId, userId, message });
486+
await appendChatMessage(dashboardAgentDb, {
487+
chatId: targetChatId,
488+
userId,
489+
organizationId: project.organizationId,
490+
message,
491+
});
487492

488493
return json({
489494
chatId: targetChatId,
@@ -554,14 +559,20 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
554559

555560
case "rename": {
556561
if (!parsed.data.title) return json({ error: "title is required" }, { status: 400 });
557-
await renameChat(dashboardAgentDb, { chatId, userId, title: parsed.data.title });
562+
await renameChat(dashboardAgentDb, {
563+
chatId,
564+
userId,
565+
organizationId: project.organizationId,
566+
title: parsed.data.title,
567+
});
558568
return json({ ok: true });
559569
}
560570

561571
case "pin": {
562572
await setChatPinned(dashboardAgentDb, {
563573
chatId,
564574
userId,
575+
organizationId: project.organizationId,
565576
pinned: parsed.data.pinned === "true",
566577
});
567578
return json({ ok: true });
@@ -570,7 +581,11 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
570581
// The user has this chat in front of them, so its watch wakes are seen. The
571582
// update is owner-scoped, so a chatId the caller doesn't own is a no-op.
572583
case "read": {
573-
await markChatRead(dashboardAgentDb, { chatId, userId });
584+
await markChatRead(dashboardAgentDb, {
585+
chatId,
586+
userId,
587+
organizationId: project.organizationId,
588+
});
574589
return json({ ok: true });
575590
}
576591

internal-packages/dashboard-agent-db/src/queries.ts

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,18 @@ export async function listChats(
8585
*/
8686
export async function getChatMessages(
8787
db: DashboardAgentDb,
88-
params: { chatId: string; userId: string }
88+
params: { chatId: string; userId: string; organizationId: string }
8989
): Promise<unknown[] | null> {
9090
const rows = await db
9191
.select({ messages: chats.messages })
9292
.from(chats)
9393
.where(
94-
and(eq(chats.id, params.chatId), eq(chats.userId, params.userId), isNull(chats.deletedAt))
94+
and(
95+
eq(chats.id, params.chatId),
96+
eq(chats.userId, params.userId),
97+
eq(chats.organizationId, params.organizationId),
98+
isNull(chats.deletedAt)
99+
)
95100
)
96101
.limit(1);
97102
return rows[0]?.messages ?? null;
@@ -139,7 +144,7 @@ export async function countUserMessages(
139144
*/
140145
export async function getSession(
141146
db: DashboardAgentDb,
142-
params: { chatId: string; userId: string }
147+
params: { chatId: string; userId: string; organizationId: string }
143148
): Promise<ChatSession | null> {
144149
const rows = await db
145150
.select({
@@ -151,7 +156,13 @@ export async function getSession(
151156
})
152157
.from(chatSessions)
153158
.innerJoin(chats, eq(chats.id, chatSessions.chatId))
154-
.where(and(eq(chatSessions.chatId, params.chatId), eq(chats.userId, params.userId)))
159+
.where(
160+
and(
161+
eq(chatSessions.chatId, params.chatId),
162+
eq(chats.userId, params.userId),
163+
eq(chats.organizationId, params.organizationId)
164+
)
165+
)
155166
.limit(1);
156167
return rows[0] ?? null;
157168
}
@@ -213,12 +224,18 @@ export const ensureChat = createChat;
213224
/** #5 Rename. */
214225
export async function renameChat(
215226
db: DashboardAgentDb,
216-
params: { chatId: string; userId: string; title: string }
227+
params: { chatId: string; userId: string; organizationId: string; title: string }
217228
): Promise<void> {
218229
await db
219230
.update(chats)
220231
.set({ title: params.title, updatedAt: sql`now()` })
221-
.where(and(eq(chats.id, params.chatId), eq(chats.userId, params.userId)));
232+
.where(
233+
and(
234+
eq(chats.id, params.chatId),
235+
eq(chats.userId, params.userId),
236+
eq(chats.organizationId, params.organizationId)
237+
)
238+
);
222239
}
223240

224241
/**
@@ -241,12 +258,18 @@ export async function setChatTitleIfDefault(
241258
/** #5 Pin / unpin. */
242259
export async function setChatPinned(
243260
db: DashboardAgentDb,
244-
params: { chatId: string; userId: string; pinned: boolean }
261+
params: { chatId: string; userId: string; organizationId: string; pinned: boolean }
245262
): Promise<void> {
246263
await db
247264
.update(chats)
248265
.set({ pinnedAt: params.pinned ? sql`now()` : null, updatedAt: sql`now()` })
249-
.where(and(eq(chats.id, params.chatId), eq(chats.userId, params.userId)));
266+
.where(
267+
and(
268+
eq(chats.id, params.chatId),
269+
eq(chats.userId, params.userId),
270+
eq(chats.organizationId, params.organizationId)
271+
)
272+
);
250273
}
251274

252275
/**
@@ -255,12 +278,18 @@ export async function setChatPinned(
255278
*/
256279
export async function markChatRead(
257280
db: DashboardAgentDb,
258-
params: { chatId: string; userId: string; at?: Date }
281+
params: { chatId: string; userId: string; organizationId: string; at?: Date }
259282
): Promise<void> {
260283
await db
261284
.update(chats)
262285
.set({ lastReadAt: params.at ?? sql`now()` })
263-
.where(and(eq(chats.id, params.chatId), eq(chats.userId, params.userId)));
286+
.where(
287+
and(
288+
eq(chats.id, params.chatId),
289+
eq(chats.userId, params.userId),
290+
eq(chats.organizationId, params.organizationId)
291+
)
292+
);
264293
}
265294

266295
/**
@@ -344,7 +373,7 @@ export async function persistMessages(
344373
*/
345374
export async function appendChatMessage(
346375
db: DashboardAgentDb,
347-
params: { chatId: string; userId: string; message: unknown }
376+
params: { chatId: string; userId: string; organizationId: string; message: unknown }
348377
): Promise<boolean> {
349378
const rows = await db
350379
.update(chats)
@@ -354,7 +383,12 @@ export async function appendChatMessage(
354383
updatedAt: sql`now()`,
355384
})
356385
.where(
357-
and(eq(chats.id, params.chatId), eq(chats.userId, params.userId), isNull(chats.deletedAt))
386+
and(
387+
eq(chats.id, params.chatId),
388+
eq(chats.userId, params.userId),
389+
eq(chats.organizationId, params.organizationId),
390+
isNull(chats.deletedAt)
391+
)
358392
)
359393
.returning({ id: chats.id });
360394

0 commit comments

Comments
 (0)