From 530439772d1da5e6a263d4a6253d44994052409f Mon Sep 17 00:00:00 2001 From: Donald Feury Date: Wed, 24 Feb 2021 23:00:21 -0500 Subject: [PATCH 001/115] Add Pagination to Tags Collection Mostly copied the logic for pagination from non tag collection --- collections.go | 9 +++++++++ routes.go | 1 + templates/collection-tags.tmpl | 11 +++++++++++ 3 files changed, 21 insertions(+) diff --git a/collections.go b/collections.go index e1ebe48..898b730 100644 --- a/collections.go +++ b/collections.go @@ -901,6 +901,15 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e coll := newDisplayCollection(c, cr, page) + coll.TotalPages = int(math.Ceil(float64(coll.TotalPosts) / float64(coll.Format.PostsPerPage()))) + if coll.TotalPages > 0 && page > coll.TotalPages { + redirURL := fmt.Sprintf("/page/%d", coll.TotalPages) + if !app.cfg.App.SingleUser { + redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL) + } + return impart.HTTPError{http.StatusFound, redirURL} + } + coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, page, cr.isCollOwner) if coll.Posts != nil && len(*coll.Posts) == 0 { return ErrCollectionPageNotFound diff --git a/routes.go b/routes.go index bb1785f..6595949 100644 --- a/routes.go +++ b/routes.go @@ -206,6 +206,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) + r.HandleFunc("/tag:{tag}/page/{page:[0-9]+}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) r.HandleFunc("/feed/", handler.AllReader(ViewFeed)) diff --git a/templates/collection-tags.tmpl b/templates/collection-tags.tmpl index e2f8962..9fb130a 100644 --- a/templates/collection-tags.tmpl +++ b/templates/collection-tags.tmpl @@ -60,6 +60,17 @@ {{if .Posts}}
{{else}}
{{end}}

{{.Tag}}

{{template "posts" .}} + + {{if gt .TotalPages 1}}{{end}} + {{if .Posts}}
{{else}}{{end}} {{ if .Collection.ShowFooterBranding }} From 9ed268754318db276236ba71ff42c69dd5eedb70 Mon Sep 17 00:00:00 2001 From: Donald Feury Date: Wed, 24 Feb 2021 23:49:15 -0500 Subject: [PATCH 002/115] Added TagCollectionPage * Implements PrevPageURL and NextPageURL * This allows the collection-tag template to get proper urls for paginating using tags. --- collections.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/collections.go b/collections.go index 898b730..c926295 100644 --- a/collections.go +++ b/collections.go @@ -562,6 +562,30 @@ type CollectionPage struct { CanInvite bool } +type TagCollectionPage struct { + CollectionPage + Tag string +} + +func (tcp TagCollectionPage) PrevPageURL(prefix string, n int, tl bool) string { + u := fmt.Sprintf("/tag:%s", tcp.Tag) + if n > 2 { + u += fmt.Sprintf("/page/%d", n-1) + } + if tl { + return u + } + return "/" + prefix + tcp.Alias + u + +} + +func (tcp TagCollectionPage) NextPageURL(prefix string, n int, tl bool) string { + if tl { + return fmt.Sprintf("/tag:%s/page/%d", tcp.Tag, n+1) + } + return fmt.Sprintf("/%s%s/tag:%s/page/%d", prefix, tcp.Alias, tcp.Tag, n+1) +} + func NewCollectionObj(c *Collection) *CollectionObj { return &CollectionObj{ Collection: *c, @@ -916,10 +940,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e } // Serve collection - displayPage := struct { - CollectionPage - Tag string - }{ + displayPage := TagCollectionPage{ CollectionPage: CollectionPage{ DisplayCollection: coll, StaticPage: pageForReq(app, r), From 4c0fcdf7c69be84933453ebe43aef4dbe5595d31 Mon Sep 17 00:00:00 2001 From: Donald Feury Date: Wed, 24 Feb 2021 23:55:56 -0500 Subject: [PATCH 003/115] Setting activitypub.go back to master version --- activitypub.go | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/activitypub.go b/activitypub.go index 18307de..db42726 100644 --- a/activitypub.go +++ b/activitypub.go @@ -397,9 +397,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request go func() { if to == nil { - if debugging { - log.Error("No `to` value!") - } + log.Error("No to! %v", err) return } @@ -494,7 +492,7 @@ func makeActivityPost(hostName string, p *activitystreams.Person, url string, m r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b)) r.Header.Add("Content-Type", "application/activity+json") - r.Header.Set("User-Agent", ServerUserAgent(hostName)) + r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") h := sha256.New() h.Write(b) r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil))) @@ -544,7 +542,7 @@ func resolveIRI(hostName, url string) ([]byte, error) { r, _ := http.NewRequest("GET", url, nil) r.Header.Add("Accept", "application/activity+json") - r.Header.Set("User-Agent", ServerUserAgent(hostName)) + r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") if debugging { dump, err := httputil.DumpRequestOut(r, true) @@ -631,17 +629,6 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { log.Info("Federating new post!") } } - - // If app is private, do not federate - if app.cfg.App.Private { - return nil - } - - // Do not federate posts from private or protected blogs - if p.Collection.Visibility == CollPrivate || p.Collection.Visibility == CollProtected { - return nil - } - actor := p.Collection.PersonObject(collID) na := p.ActivityObject(app) @@ -710,10 +697,6 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { // I don't believe we'd ever have too many mentions in a single post that this // could become a burden. remoteUser, err := getRemoteUser(app, tag.HRef) - if err != nil { - log.Error("Unable to find remote user %s. Skipping: %v", tag.HRef, err) - continue - } err = makeActivityPost(app.cfg.App.Host, actor, remoteUser.Inbox, activity) if err != nil { log.Error("Couldn't post! %v", err) From ebdb9320903f30dd85ed2f65d4ae65c9f0ce5a20 Mon Sep 17 00:00:00 2001 From: Donald Feury Date: Wed, 24 Feb 2021 23:57:35 -0500 Subject: [PATCH 004/115] I meant develop, not master --- activitypub.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/activitypub.go b/activitypub.go index db42726..0e69075 100644 --- a/activitypub.go +++ b/activitypub.go @@ -397,7 +397,9 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request go func() { if to == nil { - log.Error("No to! %v", err) + if debugging { + log.Error("No `to` value!") + } return } @@ -492,7 +494,7 @@ func makeActivityPost(hostName string, p *activitystreams.Person, url string, m r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b)) r.Header.Add("Content-Type", "application/activity+json") - r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") + r.Header.Set("User-Agent", ServerUserAgent(hostName)) h := sha256.New() h.Write(b) r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil))) @@ -542,7 +544,7 @@ func resolveIRI(hostName, url string) ([]byte, error) { r, _ := http.NewRequest("GET", url, nil) r.Header.Add("Accept", "application/activity+json") - r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") + r.Header.Set("User-Agent", ServerUserAgent(hostName)) if debugging { dump, err := httputil.DumpRequestOut(r, true) @@ -697,6 +699,10 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { // I don't believe we'd ever have too many mentions in a single post that this // could become a burden. remoteUser, err := getRemoteUser(app, tag.HRef) + if err != nil { + log.Error("Unable to find remote user %s. Skipping: %v", tag.HRef, err) + continue + } err = makeActivityPost(app.cfg.App.Host, actor, remoteUser.Inbox, activity) if err != nil { log.Error("Couldn't post! %v", err) From 2ea235f0c45ea514a33d8daa1f17dd5937c9b7e0 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Jun 2021 18:24:40 -0400 Subject: [PATCH 005/115] Support email subscriptions (base) This adds beginning email subscription functionality, with only MySQL support, Mailgun support, and incomplete support for private instances. It includes database changes, so run: writefreely db migrate to use this feature. Ref T856 --- account.go | 7 + app.go | 11 + collections.go | 23 +- config/config.go | 10 + database.go | 276 +++++++++++++++++ email.go | 466 +++++++++++++++++++++++++++++ go.mod | 12 +- go.sum | 111 ++++++- jobs.go | 72 +++++ less/core.less | 16 + migrations/drivers.go | 7 + migrations/migrations.go | 3 +- migrations/v11.go | 60 ++++ posts.go | 57 +++- routes.go | 5 + session.go | 4 + spam/email.go | 43 +++ templates/collection.tmpl | 8 + templates/include/post-render.tmpl | 25 ++ templates/user/collection.tmpl | 45 +++ users.go | 4 + 21 files changed, 1250 insertions(+), 15 deletions(-) create mode 100644 email.go create mode 100644 jobs.go create mode 100644 migrations/v11.go create mode 100644 spam/email.go diff --git a/account.go b/account.go index 72d12ee..0a6c4ae 100644 --- a/account.go +++ b/account.go @@ -869,12 +869,19 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques *UserPage *Collection Silenced bool + + config.LettersCfg + LetterReplyTo string }{ UserPage: NewUserPage(app, r, u, "Edit "+c.DisplayTitle(), flashes), Collection: c, Silenced: silenced, + LettersCfg: app.cfg.Letters, } obj.UserPage.CollAlias = c.Alias + if obj.LettersCfg.Enabled() { + obj.LetterReplyTo = app.db.GetCollectionAttribute(c.ID, collAttrLetterReplyTo) + } showUserPage(w, "collection", obj) return nil diff --git a/app.go b/app.go index 40eb858..9a72dc9 100644 --- a/app.go +++ b/app.go @@ -415,6 +415,17 @@ func Initialize(apper Apper, debug bool) (*App, error) { initActivityPub(apper.App()) + if apper.App().cfg.Letters.Domain != "" || apper.App().cfg.Letters.MailgunPrivate != "" { + if apper.App().cfg.Letters.Domain == "" { + log.Error("[FAILED] Starting publish jobs queue: no [letters]domain config value set.") + } else if apper.App().cfg.Letters.MailgunPrivate == "" { + log.Error("[FAILED] Starting publish jobs queue: no [letters]mailgun_private config value set.") + } else { + log.Info("Starting publish jobs queue...") + go startPublishJobsQueue(apper.App()) + } + } + // Handle local timeline, if enabled if apper.App().cfg.App.LocalTimeline { log.Info("Initializing local timeline...") diff --git a/collections.go b/collections.go index f79cc2d..4f1cfad 100644 --- a/collections.go +++ b/collections.go @@ -33,9 +33,12 @@ import ( "github.com/writefreely/writefreely/author" "github.com/writefreely/writefreely/config" "github.com/writefreely/writefreely/page" + "github.com/writefreely/writefreely/spam" "golang.org/x/net/idna" ) +const collAttrLetterReplyTo = "letter_reply_to" + type ( // TODO: add Direction to db // TODO: add Language to db @@ -87,6 +90,7 @@ type ( Privacy int `schema:"privacy" json:"privacy"` Pass string `schema:"password" json:"password"` MathJax bool `schema:"mathjax" json:"mathjax"` + EmailSubs bool `schema:"email_subs" json:"email_subs"` Handle string `schema:"handle" json:"handle"` // Actual collection values updated in the DB @@ -97,6 +101,7 @@ type ( Script *sql.NullString `schema:"script" json:"script"` Signature *sql.NullString `schema:"signature" json:"signature"` Monetization *string `schema:"monetization_pointer" json:"monetization_pointer"` + LetterReply *string `schema:"letter_reply" json:"letter_reply"` Visibility *int `schema:"visibility" json:"public"` Format *sql.NullString `schema:"format" json:"format"` } @@ -353,6 +358,10 @@ func (c *Collection) RenderMathJax() bool { return c.db.CollectionHasAttribute(c.ID, "render_mathjax") } +func (c *Collection) EmailSubsEnabled() bool { + return c.db.CollectionHasAttribute(c.ID, "email_subs") +} + func (c *Collection) MonetizationURL() string { if c.Monetization == "" { return "" @@ -575,13 +584,17 @@ type CollectionPage struct { IsWelcome bool IsOwner bool IsCollLoggedIn bool + Honeypot string + IsSubscriber bool CanPin bool Username string Monetization string + Flash template.HTML Collections *[]Collection PinnedPosts *[]PublicPost - IsAdmin bool - CanInvite bool + + IsAdmin bool + CanInvite bool // Helper field for Chorus mode CollAlias string @@ -821,14 +834,20 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro StaticPage: pageForReq(app, r), IsCustomDomain: cr.isCustomDomain, IsWelcome: r.FormValue("greeting") != "", + Honeypot: spam.HoneypotFieldName(), CollAlias: c.Alias, } + flashes, _ := getSessionFlashes(app, w, r, nil) + for _, f := range flashes { + displayPage.Flash = template.HTML(f) + } displayPage.IsAdmin = u != nil && u.IsAdmin() displayPage.CanInvite = canUserInvite(app.cfg, displayPage.IsAdmin) var owner *User if u != nil { displayPage.Username = u.Username displayPage.IsOwner = u.ID == coll.OwnerID + displayPage.IsSubscriber = u.IsEmailSubscriber(app, coll.ID) if displayPage.IsOwner { // Add in needed information for users viewing their own collection owner = u diff --git a/config/config.go b/config/config.go index 0f07329..00dd6a5 100644 --- a/config/config.go +++ b/config/config.go @@ -170,11 +170,17 @@ type ( DisablePasswordAuth bool `ini:"disable_password_auth"` } + LettersCfg struct { + Domain string `ini:"domain"` + MailgunPrivate string `ini:"mailgun_private"` + } + // Config holds the complete configuration for running a writefreely instance Config struct { Server ServerCfg `ini:"server"` Database DatabaseCfg `ini:"database"` App AppCfg `ini:"app"` + Letters LettersCfg `ini:"letters"` SlackOauth SlackOauthCfg `ini:"oauth.slack"` WriteAsOauth WriteAsOauthCfg `ini:"oauth.writeas"` GitlabOauth GitlabOauthCfg `ini:"oauth.gitlab"` @@ -235,6 +241,10 @@ func (ac *AppCfg) LandingPath() string { return ac.Landing } +func (lc LettersCfg) Enabled() bool { + return lc.Domain != "" && lc.MailgunPrivate != "" +} + func (ac AppCfg) SignupPath() string { if !ac.OpenRegistration { return "" diff --git a/database.go b/database.go index fefc3c1..2232652 100644 --- a/database.go +++ b/database.go @@ -14,6 +14,7 @@ import ( "context" "database/sql" "fmt" + "github.com/go-sql-driver/mysql" "github.com/writeas/web-core/silobridge" wf_db "github.com/writefreely/writefreely/db" "net/http" @@ -932,6 +933,41 @@ func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) erro } } + // Update EmailSub value + if c.EmailSubs { + // TODO: ensure these work with SQLite + _, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value = ?", collID, "email_subs", "1", "1") + if err != nil { + log.Error("Unable to insert email_subs value: %v", err) + return err + } + skipUpdate := false + if c.LetterReply != nil { + // Strip away any excess spaces + trimmed := strings.TrimSpace(*c.LetterReply) + // Only update value when it contains "@" + if strings.IndexRune(trimmed, '@') > 0 { + c.LetterReply = &trimmed + } else { + // Value appears invalid, so don't update + skipUpdate = true + } + if !skipUpdate { + _, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value = ?", collID, collAttrLetterReplyTo, *c.LetterReply, *c.LetterReply) + if err != nil { + log.Error("Unable to insert %s value: %v", collAttrLetterReplyTo, err) + return err + } + } + } + } else { + _, err = db.Exec("DELETE FROM collectionattributes WHERE collection_id = ? AND attribute = ?", collID, "email_subs") + if err != nil { + log.Error("Unable to delete email_subs value: %v", err) + return err + } + } + // Update rest of the collection data if q.Updates != "" { res, err = db.Exec("UPDATE collections SET "+q.Updates+" WHERE "+q.Conditions, q.Params...) @@ -2812,3 +2848,243 @@ func (db *datastore) GetProfilePageFromHandle(app *App, handle string) (string, } return actorIRI, nil } + +func (db *datastore) AddEmailSubscription(collID, userID int64, email string, confirmed bool) (*EmailSubscriber, error) { + friendlyChars := "0123456789BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz" + subID := id.GenerateRandomString(friendlyChars, 8) + token := id.GenerateRandomString(friendlyChars, 16) + emailVal := sql.NullString{ + String: email, + Valid: email != "", + } + userIDVal := sql.NullInt64{ + Int64: userID, + Valid: userID > 0, + } + + _, err := db.Exec("INSERT INTO emailsubscribers (id, collection_id, user_id, email, subscribed, token, confirmed) VALUES (?, ?, ?, ?, NOW(), ?, ?)", subID, collID, userIDVal, emailVal, token, confirmed) + if err != nil { + if mysqlErr, ok := err.(*mysql.MySQLError); ok { + if mysqlErr.Number == mySQLErrDuplicateKey { + // Duplicate, so just return existing subscriber information + log.Info("Duplicate subscriber for email %s, user %d; returning existing subscriber", email, userID) + return db.FetchEmailSubscriber(email, userID, collID) + } + } + return nil, err + } + + return &EmailSubscriber{ + ID: subID, + CollID: collID, + UserID: userIDVal, + Email: emailVal, + Token: token, + }, nil +} + +func (db *datastore) IsEmailSubscriber(email string, userID, collID int64) bool { + var dummy int + var err error + if email != "" { + err = db.QueryRow("SELECT 1 FROM emailsubscribers WHERE email = ? AND collection_id = ?", email, collID).Scan(&dummy) + } else { + err = db.QueryRow("SELECT 1 FROM emailsubscribers WHERE user_id = ? AND collection_id = ?", userID, collID).Scan(&dummy) + } + switch { + case err == sql.ErrNoRows: + return false + case err != nil: + return false + } + return true +} + +func (db *datastore) GetEmailSubscribers(collID int64, reqConfirmed bool) ([]*EmailSubscriber, error) { + cond := "" + if reqConfirmed { + cond = " AND confirmed = 1" + } + rows, err := db.Query(`SELECT s.id, collection_id, user_id, s.email, u.email, subscribed, token, confirmed, allow_export +FROM emailsubscribers s +LEFT JOIN users u + ON u.id = user_id +WHERE collection_id = ?`+cond+` +ORDER BY subscribed DESC`, collID) + if err != nil { + log.Error("Failed selecting email subscribers for collection %d: %v", collID, err) + return nil, err + } + defer rows.Close() + + var subs []*EmailSubscriber + for rows.Next() { + s := &EmailSubscriber{} + err = rows.Scan(&s.ID, &s.CollID, &s.UserID, &s.Email, &s.acctEmail, &s.Subscribed, &s.Token, &s.Confirmed, &s.AllowExport) + if err != nil { + log.Error("Failed scanning row from email subscribers: %v", err) + continue + } + subs = append(subs, s) + } + return subs, nil +} + +func (db *datastore) FetchEmailSubscriberEmail(subID, token string) (string, error) { + var email sql.NullString + // TODO: return user email if there's a user_id ? + err := db.QueryRow("SELECT email FROM emailsubscribers WHERE id = ? AND token = ?", subID, token).Scan(&email) + switch { + case err == sql.ErrNoRows: + return "", fmt.Errorf("Subscriber doesn't exist or token is invalid.") + case err != nil: + log.Error("Couldn't SELECT email from emailsubscribers: %v", err) + return "", fmt.Errorf("Something went very wrong.") + } + + return email.String, nil +} + +func (db *datastore) FetchEmailSubscriber(email string, userID, collID int64) (*EmailSubscriber, error) { + const emailSubCols = "id, collection_id, user_id, email, subscribed, token, confirmed, allow_export" + + s := &EmailSubscriber{} + var row *sql.Row + if email != "" { + row = db.QueryRow("SELECT "+emailSubCols+" FROM emailsubscribers WHERE email = ? AND collection_id = ?", email, collID) + } else { + row = db.QueryRow("SELECT "+emailSubCols+" FROM emailsubscribers WHERE user_id = ? AND collection_id = ?", userID, collID) + } + err := row.Scan(&s.ID, &s.CollID, &s.UserID, &s.Email, &s.Subscribed, &s.Token, &s.Confirmed, &s.AllowExport) + switch { + case err == sql.ErrNoRows: + return nil, nil + case err != nil: + return nil, err + } + return s, nil +} + +func (db *datastore) DeleteEmailSubscriber(subID, token string) error { + res, err := db.Exec("DELETE FROM emailsubscribers WHERE id = ? AND token = ?", subID, token) + if err != nil { + return err + } + + rowsAffected, _ := res.RowsAffected() + if rowsAffected == 0 { + return impart.HTTPError{http.StatusNotFound, "Invalid token, or subscriber doesn't exist"} + } + return nil +} + +func (db *datastore) DeleteEmailSubscriberByUser(email string, userID, collID int64) error { + var res sql.Result + var err error + if email != "" { + res, err = db.Exec("DELETE FROM emailsubscribers WHERE email = ? AND collection_id = ?", email, collID) + } else { + res, err = db.Exec("DELETE FROM emailsubscribers WHERE user_id = ? AND collection_id = ?", userID, collID) + } + if err != nil { + return err + } + + rowsAffected, _ := res.RowsAffected() + if rowsAffected == 0 { + return impart.HTTPError{http.StatusNotFound, "Subscriber doesn't exist"} + } + return nil +} + +func (db *datastore) UpdateSubscriberConfirmed(subID, token string) error { + email, err := db.FetchEmailSubscriberEmail(subID, token) + if err != nil { + log.Error("Didn't fetch email subscriber: %v", err) + return err + } + + // TODO: ensure all addresses with original name are also confirmed, e.g. matt+fake@write.as and matt@write.as are now confirmed + _, err = db.Exec("UPDATE emailsubscribers SET confirmed = 1 WHERE email = ?", email) + if err != nil { + log.Error("Could not update email subscriber confirmation status: %v", err) + return err + } + return nil +} + +func (db *datastore) IsSubscriberConfirmed(email string) bool { + var dummy int64 + err := db.QueryRow("SELECT 1 FROM emailsubscribers WHERE email = ? AND confirmed = 1", email).Scan(&dummy) + switch { + case err == sql.ErrNoRows: + return false + case err != nil: + log.Error("Couldn't SELECT in isSubscriberConfirmed: %v", err) + return false + } + + return true +} + +func (db *datastore) InsertJob(j *PostJob) error { + res, err := db.Exec("INSERT INTO publishjobs (post_id, action, delay) VALUES (?, ?, ?)", j.PostID, j.Action, j.Delay) + if err != nil { + return err + } + jobID, err := res.LastInsertId() + if err != nil { + log.Error("[jobs] Couldn't get last insert ID! %s", err) + } + log.Info("[jobs] Queued %s job #%d for post %s, delayed %d minutes", j.Action, jobID, j.PostID, j.Delay) + return nil +} + +func (db *datastore) UpdateJobForPost(postID string, delay int64) error { + _, err := db.Exec("UPDATE publishjobs SET delay = ? WHERE post_id = ?", delay, postID) + if err != nil { + return fmt.Errorf("Unable to update publish job: %s", err) + } + log.Info("Updated job for post %s: delay %d", postID, delay) + return nil +} + +func (db *datastore) DeleteJob(id int64) error { + _, err := db.Exec("DELETE FROM publishjobs WHERE id = ?", id) + if err != nil { + return err + } + log.Info("[job #%d] Deleted.", id) + return nil +} + +func (db *datastore) DeleteJobByPost(postID string) error { + _, err := db.Exec("DELETE FROM publishjobs WHERE post_id = ?", postID) + if err != nil { + return err + } + log.Info("[job] Deleted job for post %s", postID) + return nil +} + +func (db *datastore) GetJobsToRun(action string) ([]*PostJob, error) { + rows, err := db.Query(`SELECT pj.id, post_id, action, delay + FROM publishjobs pj + INNER JOIN posts p + ON post_id = p.id + WHERE action = ? AND created < DATE_SUB(NOW(), INTERVAL delay MINUTE) AND created > DATE_SUB(NOW(), INTERVAL delay + 5 MINUTE) + ORDER BY created ASC`, action) + if err != nil { + log.Error("Failed selecting from publishjobs: %v", err) + return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve publish jobs."} + } + defer rows.Close() + + jobs := []*PostJob{} + for rows.Next() { + j := &PostJob{} + err = rows.Scan(&j.ID, &j.PostID, &j.Action, &j.Delay) + jobs = append(jobs, j) + } + return jobs, nil +} diff --git a/email.go b/email.go new file mode 100644 index 0000000..451f988 --- /dev/null +++ b/email.go @@ -0,0 +1,466 @@ +/* + * Copyright © 2019-2021 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package writefreely + +import ( + "database/sql" + "encoding/json" + "fmt" + "html/template" + "net/http" + "strings" + "time" + + "github.com/aymerick/douceur/inliner" + "github.com/gorilla/mux" + "github.com/mailgun/mailgun-go" + stripmd "github.com/writeas/go-strip-markdown/v2" + "github.com/writeas/impart" + "github.com/writeas/web-core/data" + "github.com/writeas/web-core/log" + "github.com/writefreely/writefreely/key" + "github.com/writefreely/writefreely/spam" +) + +const ( + emailSendDelay = 15 +) + +type ( + SubmittedSubscription struct { + CollAlias string + UserID int64 + + Email string `schema:"email" json:"email"` + Web bool `schema:"web" json:"web"` + Slug string `schema:"slug" json:"slug"` + From string `schema:"from" json:"from"` + } + + EmailSubscriber struct { + ID string + CollID int64 + UserID sql.NullInt64 + Email sql.NullString + Subscribed time.Time + Token string + Confirmed bool + AllowExport bool + acctEmail sql.NullString + } +) + +func (es *EmailSubscriber) FinalEmail(keys *key.Keychain) string { + if !es.UserID.Valid || es.Email.Valid { + return es.Email.String + } + + decEmail, err := data.Decrypt(keys.EmailKey, []byte(es.acctEmail.String)) + if err != nil { + log.Error("Error decrypting user email: %v", err) + return "" + } + return string(decEmail) +} + +func (es *EmailSubscriber) SubscribedFriendly() string { + return es.Subscribed.Format("January 2, 2006") +} + +func handleCreateEmailSubscription(app *App, w http.ResponseWriter, r *http.Request) error { + reqJSON := IsJSON(r) + vars := mux.Vars(r) + var err error + + ss := SubmittedSubscription{ + CollAlias: vars["alias"], + } + u := getUserSession(app, r) + if u != nil { + ss.UserID = u.ID + } + if reqJSON { + // Decode JSON request + decoder := json.NewDecoder(r.Body) + err = decoder.Decode(&ss) + if err != nil { + log.Error("Couldn't parse new subscription JSON request: %v\n", err) + return ErrBadJSON + } + } else { + err = r.ParseForm() + if err != nil { + log.Error("Couldn't parse new subscription form request: %v\n", err) + return ErrBadFormData + } + + err = app.formDecoder.Decode(&ss, r.PostForm) + if err != nil { + log.Error("Continuing, but error decoding new subscription form request: %v\n", err) + //return ErrBadFormData + } + } + + c, err := app.db.GetCollection(ss.CollAlias) + if err != nil { + log.Error("getCollection: %s", err) + return err + } + c.hostName = app.cfg.App.Host + + from := c.CanonicalURL() + isAuthorBanned, err := app.db.IsUserSilenced(c.OwnerID) + if isAuthorBanned { + log.Info("Author is silenced, so subscription is blocked.") + return impart.HTTPError{http.StatusFound, from} + } + + if ss.Web { + if u != nil && u.ID == c.OwnerID { + from = "/" + c.Alias + "/" + } + from += ss.Slug + } + + if r.FormValue(spam.HoneypotFieldName()) != "" || r.FormValue("fake_password") != "" { + log.Info("Honeypot field was filled out! Not subscribing.") + return impart.HTTPError{http.StatusFound, from} + } + + if ss.Email == "" && ss.UserID < 1 { + log.Info("No subscriber data. Not subscribing.") + return impart.HTTPError{http.StatusFound, from} + } + + // Do email validation + // TODO: move this to an AJAX call before submitting email address, so we can immediately show errors to user + /* + err := validate(ss.Email) + if err != nil { + addSessionFlash(w, r, err.Error(), nil) + return impart.HTTPError{http.StatusFound, from} + } + */ + + confirmed := app.db.IsSubscriberConfirmed(ss.Email) + es, err := app.db.AddEmailSubscription(c.ID, ss.UserID, ss.Email, confirmed) + if err != nil { + log.Error("addEmailSubscription: %s", err) + return err + } + + // Send confirmation email if needed + if !confirmed { + sendSubConfirmEmail(app, c, ss.Email, es.ID, es.Token) + } + + if ss.Web { + session, err := app.sessionStore.Get(r, userEmailCookieName) + if err != nil { + // The cookie should still save, even if there's an error. + // Source: https://github.com/gorilla/sessions/issues/16#issuecomment-143642144 + log.Error("Getting user email cookie: %v; ignoring", err) + } + if confirmed { + addSessionFlash(app, w, r, "Subscribed. You'll now receive future blog posts via email.", nil) + } else { + addSessionFlash(app, w, r, "Please check your email and click the confirmation link to subscribe.", nil) + } + session.Values[userEmailCookieVal] = ss.Email + err = session.Save(r, w) + if err != nil { + log.Error("save email cookie: %s", err) + return err + } + + return impart.HTTPError{http.StatusFound, from} + } + return impart.WriteSuccess(w, "", http.StatusAccepted) +} + +func handleDeleteEmailSubscription(app *App, w http.ResponseWriter, r *http.Request) error { + alias := collectionAliasFromReq(r) + + vars := mux.Vars(r) + subID := vars["subscriber"] + email := r.FormValue("email") + token := r.FormValue("t") + slug := r.FormValue("slug") + isWeb := r.Method == "GET" + + // Display collection if this is a collection + var c *Collection + var err error + if app.cfg.App.SingleUser { + c, err = app.db.GetCollectionByID(1) + } else { + c, err = app.db.GetCollection(alias) + } + if err != nil { + log.Error("Get collection: %s", err) + return err + } + + from := c.CanonicalURL() + + if subID != "" { + // User unsubscribing via email, so assume action is taken by either current + // user or not current user, and only use the request's information to + // satisfy this unsubscribe, i.e. subscriberID and token. + err = app.db.DeleteEmailSubscriber(subID, token) + } else { + // User unsubscribing through the web app, so assume action is taken by + // currently-auth'd user. + var userID int64 + u := getUserSession(app, r) + if u != nil { + // User is logged in + userID = u.ID + if userID == c.OwnerID { + from = "/" + c.Alias + "/" + } + } + if email == "" && userID <= 0 { + // Get email address from saved cookie + session, err := app.sessionStore.Get(r, userEmailCookieName) + if err != nil { + log.Error("Unable to get email cookie: %s", err) + } else { + email = session.Values[userEmailCookieVal].(string) + } + } + + if email == "" && userID <= 0 { + err = fmt.Errorf("No subscriber given.") + log.Error("Not deleting subscription: %s", err) + return err + } + + err = app.db.DeleteEmailSubscriberByUser(email, userID, c.ID) + } + if err != nil { + log.Error("Unable to delete subscriber: %v", err) + return err + } + + if isWeb { + from += slug + addSessionFlash(app, w, r, "Unsubscribed. You will no longer receive these blog posts via email.", nil) + return impart.HTTPError{http.StatusFound, from} + } + return impart.WriteSuccess(w, "", http.StatusAccepted) +} + +func handleConfirmEmailSubscription(app *App, w http.ResponseWriter, r *http.Request) error { + alias := collectionAliasFromReq(r) + subID := mux.Vars(r)["subscriber"] + token := r.FormValue("t") + + var c *Collection + var err error + if app.cfg.App.SingleUser { + c, err = app.db.GetCollectionByID(1) + } else { + c, err = app.db.GetCollection(alias) + } + if err != nil { + log.Error("Get collection: %s", err) + return err + } + + from := c.CanonicalURL() + + err = app.db.UpdateSubscriberConfirmed(subID, token) + if err != nil { + addSessionFlash(app, w, r, err.Error(), nil) + return impart.HTTPError{http.StatusFound, from} + } + + addSessionFlash(app, w, r, "Confirmed! Thanks. Now you'll receive future blog posts via email.", nil) + return impart.HTTPError{http.StatusFound, from} +} + +func emailPost(app *App, p *PublicPost, collID int64) error { + p.augmentContent() + + // Do some shortcode replacement. + // Since the user is receiving this email, we can assume they're subscribed via email. + p.Content = strings.Replace(p.Content, "", `

You're subscribed to email updates.

`, -1) + + if p.HTMLContent == template.HTML("") { + p.formatContent(app.cfg, false, false) + } + p.augmentReadingDestination() + + title := p.Title.String + if title != "" { + title = p.Title.String + "\n\n" + } + plainMsg := title + "A new post from " + p.CanonicalURL(app.cfg.App.Host) + "\n\n" + stripmd.Strip(p.Content) + plainMsg += ` + +--------------------------------------------------------------------------------- + +Originally published on ` + p.Collection.DisplayTitle() + ` (` + p.Collection.CanonicalURL() + `), a blog you subscribe to. + +Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%` + + gun := mailgun.NewMailgun(app.cfg.Letters.Domain, app.cfg.Letters.MailgunPrivate) + m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Letters.Domain+">", p.Collection.DisplayTitle()+": "+p.DisplayTitle(), plainMsg) + replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo) + if replyTo != "" { + m.SetReplyTo(replyTo) + } + + subs, err := app.db.GetEmailSubscribers(collID, true) + if err != nil { + log.Error("Unable to get email subscribers: %v", err) + return err + } + if len(subs) == 0 { + return nil + } + log.Info("[email] Adding %d recipient(s)", len(subs)) + for _, s := range subs { + e := s.FinalEmail(app.keys) + log.Info("[email] Adding %s", e) + err = m.AddRecipientAndVariables(e, map[string]interface{}{ + "id": s.ID, + "to": e, + "token": s.Token, + }) + if err != nil { + log.Error("Unable to add receipient %s: %s", e, err) + } + } + + if title != "" { + title = string(`

` + p.FormattedDisplayTitle() + `

`) + } + m.AddTag("New post") + + fontFam := "Lora, Palatino, Baskerville, serif" + if p.IsSans() { + fontFam = `"Open Sans", Tahoma, Arial, sans-serif` + } else if p.IsMonospace() { + fontFam = `Hack, consolas, Menlo-Regular, Menlo, Monaco, monospace, monospace` + } + + // TODO: move this to a templated file and LESS-generated stylesheet + fullHTML := ` + + + + +
` + title + `

From ` + p.DisplayCanonicalURL() + `

+ +` + string(p.HTMLContent) + `
+
+ + +` + + // inline CSS + html, err := inliner.Inline(fullHTML) + if err != nil { + log.Error("Unable to inline email HTML: %v", err) + return err + } + + m.SetHtml(html) + res, _, err := gun.Send(m) + log.Info("[email] Send result: %s", res) + if err != nil { + log.Error("Unable to send post email: %v", err) + return err + } + + return nil +} + +func sendSubConfirmEmail(app *App, c *Collection, email, subID, token string) error { + if email == "" { + return fmt.Errorf("You must supply an email to verify.") + } + + // Send email + gun := mailgun.NewMailgun(app.cfg.Letters.Domain, app.cfg.Letters.MailgunPrivate) + + plainMsg := "Confirm your subscription to " + c.DisplayTitle() + ` (` + c.CanonicalURL() + `) to start receiving future posts. Simply click the following link (or copy and paste it into your browser): + +` + c.CanonicalURL() + "email/confirm/" + subID + "?t=" + token + ` + +If you didn't subscribe to this site or you're not sure why you're getting this email, you can delete it. You won't be subscribed or receive any future emails.` + m := mailgun.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Letters.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email)) + m.AddTag("Email Verification") + + m.SetHtml(` + +
+

Confirm your subscription to ` + c.DisplayTitle() + ` to start receiving future posts:

+

Subscribe to ` + c.DisplayTitle() + `

+

If you didn't subscribe to this site or you're not sure why you're getting this email, you can delete it. You won't be subscribed or receive any future emails.

+
+ +`) + gun.Send(m) + + return nil +} diff --git a/go.mod b/go.mod index 8344d4c..9a6616b 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,17 @@ module github.com/writefreely/writefreely require ( + github.com/PuerkitoBio/goquery v1.7.0 // indirect + github.com/aymerick/douceur v0.2.0 github.com/clbanning/mxj v1.8.4 // indirect github.com/dustin/go-humanize v1.0.0 + github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect + github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect + github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect github.com/fatih/color v1.10.0 github.com/go-sql-driver/mysql v1.6.0 github.com/go-test/deep v1.0.1 // indirect + github.com/gobuffalo/envy v1.9.0 // indirect github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect github.com/gorilla/csrf v1.7.0 github.com/gorilla/feeds v1.1.1 @@ -18,11 +24,14 @@ require ( github.com/jtolds/gls v4.2.1+incompatible // indirect github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec github.com/lunixbochs/vtclean v1.0.0 // indirect + github.com/mailgun/mailgun-go v2.0.0+incompatible github.com/manifoldco/promptui v0.8.0 github.com/mattn/go-sqlite3 v1.14.6 github.com/microcosm-cc/bluemonday v1.0.5 github.com/mitchellh/go-wordwrap v1.0.1 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d + github.com/onsi/ginkgo v1.16.4 // indirect + github.com/onsi/gomega v1.13.0 // indirect github.com/prologic/go-gopher v0.0.0-20200721020712-3e11dcff0469 github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect @@ -32,6 +41,7 @@ require ( github.com/writeas/activity v0.1.2 github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481 github.com/writeas/go-strip-markdown v2.0.1+incompatible + github.com/writeas/go-strip-markdown/v2 v2.1.1 github.com/writeas/go-webfinger v1.1.0 github.com/writeas/httpsig v1.0.0 github.com/writeas/impart v1.1.1 @@ -42,7 +52,7 @@ require ( github.com/writeas/web-core v1.3.1-0.20210330164422-95a3a717ed8f github.com/writefreely/go-nodeinfo v1.2.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20200707034311-ab3426394381 + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 gopkg.in/ini.v1 v1.62.0 ) diff --git a/go.sum b/go.sum index 354a1dd..007047b 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,10 @@ code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs= code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/PuerkitoBio/goquery v1.7.0 h1:O5SP3b9JWqMSVMG69zMfj577zwkSNpxrFf7ybS74eiw= +github.com/PuerkitoBio/goquery v1.7.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= @@ -27,21 +31,48 @@ github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0 github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/go-fed/httpsig v0.1.0/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4Lb71UuprPHqhjxGv3Jqonb9fgcaJH8= github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c= github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/csrf v1.7.0 h1:mMPjV5/3Zd460xCavIkppUdvnl5fPXMpv2uz2Zyg7/Y= @@ -66,8 +97,11 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6ejs3Ut+jmsYvuOoAW2pSM= github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2/go.mod h1:WtaVKD9TeruTED9ydiaOJU08qGoEPP/LyzTKiD3jEsw= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= @@ -82,6 +116,8 @@ github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec/go.mod h1: github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mailgun/mailgun-go v2.0.0+incompatible h1:0FoRHWwMUctnd8KIR3vtZbqdfjpIMxOZgcSa51s8F8o= +github.com/mailgun/mailgun-go v2.0.0+incompatible/go.mod h1:NWTyU+O4aczg/nsGhQnvHL6v2n5Gy6Sv5tNDVvC6FbU= github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -99,8 +135,18 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -109,6 +155,8 @@ github.com/prologic/go-gopher v0.0.0-20200721020712-3e11dcff0469 h1:rAbv2gekFbUc github.com/prologic/go-gopher v0.0.0-20200721020712-3e11dcff0469/go.mod h1:c61IFFAJw8ADWu54tti30Tj5VrBstVoTprmET35UEkY= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= +github.com/rogpeppe/go-internal v1.3.2 h1:XU784Pr0wdahMY2bYcyK6N1KuaRAdLtqD4qd8D18Bfs= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= @@ -118,6 +166,8 @@ github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -129,6 +179,8 @@ github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481 h1:BiSivIxLQ github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481/go.mod h1:4akDJSl+sSp+QhrQKMqzAqdV1gJ1pPx6XPI77zgMM8o= github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw= github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE= +github.com/writeas/go-strip-markdown/v2 v2.1.1 h1:hAxUM21Uhznf/FnbVGiJciqzska6iLei22Ijc3q2e28= +github.com/writeas/go-strip-markdown/v2 v2.1.1/go.mod h1:UvvgPJgn1vvN8nWuE5e7v/+qmDu3BSVnKAB6Gl7hFzA= github.com/writeas/go-webfinger v1.1.0 h1:MzNyt0ry/GMsRmJGftn2o9mPwqK1Q5MLdh4VuJCfb1Q= github.com/writeas/go-webfinger v1.1.0/go.mod h1:w2VxyRO/J5vfNjJHYVubsjUGHd3RLDoVciz0DE3ApOc= github.com/writeas/go-writeas v1.1.0 h1:WHGm6wriBkxYAOGbvriXH8DlMUGOi6jhSZLUZKQ+4mQ= @@ -155,33 +207,84 @@ github.com/writeas/web-core v1.3.1-0.20210330164422-95a3a717ed8f h1:ItBZYzdIbBmm github.com/writeas/web-core v1.3.1-0.20210330164422-95a3a717ed8f/go.mod h1:DzNxa0YLV/wNeeWeHFPNa/nHmyJBFIIzXN/m9PpDm5c= github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss= github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/jobs.go b/jobs.go new file mode 100644 index 0000000..251b82d --- /dev/null +++ b/jobs.go @@ -0,0 +1,72 @@ +package writefreely + +import ( + "github.com/writeas/web-core/log" + "time" +) + +type PostJob struct { + ID int64 + PostID string + Action string + Delay int64 +} + +func addJob(app *App, p *PublicPost, action string, delay int64) error { + j := &PostJob{ + PostID: p.ID, + Action: action, + Delay: delay, + } + return app.db.InsertJob(j) +} + +func startPublishJobsQueue(app *App) { + t := time.NewTicker(62 * time.Second) + for { + log.Info("[jobs] Done.") + <-t.C + log.Info("[jobs] Fetching email publish jobs...") + jobs, err := app.db.GetJobsToRun("email") + if err != nil { + log.Error("[jobs] %s - Skipping.", err) + continue + } + log.Info("[jobs] Running %d email publish jobs...", len(jobs)) + err = runJobs(app, jobs, true) + if err != nil { + log.Error("[jobs] Failed: %s", err) + } + } +} + +func runJobs(app *App, jobs []*PostJob, reqColl bool) error { + for _, j := range jobs { + p, err := app.db.GetPost(j.PostID, 0) + if err != nil { + log.Info("[job #%d] Unable to get post: %s", j.ID, err) + continue + } + if !p.CollectionID.Valid && reqColl { + log.Info("[job #%d] Post %s not part of a collection", j.ID, p.ID) + app.db.DeleteJob(j.ID) + continue + } + coll, err := app.db.GetCollectionByID(p.CollectionID.Int64) + if err != nil { + log.Info("[job #%d] Unable to get collection: %s", j.ID, err) + continue + } + coll.hostName = app.cfg.App.Host + coll.ForPublic() + p.Collection = &CollectionObj{Collection: *coll} + err = emailPost(app, p, p.Collection.ID) + if err != nil { + log.Error("[job #%d] Failed to email post %s", j.ID, p.ID) + continue + } + log.Info("[job #%d] Success for post %s.", j.ID, p.ID) + app.db.DeleteJob(j.ID) + } + return nil +} diff --git a/less/core.less b/less/core.less index 75a801b..30ecaf1 100644 --- a/less/core.less +++ b/less/core.less @@ -210,6 +210,10 @@ body { pre { line-height: 1.5; } + .flash { + text-align: center; + margin-bottom: 4em; + } } &#subpage { #wrapper { @@ -1596,6 +1600,18 @@ pre.code-block { overflow-x: auto; } +#emailsub { + text-align: center; +} +p#emailsub { + display: inline-block !important; + width: 100%; + font-style: italic; +} +#subscribe-btn { + margin-left: 0.5em; +} + #org-nav { font-family: @sansFont; font-size: 1.1em; diff --git a/migrations/drivers.go b/migrations/drivers.go index 1399411..b4a95bd 100644 --- a/migrations/drivers.go +++ b/migrations/drivers.go @@ -36,6 +36,13 @@ func (db *datastore) typeSmallInt() string { return "SMALLINT" } +func (db *datastore) typeTinyInt() string { + if db.driverName == driverSQLite { + return "INTEGER" + } + return "TINYINT" +} + func (db *datastore) typeText() string { return "TEXT" } diff --git a/migrations/migrations.go b/migrations/migrations.go index b3ebcc0..4fe7162 100644 --- a/migrations/migrations.go +++ b/migrations/migrations.go @@ -65,7 +65,8 @@ var migrations = []Migration{ New("support oauth attach", oauthAttach), // V6 -> V7 New("support oauth via invite", oauthInvites), // V7 -> V8 (v0.12.0) New("optimize drafts retrieval", optimizeDrafts), // V8 -> V9 - New("support post signatures", supportPostSignatures), // V9 -> V10 + New("support post signatures", supportPostSignatures), // V9 -> V10 (v0.13.0) + New("support newsletters", supportLetters), // V10 -> V11 } // CurrentVer returns the current migration version the application is on diff --git a/migrations/v11.go b/migrations/v11.go new file mode 100644 index 0000000..cb509f2 --- /dev/null +++ b/migrations/v11.go @@ -0,0 +1,60 @@ +/* + * Copyright © 2021 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package migrations + +func supportLetters(db *datastore) error { + t, err := db.Begin() + if err != nil { + t.Rollback() + return err + } + + _, err = t.Exec(`CREATE TABLE publishjobs ( + id ` + db.typeInt() + ` auto_increment, + post_id ` + db.typeVarChar(16) + ` not null, + action ` + db.typeVarChar(16) + ` not null, + delay ` + db.typeTinyInt() + ` not null, + PRIMARY KEY (id) +)`) + if err != nil { + t.Rollback() + return err + } + + // TODO: fix for SQLite database + _, err = t.Exec(`CREATE TABLE emailsubscribers ( + id char(8) not null, + collection_id int not null, + user_id int null, + email varchar(255) null, + subscribed datetime not null, + token char(16) not null, + confirmed tinyint(1) default 0 not null, + allow_export tinyint(1) default 0 not null, + constraint eu_coll_email + unique (collection_id, email), + constraint eu_coll_user + unique (collection_id, user_id), + PRIMARY KEY (id) +)`) + if err != nil { + t.Rollback() + return err + } + + err = t.Commit() + if err != nil { + t.Rollback() + return err + } + + return nil +} diff --git a/posts.go b/posts.go index 4d8d019..9da0975 100644 --- a/posts.go +++ b/posts.go @@ -14,6 +14,7 @@ import ( "database/sql" "encoding/json" "fmt" + "github.com/writefreely/writefreely/spam" "html/template" "net/http" "net/url" @@ -651,8 +652,17 @@ func newPost(app *App, w http.ResponseWriter, r *http.Request) error { // Write success now response := impart.WriteSuccess(w, newPost, http.StatusCreated) - if newPost.Collection != nil && !app.cfg.App.Private && app.cfg.App.Federation && !newPost.Created.After(time.Now()) { - go federatePost(app, newPost, newPost.Collection.ID, false) + if newPost.Collection != nil { + if !app.cfg.App.Private && app.cfg.App.Federation && !newPost.Created.After(time.Now()) { + go federatePost(app, newPost, newPost.Collection.ID, false) + } + if app.cfg.Letters.Enabled() && newPost.Collection.EmailSubsEnabled() { + go app.db.InsertJob(&PostJob{ + PostID: newPost.ID, + Action: "email", + Delay: emailSendDelay, + }) + } } return response @@ -952,16 +962,23 @@ func addPost(app *App, w http.ResponseWriter, r *http.Request) error { return err } - if !app.cfg.App.Private && app.cfg.App.Federation { - for _, pRes := range *res { - if pRes.Code != http.StatusOK { - continue - } + for _, pRes := range *res { + if pRes.Code != http.StatusOK { + continue + } + if !app.cfg.App.Private && app.cfg.App.Federation { if !pRes.Post.Created.After(time.Now()) { pRes.Post.Collection.hostName = app.cfg.App.Host go federatePost(app, pRes.Post, pRes.Post.Collection.ID, false) } } + if app.cfg.Letters.Enabled() && pRes.Post.Collection.EmailSubsEnabled() { + go app.db.InsertJob(&PostJob{ + PostID: pRes.Post.ID, + Action: "email", + Delay: emailSendDelay, + }) + } } return impart.WriteSuccess(w, res, http.StatusOK) } @@ -1164,6 +1181,15 @@ func (p *PublicPost) CanonicalURL(hostName string) string { return p.Collection.CanonicalURL() + p.Slug.String } +func (pp *PublicPost) DisplayCanonicalURL() string { + us := pp.CanonicalURL(pp.Collection.hostName) + u, err := url.Parse(us) + if err != nil { + return us + } + return u.Hostname() + u.Path +} + func (p *PublicPost) ActivityObject(app *App) *activitystreams.Object { cfg := app.cfg var o *activitystreams.Object @@ -1530,6 +1556,15 @@ Are you sure it was ever here?`, } else { p.extractData() p.Content = strings.Replace(p.Content, "", "", 1) + if app.cfg.Letters.Enabled() && c.EmailSubsEnabled() { + // TODO: indicate plan is inactive or subs disabled when OWNER is viewing their own post. + if u != nil && u.IsEmailSubscriber(app, c.ID) { + p.Content = strings.Replace(p.Content, "", `

You're subscribed to email updates. Unsubscribe.

`, -1) + } else { + p.Content = strings.Replace(p.Content, "", `
`, -1) + } + } + p.Content = strings.Replace(p.Content, "<!--emailsub-->", "", 1) // TODO: move this to function p.formatContent(app.cfg, cr.isCollOwner, true) tp := CollectionPostPage{ @@ -1593,6 +1628,14 @@ func (p *Post) extractData() { p.extractImages() } +func (p *Post) IsSans() bool { + return p.Font == "sans" +} + +func (p *Post) IsMonospace() bool { + return p.Font == "mono" +} + func (rp *RawPost) UserFacingCreated() string { return rp.Created.Format(postMetaDateFormat) } diff --git a/routes.go b/routes.go index 213958d..e1b6261 100644 --- a/routes.go +++ b/routes.go @@ -147,6 +147,9 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { apiColls.HandleFunc("/{alias}/collect", handler.All(addPost)).Methods("POST") apiColls.HandleFunc("/{alias}/pin", handler.All(pinPost)).Methods("POST") apiColls.HandleFunc("/{alias}/unpin", handler.All(pinPost)).Methods("POST") + apiColls.HandleFunc("/{alias}/email/subscribe", handler.All(handleCreateEmailSubscription)).Methods("POST") + apiColls.HandleFunc("/{alias}/email/subscribe", handler.All(handleDeleteEmailSubscription)).Methods("DELETE") + apiColls.HandleFunc("/{collection}/email/unsubscribe", handler.All(handleDeleteEmailSubscription)).Methods("GET") apiColls.HandleFunc("/{alias}/inbox", handler.All(handleFetchCollectionInbox)).Methods("POST") apiColls.HandleFunc("/{alias}/outbox", handler.AllReader(handleFetchCollectionOutbox)).Methods("GET") apiColls.HandleFunc("/{alias}/following", handler.AllReader(handleFetchCollectionFollowing)).Methods("GET") @@ -220,6 +223,8 @@ func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) r.HandleFunc("/feed/", handler.AllReader(ViewFeed)) + r.HandleFunc("/email/confirm/{subscriber}", handler.All(handleConfirmEmailSubscription)).Methods("GET") + r.HandleFunc("/email/unsubscribe/{subscriber}", handler.All(handleDeleteEmailSubscription)).Methods("GET") r.HandleFunc("/{slug}", handler.CollectionPostOrStatic) r.HandleFunc("/{slug}/edit", handler.Web(handleViewPad, UserLevelUser)) r.HandleFunc("/{slug}/edit/meta", handler.Web(handleViewMeta, UserLevelUser)) diff --git a/session.go b/session.go index 81d628f..100842f 100644 --- a/session.go +++ b/session.go @@ -21,6 +21,10 @@ import ( const ( day = 86400 sessionLength = 180 * day + + userEmailCookieName = "ue" + userEmailCookieVal = "email" + cookieName = "wfu" cookieUserVal = "u" diff --git a/spam/email.go b/spam/email.go new file mode 100644 index 0000000..76158f5 --- /dev/null +++ b/spam/email.go @@ -0,0 +1,43 @@ +/* + * Copyright © 2020-2021 A Bunch Tell LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package spam + +import ( + "github.com/writeas/web-core/id" + "strings" +) + +var honeypotField string + +func HoneypotFieldName() string { + if honeypotField == "" { + honeypotField = id.Generate62RandomString(39) + } + return honeypotField +} + +// CleanEmail takes an email address and strips it down to a unique address that can be blocked. +func CleanEmail(email string) string { + emailParts := strings.Split(strings.ToLower(email), "@") + if len(emailParts) < 2 { + return "" + } + u := emailParts[0] + d := emailParts[1] + // Ignore anything after '+' + plusIdx := strings.IndexRune(u, '+') + if plusIdx > -1 { + u = u[:plusIdx] + } + // Strip dots in email address + u = strings.ReplaceAll(u, ".", "") + return u + "@" + d +} diff --git a/templates/collection.tmpl b/templates/collection.tmpl index 493e6b7..7669c88 100644 --- a/templates/collection.tmpl +++ b/templates/collection.tmpl @@ -102,6 +102,12 @@ {{end}} + {{if .Flash}} +
+

{{.Flash}}

+
+ {{end}} + {{template "posts" .}} {{if gt .TotalPages 1}}{{end}} + {{if not .IsWelcome}}{{template "emailsubscribe" .}}{{end}} + {{if .Posts}}{{else}}{{end}} {{if .ShowFooterBranding }} diff --git a/templates/include/post-render.tmpl b/templates/include/post-render.tmpl index 5b84845..fd3cbf2 100644 --- a/templates/include/post-render.tmpl +++ b/templates/include/post-render.tmpl @@ -102,3 +102,28 @@ {{end}} + +{{define "emailsubscribe"}} + {{if .EmailSubsEnabled}} +
+ {{if .IsSubscriber}} +

You're subscribed to email updates. Unsubscribe.

+ {{else}} +
+ +

Enter your email to subscribe to updates.

+ + +
+ + {{end}} +
+ {{end}} +{{end}} \ No newline at end of file diff --git a/templates/user/collection.tmpl b/templates/user/collection.tmpl index 041c107..d8cbb36 100644 --- a/templates/user/collection.tmpl +++ b/templates/user/collection.tmpl @@ -94,6 +94,44 @@ textarea.section.norm { +
+

Updates

+
+

Keep readers updated with your latest posts wherever they are.

+
    +
  • + +

    Readers can subscribe to your blog's RSS feed with their favorite RSS reader.

    +
  • + {{if .LettersCfg.Enabled}} +
  • + +

    + Let readers subscribe to your blog via email, and optionally accept private replies. +

    +
    + Allow replies to this address: + +
    +
  • + {{end}} + {{if .Federation}} +
  • + + @{{.Alias}}@{{.FriendlyHost}} +

    Allow others to follow your blog and interact with your posts in the fediverse. See how it works.

    +
  • + {{end}} +
+
+
+

Display Format

@@ -249,6 +287,13 @@ var $customDomain = document.getElementById('domain-alias'); var $customHandleEnv = document.getElementById('custom-handle-env'); var $normalHandleEnv = document.getElementById('normal-handle-env'); +var $emailSubsCheck = document.getElementById('email_subs'); +var $letterReply = document.getElementById('letter_reply'); +H.getEl('email_subs').on('click', function() { + let show = $emailSubsCheck.checked + $letterReply.disabled = !show +}) + if (matchMedia('(pointer:fine)').matches) { // Only initialize Ace editor on devices with a mouse var opt = { diff --git a/users.go b/users.go index cc6764f..1e18e24 100644 --- a/users.go +++ b/users.go @@ -134,3 +134,7 @@ func (u *User) IsAdmin() bool { func (u *User) IsSilenced() bool { return u.Status&UserSilenced != 0 } + +func (u *User) IsEmailSubscriber(app *App, collID int64) bool { + return app.db.IsEmailSubscriber("", u.ID, collID) +} From 276304d5b8614863d3f40f3fae3b053d6163380c Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 27 Jun 2021 10:35:36 -0400 Subject: [PATCH 006/115] Rearrange applyMarkdownSpecial parameters --- postrender.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/postrender.go b/postrender.go index 8e71109..1e265fa 100644 --- a/postrender.go +++ b/postrender.go @@ -119,7 +119,7 @@ func (p *PublicPost) augmentReadingDestination() { } func applyMarkdown(data []byte, baseURL string, cfg *config.Config) string { - return applyMarkdownSpecial(data, false, baseURL, cfg) + return applyMarkdownSpecial(data, baseURL, cfg, false) } func disableYoutubeAutoplay(outHTML string) string { @@ -141,7 +141,7 @@ func disableYoutubeAutoplay(outHTML string) string { return outHTML } -func applyMarkdownSpecial(data []byte, skipNoFollow bool, baseURL string, cfg *config.Config) string { +func applyMarkdownSpecial(data []byte, baseURL string, cfg *config.Config, skipNoFollow bool) string { mdExtensions := 0 | blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE | From cbc24274753454e6af486f8a8f532ab4228fda41 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 27 Jun 2021 10:51:53 -0400 Subject: [PATCH 007/115] Don't apply "nofollow" to links on single-user instances --- postrender.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postrender.go b/postrender.go index 1e265fa..4d1a24d 100644 --- a/postrender.go +++ b/postrender.go @@ -119,7 +119,7 @@ func (p *PublicPost) augmentReadingDestination() { } func applyMarkdown(data []byte, baseURL string, cfg *config.Config) string { - return applyMarkdownSpecial(data, baseURL, cfg, false) + return applyMarkdownSpecial(data, baseURL, cfg, cfg.App.SingleUser) } func disableYoutubeAutoplay(outHTML string) string { From e963755393aa7d6b4985b2cb2ca17a1ead05f23c Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 10 Aug 2021 18:01:19 -0400 Subject: [PATCH 008/115] Set 'To' addresses on Letter email after message is prepared This works with mailgun.AddRecipientAndVariables, so we can safely send emails to a large number of recipients beyond Mailgun's 1,000-recipient limit. Ref T856 --- email.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/email.go b/email.go index 451f988..a007422 100644 --- a/email.go +++ b/email.go @@ -328,19 +328,6 @@ Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/un if len(subs) == 0 { return nil } - log.Info("[email] Adding %d recipient(s)", len(subs)) - for _, s := range subs { - e := s.FinalEmail(app.keys) - log.Info("[email] Adding %s", e) - err = m.AddRecipientAndVariables(e, map[string]interface{}{ - "id": s.ID, - "to": e, - "token": s.Token, - }) - if err != nil { - log.Error("Unable to add receipient %s: %s", e, err) - } - } if title != "" { title = string(`

` + p.FormattedDisplayTitle() + `

`) @@ -425,6 +412,21 @@ Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/un } m.SetHtml(html) + + log.Info("[email] Adding %d recipient(s)", len(subs)) + for _, s := range subs { + e := s.FinalEmail(app.keys) + log.Info("[email] Adding %s", e) + err = m.AddRecipientAndVariables(e, map[string]interface{}{ + "id": s.ID, + "to": e, + "token": s.Token, + }) + if err != nil { + log.Error("Unable to add receipient %s: %s", e, err) + } + } + res, _, err := gun.Send(m) log.Info("[email] Send result: %s", res) if err != nil { From fc8e209def0bd12b7b351247a3c048fa95bce4c1 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 10 Aug 2021 18:05:24 -0400 Subject: [PATCH 009/115] Strip Markdown from Letter subjects Ref T856 --- email.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/email.go b/email.go index a007422..7e6c01c 100644 --- a/email.go +++ b/email.go @@ -314,7 +314,7 @@ Originally published on ` + p.Collection.DisplayTitle() + ` (` + p.Collection.Ca Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%` gun := mailgun.NewMailgun(app.cfg.Letters.Domain, app.cfg.Letters.MailgunPrivate) - m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Letters.Domain+">", p.Collection.DisplayTitle()+": "+p.DisplayTitle(), plainMsg) + m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Letters.Domain+">", p.Collection.DisplayTitle()+": "+stripmd.Strip(p.DisplayTitle()), plainMsg) replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo) if replyTo != "" { m.SetReplyTo(replyTo) From f4977c7a3484607a63ba32050c41ba206eeda1f6 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 13 Sep 2021 18:36:36 -0400 Subject: [PATCH 010/115] Support filtering blog posts by language Ref T805 --- collections.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ database.go | 55 ++++++++++++++++++++++++++++ routes.go | 1 + 3 files changed, 153 insertions(+) diff --git a/collections.go b/collections.go index beadf19..a3889a0 100644 --- a/collections.go +++ b/collections.go @@ -28,6 +28,7 @@ import ( "github.com/writeas/web-core/activitystreams" "github.com/writeas/web-core/auth" "github.com/writeas/web-core/bots" + "github.com/writeas/web-core/i18n" "github.com/writeas/web-core/log" waposts "github.com/writeas/web-core/posts" "github.com/writefreely/writefreely/author" @@ -366,6 +367,16 @@ func (c CollectionPage) DisplayMonetization() string { return displayMonetization(c.Monetization, c.Alias) } +func (c *DisplayCollection) Direction() string { + if c.Language == "" { + return "auto" + } + if i18n.LangIsRTL(c.Language) { + return "rtl" + } + return "ltr" +} + func newCollection(app *App, w http.ResponseWriter, r *http.Request) error { reqJSON := IsJSON(r) alias := r.FormValue("alias") @@ -991,6 +1002,92 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e return nil } +func handleViewCollectionLang(app *App, w http.ResponseWriter, r *http.Request) error { + vars := mux.Vars(r) + lang := vars["lang"] + + cr := &collectionReq{} + err := processCollectionRequest(cr, vars, w, r) + if err != nil { + return err + } + + u, err := checkUserForCollection(app, cr, r, false) + if err != nil { + return err + } + + page := getCollectionPage(vars) + + c, err := processCollectionPermissions(app, cr, u, w, r) + if c == nil || err != nil { + return err + } + + coll := newDisplayCollection(c, cr, page) + coll.Language = lang + + coll.Posts, _ = app.db.GetLangPosts(app.cfg, c, lang, page, cr.isCollOwner) + if err != nil { + return ErrCollectionPageNotFound + } + + // Serve collection + displayPage := struct { + CollectionPage + Tag string + }{ + CollectionPage: CollectionPage{ + DisplayCollection: coll, + StaticPage: pageForReq(app, r), + IsCustomDomain: cr.isCustomDomain, + }, + Tag: lang, + } + var owner *User + if u != nil { + displayPage.Username = u.Username + displayPage.IsOwner = u.ID == coll.OwnerID + if displayPage.IsOwner { + // Add in needed information for users viewing their own collection + owner = u + displayPage.CanPin = true + + pubColls, err := app.db.GetPublishableCollections(owner, app.cfg.App.Host) + if err != nil { + log.Error("unable to fetch collections: %v", err) + } + displayPage.Collections = pubColls + } + } + isOwner := owner != nil + if !isOwner { + // Current user doesn't own collection; retrieve owner information + owner, err = app.db.GetUserByID(coll.OwnerID) + if err != nil { + // Log the error and just continue + log.Error("Error getting user for collection: %v", err) + } + if owner.IsSilenced() { + return ErrCollectionNotFound + } + } + displayPage.Silenced = owner != nil && owner.IsSilenced() + displayPage.Owner = owner + coll.Owner = displayPage.Owner + // Add more data + // TODO: fix this mess of collections inside collections + displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner) + displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer") + + err = templates["collection"].ExecuteTemplate(w, "collection", displayPage) + if err != nil { + log.Error("Unable to render collection lang page: %v", err) + } + + return nil +} + func handleCollectionPostRedirect(app *App, w http.ResponseWriter, r *http.Request) error { vars := mux.Vars(r) slug := vars["slug"] diff --git a/database.go b/database.go index f474ae9..455a617 100644 --- a/database.go +++ b/database.go @@ -1260,6 +1260,61 @@ func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag strin return &posts, nil } +func (db *datastore) GetLangPosts(cfg *config.Config, c *Collection, lang string, page int, includeFuture bool) (*[]PublicPost, error) { + collID := c.ID + + cf := c.NewFormat() + order := "DESC" + if cf.Ascending() { + order = "ASC" + } + + pagePosts := cf.PostsPerPage() + start := page*pagePosts - pagePosts + if page == 0 { + start = 0 + pagePosts = 1000 + } + + limitStr := "" + if page > 0 { + limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts) + } + timeCondition := "" + if !includeFuture { + timeCondition = "AND created <= " + db.now() + } + + rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? AND language = ? "+timeCondition+" ORDER BY created "+order+limitStr, collID, lang) + if err != nil { + log.Error("Failed selecting from posts: %v", err) + return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts."} + } + defer rows.Close() + + // TODO: extract this common row scanning logic for queries using `postCols` + posts := []PublicPost{} + for rows.Next() { + p := &Post{} + err = rows.Scan(&p.ID, &p.Slug, &p.Font, &p.Language, &p.RTL, &p.Privacy, &p.OwnerID, &p.CollectionID, &p.PinnedPosition, &p.Created, &p.Updated, &p.ViewCount, &p.Title, &p.Content) + if err != nil { + log.Error("Failed scanning row: %v", err) + break + } + p.extractData() + p.augmentContent(c) + p.formatContent(cfg, c, includeFuture, false) + + posts = append(posts, p.processPost()) + } + err = rows.Err() + if err != nil { + log.Error("Error after Next() on rows: %v", err) + } + + return &posts, nil +} + func (db *datastore) GetAPFollowers(c *Collection) (*[]RemoteUser, error) { rows, err := db.Query("SELECT actor_id, inbox, shared_inbox FROM remotefollows f INNER JOIN remoteusers u ON f.remote_user_id = u.id WHERE collection_id = ?", c.ID) if err != nil { diff --git a/routes.go b/routes.go index 213958d..22d2496 100644 --- a/routes.go +++ b/routes.go @@ -216,6 +216,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional)) r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) + r.HandleFunc("/lang:{lang}", handler.Web(handleViewCollectionLang, UserLevelOptional)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) From c4b124e37c9edab7a58e25fb1f300c9deaf4bc0d Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Thu, 16 Sep 2021 14:05:52 -0400 Subject: [PATCH 011/115] Limit lang filter to 2 characters Ref T805 --- routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes.go b/routes.go index 22d2496..aa3b4f2 100644 --- a/routes.go +++ b/routes.go @@ -216,7 +216,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional)) r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) - r.HandleFunc("/lang:{lang}", handler.Web(handleViewCollectionLang, UserLevelOptional)) + r.HandleFunc("/lang:{lang:[a-z]{2}}", handler.Web(handleViewCollectionLang, UserLevelOptional)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) From 414d5b0a1c7addd89ad95d9b20737e8b25459a8b Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Thu, 16 Sep 2021 14:23:35 -0400 Subject: [PATCH 012/115] Add pagination routes on lang post filter Ref T805 --- collections.go | 14 ++++++++++++++ database.go | 15 ++++++++++++++- routes.go | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/collections.go b/collections.go index a3889a0..db71295 100644 --- a/collections.go +++ b/collections.go @@ -1027,6 +1027,20 @@ func handleViewCollectionLang(app *App, w http.ResponseWriter, r *http.Request) coll := newDisplayCollection(c, cr, page) coll.Language = lang + ttlPosts, err := app.db.GetCollLangTotalPosts(coll.ID, lang) + if err != nil { + log.Error("Unable to getCollLangTotalPosts: %s", err) + } + pagePosts := coll.Format.PostsPerPage() + coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts))) + if coll.TotalPages > 0 && page > coll.TotalPages { + redirURL := fmt.Sprintf("/lang:%s/page/%d", lang, coll.TotalPages) + if !app.cfg.App.SingleUser { + redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL) + } + return impart.HTTPError{http.StatusFound, redirURL} + } + coll.Posts, _ = app.db.GetLangPosts(app.cfg, c, lang, page, cr.isCollOwner) if err != nil { return ErrCollectionPageNotFound diff --git a/database.go b/database.go index 455a617..227fc0e 100644 --- a/database.go +++ b/database.go @@ -1260,6 +1260,16 @@ func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag strin return &posts, nil } +func (db *datastore) GetCollLangTotalPosts(collID int64, lang string) (uint64, error) { + var articles uint64 + err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND language = ?", collID, lang).Scan(&articles) + if err != nil && err != sql.ErrNoRows { + log.Error("Couldn't get total lang posts count for collection %d: %v", collID, err) + return 0, err + } + return articles, nil +} + func (db *datastore) GetLangPosts(cfg *config.Config, c *Collection, lang string, page int, includeFuture bool) (*[]PublicPost, error) { collID := c.ID @@ -1285,7 +1295,10 @@ func (db *datastore) GetLangPosts(cfg *config.Config, c *Collection, lang string timeCondition = "AND created <= " + db.now() } - rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? AND language = ? "+timeCondition+" ORDER BY created "+order+limitStr, collID, lang) + rows, err := db.Query(`SELECT `+postCols+` +FROM posts +WHERE collection_id = ? AND language = ? `+timeCondition+` +ORDER BY created `+order+limitStr, collID, lang) if err != nil { log.Error("Failed selecting from posts: %v", err) return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts."} diff --git a/routes.go b/routes.go index aa3b4f2..bdc3d04 100644 --- a/routes.go +++ b/routes.go @@ -217,6 +217,7 @@ func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional)) r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) r.HandleFunc("/lang:{lang:[a-z]{2}}", handler.Web(handleViewCollectionLang, UserLevelOptional)) + r.HandleFunc("/lang:{lang:[a-z]{2}}/page/{page:[0-9]+}", handler.Web(handleViewCollectionLang, UserLevelOptional)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) From e91748c0bcc783916553fcc0cb40734c607dc469 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Thu, 16 Sep 2021 15:53:07 -0400 Subject: [PATCH 013/115] Return correct count of currently-published lang posts Previously, we'd include scheduled posts, too. Ref T805 --- database.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database.go b/database.go index 227fc0e..efc1a10 100644 --- a/database.go +++ b/database.go @@ -1262,7 +1262,7 @@ func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag strin func (db *datastore) GetCollLangTotalPosts(collID int64, lang string) (uint64, error) { var articles uint64 - err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND language = ?", collID, lang).Scan(&articles) + err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND language = ? AND created <= "+db.now(), collID, lang).Scan(&articles) if err != nil && err != sql.ErrNoRows { log.Error("Couldn't get total lang posts count for collection %d: %v", collID, err) return 0, err From 27f68ef0cf99262b69ea14e4b93ea7a929d3436d Mon Sep 17 00:00:00 2001 From: Eli Mellen <3342274+eli-oat@users.noreply.github.com> Date: Sat, 25 Dec 2021 14:03:40 -0500 Subject: [PATCH 014/115] Update 404-general.tmpl Remove gaslighting error message and replace with one that is a wee bit more respectful to the human using the software. --- pages/404-general.tmpl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pages/404-general.tmpl b/pages/404-general.tmpl index dfc4653..07f2549 100644 --- a/pages/404-general.tmpl +++ b/pages/404-general.tmpl @@ -1,7 +1,6 @@ {{define "head"}}Page not found — {{.SiteName}}{{end}} {{define "content"}}
-

This page is missing.

-

Are you sure it was ever here?

+

Page not found.

{{end}} From c3ae4e6d3caffc5ed8e56ccb17dc40f406bfa28d Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 29 Mar 2022 13:00:53 -0400 Subject: [PATCH 015/115] Remove blog name in newsletter email subject Originally requested on the forum: https://discuss.write.as/t/minimize-subject-of-email-updates/3881 --- email.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/email.go b/email.go index 7e6c01c..d97ab15 100644 --- a/email.go +++ b/email.go @@ -314,7 +314,7 @@ Originally published on ` + p.Collection.DisplayTitle() + ` (` + p.Collection.Ca Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%` gun := mailgun.NewMailgun(app.cfg.Letters.Domain, app.cfg.Letters.MailgunPrivate) - m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Letters.Domain+">", p.Collection.DisplayTitle()+": "+stripmd.Strip(p.DisplayTitle()), plainMsg) + m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Letters.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg) replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo) if replyTo != "" { m.SetReplyTo(replyTo) From baaf0580f56262d67b3697dc18f1be2189c81580 Mon Sep 17 00:00:00 2001 From: ltdk Date: Wed, 27 Apr 2022 09:03:45 -0400 Subject: [PATCH 016/115] Add unix socket support Enables listening on unix sockets by specifying a file path for the bind address --- app.go | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 4c4aeb9..aef1fe3 100644 --- a/app.go +++ b/app.go @@ -16,6 +16,7 @@ import ( "fmt" "html/template" "io/ioutil" + "net" "net/http" "net/url" "os" @@ -503,9 +504,44 @@ requests. We recommend supplying a valid host name.`) err = http.ListenAndServeTLS(fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r) } } else { - log.Info("Serving on http://%s:%d\n", bindAddress, app.cfg.Server.Port) + var network string + var protocol string + + if strings.HasPrefix(bindAddress, "/") { + network = "unix" + protocol = "http+unix" + + // old sockets will remain after server closes; + // we need to delete them in order to open new ones + removeSocketErr := os.Remove(bindAddress) + if removeSocketErr != nil && !os.IsNotExist(removeSocketErr) { + log.Error("%s already exists but could not be removed: %v", bindAddress, removeSocketErr) + os.Exit(1) + } + } else { + network = "tcp" + protocol = "http" + bindAddress = fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port) + } + + log.Info("Serving on %s://%s", protocol, bindAddress) log.Info("---") - err = http.ListenAndServe(fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port), r) + listener, listenErr := net.Listen(network, bindAddress) + if listenErr != nil { + log.Error("Could not bind to address: %v", listenErr) + os.Exit(1) + } + + if network == "unix" { + chmodSocketErr := os.Chmod(bindAddress, 0o666) + if chmodSocketErr != nil { + log.Error("Could not update socket permissions: %v", chmodSocketErr) + os.Exit(1) + } + } + + defer listener.Close() + err = http.Serve(listener, r) } if err != nil { log.Error("Unable to start: %v", err) From 0a19dc1ec24703b7ad68840f961f2b104e03f8e3 Mon Sep 17 00:00:00 2001 From: ltdk Date: Wed, 11 May 2022 13:11:22 -0400 Subject: [PATCH 017/115] Add editorconfig --- .editorconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7ab0722 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.go] +indent_style = tab From a5c80b98e7b0c623db320411a771c748f8629db3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 08:29:20 +0000 Subject: [PATCH 018/115] Bump github.com/guregu/null Bumps [github.com/guregu/null](https://github.com/guregu/null) from 3.5.0+incompatible to 4.0.0+incompatible. - [Release notes](https://github.com/guregu/null/releases) - [Commits](https://github.com/guregu/null/compare/v3.5.0...v4.0.0) --- updated-dependencies: - dependency-name: github.com/guregu/null dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index df4ae66..33828ce 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/schema v1.2.0 github.com/gorilla/sessions v1.2.1 - github.com/guregu/null v3.5.0+incompatible + github.com/guregu/null v4.0.0+incompatible github.com/hashicorp/go-multierror v1.1.1 github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec diff --git a/go.sum b/go.sum index 8549f80..c383f80 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/guregu/null v3.5.0+incompatible h1:fSdvRTQtmBA4B4YDZXhLtxTIJZYuUxBFTTHS4B9djG4= -github.com/guregu/null v3.5.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= +github.com/guregu/null v4.0.0+incompatible h1:4zw0ckM7ECd6FNNddc3Fu4aty9nTlpkkzH7dPn4/4Gw= +github.com/guregu/null v4.0.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= From 7a84d27dca8ca85edf039e01c188a9d02f97fbb8 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 26 Dec 2022 13:17:56 -0500 Subject: [PATCH 019/115] Re-use err variable, instead of creating new error vars --- app.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app.go b/app.go index aef1fe3..303cb36 100644 --- a/app.go +++ b/app.go @@ -513,9 +513,9 @@ requests. We recommend supplying a valid host name.`) // old sockets will remain after server closes; // we need to delete them in order to open new ones - removeSocketErr := os.Remove(bindAddress) - if removeSocketErr != nil && !os.IsNotExist(removeSocketErr) { - log.Error("%s already exists but could not be removed: %v", bindAddress, removeSocketErr) + err = os.Remove(bindAddress) + if err != nil && !os.IsNotExist(err) { + log.Error("%s already exists but could not be removed: %v", bindAddress, err) os.Exit(1) } } else { @@ -526,16 +526,16 @@ requests. We recommend supplying a valid host name.`) log.Info("Serving on %s://%s", protocol, bindAddress) log.Info("---") - listener, listenErr := net.Listen(network, bindAddress) - if listenErr != nil { - log.Error("Could not bind to address: %v", listenErr) + listener, err := net.Listen(network, bindAddress) + if err != nil { + log.Error("Could not bind to address: %v", err) os.Exit(1) } if network == "unix" { - chmodSocketErr := os.Chmod(bindAddress, 0o666) - if chmodSocketErr != nil { - log.Error("Could not update socket permissions: %v", chmodSocketErr) + err = os.Chmod(bindAddress, 0o666) + if err != nil { + log.Error("Could not update socket permissions: %v", err) os.Exit(1) } } From f84b4b0f74e803504304d441ccd18aea900c0816 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 26 Dec 2022 13:18:45 -0500 Subject: [PATCH 020/115] Use more idiomatic variable initialization for network/protocol --- app.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app.go b/app.go index 303cb36..990f71c 100644 --- a/app.go +++ b/app.go @@ -504,9 +504,8 @@ requests. We recommend supplying a valid host name.`) err = http.ListenAndServeTLS(fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r) } } else { - var network string - var protocol string - + network := "tcp" + protocol := "http" if strings.HasPrefix(bindAddress, "/") { network = "unix" protocol = "http+unix" @@ -519,8 +518,6 @@ requests. We recommend supplying a valid host name.`) os.Exit(1) } } else { - network = "tcp" - protocol = "http" bindAddress = fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port) } From a0f1e1821f8f3efe7a5d90120176ddec34562713 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 26 Dec 2022 13:20:28 -0500 Subject: [PATCH 021/115] Delete socket file on server shutdown --- app.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app.go b/app.go index 990f71c..1e48b90 100644 --- a/app.go +++ b/app.go @@ -845,6 +845,16 @@ func connectToDatabase(app *App) { func shutdown(app *App) { log.Info("Closing database connection...") app.db.Close() + if strings.HasPrefix(app.cfg.Server.Bind, "/") { + // Clean up socket + log.Info("Removing socket file...") + err := os.Remove(app.cfg.Server.Bind) + if err != nil { + log.Error("Unable to remove socket: %s", err) + os.Exit(1) + } + log.Info("Success.") + } } // CreateUser creates a new admin or normal user from the given credentials. From fc5a79a6bcf9ec920c6058b4f53b45a96dae2cf3 Mon Sep 17 00:00:00 2001 From: Timshel Date: Tue, 3 Jan 2023 17:18:02 +0100 Subject: [PATCH 022/115] Use TEXT for oauth_users.access_token to prevent insertion failure --- migrations/migrations.go | 1 + migrations/v11.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 migrations/v11.go diff --git a/migrations/migrations.go b/migrations/migrations.go index 310590b..9a20528 100644 --- a/migrations/migrations.go +++ b/migrations/migrations.go @@ -66,6 +66,7 @@ var migrations = []Migration{ New("support oauth via invite", oauthInvites), // V7 -> V8 (v0.12.0) New("optimize drafts retrieval", optimizeDrafts), // V8 -> V9 New("support post signatures", supportPostSignatures), // V9 -> V10 + New("Widen oauth_users.access_token", widenOauthAcceesToken), // V10 -> V11 } // CurrentVer returns the current migration version the application is on diff --git a/migrations/v11.go b/migrations/v11.go new file mode 100644 index 0000000..412c648 --- /dev/null +++ b/migrations/v11.go @@ -0,0 +1,38 @@ +/* + * Copyright © 2020 Musing Studio LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package migrations + +/** + * Widen `oauth_users.access_token`, necessary only for mysql + */ +func widenOauthAcceesToken(db *datastore) error { + if db.driverName == driverMySQL { + t, err := db.Begin() + if err != nil { + t.Rollback() + return err + } + + _, err = t.Exec(`ALTER TABLE oauth_users MODIFY COLUMN access_token ` + db.typeText() + db.collateMultiByte() + ` NULL`) + if err != nil { + t.Rollback() + return err + } + + err = t.Commit() + if err != nil { + t.Rollback() + return err + } + } + + return nil +} From 99d72881cf833326f02d86a8896bbd2b8156327b Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 6 Jan 2023 15:34:22 -0500 Subject: [PATCH 023/115] Catch and output directory walking errors Previously, app would panic and admins would see unhelpful errors. This closes #620 --- author/author.go | 10 +++++++++- templates.go | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/author/author.go b/author/author.go index 9c1fec9..a95eb61 100644 --- a/author/author.go +++ b/author/author.go @@ -11,6 +11,7 @@ package author import ( + "github.com/writeas/web-core/log" "github.com/writefreely/writefreely/config" "os" "path/filepath" @@ -113,10 +114,17 @@ func IsValidUsername(cfg *config.Config, username string) bool { // Username is invalid if page with the same name exists. So traverse // available pages, adding them to reservedUsernames map that'll be checked // later. - filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, "pages"), func(path string, i os.FileInfo, err error) error { + err := filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, "pages"), func(path string, i os.FileInfo, err error) error { + if err != nil { + return err + } reservedUsernames[i.Name()] = true return nil }) + if err != nil { + log.Error("[IMPORTANT WARNING]: Could not determine IsValidUsername! %s", err) + return false + } // Username is invalid if it is reserved! if _, reserved := reservedUsernames[username]; reserved { diff --git a/templates.go b/templates.go index ecd8750..e51317b 100644 --- a/templates.go +++ b/templates.go @@ -135,7 +135,10 @@ func InitTemplates(cfg *config.Config) error { log.Info("Loading pages...") // Initialize all static pages that use the base template - filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, pagesDir), func(path string, i os.FileInfo, err error) error { + err = filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, pagesDir), func(path string, i os.FileInfo, err error) error { + if err != nil { + return err + } if !i.IsDir() && !strings.HasPrefix(i.Name(), ".") { key := i.Name() initPage(cfg.Server.PagesParentDir, path, key) @@ -143,10 +146,16 @@ func InitTemplates(cfg *config.Config) error { return nil }) + if err != nil { + return err + } log.Info("Loading user pages...") // Initialize all user pages that use base templates - filepath.Walk(filepath.Join(cfg.Server.TemplatesParentDir, templatesDir, "user"), func(path string, f os.FileInfo, err error) error { + err = filepath.Walk(filepath.Join(cfg.Server.TemplatesParentDir, templatesDir, "user"), func(path string, f os.FileInfo, err error) error { + if err != nil { + return err + } if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") { corePath := path if cfg.Server.TemplatesParentDir != "" { @@ -162,6 +171,9 @@ func InitTemplates(cfg *config.Config) error { return nil }) + if err != nil { + return err + } return nil } From 77cc1cc816fb81ae946fb0e9985545ab0aec2b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lteri=C5=9F=20Ya=C4=9F=C4=B1ztegin=20Ero=C4=9Flu?= Date: Fri, 20 Jan 2023 11:36:11 +0000 Subject: [PATCH 024/115] fix Makefile and Dockerfile to build on latest go versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: İlteriş Yağıztegin Eroğlu --- Dockerfile | 5 ++++- Makefile | 24 ++++++++++++------------ ossl_legacy.cnf | 10 ++++++++++ 3 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 ossl_legacy.cnf diff --git a/Dockerfile b/Dockerfile index 38cd2c7..fb34f48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build image -FROM golang:1.15-alpine as build +FROM golang:1.19-alpine as build RUN apk add --update nodejs npm make g++ git RUN npm install -g less less-plugin-clean-css @@ -9,7 +9,10 @@ WORKDIR /go/src/github.com/writefreely/writefreely COPY . . +RUN cat ossl_legacy.cnf > /etc/ssl/openssl.cnf + ENV GO111MODULE=on +ENV NODE_OPTIONS=--openssl-legacy-provider RUN make build \ && make ui diff --git a/Makefile b/Makefile index 6f59a18..7e7102d 100644 --- a/Makefile +++ b/Makefile @@ -25,39 +25,39 @@ build-no-sqlite: deps-no-sqlite build-linux: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GOGET) -u src.techknowlogick.com/xgo; \ + $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely + xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-windows: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GOGET) -u src.techknowlogick.com/xgo; \ + $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely + xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-darwin: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GOGET) -u src.techknowlogick.com/xgo; \ + $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely + xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-arm6: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GOGET) -u src.techknowlogick.com/xgo; \ + $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely + xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-arm7: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GOGET) -u src.techknowlogick.com/xgo; \ + $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely + xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-arm64: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GOGET) -u src.techknowlogick.com/xgo; \ + $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely + xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-docker : $(DOCKERCMD) build -t $(IMAGE_NAME):latest -t $(IMAGE_NAME):$(GITREV) . diff --git a/ossl_legacy.cnf b/ossl_legacy.cnf new file mode 100644 index 0000000..90c36f2 --- /dev/null +++ b/ossl_legacy.cnf @@ -0,0 +1,10 @@ + +[provider_sect] +default = default_sect +legacy = legacy_sect + +[default_sect] +activate = 1 + +[legacy_sect] +activate = 1 From 83765d5cbc756e2a210775618bb2a10a3c424948 Mon Sep 17 00:00:00 2001 From: Abdullah <73560425+zer-far@users.noreply.github.com> Date: Tue, 31 Jan 2023 21:27:56 +0000 Subject: [PATCH 025/115] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6f59a18..e9374c4 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ GITREV=`git describe | cut -c 2-` -LDFLAGS=-ldflags="-X 'github.com/writefreely/writefreely.softwareVer=$(GITREV)'" +LDFLAGS=-ldflags="-s -w -X 'github.com/writefreely/writefreely.softwareVer=$(GITREV)'" GOCMD=go GOINSTALL=$(GOCMD) install $(LDFLAGS) From ee665c0c6886b633d58655efdd37fbe1ba55e174 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 23:00:16 +0000 Subject: [PATCH 026/115] Bump github.com/writeas/web-core Bumps [github.com/writeas/web-core](https://github.com/writeas/web-core) from 1.4.1-0.20220118212728-0da0bcaf018e to 1.4.1. - [Release notes](https://github.com/writeas/web-core/releases) - [Commits](https://github.com/writeas/web-core/commits/v1.4.1) --- updated-dependencies: - dependency-name: github.com/writeas/web-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 9bdb518..12dfdd1 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219 github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320 github.com/writeas/slug v1.2.0 - github.com/writeas/web-core v1.4.1-0.20220118212728-0da0bcaf018e + github.com/writeas/web-core v1.4.1 github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b github.com/writefreely/go-nodeinfo v1.2.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 diff --git a/go.sum b/go.sum index b3dc01e..73979fe 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,6 @@ github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6 github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2/go.mod h1:WtaVKD9TeruTED9ydiaOJU08qGoEPP/LyzTKiD3jEsw= github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= -github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -152,8 +150,8 @@ github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320 h1:PozPZ29CQ/xt github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ= github.com/writeas/slug v1.2.0 h1:EMQ+cwLiOcA6EtFwUgyw3Ge18x9uflUnOnR6bp/J+/g= github.com/writeas/slug v1.2.0/go.mod h1:RE8shOqQP3YhsfsQe0L3RnuejfQ4Mk+JjY5YJQFubfQ= -github.com/writeas/web-core v1.4.1-0.20220118212728-0da0bcaf018e h1:/SiS+iQb5YbE/QtKlfJKHSO2c8KwamtTkg0Ojg1uFEI= -github.com/writeas/web-core v1.4.1-0.20220118212728-0da0bcaf018e/go.mod h1:MTWDZWikeG063S9IrI6ekvu3N2tJEVRpZuU4kAWg1DY= +github.com/writeas/web-core v1.4.1 h1:mdDwZepEyQb76j8gNUIPblV7SUIXi4WQ0h3Xl0ZwKT4= +github.com/writeas/web-core v1.4.1/go.mod h1:MTWDZWikeG063S9IrI6ekvu3N2tJEVRpZuU4kAWg1DY= github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b h1:h3NzB8OZ50NNi5k9yrFeyFszt3LyqyVK4+xUHFYY8B0= github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b/go.mod h1:T2UVVzt+R5KSSZe2xRSytnwc2M9AoDegi7foeIsik+M= github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss= From af0927cf5c9ea49b4c27559589ed4068024f09e5 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:23:23 -0500 Subject: [PATCH 027/115] spelling: consequences Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- keys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keys.go b/keys.go index 98ff13f..06a443a 100644 --- a/keys.go +++ b/keys.go @@ -52,7 +52,7 @@ func initKeyPaths(app *App) { func generateKey(path string) error { // Check if key file exists if _, err := os.Stat(path); err == nil { - log.Info("%s already exists. rm the file if you understand the consquences.", path) + log.Info("%s already exists. rm the file if you understand the consequences.", path) return nil } else if !os.IsNotExist(err) { log.Error("%s", err) From bc53300e335f93897d5066d5df7afc099db6b031 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:23:24 -0500 Subject: [PATCH 028/115] spelling: dynamic Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes.go b/routes.go index 00b6bd0..b935759 100644 --- a/routes.go +++ b/routes.go @@ -82,7 +82,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { configureGenericOauth(handler, write, apper.App()) configureGiteaOauth(handler, write, apper.App()) - // Set up dyamic page handlers + // Set up dynamic page handlers // Handle auth auth := write.PathPrefix("/api/auth/").Subrouter() if apper.App().cfg.App.OpenRegistration { From 680f0d1e2051dcb66fa754c5fa216804be87950b Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:12:25 -0500 Subject: [PATCH 029/115] spelling: github Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d3bc432..9acc89e 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,4 +1,4 @@ -name: Build container image, publish as Github-package +name: Build container image, publish as GitHub-package # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by From 7feea370ed46820c7a93a641cc0c49a416007388 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:23:24 -0500 Subject: [PATCH 030/115] spelling: highlight Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- templates/include/post-render.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/include/post-render.tmpl b/templates/include/post-render.tmpl index 5b84845..c44e3a7 100644 --- a/templates/include/post-render.tmpl +++ b/templates/include/post-render.tmpl @@ -76,7 +76,7 @@ jss.push(lurl); } } - // Load files in order, higlight on last load + // Load files in order, highlight on last load loadLanguages(jss, () => {highlight(lb)}); } }); From 8834253502f094e00e43e88df69d9f167bc4b443 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:14:51 -0500 Subject: [PATCH 031/115] spelling: into Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- account.go | 2 +- pages.go | 2 +- pages/login.tmpl | 6 +++--- templates/user/admin/view-user.tmpl | 2 +- templates/user/settings.tmpl | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/account.go b/account.go index 91a8ace..ff87e41 100644 --- a/account.go +++ b/account.go @@ -504,7 +504,7 @@ func login(app *App, w http.ResponseWriter, r *http.Request) error { // User has no email set, so check if they haven't added a password, either, // so we can return a more helpful error message. if hasPass, _ := app.db.IsUserPassSet(u.ID); !hasPass { - log.Info("Tried logging in to %s, but no password or email.", signin.Alias) + log.Info("Tried logging into %s, but no password or email.", signin.Alias) return impart.HTTPError{http.StatusPreconditionFailed, "This user never added a password or email address. Please contact us for help."} } } diff --git a/pages.go b/pages.go index 8b3a987..ce37688 100644 --- a/pages.go +++ b/pages.go @@ -75,7 +75,7 @@ func defaultPrivacyPolicy(cfg *config.Config) string { It retains as little data about you as possible, not even requiring an email address to sign up. However, if you _do_ give us your email address, it is stored encrypted in our database. We salt and hash your account's password. -We store log files, or data about what happens on our servers. We also use cookies to keep you logged in to your account. +We store log files, or data about what happens on our servers. We also use cookies to keep you logged into your account. Beyond this, it's important that you trust whoever runs **` + cfg.App.SiteName + `**. Software can only do so much to protect you -- your level of privacy protections will ultimately fall on the humans that run this particular service.` } diff --git a/pages/login.tmpl b/pages/login.tmpl index f0a54eb..0fe29a1 100644 --- a/pages/login.tmpl +++ b/pages/login.tmpl @@ -1,13 +1,13 @@ {{define "head"}}Log in — {{.SiteName}} - - + + {{end}} {{define "content"}}
-

Log in to {{.SiteName}}

+

Log into {{.SiteName}}

{{if .Flashes}}
    {{range .Flashes}}
  • {{.}}
  • {{end}} diff --git a/templates/user/admin/view-user.tmpl b/templates/user/admin/view-user.tmpl index dac88bf..4a06c03 100644 --- a/templates/user/admin/view-user.tmpl +++ b/templates/user/admin/view-user.tmpl @@ -45,7 +45,7 @@ input.copy-text { {{if .NewPassword}}

    This user's password has been reset to:

    -

    They can use this new password to log in to their account. This will only be shown once, so be sure to copy it and send it to them now.

    +

    They can use this new password to log into their account. This will only be shown once, so be sure to copy it and send it to them now.

    {{if .ClearEmail}}

    Their email address is: {{.ClearEmail}}

    {{end}}
    {{end}} diff --git a/templates/user/settings.tmpl b/templates/user/settings.tmpl index 338ea9a..829e4be 100644 --- a/templates/user/settings.tmpl +++ b/templates/user/settings.tmpl @@ -55,7 +55,7 @@ h3 { font-weight: normal; }

    Passphrase

    - {{if and (not .HasPass) (not .IsLogOut)}}

    Add a passphrase to easily log in to your account.

    {{end}} + {{if and (not .HasPass) (not .IsLogOut)}}

    Add a passphrase to easily log into your account.

    {{end}} {{if .HasPass}}

    Current passphrase

    New passphrase

    From 8dd7b40c025e356e5fe133a5e8f7b748515f385e Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:12:36 -0500 Subject: [PATCH 032/115] spelling: javascript Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- static/js/README.md | 2 +- templates/bare.tmpl | 2 +- templates/classic.tmpl | 2 +- templates/pad.tmpl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/static/js/README.md b/static/js/README.md index 7e387db..8497b66 100644 --- a/static/js/README.md +++ b/static/js/README.md @@ -1,6 +1,6 @@ # static/js -This directory is for Javascript. +This directory is for JavaScript. ## Updating libraries diff --git a/templates/bare.tmpl b/templates/bare.tmpl index a5f9910..cd0fa8c 100644 --- a/templates/bare.tmpl +++ b/templates/bare.tmpl @@ -28,7 +28,7 @@
- +
{{if .Editing}}{{end}}
diff --git a/templates/classic.tmpl b/templates/classic.tmpl index 7032f58..58f82c7 100644 --- a/templates/classic.tmpl +++ b/templates/classic.tmpl @@ -62,7 +62,7 @@
- +
{{if .Editing}}{{end}} diff --git a/templates/pad.tmpl b/templates/pad.tmpl index 555bbb3..eb1afba 100644 --- a/templates/pad.tmpl +++ b/templates/pad.tmpl @@ -57,7 +57,7 @@
- +
{{if .Editing}}{{end}} From af875b4d87caba2f67b04a5fb77dd4d4b45faff8 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:23:25 -0500 Subject: [PATCH 033/115] spelling: message Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- account.go | 2 +- posts.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/account.go b/account.go index ff87e41..89e8d15 100644 --- a/account.go +++ b/account.go @@ -577,7 +577,7 @@ func getVerboseAuthUser(app *App, token string, u *User, verbose bool) *AuthUser } passIsSet, err := app.db.IsUserPassSet(u.ID) if err != nil { - // TODO: correct error meesage + // TODO: correct error message log.Error("Login: Unable to get user collections: %v", err) } diff --git a/posts.go b/posts.go index e95532e..c78036b 100644 --- a/posts.go +++ b/posts.go @@ -1067,7 +1067,7 @@ func pinPost(app *App, w http.ResponseWriter, r *http.Request) error { ppr := PinPostResult{ID: p.ID} if err != nil { ppr.Code = http.StatusInternalServerError - // TODO: set error messsage + // TODO: set error message } else { ppr.Code = http.StatusOK } From 7e5d60043d7a20fe74b1fb45e72978e1bb397645 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:23:26 -0500 Subject: [PATCH 034/115] spelling: miscellaneous Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- templates/include/post-render.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/include/post-render.tmpl b/templates/include/post-render.tmpl index c44e3a7..39bc7f6 100644 --- a/templates/include/post-render.tmpl +++ b/templates/include/post-render.tmpl @@ -1,4 +1,4 @@ - + {{define "collection-meta"}} {{if .Monetization -}} From 0746ec8567976192eb588d8dcd430df9d6ce1595 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:23:26 -0500 Subject: [PATCH 035/115] spelling: modified Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- scripts/upgrade-server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/upgrade-server.sh b/scripts/upgrade-server.sh index b129943..f010c94 100755 --- a/scripts/upgrade-server.sh +++ b/scripts/upgrade-server.sh @@ -2,7 +2,7 @@ ############################################################################### ## writefreely update script ## ## ## -## WARNING: running this script will overwrite any modifed assets or ## +## WARNING: running this script will overwrite any modified assets or ## ## template files. If you have any custom changes to these files you ## ## should back them up FIRST. ## ## ## From 02fb079a9f8246a2a7c07f2790cdf930117ff887 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:24:07 -0500 Subject: [PATCH 036/115] spelling: optional Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- handle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handle.go b/handle.go index e0600bb..7537b61 100644 --- a/handle.go +++ b/handle.go @@ -262,7 +262,7 @@ func apiAuth(app *App, r *http.Request) (*User, error) { return u, nil } -// optionaAPIAuth is used for endpoints that accept authenticated requests via +// optionalAPIAuth is used for endpoints that accept authenticated requests via // Authorization header or cookie, unlike apiAuth. It returns a different err // in the case where no Authorization header is present. func optionalAPIAuth(app *App, r *http.Request) (*User, error) { From ea81e2c8396796e0229d9c63cacda0002f4cac77 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Sun, 5 Mar 2023 02:23:27 -0500 Subject: [PATCH 037/115] spelling: pattern Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- templates/user/admin/users.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/user/admin/users.tmpl b/templates/user/admin/users.tmpl index f6b218c..935d445 100644 --- a/templates/user/admin/users.tmpl +++ b/templates/user/admin/users.tmpl @@ -4,7 +4,7 @@
{{template "admin-header" .}} - + {{if .Flashes}}

{{range .Flashes}}{{.}}{{end}} From 64772aa20326fde084bb1a9ea5b60d7fe325f769 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 14 Mar 2023 15:13:03 -0400 Subject: [PATCH 038/115] Loosen restrictions on rendered img alt attribute Previously, certain characters weren't allowed, and they would cause the entire alt attribute to be elided from the rendered page. Since we safely sanitize the content of this attribute anyway, this is unnecessary, so we now allow all text entered there. Fixes #649 --- postrender.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/postrender.go b/postrender.go index 5be8d0c..8e4d706 100644 --- a/postrender.go +++ b/postrender.go @@ -270,6 +270,7 @@ func getSanitizationPolicy() *bluemonday.Policy { policy.AllowAttrs("target").OnElements("a") policy.AllowAttrs("title").OnElements("abbr") policy.AllowAttrs("style", "class", "id").Globally() + policy.AllowAttrs("alt").OnElements("img") policy.AllowElements("header", "footer") policy.AllowURLSchemes("http", "https", "mailto", "xmpp") return policy @@ -284,12 +285,13 @@ func sanitizePost(content string) string { // choosing what to generate. In case a post has a title, this function will // fail, and logic should instead be implemented to skip this when there's no // title, like so: -// var desc string -// if title == "" { -// desc = postDescription(content, title, friendlyId) -// } else { -// desc = shortPostDescription(content) -// } +// +// var desc string +// if title == "" { +// desc = postDescription(content, title, friendlyId) +// } else { +// desc = shortPostDescription(content) +// } func postDescription(content, title, friendlyId string) string { maxLen := 140 From 43176ed7ea71086fc9b19309441d4f9c95926921 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Apr 2023 22:59:14 +0000 Subject: [PATCH 039/115] Bump github.com/fatih/color from 1.13.0 to 1.15.0 Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.13.0 to 1.15.0. - [Release notes](https://github.com/fatih/color/releases) - [Commits](https://github.com/fatih/color/compare/v1.13.0...v1.15.0) --- updated-dependencies: - dependency-name: github.com/fatih/color dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 22 ++++++++++------------ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 12dfdd1..b057c0e 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/writefreely/writefreely require ( github.com/dustin/go-humanize v1.0.0 - github.com/fatih/color v1.13.0 + github.com/fatih/color v1.15.0 github.com/go-ini/ini v1.67.0 github.com/go-sql-driver/mysql v1.6.0 github.com/gorilla/csrf v1.7.1 @@ -58,8 +58,8 @@ require ( github.com/gorilla/securecookie v1.1.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/jtolds/gls v4.2.1+incompatible // indirect - github.com/mattn/go-colorable v0.1.9 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -73,7 +73,7 @@ require ( github.com/writeas/go-writeas/v2 v2.0.2 // indirect github.com/writeas/openssl-go v1.0.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 73979fe..2e36ecf 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0 github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/go-fed/httpsig v0.1.0/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= @@ -79,11 +79,11 @@ github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec h1:ZXWuspq github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec/go.mod h1:voECJzdraJmolzPBgL9Z7ANwXf4oMXaTCsIkdiPpR/g= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/microcosm-cc/bluemonday v1.0.5/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= @@ -171,11 +171,9 @@ golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= From ad6c8f30bc0038d1f50408b44a23fd07176edd41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Apr 2023 15:22:13 +0000 Subject: [PATCH 040/115] Bump golang.org/x/crypto from 0.0.0-20200622213623-75b288015ac9 to 0.8.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20200622213623-75b288015ac9 to 0.8.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/commits/v0.8.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index b057c0e..11e12bc 100644 --- a/go.mod +++ b/go.mod @@ -34,8 +34,8 @@ require ( github.com/writeas/web-core v1.4.1 github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b github.com/writefreely/go-nodeinfo v1.2.0 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20221002022538-bcab6841153b + golang.org/x/crypto v0.8.0 + golang.org/x/net v0.9.0 ) require ( @@ -73,8 +73,8 @@ require ( github.com/writeas/go-writeas/v2 v2.0.2 // indirect github.com/writeas/openssl-go v1.0.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2e36ecf..1654ffc 100644 --- a/go.sum +++ b/go.sum @@ -161,22 +161,22 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 0dcfd1809db160f6426bd55c2e144178a6f5639e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Apr 2023 15:22:17 +0000 Subject: [PATCH 041/115] Bump github.com/dustin/go-humanize from 1.0.0 to 1.0.1 Bumps [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) from 1.0.0 to 1.0.1. - [Release notes](https://github.com/dustin/go-humanize/releases) - [Commits](https://github.com/dustin/go-humanize/compare/v1.0.0...v1.0.1) --- updated-dependencies: - dependency-name: github.com/dustin/go-humanize dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b057c0e..d408313 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/writefreely/writefreely require ( - github.com/dustin/go-humanize v1.0.0 + github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.15.0 github.com/go-ini/ini v1.67.0 github.com/go-sql-driver/mysql v1.6.0 diff --git a/go.sum b/go.sum index 2e36ecf..7f3c80d 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs= github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= From cdaa13a260b54b4216c4a33fb6a24c555c23aa78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Apr 2023 15:22:18 +0000 Subject: [PATCH 042/115] Bump github.com/go-sql-driver/mysql from 1.6.0 to 1.7.0 Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.6.0 to 1.7.0. - [Release notes](https://github.com/go-sql-driver/mysql/releases) - [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md) - [Commits](https://github.com/go-sql-driver/mysql/compare/v1.6.0...v1.7.0) --- updated-dependencies: - dependency-name: github.com/go-sql-driver/mysql dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b057c0e..23ebbeb 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/fatih/color v1.15.0 github.com/go-ini/ini v1.67.0 - github.com/go-sql-driver/mysql v1.6.0 + github.com/go-sql-driver/mysql v1.7.0 github.com/gorilla/csrf v1.7.1 github.com/gorilla/feeds v1.1.1 github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index 2e36ecf..fa17a91 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4L github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= From 1a4845aca8b9e0956333c2eb3158906fb92eace1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Apr 2023 15:24:24 +0000 Subject: [PATCH 043/115] Bump github.com/microcosm-cc/bluemonday from 1.0.21 to 1.0.23 Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.21 to 1.0.23. - [Release notes](https://github.com/microcosm-cc/bluemonday/releases) - [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.21...v1.0.23) --- updated-dependencies: - dependency-name: github.com/microcosm-cc/bluemonday dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 11e12bc..927e61e 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec github.com/manifoldco/promptui v0.9.0 github.com/mattn/go-sqlite3 v1.14.16 - github.com/microcosm-cc/bluemonday v1.0.21 + github.com/microcosm-cc/bluemonday v1.0.23 github.com/mitchellh/go-wordwrap v1.0.1 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d github.com/stretchr/testify v1.8.1 diff --git a/go.sum b/go.sum index 1654ffc..fe31d9a 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/microcosm-cc/bluemonday v1.0.5/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= -github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= -github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY= +github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= From 3a7554abe81168b1a003d19113f9806c9043c017 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 28 Apr 2023 10:07:00 -0400 Subject: [PATCH 044/115] Use Go DNS resolution in release binaries This builds with the `netgo` tag, ensuring WF binaries use Go DNS resolution instead of libc, preventing unhelpful errors when the application can't resolve addresses. Closes #675 --- Makefile | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 7e7102d..4dda043 100644 --- a/Makefile +++ b/Makefile @@ -18,46 +18,46 @@ ci: deps cd cmd/writefreely; $(GOBUILD) -v build: deps - cd cmd/writefreely; $(GOBUILD) -v -tags='sqlite' + cd cmd/writefreely; $(GOBUILD) -v -tags='netgo sqlite' build-no-sqlite: deps-no-sqlite - cd cmd/writefreely; $(GOBUILD) -v -o $(BINARY_NAME) + cd cmd/writefreely; $(GOBUILD) -v -tags='netgo' -o $(BINARY_NAME) build-linux: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . + xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-windows: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . + xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-darwin: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . + xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-arm6: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . + xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-arm7: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . + xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-arm64: deps @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \ fi - xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . + xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely . build-docker : $(DOCKERCMD) build -t $(IMAGE_NAME):latest -t $(IMAGE_NAME):$(GITREV) . @@ -66,7 +66,7 @@ test: $(GOTEST) -v ./... run: - $(GOINSTALL) -tags='sqlite' ./... + $(GOINSTALL) -tags='netgo sqlite' ./... $(BINARY_NAME) --debug deps : From e1e05e5f2967eb076241278977d0065654450da1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Apr 2023 14:20:58 +0000 Subject: [PATCH 045/115] Bump github.com/go-sql-driver/mysql from 1.7.0 to 1.7.1 Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.7.0 to 1.7.1. - [Release notes](https://github.com/go-sql-driver/mysql/releases) - [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md) - [Commits](https://github.com/go-sql-driver/mysql/compare/v1.7.0...v1.7.1) --- updated-dependencies: - dependency-name: github.com/go-sql-driver/mysql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7a161b1..cf24463 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.15.0 github.com/go-ini/ini v1.67.0 - github.com/go-sql-driver/mysql v1.7.0 + github.com/go-sql-driver/mysql v1.7.1 github.com/gorilla/csrf v1.7.1 github.com/gorilla/feeds v1.1.1 github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index c13c870..e08a857 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4L github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= From b6d17a9594bf8947280c85fa718fc034eb2a7a89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 22:58:34 +0000 Subject: [PATCH 046/115] Bump github.com/mattn/go-sqlite3 from 1.14.16 to 1.14.17 Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.16 to 1.14.17. - [Release notes](https://github.com/mattn/go-sqlite3/releases) - [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.16...v1.14.17) --- updated-dependencies: - dependency-name: github.com/mattn/go-sqlite3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7a161b1..617ab95 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec github.com/manifoldco/promptui v0.9.0 - github.com/mattn/go-sqlite3 v1.14.16 + github.com/mattn/go-sqlite3 v1.14.17 github.com/microcosm-cc/bluemonday v1.0.23 github.com/mitchellh/go-wordwrap v1.0.1 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d diff --git a/go.sum b/go.sum index c13c870..b48162c 100644 --- a/go.sum +++ b/go.sum @@ -84,8 +84,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/microcosm-cc/bluemonday v1.0.5/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY= github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= From 77823a382b07c93b2953e49e03967ffc5b8ae066 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 22:58:42 +0000 Subject: [PATCH 047/115] Bump github.com/stretchr/testify from 1.8.1 to 1.8.4 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.4. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.4) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 7a161b1..43fd222 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/microcosm-cc/bluemonday v1.0.23 github.com/mitchellh/go-wordwrap v1.0.1 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.4 github.com/urfave/cli/v2 v2.23.5 github.com/writeas/activity v0.1.2 github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481 diff --git a/go.sum b/go.sum index c13c870..080fcc9 100644 --- a/go.sum +++ b/go.sum @@ -112,13 +112,9 @@ github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw= github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY= From e2237653bbc6d34996686f6632100e625efd9242 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 23:00:05 +0000 Subject: [PATCH 048/115] Bump docker/login-action from 2.1.0 to 2.2.0 Bumps [docker/login-action](https://github.com/docker/login-action) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2.1.0...v2.2.0) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d3bc432..d85ac7c 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -34,7 +34,7 @@ jobs: # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@v2.1.0 + uses: docker/login-action@v2.2.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} From b0b166e827cda5c8200dc34f55cb5ff24cec628f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 23:00:19 +0000 Subject: [PATCH 049/115] Bump docker/metadata-action from 4.1.1 to 4.6.0 Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.1.1 to 4.6.0. - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/v4.1.1...v4.6.0) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d3bc432..0c3fae0 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -44,7 +44,7 @@ jobs: # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v4.1.1 + uses: docker/metadata-action@v4.6.0 with: images: | ghcr.io/${{ github.repository }} From 639770be4d4bcd1b3a62f2a44e74f4c00e51d9a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 23:00:23 +0000 Subject: [PATCH 050/115] Bump docker/build-push-action from 3.2.0 to 4.1.1 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.2.0 to 4.1.1. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v3.2.0...v4.1.1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d3bc432..b5ecaf1 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -53,7 +53,7 @@ jobs: # Build and push Docker image with Buildx (don't push on PR) # https://github.com/docker/build-push-action - name: Build and push Docker images - uses: docker/build-push-action@v3.2.0 + uses: docker/build-push-action@v4.1.1 with: context: . push: ${{ github.event_name != 'pull_request' }} From d3f935f693e3d490114d40338abb1c06b1f94a6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Jun 2023 21:57:43 +0000 Subject: [PATCH 051/115] Bump golang.org/x/net from 0.9.0 to 0.11.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.11.0. - [Commits](https://github.com/golang/net/compare/v0.9.0...v0.11.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 43fd222..2167853 100644 --- a/go.mod +++ b/go.mod @@ -34,8 +34,8 @@ require ( github.com/writeas/web-core v1.4.1 github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b github.com/writefreely/go-nodeinfo v1.2.0 - golang.org/x/crypto v0.8.0 - golang.org/x/net v0.9.0 + golang.org/x/crypto v0.10.0 + golang.org/x/net v0.11.0 ) require ( @@ -73,8 +73,8 @@ require ( github.com/writeas/go-writeas/v2 v2.0.2 // indirect github.com/writeas/openssl-go v1.0.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/sys v0.7.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 080fcc9..ea992ec 100644 --- a/go.sum +++ b/go.sum @@ -157,22 +157,22 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 98790ee37122371cc451c2fdb4d08e439c66ab6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Jun 2023 21:57:55 +0000 Subject: [PATCH 052/115] Bump github.com/urfave/cli/v2 from 2.23.5 to 2.25.7 Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.23.5 to 2.25.7. - [Release notes](https://github.com/urfave/cli/releases) - [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md) - [Commits](https://github.com/urfave/cli/compare/v2.23.5...v2.25.7) --- updated-dependencies: - dependency-name: github.com/urfave/cli/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 43fd222..f14a9ae 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d github.com/stretchr/testify v1.8.4 - github.com/urfave/cli/v2 v2.23.5 + github.com/urfave/cli/v2 v2.25.7 github.com/writeas/activity v0.1.2 github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481 github.com/writeas/go-strip-markdown/v2 v2.1.1 diff --git a/go.sum b/go.sum index 080fcc9..93ee532 100644 --- a/go.sum +++ b/go.sum @@ -115,8 +115,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw= -github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY= github.com/writeas/activity v0.1.2/go.mod h1:mYYgiewmEM+8tlifirK/vl6tmB2EbjYaxwb+ndUw5T0= github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481 h1:BiSivIxLQFcKoUorpNN3rNwwFG5bITPnqUSyIccfdh0= From ba8aebaa6fd70e346584bc5045187b0d19864a53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Jun 2023 22:05:07 +0000 Subject: [PATCH 053/115] Bump github.com/microcosm-cc/bluemonday from 1.0.23 to 1.0.24 Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.23 to 1.0.24. - [Release notes](https://github.com/microcosm-cc/bluemonday/releases) - [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.23...v1.0.24) --- updated-dependencies: - dependency-name: github.com/microcosm-cc/bluemonday dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 41657af..8e7f154 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec github.com/manifoldco/promptui v0.9.0 github.com/mattn/go-sqlite3 v1.14.17 - github.com/microcosm-cc/bluemonday v1.0.23 + github.com/microcosm-cc/bluemonday v1.0.24 github.com/mitchellh/go-wordwrap v1.0.1 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index 2ab61b1..894b946 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/microcosm-cc/bluemonday v1.0.5/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= -github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY= -github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= +github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw= +github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= From 54eb2db14d591f69f113a64417e0bede104cb6a7 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 8 Jul 2023 00:31:02 -0400 Subject: [PATCH 054/115] Fix tagged posts falsely showing Older link --- collections.go | 9 ++++++- database.go | 46 ++++++++++++++++++++++++++++++++++ templates/collection-tags.tmpl | 4 +-- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/collections.go b/collections.go index 9e9c34c..b78bb52 100644 --- a/collections.go +++ b/collections.go @@ -975,7 +975,14 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e coll := newDisplayCollection(c, cr, page) - coll.TotalPages = int(math.Ceil(float64(coll.TotalPosts) / float64(coll.Format.PostsPerPage()))) + taggedPostIDs, err := app.db.GetAllPostsTaggedIDs(c, tag, cr.isCollOwner) + if err != nil { + return err + } + + ttlPosts := len(taggedPostIDs) + pagePosts := coll.Format.PostsPerPage() + coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts))) if coll.TotalPages > 0 && page > coll.TotalPages { redirURL := fmt.Sprintf("/page/%d", coll.TotalPages) if !app.cfg.App.SingleUser { diff --git a/database.go b/database.go index 8bd5a43..f2a87dc 100644 --- a/database.go +++ b/database.go @@ -113,6 +113,7 @@ type writestore interface { GetPostsCount(c *CollectionObj, includeFuture bool) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error) + GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error) GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error) GetAPFollowers(c *Collection) (*[]RemoteUser, error) @@ -1195,6 +1196,51 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu return &posts, nil } +func (db *datastore) GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error) { + collID := c.ID + + cf := c.NewFormat() + order := "DESC" + if cf.Ascending() { + order = "ASC" + } + + timeCondition := "" + if !includeFuture { + timeCondition = "AND created <= NOW()" + } + var rows *sql.Rows + var err error + if db.driverName == driverSQLite { + rows, err = db.Query("SELECT id FROM posts WHERE collection_id = ? AND LOWER(content) regexp ? "+timeCondition+" ORDER BY created "+order, collID, `.*#`+strings.ToLower(tag)+`\b.*`) + } else { + rows, err = db.Query("SELECT id FROM posts WHERE collection_id = ? AND LOWER(content) RLIKE ? "+timeCondition+" ORDER BY created "+order, collID, "#"+strings.ToLower(tag)+"[[:>:]]") + } + if err != nil { + log.Error("Failed selecting tagged posts: %v", err) + return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve tagged collection posts."} + } + defer rows.Close() + + ids := []string{} + for rows.Next() { + var id string + err = rows.Scan(&id) + if err != nil { + log.Error("Failed scanning row: %v", err) + break + } + + ids = append(ids, id) + } + err = rows.Err() + if err != nil { + log.Error("Error after Next() on rows: %v", err) + } + + return ids, nil +} + // GetPostsTagged retrieves all posts on the given Collection that contain the // given tag. // It will return future posts if `includeFuture` is true. diff --git a/templates/collection-tags.tmpl b/templates/collection-tags.tmpl index c164978..c8e8a12 100644 --- a/templates/collection-tags.tmpl +++ b/templates/collection-tags.tmpl @@ -63,8 +63,8 @@ {{template "posts" .}} {{if gt .TotalPages 1}}

+
+

Verification

+
+

Verify that you own another site on the open web, fediverse, etc. For example, enter your Mastodon profile address here, then on Mastodon add a link back to this blog — it will show up as verified there.

+ +

This adds a rel="me" code in your blog's <head>.

+
+
+ {{if .UserPage.StaticPage.AppCfg.Monetization}}

Web Monetization

From 8626aa12cce05ee7fdfc55cc0f865179706a872e Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 22 Sep 2023 11:52:14 -0400 Subject: [PATCH 080/115] Fix post page rendering after rel=me changes Ref T744 --- posts.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/posts.go b/posts.go index 39c12b1..da59df9 100644 --- a/posts.go +++ b/posts.go @@ -139,6 +139,7 @@ type ( IsPinned bool IsCustomDomain bool Monetization string + Verification string PinnedPosts *[]PublicPost IsFound bool IsAdmin bool @@ -1547,6 +1548,7 @@ Are you sure it was ever here?`, tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll, p.IsOwner) tp.IsPinned = len(*tp.PinnedPosts) > 0 && PostsContains(tp.PinnedPosts, p) tp.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer") + tp.Verification = coll.Verification if !postFound { w.WriteHeader(http.StatusNotFound) From aa72bcba500218a51b4bec13e749589d86432578 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 22 Sep 2023 11:53:57 -0400 Subject: [PATCH 081/115] Fix funky comment after gofmt in posts.go --- posts.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/posts.go b/posts.go index da59df9..4bc5633 100644 --- a/posts.go +++ b/posts.go @@ -517,9 +517,9 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error { // newPost creates a new post with or without an owning Collection. // // Endpoints: -// /posts -// /posts?collection={alias} -// ? /collections/{alias}/posts +// - /posts +// - /posts?collection={alias} +// - ? /collections/{alias}/posts func newPost(app *App, w http.ResponseWriter, r *http.Request) error { reqJSON := IsJSON(r) vars := mux.Vars(r) From efe669b874f9d511912566fcfb97e52026a2085b Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 22 Sep 2023 11:58:11 -0400 Subject: [PATCH 082/115] Remove redundant query on post pages Previously, we'd call GetCollectionAttribute for the monetization attribute, when it's already in the collection data. --- posts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts.go b/posts.go index 4bc5633..dfa6e51 100644 --- a/posts.go +++ b/posts.go @@ -1547,7 +1547,7 @@ Are you sure it was ever here?`, tp.CanInvite = canUserInvite(app.cfg, tp.IsAdmin) tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll, p.IsOwner) tp.IsPinned = len(*tp.PinnedPosts) > 0 && PostsContains(tp.PinnedPosts, p) - tp.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer") + tp.Monetization = coll.Monetization tp.Verification = coll.Verification if !postFound { From 83f230ddaff5f2c58caf4943909320d04cb7cdf2 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 22 Sep 2023 12:25:19 -0400 Subject: [PATCH 083/115] Instruct users to contact admin, not WF devevelopers on 500 page Misconfigured or broken servers has directed people to the wrong place. Fixes #684 --- pages/500.tmpl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pages/500.tmpl b/pages/500.tmpl index e148fb5..37b031d 100644 --- a/pages/500.tmpl +++ b/pages/500.tmpl @@ -2,9 +2,7 @@ {{define "content"}}

Server error 😵

-

Please contact the human authors of this software and remind them of their many shortcomings.

-

Be gentle, though. They are fragile mortal beings.

-

Also, unlike the AI that will soon replace them, you will need to include an error log from the server in your report. (Utterly primitive, we know.)

+

There seems to be an issue with this server. Please contact the admin and let them know they'll need to fix it.

– {{.SiteName}} 🤖

{{end}} From d48262a6df327d363a48cbe1897c1c6555b59367 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 22 Sep 2023 12:37:15 -0400 Subject: [PATCH 084/115] Add a customizable Contact page --- admin.go | 28 +++++++++++++++++++----- app.go | 8 ++++++- pages.go | 34 +++++++++++++++++++++++++++++ pages/500.tmpl | 2 +- pages/contact.tmpl | 8 +++++++ templates/user/admin/view-page.tmpl | 2 ++ 6 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 pages/contact.tmpl diff --git a/admin.go b/admin.go index 6408cfe..9a3c824 100644 --- a/admin.go +++ b/admin.go @@ -13,6 +13,7 @@ package writefreely import ( "database/sql" "fmt" + "html/template" "net/http" "runtime" "strconv" @@ -102,13 +103,16 @@ func NewAdminPage(app *App) *AdminPage { return ap } -func (c instanceContent) UpdatedFriendly() string { +func (c instanceContent) UpdatedFriendly() template.HTML { /* // TODO: accept a locale in this method and use that for the format var loc monday.Locale = monday.LocaleEnUS return monday.Format(u.Created, monday.DateTimeFormatsByLocale[loc], loc) */ - return c.Updated.Format("January 2, 2006, 3:04 PM") + if c.Updated.IsZero() { + return "Never" + } + return template.HTML(c.Updated.Format("January 2, 2006, 3:04 PM")) } func handleViewAdminDash(app *App, u *User, w http.ResponseWriter, r *http.Request) error { @@ -426,9 +430,9 @@ func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Requ } // Add in default pages - var hasAbout, hasPrivacy bool + var hasAbout, hasContact, hasPrivacy bool for i, c := range p.Pages { - if hasAbout && hasPrivacy { + if hasAbout && hasContact && hasPrivacy { break } if c.ID == "about" { @@ -436,6 +440,11 @@ func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Requ if !c.Title.Valid { p.Pages[i].Title = defaultAboutTitle(app.cfg) } + } else if c.ID == "contact" { + hasContact = true + if !c.Title.Valid { + p.Pages[i].Title = defaultContactTitle() + } } else if c.ID == "privacy" { hasPrivacy = true if !c.Title.Valid { @@ -451,6 +460,13 @@ func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Requ Updated: defaultPageUpdatedTime, }) } + if !hasAbout { + p.Pages = append(p.Pages, &instanceContent{ + ID: "contact", + Title: defaultContactTitle(), + Content: defaultContactPage(app), + }) + } if !hasPrivacy { p.Pages = append(p.Pages, &instanceContent{ ID: "privacy", @@ -489,6 +505,8 @@ func handleViewAdminPage(app *App, u *User, w http.ResponseWriter, r *http.Reque // Get pre-defined pages, or select slug if slug == "about" { p.Content, err = getAboutPage(app) + } else if slug == "contact" { + p.Content, err = getContactPage(app) } else if slug == "privacy" { p.Content, err = getPrivacyPage(app) } else if slug == "landing" { @@ -523,7 +541,7 @@ func handleAdminUpdateSite(app *App, u *User, w http.ResponseWriter, r *http.Req id := vars["page"] // Validate - if id != "about" && id != "privacy" && id != "landing" && id != "reader" { + if id != "about" && id != "contact" && id != "privacy" && id != "landing" && id != "reader" { return impart.HTTPError{http.StatusNotFound, "No such page."} } diff --git a/app.go b/app.go index bf15b9e..efdd9df 100644 --- a/app.go +++ b/app.go @@ -318,7 +318,7 @@ func handleTemplatedPage(app *App, w http.ResponseWriter, r *http.Request, t *te }{ StaticPage: pageForReq(app, r), } - if r.URL.Path == "/about" || r.URL.Path == "/privacy" { + if r.URL.Path == "/about" || r.URL.Path == "/contact" || r.URL.Path == "/privacy" { var c *instanceContent var err error @@ -329,6 +329,12 @@ func handleTemplatedPage(app *App, w http.ResponseWriter, r *http.Request, t *te p.AboutStats = &InstanceStats{} p.AboutStats.NumPosts, _ = app.db.GetTotalPosts() p.AboutStats.NumBlogs, _ = app.db.GetTotalCollections() + } else if r.URL.Path == "/contact" { + c, err = getContactPage(app) + if c.Updated.IsZero() { + // Page was never set up, so return 404 + return ErrPostNotFound + } } else { c, err = getPrivacyPage(app) } diff --git a/pages.go b/pages.go index 8b3a987..bf85526 100644 --- a/pages.go +++ b/pages.go @@ -40,6 +40,28 @@ func defaultAboutTitle(cfg *config.Config) sql.NullString { return sql.NullString{String: "About " + cfg.App.SiteName, Valid: true} } +func getContactPage(app *App) (*instanceContent, error) { + c, err := app.db.GetDynamicContent("contact") + if err != nil { + return nil, err + } + if c == nil { + c = &instanceContent{ + ID: "contact", + Type: "page", + Content: defaultContactPage(app), + } + } + if !c.Title.Valid { + c.Title = defaultContactTitle() + } + return c, nil +} + +func defaultContactTitle() sql.NullString { + return sql.NullString{String: "Contact Us", Valid: true} +} + func getPrivacyPage(app *App) (*instanceContent, error) { c, err := app.db.GetDynamicContent("privacy") if err != nil { @@ -70,6 +92,18 @@ func defaultAboutPage(cfg *config.Config) string { return `_` + cfg.App.SiteName + `_ is a place for you to write and publish, powered by [WriteFreely](https://writefreely.org).` } +func defaultContactPage(app *App) string { + c, err := app.db.GetCollectionByID(1) + if err != nil { + return "" + } + return `_` + app.cfg.App.SiteName + `_ is administered by: [**` + c.Alias + `**](/` + c.Alias + `/). + +Contact them at this email address: _EMAIL GOES HERE_. + +You can also reach them here...` +} + func defaultPrivacyPolicy(cfg *config.Config) string { return `[WriteFreely](https://writefreely.org), the software that powers this site, is built to enforce your right to privacy by default. diff --git a/pages/500.tmpl b/pages/500.tmpl index 37b031d..4240348 100644 --- a/pages/500.tmpl +++ b/pages/500.tmpl @@ -2,7 +2,7 @@ {{define "content"}}

Server error 😵

-

There seems to be an issue with this server. Please contact the admin and let them know they'll need to fix it.

+

There seems to be an issue with this server. Please contact the admin and let them know they'll need to fix it.

– {{.SiteName}} 🤖

{{end}} diff --git a/pages/contact.tmpl b/pages/contact.tmpl new file mode 100644 index 0000000..8dff9ae --- /dev/null +++ b/pages/contact.tmpl @@ -0,0 +1,8 @@ +{{define "head"}}{{.ContentTitle}} — {{.SiteName}} + +{{end}} +{{define "content"}}
+

{{.ContentTitle}}

+ {{.Content}} +
+{{end}} diff --git a/templates/user/admin/view-page.tmpl b/templates/user/admin/view-page.tmpl index 161e40b..dfcf4cd 100644 --- a/templates/user/admin/view-page.tmpl +++ b/templates/user/admin/view-page.tmpl @@ -29,6 +29,8 @@ input[type=text] { {{if eq .Content.ID "about"}}

Describe what your instance is about.

+ {{else if eq .Content.ID "contact"}} +

Tell your users and outside visitors how to contact you.

{{else if eq .Content.ID "privacy"}}

Outline your privacy policy.

{{else if eq .Content.ID "reader"}} From c9dc8d5a908e060c455c360cd1285e745a2cefcf Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 22 Sep 2023 12:46:21 -0400 Subject: [PATCH 085/115] Fix bad copy pasta --- admin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin.go b/admin.go index 9a3c824..258d0b8 100644 --- a/admin.go +++ b/admin.go @@ -460,7 +460,7 @@ func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Requ Updated: defaultPageUpdatedTime, }) } - if !hasAbout { + if !hasContact { p.Pages = append(p.Pages, &instanceContent{ ID: "contact", Title: defaultContactTitle(), From a8afa18ab21ad056fee0c046562600f1f6e9ee8d Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 22 Sep 2023 13:12:47 -0400 Subject: [PATCH 086/115] Bump version to 0.14 --- app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.go b/app.go index 00dbcfe..eb07073 100644 --- a/app.go +++ b/app.go @@ -58,7 +58,7 @@ var ( debugging bool // Software version can be set from git env using -ldflags - softwareVer = "0.13.2" + softwareVer = "0.14.0" // DEPRECATED VARS isSingleUser bool From 62f9b2948ecaf6e8b4362570bf0e55111a24a183 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 22 Sep 2023 17:10:42 -0400 Subject: [PATCH 087/115] Exclude local static files from release build --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index c599a58..b230cb7 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,7 @@ release : clean ui cp -r templates $(BUILDPATH) cp -r pages $(BUILDPATH) cp -r static $(BUILDPATH) + rm -r $(BUILDPATH)/static/local scripts/invalidate-css.sh $(BUILDPATH) mkdir $(BUILDPATH)/keys $(MAKE) build-linux From cc9705447dbc3a53f9a993614fab8fdd86661426 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 25 Sep 2023 14:00:18 -0400 Subject: [PATCH 088/115] Re-add letters migration --- migrations/v13.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/migrations/v13.go b/migrations/v13.go index 40e8fa8..cb509f2 100644 --- a/migrations/v13.go +++ b/migrations/v13.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2023 A Bunch Tell LLC. + * Copyright © 2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -9,3 +9,52 @@ */ package migrations + +func supportLetters(db *datastore) error { + t, err := db.Begin() + if err != nil { + t.Rollback() + return err + } + + _, err = t.Exec(`CREATE TABLE publishjobs ( + id ` + db.typeInt() + ` auto_increment, + post_id ` + db.typeVarChar(16) + ` not null, + action ` + db.typeVarChar(16) + ` not null, + delay ` + db.typeTinyInt() + ` not null, + PRIMARY KEY (id) +)`) + if err != nil { + t.Rollback() + return err + } + + // TODO: fix for SQLite database + _, err = t.Exec(`CREATE TABLE emailsubscribers ( + id char(8) not null, + collection_id int not null, + user_id int null, + email varchar(255) null, + subscribed datetime not null, + token char(16) not null, + confirmed tinyint(1) default 0 not null, + allow_export tinyint(1) default 0 not null, + constraint eu_coll_email + unique (collection_id, email), + constraint eu_coll_user + unique (collection_id, user_id), + PRIMARY KEY (id) +)`) + if err != nil { + t.Rollback() + return err + } + + err = t.Commit() + if err != nil { + t.Rollback() + return err + } + + return nil +} From 221d0d7dbb8f7b09a1feeb1f0ee16e133feadd04 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 25 Sep 2023 14:25:24 -0400 Subject: [PATCH 089/115] Make letters (v13) migration compatible with SQLite --- migrations/v13.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/migrations/v13.go b/migrations/v13.go index cb509f2..b6c6c3a 100644 --- a/migrations/v13.go +++ b/migrations/v13.go @@ -29,16 +29,15 @@ func supportLetters(db *datastore) error { return err } - // TODO: fix for SQLite database _, err = t.Exec(`CREATE TABLE emailsubscribers ( - id char(8) not null, - collection_id int not null, - user_id int null, - email varchar(255) null, - subscribed datetime not null, - token char(16) not null, - confirmed tinyint(1) default 0 not null, - allow_export tinyint(1) default 0 not null, + id ` + db.typeChar(8) + ` not null, + collection_id ` + db.typeInt() + ` not null, + user_id ` + db.typeInt() + ` null, + email ` + db.typeVarChar(255) + ` null, + subscribed ` + db.typeDateTime() + ` not null, + token ` + db.typeChar(16) + ` not null, + confirmed ` + db.typeBool() + ` default 0 not null, + allow_export ` + db.typeBool() + ` default 0 not null, constraint eu_coll_email unique (collection_id, email), constraint eu_coll_user From cc75be1eb5699bd1bd6b7cee6ec4cfaa7f906879 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 25 Sep 2023 14:26:41 -0400 Subject: [PATCH 090/115] Rename Letters [letters] config section to Email [email] --- account.go | 6 +++--- app.go | 6 +++--- config/config.go | 6 +++--- email.go | 8 ++++---- posts.go | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/account.go b/account.go index 97d3a62..e902b5b 100644 --- a/account.go +++ b/account.go @@ -876,16 +876,16 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques *Collection Silenced bool - config.LettersCfg + config.EmailCfg LetterReplyTo string }{ UserPage: NewUserPage(app, r, u, "Edit "+c.DisplayTitle(), flashes), Collection: c, Silenced: silenced, - LettersCfg: app.cfg.Letters, + EmailCfg: app.cfg.Email, } obj.UserPage.CollAlias = c.Alias - if obj.LettersCfg.Enabled() { + if obj.EmailCfg.Enabled() { obj.LetterReplyTo = app.db.GetCollectionAttribute(c.ID, collAttrLetterReplyTo) } diff --git a/app.go b/app.go index 54a6ccc..42d9891 100644 --- a/app.go +++ b/app.go @@ -428,10 +428,10 @@ func Initialize(apper Apper, debug bool) (*App, error) { initActivityPub(apper.App()) - if apper.App().cfg.Letters.Domain != "" || apper.App().cfg.Letters.MailgunPrivate != "" { - if apper.App().cfg.Letters.Domain == "" { + if apper.App().cfg.Email.Domain != "" || apper.App().cfg.Email.MailgunPrivate != "" { + if apper.App().cfg.Email.Domain == "" { log.Error("[FAILED] Starting publish jobs queue: no [letters]domain config value set.") - } else if apper.App().cfg.Letters.MailgunPrivate == "" { + } else if apper.App().cfg.Email.MailgunPrivate == "" { log.Error("[FAILED] Starting publish jobs queue: no [letters]mailgun_private config value set.") } else { log.Info("Starting publish jobs queue...") diff --git a/config/config.go b/config/config.go index 36bbc82..1afd5f3 100644 --- a/config/config.go +++ b/config/config.go @@ -170,7 +170,7 @@ type ( DisablePasswordAuth bool `ini:"disable_password_auth"` } - LettersCfg struct { + EmailCfg struct { Domain string `ini:"domain"` MailgunPrivate string `ini:"mailgun_private"` } @@ -180,7 +180,7 @@ type ( Server ServerCfg `ini:"server"` Database DatabaseCfg `ini:"database"` App AppCfg `ini:"app"` - Letters LettersCfg `ini:"letters"` + Email EmailCfg `ini:"email"` SlackOauth SlackOauthCfg `ini:"oauth.slack"` WriteAsOauth WriteAsOauthCfg `ini:"oauth.writeas"` GitlabOauth GitlabOauthCfg `ini:"oauth.gitlab"` @@ -241,7 +241,7 @@ func (ac *AppCfg) LandingPath() string { return ac.Landing } -func (lc LettersCfg) Enabled() bool { +func (lc EmailCfg) Enabled() bool { return lc.Domain != "" && lc.MailgunPrivate != "" } diff --git a/email.go b/email.go index d97ab15..551dd97 100644 --- a/email.go +++ b/email.go @@ -313,8 +313,8 @@ Originally published on ` + p.Collection.DisplayTitle() + ` (` + p.Collection.Ca Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%` - gun := mailgun.NewMailgun(app.cfg.Letters.Domain, app.cfg.Letters.MailgunPrivate) - m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Letters.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg) + gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate) + m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Email.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg) replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo) if replyTo != "" { m.SetReplyTo(replyTo) @@ -443,14 +443,14 @@ func sendSubConfirmEmail(app *App, c *Collection, email, subID, token string) er } // Send email - gun := mailgun.NewMailgun(app.cfg.Letters.Domain, app.cfg.Letters.MailgunPrivate) + gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate) plainMsg := "Confirm your subscription to " + c.DisplayTitle() + ` (` + c.CanonicalURL() + `) to start receiving future posts. Simply click the following link (or copy and paste it into your browser): ` + c.CanonicalURL() + "email/confirm/" + subID + "?t=" + token + ` If you didn't subscribe to this site or you're not sure why you're getting this email, you can delete it. You won't be subscribed or receive any future emails.` - m := mailgun.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Letters.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email)) + m := mailgun.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Email.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email)) m.AddTag("Email Verification") m.SetHtml(` diff --git a/posts.go b/posts.go index 6b94cf8..fc8eddc 100644 --- a/posts.go +++ b/posts.go @@ -657,7 +657,7 @@ func newPost(app *App, w http.ResponseWriter, r *http.Request) error { if !app.cfg.App.Private && app.cfg.App.Federation && !newPost.Created.After(time.Now()) { go federatePost(app, newPost, newPost.Collection.ID, false) } - if app.cfg.Letters.Enabled() && newPost.Collection.EmailSubsEnabled() { + if app.cfg.Email.Enabled() && newPost.Collection.EmailSubsEnabled() { go app.db.InsertJob(&PostJob{ PostID: newPost.ID, Action: "email", @@ -973,7 +973,7 @@ func addPost(app *App, w http.ResponseWriter, r *http.Request) error { go federatePost(app, pRes.Post, pRes.Post.Collection.ID, false) } } - if app.cfg.Letters.Enabled() && pRes.Post.Collection.EmailSubsEnabled() { + if app.cfg.Email.Enabled() && pRes.Post.Collection.EmailSubsEnabled() { go app.db.InsertJob(&PostJob{ PostID: pRes.Post.ID, Action: "email", @@ -1558,7 +1558,7 @@ Are you sure it was ever here?`, } else { p.extractData() p.Content = strings.Replace(p.Content, "", "", 1) - if app.cfg.Letters.Enabled() && c.EmailSubsEnabled() { + if app.cfg.Email.Enabled() && c.EmailSubsEnabled() { // TODO: indicate plan is inactive or subs disabled when OWNER is viewing their own post. if u != nil && u.IsEmailSubscriber(app, c.ID) { p.Content = strings.Replace(p.Content, "", `

You're subscribed to email updates. Unsubscribe.

`, -1) From e2b2ba45774aec66e9b927488adee693b6f6ba33 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 25 Sep 2023 14:28:37 -0400 Subject: [PATCH 091/115] Rename Letters config to Email in collection.tmpl --- templates/user/collection.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/user/collection.tmpl b/templates/user/collection.tmpl index f057bdd..c30b479 100644 --- a/templates/user/collection.tmpl +++ b/templates/user/collection.tmpl @@ -101,7 +101,7 @@ textarea.section.norm {

Readers can subscribe to your blog's RSS feed with their favorite RSS reader.

- {{if .LettersCfg.Enabled}} + {{if .EmailCfg.Enabled}}
  • New Post
  • {{end}}
  • Customize
  • Stats
  • +
  • Subscribers

  • {{if not .SingleUser}}
  • View Blogs
  • {{end}}
  • View Drafts
  • diff --git a/templates/user/include/header.tmpl b/templates/user/include/header.tmpl index 66a2a84..9b6912b 100644 --- a/templates/user/include/header.tmpl +++ b/templates/user/include/header.tmpl @@ -18,6 +18,7 @@ From e2fde518ca986f21f98352359436d5243faf3ca1 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 25 Sep 2023 18:18:01 -0400 Subject: [PATCH 102/115] Fix GetTemporaryOneTimeAccessToken query for SQLite --- database.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/database.go b/database.go index f1e9cfa..85cce90 100644 --- a/database.go +++ b/database.go @@ -174,6 +174,13 @@ func (db *datastore) upsert(indexedCols ...string) string { return "ON DUPLICATE KEY UPDATE" } +func (db *datastore) dateAdd(l int, unit string) string { + if db.driverName == driverSQLite { + return fmt.Sprintf("DATETIME('now', '%d %s')", l, unit) + } + return fmt.Sprintf("DATE_ADD(NOW(), INTERVAL %d %s)", l, unit) +} + func (db *datastore) dateSub(l int, unit string) string { if db.driverName == driverSQLite { return fmt.Sprintf("DATETIME('now', '-%d %s')", l, unit) @@ -567,7 +574,7 @@ func (db *datastore) GetTemporaryOneTimeAccessToken(userID int64, validSecs int, expirationVal := "NULL" if validSecs > 0 { - expirationVal = fmt.Sprintf("DATE_ADD("+db.now()+", INTERVAL %d SECOND)", validSecs) + expirationVal = db.dateAdd(validSecs, "SECOND") } _, err = db.Exec("INSERT INTO accesstokens (token, user_id, one_time, expires) VALUES (?, ?, ?, "+expirationVal+")", string(binTok), userID, oneTime) From 7dda53146dd82ad59098b2179f71331c7f3adb04 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 25 Sep 2023 18:21:20 -0400 Subject: [PATCH 103/115] Add function for logging in via emailed link This doesn't add any user-facing behavior, but provides the basic functionality to generate a one-time use token and email it to a user, so they can log in with a link instead of a password. --- account.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/account.go b/account.go index ecd02e3..ecc39b4 100644 --- a/account.go +++ b/account.go @@ -13,6 +13,7 @@ package writefreely import ( "encoding/json" "fmt" + "github.com/mailgun/mailgun-go" "html/template" "net/http" "regexp" @@ -1237,6 +1238,54 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err return nil } +func loginViaEmail(app *App, alias, redirectTo string) error { + if !app.cfg.Email.Enabled() { + return fmt.Errorf("EMAIL ISN'T CONFIGURED on this server") + } + + // Make sure user has added an email + // TODO: create a new func to just get user's email; "ForAuth" doesn't match here + u, _ := app.db.GetUserForAuth(alias) + if u == nil { + if strings.IndexAny(alias, "@") > 0 { + return ErrUserNotFoundEmail + } + return ErrUserNotFound + } + if u.Email.String == "" { + return impart.HTTPError{http.StatusPreconditionFailed, "User doesn't have an email address. Log in with password, instead."} + } + + // Generate one-time login token + t, err := app.db.GetTemporaryOneTimeAccessToken(u.ID, 60*15, true) + if err != nil { + log.Error("Unable to generate token for email login: %s", err) + return impart.HTTPError{http.StatusInternalServerError, "Unable to generate token."} + } + + // Send email + gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate) + toEmail := u.EmailClear(app.keys) + footerPara := "This link will only work once and expires in 15 minutes. Didn't ask us to log in? You can safely ignore this email." + + plainMsg := fmt.Sprintf("Log in to %s here: %s/login?to=%s&with=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, footerPara) + m := mailgun.NewMessage(app.cfg.App.SiteName+" ", "Log in to "+app.cfg.App.SiteName, plainMsg, fmt.Sprintf("<%s>", toEmail)) + m.AddTag("Email Login") + + m.SetHtml(fmt.Sprintf(` + +
    +

    %s

    +

    Log in to %s here.

    +

    %s

    +
    + +`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, app.cfg.App.SiteName, footerPara)) + _, _, err = gun.Send(m) + + return err +} + func saveTempInfo(app *App, key, val string, r *http.Request, w http.ResponseWriter) error { session, err := app.sessionStore.Get(r, "t") if err != nil { From f404f7b92881157d223d5fb843fb97aa0835d4cb Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 25 Sep 2023 18:48:14 -0400 Subject: [PATCH 104/115] Support resetting password via email This adds a self-serve password reset page. Users can enter their username and receive an email with a link that will let them create a new password. If they've never set a password, it will send them a one-time login link (building on #776) that will then take them to their Account Settings page. If they don't have an email associated with their account, they'll be instructed to contact the admin, so they can manually reset the password. Includes changes to the stylesheet and database, so run: make ui writefreely db migrate Closes T508 --- account.go | 146 +++++++++++++++++++++++++++++++++++++++ database.go | 31 +++++++++ less/core.less | 3 + migrations/drivers.go | 7 ++ migrations/migrations.go | 1 + migrations/v14.go | 37 ++++++++++ pages/login.tmpl | 4 ++ pages/reset.tmpl | 48 +++++++++++++ routes.go | 1 + spam/ip.go | 25 +++++++ 10 files changed, 303 insertions(+) create mode 100644 migrations/v14.go create mode 100644 pages/reset.tmpl create mode 100644 spam/ip.go diff --git a/account.go b/account.go index ecc39b4..e88c1fd 100644 --- a/account.go +++ b/account.go @@ -14,6 +14,7 @@ import ( "encoding/json" "fmt" "github.com/mailgun/mailgun-go" + "github.com/writefreely/writefreely/spam" "html/template" "net/http" "regexp" @@ -1238,6 +1239,151 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err return nil } +func viewResetPassword(app *App, w http.ResponseWriter, r *http.Request) error { + token := r.FormValue("t") + resetting := false + var userID int64 = 0 + if token != "" { + // Show new password page + userID = app.db.GetUserFromPasswordReset(token) + if userID == 0 { + return impart.HTTPError{http.StatusNotFound, ""} + } + resetting = true + } + + if r.Method == http.MethodPost { + newPass := r.FormValue("new-pass") + if newPass == "" { + // Send password reset email + return handleResetPasswordInit(app, w, r) + } + + // Do actual password reset + // Assumes token has been validated above + err := doAutomatedPasswordChange(app, userID, newPass) + if err != nil { + return err + } + err = app.db.ConsumePasswordResetToken(token) + if err != nil { + log.Error("Couldn't consume token %s for user %d!!! %s", token, userID, err) + } + addSessionFlash(app, w, r, "Your password was reset. Now you can log in below.", nil) + return impart.HTTPError{http.StatusFound, "/login"} + } + + f, _ := getSessionFlashes(app, w, r, nil) + + // Show reset password page + d := struct { + page.StaticPage + Flashes []string + CSRFField template.HTML + Token string + IsResetting bool + IsSent bool + }{ + StaticPage: pageForReq(app, r), + Flashes: f, + CSRFField: csrf.TemplateField(r), + Token: token, + IsResetting: resetting, + IsSent: r.FormValue("sent") == "1", + } + err := pages["reset.tmpl"].ExecuteTemplate(w, "base", d) + if err != nil { + log.Error("Unable to render password reset page: %v", err) + return err + } + return err +} + +func doAutomatedPasswordChange(app *App, userID int64, newPass string) error { + // Do password reset + hashedPass, err := auth.HashPass([]byte(newPass)) + if err != nil { + return impart.HTTPError{http.StatusInternalServerError, "Could not create password hash."} + } + + // Do update + err = app.db.ChangePassphrase(userID, true, "", hashedPass) + if err != nil { + return err + } + return nil +} + +func handleResetPasswordInit(app *App, w http.ResponseWriter, r *http.Request) error { + returnLoc := impart.HTTPError{http.StatusFound, "/reset"} + + ip := spam.GetIP(r) + alias := r.FormValue("alias") + + u, err := app.db.GetUserForAuth(alias) + if err != nil { + if strings.IndexAny(alias, "@") > 0 { + addSessionFlash(app, w, r, ErrUserNotFoundEmail.Message, nil) + return returnLoc + } + addSessionFlash(app, w, r, ErrUserNotFound.Message, nil) + return returnLoc + } + if u.IsAdmin() { + // Prevent any reset emails on admin accounts + log.Error("Admin reset attempt", `Someone just tried to reset the password for an admin (ID %d - %s). IP address: %s`, u.ID, u.Username, ip) + return returnLoc + } + if u.Email.String == "" { + err := impart.HTTPError{http.StatusPreconditionFailed, "User doesn't have an email address. Please contact us (" + app.cfg.App.Host + "/contact) to reset your password."} + addSessionFlash(app, w, r, err.Message, nil) + return returnLoc + } + if isSet, _ := app.db.IsUserPassSet(u.ID); !isSet { + err = loginViaEmail(app, u.Username, "/me/settings") + if err != nil { + return err + } + addSessionFlash(app, w, r, "We've emailed you a link to log in with.", nil) + return returnLoc + } + + token, err := app.db.CreatePasswordResetToken(u.ID) + if err != nil { + log.Error("Error resetting password: %s", err) + addSessionFlash(app, w, r, ErrInternalGeneral.Message, nil) + return returnLoc + } + + emailPasswordReset(app, u.EmailClear(app.keys), token) + + addSessionFlash(app, w, r, "We sent an email to the address associated with this account.", nil) + returnLoc.Message += "?sent=1" + return returnLoc +} + +func emailPasswordReset(app *App, toEmail, token string) error { + // Send email + gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate) + footerPara := "Didn't request this password reset? Your account is still safe, and you can safely ignore this email." + + plainMsg := fmt.Sprintf("We received a request to reset your password on %s. Please click the following link to continue (or copy and paste it into your browser): %s/reset?t=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara) + m := mailgun.NewMessage(app.cfg.App.SiteName+" ", "Reset Your "+app.cfg.App.SiteName+" Password", plainMsg, fmt.Sprintf("<%s>", toEmail)) + m.AddTag("Password Reset") + m.SetHtml(fmt.Sprintf(` + +
    +

    %s

    +

    We received a request to reset your password on %s. Please click the following link to continue:

    +

    Reset your password

    +

    %s

    +
    + +`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara)) + _, _, err := gun.Send(m) + return err +} + func loginViaEmail(app *App, alias, redirectTo string) error { if !app.cfg.Email.Enabled() { return fmt.Errorf("EMAIL ISN'T CONFIGURED on this server") diff --git a/database.go b/database.go index 85cce90..d238642 100644 --- a/database.go +++ b/database.go @@ -586,6 +586,37 @@ func (db *datastore) GetTemporaryOneTimeAccessToken(userID int64, validSecs int, return u.String(), nil } +func (db *datastore) CreatePasswordResetToken(userID int64) (string, error) { + t := id.Generate62RandomString(32) + + _, err := db.Exec("INSERT INTO password_resets (user_id, token, used, created) VALUES (?, ?, 0, "+db.now()+")", userID, t) + if err != nil { + log.Error("Couldn't INSERT password_resets: %v", err) + return "", err + } + + return t, nil +} + +func (db *datastore) GetUserFromPasswordReset(token string) int64 { + var userID int64 + err := db.QueryRow("SELECT user_id FROM password_resets WHERE token = ? AND used = 0 AND created > "+db.dateSub(3, "HOUR"), token).Scan(&userID) + if err != nil { + return 0 + } + return userID +} + +func (db *datastore) ConsumePasswordResetToken(t string) error { + _, err := db.Exec("UPDATE password_resets SET used = 1 WHERE token = ?", t) + if err != nil { + log.Error("Couldn't UPDATE password_resets: %v", err) + return err + } + + return nil +} + func (db *datastore) CreateOwnedPost(post *SubmittedPost, accessToken, collAlias, hostName string) (*PublicPost, error) { var userID, collID int64 = -1, -1 var coll *Collection diff --git a/less/core.less b/less/core.less index 1b418ba..a64056a 100644 --- a/less/core.less +++ b/less/core.less @@ -831,6 +831,9 @@ input { margin: 0 auto 3em; font-size: 1.2em; + &.toosmall { + max-width: 25em; + } &.tight { max-width: 30em; } diff --git a/migrations/drivers.go b/migrations/drivers.go index 800d2a6..5c6958a 100644 --- a/migrations/drivers.go +++ b/migrations/drivers.go @@ -61,6 +61,13 @@ func (db *datastore) typeVarChar(l int) string { return fmt.Sprintf("VARCHAR(%d)", l) } +func (db *datastore) typeVarBinary(l int) string { + if db.driverName == driverSQLite { + return "BLOB" + } + return fmt.Sprintf("VARBINARY(%d)", l) +} + func (db *datastore) typeBool() string { if db.driverName == driverSQLite { return "INTEGER" diff --git a/migrations/migrations.go b/migrations/migrations.go index d4f9d4b..3597bbd 100644 --- a/migrations/migrations.go +++ b/migrations/migrations.go @@ -69,6 +69,7 @@ var migrations = []Migration{ New("Widen oauth_users.access_token", widenOauthAcceesToken), // V10 -> V11 New("support verifying fedi profile", fediverseVerifyProfile), // V11 -> V12 (v0.14.0) New("support newsletters", supportLetters), // V12 -> V13 + New("support password resetting", supportPassReset), // V13 -> V14 } // CurrentVer returns the current migration version the application is on diff --git a/migrations/v14.go b/migrations/v14.go new file mode 100644 index 0000000..2883001 --- /dev/null +++ b/migrations/v14.go @@ -0,0 +1,37 @@ +/* + * Copyright © 2023 Musing Studio LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package migrations + +func supportPassReset(db *datastore) error { + t, err := db.Begin() + if err != nil { + t.Rollback() + return err + } + + _, err = t.Exec(`CREATE TABLE password_resets ( + user_id ` + db.typeInt() + ` not null, + token ` + db.typeChar(32) + ` not null primary key, + used ` + db.typeBool() + ` default 0 not null, + created ` + db.typeDateTime() + ` not null +)`) + if err != nil { + t.Rollback() + return err + } + + err = t.Commit() + if err != nil { + t.Rollback() + return err + } + return nil +} diff --git a/pages/login.tmpl b/pages/login.tmpl index f0a54eb..29cc6d6 100644 --- a/pages/login.tmpl +++ b/pages/login.tmpl @@ -3,6 +3,9 @@ {{end}} {{define "content"}} @@ -19,6 +22,7 @@ input{margin-bottom:0.5em;}


    +

    Forgot password?

    {{if .To}}{{end}}
    diff --git a/pages/reset.tmpl b/pages/reset.tmpl new file mode 100644 index 0000000..6deac41 --- /dev/null +++ b/pages/reset.tmpl @@ -0,0 +1,48 @@ +{{define "head"}}Reset password — {{.SiteName}} + +{{end}} +{{define "content"}} +
    +

    Reset your password

    + + {{if .Flashes}}
      + {{range .Flashes}}
    • {{.}}
    • {{end}} +
    {{end}} + + {{if .IsResetting}} +
    + + + + {{ .CSRFField }} +
    + {{else if not .IsSent}} +
    + + {{ .CSRFField }} + +
    + {{end}} + + +{{end}} diff --git a/routes.go b/routes.go index f17a72d..52ed0a6 100644 --- a/routes.go +++ b/routes.go @@ -184,6 +184,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { write.HandleFunc("/admin/updates", handler.Admin(handleViewAdminUpdates)).Methods("GET") // Handle special pages first + write.Path("/reset").Handler(csrf.Protect(apper.App().keys.CSRFKey)(handler.Web(viewResetPassword, UserLevelNoneRequired))) write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired)) write.HandleFunc("/signup", handler.Web(handleViewLanding, UserLevelNoneRequired)) write.HandleFunc("/invite/{code:[a-zA-Z0-9]+}", handler.Web(handleViewInvite, UserLevelOptional)).Methods("GET") diff --git a/spam/ip.go b/spam/ip.go new file mode 100644 index 0000000..89e317f --- /dev/null +++ b/spam/ip.go @@ -0,0 +1,25 @@ +/* + * Copyright © 2023 Musing Studio LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package spam + +import ( + "net/http" + "strings" +) + +func GetIP(r *http.Request) string { + h := r.Header.Get("X-Forwarded-For") + if h == "" { + return "" + } + ips := strings.Split(h, ",") + return strings.TrimSpace(ips[0]) +} From e96e657430e5987349f59f86bf478a8bea531e00 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 25 Sep 2023 19:07:06 -0400 Subject: [PATCH 105/115] Fix copyright notices with wrong company name --- email.go | 2 +- migrations/v13.go | 2 +- spam/email.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/email.go b/email.go index 54ca48a..da4590e 100644 --- a/email.go +++ b/email.go @@ -1,5 +1,5 @@ /* - * Copyright © 2019-2021 A Bunch Tell LLC. + * Copyright © 2019-2021 Musing Studio LLC. * * This file is part of WriteFreely. * diff --git a/migrations/v13.go b/migrations/v13.go index 5bb954b..908ceac 100644 --- a/migrations/v13.go +++ b/migrations/v13.go @@ -1,5 +1,5 @@ /* - * Copyright © 2021 A Bunch Tell LLC. + * Copyright © 2021 Musing Studio LLC. * * This file is part of WriteFreely. * diff --git a/spam/email.go b/spam/email.go index 76158f5..de017ab 100644 --- a/spam/email.go +++ b/spam/email.go @@ -1,5 +1,5 @@ /* - * Copyright © 2020-2021 A Bunch Tell LLC. + * Copyright © 2020-2021 Musing Studio LLC. * * This file is part of WriteFreely. * From f96f8268f0195a8ead2a196d18525a2ef7e15091 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 26 Sep 2023 14:36:34 -0400 Subject: [PATCH 106/115] Add index to improve post retrieval speed on large instances On an instance with millions of posts across all users, a single blog with thousands of posts on it can take a long time to render. This adds an index to the `posts` table to speed up the basic GetPosts query. Run: `writefreely db migrate` Closes #741 --- migrations/migrations.go | 1 + migrations/v15.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 migrations/v15.go diff --git a/migrations/migrations.go b/migrations/migrations.go index 3597bbd..fc638ee 100644 --- a/migrations/migrations.go +++ b/migrations/migrations.go @@ -70,6 +70,7 @@ var migrations = []Migration{ New("support verifying fedi profile", fediverseVerifyProfile), // V11 -> V12 (v0.14.0) New("support newsletters", supportLetters), // V12 -> V13 New("support password resetting", supportPassReset), // V13 -> V14 + New("speed up blog post retrieval", addPostRetrievalIndex), // V14 -> V15 } // CurrentVer returns the current migration version the application is on diff --git a/migrations/v15.go b/migrations/v15.go new file mode 100644 index 0000000..0875c5a --- /dev/null +++ b/migrations/v15.go @@ -0,0 +1,33 @@ +/* + * Copyright © 2023 Musing Studio LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package migrations + +func addPostRetrievalIndex(db *datastore) error { + t, err := db.Begin() + if err != nil { + t.Rollback() + return err + } + + _, err = t.Exec("CREATE INDEX posts_get_collection_index ON posts (`collection_id`, `pinned_position`, `created`)") + if err != nil { + t.Rollback() + return err + } + + err = t.Commit() + if err != nil { + t.Rollback() + return err + } + + return nil +} From 2275a288b961e1257cfbc4c074a5b455b4e33f16 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 26 Sep 2023 14:46:35 -0400 Subject: [PATCH 107/115] Correctly respond to application/ld+json requests, part 2 This finishes the work started in #766, ensuring that requests to canonical URLs of blogs and posts (not just at their API endpoints) respond correctly to `application/ld+json;...` requests. Fully addresses issue #564 --- collections.go | 2 +- handle.go | 2 +- posts.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/collections.go b/collections.go index e3674fa..75e0421 100644 --- a/collections.go +++ b/collections.go @@ -853,7 +853,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro } // Serve ActivityStreams data now, if requested - if strings.Contains(r.Header.Get("Accept"), "application/activity+json") { + if IsActivityPubRequest(r) { ac := c.PersonObject() ac.Context = []interface{}{activitystreams.Namespace} setCacheControl(w, apCacheTime) diff --git a/handle.go b/handle.go index 7537b61..1756be3 100644 --- a/handle.go +++ b/handle.go @@ -818,7 +818,7 @@ func (h *Handler) handleHTTPError(w http.ResponseWriter, r *http.Request, err er return } else if err.Status == http.StatusNotFound { w.WriteHeader(err.Status) - if strings.Contains(r.Header.Get("Accept"), "application/activity+json") { + if IsActivityPubRequest(r) { // This is a fediverse request; simply return the header return } diff --git a/posts.go b/posts.go index c9ac6ec..2fb23cc 100644 --- a/posts.go +++ b/posts.go @@ -1520,7 +1520,7 @@ Are you sure it was ever here?`, fmt.Fprintf(w, "# %s\n\n", p.Title.String) } fmt.Fprint(w, p.Content) - } else if strings.Contains(r.Header.Get("Accept"), "application/activity+json") { + } else if IsActivityPubRequest(r) { if !postFound { return ErrCollectionPageNotFound } From b232e7efd71ef1c12d7c7486323b0cb76984ed6b Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Oct 2023 10:56:23 -0400 Subject: [PATCH 108/115] Fix indentation in subscribers.tmpl --- templates/user/subscribers.tmpl | 40 +++++++++++++++++---------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/templates/user/subscribers.tmpl b/templates/user/subscribers.tmpl index 2f21474..1e79ddb 100644 --- a/templates/user/subscribers.tmpl +++ b/templates/user/subscribers.tmpl @@ -13,21 +13,23 @@ {{template "user-silenced"}} {{end}} -{{if .Collection.Collection}}{{template "collection-breadcrumbs" .}}{{end}} + {{if .Collection.Collection}}{{template "collection-breadcrumbs" .}}{{end}} -

    Subscribers

    -{{if .Collection.Collection}} - {{template "collection-nav" .Collection}} +

    Subscribers

    + {{if .Collection.Collection}} + {{template "collection-nav" .Collection}} - -{{end}} + + {{end}} - {{if .Flashes}}
      - {{range .Flashes}}
    • {{.}}
    • {{end}} -
    {{end}} + {{if .Flashes -}} +
      + {{range .Flashes}}
    • {{.}}
    • {{end}} +
    + {{- end}} {{ if eq .Filter "fediverse" }} @@ -36,23 +38,23 @@ - {{if and (gt (len .Followers) 0) (not .FederationEnabled)}} + {{if and (gt (len .Followers) 0) (not .FederationEnabled)}}

    Federation is disabled on this server, so followers won't receive any new posts.

    - {{end}} - {{ if gt (len .Followers) 0 }} - {{range $el := .Followers}} + {{end}} + {{ if gt (len .Followers) 0 }} + {{range $el := .Followers}} - {{end}} - {{ else }} + {{end}} + {{ else }} - {{ end }} + {{ end }}
    Since
    @{{.EstimatedHandle}} {{.CreatedFriendly}}
    No followers yet.
    {{ else }} {{if or .CanEmailSub .EmailSubs}} From c18987705c20f6e8d52775b8ecd45bc67eacc6f1 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Oct 2023 11:15:33 -0400 Subject: [PATCH 109/115] Display friendly message on /reset if email is disabled --- account.go | 24 +++++++++++++----------- pages/reset.tmpl | 20 +++++++++++++------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/account.go b/account.go index e88c1fd..0c83ad4 100644 --- a/account.go +++ b/account.go @@ -1278,18 +1278,20 @@ func viewResetPassword(app *App, w http.ResponseWriter, r *http.Request) error { // Show reset password page d := struct { page.StaticPage - Flashes []string - CSRFField template.HTML - Token string - IsResetting bool - IsSent bool + Flashes []string + EmailEnabled bool + CSRFField template.HTML + Token string + IsResetting bool + IsSent bool }{ - StaticPage: pageForReq(app, r), - Flashes: f, - CSRFField: csrf.TemplateField(r), - Token: token, - IsResetting: resetting, - IsSent: r.FormValue("sent") == "1", + StaticPage: pageForReq(app, r), + Flashes: f, + EmailEnabled: app.cfg.Email.Enabled(), + CSRFField: csrf.TemplateField(r), + Token: token, + IsResetting: resetting, + IsSent: r.FormValue("sent") == "1", } err := pages["reset.tmpl"].ExecuteTemplate(w, "base", d) if err != nil { diff --git a/pages/reset.tmpl b/pages/reset.tmpl index 6deac41..3db2418 100644 --- a/pages/reset.tmpl +++ b/pages/reset.tmpl @@ -14,6 +14,11 @@ label {

    Reset your password

    +{{ if not .EmailEnabled }} +
    +

    Email is not configured on this server! Please contact your admin to reset your password.

    +
    +{{ else }} {{if .Flashes}}
      {{range .Flashes}}
    • {{.}}
    • {{end}}
    {{end}} @@ -26,7 +31,7 @@ label { - {{ .CSRFField }} + {{ .CSRFField }} {{else if not .IsSent}}
    @@ -39,10 +44,11 @@ label {
    {{end}} - + +{{ end }} {{end}} From 1e37f60d501d4533476bd8334cfeb01e3b8f7e2f Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Oct 2023 11:16:11 -0400 Subject: [PATCH 110/115] Hide "Reset?" link on login page when email disabled --- account.go | 2 ++ pages/login.tmpl | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/account.go b/account.go index 0c83ad4..dd27ee2 100644 --- a/account.go +++ b/account.go @@ -325,6 +325,7 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error { To string Message template.HTML Flashes []template.HTML + EmailEnabled bool LoginUsername string }{ StaticPage: pageForReq(app, r), @@ -332,6 +333,7 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error { To: r.FormValue("to"), Message: template.HTML(""), Flashes: []template.HTML{}, + EmailEnabled: app.cfg.Email.Enabled(), LoginUsername: getTempInfo(app, "login-user", r, w), } diff --git a/pages/login.tmpl b/pages/login.tmpl index 29cc6d6..ae11edb 100644 --- a/pages/login.tmpl +++ b/pages/login.tmpl @@ -22,7 +22,7 @@ p.forgot {


    -

    Forgot password?

    + {{if .EmailEnabled}}

    Forgot password?

    {{end}} {{if .To}}{{end}}
    From 8f02449ee8f081aef28d18d238d0d171e9cc85c3 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Oct 2023 11:19:47 -0400 Subject: [PATCH 111/115] Show friendly message on /reset when password-based login is disabled --- pages/reset.tmpl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pages/reset.tmpl b/pages/reset.tmpl index 3db2418..bc18377 100644 --- a/pages/reset.tmpl +++ b/pages/reset.tmpl @@ -14,7 +14,11 @@ label {

    Reset your password

    -{{ if not .EmailEnabled }} +{{ if .DisablePasswordAuth }} +
    +

    Password login is disabled on this server, so it's not possible to reset your password.

    +
    +{{ else if not .EmailEnabled }}

    Email is not configured on this server! Please contact your admin to reset your password.

    From ed60aea39e76d46825c1dd716ba7de574bd6656e Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Oct 2023 11:25:05 -0400 Subject: [PATCH 112/115] Catch and log emailPasswordReset errors --- account.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/account.go b/account.go index dd27ee2..ce15a41 100644 --- a/account.go +++ b/account.go @@ -1359,7 +1359,12 @@ func handleResetPasswordInit(app *App, w http.ResponseWriter, r *http.Request) e return returnLoc } - emailPasswordReset(app, u.EmailClear(app.keys), token) + err = emailPasswordReset(app, u.EmailClear(app.keys), token) + if err != nil { + log.Error("Error emailing password reset: %s", err) + addSessionFlash(app, w, r, ErrInternalGeneral.Message, nil) + return returnLoc + } addSessionFlash(app, w, r, "We sent an email to the address associated with this account.", nil) returnLoc.Message += "?sent=1" From 7b84dafea79f36dfa3521445d9b2ea29e2813f40 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Oct 2023 11:28:24 -0400 Subject: [PATCH 113/115] Correctly return on /reset submission when email isn't configured --- account.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/account.go b/account.go index ce15a41..c280a8a 100644 --- a/account.go +++ b/account.go @@ -1321,6 +1321,11 @@ func doAutomatedPasswordChange(app *App, userID int64, newPass string) error { func handleResetPasswordInit(app *App, w http.ResponseWriter, r *http.Request) error { returnLoc := impart.HTTPError{http.StatusFound, "/reset"} + if !app.cfg.Email.Enabled() { + // Email isn't configured, so there's nothing to do; send back to the reset form, where they'll get an explanation + return returnLoc + } + ip := spam.GetIP(r) alias := r.FormValue("alias") From 8207a25fa91ea4d5344bc6854a182f58bc56e88a Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Oct 2023 11:39:41 -0400 Subject: [PATCH 114/115] Tweak style of "Forgot" link on login page --- pages/login.tmpl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pages/login.tmpl b/pages/login.tmpl index ae11edb..d0c0d22 100644 --- a/pages/login.tmpl +++ b/pages/login.tmpl @@ -4,7 +4,10 @@ {{end}} From 87b3585c44a0e90d49cfceac5ed8ab013370d32b Mon Sep 17 00:00:00 2001 From: Brennan Lujan <7959971+blujan@users.noreply.github.com> Date: Fri, 6 Oct 2023 20:20:40 -0700 Subject: [PATCH 115/115] Fix use of NOW() when getting tagged posts --- database.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database.go b/database.go index d238642..2614523 100644 --- a/database.go +++ b/database.go @@ -1320,7 +1320,7 @@ func (db *datastore) GetAllPostsTaggedIDs(c *Collection, tag string, includeFutu timeCondition := "" if !includeFuture { - timeCondition = "AND created <= NOW()" + timeCondition = "AND created <= " + db.now() } var rows *sql.Rows var err error