fix(web): attach JWT token to file download URL to fix auth error

Browser <a> tag downloads don't send Authorization headers, causing
"missing or malformed jwt" errors. Pass token via query param which
the backend JWT middleware already supports.
This commit is contained in:
Acbox
2026-03-29 17:43:42 +08:00
parent 6c2da4b2f5
commit d133a85fe3
2 changed files with 18 additions and 26 deletions
@@ -2,7 +2,7 @@
import { ref, watch, computed, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { toast } from 'vue-sonner'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { File, Download, X } from 'lucide-vue-next'
import { Button, Spinner } from '@memohai/ui'
import {
getBotsByBotIdContainerFsRead,
@@ -93,7 +93,8 @@ async function handleSave() {
}
function handleDownload() {
const url = `/api/bots/${props.botId}/container/fs/download?path=${encodeURIComponent(filePath.value)}`
const token = localStorage.getItem('token') ?? ''
const url = `/api/bots/${props.botId}/container/fs/download?path=${encodeURIComponent(filePath.value)}&token=${encodeURIComponent(token)}`
const a = document.createElement('a')
a.href = url
a.download = filename.value
@@ -128,8 +129,7 @@ onBeforeUnmount(() => {
<!-- Header -->
<div class="flex items-center justify-between border-b border-border px-4 py-2">
<div class="flex items-center gap-2 min-w-0">
<FontAwesomeIcon
:icon="['fas', 'file']"
<File
class="size-3.5 shrink-0 text-muted-foreground"
/>
<span class="truncate text-xs font-medium">{{ filename }}</span>
@@ -157,8 +157,7 @@ onBeforeUnmount(() => {
size="sm"
@click="handleDownload"
>
<FontAwesomeIcon
:icon="['fas', 'download']"
<Download
class="mr-1 size-3"
/>
{{ t('bots.files.download') }}
@@ -169,8 +168,7 @@ onBeforeUnmount(() => {
class="size-7 p-0"
@click="emit('close')"
>
<FontAwesomeIcon
:icon="['fas', 'xmark']"
<X
class="size-4"
/>
</Button>
@@ -209,8 +207,7 @@ onBeforeUnmount(() => {
v-else
class="flex h-full flex-col items-center justify-center gap-3 text-muted-foreground"
>
<FontAwesomeIcon
:icon="['fas', 'file']"
<File
class="size-12 opacity-30"
/>
<p class="text-xs">
@@ -221,8 +218,7 @@ onBeforeUnmount(() => {
size="sm"
@click="handleDownload"
>
<FontAwesomeIcon
:icon="['fas', 'download']"
<Download
class="mr-1.5 size-3"
/>
{{ t('bots.files.download') }}
+10 -14
View File
@@ -3,7 +3,7 @@ import { ref, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { toast } from 'vue-sonner'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { ChevronRight, Folder, Upload, FolderPlus, RefreshCw } from 'lucide-vue-next'
import { useSyncedQueryParam } from '@/composables/useSyncedQueryParam'
import {
Button,
@@ -238,7 +238,8 @@ async function handleDelete() {
// Download
function handleDownload(entry: HandlersFsFileInfo) {
const url = `/api/bots/${props.botId}/container/fs/download?path=${encodeURIComponent(entry.path ?? '')}`
const token = localStorage.getItem('token') ?? ''
const url = `/api/bots/${props.botId}/container/fs/download?path=${encodeURIComponent(entry.path ?? '')}&token=${encodeURIComponent(token)}`
const a = document.createElement('a')
a.href = url
a.download = entry.name ?? 'file'
@@ -282,20 +283,18 @@ defineExpose({ navigateTo, openFileByPath })
v-for="(seg, idx) in pathSegments(currentPath)"
:key="seg.path"
>
<FontAwesomeIcon
<ChevronRight
v-if="idx > 0"
:icon="['fas', 'chevron-right']"
class="size-2.5 shrink-0 text-muted-foreground"
/>
<button
class="truncate rounded px-1.5 py-0.5 hover:bg-muted transition-colors"
class="inline-flex items-center truncate rounded px-1.5 py-0.5 hover:bg-muted transition-colors"
:class="idx === pathSegments(currentPath).length - 1 ? 'font-medium text-foreground' : 'text-muted-foreground'"
@click="handleNavigate(seg.path)"
>
<FontAwesomeIcon
<Folder
v-if="idx === 0"
:icon="['fas', 'folder']"
class="mr-1 size-3"
class="mr-1 size-3 shrink-0"
/>
{{ seg.name }}
</button>
@@ -315,8 +314,7 @@ defineExpose({ navigateTo, openFileByPath })
size="sm"
@click="triggerUpload"
>
<FontAwesomeIcon
:icon="['fas', 'upload']"
<Upload
class="mr-1.5 size-3"
/>
{{ t('bots.files.upload') }}
@@ -326,8 +324,7 @@ defineExpose({ navigateTo, openFileByPath })
size="sm"
@click="openMkdirDialog"
>
<FontAwesomeIcon
:icon="['fas', 'folder-plus']"
<FolderPlus
class="mr-1.5 size-3"
/>
{{ t('bots.files.newFolder') }}
@@ -339,8 +336,7 @@ defineExpose({ navigateTo, openFileByPath })
:disabled="listLoading"
@click="() => loadDirectory(currentPath)"
>
<FontAwesomeIcon
:icon="['fas', 'rotate']"
<RefreshCw
class="size-3.5"
:class="{ 'animate-spin': listLoading }"
/>