Allow user-uploaded square avatar (server-side center-crop, circular display, Regenerate/Remove preserved) #601
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#601
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What's requested
Let a user upload their own avatar image on their profile, complementing the generated SVG avatar. The upload is a square image stored server-side, center-cropped to square, and displayed in the same circle treatment as generated avatars, with the existing Regenerate / Remove semantics preserved and the existing cache-busting contract upheld.
Current state (evidence)
internal/identity/avatar.go:53: "SVGs, never uploads (unlike #359 org avatars, which accept PNG/JPEG/GIF/WebP uploads and reject SVG)". The service writesusers/<username>/avatar.svg(UserAvatarKey, avatar.go:191).RegenerateUserAvatar(avatar.go:325) — POST, synchronous, self-or-admin.DeleteUserAvatar(avatar.go:284) — DELETE, opts out (avatar_disabled), so later logins do NOT regenerate until the user regenerates; idempotent./api/v1/users/{principal}/avatar(internal/identity/http.go:402); SDKrepos.users.avatar.{url,regenerate,remove}(web/sdk/src/users.js:37-50).?v=<avatar_updated_at>(avatar.go:196-201, the #359 pattern — immutable cache class, pointer timestamp busts it); the GET handler sets the matching ETaguser-avatar-<AvatarUpdatedAt>(http.go:438). Any upload path MUST bumpAvatarUpdatedAtor clients serve stale avatars.internal/identity/orgs.go:104-129:maxOrgAvatarBytes2 MiB cap (413 over cap), magic-sniff allowlist PNG/JPEG/GIF/WebP (415 otherwise, SVG rejected for same-origin script-execution reasons), single-flight hazard documented at orgs.go:375-388; PUT route at http.go:628.rounded-full h-8 w-8circle with initial fallback (web/src/components/IdentityMenu.jsx:94-105); the profile-page asides gate onavatar_content_typeand never probe bytes (web/src/pages/Repos.jsx:369-382); self-service Regenerate/Remove controls live on the owner profile page gated onisSelf(Repos.jsx:425-460).Architecture notes
PutOrgAvatar: same 2 MiB cap, same PNG/JPEG/GIF/WebP magic-sniff allowlist, same SVG rejection (the existing comment's prohibition was against SVG uploads for users, not against uploads generally — amend the comment to state the new design).AvatarContentType). The implementer may choose the processing approach (pure-Go image package vs. a dependency) — if it needs a new module dependency, flag it as a dependency-law decision for the user, do not add silently./api/v1/users/{principal}/avatarpath gains the upload verb. Note the current route set is GET/POST/DELETE; an upload is conventionally PUT (matching the org twin, http.go:628) but POST is defensible — implementer's call, keep it consistent with the org-avatar twin and wire all three route twins if any handler registration pattern requires it.avatar_url(discoveryme()payload,internal/api/discovery.go:158-174) already derives fromAvatarUpdatedAt, so a bump on upload flows through — verify the ETag coverage on any cached response carrying the field rather than assuming.Acceptance criteria
AvatarUpdatedAtbumps on upload; navbar/profile refetches show the new avatar without a stale?v=hit (ETag + cache-bust verified).internal/identity/avatar.go:53is amended to match the new design.Fixed by #608 (merged): PUT avatar upload (PNG/JPEG/GIF, 2 MiB + 4096px/16Mpx bomb guards, stdlib center-crop to square PNG); WebP/SVG 415; AvatarUpdatedAt bumped; Regenerate replaces upload; Remove opts out both kinds; UI upload beside Regenerate/Remove (isSelf). Review caught + fixed a decompression-bomb hole pre-merge (no pixel bound → DecodeConfig gate). Verified: identity 95.5% cover, -race green, 1546 web unit green (smoke excluded, pre-existing), vite/esbuild green, independent review APPROVE.