Files
zpan/server/http/avatar-blobs.ts
T
saltbo 3064403c92 fix(ids): make normalization optional
Keep historical identifiers readable and addressable while generating all new entity IDs with Base62. Reject unsafe external ID references and preserve API keys and device codes during optional normalization.
2026-08-05 13:06:41 -04:00

26 lines
1.2 KiB
TypeScript

import type { Context } from 'hono'
import { OPAQUE_ID_PATTERN } from '../../shared/ids'
import type { Env } from '../middleware/platform'
import { PUBLIC_IMAGES_BINDING, type R2BucketLike } from '../platform/interface'
// Public read of a self-hosted avatar blob from the PUBLIC_IMAGES R2 binding. Only used on
// Cloudflare when the binding is present and PUBLIC_IMAGES_URL is NOT set (e.g. local
// miniflare, which gives R2 no public URL); with a custom domain set, or on Node/Docker
// (Cloud avatar service), the stored URL is absolute and this route is never hit.
export async function serveAvatarBlob(c: Context<Env>) {
const scope = c.req.param('scope')
const id = c.req.param('id')
if ((scope !== 'user' && scope !== 'team') || !id || !OPAQUE_ID_PATTERN.test(id)) return c.body(null, 404)
const bucket = c.get('platform').getBinding<R2BucketLike>(PUBLIC_IMAGES_BINDING)
if (!bucket) return c.body(null, 404)
const obj = await bucket.get(`${scope}/${id}`)
if (!obj) return c.body(null, 404)
return c.body(await obj.arrayBuffer(), 200, {
'Content-Type': obj.httpMetadata?.contentType ?? 'application/octet-stream',
'Cache-Control': 'public, max-age=31536000, immutable',
})
}