Activity Pub Implementation #58
|
@ -55,12 +55,18 @@ func (cfg *ActivityPubConfig) PrivateKey() *rsa.PrivateKey {
|
||||||
type ActivityPubService struct {
|
type ActivityPubService struct {
|
||||||
followersRepo repository.FollowerRepository
|
followersRepo repository.FollowerRepository
|
||||||
configRepo repository.ConfigRepository
|
configRepo repository.ConfigRepository
|
||||||
|
siteConfigServcie *SiteConfigService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewActivityPubService(followersRepo repository.FollowerRepository, configRepo repository.ConfigRepository) *ActivityPubService {
|
func NewActivityPubService(
|
||||||
|
followersRepo repository.FollowerRepository,
|
||||||
|
configRepo repository.ConfigRepository,
|
||||||
|
siteConfigServcie *SiteConfigService,
|
||||||
|
) *ActivityPubService {
|
||||||
return &ActivityPubService{
|
return &ActivityPubService{
|
||||||
followersRepo: followersRepo,
|
followersRepo: followersRepo,
|
||||||
configRepo: configRepo,
|
configRepo: configRepo,
|
||||||
|
siteConfigServcie: siteConfigServcie,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +110,27 @@ func (svc *ActivityPubService) GetApConfig() (ActivityPubConfig, error) {
|
||||||
return apConfig, nil
|
return apConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *ActivityPubService) ActorUrl() string {
|
||||||
|
cfg, _ := svc.siteConfigServcie.GetSiteConfig()
|
||||||
|
return cfg.FullUrl
|
||||||
|
}
|
||||||
|
func (svc *ActivityPubService) MainKeyUri() string {
|
||||||
|
cfg, _ := svc.siteConfigServcie.GetSiteConfig()
|
||||||
|
return cfg.FullUrl + "#main-key"
|
||||||
|
}
|
||||||
|
func (svc *ActivityPubService) InboxUrl() string {
|
||||||
|
cfg, _ := svc.siteConfigServcie.GetSiteConfig()
|
||||||
|
return cfg.FullUrl + "/activitypub/inbox"
|
||||||
|
}
|
||||||
|
func (svc *ActivityPubService) OutboxUrl() string {
|
||||||
|
cfg, _ := svc.siteConfigServcie.GetSiteConfig()
|
||||||
|
return cfg.FullUrl + "/activitypub/outbox"
|
||||||
|
}
|
||||||
|
func (svc *ActivityPubService) FollowersUrl() string {
|
||||||
|
cfg, _ := svc.siteConfigServcie.GetSiteConfig()
|
||||||
|
return cfg.FullUrl + "/activitypub/followers"
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ActivityPubService) AddFollower(follower string) error {
|
func (s *ActivityPubService) AddFollower(follower string) error {
|
||||||
return s.followersRepo.Add(follower)
|
return s.followersRepo.Add(follower)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +184,7 @@ func (s *ActivityPubService) GetActor(reqUrl string) (vocab.Actor, error) {
|
||||||
req.Header.Set("Date", time.Now().Format(http.TimeFormat))
|
req.Header.Set("Date", time.Now().Format(http.TimeFormat))
|
||||||
req.Header.Set("Host", parsedUrl.Host)
|
req.Header.Set("Host", parsedUrl.Host)
|
||||||
|
|
||||||
err = s.sign(apConfig.PrivateKey(), siteConfig.FullUrl+"/activitypub/actor#main-key", nil, req)
|
err = s.sign(apConfig.PrivateKey(), s.MainKeyUri(), nil, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Signing error", "err", err)
|
slog.Error("Signing error", "err", err)
|
||||||
return vocab.Actor{}, err
|
return vocab.Actor{}, err
|
||||||
|
@ -195,7 +222,7 @@ func (s *ActivityPubService) VerifySignature(r *http.Request, sender string) err
|
||||||
s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig)
|
s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig)
|
||||||
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
||||||
|
|
||||||
slog.Info("verifying for", "sender", sender, "retriever", siteConfig.FullUrl+"/activitypub/actor")
|
slog.Info("verifying for", "sender", sender, "retriever", s.ActorUrl())
|
||||||
|
|
||||||
actor, err := s.GetActor(sender)
|
actor, err := s.GetActor(sender)
|
||||||
// actor does not have a pub key -> don't verify
|
// actor does not have a pub key -> don't verify
|
||||||
|
@ -264,7 +291,7 @@ func (s *ActivityPubService) sendObject(to vocab.Actor, data []byte) error {
|
||||||
req.Header.Set("Accept", "application/ld+json")
|
req.Header.Set("Accept", "application/ld+json")
|
||||||
req.Header.Set("Date", time.Now().Format(http.TimeFormat))
|
req.Header.Set("Date", time.Now().Format(http.TimeFormat))
|
||||||
req.Header.Set("Host", actorUrl.Host)
|
req.Header.Set("Host", actorUrl.Host)
|
||||||
err = s.sign(apConfig.PrivateKey(), siteConfig.FullUrl+"/activitypub/actor#main-key", data, req)
|
err = s.sign(apConfig.PrivateKey(), s.MainKeyUri(), data, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Signing error", "err", err)
|
slog.Error("Signing error", "err", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -66,7 +66,7 @@ func App(db infra.Database) *web.WebApp {
|
||||||
webmentionService := app.NewWebmentionService(
|
webmentionService := app.NewWebmentionService(
|
||||||
siteConfigService, interactionRepo, entryRepo, httpClient, eventBus,
|
siteConfigService, interactionRepo, entryRepo, httpClient, eventBus,
|
||||||
)
|
)
|
||||||
apService := app.NewActivityPubService(followersRepo, configRepo)
|
apService := app.NewActivityPubService(followersRepo, configRepo, siteConfigService)
|
||||||
|
|
||||||
// setup render functions
|
// setup render functions
|
||||||
render.SiteConfigService = siteConfigService
|
render.SiteConfigService = siteConfigService
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"owl-blogs/app"
|
"owl-blogs/app"
|
||||||
|
"strings"
|
||||||
|
|
||||||
vocab "github.com/go-ap/activitypub"
|
vocab "github.com/go-ap/activitypub"
|
||||||
"github.com/go-ap/jsonld"
|
"github.com/go-ap/jsonld"
|
||||||
|
@ -63,7 +64,7 @@ func (s *ActivityPubServer) HandleWebfinger(ctx *fiber.Ctx) error {
|
||||||
{
|
{
|
||||||
Rel: "self",
|
Rel: "self",
|
||||||
Type: "application/activity+json",
|
Type: "application/activity+json",
|
||||||
Href: siteConfig.FullUrl + "/activitypub/actor",
|
Href: s.apService.ActorUrl(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -73,24 +74,28 @@ func (s *ActivityPubServer) HandleWebfinger(ctx *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ActivityPubServer) Router(router fiber.Router) {
|
func (s *ActivityPubServer) Router(router fiber.Router) {
|
||||||
router.Get("/actor", s.HandleActor)
|
// router.Get("/actor", s.HandleActor)
|
||||||
router.Get("/outbox", s.HandleOutbox)
|
router.Get("/outbox", s.HandleOutbox)
|
||||||
router.Post("/inbox", s.HandleInbox)
|
router.Post("/inbox", s.HandleInbox)
|
||||||
router.Get("/followers", s.HandleFollowers)
|
router.Get("/followers", s.HandleFollowers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ActivityPubServer) HandleActor(ctx *fiber.Ctx) error {
|
func (s *ActivityPubServer) HandleActor(ctx *fiber.Ctx) error {
|
||||||
siteConfig, _ := s.siteConfigService.GetSiteConfig()
|
accepts := strings.Contains(string(ctx.Request().Header.Peek("Accept")), "application/activity+json")
|
||||||
|
req_content := strings.Contains(string(ctx.Request().Header.Peek("Content-Type")), "application/activity+json")
|
||||||
|
if !accepts && !req_content {
|
||||||
|
return ctx.Next()
|
||||||
|
}
|
||||||
apConfig, _ := s.apService.GetApConfig()
|
apConfig, _ := s.apService.GetApConfig()
|
||||||
|
|
||||||
actor := vocab.PersonNew(vocab.IRI(siteConfig.FullUrl + "/activitypub/actor"))
|
actor := vocab.PersonNew(vocab.IRI(s.apService.ActorUrl()))
|
||||||
actor.PreferredUsername = vocab.NaturalLanguageValues{{Value: vocab.Content(apConfig.PreferredUsername)}}
|
actor.PreferredUsername = vocab.NaturalLanguageValues{{Value: vocab.Content(apConfig.PreferredUsername)}}
|
||||||
actor.Inbox = vocab.IRI(siteConfig.FullUrl + "/activitypub/inbox")
|
actor.Inbox = vocab.IRI(s.apService.InboxUrl())
|
||||||
actor.Outbox = vocab.IRI(siteConfig.FullUrl + "/activitypub/outbox")
|
actor.Outbox = vocab.IRI(s.apService.OutboxUrl())
|
||||||
actor.Followers = vocab.IRI(siteConfig.FullUrl + "/activitypub/followers")
|
actor.Followers = vocab.IRI(s.apService.FollowersUrl())
|
||||||
actor.PublicKey = vocab.PublicKey{
|
actor.PublicKey = vocab.PublicKey{
|
||||||
ID: vocab.ID(siteConfig.FullUrl + "/activitypub/actor#main-key"),
|
ID: vocab.IRI(s.apService.MainKeyUri()),
|
||||||
Owner: vocab.IRI(siteConfig.FullUrl + "/activitypub/actor"),
|
Owner: vocab.IRI(s.apService.ActorUrl()),
|
||||||
PublicKeyPem: apConfig.PublicKeyPem,
|
PublicKeyPem: apConfig.PublicKeyPem,
|
||||||
}
|
}
|
||||||
data, err := jsonld.WithContext(
|
data, err := jsonld.WithContext(
|
||||||
|
@ -125,7 +130,7 @@ func (s *ActivityPubServer) HandleOutbox(ctx *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
outbox := vocab.OrderedCollectionNew(vocab.IRI(siteConfig.FullUrl + "/activitypub/outbox"))
|
outbox := vocab.OrderedCollectionNew(vocab.IRI(s.apService.OutboxUrl()))
|
||||||
outbox.TotalItems = uint(len(items))
|
outbox.TotalItems = uint(len(items))
|
||||||
outbox.OrderedItems = items
|
outbox.OrderedItems = items
|
||||||
|
|
||||||
|
@ -205,9 +210,6 @@ func (s *ActivityPubServer) HandleInbox(ctx *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ActivityPubServer) HandleFollowers(ctx *fiber.Ctx) error {
|
func (s *ActivityPubServer) HandleFollowers(ctx *fiber.Ctx) error {
|
||||||
siteConfig, _ := s.siteConfigService.GetSiteConfig()
|
|
||||||
// apConfig, _ := s.apService.GetApConfig()
|
|
||||||
|
|
||||||
fs, err := s.apService.AllFollowers()
|
fs, err := s.apService.AllFollowers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -218,7 +220,7 @@ func (s *ActivityPubServer) HandleFollowers(ctx *fiber.Ctx) error {
|
||||||
followers.Append(vocab.IRI(f))
|
followers.Append(vocab.IRI(f))
|
||||||
}
|
}
|
||||||
followers.TotalItems = uint(len(fs))
|
followers.TotalItems = uint(len(fs))
|
||||||
followers.ID = vocab.IRI(siteConfig.FullUrl + "/activitypub/followers")
|
followers.ID = vocab.IRI(s.apService.FollowersUrl())
|
||||||
data, err := jsonld.WithContext(
|
data, err := jsonld.WithContext(
|
||||||
jsonld.IRI(vocab.ActivityBaseURI),
|
jsonld.IRI(vocab.ActivityBaseURI),
|
||||||
).Marshal(followers)
|
).Marshal(followers)
|
||||||
|
|
|
@ -109,12 +109,15 @@ func NewWebApp(
|
||||||
siteConfig.Post("/menus/create/", siteConfigMenusHandler.HandleCreate)
|
siteConfig.Post("/menus/create/", siteConfigMenusHandler.HandleCreate)
|
||||||
siteConfig.Post("/menus/delete/", siteConfigMenusHandler.HandleDelete)
|
siteConfig.Post("/menus/delete/", siteConfigMenusHandler.HandleDelete)
|
||||||
|
|
||||||
|
activityPubServer := NewActivityPubServer(siteConfigService, entryService, apService)
|
||||||
|
configRegister.Register(config.ACT_PUB_CONF_NAME, &app.ActivityPubConfig{})
|
||||||
|
|
||||||
fiberApp.Use("/static", filesystem.New(filesystem.Config{
|
fiberApp.Use("/static", filesystem.New(filesystem.Config{
|
||||||
Root: http.FS(embedDirStatic),
|
Root: http.FS(embedDirStatic),
|
||||||
PathPrefix: "static",
|
PathPrefix: "static",
|
||||||
Browse: false,
|
Browse: false,
|
||||||
}))
|
}))
|
||||||
fiberApp.Get("/", indexHandler.Handle)
|
fiberApp.Get("/", activityPubServer.HandleActor, indexHandler.Handle)
|
||||||
fiberApp.Get("/lists/:list/", listHandler.Handle)
|
fiberApp.Get("/lists/:list/", listHandler.Handle)
|
||||||
// Media
|
// Media
|
||||||
fiberApp.Get("/media/+", mediaHandler.Handle)
|
fiberApp.Get("/media/+", mediaHandler.Handle)
|
||||||
|
@ -135,8 +138,6 @@ func NewWebApp(
|
||||||
fiberApp.Get("/sitemap.xml", NewSiteMapHandler(entryService, siteConfigService).Handle)
|
fiberApp.Get("/sitemap.xml", NewSiteMapHandler(entryService, siteConfigService).Handle)
|
||||||
|
|
||||||
// ActivityPub
|
// ActivityPub
|
||||||
activityPubServer := NewActivityPubServer(siteConfigService, entryService, apService)
|
|
||||||
configRegister.Register(config.ACT_PUB_CONF_NAME, &app.ActivityPubConfig{})
|
|
||||||
fiberApp.Get("/.well-known/webfinger", activityPubServer.HandleWebfinger)
|
fiberApp.Get("/.well-known/webfinger", activityPubServer.HandleWebfinger)
|
||||||
fiberApp.Route("/activitypub", activityPubServer.Router)
|
fiberApp.Route("/activitypub", activityPubServer.Router)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue