Sign actor fetch request

This fixes federation with Mastodon instances that have Authorized
Fetch turned on by signing the GET request to fetch the actor when
a blog is first followed.

Ref T820
This commit is contained in:
Matt Baer 2021-03-08 11:43:38 -05:00
parent 1f6d0e2e70
commit 9484880bca
4 changed files with 53 additions and 6 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2020 A Bunch Tell LLC. * Copyright © 2018-2021 A Bunch Tell LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -17,10 +17,12 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/writeas/writefreely/config"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"path/filepath"
"strconv" "strconv"
"time" "time"
@ -41,6 +43,17 @@ const (
apCacheTime = time.Minute apCacheTime = time.Minute
) )
var instanceColl *Collection
func initActivityPub(cfg *config.Config) {
ur, _ := url.Parse(cfg.App.Host)
instanceColl = &Collection{
ID: 0,
Alias: ur.Host,
Title: ur.Host,
}
}
type RemoteUser struct { type RemoteUser struct {
ID int64 ID int64
ActorID string ActorID string
@ -76,12 +89,17 @@ func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Re
vars := mux.Vars(r) vars := mux.Vars(r)
alias := vars["alias"] alias := vars["alias"]
if alias == "" {
alias = filepath.Base(r.RequestURI)
}
// TODO: enforce visibility // TODO: enforce visibility
// Get base Collection data // Get base Collection data
var c *Collection var c *Collection
var err error 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) c, err = app.db.GetCollectionByID(1)
} else { } else {
c, err = app.db.GetCollection(alias) c, err = app.db.GetCollection(alias)
@ -546,6 +564,22 @@ func resolveIRI(hostName, url string) ([]byte, error) {
r.Header.Add("Accept", "application/activity+json") r.Header.Add("Accept", "application/activity+json")
r.Header.Set("User-Agent", ServerUserAgent(hostName)) 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 { if debugging {
dump, err := httputil.DumpRequestOut(r, true) dump, err := httputil.DumpRequestOut(r, true)
if err != nil { if err != nil {

8
app.go
View File

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019 A Bunch Tell LLC. * Copyright © 2018-2021 A Bunch Tell LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -384,6 +384,8 @@ func Initialize(apper Apper, debug bool) (*App, error) {
apper.App().InitDecoder() apper.App().InitDecoder()
apper.App().InitActivityPub()
err = ConnectToDatabase(apper.App()) err = ConnectToDatabase(apper.App())
if err != nil { if err != nil {
return nil, fmt.Errorf("connect to DB: %s", err) return nil, fmt.Errorf("connect to DB: %s", err)
@ -499,6 +501,10 @@ func (app *App) InitDecoder() {
app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64) app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64)
} }
func (app *App) InitActivityPub() {
initActivityPub(app.cfg)
}
// ConnectToDatabase validates and connects to the configured database, then // ConnectToDatabase validates and connects to the configured database, then
// tests the connection. // tests the connection.
func ConnectToDatabase(app *App) error { func ConnectToDatabase(app *App) error {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019 A Bunch Tell LLC. * Copyright © 2018-2021 A Bunch Tell LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -12,6 +12,7 @@ package writefreely
import ( import (
"net/http" "net/http"
"net/url"
"path/filepath" "path/filepath"
"strings" "strings"
@ -125,9 +126,13 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
write.HandleFunc("/api/markdown", handler.All(handleRenderMarkdown)).Methods("POST") write.HandleFunc("/api/markdown", handler.All(handleRenderMarkdown)).Methods("POST")
instanceURL, _ := url.Parse(apper.App().Config().App.Host)
host := instanceURL.Host
// Handle collections // Handle collections
write.HandleFunc("/api/collections", handler.All(newCollection)).Methods("POST") write.HandleFunc("/api/collections", handler.All(newCollection)).Methods("POST")
apiColls := write.PathPrefix("/api/collections/").Subrouter() 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.AllReader(fetchCollection)).Methods("GET")
apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.All(existingCollection)).Methods("POST", "DELETE") apiColls.HandleFunc("/{alias:[0-9a-zA-Z\\-]+}", handler.All(existingCollection)).Methods("POST", "DELETE")
apiColls.HandleFunc("/{alias}/posts", handler.AllReader(fetchCollectionPosts)).Methods("GET") apiColls.HandleFunc("/{alias}/posts", handler.AllReader(fetchCollectionPosts)).Methods("GET")

View File

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2020 A Bunch Tell LLC. * Copyright © 2018-2021 A Bunch Tell LLC.
* *
* This file is part of WriteFreely. * 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) { func (wfr wfResolver) FindUser(username string, host, requestHost string, r []webfinger.Rel) (*webfinger.Resource, error) {
var c *Collection var c *Collection
var err error 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) c, err = wfr.db.GetCollectionByID(1)
} else { } else {
c, err = wfr.db.GetCollection(username) c, err = wfr.db.GetCollection(username)