Files
HealthLog/prisma/migrations/0085_v155_user_avatar/migration.sql
T
Marc-André BombeckandClaude Opus 4.7 ca1c52ffcb feat(avatar): self-hosted avatar storage replacing the Gravatar leak
The previous /me payload carried a gravatarUrl built from
SHA-256(email) and pointed every authenticated page-load at
www.gravatar.com — Automattic could correlate the digest against
its known-email table on each request. This release moves the
avatar in-house: the user uploads through POST /api/user/avatar
(multipart, JPEG/PNG/WebP, 2 MiB / 2048×2048 max), the bytes ride
on the User row alongside a content-type and updatedAt timestamp,
and GET /api/user/avatar/{id} serves them from same-origin with
the cache-busting timestamp on the profile response.

The image-header parsing is hand-rolled in src/lib/avatar.ts so
the standalone image does not gain a native dep just to read width
and height. Validation rejects oversize / wrong content-type at
the multipart parse boundary; DELETE clears the row. The bytes
ride pg_dump alongside the rest of the row so the existing weekly
encrypted backup path already covers them.

The sidebar, the coach panel, the use-auth hook, and the disable-
coach card stop reading gravatarUrl; src/lib/gravatar.ts retires.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:39:20 +02:00

25 lines
1.1 KiB
SQL

-- v1.5.5 — self-hosted avatar storage.
--
-- Replaces the Gravatar third-party leak. The previous /me payload
-- carried a `gravatarUrl` built from `SHA-256(email)` and pointed
-- every authenticated page-load at www.gravatar.com — Automattic
-- could correlate the digest against its known-email table on each
-- request. The new path stores the image bytes on the User row and
-- serves them from same-origin.
--
-- Three nullable columns; null on all three = the user has not
-- uploaded an avatar and the profile response returns
-- `avatarUrl: null`. The upload route guards the size + content-
-- type + dimensions before the row write, so the column-level
-- constraints intentionally stay loose (the validation is enforced
-- at the API layer where it can return a useful 4xx).
--
-- The bytes ride in pg_dump alongside the rest of the row, so the
-- standard backup path already covers them. No new filesystem
-- volume to mount, no new Docker compose change.
ALTER TABLE "users"
ADD COLUMN "avatar_bytes" BYTEA,
ADD COLUMN "avatar_content_type" TEXT,
ADD COLUMN "avatar_updated_at" TIMESTAMP(3);