Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions FEDERATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ By default, tootik uses `draft-cavage-http-signatures` when it signs outgoing re

## Application Actor

tootik creates a special user named `nobody`, which acts as an [Application Actor](https://codeberg.org/fediverse/fep/src/branch/main/fep/2677/fep-2677.md). Its key is used to sign outgoing requests not initiated by a particular user.
tootik creates a special user named `actor`, which acts as an [Application Actor](https://codeberg.org/fediverse/fep/src/branch/main/fep/2677/fep-2677.md). Its key is used to sign outgoing requests not initiated by a particular user.

There are multiple ways to "discover" this actor:

1. Using [WebFinger](https://www.rfc-editor.org/rfc/rfc7033), just like any other user:

```sh
curl https://example.org/.well-known/webfinger?resource=acct:nobody@example.org
curl https://example.org/.well-known/webfinger?resource=acct:actor@example.org
```

2. For compatibility with servers that allow discovery of the Application Actor, the domain is an alias of `nobody`:
2. For compatibility with servers that allow discovery of the Application Actor, the domain is an alias of `actor`:

```sh
curl https://example.org/.well-known/webfinger?resource=acct:[email protected]
Expand All @@ -85,15 +85,15 @@ curl -H "accept: application/activity+json" https://example.org
curl https://example.org/actor
```

5. The `links` array returned by `/.well-known/nodeinfo` links to `nobody`, as [FEP-2677](https://codeberg.org/fediverse/fep/src/branch/main/fep/2677/fep-2677.md) requires
5. The `links` array returned by `/.well-known/nodeinfo` links to `actor`, as [FEP-2677](https://codeberg.org/fediverse/fep/src/branch/main/fep/2677/fep-2677.md) requires

```sh
curl https://example.org/.well-known/nodeinfo
```

The `sharedInbox` of non-portable actors points to `nobody`'s inbox, to reduce the number of requests from servers that deduplicate outgoing requests by `sharedInbox` during wide delivery of posts.
`actor` advertises support for RFC9421 and Ed25519 using [FEP-844e](https://codeberg.org/fediverse/fep/src/branch/main/fep/844e/fep-844e.md), to encourage other servers to use these capabilities when talking to tootik.

`nobody` advertises support for RFC9421 and Ed25519 using [FEP-844e](https://codeberg.org/fediverse/fep/src/branch/main/fep/844e/fep-844e.md), to encourage other servers to use these capabilities when talking to tootik.
Before v0.21.0, tootik used to call this actor `nobody` and set the `sharedInbox` of non-portable actors to `nobody`'s inbox, to reduce the number of requests from servers that deduplicate outgoing requests by `sharedInbox` during wide delivery of posts.

## Forwarding

Expand Down
2 changes: 1 addition & 1 deletion cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (c *Config) FillDefaults() {
c.CompiledUserNameRegex = regexp.MustCompile(c.UserNameRegex)

if c.ForbiddenUserNameRegex == "" {
c.ForbiddenUserNameRegex = `^(root|localhost|ip6-.*|.*(admin|tootik).*)$`
c.ForbiddenUserNameRegex = `^(actor|nobody|root|localhost|ip6-.*|.*(admin|tootik).*)$`
}

c.CompiledForbiddenUserNameRegex = regexp.MustCompile(c.ForbiddenUserNameRegex)
Expand Down
8 changes: 4 additions & 4 deletions cluster/followers_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestCluster_FollowersSyncMissingRemoteFollow(t *testing.T) {
Config: cluster["a.localdomain"].Config,
DB: cluster["a.localdomain"].DB,
Resolver: cluster["a.localdomain"].Resolver,
Keys: cluster["a.localdomain"].NobodyKeys,
Keys: cluster["a.localdomain"].AppActorKeys,
Inbox: cluster["a.localdomain"].Inbox,
}
if _, err := syncer.ProcessBatch(t.Context()); err != nil {
Expand Down Expand Up @@ -128,7 +128,7 @@ func TestCluster_FollowersSyncMissingLocalFollow(t *testing.T) {
Config: cluster["a.localdomain"].Config,
DB: cluster["a.localdomain"].DB,
Resolver: cluster["a.localdomain"].Resolver,
Keys: cluster["a.localdomain"].NobodyKeys,
Keys: cluster["a.localdomain"].AppActorKeys,
Inbox: cluster["a.localdomain"].Inbox,
}
if _, err := syncer.ProcessBatch(t.Context()); err != nil {
Expand Down Expand Up @@ -198,7 +198,7 @@ func TestCluster_FollowersSyncMissingRemoteFollowPortableActor(t *testing.T) {
Config: cluster["a.localdomain"].Config,
DB: cluster["a.localdomain"].DB,
Resolver: cluster["a.localdomain"].Resolver,
Keys: cluster["a.localdomain"].NobodyKeys,
Keys: cluster["a.localdomain"].AppActorKeys,
Inbox: cluster["a.localdomain"].Inbox,
}
if _, err := syncer.ProcessBatch(t.Context()); err != nil {
Expand Down Expand Up @@ -268,7 +268,7 @@ func TestCluster_FollowersSyncMissingLocalFollowPortableActor(t *testing.T) {
Config: cluster["a.localdomain"].Config,
DB: cluster["a.localdomain"].DB,
Resolver: cluster["a.localdomain"].Resolver,
Keys: cluster["a.localdomain"].NobodyKeys,
Keys: cluster["a.localdomain"].AppActorKeys,
Inbox: cluster["a.localdomain"].Inbox,
}
if _, err := syncer.ProcessBatch(t.Context()); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cluster/move_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestCluster_MovedAccount(t *testing.T) {
Domain: "b.localdomain",
DB: cluster["b.localdomain"].DB,
Resolver: cluster["b.localdomain"].Resolver,
Keys: cluster["b.localdomain"].NobodyKeys,
Keys: cluster["b.localdomain"].AppActorKeys,
Inbox: cluster["b.localdomain"].Inbox,
}
if err := mover.Run(t.Context()); err != nil {
Expand Down
51 changes: 26 additions & 25 deletions cluster/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ import (
)

type Server struct {
Test *testing.T
Domain string
Config *cfg.Config
DB *sql.DB
Resolver *fed.Resolver
NobodyKeys [2]httpsig.Key
Frontend gemini.Listener
Backend http.Handler
Inbox *inbox.Inbox
Incoming *inbox.Queue
Outgoing *fed.Queue
Test *testing.T
Domain string
Config *cfg.Config
DB *sql.DB
Resolver *fed.Resolver
AppActorKeys [2]httpsig.Key
Frontend gemini.Listener
Backend http.Handler
Inbox *inbox.Inbox
Incoming *inbox.Queue
Outgoing *fed.Queue

listener, tlsListener net.Listener
socketPath string
Expand Down Expand Up @@ -160,9 +160,9 @@ func NewServer(t *testing.T, domain string, client fed.Client) *Server {

resolver := fed.NewResolver(nil, domain, &cfg, client, db)

_, nobodyKeys, err := user.CreateNobody(t.Context(), domain, db, &cfg)
appActor, appActorKeys, err := user.CreateApplicationActor(t.Context(), domain, db, &cfg)
if err != nil {
t.Fatalf("Failed to run create the nobody user: %v", err)
t.Fatalf("Failed to create the application actor: %v", err)
}

localInbox := &inbox.Inbox{
Expand All @@ -182,11 +182,12 @@ func NewServer(t *testing.T, domain string, client fed.Client) *Server {
}

backend, err := (&fed.Listener{
Domain: domain,
Config: &cfg,
DB: db,
ActorKeys: nobodyKeys,
Resolver: resolver,
Domain: domain,
Config: &cfg,
DB: db,
AppActor: appActor,
AppActorKeys: appActorKeys,
Resolver: resolver,
}).NewHandler()
if err != nil {
t.Fatalf("Failed to run create the federation handler: %v", err)
Expand All @@ -208,12 +209,12 @@ func NewServer(t *testing.T, domain string, client fed.Client) *Server {
tlsListener := tls.NewListener(listener, &serverCfg)

return &Server{
Test: t,
Domain: domain,
Config: &cfg,
DB: db,
Resolver: resolver,
NobodyKeys: nobodyKeys,
Test: t,
Domain: domain,
Config: &cfg,
DB: db,
Resolver: resolver,
AppActorKeys: appActorKeys,
Frontend: gemini.Listener{
Domain: domain,
Config: &cfg,
Expand All @@ -227,7 +228,7 @@ func NewServer(t *testing.T, domain string, client fed.Client) *Server {
DB: db,
Inbox: localInbox,
Resolver: resolver,
Keys: nobodyKeys,
Keys: appActorKeys,
},
Outgoing: &fed.Queue{
Domain: domain,
Expand Down
29 changes: 15 additions & 14 deletions cmd/tootik/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func main() {
panic(err)
}

_, nobodyKeys, err := user.CreateNobody(ctx, *domain, db, &cfg)
appActor, appActorKeys, err := user.CreateApplicationActor(ctx, *domain, db, &cfg)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -316,16 +316,17 @@ func main() {
{
"HTTPS",
&fed.Listener{
Domain: *domain,
Config: &cfg,
DB: db,
ActorKeys: nobodyKeys,
Resolver: resolver,
Addr: *addr,
Cert: *cert,
Key: *key,
Plain: *plain,
BlockList: blockList,
Domain: *domain,
Config: &cfg,
DB: db,
AppActor: appActor,
AppActorKeys: appActorKeys,
Resolver: resolver,
Addr: *addr,
Cert: *cert,
Key: *key,
Plain: *plain,
BlockList: blockList,
},
},
{
Expand Down Expand Up @@ -362,7 +363,7 @@ func main() {
DB: db,
Inbox: localInbox,
Resolver: resolver,
Keys: nobodyKeys,
Keys: appActorKeys,
},
},
{
Expand Down Expand Up @@ -416,7 +417,7 @@ func main() {
DB: db,
Inbox: localInbox,
Resolver: resolver,
Keys: nobodyKeys,
Keys: appActorKeys,
},
},
{
Expand All @@ -427,7 +428,7 @@ func main() {
Config: &cfg,
DB: db,
Resolver: resolver,
Keys: nobodyKeys,
Keys: appActorKeys,
Inbox: localInbox,
},
},
Expand Down
2 changes: 1 addition & 1 deletion fed/apgateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (l *Listener) handleAPGatewayPost(w http.ResponseWriter, r *http.Request) {
}

func (l *Listener) handleApGatewayFollowers(w http.ResponseWriter, r *http.Request, did string) {
_, sender, err := l.verifyRequest(r, nil, ap.InstanceActor, l.ActorKeys)
_, sender, err := l.verifyRequest(r, nil, ap.InstanceActor, l.AppActorKeys)
if err != nil {
slog.Warn("Failed to verify followers request", "error", err)
w.WriteHeader(http.StatusUnauthorized)
Expand Down
2 changes: 1 addition & 1 deletion fed/followers.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func (f partialFollowers) Digest(ctx context.Context, db *sql.DB, domain string,
func (l *Listener) handleFollowers(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("username")

_, sender, err := l.verifyRequest(r, nil, ap.InstanceActor, l.ActorKeys)
_, sender, err := l.verifyRequest(r, nil, ap.InstanceActor, l.AppActorKeys)
if err != nil {
slog.Warn("Failed to verify followers request", "error", err)
w.WriteHeader(http.StatusUnauthorized)
Expand Down
2 changes: 1 addition & 1 deletion fed/inbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ func (l *Listener) fetchObject(ctx context.Context, id string, keys [2]httpsig.K
}

func (l *Listener) handleSharedInbox(w http.ResponseWriter, r *http.Request) {
l.doHandleInbox(w, r, l.ActorKeys)
l.doHandleInbox(w, r, l.AppActorKeys)
}

func (l *Listener) handleInbox(w http.ResponseWriter, r *http.Request) {
Expand Down
2 changes: 1 addition & 1 deletion fed/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import "net/http"
func (l *Listener) handleIndex(w http.ResponseWriter, r *http.Request) {
// this is how PieFed fetches the instance actor to detect its inbox and use it as a shared inbox for this instance
if accept := r.Header.Get("Accept"); accept == "application/activity+json" || accept == `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` {
l.doHandleUser(w, r, "nobody")
l.doHandleUser(w, r, "actor")
return
}

Expand Down
28 changes: 15 additions & 13 deletions fed/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,24 @@ import (
"sync"
"time"

"github.com/dimkr/tootik/ap"
"github.com/dimkr/tootik/cfg"
"github.com/dimkr/tootik/httpsig"
"github.com/fsnotify/fsnotify"
)

type Listener struct {
Domain string
Config *cfg.Config
DB *sql.DB
Resolver *Resolver
ActorKeys [2]httpsig.Key
Addr string
Cert string
Key string
Plain bool
BlockList *BlockList
Domain string
Config *cfg.Config
DB *sql.DB
Resolver *Resolver
AppActor *ap.Actor
AppActorKeys [2]httpsig.Key
Addr string
Cert string
Key string
Plain bool
BlockList *BlockList
}

const certReloadDelay = time.Second * 5
Expand All @@ -59,8 +61,8 @@ func (l *Listener) newHandler() (*http.ServeMux, error) {
mux.HandleFunc("GET /actor", l.handleActor)
mux.HandleFunc("GET /icon/{username}", l.handleIcon)
mux.HandleFunc("POST /inbox/{username}", l.handleInbox)
mux.HandleFunc("POST /inbox/nobody", l.handleSharedInbox)
mux.HandleFunc("POST /inbox", l.handleSharedInbox) // PieFed falls back https://$domain/inbox if it can't fetch instance actor
mux.HandleFunc("POST /inbox/actor", l.handleSharedInbox)
mux.HandleFunc("POST /inbox", l.handleSharedInbox)
mux.HandleFunc("GET /outbox/{username}", l.handleOutbox)
mux.HandleFunc("GET /post/{hash}", l.handlePost)
mux.HandleFunc("GET /create/{hash}", l.handleCreate)
Expand All @@ -73,7 +75,7 @@ func (l *Listener) newHandler() (*http.ServeMux, error) {
w.WriteHeader(http.StatusNotFound)
})

if err := addNodeInfo(mux, l.Domain, l.Config, l.DB); err != nil {
if err := l.addNodeInfo(mux); err != nil {
return nil, err
}

Expand Down
Loading
Loading