fix(send): refresh routes and gate file access

This commit is contained in:
shuaiplus
2026-07-05 23:44:07 +08:00
parent d9a36fefe6
commit 8c481a1564
4 changed files with 31 additions and 12 deletions
+7 -1
View File
@@ -8,6 +8,7 @@ import { LIMITS } from '../config/limits';
import {
getBlobStorageMaxBytes,
getSendFileObjectKey,
getBlobObject,
putBlobObject,
deleteBlobObject,
} from '../services/blob-store';
@@ -82,8 +83,13 @@ async function processSendFileUpload(
return upload;
}
const path = getSendFileObjectKey(send.id, fileId);
if (await getBlobObject(env, path)) {
return errorResponse('Send file has already been uploaded', 409);
}
try {
await putBlobObject(env, getSendFileObjectKey(send.id, fileId), upload.body, {
await putBlobObject(env, path, upload.body, {
size: upload.size,
contentType: upload.contentType,
customMetadata: {
+10 -2
View File
@@ -290,12 +290,20 @@ export async function handleDownloadSendFile(
}
const storage = new StorageService(env.DB);
const send = await storage.getSend(sendId);
if (!send || !isSendAvailable(send) || send.type !== SendType.File) {
return errorResponse(SEND_INACCESSIBLE_MSG, 404);
}
const data = parseStoredSendData(send);
const expectedFileId = typeof data.id === 'string' ? data.id : null;
if (!expectedFileId || expectedFileId !== fileId) {
return errorResponse(SEND_INACCESSIBLE_MSG, 404);
}
const object = await getBlobObject(env, getSendFileObjectKey(sendId, fileId));
if (!object) {
return errorResponse('Send file not found', 404);
}
const send = await storage.getSend(sendId);
const data = send ? parseStoredSendData(send) : {};
const fileName = typeof data.fileName === 'string' ? data.fileName : fileId;
const firstUse = await storage.consumeAttachmentDownloadToken(`send:${claims.jti}`, claims.exp);
+6 -2
View File
@@ -93,9 +93,13 @@ export async function incrementSendAccessCount(db: D1Database, sendId: string):
const result = await db
.prepare(
'UPDATE sends SET access_count = access_count + 1, updated_at = ? ' +
'WHERE id = ? AND (max_access_count IS NULL OR access_count < max_access_count)'
'WHERE id = ? ' +
'AND disabled = 0 ' +
'AND (max_access_count IS NULL OR access_count < max_access_count) ' +
'AND (expiration_date IS NULL OR expiration_date > ?) ' +
'AND deletion_date > ?'
)
.bind(now, sendId)
.bind(now, sendId, now, now)
.run();
return (result.meta.changes ?? 0) > 0;
}
+8 -7
View File
@@ -228,6 +228,7 @@ export default function App() {
hint: null,
});
const [inviteCodeFromUrl, setInviteCodeFromUrl] = useState(initialInviteCode);
const [hashPathRaw, setHashPathRaw] = useState(() => (typeof window !== 'undefined' ? window.location.hash || '' : ''));
const [unlockPassword, setUnlockPassword] = useState('');
const [pendingTotp, setPendingTotp] = useState<PendingTotp | null>(null);
const [pendingTotpMode, setPendingTotpMode] = useState<'login' | 'unlock' | null>(null);
@@ -295,15 +296,16 @@ export default function App() {
}, [pushToast]);
useEffect(() => {
const syncInviteFromUrl = () => {
const syncUrlState = () => {
setInviteCodeFromUrl(readInviteCodeFromUrl());
setHashPathRaw(window.location.hash || '');
};
syncInviteFromUrl();
window.addEventListener('hashchange', syncInviteFromUrl);
window.addEventListener('popstate', syncInviteFromUrl);
syncUrlState();
window.addEventListener('hashchange', syncUrlState);
window.addEventListener('popstate', syncUrlState);
return () => {
window.removeEventListener('hashchange', syncInviteFromUrl);
window.removeEventListener('popstate', syncInviteFromUrl);
window.removeEventListener('hashchange', syncUrlState);
window.removeEventListener('popstate', syncUrlState);
};
}, []);
@@ -1862,7 +1864,6 @@ export default function App() {
await pendingAuthRequestsQuery.refetch();
};
const hashPathRaw = typeof window !== 'undefined' ? window.location.hash || '' : '';
const hashPath = hashPathRaw.startsWith('#') ? hashPathRaw.slice(1) : hashPathRaw;
const hashPathOnly = String(hashPath || '').split('?')[0].split('#')[0];
const trimmedHashPath = hashPathOnly.replace(/^\/+/, '').replace(/\/+$/, '');