(
documentsToDelete: Array<{ id: string; fileUrl: string | null; workspaceId?: string | null }>,
requestId: string
)
| 1845 | } |
| 1846 | |
| 1847 | export async function deleteDocumentStorageFiles( |
| 1848 | documentsToDelete: Array<{ id: string; fileUrl: string | null; workspaceId?: string | null }>, |
| 1849 | requestId: string |
| 1850 | ): Promise<void> { |
| 1851 | const entries = documentsToDelete.map((doc) => ({ |
| 1852 | doc, |
| 1853 | storageKey: getKnowledgeBaseStorageKey(doc.fileUrl), |
| 1854 | })) |
| 1855 | |
| 1856 | // Resolve all kb/ ownership bindings in one query (avoids an N+1 across the |
| 1857 | // delete fan-out below). |
| 1858 | const kbKeys = [ |
| 1859 | ...new Set( |
| 1860 | entries |
| 1861 | .map((entry) => entry.storageKey) |
| 1862 | .filter((key): key is string => typeof key === 'string' && key.startsWith('kb/')) |
| 1863 | ), |
| 1864 | ] |
| 1865 | const ownerByKey = new Map<string, string | null>() |
| 1866 | if (kbKeys.length > 0) { |
| 1867 | const bindings = await getFileMetadataByKeys(kbKeys, 'knowledge-base') |
| 1868 | for (const binding of bindings) { |
| 1869 | ownerByKey.set(binding.key, binding.workspaceId) |
| 1870 | } |
| 1871 | } |
| 1872 | |
| 1873 | await Promise.allSettled( |
| 1874 | entries.map(async ({ doc, storageKey }) => { |
| 1875 | if (!storageKey) { |
| 1876 | return |
| 1877 | } |
| 1878 | |
| 1879 | // Only delete a kb/ object when its trusted ownership binding confirms the |
| 1880 | // deleting document's workspace owns it. Prevents deleting another tenant's |
| 1881 | // object via a document with a planted fileUrl. |
| 1882 | if (storageKey.startsWith('kb/')) { |
| 1883 | const bindingWorkspaceId = ownerByKey.get(storageKey) |
| 1884 | if (!bindingWorkspaceId) { |
| 1885 | logger.warn(`[${requestId}] Skipping storage delete: no ownership binding for key`, { |
| 1886 | documentId: doc.id, |
| 1887 | storageKey, |
| 1888 | }) |
| 1889 | return |
| 1890 | } |
| 1891 | if (!doc.workspaceId || bindingWorkspaceId !== doc.workspaceId) { |
| 1892 | logger.warn(`[${requestId}] Skipping storage delete: ownership binding mismatch`, { |
| 1893 | documentId: doc.id, |
| 1894 | storageKey, |
| 1895 | bindingWorkspaceId, |
| 1896 | documentWorkspaceId: doc.workspaceId ?? null, |
| 1897 | }) |
| 1898 | return |
| 1899 | } |
| 1900 | } |
| 1901 | |
| 1902 | try { |
| 1903 | await deleteFile({ key: storageKey, context: 'knowledge-base' }) |
| 1904 | await deleteFileMetadata(storageKey) |
no test coverage detected