patch: 🩹 attach has metadata stripped out

This commit is contained in:
amianthus
2026-06-08 20:27:44 +01:00
parent d91ef05e73
commit 6a32c660c6
5 changed files with 38 additions and 10 deletions

View File

@@ -13,11 +13,13 @@ import { resolveIdentity } from "./resolveIdentity";
const buildSdkArgs = ({
body,
identity,
route,
}: {
body: unknown;
identity: ResolvedIdentity;
route: RouteDefinition;
}): Record<string, unknown> => {
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);

View File

@@ -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<RouteName>[] = [
// 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) {

View File

@@ -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<T extends RouteName = RouteName> = {
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;
};

View File

@@ -1,3 +1,8 @@
export { secretKeyCheck } from "./secretKeyCheck";
export { backendSuccess, backendError, isBackendResult } from "./backendRes";
export { sanitizeBody } from "./sanitizeBody";
export {
CUSTOMER_PROTECTED_BODY_FIELDS,
DEFAULT_PROTECTED_BODY_FIELDS,
sanitizeBody,
} from "./sanitizeBody";
export type { ProtectedBodyField } from "./sanitizeBody";

View File

@@ -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<string, unknown> => {
export const sanitizeBody = (
body: unknown,
protectedFields: readonly ProtectedBodyField[] = DEFAULT_PROTECTED_BODY_FIELDS,
): Record<string, unknown> => {
const rawBody = (body as Record<string, unknown>) || {};
const sanitized: Record<string, unknown> = {};
for (const [key, value] of Object.entries(rawBody)) {
if (!PROTECTED_FIELDS.includes(key)) {
if (!protectedFields.includes(key as ProtectedBodyField)) {
sanitized[key] = value;
}
}