fix(webdav): make preview auth and locks D1-safe (#399)

Agent-Profile: https://agent-kanban.dev/agents/1dc839c09b5ee5e5
This commit is contained in:
Jasper Van
2026-05-12 12:28:53 -04:00
committed by GitHub
parent e41ea3f016
commit c218f90712
2 changed files with 43 additions and 19 deletions
+29 -12
View File
@@ -1,9 +1,10 @@
import { defaultKeyHasher } from '@better-auth/api-key'
import { and, eq, like, or } from 'drizzle-orm'
import type { Context } from 'hono'
import { Hono } from 'hono'
import { DirType, ObjectStatus } from '../../shared/constants'
import type { Storage as S3Storage } from '../../shared/types'
import { user } from '../db/auth-schema'
import { apikey, user } from '../db/auth-schema'
import { matters } from '../db/schema'
import type { Env } from '../middleware/platform'
import {
@@ -75,18 +76,28 @@ async function requireWebDavApiKey(c: DavContext): Promise<DavAuth | Response> {
if (!credentials) return unauthorized()
try {
// biome-ignore lint/suspicious/noExplicitAny: better-auth plugin API is not fully typed
const result = (await (c.get('auth').api as any).verifyApiKey({
body: { configId: WEBDAV_CONFIG_ID, key: credentials.password, permissions: { [WEBDAV_RESOURCE]: [action] } },
})) as {
valid: boolean
key: { referenceId: string } | null
error: { message?: string } | null
}
if (!result?.valid || !result.key?.referenceId) return unauthorized()
if (!(await usernameMatches(c.get('platform').db, result.key.referenceId, credentials.username)))
const db = c.get('platform').db
const hashedKey = await defaultKeyHasher(credentials.password)
const rows = await db
.select({
id: apikey.id,
referenceId: apikey.referenceId,
permissions: apikey.permissions,
enabled: apikey.enabled,
expiresAt: apikey.expiresAt,
})
.from(apikey)
.where(and(eq(apikey.configId, WEBDAV_CONFIG_ID), eq(apikey.key, hashedKey)))
.limit(1)
const key = rows[0]
if (
!key?.enabled ||
(key.expiresAt && key.expiresAt.getTime() <= Date.now()) ||
!hasWebDavPermission(key.permissions, action)
)
return unauthorized()
return { userId: result.key.referenceId }
if (!(await usernameMatches(db, key.referenceId, credentials.username))) return unauthorized()
return { userId: key.referenceId }
} catch {
return unauthorized()
}
@@ -117,6 +128,12 @@ function parseBasicAuth(header: string | null): { username: string; password: st
return { username, password }
}
function hasWebDavPermission(permissions: string | null, action: 'read' | 'write'): boolean {
if (!permissions) return false
const parsed = JSON.parse(permissions) as Partial<Record<string, string[]>>
return parsed[WEBDAV_RESOURCE]?.includes(action) ?? false
}
async function usernameMatches(
db: Env['Variables']['platform']['db'],
userId: string,
+14 -7
View File
@@ -275,8 +275,8 @@ export async function refreshLock(
const now = new Date()
const expiresAt = new Date(now.getTime() + timeoutSeconds * 1000)
const rows = await db
.update(webdavLocks)
.set({ expiresAt, updatedAt: now })
.select()
.from(webdavLocks)
.where(
and(
eq(webdavLocks.orgId, orgId),
@@ -289,14 +289,18 @@ export async function refreshLock(
),
),
)
.returning()
return rows[0] ?? null
.limit(1)
const lock = rows[0]
if (!lock) return null
await db.update(webdavLocks).set({ expiresAt, updatedAt: now }).where(eq(webdavLocks.id, lock.id))
return { ...lock, expiresAt, updatedAt: now }
}
export async function removeLock(db: Database, orgId: string, resourcePath: string, token: string): Promise<boolean> {
await purgeExpiredLocks(db)
const rows = await db
.delete(webdavLocks)
.select({ id: webdavLocks.id })
.from(webdavLocks)
.where(
and(
eq(webdavLocks.orgId, orgId),
@@ -309,8 +313,11 @@ export async function removeLock(db: Database, orgId: string, resourcePath: stri
),
),
)
.returning({ id: webdavLocks.id })
return rows.length > 0
.limit(1)
const lock = rows[0]
if (!lock) return false
await db.delete(webdavLocks).where(eq(webdavLocks.id, lock.id))
return true
}
async function purgeExpiredLocks(db: Database): Promise<void> {