security: remediate vulnerabilities and issues from codebase audit
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m15s

This commit is contained in:
Elijah 2026-05-23 10:04:08 -07:00
parent b60a09196a
commit fb3f3a3393
15 changed files with 465 additions and 208 deletions

View file

@ -159,7 +159,7 @@ function Tooltip({ children, text }: { children: React.ReactNode; text: string }
);
}
function ThumbnailImage({ checksum, fallbackIcon }: { checksum: string; fallbackIcon: React.ReactNode }) {
function ThumbnailImage({ checksum, fallbackIcon, downloadToken }: { checksum: string; fallbackIcon: React.ReactNode; downloadToken: string }) {
const [errorCount, setErrorCount] = useState(0);
const [key, setKey] = useState(0);
const [showFallback, setShowFallback] = useState(false);
@ -168,7 +168,7 @@ function ThumbnailImage({ checksum, fallbackIcon }: { checksum: string; fallback
<>
<img
key={key}
src={`${api.getThumbnailUrl(checksum)}${errorCount > 0 ? `?t=${key}` : ''}`}
src={downloadToken ? `${api.getThumbnailUrl(checksum, downloadToken)}${errorCount > 0 ? `&t=${key}` : ''}` : ''}
alt=""
className="w-full h-full object-cover"
style={{ display: showFallback ? 'none' : 'block' }}
@ -248,6 +248,26 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
const [selectionBox, setSelectionBox] = useState<{ startX: number; startY: number; currentX: number; currentY: number } | null>(null);
const [initialSelectedFiles, setInitialSelectedFiles] = useState<Set<string>>(new Set());
const searchTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
const [downloadToken, setDownloadToken] = useState<string>('');
// Fetch download token loop
useEffect(() => {
let mounted = true;
const fetchToken = async () => {
try {
const token = await api.createDownloadToken();
if (mounted) setDownloadToken(token);
} catch (e) {
console.error('Failed to refresh download token', e);
}
};
fetchToken();
const interval = setInterval(fetchToken, 4 * 60 * 1000); // 4 minutes
return () => {
mounted = false;
clearInterval(interval);
};
}, []);
const mainContainerRef = useRef<HTMLDivElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
@ -810,7 +830,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
if (isMedia && file.checksum && viewMode === 'grid') {
return (
<div className="relative w-full h-full flex items-center justify-center overflow-hidden rounded-md">
<ThumbnailImage checksum={file.checksum} fallbackIcon={fallbackIcon} />
<ThumbnailImage checksum={file.checksum} fallbackIcon={fallbackIcon} downloadToken={downloadToken} />
</div>
);
}
@ -1496,18 +1516,17 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
style={{ color: 'var(--color-text-primary)' }}
title="Download Selected"
onClick={() => {
if (!downloadToken) return;
const form = document.createElement('form');
form.method = 'POST';
form.action = api.getBulkDownloadUrl();
form.action = api.getBulkDownloadUrl(downloadToken);
form.target = '_blank';
Array.from(selectedFiles).forEach(path => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'paths';
input.value = path;
form.appendChild(input);
});
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'paths';
input.value = JSON.stringify(Array.from(selectedFiles));
form.appendChild(input);
document.body.appendChild(form);
form.submit();
@ -1598,17 +1617,27 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
setContextMenu(null);
}}
/>
<ContextMenuItem
icon={<Download className="w-4 h-4" />}
label="Download"
onClick={() => {
const downloadUrl = contextMenu.file.is_dir
? api.getDownloadFolderUrl(contextMenu.file.path)
: api.getDownloadUrl(contextMenu.file.path);
window.open(downloadUrl, '_blank');
setContextMenu(null);
}}
/>
{contextMenu.file.is_dir ? (
<ContextMenuItem
icon={<Download className="w-4 h-4" />}
label="Download Folder"
onClick={() => {
if (!downloadToken) return;
window.location.href = api.getDownloadFolderUrl(contextMenu.file.path, downloadToken);
setContextMenu(null);
}}
/>
) : (
<ContextMenuItem
icon={<Download className="w-4 h-4" />}
label="Download"
onClick={() => {
if (!downloadToken) return;
window.location.href = api.getDownloadUrl(contextMenu.file.path, downloadToken);
setContextMenu(null);
}}
/>
)}
<ContextMenuItem
icon={<Pencil className="w-4 h-4" />}
label="Rename"
@ -1767,7 +1796,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
</AnimatePresence>
<TaskManager />
<FilePreview file={previewFile} onClose={() => setPreviewFile(null)} />
<FilePreview file={previewFile} onClose={() => setPreviewFile(null)} downloadToken={downloadToken} />
<AnimatePresence>
{sharingFile && (
<ShareModal filePath={sharingFile} onClose={() => setSharingFile(null)} />