Avatars: from Gravatar by the primary email first, from an uploaded image later #7
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#7
Loading…
Add table
Add a link
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?
Observation
What exists today
Nothing. The shell shows the account as text —
user.display_nameinsrc/postulo/templates/base.html— and the profile page has no image. There is no image handling anywhere in the interface.Two things that shape the design are already true, checked rather than assumed:
img-src 'self' data:(src/postulo/config/settings/prod.py). A Gravatar image referenced by URL in the page would not load at all. Relaxing the policy to admitgravatar.comis possible, but see the next point for why it is the wrong fix.Stage one: Gravatar, by the primary address
Fetch it server-side, once, and serve it ourselves. Not
<img src="https://gravatar.com/avatar/…">.The reason is the project's own principle: Postulo makes no outbound request on its own, and it stores the personal documents of people who are, right now, exposed. Embedding a Gravatar URL in the page makes every page view a request from the reader's browser to Automattic, carrying their IP and a hash of their email — and the hash of an address is reversible for any address that appears in a breach corpus. A server-side fetch means one request, from the server, when the person turns the feature on, and never again until they change address or ask for a refresh. It also keeps the CSP exactly as it is.
?d=404&s=256so an absent avatar is a clean miss rather than a generated placeholder.data/media/avatars/<user>/…) and serve it throughserve_private_file(src/postulo/core/files.py), same as every other personal file. Cache headers can be friendlier than for CVs — it is not sensitive — but it still never comes from the web server directly.Stage two: an uploaded image
Profile.avataras anImageFieldunder private media, with what an upload of a personal photograph needs:A third source arrives with #6 for free: OIDC providers commonly send a
pictureclaim. Worth wiring once the other two exist, behind the same opt-in.Where it shows, and where it deliberately does not
Other touches
src/postulo/core/export.py,importer.py): the avatar file joins the media archive and theaccountsection, so it survives a move between instances.seed_demo: an initials avatar needs nothing; leave Gravatar off for the fictional persona.A note on modularity
The mission is a plugin interface wherever a choice could reasonably vary, and it is worth saying explicitly why this is not one. Two sources — a lookup and an upload — with a fixed precedence are core behaviour with no plausible third implementation that a separately installed package would provide better than a claim from #6. An "avatar source" plugin group would be modularity by reflex. If that ever changes, the precedence list is the seam.
Classification
An enhancement. Not breaking: a nullable field and a checkbox, off by default; no existing behaviour changes.
Open questions
?r=g) at all — a personal avatar on a personal instance arguably needs no rating filter.