diff --git a/activitypub.go b/activitypub.go index 0e69075..6080884 100644 --- a/activitypub.go +++ b/activitypub.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2020 A Bunch Tell LLC. + * Copyright © 2018-2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -21,6 +21,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "path/filepath" "strconv" "time" @@ -41,6 +42,19 @@ const ( apCacheTime = time.Minute ) +var instanceColl *Collection + +func initActivityPub(app *App) { + ur, _ := url.Parse(app.cfg.App.Host) + instanceColl = &Collection{ + ID: 0, + Alias: ur.Host, + Title: ur.Host, + db: app.db, + hostName: app.cfg.App.Host, + } +} + type RemoteUser struct { ID int64 ActorID string @@ -76,12 +90,17 @@ func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Re vars := mux.Vars(r) alias := vars["alias"] + if alias == "" { + alias = filepath.Base(r.RequestURI) + } // TODO: enforce visibility // Get base Collection data var c *Collection var err error - if app.cfg.App.SingleUser { + if alias == r.Host { + c = instanceColl + } else if app.cfg.App.SingleUser { c, err = app.db.GetCollectionByID(1) } else { c, err = app.db.GetCollection(alias) @@ -89,16 +108,19 @@ func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Re if err != nil { return err } - silenced, err := app.db.IsUserSilenced(c.OwnerID) - if err != nil { - log.Error("fetch collection activities: %v", err) - return ErrInternalGeneral - } - if silenced { - return ErrCollectionNotFound - } c.hostName = app.cfg.App.Host + if !c.IsInstanceColl() { + silenced, err := app.db.IsUserSilenced(c.OwnerID) + if err != nil { + log.Error("fetch collection activities: %v", err) + return ErrInternalGeneral + } + if silenced { + return ErrCollectionNotFound + } + } + p := c.PersonObject() setCacheControl(w, apCacheTime) @@ -546,6 +568,22 @@ func resolveIRI(hostName, url string) ([]byte, error) { r.Header.Add("Accept", "application/activity+json") r.Header.Set("User-Agent", ServerUserAgent(hostName)) + p := instanceColl.PersonObject() + h := sha256.New() + h.Write([]byte{}) + r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil))) + + // Sign using the 'Signature' header + privKey, err := activitypub.DecodePrivateKey(p.GetPrivKey()) + if err != nil { + return nil, err + } + signer := httpsig.NewSigner(p.PublicKey.ID, privKey, httpsig.RSASHA256, []string{"(request-target)", "date", "host", "digest"}) + err = signer.SignSigHeader(r) + if err != nil { + log.Error("Can't sign: %v", err) + } + if debugging { dump, err := httputil.DumpRequestOut(r, true) if err != nil { diff --git a/app.go b/app.go index 2aed437..d34daf5 100644 --- a/app.go +++ b/app.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 A Bunch Tell LLC. + * Copyright © 2018-2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -389,6 +389,8 @@ func Initialize(apper Apper, debug bool) (*App, error) { return nil, fmt.Errorf("connect to DB: %s", err) } + initActivityPub(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 e1ebe48..c7b84dc 100644 --- a/collections.go +++ b/collections.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2020 A Bunch Tell LLC. + * Copyright © 2018-2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -180,6 +180,11 @@ func (c *Collection) NewFormat() *CollectionFormat { return cf } +func (c *Collection) IsInstanceColl() bool { + ur, _ := url.Parse(c.hostName) + return c.Alias == ur.Host +} + func (c *Collection) IsUnlisted() bool { return c.Visibility == 0 } diff --git a/routes.go b/routes.go index bb1785f..d3c56ca 100644 --- a/routes.go +++ b/routes.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2019 A Bunch Tell LLC. + * Copyright © 2018-2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -12,6 +12,7 @@ package writefreely import ( "net/http" + "net/url" "path/filepath" "strings" @@ -125,9 +126,13 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { write.HandleFunc("/api/markdown", handler.All(handleRenderMarkdown)).Methods("POST") + instanceURL, _ := url.Parse(apper.App().Config().App.Host) + host := instanceURL.Host + // Handle collections write.HandleFunc("/api/collections", handler.All(newCollection)).Methods("POST") apiColls := write.PathPrefix("/api/collections/").Subrouter() + apiColls.HandleFunc("/"+host, handler.AllReader(fetchCollection)).Methods("GET") apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.AllReader(fetchCollection)).Methods("GET") apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.All(existingCollection)).Methods("POST", "DELETE") apiColls.HandleFunc("/{alias}/posts", handler.AllReader(fetchCollectionPosts)).Methods("GET") diff --git a/webfinger.go b/webfinger.go index 993272f..581d940 100644 --- a/webfinger.go +++ b/webfinger.go @@ -1,5 +1,5 @@ /* - * Copyright © 2018-2020 A Bunch Tell LLC. + * Copyright © 2018-2021 A Bunch Tell LLC. * * This file is part of WriteFreely. * @@ -32,7 +32,9 @@ var wfUserNotFoundErr = impart.HTTPError{http.StatusNotFound, "User not found."} func (wfr wfResolver) FindUser(username string, host, requestHost string, r []webfinger.Rel) (*webfinger.Resource, error) { var c *Collection var err error - if wfr.cfg.App.SingleUser { + if username == host { + c = instanceColl + } else if wfr.cfg.App.SingleUser { c, err = wfr.db.GetCollectionByID(1) } else { c, err = wfr.db.GetCollection(username) @@ -41,15 +43,18 @@ func (wfr wfResolver) FindUser(username string, host, requestHost string, r []we log.Error("Unable to get blog: %v", err) return nil, err } - silenced, err := wfr.db.IsUserSilenced(c.OwnerID) - if err != nil { - log.Error("webfinger find user: check is silenced: %v", err) - return nil, err - } - if silenced { - return nil, wfUserNotFoundErr - } c.hostName = wfr.cfg.App.Host + + if !c.IsInstanceColl() { + silenced, err := wfr.db.IsUserSilenced(c.OwnerID) + if err != nil { + log.Error("webfinger find user: check is silenced: %v", err) + return nil, err + } + if silenced { + return nil, wfUserNotFoundErr + } + } if wfr.cfg.App.SingleUser { // Ensure handle matches user-chosen one on single-user blogs if username != c.Alias {