Skip to content

Commit bd2b611

Browse files
committed
docs(ai-chat): bind chatId to the caller in the migration guide examples
The server action examples checked only that a session existed. Since chatId comes from the browser, any signed-in user copying them could mint a token scoped read/write to someone else's chat session. Both actions now bind the two, and the head-start route handler gets the same check.
1 parent e797790 commit bd2b611

1 file changed

Lines changed: 15 additions & 1 deletion

File tree

docs/ai-chat/migrating-from-a-route-handler.mdx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ The route handler was doing two jobs: authorizing the request, and terminating t
207207
import { auth as triggerAuth } from "@trigger.dev/sdk";
208208
import { chat, type ChatStartSessionParams } from "@trigger.dev/sdk/ai";
209209
import { auth } from "@/lib/auth";
210+
import { assertChatOwner, claimChat } from "@/lib/chat-access";
210211
import type { myChat } from "@/trigger/chat";
211212

212213
const start = chat.createStartSessionAction<typeof myChat>("my-chat");
@@ -217,6 +218,8 @@ export async function startChatSession(params: ChatStartSessionParams<typeof myC
217218
const session = await auth();
218219
if (!session) throw new Error("Unauthorized");
219220

221+
await claimChat(params.chatId, session.user.id);
222+
220223
return start(params);
221224
}
222225

@@ -225,6 +228,8 @@ export async function mintChatAccessToken(chatId: string) {
225228
const session = await auth();
226229
if (!session) throw new Error("Unauthorized");
227230

231+
await assertChatOwner(chatId, session.user.id);
232+
228233
return triggerAuth.createPublicToken({
229234
scopes: {
230235
read: { sessions: chatId },
@@ -237,6 +242,10 @@ export async function mintChatAccessToken(chatId: string) {
237242

238243
Both run on your server, so the browser never sees `TRIGGER_SECRET_KEY`. This is where per-user and per-plan authorization belongs, alongside any database writes you want paired with session creation.
239244

245+
<Warning>
246+
Signed in is not the same as entitled to this chat, and `chatId` arrives from the browser. Bind the two yourself: `claimChat` records the owner the first time a chat id is seen and rejects it if someone else already holds it, and `assertChatOwner` requires a row the caller owns. Check only that a session exists and any signed-in user can mint a read/write token for someone else's conversation.
247+
</Warning>
248+
240249
<Note>
241250
If you'd rather keep REST endpoints than use server actions, both callbacks accept any async function — see [calling a fetch endpoint instead](/ai-chat/frontend#calling-a-fetch-endpoint-instead-of-a-server-action).
242251
</Note>
@@ -475,10 +484,11 @@ Head Start brings the route handler back for exactly that first turn. It runs st
475484
Your provider keys never leave your server — the first-turn model call runs in your process, so that environment needs whatever the model requires.
476485
</Step>
477486
<Step title="Mount it where the old handler was, auth check and all">
478-
The authorization check you moved into the server actions belongs here too, in the same place it always was. Wrap the handler rather than exporting it directly:
487+
The authorization check you moved into the server actions belongs here too, in the same place it always was, ownership check included. Wrap the handler rather than exporting it directly:
479488

480489
```ts app/api/chat/route.ts
481490
import { auth } from "@/lib/auth";
491+
import { claimChat } from "@/lib/chat-access";
482492
import { chatHandler } from "@/lib/chat-handler";
483493

484494
// The handler holds the SSE response open until the agent signals
@@ -489,6 +499,10 @@ Head Start brings the route handler back for exactly that first turn. It runs st
489499
const session = await auth();
490500
if (!session) return new Response("Unauthorized", { status: 401 });
491501

502+
// Clone so the handler still gets an unread body.
503+
const { chatId } = await req.clone().json();
504+
await claimChat(chatId, session.user.id);
505+
492506
return chatHandler(req);
493507
}
494508
```

0 commit comments

Comments
 (0)