security(api): harden V3 authentication and unify CRUD endpoint patterns

Implemented critical security and architectural fixes to align the frontend with the Aether API V3 standard and resolve 403 Forbidden race conditions.

- Unified CRUD Helpers: Updated get, create, update, and delete helpers to use the standard /v3/crud/{obj_type}/{id} paths, ensuring correct backend isolation context.
- Auth Scavenging: Implemented direct localStorage scavenging for 'x-account-id' in core fetch helpers to prevent hydration race conditions in Svelte 5.
- Header Cleanup: Purged redundant 'x-aether-api-token' and fixed misplaced protocol headers in global stores.
- Reliability: Fixed 'Content-Type' typos and standardized kebab-case header normalization.
This commit is contained in:
Scott Idem
2026-02-13 19:10:32 -05:00
parent 3e83890932
commit f62bd9fb79
7 changed files with 68 additions and 313 deletions

View File

@@ -125,88 +125,10 @@ export const create_ae_obj_crud = async function create_ae_obj_crud({
data['super_key'] = key;
data['jwt'] = jwt;
// NOTE: The key and or JWT should be in the header of the DELETE, GET, PATCH, POST
// This obj_v_name is the view name to use when returning data. Do not prefix it with v_. This is checked and done automatically by the API.
// This is not currently being exposed to other areas of the code. It is only used here. For now?
if (obj_v_name) {
obj_v_name = '';
}
// V3 Standard: Unified endpoint for all objects
const endpoint = `/v3/crud/${obj_type}`;
let endpoint = '';
if (obj_type == 'account') {
endpoint = `/crud/account`;
} else if (obj_type == 'activity_log') {
endpoint = `/crud/activity_log`;
} else if (obj_type == 'address') {
endpoint = `/crud/address`;
} else if (obj_type == 'archive') {
endpoint = `/crud/archive`;
} else if (obj_type == 'archive_content') {
endpoint = `/crud/archive/content`;
} else if (obj_type == 'contact') {
endpoint = `/crud/contact`;
} else if (obj_type == 'data_store') {
endpoint = `/crud/data_store`;
} else if (obj_type == 'event') {
endpoint = `/crud/event`;
} else if (obj_type == 'event_abstract') {
endpoint = `/crud/event/abstract`;
} else if (obj_type == 'event_badge') {
endpoint = `/crud/event/badge`;
} else if (obj_type == 'event_device') {
endpoint = `/crud/event/device`;
} else if (obj_type == 'event_exhibit') {
endpoint = `/crud/event/exhibit`;
} else if (obj_type == 'event_exhibit_tracking') {
endpoint = `/crud/event/exhibit/tracking`;
} else if (obj_type == 'event_file') {
endpoint = `/crud/event/file`;
} else if (obj_type == 'event_location') {
endpoint = `/crud/event/location`;
} else if (obj_type == 'event_person') {
endpoint = `/crud/event/person`;
} else if (obj_type == 'event_presentation') {
endpoint = `/crud/event/presentation`;
} else if (obj_type == 'event_presenter') {
endpoint = `/crud/event/presenter`;
// obj_v_name = 'event_presenter_soft_links';
} else if (obj_type == 'event_session') {
endpoint = `/crud/event/session`;
} else if (obj_type == 'event_track') {
endpoint = `/crud/event/track`;
} else if (obj_type == 'grant') {
endpoint = `/crud/grant`;
} else if (obj_type == 'hosted_file') {
endpoint = `/crud/hosted_file`;
} else if (obj_type == 'journal') {
endpoint = `/crud/journal`;
} else if (obj_type == 'journal_entry') {
endpoint = `/crud/journal/entry`;
} else if (obj_type == 'order') {
endpoint = `/crud/order`;
} else if (obj_type == 'order_line') {
endpoint = `/crud/order/line`;
} else if (obj_type == 'page') {
endpoint = `/crud/page`;
} else if (obj_type == 'person') {
endpoint = `/crud/person`;
} else if (obj_type == 'post') {
endpoint = `/crud/post`;
} else if (obj_type == 'post_comment') {
endpoint = `/crud/post/comment`;
} else if (obj_type == 'sponsorship_cfg') {
endpoint = `/crud/sponsorship/cfg`;
} else if (obj_type == 'sponsorship') {
endpoint = `/crud/sponsorship`;
} else if (obj_type == 'site') {
endpoint = `/crud/site`;
// } else if (obj_type == 'user') {
// endpoint = `/crud/user`;
} else {
console.log(`Unknown object type: ${obj_type}`);
return false;
}
if (log_lvl) {
console.log('Endpoint:', endpoint);
}
@@ -315,86 +237,9 @@ export const update_ae_obj_id_crud = async function update_ae_obj_id_crud({
data['super_key'] = key;
data['jwt'] = jwt;
// NOTE: The key and or JWT should be in the header of the DELETE, GET, PATCH, POST
// V3 Standard: Unified endpoint for all objects
const endpoint = `/v3/crud/${obj_type}/${obj_id}`;
// This obj_v_name is the view name to use when returning data. Do not prefix it with v_. This is checked and done automatically by the API.
// This is not currently being exposed to other areas of the code. It is only used here. For now?
if (obj_v_name) {
obj_v_name = '';
}
let endpoint = '';
if (obj_type == 'account') {
endpoint = `/crud/account/${obj_id}`;
} else if (obj_type == 'address') {
endpoint = `/crud/address/${obj_id}`;
} else if (obj_type == 'archive') {
endpoint = `/crud/archive/${obj_id}`;
} else if (obj_type == 'archive_content') {
endpoint = `/crud/archive/content/${obj_id}`;
} else if (obj_type == 'contact') {
endpoint = `/crud/contact/${obj_id}`;
} else if (obj_type == 'data_store') {
endpoint = `/crud/data_store/${obj_id}`;
} else if (obj_type == 'event') {
endpoint = `/crud/event/${obj_id}`;
} else if (obj_type == 'event_abstract') {
endpoint = `/crud/event/abstract/${obj_id}`;
} else if (obj_type == 'event_badge') {
endpoint = `/crud/event/badge/${obj_id}`;
} else if (obj_type == 'event_device') {
endpoint = `/crud/event/device/${obj_id}`;
} else if (obj_type == 'event_exhibit') {
endpoint = `/crud/event/exhibit/${obj_id}`;
} else if (obj_type == 'event_exhibit_tracking') {
endpoint = `/crud/event/exhibit/tracking/${obj_id}`;
} else if (obj_type == 'event_file') {
endpoint = `/crud/event/file/${obj_id}`;
} else if (obj_type == 'event_location') {
endpoint = `/crud/event/location/${obj_id}`;
} else if (obj_type == 'event_person') {
endpoint = `/crud/event/person/${obj_id}`;
} else if (obj_type == 'event_presentation') {
endpoint = `/crud/event/presentation/${obj_id}`;
} else if (obj_type == 'event_presenter') {
endpoint = `/crud/event/presenter/${obj_id}`;
// obj_v_name = 'event_presenter_soft_links';
} else if (obj_type == 'event_session') {
endpoint = `/crud/event/session/${obj_id}`;
} else if (obj_type == 'event_track') {
endpoint = `/crud/event/track/${obj_id}`;
} else if (obj_type == 'grant') {
endpoint = `/crud/grant/${obj_id}`;
} else if (obj_type == 'hosted_file') {
endpoint = `/crud/hosted_file/${obj_id}`;
} else if (obj_type == 'journal') {
endpoint = `/crud/journal/${obj_id}`;
} else if (obj_type == 'journal_entry') {
endpoint = `/crud/journal/entry/${obj_id}`;
} else if (obj_type == 'order') {
endpoint = `/crud/order/${obj_id}`;
} else if (obj_type == 'order_line') {
endpoint = `/crud/order/line/${obj_id}`;
} else if (obj_type == 'page') {
endpoint = `/crud/page/${obj_id}`;
} else if (obj_type == 'person') {
endpoint = `/crud/person/${obj_id}`;
} else if (obj_type == 'post') {
endpoint = `/crud/post/${obj_id}`;
} else if (obj_type == 'post_comment') {
endpoint = `/crud/post/comment/${obj_id}`;
} else if (obj_type == 'site') {
endpoint = `/crud/site/${obj_id}`;
} else if (obj_type == 'sponsorship_cfg') {
endpoint = `/crud/sponsorship/cfg/${obj_id}`;
} else if (obj_type == 'sponsorship') {
endpoint = `/crud/sponsorship/${obj_id}`;
// } else if (obj_type == 'user') {
// endpoint = `/crud/user/${obj_id}`;
} else {
console.log(`Unknown object type: ${obj_type}`);
return false;
}
if (log_lvl) {
console.log('Endpoint:', endpoint);
}
@@ -434,34 +279,6 @@ export const update_ae_obj_id_crud = async function update_ae_obj_id_crud({
}
}
// If the data is an object then we need to loop through the object and convert any objects to JSON strings, but only if the property name ends with "_json".
// if (Array.isArray(data)) {
// // console.log('Data is an array');
// for (let i = 0; i < data.length; i++) {
// // console.log(data[i]);
// if (typeof data[i] == 'object') {
// // console.log('Data is an object');
// for (const [key, value] of Object.entries(data[i])) {
// // console.log(key, value);
// if (key.endsWith('_json')) {
// console.log(`${key}: ${value}`);
// data[i][key] = JSON.stringify(value);
// }
// }
// }
// }
// } else if (typeof data == 'object') {
// // console.log('Data is an object');
// for (const [key, value] of Object.entries(data)) {
// // console.log(key, value);
// if (key.endsWith('_json')) {
// console.log(`${key}: ${value}`);
// data[key] = JSON.stringify(value);
// }
// }
// }
if (log_lvl) {
console.log('Data:', data);
}
@@ -521,77 +338,9 @@ export const delete_ae_obj_id_crud = async function delete_ae_obj_id_crud({
data['jwt'] = jwt;
// NOTE: The key and or JWT should be in the header of the DELETE, GET, PATCH, POST
let endpoint = '';
if (obj_type == 'account') {
endpoint = `/crud/account/${obj_id}`;
} else if (obj_type == 'address') {
endpoint = `/crud/address/${obj_id}`;
} else if (obj_type == 'archive') {
endpoint = `/crud/archive/${obj_id}`;
} else if (obj_type == 'archive_content') {
endpoint = `/crud/archive/content/${obj_id}`;
} else if (obj_type == 'contact') {
endpoint = `/crud/contact/${obj_id}`;
} else if (obj_type == 'data_store') {
endpoint = `/crud/data_store/${obj_id}`;
} else if (obj_type == 'event') {
endpoint = `/crud/event/${obj_id}`;
} else if (obj_type == 'event_abstract') {
endpoint = `/crud/event/abstract/${obj_id}`;
} else if (obj_type == 'event_badge') {
endpoint = `/crud/event/badge/${obj_id}`;
} else if (obj_type == 'event_device') {
endpoint = `/crud/event/device/${obj_id}`;
} else if (obj_type == 'event_exhibit') {
endpoint = `/crud/event/exhibit/${obj_id}`;
} else if (obj_type == 'event_exhibit_tracking') {
endpoint = `/crud/event/exhibit/tracking/${obj_id}`;
} else if (obj_type == 'event_file') {
endpoint = `/crud/event/file/${obj_id}`;
} else if (obj_type == 'event_location') {
endpoint = `/crud/event/location/${obj_id}`;
} else if (obj_type == 'event_person') {
endpoint = `/crud/event/person/${obj_id}`;
} else if (obj_type == 'event_presentation') {
endpoint = `/crud/event/presentation/${obj_id}`;
} else if (obj_type == 'event_presenter') {
endpoint = `/crud/event/presenter/${obj_id}`;
} else if (obj_type == 'event_session') {
endpoint = `/crud/event/session/${obj_id}`;
} else if (obj_type == 'event_track') {
endpoint = `/crud/event/track/${obj_id}`;
} else if (obj_type == 'grant') {
endpoint = `/crud/grant/${obj_id}`;
} else if (obj_type == 'hosted_file') {
endpoint = `/crud/hosted_file/${obj_id}`;
} else if (obj_type == 'journal') {
endpoint = `/crud/journal/${obj_id}`;
} else if (obj_type == 'journal_entry') {
endpoint = `/crud/journal/entry/${obj_id}`;
} else if (obj_type == 'order') {
endpoint = `/crud/order/${obj_id}`;
} else if (obj_type == 'order_line') {
endpoint = `/crud/order/line/${obj_id}`;
} else if (obj_type == 'page') {
endpoint = `/crud/page/${obj_id}`;
} else if (obj_type == 'person') {
endpoint = `/crud/person/${obj_id}`;
} else if (obj_type == 'post') {
endpoint = `/crud/post/${obj_id}`;
} else if (obj_type == 'post_comment') {
endpoint = `/crud/post/comment/${obj_id}`;
} else if (obj_type == 'site') {
endpoint = `/crud/site/${obj_id}`;
} else if (obj_type == 'sponsorship_cfg') {
endpoint = `/crud/sponsorship/cfg/${obj_id}`;
} else if (obj_type == 'sponsorship') {
endpoint = `/crud/sponsorship/${obj_id}`;
// } else if (obj_type == 'user') {
// endpoint = `/crud/user/${obj_id}`;
} else {
console.log(`Unknown object type: ${obj_type}`);
return false;
}
// V3 Standard: Unified endpoint for all objects
const endpoint = `/v3/crud/${obj_type}/${obj_id}`;
if (log_lvl) {
console.log('Endpoint:', endpoint);
}
@@ -620,6 +369,7 @@ export const delete_ae_obj_id_crud = async function delete_ae_obj_id_crud({
return object_obj_delete_promise;
};
/* BEGIN: Hosted File Related */
// Updated 2026-01-07