mirror of
https://github.com/langchain-ai/langgraphjs.git
synced 2026-07-25 13:15:45 -04:00
fix(api): call threads:create auth handler when copying a thread (#1641)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@langchain/langgraph-api": patch
|
||||
---
|
||||
|
||||
fix(api): call threads:create auth handler when copying a thread
|
||||
@@ -908,7 +908,7 @@ export class FileSystemThreads implements ThreadsRepo {
|
||||
thread_id,
|
||||
});
|
||||
|
||||
return this.conn.with((STORE) => {
|
||||
const fromThread = await this.conn.with((STORE) => {
|
||||
const thread = STORE.threads[thread_id];
|
||||
if (!thread)
|
||||
throw new HTTPException(409, { message: "Thread not found" });
|
||||
@@ -916,14 +916,23 @@ export class FileSystemThreads implements ThreadsRepo {
|
||||
if (!isAuthMatching(thread["metadata"], filters)) {
|
||||
throw new HTTPException(409, { message: "Thread not found" });
|
||||
}
|
||||
return thread;
|
||||
});
|
||||
|
||||
const newThreadId = uuid4();
|
||||
const now = new Date();
|
||||
const newThreadId = uuid4();
|
||||
const now = new Date();
|
||||
const newMetadata = { ...fromThread.metadata, thread_id: newThreadId };
|
||||
await handleAuthEvent(auth, "threads:create", {
|
||||
thread_id: newThreadId,
|
||||
metadata: newMetadata,
|
||||
});
|
||||
|
||||
return this.conn.with((STORE) => {
|
||||
STORE.threads[newThreadId] = {
|
||||
thread_id: newThreadId,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
metadata: { ...thread.metadata, thread_id: newThreadId },
|
||||
metadata: newMetadata,
|
||||
config: {},
|
||||
status: "idle",
|
||||
};
|
||||
|
||||
@@ -684,7 +684,7 @@ it.skipIf(process.version.startsWith("v18."))(
|
||||
);
|
||||
|
||||
it.skipIf(process.version.startsWith("v18."))(
|
||||
"thread copy authorization",
|
||||
"thread copy authorization (by owner)",
|
||||
async () => {
|
||||
const owner = await createJwtClient("johndoe", ["me"]);
|
||||
const otherUser = await createJwtClient("alice", ["me"]);
|
||||
@@ -702,6 +702,31 @@ it.skipIf(process.version.startsWith("v18."))(
|
||||
}
|
||||
);
|
||||
|
||||
it.skipIf(process.version.startsWith("v18."))(
|
||||
"thread copy authorization (requires read and write)",
|
||||
async () => {
|
||||
const writer = await createJwtClient("johndoe", ["me"]);
|
||||
const reader = await createJwtClient("johndoe", ["threads:read"]);
|
||||
|
||||
const thread = await writer.threads.create();
|
||||
|
||||
// Can read the thread from both...
|
||||
const readerThread = await reader.threads.get(thread.thread_id);
|
||||
const writerThread = await writer.threads.get(thread.thread_id);
|
||||
expect(thread).toEqual(readerThread);
|
||||
expect(thread).toEqual(writerThread);
|
||||
|
||||
// But can only copy the thread from the writer
|
||||
const copiedThread = await writer.threads.copy(thread.thread_id);
|
||||
expect(copiedThread).not.toBeNull();
|
||||
expect(copiedThread.thread_id).not.toBe(thread.thread_id);
|
||||
|
||||
await expect(reader.threads.copy(thread.thread_id)).rejects.toThrow(
|
||||
"HTTP 403: Not authorized"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.skipIf(process.version.startsWith("v18."))(
|
||||
"thread history authorization",
|
||||
async () => {
|
||||
|
||||
@@ -103,4 +103,18 @@ export const auth = new Auth()
|
||||
if (!identity || !value.namespace?.includes(identity)) {
|
||||
throw new HTTPException(403, { message: "Not authorized" });
|
||||
}
|
||||
})
|
||||
.on("threads:create", ({ value, user, permissions }) => {
|
||||
if (
|
||||
!["me", "threads:write", "threads:*"].some((p) =>
|
||||
permissions?.includes(p)
|
||||
)
|
||||
) {
|
||||
throw new HTTPException(403, { message: "Not authorized" });
|
||||
}
|
||||
|
||||
const filters = { owner: user.identity };
|
||||
value.metadata ??= {};
|
||||
value.metadata["owner"] = user.identity;
|
||||
return filters;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user