fix(documents): defense-in-depth port_id scope + invisible chevron a11y
- renameFolder/moveFolder UPDATE and deleteFolderSoftRescue DELETE now carry an explicit port_id predicate so the write is bounded to the same tenancy the pre-fetch verified, defending against future refactors that drop or reorder the ownership check. - FolderRow's collapsed-children chevron is `invisible` for layout purposes, but it was still in the tab order with a misleading Expand/Collapse aria-label. Add aria-hidden + tabIndex=-1 when no children so keyboard users skip it. Surfaced by post-implementation review (subagent code-review pass). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,8 @@ function FolderRow({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={open ? 'Collapse' : 'Expand'}
|
aria-label={open ? 'Collapse' : 'Expand'}
|
||||||
|
aria-hidden={!hasChildren}
|
||||||
|
tabIndex={hasChildren ? 0 : -1}
|
||||||
onClick={() => setOpen((o) => !o)}
|
onClick={() => setOpen((o) => !o)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-5 w-5 items-center justify-center text-muted-foreground hover:text-foreground',
|
'flex h-5 w-5 items-center justify-center text-muted-foreground hover:text-foreground',
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export async function renameFolder(
|
|||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.update(documentFolders)
|
.update(documentFolders)
|
||||||
.set({ name: trimmed, updatedAt: new Date() })
|
.set({ name: trimmed, updatedAt: new Date() })
|
||||||
.where(eq(documentFolders.id, folderId))
|
.where(and(eq(documentFolders.id, folderId), eq(documentFolders.portId, portId)))
|
||||||
.returning();
|
.returning();
|
||||||
if (!updated) throw new NotFoundError('Folder');
|
if (!updated) throw new NotFoundError('Folder');
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ export async function moveFolder(
|
|||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.update(documentFolders)
|
.update(documentFolders)
|
||||||
.set({ parentId: newParentId, updatedAt: new Date() })
|
.set({ parentId: newParentId, updatedAt: new Date() })
|
||||||
.where(eq(documentFolders.id, folderId))
|
.where(and(eq(documentFolders.id, folderId), eq(documentFolders.portId, portId)))
|
||||||
.returning();
|
.returning();
|
||||||
if (!updated) throw new NotFoundError('Folder');
|
if (!updated) throw new NotFoundError('Folder');
|
||||||
|
|
||||||
@@ -262,7 +262,9 @@ export async function deleteFolderSoftRescue(
|
|||||||
.set({ folderId: newParent })
|
.set({ folderId: newParent })
|
||||||
.where(and(eq(documents.folderId, folderId), eq(documents.portId, portId)));
|
.where(and(eq(documents.folderId, folderId), eq(documents.portId, portId)));
|
||||||
|
|
||||||
await tx.delete(documentFolders).where(eq(documentFolders.id, folderId));
|
await tx
|
||||||
|
.delete(documentFolders)
|
||||||
|
.where(and(eq(documentFolders.id, folderId), eq(documentFolders.portId, portId)));
|
||||||
});
|
});
|
||||||
|
|
||||||
void createAuditLog({
|
void createAuditLog({
|
||||||
|
|||||||
Reference in New Issue
Block a user