toolshed/docs/federation.md
2026-08-17 18:28:01 +02:00

8.4 KiB

Federation

This section will cover how federation works in Toolshed.

What is Federation?

Since user of Toolshed you can search and interact the inventory of all their 'friends' that are potentially on different servers there is a need for a way to communicate between servers. We don't want to rely on a central server that stores all the data and we don't want to have a central server that handles all the communication between servers. This is where federation comes in. Toolshed uses a protocol that can not only exchange data with the server where the user is registered but also with the servers where their friends are registered.

How does it work?

Any user can register on any server and creates a personal key pair. The public key is stored on the server and the private key is stored on the client. The private key is used to sign all requests to the server and the public key is used to verify the signature. Once a user has registered on a server they can send friend requests to other users containing their public key. If the other user accepts the friend request, the server stores the public key of the friend and uses it to verify access to the friend's inventory. While accepting a friend request the user also automatically sends their own public key to the friend's server. This way both users can access each other's inventory.

The protocol is based on a simple HTTPS API exchanging JSON data that is signed with the user's private key. By default Toolshed servers provide a documentation of the API at /docs/api.

Unique Handles

Federation only works if every server can talk about the same thing without a central authority to ask. Toolshed's answer is that every kind of entity that needs to be referenced across servers gets a handle: a name that is unique within its own scope and that carries, as part of itself, enough information to say where it is authoritative. This keeps servers independent of each other while still letting them agree on what they're talking about.

Users (and Groups)

A user's handle is their username paired with the domain their account belongs to, written the way an email address is, e.g. user@toolsheddomain.tld. Uniqueness is only required within a single domain, not across all of Toolshed, so two different domains can each have their own "alice" without conflict, the same way two different email providers can each have an "alice" mailbox. The domain half of the handle is what makes the name globally unambiguous, and it is also what tells any other backend where to look to find out who's currently authoritative for that identity, i.e. which backend holds the account and can vouch for its public key.

Groups aren't implemented yet, but they're intended to fit the same idea: a group would get its own handle on the domain of the server that hosts it, the same way a user does, so that group membership and group-owned data could be referenced by other servers without needing a separate mechanism. A group handle is written with a leading #, e.g. #groupname@toolsheddomain.tld, so that group and user handles occupy visibly distinct spaces on the same domain and a name can't be squatted as one to collide with the other. See groups.md for details.

Servers

The domain half of a handle, e.g. toolsheddomain.tld, is an authority record, not a location. Owning a domain just means being able to say which backend is currently authoritative for handles under it; it says nothing about where that backend is hosted, who operates it, or how many other domains it might also be authoritative for. A single backend can just as easily host entities for one domain or for many unrelated ones at once, there's no assumption anywhere in the model that a domain and a backend are the same thing, or that the relationship is one to one.

The frontend application is a third, separate thing again. The app a user loads isn't necessarily served by, or even related to, the backend that ends up handling their requests: when given a handle, the frontend looks up which backend is currently authoritative for that handle's domain and talks to that backend directly from then on. So using the frontend at one domain to log into a backend authoritative for a completely different domain isn't a special case, it's the normal path, since "where the app was loaded from" and "which backend answers for a given handle" were never the same question to begin with. Servers, in the cryptographic sense described below, don't have an identity of their own beyond the handles they're currently authoritative for; a backend is, conceptually, just wherever a given domain's handles happen to resolve to right now.

Tags, Properties, and Categories

Inventory items aren't just described in free text, they can be classified with tags, properties, and categories, and those get handles too, written as an origin followed by the kind and name, e.g. origin#tag:drill or origin#category:power-tools. This lets the same short name (e.g. a "drill" tag) exist independently under different origins without colliding, while a handle as a whole unambiguously says which taxonomy an entry belongs to.

An origin isn't necessarily a server; it's whatever the classification is considered to have come from, which could be a shared, canonical reference dataset that multiple servers import and reuse, just as easily as it could be a server's own locally-invented taxonomy. This lets independently-run servers converge on a shared vocabulary where it matters, without forcing every server to invent its own from scratch or requiring a central body to define one.

Handles are resolved strictly: a reference to an origin or entity a server doesn't know about is left unresolved rather than being guessed at or silently merged into something that looks similar. This mirrors the rest of Toolshed's federation philosophy, nothing is combined across servers implicitly; agreement always has to be traceable to an explicit, shared handle.

Items

Inventory items don't get a handle of their own the way tags or categories do, because they don't need one: every item belongs to exactly one user, so a simple local identifier is already enough to tell two items apart within that user's inventory. Combined with the owner's user handle, that local identifier is automatically unique across all of Toolshed too, since no two users share a handle. Unlike a tag or category, an item isn't meant to be the same entity reused across servers, it describes something one specific person actually owns, so there's no shared-origin concept to design for here, ownership alone already provides the scope.

Cryptography

Handles say who or what is being referred to; cryptography is what lets a server trust that the entity behind a handle really is who it claims to be, without needing to ask a central authority. Every user handle has exactly one asymmetric keypair backing it: a private key that never leaves the user's control, and a public key that gets handed out freely as part of establishing that handle elsewhere.

A server first learns a public key at the moment it has reason to trust it: for its own users, that's registration; for a friend's handle, that's the friend-request/accept exchange described above. From then on, a public key is permanently paired with the handle it arrived with, never with a server. This is why friending is really a key-exchange ceremony rather than just a social action, accepting a request is the moment a server starts trusting a new handle's signature.

Every request made on a user's behalf is signed with that user's private key, and whichever server receives it verifies the signature against the public key it holds for that handle. This is what makes it safe for a request to travel to a server that isn't the user's home server: the receiving server doesn't need to trust the network path or the sender, only the signature.

It's worth being explicit about what this layer of cryptography is for and what it isn't. Signing establishes authenticity and integrity, that a request genuinely came from the handle it claims to, unaltered, not confidentiality. The data itself isn't encrypted by the protocol; keeping it private in transit is what the underlying HTTPS layer is for. Only user handles carry a keypair; tags, categories, properties, and item handles are just names, their trustworthiness comes entirely from being reachable only through a signed request from the user handle that owns or created them, not from any cryptographic identity of their own.