feat(authz): add atomic mobile knowledge writes
This commit is contained in:
parent
a3282f7fe1
commit
5e2ba5b006
6 changed files with 527 additions and 19 deletions
|
|
@ -36,6 +36,25 @@ async function withServer(run) {
|
|||
content_type: "application/zip",
|
||||
body: Buffer.from("PK-test-archive"),
|
||||
}),
|
||||
writePage: request => {
|
||||
if (request.path.includes("本地密钥")) {
|
||||
throw new Error("knowledge_secret_page_forbidden");
|
||||
}
|
||||
if (request.path === "notes/conflict.md") {
|
||||
throw new Error("knowledge_commit_conflict");
|
||||
}
|
||||
return {
|
||||
schema: "guanghu.hololake-knowledge-write-receipt/v1",
|
||||
repository: "bingshuo/hololake-knowledge-base",
|
||||
path: request.path,
|
||||
base_commit: request.baseCommit,
|
||||
commit: "c".repeat(40),
|
||||
content_sha256: crypto
|
||||
.createHash("sha256")
|
||||
.update(request.content)
|
||||
.digest("hex"),
|
||||
};
|
||||
},
|
||||
};
|
||||
const aiGateway = {
|
||||
catalog: () => ({
|
||||
|
|
@ -177,6 +196,21 @@ test("authenticated device can read fixed knowledge, use AI proxy, inspect and r
|
|||
assert.equal(archive.headers.get("x-content-sha256"), "b".repeat(64));
|
||||
assert.equal(Buffer.from(await archive.arrayBuffer()).toString(), "PK-test-archive");
|
||||
|
||||
const pageWrite = await fetch(`${base}/api/hololake/knowledge/page`, {
|
||||
method: "PUT",
|
||||
headers: { ...headers, "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
path: "个人知识/手机页面.md",
|
||||
content: "# 手机页面\n",
|
||||
base_commit: "a".repeat(40),
|
||||
}),
|
||||
});
|
||||
assert.equal(pageWrite.status, 200);
|
||||
const writeReceipt = await pageWrite.json();
|
||||
assert.equal(writeReceipt.path, "个人知识/手机页面.md");
|
||||
assert.equal(writeReceipt.commit, "c".repeat(40));
|
||||
assert.doesNotMatch(JSON.stringify(writeReceipt), /# 手机页面/);
|
||||
|
||||
const ai = await fetch(`${base}/api/hololake/ai/execute`, {
|
||||
method: "POST",
|
||||
headers: { ...headers, "content-type": "application/json" },
|
||||
|
|
@ -221,6 +255,18 @@ test("knowledge and AI endpoints require the session and matching device", async
|
|||
})).status,
|
||||
401,
|
||||
);
|
||||
assert.equal(
|
||||
(await fetch(`${base}/api/hololake/knowledge/page`, {
|
||||
method: "PUT",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
path: "notes/no-session.md",
|
||||
content: "# no\n",
|
||||
base_commit: "a".repeat(40),
|
||||
}),
|
||||
})).status,
|
||||
401,
|
||||
);
|
||||
|
||||
const token = await login(base, mail);
|
||||
assert.equal(
|
||||
|
|
@ -234,3 +280,39 @@ test("knowledge and AI endpoints require the session and matching device", async
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("knowledge write endpoint maps conflicts and validation failures without echoing content", async () => {
|
||||
await withServer(async ({ base, mail }) => {
|
||||
const deviceId = "ios-device-001";
|
||||
const token = await login(base, mail, deviceId);
|
||||
const headers = {
|
||||
authorization: `Bearer ${token}`,
|
||||
"x-hololake-device-id": deviceId,
|
||||
"content-type": "application/json",
|
||||
};
|
||||
|
||||
const forbidden = await fetch(`${base}/api/hololake/knowledge/page`, {
|
||||
method: "PUT",
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
path: "本地密钥/openai.md",
|
||||
content: "super-secret-value",
|
||||
base_commit: "a".repeat(40),
|
||||
}),
|
||||
});
|
||||
assert.equal(forbidden.status, 400);
|
||||
assert.doesNotMatch(await forbidden.text(), /super-secret-value/);
|
||||
|
||||
const conflict = await fetch(`${base}/api/hololake/knowledge/page`, {
|
||||
method: "PUT",
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
path: "notes/conflict.md",
|
||||
content: "# stale\n",
|
||||
base_commit: "a".repeat(40),
|
||||
}),
|
||||
});
|
||||
assert.equal(conflict.status, 409);
|
||||
assert.equal((await conflict.json()).error, "knowledge_commit_conflict");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue