Remove backend dev A2A auth fallback

This commit is contained in:
-Puter
2026-07-13 18:04:22 +05:30
parent 26e0e54488
commit 70c3634178
4 changed files with 35 additions and 8 deletions

View File

@@ -98,7 +98,7 @@ services:
CLERK_SECRET_KEY: ${CLERK_SECRET_KEY}
CLERK_PUBLISHABLE_KEY: ${CLERK_PUBLISHABLE_KEY}
SERVICE_TOKEN: ${SERVICE_TOKEN:-dev-service-token}
A2A_ALLOWED_KEY: ${A2A_ALLOWED_KEY:-dev-a2a-key}
A2A_ALLOWED_KEY: ${A2A_ALLOWED_KEY:-}
# LLM
OPENCODE_API_KEY: ${OPENCODE_API_KEY}
LLM_PROVIDER: ${LLM_PROVIDER:-opencode}

View File

@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import { config } from "../src/config.js";
import { resolveTrustedServiceTokens } from "../src/auth/clerk.js";
import { fetchOnboardingReadProfile, fetchUserProfile, patchPreferencesRequest } from "../src/services/user-profile.js";
/**
@@ -282,5 +283,24 @@ async function runFetchUserProfileAfterBodyConsumed(): Promise<{
globalThis.fetch = originalFetch;
}
}
// ── 8. trusted token resolution excludes A2A in production ────────────────
{
assert.equal(config.a2aAllowedKey, process.env.A2A_ALLOWED_KEY ?? "", "A2A config must not have a dev fallback");
const production = resolveTrustedServiceTokens({
nodeEnv: "production",
serviceToken: "service-token",
a2aAllowedKey: "a2a-token",
});
assert.equal(production.has("service-token"), true);
assert.equal(production.has("a2a-token"), false, "production must exclude A2A token");
const development = resolveTrustedServiceTokens({
nodeEnv: "development",
serviceToken: "service-token",
a2aAllowedKey: "a2a-token",
});
assert.equal(development.has("service-token"), true);
assert.equal(development.has("a2a-token"), true, "development must include configured A2A token");
}
console.log("user-profile: all assertions passed");

View File

@@ -14,6 +14,17 @@ export type AuthContext = {
};
};
/** Resolve inbound service tokens without allowing A2A in production. */
export function resolveTrustedServiceTokens(input: {
nodeEnv: string;
serviceToken: string;
a2aAllowedKey: string;
}): ReadonlySet<string> {
return new Set(
[input.serviceToken, input.nodeEnv !== "production" ? input.a2aAllowedKey : ""].filter(Boolean),
);
}
export const clerk = config.clerkSecretKey
? createClerkClient({ secretKey: config.clerkSecretKey })
: null;
@@ -28,12 +39,7 @@ export const requireUser = createMiddleware<AuthContext>(async (c, next) => {
// Service-to-service path (Grow stack calling backend).
// Header `x-growqr-user` is REQUIRED so we can scope the call.
const trustedServiceTokens = new Set(
[
config.serviceToken,
config.nodeEnv !== "production" ? config.a2aAllowedKey : "",
].filter(Boolean),
);
const trustedServiceTokens = resolveTrustedServiceTokens(config);
if (
token &&

View File

@@ -57,7 +57,8 @@ export const config = {
clerkPublishableKey: process.env.CLERK_PUBLISHABLE_KEY ?? "",
// Optional: lock service-to-service calls (actor → backend).
serviceToken: process.env.SERVICE_TOKEN ?? "",
a2aAllowedKey: process.env.A2A_ALLOWED_KEY ?? "dev-a2a-key",
// A2A is intentionally unset unless an operator configures it.
a2aAllowedKey: process.env.A2A_ALLOWED_KEY ?? "",
// Service → backend event stream. Redis is optional; HTTP /events/ingest/service is always available.
// Explicit opt-in flags: both Redis ingestion paths are default-off. The REST