arcrun — AI workflow execution engine (clean history)
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。 此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在 richblack/arcrun 與本地 backup 分支)。含: - acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe) - recipe push 把關(資料外流提醒 + 打通檢查) - 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1) - CLI / cypher-executor / registry / 完整 SDD Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Credentials API — 多租戶 credential 管理
|
||||
*
|
||||
* POST /credentials
|
||||
* Body: { name: string, encrypted: string, iv: string }
|
||||
* Header: X-Arcrun-API-Key
|
||||
* → 以 {api_key}:cred:{name} 為 KV key 存入 CREDENTIALS_KV
|
||||
*
|
||||
* DELETE /credentials/:name
|
||||
* Header: X-Arcrun-API-Key
|
||||
* → 刪除 {api_key}:cred:{name}
|
||||
*
|
||||
* GET /credentials
|
||||
* Header: X-Arcrun-API-Key
|
||||
* → 列出當前 api_key 下所有 credential 名稱(不含加密值)
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import type { Bindings } from '../types';
|
||||
|
||||
export const credentialsRouter = new Hono<{ Bindings: Bindings }>();
|
||||
|
||||
// POST /credentials — 上傳加密 credential
|
||||
credentialsRouter.post('/credentials', async (c) => {
|
||||
const apiKey = c.req.header('X-Arcrun-API-Key');
|
||||
if (!apiKey) {
|
||||
return c.json({ error: '缺少 X-Arcrun-API-Key header' }, 401);
|
||||
}
|
||||
|
||||
const body = await c.req.json().catch(() => null) as {
|
||||
name?: string;
|
||||
encrypted?: string;
|
||||
iv?: string;
|
||||
} | null;
|
||||
|
||||
if (!body?.name || !body.encrypted || !body.iv) {
|
||||
return c.json({ error: '缺少必要欄位:name, encrypted, iv' }, 400);
|
||||
}
|
||||
|
||||
const name = body.name.trim();
|
||||
if (!/^\w+$/.test(name)) {
|
||||
return c.json({ error: 'credential name 只能包含英文字母、數字和底線' }, 400);
|
||||
}
|
||||
|
||||
const kvKey = `${apiKey}:cred:${name}`;
|
||||
const record = JSON.stringify({ encrypted: body.encrypted, iv: body.iv });
|
||||
|
||||
await c.env.CREDENTIALS_KV.put(kvKey, record);
|
||||
|
||||
return c.json({ success: true, name });
|
||||
});
|
||||
|
||||
// DELETE /credentials/:name — 刪除 credential
|
||||
credentialsRouter.delete('/credentials/:name', async (c) => {
|
||||
const apiKey = c.req.header('X-Arcrun-API-Key');
|
||||
if (!apiKey) {
|
||||
return c.json({ error: '缺少 X-Arcrun-API-Key header' }, 401);
|
||||
}
|
||||
|
||||
const name = c.req.param('name');
|
||||
const kvKey = `${apiKey}:cred:${name}`;
|
||||
await c.env.CREDENTIALS_KV.delete(kvKey);
|
||||
|
||||
return c.json({ success: true, name });
|
||||
});
|
||||
|
||||
// GET /credentials — 列出 credential 名稱(不含值)
|
||||
credentialsRouter.get('/credentials', async (c) => {
|
||||
const apiKey = c.req.header('X-Arcrun-API-Key');
|
||||
if (!apiKey) {
|
||||
return c.json({ error: '缺少 X-Arcrun-API-Key header' }, 401);
|
||||
}
|
||||
|
||||
const prefix = `${apiKey}:cred:`;
|
||||
const list = await c.env.CREDENTIALS_KV.list({ prefix });
|
||||
const names = list.keys.map(k => k.name.slice(prefix.length));
|
||||
|
||||
return c.json({ credentials: names });
|
||||
});
|
||||
Reference in New Issue
Block a user