fix(portal): drop writable photoKey from PATCH /portal/pets — S3 key-hijack (GRO-2187/GRO-2198) (#172)
CI / Test (push) Successful in 24s
CI / Lint & Typecheck (push) Successful in 26s
CI / Build & Push Docker Images (push) Successful in 29s
CI / Lint & Typecheck (pull_request) Successful in 24s
CI / Test (pull_request) Successful in 30s
CI / Build & Push Docker Images (pull_request) Successful in 44s
CI / Test (push) Successful in 24s
CI / Lint & Typecheck (push) Successful in 26s
CI / Build & Push Docker Images (push) Successful in 29s
CI / Lint & Typecheck (pull_request) Successful in 24s
CI / Test (pull_request) Successful in 30s
CI / Build & Push Docker Images (pull_request) Successful in 44s
This commit was merged in pull request #172.
This commit is contained in:
+11
-6
@@ -261,8 +261,8 @@ const PORTAL_PET_SIZE_ALIASES: Record<string, string> = { xlarge: "extra_large"
|
||||
|
||||
const portalMedicalAlertSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
type: z.string(),
|
||||
description: z.string(),
|
||||
type: z.string().max(2000),
|
||||
description: z.string().max(2000),
|
||||
severity: z.enum(["low", "medium", "high"]),
|
||||
});
|
||||
|
||||
@@ -275,12 +275,16 @@ const portalPetUpdateSchema = z.object({
|
||||
birthDate: z.string().nullable().optional(),
|
||||
notes: z.string().max(2000).nullable().optional(),
|
||||
healthAlerts: z.string().max(2000).nullable().optional(),
|
||||
photoUrl: z.string().nullable().optional(),
|
||||
// photoUrl/photoKey are intentionally NOT writable here: photoKey is a trusted
|
||||
// S3 object key consumed server-side (getPresignedGetUrl / deleteObject), and the
|
||||
// upload path (pets.ts) already enforces a pets/{petId}/ prefix guard against key
|
||||
// hijacking. Photo changes go through the dedicated upload + /photo/confirm flow.
|
||||
// The web form round-trips the GET-shaped photoUrl; Zod strips it as an unknown key.
|
||||
// coatType / petSizeCategory validated in-handler so bad values return 422.
|
||||
coatType: z.string().nullable().optional(),
|
||||
petSizeCategory: z.string().nullable().optional(),
|
||||
preferredCuts: z.array(z.string()).nullable().optional(),
|
||||
medicalAlerts: z.array(portalMedicalAlertSchema).nullable().optional(),
|
||||
preferredCuts: z.array(z.string().max(2000)).max(50).nullable().optional(),
|
||||
medicalAlerts: z.array(portalMedicalAlertSchema).max(50).nullable().optional(),
|
||||
});
|
||||
|
||||
portalRouter.patch(
|
||||
@@ -322,7 +326,8 @@ portalRouter.patch(
|
||||
|
||||
if (body.notes !== undefined) updateData.groomingNotes = body.notes;
|
||||
if (body.healthAlerts !== undefined) updateData.healthAlerts = body.healthAlerts;
|
||||
if (body.photoUrl !== undefined) updateData.photoKey = body.photoUrl;
|
||||
// photoKey is intentionally not writable here — see portalPetUpdateSchema note.
|
||||
// Photo changes go through the key-validated upload + /photo/confirm flow.
|
||||
|
||||
if (body.coatType !== undefined) {
|
||||
if (body.coatType !== null && !PORTAL_COAT_TYPES.includes(body.coatType)) {
|
||||
|
||||
Reference in New Issue
Block a user