* Authorize a destructive operation (delete) on a "public" asset context: * `profile-pictures`, `workspace-logos`, or `og-images`. These contexts are * world-readable, so verifyFileAccess short-circuits reads — but a write * must prove ownership of the user/workspace the object belongs to
( cloudKey: string, userId: string, context: 'profile-pictures' | 'og-images' | 'workspace-logos', customConfig?: StorageConfig )
| 285 | * and no user-facing delete path; always deny. |
| 286 | */ |
| 287 | async function verifyPublicAssetWriteAccess( |
| 288 | cloudKey: string, |
| 289 | userId: string, |
| 290 | context: 'profile-pictures' | 'og-images' | 'workspace-logos', |
| 291 | customConfig?: StorageConfig |
| 292 | ): Promise<boolean> { |
| 293 | try { |
| 294 | if (context === 'workspace-logos') { |
| 295 | const binding = await getFileMetadataByKey(cloudKey, 'workspace-logos') |
| 296 | if (!binding?.workspaceId) { |
| 297 | logger.warn('workspace-logos delete denied: no ownership binding', { userId, cloudKey }) |
| 298 | return false |
| 299 | } |
| 300 | const permission = await getUserEntityPermissions(userId, 'workspace', binding.workspaceId) |
| 301 | if (!workspacePermissionSatisfies(permission, true)) { |
| 302 | logger.warn('workspace-logos delete denied: write/admin required on owner workspace', { |
| 303 | userId, |
| 304 | workspaceId: binding.workspaceId, |
| 305 | cloudKey, |
| 306 | }) |
| 307 | return false |
| 308 | } |
| 309 | return true |
| 310 | } |
| 311 | |
| 312 | if (context === 'profile-pictures') { |
| 313 | const config: StorageConfig = customConfig || {} |
| 314 | const metadata = await getFileMetadata(cloudKey, config) |
| 315 | if (metadata.userId && metadata.userId === userId) { |
| 316 | return true |
| 317 | } |
| 318 | // Fail closed when the owner cannot be established. Distinguish a missing |
| 319 | // owner record (no `userId` metadata — e.g. an object predating owner |
| 320 | // tagging) from a genuine ownership mismatch so the denial is diagnosable. |
| 321 | if (!metadata.userId) { |
| 322 | logger.warn( |
| 323 | 'profile-pictures delete denied: file has no owner metadata to verify against', |
| 324 | { |
| 325 | userId, |
| 326 | cloudKey, |
| 327 | } |
| 328 | ) |
| 329 | } else { |
| 330 | logger.warn('profile-pictures delete denied: caller does not own the file', { |
| 331 | userId, |
| 332 | fileUserId: metadata.userId, |
| 333 | cloudKey, |
| 334 | }) |
| 335 | } |
| 336 | return false |
| 337 | } |
| 338 | |
| 339 | logger.warn('og-images delete denied: no user-facing delete path', { userId, cloudKey }) |
| 340 | return false |
| 341 | } catch (error) { |
| 342 | logger.error('Error verifying public asset write access', { cloudKey, userId, error }) |
| 343 | return false |
| 344 | } |
no test coverage detected