From 4a75a503703a481a998136021ef1fd495ddc5f91 Mon Sep 17 00:00:00 2001 From: Thomas Miceli <27960254+thomiceli@users.noreply.github.com> Date: Fri, 26 May 2023 09:15:37 +0200 Subject: [PATCH] Disable Gravatar (#37) * Disable Gravatar * Lowercase emails * Add migration --- README.md | 2 +- fs_embed.go | 2 +- internal/models/admin_setting.go | 1 + internal/models/db.go | 1 + internal/models/migration.go | 7 +++++ internal/models/user.go | 35 ++++++++++++++++++++-- internal/web/auth.go | 44 +++++++++++++++++++++++++++- internal/web/gist.go | 14 +++++++++ internal/web/run.go | 24 +++++++++++---- internal/web/settings.go | 2 +- public/default.png | Bin 0 -> 15655 bytes public/main.ts | 1 + templates/pages/admin_settings.html | 11 +++++++ templates/pages/all.html | 2 +- templates/pages/forks.html | 2 +- templates/pages/likes.html | 2 +- templates/pages/revisions.html | 5 ++-- 17 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 public/default.png diff --git a/README.md b/README.md index cb8d923..6f0fabb 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic * Editor with indentation mode & size ; drag and drop files * Download raw files or as a ZIP archive * OAuth2 login with GitHub and Gitea -* Avatars +* Avatars via Gravatar or OAuth2 providers * Responsive UI * Enable or disable signups * Restrict or unrestrict snippets visibility to anonymous users diff --git a/fs_embed.go b/fs_embed.go index a54d661..30d7fd4 100644 --- a/fs_embed.go +++ b/fs_embed.go @@ -4,5 +4,5 @@ package main import "embed" -//go:embed templates/*/*.html public/manifest.json public/assets/*.js public/assets/*.css public/assets/*.svg +//go:embed templates/*/*.html public/manifest.json public/assets/*.js public/assets/*.css public/assets/*.svg public/assets/*.png var dirFS embed.FS diff --git a/internal/models/admin_setting.go b/internal/models/admin_setting.go index 0cfae52..e23e938 100644 --- a/internal/models/admin_setting.go +++ b/internal/models/admin_setting.go @@ -13,6 +13,7 @@ const ( SettingDisableSignup = "disable-signup" SettingRequireLogin = "require-login" SettingDisableLoginForm = "disable-login-form" + SettingDisableGravatar = "disable-gravatar" ) func GetSetting(key string) (string, error) { diff --git a/internal/models/db.go b/internal/models/db.go index 540d539..4a417ca 100644 --- a/internal/models/db.go +++ b/internal/models/db.go @@ -30,6 +30,7 @@ func Setup(dbpath string) error { SettingDisableSignup: "0", SettingRequireLogin: "0", SettingDisableLoginForm: "0", + SettingDisableGravatar: "0", }) } diff --git a/internal/models/migration.go b/internal/models/migration.go index d4d7629..3240bf0 100644 --- a/internal/models/migration.go +++ b/internal/models/migration.go @@ -28,6 +28,7 @@ func ApplyMigrations(db *gorm.DB) error { Func func(*gorm.DB) error }{ {1, v1_modifyConstraintToSSHKeys}, + {2, v2_lowercaseEmails}, // Add more migrations here as needed } @@ -94,3 +95,9 @@ func v1_modifyConstraintToSSHKeys(db *gorm.DB) error { renameSQL := `ALTER TABLE ssh_keys_temp RENAME TO ssh_keys;` return db.Exec(renameSQL).Error } + +func v2_lowercaseEmails(db *gorm.DB) error { + // Copy the lowercase emails into the new column + copySQL := `UPDATE users SET email = lower(email);` + return db.Exec(copySQL).Error +} diff --git a/internal/models/user.go b/internal/models/user.go index 73682e2..9208d0d 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -12,6 +12,7 @@ type User struct { CreatedAt int64 Email string MD5Hash string // for gravatar, if no Email is specified, the value is random + AvatarURL string GithubID string GiteaID string @@ -81,6 +82,30 @@ func GetUserById(userId uint) (*User, error) { return user, err } +func GetUsersFromEmails(emailsSet map[string]struct{}) (map[string]*User, error) { + var users []*User + + emails := make([]string, 0, len(emailsSet)) + for email := range emailsSet { + emails = append(emails, email) + } + + err := db. + Where("email IN ?", emails). + Find(&users).Error + + if err != nil { + return nil, err + } + + userMap := make(map[string]*User) + for _, user := range users { + userMap[user.Email] = user + } + + return userMap, nil +} + func SSHKeyExistsForUser(sshKey string, userId uint) (*SSHKey, error) { key := new(SSHKey) err := db. @@ -135,9 +160,15 @@ func (user *User) HasLiked(gist *Gist) (bool, error) { func (user *User) DeleteProviderID(provider string) error { switch provider { case "github": - return db.Model(&user).Update("github_id", nil).Error + return db.Model(&user). + Update("github_id", nil). + Update("avatar_url", nil). + Error case "gitea": - return db.Model(&user).Update("gitea_id", nil).Error + return db.Model(&user). + Update("gitea_id", nil). + Update("avatar_url", nil). + Error } return nil diff --git a/internal/web/auth.go b/internal/web/auth.go index a904ce4..2db5af0 100644 --- a/internal/web/auth.go +++ b/internal/web/auth.go @@ -3,6 +3,7 @@ package web import ( "context" "crypto/md5" + "encoding/json" "errors" "fmt" "github.com/labstack/echo/v4" @@ -139,12 +140,14 @@ func oauthCallback(ctx echo.Context) error { currUser := getUserLogged(ctx) if currUser != nil { - // if user is logged in, link account to user + // if user is logged in, link account to user and update its avatar URL switch user.Provider { case "github": currUser.GithubID = user.UserID + currUser.AvatarURL = getAvatarUrlFromProvider("github", user.UserID) case "gitea": currUser.GiteaID = user.UserID + currUser.AvatarURL = getAvatarUrlFromProvider("gitea", user.NickName) } if err = currUser.Update(); err != nil { @@ -172,11 +175,14 @@ func oauthCallback(ctx echo.Context) error { MD5Hash: fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(strings.TrimSpace(user.Email))))), } + // set provider id and avatar URL switch user.Provider { case "github": userDB.GithubID = user.UserID + userDB.AvatarURL = getAvatarUrlFromProvider("github", user.UserID) case "gitea": userDB.GiteaID = user.UserID + userDB.AvatarURL = getAvatarUrlFromProvider("gitea", user.NickName) } if err = userDB.Create(); err != nil { @@ -328,3 +334,39 @@ func trimGiteaUrl() string { return giteaUrl } + +func getAvatarUrlFromProvider(provider string, identifier string) string { + fmt.Println("getAvatarUrlFromProvider", provider, identifier) + switch provider { + case "github": + return "https://avatars.githubusercontent.com/u/" + identifier + "?v=4" + case "gitea": + resp, err := http.Get("https://gitea.com/api/v1/users/" + identifier) + if err != nil { + log.Error().Err(err).Msg("Cannot get user from Gitea") + return "" + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Error().Err(err).Msg("Cannot read Gitea response body") + return "" + } + + var result map[string]interface{} + err = json.Unmarshal(body, &result) + if err != nil { + log.Error().Err(err).Msg("Cannot unmarshal Gitea response body") + return "" + } + + field, ok := result["avatar_url"] + if !ok { + log.Error().Msg("Field 'avatar_url' not found in Gitea JSON response") + return "" + } + return field.(string) + } + return "" +} diff --git a/internal/web/gist.go b/internal/web/gist.go index acee492..9778f31 100644 --- a/internal/web/gist.go +++ b/internal/web/gist.go @@ -186,8 +186,22 @@ func revisions(ctx echo.Context) error { return errorRes(404, "Page not found", nil) } + emailsSet := map[string]struct{}{} + for _, commit := range commits { + if commit.AuthorEmail == "" { + continue + } + emailsSet[strings.ToLower(commit.AuthorEmail)] = struct{}{} + } + + emailsUsers, err := models.GetUsersFromEmails(emailsSet) + if err != nil { + return errorRes(500, "Error fetching users emails", err) + } + setData(ctx, "page", "revisions") setData(ctx, "revision", "HEAD") + setData(ctx, "emails", emailsUsers) setData(ctx, "htmlTitle", "Revision of "+gist.Title) return html(ctx, "revisions.html") diff --git a/internal/web/run.go b/internal/web/run.go index 3f0f0dd..dc5d0cc 100644 --- a/internal/web/run.go +++ b/internal/web/run.go @@ -2,7 +2,6 @@ package web import ( "context" - "crypto/md5" "encoding/json" "fmt" "github.com/gorilla/sessions" @@ -71,11 +70,16 @@ var fm = template.FuncMap{ "slug": func(s string) string { return strings.Trim(re.ReplaceAllString(strings.ToLower(s), "-"), "-") }, - "avatarUrl": func(userHash string) string { - return "https://www.gravatar.com/avatar/" + userHash + "?d=identicon&s=200" - }, - "emailToMD5": func(email string) string { - return fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(strings.TrimSpace(email))))) + "avatarUrl": func(user *models.User, noGravatar bool) string { + if user.AvatarURL != "" { + return user.AvatarURL + } + + if user.MD5Hash != "" && !noGravatar { + return "https://www.gravatar.com/avatar/" + user.MD5Hash + "?d=identicon&s=200" + } + + return defaultAvatar() }, "asset": func(jsfile string) string { if dev { @@ -83,6 +87,7 @@ var fm = template.FuncMap{ } return "/" + manifestEntries[jsfile].File }, + "defaultAvatar": defaultAvatar, } var EmbedFS fs.FS @@ -364,3 +369,10 @@ func parseManifestEntries() { log.Fatal().Err(err).Msg("Failed to unmarshal manifest.json") } } + +func defaultAvatar() string { + if dev { + return "http://localhost:16157/default.png" + } + return "/" + manifestEntries["default.png"].File +} diff --git a/internal/web/settings.go b/internal/web/settings.go index 4e4a3d5..fd6f0d3 100644 --- a/internal/web/settings.go +++ b/internal/web/settings.go @@ -37,7 +37,7 @@ func emailProcess(ctx echo.Context) error { hash = fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(strings.TrimSpace(email))))) } - user.Email = email + user.Email = strings.ToLower(email) user.MD5Hash = hash if err := user.Update(); err != nil { diff --git a/public/default.png b/public/default.png new file mode 100644 index 0000000000000000000000000000000000000000..af51e5d5f5868d74fe3f51f718a121879d928a60 GIT binary patch literal 15655 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 z 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 +
  • +
    + + Disable Gravatar + Disable the usage of Gravatar as an avatar provider. + + +
    +
  • {{ .csrfHtml }} diff --git a/templates/pages/all.html b/templates/pages/all.html index 1dd2be2..9496389 100644 --- a/templates/pages/all.html +++ b/templates/pages/all.html @@ -5,7 +5,7 @@ {{if .fromUser}}
    - +

    {{.fromUser.Username}}

    diff --git a/templates/pages/forks.html b/templates/pages/forks.html index 417736e..93cd1b9 100644 --- a/templates/pages/forks.html +++ b/templates/pages/forks.html @@ -8,7 +8,7 @@ {{ range $gist := .forks }}
  • - +
    {{ $gist.User.Username }} diff --git a/templates/pages/likes.html b/templates/pages/likes.html index 8269f14..b7ed806 100644 --- a/templates/pages/likes.html +++ b/templates/pages/likes.html @@ -6,7 +6,7 @@ {{ range $user := .likers }}