From 295886cec5afbbcc7855f17aafc0ebb9c34c3efe Mon Sep 17 00:00:00 2001 From: Andy-Sverdlov-LucaNet <141342766+Andy-Sverdlov-LucaNet@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:13:35 +0200 Subject: [PATCH] fix: lowercase the email before hashing the Gravatar URL The Gravatar specification hashes the trimmed, lowercased address. GetAvatarURL only trimmed it, so an account whose stored address contains an uppercase letter hashed to an address Gravatar does not know: the user's avatar was never found and the identicon fallback was rendered instead. selectedAvatar recomputes this URL from the stored address on every response, so this affected both default_avatar: gravatar and an explicit per-user avatar.type: gravatar, and a user could not work around it by re-selecting Gravatar in their profile. Nothing in the backend normalises a stored address, and the external login path copies the provider's address verbatim, so accounts created through an OIDC/OAuth2 connector inherit whatever casing the identity provider sends. The web UI already lowercases before hashing, so the Settings -> Profile preview showed the user's real avatar while every other surface showed an identicon. This removes that disagreement. The hash is computed on read, so existing accounts resolve correctly as soon as this ships. Stored addresses are left untouched. Co-Authored-By: Claude Opus 5 --- pkg/gravatar/gravatar.go | 2 +- pkg/gravatar/gravatar_test.go | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pkg/gravatar/gravatar.go b/pkg/gravatar/gravatar.go index 9c79b3872..30874f159 100644 --- a/pkg/gravatar/gravatar.go +++ b/pkg/gravatar/gravatar.go @@ -29,7 +29,7 @@ import ( // GetAvatarURL get avatar url from gravatar by email func GetAvatarURL(baseURL, email string) string { - hasher := sha256.Sum256([]byte(strings.TrimSpace(email))) + hasher := sha256.Sum256([]byte(strings.ToLower(strings.TrimSpace(email)))) hash := hex.EncodeToString(hasher[:]) return baseURL + hash } diff --git a/pkg/gravatar/gravatar_test.go b/pkg/gravatar/gravatar_test.go index b88a69649..9a51773a0 100644 --- a/pkg/gravatar/gravatar_test.go +++ b/pkg/gravatar/gravatar_test.go @@ -41,6 +41,21 @@ func TestGetAvatarURL(t *testing.T) { args: args{email: "answer@answer.com"}, want: "https://www.gravatar.com/avatar/7296942c1f63d97f6c124705142009867638f7b3dbcdadd0cb1bcb40e427eb8e", }, + { + name: "mixed case address", + args: args{email: "Answer@Answer.com"}, + want: "https://www.gravatar.com/avatar/7296942c1f63d97f6c124705142009867638f7b3dbcdadd0cb1bcb40e427eb8e", + }, + { + name: "upper case address", + args: args{email: "ANSWER@ANSWER.COM"}, + want: "https://www.gravatar.com/avatar/7296942c1f63d97f6c124705142009867638f7b3dbcdadd0cb1bcb40e427eb8e", + }, + { + name: "padded mixed case address", + args: args{email: " Answer@Answer.com "}, + want: "https://www.gravatar.com/avatar/7296942c1f63d97f6c124705142009867638f7b3dbcdadd0cb1bcb40e427eb8e", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {