Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions integrations/agent-memory-api/dual-key-auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { assertEquals } from "jsr:@std/assert@1";
import { app, configureAgentMemoryAppForTest } from "./index.ts";

const NEW_KEY = "test-dedicated-agent-memory-key";
const OLD_KEY = "test-shared-mcp-key";

async function healthStatus(key?: string): Promise<number> {
const headers: Record<string, string> = {};
if (key) headers["x-brain-key"] = key;
const res = await app.fetch(
new Request("http://localhost/health", { headers }),
);
await res.body?.cancel();
return res.status;
}

Deno.test("dual-key: new dedicated key accepted when set", async () => {
configureAgentMemoryAppForTest({
mcpAccessKey: OLD_KEY,
agentMemoryAccessKey: NEW_KEY,
});
assertEquals(await healthStatus(NEW_KEY), 200);
});

Deno.test("dual-key: MCP_ACCESS_KEY fallback still accepted (D1 interim)", async () => {
configureAgentMemoryAppForTest({
mcpAccessKey: OLD_KEY,
agentMemoryAccessKey: NEW_KEY,
});
assertEquals(await healthStatus(OLD_KEY), 200);
});

Deno.test("dual-key: wrong key rejected 401", async () => {
configureAgentMemoryAppForTest({
mcpAccessKey: OLD_KEY,
agentMemoryAccessKey: NEW_KEY,
});
assertEquals(await healthStatus("wrong-key"), 401);
});

Deno.test("dual-key: missing key rejected 401", async () => {
configureAgentMemoryAppForTest({
mcpAccessKey: OLD_KEY,
agentMemoryAccessKey: NEW_KEY,
});
assertEquals(await healthStatus(undefined), 401);
});

Deno.test("dual-key: unset dedicated key preserves MCP-only behavior", async () => {
configureAgentMemoryAppForTest({
mcpAccessKey: OLD_KEY,
agentMemoryAccessKey: "",
});
assertEquals(await healthStatus(OLD_KEY), 200);
assertEquals(await healthStatus(NEW_KEY), 401);
});
14 changes: 14 additions & 0 deletions integrations/agent-memory-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const SUPABASE_URL = safeEnv("SUPABASE_URL")!;
const SUPABASE_SERVICE_ROLE_KEY = safeEnv("SUPABASE_SERVICE_ROLE_KEY")!;
const OPENROUTER_API_KEY = safeEnv("OPENROUTER_API_KEY")!;
let MCP_ACCESS_KEY = safeEnv("MCP_ACCESS_KEY")!;
let AGENT_MEMORY_ACCESS_KEY = safeEnv("AGENT_MEMORY_ACCESS_KEY");
const OPENROUTER_BASE = "https://openrouter.ai/api/v1";
let AGENT_MEMORY_READ_ONLY = parseBooleanEnv(safeEnv("AGENT_MEMORY_READ_ONLY"));
let AGENT_MEMORY_ALLOW_QUERY_KEY = parseBooleanEnv(
Expand Down Expand Up @@ -57,6 +58,7 @@ function db() {
type AgentMemoryAppTestRuntime = {
supabase?: SupabaseClientLike;
mcpAccessKey?: string;
agentMemoryAccessKey?: string;
readOnly?: boolean;
allowQueryKey?: boolean;
allowedScope?: {
Expand All @@ -70,6 +72,9 @@ export function configureAgentMemoryAppForTest(
) {
if (runtime.supabase) supabase = runtime.supabase;
if (runtime.mcpAccessKey !== undefined) MCP_ACCESS_KEY = runtime.mcpAccessKey;
if (runtime.agentMemoryAccessKey !== undefined) {
AGENT_MEMORY_ACCESS_KEY = runtime.agentMemoryAccessKey;
}
if (runtime.readOnly !== undefined) AGENT_MEMORY_READ_ONLY = runtime.readOnly;
if (runtime.allowQueryKey !== undefined) {
AGENT_MEMORY_ALLOW_QUERY_KEY = runtime.allowQueryKey;
Expand Down Expand Up @@ -301,6 +306,15 @@ function auth(c: { req: { raw: Request; url: string } }) {
const provided = selectAccessKey(c.req.raw.headers, c.req.url, {
allowQueryKey: AGENT_MEMORY_ALLOW_QUERY_KEY,
});
// Dedicated read-surface key is preferred when set; the shared
// MCP_ACCESS_KEY fallback is retained until Stone-side harnesses
// migrate (design decision D1 — full decouple is a later increment).
if (
AGENT_MEMORY_ACCESS_KEY &&
accessKeyMatches(provided, AGENT_MEMORY_ACCESS_KEY)
) {
return true;
}
return accessKeyMatches(provided, MCP_ACCESS_KEY);
}

Expand Down
Loading