mirror of https://github.com/jlelse/GoBlog
Send ActivityPub profile updates
parent
607f8e10f0
commit
0159a04a43
|
@ -27,12 +27,8 @@ import (
|
|||
)
|
||||
|
||||
func (a *goBlog) initActivityPub() error {
|
||||
if a.isPrivate() {
|
||||
// Private mode, no AP
|
||||
return nil
|
||||
}
|
||||
if apc := a.cfg.ActivityPub; apc == nil || !apc.Enabled {
|
||||
// Disabled
|
||||
if !a.apEnabled() {
|
||||
// ActivityPub disabled
|
||||
return nil
|
||||
}
|
||||
// Add hooks
|
||||
|
@ -80,9 +76,28 @@ func (a *goBlog) initActivityPub() error {
|
|||
}
|
||||
// Init send queue
|
||||
a.initAPSendQueue()
|
||||
// Send profile updates
|
||||
go func() {
|
||||
// First wait a bit
|
||||
time.Sleep(time.Second * 10)
|
||||
// Then send profile update
|
||||
a.apSendProfileUpdates()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *goBlog) apEnabled() bool {
|
||||
if a.isPrivate() {
|
||||
// Private mode, no AP
|
||||
return false
|
||||
}
|
||||
if apc := a.cfg.ActivityPub; apc == nil || !apc.Enabled {
|
||||
// Disabled
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *goBlog) apHandleWebfinger(w http.ResponseWriter, r *http.Request) {
|
||||
blog, ok := a.webfingerResources[r.URL.Query().Get("resource")]
|
||||
if !ok {
|
||||
|
@ -383,6 +398,23 @@ func (a *goBlog) apAccept(blogName string, blog *configBlog, follow map[string]a
|
|||
_ = a.apQueueSendSigned(a.apIri(blog), inbox, accept)
|
||||
}
|
||||
|
||||
func (a *goBlog) apSendProfileUpdates() {
|
||||
for blog, config := range a.cfg.Blogs {
|
||||
person, err := a.toAsPerson(blog)
|
||||
if err != nil {
|
||||
log.Println("Failed to create Person object:", err)
|
||||
continue
|
||||
}
|
||||
a.apSendToAllFollowers(blog, map[string]any{
|
||||
"@context": []string{asContext},
|
||||
"actor": a.apIri(config),
|
||||
"published": time.Now().Format("2006-01-02T15:04:05-07:00"),
|
||||
"type": "Update",
|
||||
"object": person,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (a *goBlog) apSendToAllFollowers(blog string, activity any) {
|
||||
inboxes, err := a.db.apGetAllInboxes(blog)
|
||||
if err != nil {
|
||||
|
@ -426,18 +458,29 @@ func (a *goBlog) loadActivityPubPrivateKey() error {
|
|||
// continue
|
||||
} else {
|
||||
key, err := x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes)
|
||||
if err == nil && key != nil {
|
||||
a.apPrivateKey = key
|
||||
return nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.apPrivateKey = key
|
||||
a.apPubKeyBytes = pubKeyBytes
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Generate and cache key
|
||||
var err error
|
||||
a.apPrivateKey, err = rsa.GenerateKey(rand.Reader, 2048)
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.apPrivateKey = key
|
||||
a.apPubKeyBytes = pubKeyBytes
|
||||
return a.db.cachePersistently(
|
||||
"activitypub_key",
|
||||
pem.EncodeToMemory(&pem.Block{
|
||||
|
|
|
@ -29,6 +29,7 @@ func Test_loadActivityPubPrivateKey(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, app.apPrivateKey)
|
||||
assert.NotEmpty(t, app.apPubKeyBytes)
|
||||
|
||||
oldEncodedKey := x509.MarshalPKCS1PrivateKey(app.apPrivateKey)
|
||||
oldPemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: oldEncodedKey})
|
||||
|
@ -38,6 +39,7 @@ func Test_loadActivityPubPrivateKey(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, app.apPrivateKey)
|
||||
assert.NotEmpty(t, app.apPubKeyBytes)
|
||||
|
||||
newEncodedKey := x509.MarshalPKCS1PrivateKey(app.apPrivateKey)
|
||||
newPemEncoded := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: newEncodedKey})
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
@ -168,13 +167,8 @@ func (a *goBlog) activityPubId(p *post) string {
|
|||
return fu
|
||||
}
|
||||
|
||||
func (a *goBlog) serveActivityStreams(blog string, w http.ResponseWriter, r *http.Request) {
|
||||
func (a *goBlog) toAsPerson(blog string) (*asPerson, error) {
|
||||
b := a.cfg.Blogs[blog]
|
||||
publicKeyDer, err := x509.MarshalPKIXPublicKey(&(a.apPrivateKey.PublicKey))
|
||||
if err != nil {
|
||||
a.serveError(w, r, "Failed to marshal public key", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
asBlog := &asPerson{
|
||||
Context: []string{asContext},
|
||||
Type: "Person",
|
||||
|
@ -190,21 +184,29 @@ func (a *goBlog) serveActivityStreams(blog string, w http.ResponseWriter, r *htt
|
|||
PublicKeyPem: string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Headers: nil,
|
||||
Bytes: publicKeyDer,
|
||||
Bytes: a.apPubKeyBytes,
|
||||
})),
|
||||
},
|
||||
}
|
||||
// Add profile picture
|
||||
if a.cfg.User.Picture != "" {
|
||||
asBlog.Icon = &asAttachment{
|
||||
Type: "Image",
|
||||
URL: a.cfg.User.Picture,
|
||||
}
|
||||
}
|
||||
return asBlog, nil
|
||||
}
|
||||
|
||||
func (a *goBlog) serveActivityStreams(blog string, w http.ResponseWriter, r *http.Request) {
|
||||
person, err := a.toAsPerson(blog)
|
||||
if err != nil {
|
||||
a.serveError(w, r, "Failed to create ActivityStreams Person", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Encode
|
||||
buf := bufferpool.Get()
|
||||
defer bufferpool.Put(buf)
|
||||
if err := json.NewEncoder(buf).Encode(asBlog); err != nil {
|
||||
if err := json.NewEncoder(buf).Encode(person); err != nil {
|
||||
a.serveError(w, r, "Encoding failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
1
app.go
1
app.go
|
@ -22,6 +22,7 @@ import (
|
|||
type goBlog struct {
|
||||
// ActivityPub
|
||||
apPrivateKey *rsa.PrivateKey
|
||||
apPubKeyBytes []byte
|
||||
apPostSigner httpsig.Signer
|
||||
apPostSignMutex sync.Mutex
|
||||
webfingerResources map[string]*configBlog
|
||||
|
|
4
go.mod
4
go.mod
|
@ -43,7 +43,7 @@ require (
|
|||
github.com/paulmach/go.geojson v1.4.0
|
||||
github.com/posener/wstest v1.2.0
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/samber/lo v1.13.0
|
||||
github.com/samber/lo v1.14.0
|
||||
github.com/schollz/sqlite3dump v1.3.1
|
||||
github.com/snabb/sitemap v1.0.0
|
||||
github.com/spf13/cast v1.4.1
|
||||
|
@ -58,7 +58,7 @@ require (
|
|||
// master
|
||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5
|
||||
golang.org/x/net v0.0.0-20220420153159-1850ba15e1be
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/text v0.3.7
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
|
|
8
go.sum
8
go.sum
|
@ -410,8 +410,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
|||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/samber/lo v1.13.0 h1:82AIN+opOjdaP2pntOu8UxK1Zp0OyjJrOTMFb44YF1o=
|
||||
github.com/samber/lo v1.13.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
||||
github.com/samber/lo v1.14.0 h1:WpQM8MI6C7K3YNEhAzhOSm84QBV70Bme64Up6zhA1jQ=
|
||||
github.com/samber/lo v1.14.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
|
||||
github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA=
|
||||
github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI=
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
|
@ -602,8 +602,8 @@ golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5 h1:bRb386wvrE+oBNdF1d/Xh9mQrfQ4ecYhW5qJ5GvTGT4=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220420153159-1850ba15e1be h1:yx80W7nvY5ySWpaU8UWaj5o9e23YgO9BRhQol7Lc+JI=
|
||||
golang.org/x/net v0.0.0-20220420153159-1850ba15e1be/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
Loading…
Reference in New Issue