* initial commit * add lists * update permissions * fix waypoint create * needs_full_sync for activitypub trails * fixes build issues * fix list get * further CSRF protection * update API docs * adds rate limiter * fix tiptap mentions * a bit more cleanup of main.go * Fix migration order * fixes reviewed notes * require context for activitpub server calls * improve hashing for identifier * fix copy paste error * fix sync trail/list issues * fix summit log/comment duplicates * adaptions after trail merge * fix dockerignore --------- Co-authored-by: Christian Beutel <> Co-authored-by: slothful-vassal <89943360+slothful-vassal@users.noreply.github.com>
211 lines
5.3 KiB
Go
211 lines
5.3 KiB
Go
package routes
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"pocketbase/federation"
|
|
"pocketbase/util"
|
|
"strconv"
|
|
"strings"
|
|
|
|
pub "github.com/go-ap/activitypub"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
)
|
|
|
|
func ActivitypubActor(e *core.RequestEvent) error {
|
|
resource := e.Request.URL.Query().Get("resource")
|
|
resource = strings.TrimPrefix(resource, "acct:")
|
|
|
|
iri := e.Request.URL.Query().Get("iri")
|
|
follows := e.Request.URL.Query().Get("follows") == "true"
|
|
|
|
var userActor *core.Record
|
|
var err error
|
|
if e.Auth != nil {
|
|
userActor, err = e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
ctx, err := util.GetSafeActorContext(e.Request, userActor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var actor *core.Record
|
|
if resource != "" {
|
|
actor, err = federation.GetActorByHandle(e.App, ctx, resource, follows)
|
|
} else {
|
|
actor, err = federation.GetActorByIRI(e.App, ctx, iri, follows)
|
|
}
|
|
if err != nil && actor == nil {
|
|
if strings.HasPrefix(err.Error(), "webfinger") {
|
|
return e.NotFoundError("Not found", err)
|
|
}
|
|
return err
|
|
} else if err != nil && actor != nil {
|
|
if errors.Is(err, federation.ErrProfilePrivate) {
|
|
// this is our own profile
|
|
if e.Auth != nil && actor.GetString("user") == e.Auth.Id {
|
|
return e.JSON(http.StatusOK, map[string]any{"actor": actor, "error": nil})
|
|
} else {
|
|
return e.JSON(http.StatusNotFound, map[string]any{"error": "profile is private"})
|
|
}
|
|
}
|
|
// we could not fetch the remote actor so we return our local cached copy
|
|
return e.JSON(http.StatusOK, map[string]any{"actor": actor, "error": err.Error()})
|
|
}
|
|
|
|
return e.JSON(http.StatusOK, map[string]any{"actor": actor, "error": nil})
|
|
}
|
|
|
|
func ActivitypubActivityProcess(e *core.RequestEvent) error {
|
|
origin := os.Getenv("ORIGIN")
|
|
if origin == "" {
|
|
return fmt.Errorf("ORIGIN not set")
|
|
}
|
|
|
|
body, err := io.ReadAll(e.Request.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var activity pub.Activity
|
|
err = activity.UnmarshalJSON(body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
inbox := fmt.Sprintf("%s%s", origin, e.Request.Header.Get("X-Forwarded-Path"))
|
|
|
|
recipient, err := e.App.FindFirstRecordByData("activitypub_actors", "inbox", inbox)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
actor, err := e.App.FindFirstRecordByData("activitypub_actors", "iri", activity.Actor.GetID().String())
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
ctx, err := util.GetSafeActorContext(e.Request, recipient)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
actor, err = federation.GetActorByIRI(e.App, ctx, activity.Actor.GetID().String(), false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return err
|
|
|
|
}
|
|
}
|
|
|
|
verified, err := util.VerifySignature(e.App, e.Request, actor.GetString("public_key"))
|
|
if err != nil || !verified {
|
|
e.App.Logger().Error(err.Error())
|
|
return e.UnauthorizedError("Invalid http signature", err)
|
|
}
|
|
|
|
switch activity.Type {
|
|
case pub.FollowType:
|
|
err = federation.ProcessFollowActivity(e.App, actor, activity)
|
|
case pub.AcceptType:
|
|
err = federation.ProcessAcceptActivity(e.App, actor, activity)
|
|
case pub.UndoType:
|
|
err = federation.ProcessUndoActivity(e.App, actor, activity)
|
|
case pub.UpdateType:
|
|
fallthrough
|
|
case pub.CreateType:
|
|
err = federation.ProcessCreateOrUpdateActivity(e.App, actor, recipient, activity)
|
|
case pub.DeleteType:
|
|
err = federation.ProcessDeleteActivity(e.App, actor, activity)
|
|
case pub.AnnounceType:
|
|
err = federation.ProcessAnnounceActivity(e.App, actor, activity)
|
|
case pub.LikeType:
|
|
err = federation.ProcessLikeActivity(e.App, actor, activity)
|
|
}
|
|
return e.JSON(http.StatusOK, err)
|
|
}
|
|
|
|
func ActivitypubActorFollow(e *core.RequestEvent) error {
|
|
id := e.Request.PathValue("id")
|
|
followType := e.Request.PathValue("follow")
|
|
page := e.Request.URL.Query().Get("page")
|
|
intPage := 0
|
|
|
|
if page != "" {
|
|
var err error
|
|
intPage, err = strconv.Atoi(page)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
actor, err := e.App.FindRecordById("activitypub_actors", id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var userActor *core.Record
|
|
if e.Auth != nil {
|
|
userActor, err = e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
ctx, err := util.GetSafeActorContext(e.Request, userActor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
url := actor.GetString(followType)
|
|
|
|
if url == "" {
|
|
return e.BadRequestError("unknown type: "+followType, nil)
|
|
}
|
|
collection, err := federation.FetchCollection(e.App, ctx, fmt.Sprintf("%s?page=%d", url, intPage))
|
|
if err != nil {
|
|
if errors.Is(err, federation.ErrProfilePrivate) {
|
|
return e.JSON(http.StatusNotFound, map[string]any{"error": "profile is private"})
|
|
} else if errors.Is(err, util.ErrRateLimited) {
|
|
return e.TooManyRequestsError("Too many requests", err)
|
|
}
|
|
return err
|
|
}
|
|
return e.JSON(http.StatusOK, collection)
|
|
}
|
|
|
|
func ActivitypubTrail(e *core.RequestEvent) error {
|
|
id := e.Request.PathValue("id")
|
|
|
|
trail, err := e.App.FindRecordById("trails", id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
trailObject, err := util.ObjectFromTrail(e.App, trail, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return e.JSON(http.StatusOK, trailObject)
|
|
}
|
|
|
|
func ActivitypubComment(e *core.RequestEvent) error {
|
|
id := e.Request.PathValue("id")
|
|
|
|
comment, err := e.App.FindRecordById("comments", id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
commentObject, err := util.ObjectFromComment(e.App, comment, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return e.JSON(http.StatusOK, commentObject)
|
|
}
|