Add traverse access level for folder navigation without file access

When a permission is set on a nested folder, traverse is automatically
granted on all ancestor folders so users can navigate to it. Traverse
only shows subfolders in listings — files are hidden. This prevents
orphaned permissions where a user has access to a nested folder but
can't reach it.

Hierarchy: traverse < view < edit < admin
This commit is contained in:
Ryan Moon
2026-03-29 18:04:24 -05:00
parent 51ca2ca683
commit f998b16a3f
9 changed files with 72 additions and 18 deletions

View File

@@ -97,7 +97,7 @@ export const storageRoutes: FastifyPluginAsync = async (app) => {
}
if (!roleId && !userId) throw new ValidationError('Either roleId or userId is required')
if (!accessLevel || !['view', 'edit', 'admin'].includes(accessLevel)) throw new ValidationError('accessLevel must be view, edit, or admin')
if (!accessLevel || !['traverse', 'view', 'edit', 'admin'].includes(accessLevel)) throw new ValidationError('accessLevel must be traverse, view, edit, or admin')
const perm = await StoragePermissionService.setPermission(app.db, id, roleId, userId, accessLevel)
return reply.status(201).send(perm)
@@ -139,8 +139,9 @@ export const storageRoutes: FastifyPluginAsync = async (app) => {
app.get('/storage/folders/:folderId/files', { preHandler: [app.authenticate, app.requirePermission('files.view')] }, async (request, reply) => {
const { folderId } = request.params as { folderId: string }
const hasAccess = await StoragePermissionService.canAccess(app.db, folderId, request.user.id)
if (!hasAccess) return reply.status(403).send({ error: { message: 'Access denied', statusCode: 403 } })
// traverse only allows navigating folders, not viewing files
const accessLevel = await StoragePermissionService.getAccessLevel(app.db, folderId, request.user.id)
if (!accessLevel || accessLevel === 'traverse') return reply.status(403).send({ error: { message: 'Access denied', statusCode: 403 } })
const params = PaginationSchema.parse(request.query)
const result = await StorageFileService.listByFolder(app.db, folderId, params)