fix(audit): H1 (webhook redirect SSRF), H6 (berth-status case), H7 (residential notes URL)
H1: webhook delivery fetch now uses redirect:'manual' and refuses to read or expose a redirected (un-revalidated) response, closing the SSRF read primitive. H6: dashboard report queries matched title-case 'Sold'/'Under offer' that never match the lowercase canonical, silently reporting 0 sold / understated occupancy — now lowercase. H7: NotesList maps the entityType discriminator to its REST path (residential_* -> residential/clients| interests) instead of interpolating the raw underscore, which 404'd every residential notes request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,20 @@ const AGGREGATABLE: ReadonlySet<NotesEntityType> = new Set<NotesEntityType>([
|
||||
'residential_clients',
|
||||
]);
|
||||
|
||||
/** Maps the entityType discriminator to its REST path segment. The
|
||||
* residential entities use slash-separated routes
|
||||
* (`/api/v1/residential/clients/...`), so the raw underscore discriminator
|
||||
* must NOT be interpolated into the URL — doing so 404'd every residential
|
||||
* notes request and the UI silently showed "No notes yet" (audit H7). */
|
||||
const NOTES_API_PATH: Record<NotesEntityType, string> = {
|
||||
clients: 'clients',
|
||||
interests: 'interests',
|
||||
yachts: 'yachts',
|
||||
companies: 'companies',
|
||||
residential_clients: 'residential/clients',
|
||||
residential_interests: 'residential/interests',
|
||||
};
|
||||
|
||||
const SOURCE_BADGE_CLASS: Record<NoteSource, string> = {
|
||||
client: 'bg-violet-100 text-violet-900',
|
||||
interest: 'bg-blue-100 text-blue-900',
|
||||
@@ -189,7 +203,7 @@ export function NotesList({
|
||||
}, []);
|
||||
|
||||
const aggregateOn = !!aggregate && AGGREGATABLE.has(entityType);
|
||||
const baseEndpoint = `/api/v1/${entityType}/${entityId}/notes`;
|
||||
const baseEndpoint = `/api/v1/${NOTES_API_PATH[entityType]}/${entityId}/notes`;
|
||||
const listEndpoint = aggregateOn ? `${baseEndpoint}?aggregate=true` : baseEndpoint;
|
||||
const queryKey = [entityType, entityId, 'notes', aggregateOn ? 'aggregated' : 'own'];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user