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:
@@ -47,50 +47,8 @@ export async function get_ae_obj_id_crud({
|
||||
console.log(`*** get_ae_obj_id_crud() *** Type: ${obj_type} ID: ${obj_id}`);
|
||||
}
|
||||
|
||||
let endpoint = '';
|
||||
// Map object types to their respective CRUD endpoints
|
||||
const objTypeToEndpointMap: Record<string, string> = {
|
||||
'account': '/crud/account',
|
||||
'address': '/crud/address',
|
||||
'archive': '/crud/archive',
|
||||
'archive_content': '/crud/archive/content',
|
||||
'contact': '/crud/contact',
|
||||
'data_store': '/crud/data_store',
|
||||
'event': '/crud/event',
|
||||
'event_abstract': '/crud/event/abstract',
|
||||
'event_badge': '/crud/event/badge',
|
||||
'event_device': '/crud/event/device',
|
||||
'event_exhibit': '/crud/event/exhibit',
|
||||
'event_exhibit_tracking': '/crud/event/exhibit/tracking',
|
||||
'event_file': '/crud/event/file',
|
||||
'event_location': '/crud/event/location',
|
||||
'event_person': '/crud/event/person',
|
||||
'event_presentation': '/crud/event/presentation',
|
||||
'event_presenter': '/crud/event/presenter',
|
||||
'event_session': '/crud/event/session',
|
||||
'event_track': '/crud/event/track',
|
||||
'grant': '/crud/grant',
|
||||
'hosted_file': '/crud/hosted_file',
|
||||
'journal': '/crud/journal',
|
||||
'journal_entry': '/crud/journal/entry',
|
||||
'order': '/crud/order',
|
||||
'order_line': '/crud/order/line',
|
||||
'page': '/crud/page',
|
||||
'person': '/crud/person',
|
||||
'post': '/crud/post',
|
||||
'post_comment': '/crud/post/comment',
|
||||
'site': '/crud/site',
|
||||
'site_domain': '/crud/site/domain',
|
||||
'sponsorship_cfg': '/crud/sponsorship/cfg',
|
||||
'sponsorship': '/crud/sponsorship'
|
||||
};
|
||||
|
||||
if (objTypeToEndpointMap[obj_type]) {
|
||||
endpoint = `${objTypeToEndpointMap[obj_type]}/${obj_id}`;
|
||||
} else {
|
||||
console.error(`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 > 1) {
|
||||
console.log('Endpoint:', endpoint);
|
||||
|
||||
@@ -72,8 +72,25 @@ export const get_object = async function get_object({
|
||||
const merged_headers = { ...api_cfg['headers'], ...headers };
|
||||
|
||||
// Auto-promote account_id from api_cfg to header if missing
|
||||
if (!merged_headers['x-account-id'] && api_cfg['account_id']) {
|
||||
merged_headers['x-account-id'] = api_cfg['account_id'];
|
||||
let account_id = merged_headers['x-account-id'] || api_cfg['account_id'];
|
||||
|
||||
// IMMEDIATE ACCOUNT ID SCAVENGING: Read from localStorage to avoid race conditions
|
||||
if (!account_id && typeof localStorage !== 'undefined') {
|
||||
try {
|
||||
const ae_loc_raw = localStorage.getItem('ae_loc');
|
||||
if (ae_loc_raw) {
|
||||
const ae_loc_json = JSON.parse(ae_loc_raw);
|
||||
if (ae_loc_json.account_id) {
|
||||
account_id = ae_loc_json.account_id;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail on storage read
|
||||
}
|
||||
}
|
||||
|
||||
if (account_id) {
|
||||
merged_headers['x-account-id'] = account_id;
|
||||
}
|
||||
|
||||
// Handle "Bootstrap Paradox" for unauthenticated requests
|
||||
|
||||
@@ -51,8 +51,25 @@ export const patch_object = async function patch_object({
|
||||
const merged_headers = { ...api_cfg['headers'], ...headers };
|
||||
|
||||
// Auto-promote account_id from api_cfg to header if missing
|
||||
if (!merged_headers['x-account-id'] && api_cfg['account_id']) {
|
||||
merged_headers['x-account-id'] = api_cfg['account_id'];
|
||||
let account_id = merged_headers['x-account-id'] || api_cfg['account_id'];
|
||||
|
||||
// IMMEDIATE ACCOUNT ID SCAVENGING: Read from localStorage to avoid race conditions
|
||||
if (!account_id && typeof localStorage !== 'undefined') {
|
||||
try {
|
||||
const ae_loc_raw = localStorage.getItem('ae_loc');
|
||||
if (ae_loc_raw) {
|
||||
const ae_loc_json = JSON.parse(ae_loc_raw);
|
||||
if (ae_loc_json.account_id) {
|
||||
account_id = ae_loc_json.account_id;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail on storage read
|
||||
}
|
||||
}
|
||||
|
||||
if (account_id) {
|
||||
merged_headers['x-account-id'] = account_id;
|
||||
}
|
||||
|
||||
// Handle "Bootstrap Paradox" for unauthenticated requests
|
||||
|
||||
@@ -71,8 +71,25 @@ export const post_object = async function post_object({
|
||||
const merged_headers = { ...api_cfg['headers'], ...headers };
|
||||
|
||||
// Auto-promote account_id from api_cfg to header if missing
|
||||
if (!merged_headers['x-account-id'] && api_cfg['account_id']) {
|
||||
merged_headers['x-account-id'] = api_cfg['account_id'];
|
||||
let account_id = merged_headers['x-account-id'] || api_cfg['account_id'];
|
||||
|
||||
// IMMEDIATE ACCOUNT ID SCAVENGING: Read from localStorage to avoid race conditions
|
||||
if (!account_id && typeof localStorage !== 'undefined') {
|
||||
try {
|
||||
const ae_loc_raw = localStorage.getItem('ae_loc');
|
||||
if (ae_loc_raw) {
|
||||
const ae_loc_json = JSON.parse(ae_loc_raw);
|
||||
if (ae_loc_json.account_id) {
|
||||
account_id = ae_loc_json.account_id;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail on storage read
|
||||
}
|
||||
}
|
||||
|
||||
if (account_id) {
|
||||
merged_headers['x-account-id'] = account_id;
|
||||
}
|
||||
|
||||
// Handle "Bootstrap Paradox" for unauthenticated requests
|
||||
|
||||
@@ -137,7 +137,6 @@ async function _refresh_site_domain_v3_background({ api_cfg, fqdn, view, log_lvl
|
||||
|
||||
const auth_props = [
|
||||
'x-account-id',
|
||||
'x-aether-api-token',
|
||||
'Authorization',
|
||||
'authorization',
|
||||
'jwt',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -457,11 +457,8 @@ export const ae_api_data_struct: key_val = {
|
||||
};
|
||||
|
||||
const ae_api_headers: key_val = {};
|
||||
ae_api_headers['Access-Control-Allow-Origin'] = '*';
|
||||
ae_api_headers['Content-Yype'] = 'application/json';
|
||||
ae_api_headers['Content-Type'] = 'application/json';
|
||||
ae_api_headers['x-aether-api-key'] = ae_api_data_struct.api_secret_key;
|
||||
ae_api_headers['x-aether-api-token'] = 'fake-temp-token';
|
||||
ae_api_headers['x-aether-api-expire-on'] = '';
|
||||
if (ae_account_id) {
|
||||
ae_api_headers['x-account-id'] = ae_account_id;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user