feat(v3-auth): modernize hosted file access with simplified bypass pattern

- Roll out platform-wide standard for unauthenticated binary access using '?key=[account_id]' query parameter.
- Update API helpers (get, post, patch) to recognize 'key' bypass and strip account context headers accordingly.
- Refactor IDAA Bulletin Board to restore inline image rendering and edit-mode previews.
- Modernize Events Launcher (Layout, Sync, Session View) to use V3 Action URLs with verified auth.
- Update HTML generators in 'ae_utils.ts' to support the new authenticated URL structure.
- Harden 'ae_comp__event_file_obj_tbl' CSV export and clipboard links with V3 standard patterns.
This commit is contained in:
Scott Idem
2026-02-03 18:37:55 -05:00
parent 6634c9aef0
commit 0809ad3eac
21 changed files with 412 additions and 701 deletions

View File

@@ -564,23 +564,17 @@
log_lvl={1}
>
{#snippet label()}
<span>
<div>
<span class="fas fa-upload"></span>
<strong class="bg-green-100 p-1"
>Upload archive files</strong
>
<div class="flex flex-col items-center gap-2 py-2">
<div class="p-3 rounded-full bg-primary-500/10 text-primary-500">
<span class="fas fa-upload fa-lg"></span>
</div>
<span
class="text-sm text-gray-600 dark:text-gray-400 italic"
>
<strong>Aether hosted files only</strong><br />
Recommended: PowerPoint (pptx) or Keynote (key) or Adobe
PDF<br />
Media: audio (mp3, m4a) and video (mp4, mkv)<br />
Supplemental files: Word Doc, Excel, txt, etc
</span>
</span>
<div class="text-center">
<p class="font-bold text-primary-500">Upload archive files</p>
<p class="text-xs opacity-60">
Recommended: pptx, key, PDF, mp3, mp4, docx
</p>
</div>
</div>
{/snippet}
</Comp_hosted_files_upload>
</div>

View File

@@ -23,6 +23,8 @@
} from '$lib/stores/ae_stores';
import { idaa_loc, idaa_sess, idaa_slct } from '$lib/stores/ae_idaa_stores';
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
let ae_promises: key_val = $state({});
// let ae_tmp: key_val = {};
// let ae_triggers: key_val = {};
@@ -45,6 +47,7 @@
file_icons['mov'] = 'file-video';
file_icons['mp3'] = 'file-audio';
file_icons['mp4'] = 'file-video';
file_icons['mp4'] = 'file-video';
file_icons['pdf'] = 'file-pdf';
file_icons['png'] = 'file-image';
file_icons['ppt'] = 'file-powerpoint';
@@ -190,62 +193,14 @@
</button>
{#if $ae_loc.trusted_access && idaa_archive_content_obj?.hosted_file_id}
<button
type="button"
disabled={!$ae_loc.trusted_access}
onclick={() => {
ae_promises[idaa_archive_content_obj.hosted_file_id] =
api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id:
idaa_archive_content_obj.hosted_file_id,
return_file: true,
filename: idaa_archive_content_obj.filename,
auto_download: true
});
}}
class="novi_btn btn btn-sm lg:btn-md preset-tonal-primary hover:preset-filled-primary-500 min-w-72 lg:min-w-96"
title={`Download this file:\n${idaa_archive_content_obj.filename}\n[API] SHA256: ${idaa_archive_content_obj?.hash_sha256.slice(0, 10)}... Hosted ID: ${idaa_archive_content_obj.hosted_file_id} Archive Content ID: ${idaa_archive_content_obj.archive_content_id}`}
>
{#await ae_promises[idaa_archive_content_obj.hosted_file_id]}
<span class="fas fa-spinner fa-spin mx-1"></span>
<span class="">
Downloading
{#if $ae_sess.api_download_kv[idaa_archive_content_obj.hosted_file_id]}
{$ae_sess.api_download_kv[
idaa_archive_content_obj.hosted_file_id
].percent_completed}%
{/if}
:
</span>
{:then}
<span
class="fas fa-{ae_util.file_extension_icon(
idaa_archive_content_obj?.file_extension
)}"
></span>
{/await}
<span class="grow">
{ae_util.shorten_filename({
filename: idaa_archive_content_obj?.filename,
max_length: 30
})}
</span>
</button>
<!-- <a href="{$ae_loc.base_url}{idaa_archive_content_obj.hosted_file_path}" class="btn btn-md variant-ghost-secondary" title="Download this file">
<span class="fas fa-download m-1"></span>
Download
<span class="badge">
{#if ae_util.file_extension_icon(idaa_archive_content_obj?.file_extension)}
<span class="fas fa-{ae_util.file_extension_icon(idaa_archive_content_obj?.file_extension)} m-1"></span>
{:else}
<span class="fas fa-file"></span>
{/if}
.{idaa_archive_content_obj?.file_extension}</span>
</a> -->
<AE_Comp_Hosted_Files_Download_Button
hosted_file_id={idaa_archive_content_obj.hosted_file_id}
hosted_file_obj={idaa_archive_content_obj}
max_filename={30}
classes="novi_btn btn btn-sm lg:btn-md preset-tonal-primary hover:preset-filled-primary-500 min-w-72 lg:min-w-96"
linked_to_type="archive_content"
linked_to_id={idaa_archive_content_obj.archive_content_id}
/>
{/if}
{:else}
<span class="text-sm text-gray-500 italic" title="No file linked.">

View File

@@ -92,7 +92,7 @@
<source
id="view_archive_content_audio_source"
src="{$ae_api.base_url}/hosted_file/{$idaa_slct.archive_content_obj
?.hosted_file_id}/download?x_no_account_id_token=direct-download"
?.hosted_file_id}/download?key=${$ae_api.account_id}"
type="audio/mpeg"
/>
<!--<source src="audio.ogg" type="audio/ogg">-->
@@ -104,7 +104,7 @@
<video autoplay controls class="w-full h-auto">
<source
src="{$ae_api.base_url}/hosted_file/{$idaa_slct.archive_content_obj
?.hosted_file_id}/download?x_no_account_id_token=direct-download"
?.hosted_file_id}/download?key=${$ae_api.account_id}"
type="video/mp4"
/>
<!--<source src="video.ogg" type="video/ogg">-->
@@ -114,14 +114,14 @@
{:else if file_icons[$idaa_slct.archive_content_obj.file_extension] == 'file-image'}
<img
src="{$ae_api.base_url}/hosted_file/{$idaa_slct.archive_content_obj
?.hosted_file_id}/download?x_no_account_id_token=direct-download"
?.hosted_file_id}/download?key=${$ae_api.account_id}"
alt={$idaa_slct.archive_content_obj.name}
style="max-width: 100%; max-height: 65vh;"
/>
{:else}
<a
href="{$ae_api.base_url}/hosted_file/{$idaa_slct.archive_content_obj
?.hosted_file_id}/download?x_no_account_id_token=direct-download"
?.hosted_file_id}/download?key=${$ae_api.account_id}"
target="_blank"
class="
novi_btn

View File

@@ -33,6 +33,7 @@
import { posts_func } from '$lib/ae_posts/ae_posts_functions';
import AE_Comp_Editor_TipTap from '$lib/elements/AE_Comp_Editor_TipTap.svelte';
import Comp_hosted_files_upload from '$lib/ae_core/ae_comp__hosted_files_upload.svelte';
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
// let obj_changed = $state(false);
// let orig_post_obj: any = $state(null);
@@ -555,7 +556,7 @@ Copy and paste link: <a href="${link_base_url}?post_id=${$idaa_slct.post_id}">${
<span class="fas fa-paperclip"></span>
<span class="text-sm text-surface-600-400 italic">Linked files:</span>
{#each $idaa_slct.post_obj.linked_li_json as linked_obj, index}
<span>
<span class="flex flex-col items-center gap-1 p-1 border rounded-lg bg-white/50">
<!-- <a
href={linked_obj.url}
target="_blank"
@@ -566,142 +567,123 @@ Copy and paste link: <a href="${link_base_url}?post_id=${$idaa_slct.post_id}">${
({linked_obj.hosted_file_id_random})
</a> -->
{#if $ae_loc.authenticated_access && linked_obj?.hosted_file_id_random}
<button
type="button"
disabled={!$ae_loc.trusted_access}
onclick={() => {
ae_promises[linked_obj.hosted_file_id_random] =
api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id: linked_obj.hosted_file_id_random,
return_file: true,
filename: linked_obj.filename,
auto_download: true,
log_lvl: 0
});
{#if $ae_loc.authenticated_access}
{@const file_id = linked_obj?.hosted_file_id_random || linked_obj?.id || linked_obj?.hosted_file_id}
{#if file_id}
{@const ext = (linked_obj.extension || linked_obj.file_extension || ae_util.guess_file_extension(linked_obj.filename) || '').toLowerCase()}
{#if ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].includes(ext)}
<div class="mb-1">
<img
src="{$ae_api.base_url}/v3/action/hosted_file/{file_id}/download?key={$ae_api.account_id}"
alt={linked_obj.filename}
class="max-w-32 h-auto object-cover rounded shadow-sm border border-surface-500/20"
/>
</div>
{/if}
}}
class="novi_btn btn btn-sm lg:btn-md preset-tonal-tertiary border border-tertiary-500 hover:preset-filled-tertiary-500 min-w-48"
title={`Download this file:\n${linked_obj.filename}\n[API] SHA256: ${linked_obj?.hash_sha256.slice(0, 10)}... Hosted ID: ${linked_obj.hosted_file_id_random} Archive Content ID: ${linked_obj.archive_content_id}`}
>
{#await ae_promises[linked_obj.hosted_file_id_random]}
<span class="fas fa-spinner fa-spin mx-1"></span>
<span class="">
Downloading
{#if $ae_sess.api_download_kv[linked_obj.hosted_file_id_random]}
{$ae_sess.api_download_kv[
linked_obj.hosted_file_id_random
].percent_completed}%
{/if}
:
</span>
{:then}
<span
class="fas fa-{ae_util.file_extension_icon(
linked_obj?.extension
)}"
></span>
{/await}
<div class="flex flex-row items-center gap-1">
<AE_Comp_Hosted_Files_Download_Button
hosted_file_id={file_id}
hosted_file_obj={linked_obj}
color="tertiary"
variant="tonal"
classes="novi_btn btn-sm lg:btn-md min-w-48"
max_filename={30}
/>
<span class="grow">
{ae_util.shorten_filename({
filename: linked_obj?.filename,
max_length: 30
})}
</span>
</button>
<button
type="button"
disabled={!$ae_loc.trusted_access}
onclick={async () => {
if (
!confirm(
`Are you sure you want to delete this file?\n${linked_obj.filename} [${file_id}]`
)
) {
return false;
}
<button
type="button"
disabled={!$ae_loc.trusted_access}
onclick={async () => {
if (
!confirm(
`Are you sure you want to delete this file?\n${linked_obj.filename} [${linked_obj.hosted_file_id_random}]`
)
) {
return false;
}
// ae_promises[linked_obj.event_file_id_random] = handle_delete__event_file({event_file_id: linked_obj.event_file_id_random});
// ae_promises[linked_obj.event_file_id_random] = handle_delete__event_file({event_file_id: linked_obj.event_file_id_random});
// First - Attempt to delete the hosted file
ae_promises.delete__linked_obj = await core_func
.delete_ae_obj_id__hosted_file({
api_cfg: $ae_api,
hosted_file_id: linked_obj.hosted_file_id_random,
link_to_type: 'post',
link_to_id: $idaa_slct.post_id,
rm_orphan: true,
fake_delete: false,
log_lvl: log_lvl
})
.then(function (delete_result) {
// Second - If deleted, then update the post_obj
console.log(
`File removed. Now update the post_obj`
);
// We need to remove the record from the $idaa_slct.post_obj.hosted_file_obj_li and then update the post_obj.linked_li_json with the new value.
let delete_result_li =
$idaa_slct.post_obj.linked_li_json.filter(
function (hosted_file_obj) {
console.log(
`hosted_file_obj.hosted_file_id_random = ${hosted_file_obj.hosted_file_id_random}`
);
return (
hosted_file_obj.hosted_file_id_random !==
linked_obj.hosted_file_id_random
);
}
);
$idaa_slct.post_obj.hosted_file_obj_li =
delete_result_li;
$idaa_slct.post_obj.linked_li_json =
delete_result_li;
prom_api__post_obj = posts_func
.update_ae_obj__post({
// First - Attempt to delete the hosted file
ae_promises.delete__linked_obj = await core_func
.delete_ae_obj_id__hosted_file({
api_cfg: $ae_api,
post_id: $idaa_slct.post_id,
data_kv: {
linked_li_json: JSON.stringify(
$idaa_slct.post_obj.linked_li_json
)
},
hosted_file_id: file_id,
link_to_type: 'post',
link_to_id: $idaa_slct.post_id,
rm_orphan: true,
fake_delete: false,
log_lvl: log_lvl
})
.then(function (post_obj_update_result) {
// We need to do all of this since the DB object has changed and the SLCT object does automatically update (yet...??? Svelte 5?).
// $idaa_slct.post_obj = $lq__post_obj;
.then(function (delete_result) {
// Second - If deleted, then update the post_obj
console.log(
`File removed. Now update the post_obj`
);
// We need to remove the record from the $idaa_slct.post_obj.hosted_file_obj_li and then update the post_obj.linked_li_json with the new value.
let delete_result_li =
$idaa_slct.post_obj.linked_li_json.filter(
function (hosted_file_obj) {
const current_id = hosted_file_obj?.hosted_file_id_random || hosted_file_obj?.id || hosted_file_obj?.hosted_file_id;
console.log(
`Comparing: ${current_id} vs ${file_id}`
);
return (
current_id !==
file_id
);
}
);
$idaa_slct.post_obj.hosted_file_obj_li =
delete_result_li;
$idaa_slct.post_obj.linked_li_json =
delete_result_li;
prom_api__post_obj = posts_func
.update_ae_obj__post({
api_cfg: $ae_api,
post_id: $idaa_slct.post_id,
data_kv: {
linked_li_json: JSON.stringify(
$idaa_slct.post_obj.linked_li_json
)
},
log_lvl: log_lvl
})
.then(function (post_obj_update_result) {
// We need to do all of this since the DB object has changed and the SLCT object does automatically update (yet...??? Svelte 5?).
// $idaa_slct.post_obj = $lq__post_obj;
})
.catch(function (error: any) {
console.log('Something went wrong.');
console.log(error);
return false;
});
})
.catch(function (error: any) {
console.log('Something went wrong.');
console.log(error);
return false;
});
})
.catch(function (error: any) {
console.log('Something went wrong.');
console.log(error);
return false;
})
.finally(() => {});
}}
class="
novi_btn
btn btn-sm
preset-tonal-warning preset-outlined-warning-100-900 hover:preset-filled-warning-400-600
transition
"
title="Delete this file"
>
<span class="fas fa-trash-alt mx-1"></span>
<!-- <span class="fas fa-minus mx-1"></span> -->
Delete
</button>
})
.finally(() => {});
}}
class="
novi_btn
btn btn-sm
preset-tonal-warning preset-outlined-warning-100-900 hover:preset-filled-warning-400-600
transition
"
title="Delete this file"
>
<span class="fas fa-trash-alt mx-1"></span>
<!-- <span class="fas fa-minus mx-1"></span> -->
Delete
</button>
</div>
{/if}
{/if}
</span>
{/each}

View File

@@ -27,6 +27,7 @@
// import Comp__post_obj_id_edit from './ae_idaa_comp__post_obj_id_edit.svelte';
import Comp__post_comment_obj_id_edit from './ae_idaa_comp__post_comment_obj_id_edit.svelte';
import AE_Comp_Hosted_Files_Download_Button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
let ae_promises: key_val = $state({});
@@ -143,72 +144,28 @@
({linked_obj.hosted_file_id_random})
</a> -->
{#if $ae_loc.authenticated_access && linked_obj?.hosted_file_id_random}
{#if linked_obj.extension === 'png' || linked_obj.extension === 'jpg' || linked_obj.extension === 'jpeg' || linked_obj.extension === 'gif' || linked_obj.extension === 'webp' || linked_obj.extension === 'svg'}
<div>
<img
src="{$ae_api.base_url}/hosted_file/{linked_obj?.hosted_file_id_random}/download?x_no_account_id_token=direct-download"
alt={linked_obj.filename}
class="w-fit min-w-96 h-fit object-cover rounded-lg shadow-md"
{#if $ae_loc.authenticated_access}
{@const file_id = linked_obj?.hosted_file_id_random || linked_obj?.id || linked_obj?.hosted_file_id}
{#if file_id}
{@const ext = (linked_obj.extension || linked_obj.file_extension || ae_util.guess_file_extension(linked_obj.filename) || '').toLowerCase()}
{#if ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].includes(ext)}
<div>
<img
src="{$ae_api.base_url}/v3/action/hosted_file/{file_id}/download?key={$ae_api.account_id}"
alt={linked_obj.filename}
class="w-fit min-w-96 h-fit object-cover rounded-lg shadow-md"
/>
</div>
{:else}
<AE_Comp_Hosted_Files_Download_Button
hosted_file_id={file_id}
hosted_file_obj={linked_obj}
color="tertiary"
variant="tonal"
classes="border border-tertiary-500 hover:preset-filled-tertiary-500 min-w-48"
max_filename={30}
/>
<!-- <img src={api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id: linked_obj.hosted_file_id_random,
return_file: true,
filename: linked_obj.filename,
auto_download: false,
log_lvl: 1
})
}
alt={linked_obj.filename}
class="w-fit min-w-96 h-fit object-cover rounded-lg shadow-md"
/> -->
</div>
{:else}
<button
type="button"
disabled={!$ae_loc.trusted_access}
onclick={() => {
ae_promises[linked_obj.hosted_file_id_random] =
api.download_hosted_file({
api_cfg: $ae_api,
hosted_file_id: linked_obj.hosted_file_id_random,
return_file: true,
filename: linked_obj.filename,
auto_download: true,
log_lvl: 0
});
}}
class="novi_btn btn btn-sm lg:btn-md preset-tonal-tertiary border border-tertiary-500 hover:preset-filled-tertiary-500 min-w-48"
title={`Download this file:\n${linked_obj.filename}\n[API] SHA256: ${linked_obj?.hash_sha256.slice(0, 10)}... Hosted ID: ${linked_obj.hosted_file_id_random} Archive Content ID: ${linked_obj.archive_content_id}`}
>
{#await ae_promises[linked_obj.hosted_file_id_random]}
<span class="fas fa-spinner fa-spin mx-1"></span>
<span class="">
Downloading
{#if $ae_sess.api_download_kv[linked_obj.hosted_file_id_random]}
{$ae_sess.api_download_kv[
linked_obj.hosted_file_id_random
].percent_completed}%
{/if}
:
</span>
{:then}
<span
class="fas fa-{ae_util.file_extension_icon(
linked_obj?.extension
)}"
></span>
{/await}
<span class="grow">
{ae_util.shorten_filename({
filename: linked_obj?.filename,
max_length: 30
})}
</span>
</button>
{/if}
{/if}
{/if}
{/each}