security: remediate vulnerabilities and issues from codebase audit
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m15s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m15s
This commit is contained in:
parent
b60a09196a
commit
fb3f3a3393
15 changed files with 465 additions and 208 deletions
|
|
@ -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)} />
|
||||
|
|
|
|||
Reference in a new issue