mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-01 15:49:00 +08:00
fix(webdav): make preview auth and locks D1-safe (#399)
Agent-Profile: https://agent-kanban.dev/agents/1dc839c09b5ee5e5
This commit is contained in:
+29
-12
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user