From 856480989cc62ed486b09de4205cc00e5855dcc6 Mon Sep 17 00:00:00 2001 From: Xeronith Date: Fri, 14 Jul 2023 16:53:55 +0330 Subject: [PATCH] feat(app): :sparkles: improve federation --- app/activitypub/follow.go | 34 +++++++++++++ app/commands/follow_actor.go | 60 ++++++++++++++++++---- app/commands/post_to_inbox.go | 93 ++++++++++++++++++---------------- app/commands/post_to_outbox.go | 34 +++++-------- 4 files changed, 148 insertions(+), 73 deletions(-) create mode 100644 app/activitypub/follow.go diff --git a/app/activitypub/follow.go b/app/activitypub/follow.go new file mode 100644 index 0000000..19cd304 --- /dev/null +++ b/app/activitypub/follow.go @@ -0,0 +1,34 @@ +package activitypub + +import ( + "encoding/json" + "fmt" +) + +type Follow struct { + Context string `json:"@context" validate:"activitystream"` + Id string `json:"id"` + Type string `json:"type"` + Actor string `json:"actor"` + Object string `json:"object"` +} + +func NewFollow(follower, followee, uuid string) *Follow { + return &Follow{ + Context: ActivityStreams, + Id: fmt.Sprintf("%s#follow/%s", follower, uuid), + Type: TypeFollow, + Actor: follower, + Object: followee, + } +} + +func UnmarshalFollow(data []byte) (Follow, error) { + var follow Follow + err := json.Unmarshal(data, &follow) + return follow, err +} + +func (follow *Follow) Marshal() ([]byte, error) { + return json.Marshal(follow) +} diff --git a/app/commands/follow_actor.go b/app/commands/follow_actor.go index 92a6c7a..bb28df7 100644 --- a/app/commands/follow_actor.go +++ b/app/commands/follow_actor.go @@ -1,10 +1,8 @@ package commands import ( - "fmt" "io" "net/http" - "net/url" "strings" "github.com/reiver/greatape/app/activitypub" @@ -26,20 +24,62 @@ func FollowActor(x IDispatcher, username string, acct string) (IFollowActorResul webfinger, err := activitypub.UnmarshalWebfinger(data) x.AssertNoError(err) - template := "" + subject := "" for _, link := range webfinger.Links { - if link.Rel == OSTATUS_SUBSCRIPTION { - template = *link.Template + if link.Rel == "self" { + subject = *link.Href break } } - if template == "" { - return nil, fmt.Errorf("remote_account_lookup_failed") + if x.IsEmpty(subject) { + return nil, ERROR_INVALID_PARAMETERS } - uri := url.QueryEscape(x.Format("%s/u/%s", x.PublicUrl(), username)) - template = strings.Replace(template, "{uri}", uri, -1) + identities := x.FilterIdentities(func(identity IIdentity) bool { + return identity.Username() == username + }) - return x.NewFollowActorResult(template), nil + x.Assert(identities.HasExactlyOneItem()).Or(ERROR_USER_NOT_FOUND) + identity := identities.First() + + follower := x.GetActorId() + + followee := &activitypub.Actor{} + if err := x.GetActivityStreamSigned(subject, nil, followee); err != nil { + return nil, err + } + + uniqueIdentifier := x.GenerateUUID() + follow := activitypub.NewFollow(follower, followee.ID, uniqueIdentifier) + + x.Atomic(func() error { + activity := x.MarshalJson(follow) + + x.AddActivityPubOutgoingActivity( + identity.Id(), + uniqueIdentifier, + x.UnixNano(), + follower, + followee.ID, + activitypub.TypeFollow, + activity, + ) + + x.AddActivityPubFollower( + follower, + followee.Inbox, + followee.ID, + activity, + false, + ) + + if err := x.PostActivityStreamSigned(followee.Inbox, follow, nil); err != nil { + return err + } + + return nil + }) + + return x.NewFollowActorResult(follow.Id), nil } diff --git a/app/commands/post_to_inbox.go b/app/commands/post_to_inbox.go index 874dc02..156a301 100644 --- a/app/commands/post_to_inbox.go +++ b/app/commands/post_to_inbox.go @@ -1,9 +1,6 @@ package commands import ( - "encoding/json" - - "github.com/mitchellh/mapstructure" "github.com/reiver/greatape/app/activitypub" . "github.com/reiver/greatape/components/constants" . "github.com/reiver/greatape/components/contracts" @@ -18,54 +15,72 @@ func PostToInbox(x IDispatcher, username string, body []byte) (IPostToInboxResul identity := identities.First() object := &activitypub.Object{} - if err := json.Unmarshal(body, object); err != nil { - return nil, ERROR_UNKNOWN_ACTIVITY_PUB_OBJECT - } - - keyId := x.Format("%s/u/%s#main-key", x.PublicUrl(), username) + x.UnmarshalJson(body, object) switch object.Type { - case activitypub.TypeFollow: + case activitypub.TypeAccept: { activity := &activitypub.Activity{} - if err := json.Unmarshal(body, activity); err != nil { - return nil, ERROR_UNKNOWN_ACTIVITY_PUB_ACTIVITY + x.UnmarshalJson(body, activity) + + switch activity.Object.(map[string]interface{})["type"] { + case activitypub.TypeFollow: + follow := &activitypub.Follow{} + x.DecodeMapStructure(activity.Object, follow) + + x.Atomic(func() error { + x.ForEachActivityPubFollower(func(record IActivityPubFollower) { + if record.Handle() == follow.Actor && record.Subject() == follow.Object { + record.UpdateAcceptedAtomic(x.Transaction(), true, x.Identity()) + } + }) + + x.AddActivityPubIncomingActivity( + identity.Id(), + x.GenerateUUID(), + x.UnixNano(), + follow.Object, + follow.Actor, + activitypub.TypeAccept, + string(body), + ) + + return nil + }) + + default: + return nil, ERROR_INVALID_PARAMETERS } + } + case activitypub.TypeFollow: + { + follow := &activitypub.Follow{} + x.UnmarshalJson(body, follow) - url := activity.Actor - var inbox string + url := follow.Actor - { - actor := &activitypub.Actor{} - if err := x.GetActivityStreamSigned(url, keyId, identity.PrivateKey(), nil, actor); err != nil { - return nil, err - } - - inbox = actor.Inbox - } - - data, err := json.Marshal(activity) - if err != nil { + actor := &activitypub.Actor{} + if err := x.GetActivityStreamSigned(url, nil, actor); err != nil { return nil, err } follower := x.AddActivityPubFollower( - activity.Actor, - inbox, + follow.Actor, + actor.Inbox, x.Format("%s/u/%s", x.PublicUrl(), username), - string(data), + x.MarshalJson(follow), false, ) - data, _ = json.Marshal(&activitypub.Activity{ + activity := &activitypub.Activity{ Context: activitypub.ActivityStreams, ID: x.Format("%s/%s", x.PublicUrl(), x.GenerateUUID()), Type: activitypub.TypeAccept, Actor: x.Format("%s/u/%s", x.PublicUrl(), username), - Object: activity, - }) + Object: follow, + } - if err := x.PostActivityStreamSigned(inbox, keyId, identity.PrivateKey(), data, nil); err != nil { + if err := x.PostActivityStreamSigned(actor.Inbox, activity, nil); err != nil { return nil, err } @@ -74,18 +89,12 @@ func PostToInbox(x IDispatcher, username string, body []byte) (IPostToInboxResul case activitypub.TypeCreate: { activity := &activitypub.Activity{} - if err := json.Unmarshal(body, activity); err != nil { - return nil, ERROR_UNKNOWN_ACTIVITY_PUB_ACTIVITY - } + x.UnmarshalJson(body, activity) switch activity.Object.(map[string]interface{})["type"] { case activitypub.TypeNote: note := &activitypub.Note{} - if err := mapstructure.Decode(activity.Object, note); err != nil { - return nil, ERROR_UNKNOWN_ACTIVITY_PUB_ACTIVITY - } - - raw, _ := json.Marshal(note) + x.DecodeMapStructure(activity.Object, note) x.AddActivityPubIncomingActivity( identity.Id(), @@ -94,16 +103,14 @@ func PostToInbox(x IDispatcher, username string, body []byte) (IPostToInboxResul note.AttributedTo, note.To[0], note.Content, - string(raw), + string(body), ) default: return nil, ERROR_INVALID_PARAMETERS } } default: - { - return nil, ERROR_INVALID_PARAMETERS - } + return nil, ERROR_INVALID_PARAMETERS } return x.NewPostToInboxResult(body), nil diff --git a/app/commands/post_to_outbox.go b/app/commands/post_to_outbox.go index a8e5863..9cec3a9 100644 --- a/app/commands/post_to_outbox.go +++ b/app/commands/post_to_outbox.go @@ -1,11 +1,9 @@ package commands import ( - "encoding/json" "fmt" "time" - ap "github.com/go-ap/activitypub" "github.com/reiver/greatape/app/activitypub" . "github.com/reiver/greatape/components/constants" . "github.com/reiver/greatape/components/contracts" @@ -21,21 +19,21 @@ func PostToOutbox(x IDispatcher, username string, body []byte) (IPostToOutboxRes item := x.UnmarshalActivityPubObjectOrLink(body) - id := x.Format("%s/u/%s", x.PublicUrl(), identity.Username()) - - publicKeyId := x.Format("%s#main-key", id) - privateKey := identity.PrivateKey() + actorId := x.GetActorId() switch item.GetType() { - case ap.NoteType: + case activitypub.TypeNote: { - note := x.UnmarshalActivityPubNote(body) + note, err := activitypub.UnmarshalNote(body) + if err != nil { + return nil, ERROR_INVALID_PARAMETERS + } - content := note.Content.First().Value.String() - to := note.To.First().GetID().String() - from := note.AttributedTo.GetID().String() + content := note.Content + to := note.To[0] + from := note.AttributedTo - if from != id { + if from != actorId { return nil, ERROR_INVALID_PARAMETERS } @@ -51,23 +49,19 @@ func PostToOutbox(x IDispatcher, username string, body []byte) (IPostToOutboxRes Object: note, } - if to != activitypub.Public { + if to != ACTIVITY_PUB_PUBLIC { recipient := &activitypub.Actor{} - if err := x.GetActivityStreamSigned(to, publicKeyId, privateKey, nil, recipient); err != nil { + if err := x.GetActivityStreamSigned(to, nil, recipient); err != nil { return nil, err } to = recipient.ID - data, _ := json.Marshal(activity) - output := &struct{}{} - if err := x.PostActivityStreamSigned(recipient.Inbox, publicKeyId, privateKey, data, output); err != nil { + if err := x.PostActivityStreamSigned(recipient.Inbox, activity, nil); err != nil { return nil, err } } - raw, _ := json.Marshal(note) - x.LogActivityPubOutgoingActivity( identity.Id(), uniqueIdentifier, @@ -75,7 +69,7 @@ func PostToOutbox(x IDispatcher, username string, body []byte) (IPostToOutboxRes from, to, content, - string(raw), + string(body), "PostToOutbox", EMPTY_JSON, )