Skip to content

Serve portable objects through existing object dispatchers #835

Description

@dahlia

Background

FEP-ef61 defines a gateway endpoint for retrieving portable ActivityPub objects over HTTP. A gateway serves objects identified by ap: URIs through a well-known path such as:

GET /.well-known/apgateway/did:key:z6MkAlice/users/alice/notes/123

Fedify already serves dereferenceable ActivityPub objects through object dispatchers registered with setObjectDispatcher(). An object dispatcher maps a vocabulary class and a URI Template path, such as /users/{userId}/notes/{noteId}, to application code that returns the object.

For the first FEP-ef61 gateway serving implementation, Fedify should reuse that same object dispatcher path as the portable object path. In other words, if an application has this dispatcher:

federation.setObjectDispatcher(
  Note,
  "/users/{userId}/notes/{noteId}",
  async (ctx, { userId, noteId }) => {
    // ...
  },
);

then this portable gateway request should route to the same dispatcher:

GET /.well-known/apgateway/did:key:z6MkAlice/users/alice/notes/123

The requested portable object ID would be reconstructed as:

ap+ef61://did:key:z6MkAlice/users/alice/notes/123

This keeps the first implementation close to Fedify's existing routing model instead of introducing a separate portable object dispatcher API.

Proposed work

Extend the existing object dispatcher machinery so Fedify can serve portable objects through the fixed FEP-ef61 gateway endpoint.

The work should include:

  • handling GET /.well-known/apgateway/{did}/{path...};
  • reconstructing the requested portable ID as an ap+ef61: URI from the DID authority and dispatcher path;
  • routing the portable path portion through the existing object dispatcher router;
  • using the same URI Template path registered through setObjectDispatcher() as the portable object path;
  • adding or extending context helpers so object dispatchers can construct portable object IDs, similar to Context.getObjectUri();
  • giving object dispatchers enough request context to know whether they are serving an ordinary HTTP object URL or a portable gateway request;
  • validating that the returned object's ID canonically matches the requested portable ID;
  • applying FEP-ef61 proof policy before serving portable actors, activities, and non-collection objects;
  • returning ActivityPub JSON-LD with the required content type;
  • returning 404 Not Found when no object dispatcher matches or the dispatcher returns null;
  • preserving existing object dispatcher behavior for ordinary HTTP object requests.

The exact API can be decided during implementation, but a possible context helper could look like:

ctx.getPortableObjectUri(Note, {
  userId: "alice",
  noteId: "123",
});

The helper would use the current portable DID authority when called during a gateway request, or require an explicit DID authority when called outside a gateway request.

Design questions

The implementation should settle these API details before finalizing the code path:

  • Should portable serving be enabled automatically for every object dispatcher, or should applications opt in per dispatcher?
  • How should a dispatcher distinguish an ordinary HTTP request from a portable gateway request?
  • Should ctx.getPortableObjectUri() require an explicit DID authority outside a gateway request?
  • Should dispatcher authorization predicates run unchanged for portable gateway requests, or should they receive additional portable-request context?
  • How should private object access and signed GET behavior fit into the existing object authorization model?

Scope

This issue is only about read-only serving of local portable objects through the fixed well-known FEP-ef61 gateway endpoint using existing object dispatchers.

It does not include:

  • GET /.well-known/apgateway discovery metadata;
  • gateway dereferencing of remote portable objects;
  • inbox or outbox POST handling;
  • gateway-to-gateway forwarding;
  • FEP-ae97 actor registration;
  • media upload, serving, or deletion;
  • arbitrary gateway path support;
  • automatic storage for portable objects.

Private object access and signed GET behavior should be considered during design. If it requires broader authorization API changes, it can be split into a follow-up issue.

Dependencies

This should depend on the lower-level portable URI and proof policy work under #288, especially:

  • portable URI codec support;
  • portable URI canonicalization and comparison;
  • FEP-fe34 cryptographic origins;
  • FEP-ef61 proof policy.

Tests

Add regression tests for portable gateway object serving through object dispatchers.

The tests should cover:

  • routing GET /.well-known/apgateway/did:key:.../users/alice/notes/123 to an existing setObjectDispatcher(Note, "/users/{userId}/notes/{noteId}", ...) dispatcher;
  • reconstructing the expected ap+ef61: portable ID;
  • constructing a portable object ID from context inside a gateway request;
  • returning 404 Not Found when no object dispatcher matches;
  • returning 404 Not Found when the matched dispatcher returns null;
  • returning ActivityPub JSON-LD with the expected Content-Type;
  • rejecting or failing a dispatcher result whose ID does not canonically match the requested portable ID;
  • applying FEP-ef61 proof policy before serving portable actors, activities, and objects;
  • preserving existing ordinary object dispatcher behavior;
  • rejecting malformed DID/path requests.

This should be added as a sub-issue of #288.

Metadata

Metadata

Assignees

Priority

High

Effort

High

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions