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

@@ -88,9 +88,9 @@ export const webdavRoutes: FastifyPluginAsync = async (app) => {
resources.push(...children)
}
} else if (resolved.type === 'folder' && resolved.folder) {
// Check access
const hasAccess = await StoragePermissionService.canAccess(app.db, resolved.folder.id, request.user.id)
if (!hasAccess) return reply.status(403).send('Access Denied')
// Check access — traverse or higher lets you see the folder
const accessLevel = await StoragePermissionService.getAccessLevel(app.db, resolved.folder.id, request.user.id)
if (!accessLevel) return reply.status(403).send('Access Denied')
resources.push({
href: basePath.slice(0, -1) + '/',
@@ -100,12 +100,14 @@ export const webdavRoutes: FastifyPluginAsync = async (app) => {
createdAt: resolved.folder.createdAt ? new Date(resolved.folder.createdAt) : undefined,
})
if (depth !== '0') {
const children = await WebDavService.listChildren(app.db, resolved.folder.id, basePath)
// traverse: show subfolders only, no files. view+: show everything
const children = await WebDavService.listChildren(app.db, resolved.folder.id, basePath, accessLevel === 'traverse')
resources.push(...children)
}
} else if (resolved.type === 'file' && resolved.file && resolved.folder) {
const hasAccess = await StoragePermissionService.canAccess(app.db, resolved.folder.id, request.user.id)
if (!hasAccess) return reply.status(403).send('Access Denied')
// File access requires at least view (not traverse)
const accessLevel = await StoragePermissionService.getAccessLevel(app.db, resolved.folder.id, request.user.id)
if (!accessLevel || accessLevel === 'traverse') return reply.status(403).send('Access Denied')
resources.push({
href: `${WEBDAV_PREFIX}${resourcePath}`,
@@ -159,8 +161,8 @@ export const webdavRoutes: FastifyPluginAsync = async (app) => {
const resolved = await WebDavService.resolvePath(app.db, resourcePath)
if (resolved.type === 'file' && resolved.file && resolved.folder) {
const hasAccess = await StoragePermissionService.canAccess(app.db, resolved.folder.id, request.user.id)
if (!hasAccess) return reply.status(403).send('Access Denied')
const accessLevel = await StoragePermissionService.getAccessLevel(app.db, resolved.folder.id, request.user.id)
if (!accessLevel || accessLevel === 'traverse') return reply.status(403).send('Access Denied')
const data = await app.storage.get(resolved.file.path)
return reply