From 6a32c660c6f6ef3349f40f6ba0ebee872a2c750b Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:27:44 +0100 Subject: [PATCH] =?UTF-8?q?patch:=20=F0=9F=A9=B9=20attach=20has=20metadata?= =?UTF-8?q?=20stripped=20out?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/backend/core/handlers/executeRoute.ts | 6 +++-- .../src/backend/core/routes/routeConfigs.ts | 10 +++++++-- .../src/backend/core/types/routeTypes.ts | 3 +++ .../autumn-js/src/backend/core/utils/index.ts | 7 +++++- .../src/backend/core/utils/sanitizeBody.ts | 22 ++++++++++++++----- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/packages/autumn-js/src/backend/core/handlers/executeRoute.ts b/packages/autumn-js/src/backend/core/handlers/executeRoute.ts index 2503fe13f..a007189b0 100644 --- a/packages/autumn-js/src/backend/core/handlers/executeRoute.ts +++ b/packages/autumn-js/src/backend/core/handlers/executeRoute.ts @@ -13,11 +13,13 @@ import { resolveIdentity } from "./resolveIdentity"; const buildSdkArgs = ({ body, identity, + route, }: { body: unknown; identity: ResolvedIdentity; + route: RouteDefinition; }): Record => { - const args = sanitizeBody(body); + const args = sanitizeBody(body, route.protectedBodyFields); if (identity.customerId) { args.customerId = identity.customerId; @@ -71,7 +73,7 @@ export const executeRoute = async ({ } // 3. Build args and call SDK - const sdkArgs = buildSdkArgs({ body, identity }); + const sdkArgs = buildSdkArgs({ body, identity, route }); try { const result = await route.sdkMethod(autumn, sdkArgs); diff --git a/packages/autumn-js/src/backend/core/routes/routeConfigs.ts b/packages/autumn-js/src/backend/core/routes/routeConfigs.ts index 261729cd0..719ddd7d0 100644 --- a/packages/autumn-js/src/backend/core/routes/routeConfigs.ts +++ b/packages/autumn-js/src/backend/core/routes/routeConfigs.ts @@ -16,7 +16,12 @@ import { updateSubscriptionParamsSchema, } from "../../../generated"; import type { RouteDefinition, RouteName } from "../types"; -import { backendError, backendSuccess, sanitizeBody } from "../utils"; +import { + backendError, + backendSuccess, + CUSTOMER_PROTECTED_BODY_FIELDS, + sanitizeBody, +} from "../utils"; const getEntityBodySchema = z.object({ entityId: z.string(), @@ -33,8 +38,9 @@ export const routeConfigs: RouteDefinition[] = [ // expand: z.array(z.enum(CustomerExpand)).optional(), expand: z.array(z.string()).optional(), }), + protectedBodyFields: CUSTOMER_PROTECTED_BODY_FIELDS, customHandler: async ({ autumn, identity, body }) => { - const sanitizedBody = sanitizeBody(body); + const sanitizedBody = sanitizeBody(body, CUSTOMER_PROTECTED_BODY_FIELDS); // Special case: if no customer and errorOnNotFound is false, return 204 if (!identity?.customerId && sanitizedBody.errorOnNotFound === false) { diff --git a/packages/autumn-js/src/backend/core/types/routeTypes.ts b/packages/autumn-js/src/backend/core/types/routeTypes.ts index 6ad13c98b..82559a565 100644 --- a/packages/autumn-js/src/backend/core/types/routeTypes.ts +++ b/packages/autumn-js/src/backend/core/types/routeTypes.ts @@ -1,5 +1,6 @@ import type { Autumn } from "@useautumn/sdk"; import type { z } from "zod/v4"; +import type { ProtectedBodyField } from "../utils/sanitizeBody"; import type { ResolvedIdentity } from "./authTypes"; import type { BackendResult } from "./responseTypes"; @@ -48,6 +49,8 @@ export type RouteDefinition = { customHandler?: CustomHandlerFn; /** Whether customer ID is required (default: true) */ requireCustomer?: boolean; + /** Body fields that must come from identity, not frontend */ + protectedBodyFields?: readonly ProtectedBodyField[]; /** Zod schema for request body validation (used by better-auth plugin) */ bodySchema?: z.ZodTypeAny; }; diff --git a/packages/autumn-js/src/backend/core/utils/index.ts b/packages/autumn-js/src/backend/core/utils/index.ts index 3e1926ac6..f1ce2dcf6 100644 --- a/packages/autumn-js/src/backend/core/utils/index.ts +++ b/packages/autumn-js/src/backend/core/utils/index.ts @@ -1,3 +1,8 @@ export { secretKeyCheck } from "./secretKeyCheck"; export { backendSuccess, backendError, isBackendResult } from "./backendRes"; -export { sanitizeBody } from "./sanitizeBody"; \ No newline at end of file +export { + CUSTOMER_PROTECTED_BODY_FIELDS, + DEFAULT_PROTECTED_BODY_FIELDS, + sanitizeBody, +} from "./sanitizeBody"; +export type { ProtectedBodyField } from "./sanitizeBody"; diff --git a/packages/autumn-js/src/backend/core/utils/sanitizeBody.ts b/packages/autumn-js/src/backend/core/utils/sanitizeBody.ts index fab0640bd..dfe8de604 100644 --- a/packages/autumn-js/src/backend/core/utils/sanitizeBody.ts +++ b/packages/autumn-js/src/backend/core/utils/sanitizeBody.ts @@ -1,19 +1,31 @@ /** Fields that must come from identity, not frontend */ -const PROTECTED_FIELDS = [ +export const DEFAULT_PROTECTED_BODY_FIELDS = [ "customerId", + "customerData", "name", "email", - "metadata", "stripeId", -]; +] as const; + +export const CUSTOMER_PROTECTED_BODY_FIELDS = [ + ...DEFAULT_PROTECTED_BODY_FIELDS, + "metadata", +] as const; + +export type ProtectedBodyField = + | (typeof DEFAULT_PROTECTED_BODY_FIELDS)[number] + | (typeof CUSTOMER_PROTECTED_BODY_FIELDS)[number]; /** Strip protected fields from body to prevent spoofing */ -export const sanitizeBody = (body: unknown): Record => { +export const sanitizeBody = ( + body: unknown, + protectedFields: readonly ProtectedBodyField[] = DEFAULT_PROTECTED_BODY_FIELDS, +): Record => { const rawBody = (body as Record) || {}; const sanitized: Record = {}; for (const [key, value] of Object.entries(rawBody)) { - if (!PROTECTED_FIELDS.includes(key)) { + if (!protectedFields.includes(key as ProtectedBodyField)) { sanitized[key] = value; } }