Discoverability (WebFinger)

One of the gaps in ActivityPub is that even though there is a specified way to provide all the information about a user (an “Actor”, in AP), there is no way to know what URL to query to get that data. We could use a convention such as https://server.com/user/user_name but there are different types of Actors other than just “User” so the /user/ part might not always be appropriate. We need a way to translate something short and human-typable, like rimu@mastodon.nzoss.nz to whatever mastodon.nzoss.nz uses as the URL for that actor (which could be a user, or a community/group, or other things). Kind of like DNS does for IP addresses. Once we have that URL we can HTTP GET it to retrieve the full JSON for that Actor.

WebFinger is pretty straightforward. If you want to retrieve the data for rimu@mastodon.nzoss.nz, make a GET request formatted like this:

https://mastodon.nzoss.nz/.well-known/webfinger?resource=rimu@mastodon.nzoss.nz

You’ll get some JSON back:

{
    "subject": "acct:rimu@mastodon.nzoss.nz",
    "aliases": [
        "https://mastodon.nzoss.nz/@rimu",
        "https://mastodon.nzoss.nz/users/rimu"
    ],
    "links": [
        {
            "rel": "http://webfinger.net/rel/profile-page",
            "type": "text/html",
            "href": "https://mastodon.nzoss.nz/@rimu"
        },
        {
            "rel": "self",
            "type": "application/activity+json",
            "href": "https://mastodon.nzoss.nz/users/rimu"
        },
        {
            "rel": "http://ostatus.org/schema/1.0/subscribe",
            "template": "https://mastodon.nzoss.nz/authorize_interaction?uri={uri}"
        },
        {
            "rel": "http://webfinger.net/rel/avatar",
            "type": "image/jpeg",
            "href": "https://mastodon.nzoss.nz/system/accounts/avatars/000/069/798/original/3105d31783a60017.jpg"
        }
    ]
}

Iterate through the “links” array until you find one with “rel” == “self”. The “href” contains the URL of the endpoint you need to query.

Now make a GET request to https://mastodon.nzoss.nz/users/rimu with the header

Accept: application/ld+json

If you don’t include that header then you’ll get a HTML representation of the profile, suitable for display in a browser. If you’ve got the header set correctly you will receive some JSON representing that Actor:

{
    "@context": [
        // snip
    ],
    "id": "https://mastodon.nzoss.nz/users/rimu",
    "type": "Person",
    "following": "https://mastodon.nzoss.nz/users/rimu/following",
    "followers": "https://mastodon.nzoss.nz/users/rimu/followers",
    "inbox": "https://mastodon.nzoss.nz/users/rimu/inbox",
    "outbox": "https://mastodon.nzoss.nz/users/rimu/outbox",
    "featured": "https://mastodon.nzoss.nz/users/rimu/collections/featured",
    "featuredTags": "https://mastodon.nzoss.nz/users/rimu/collections/tags",
    "preferredUsername": "rimu",
    "name": "Rimu",
    "summary": "<p>web dev. php, python<br />greenie<br />lefty<br /><a href=\"https://mastodon.nzoss.nz/tags/gemini\" class=\"mention hashtag\" rel=\"tag\">#<span>gemini</span></a><br />nomad</p>",
    "url": "https://mastodon.nzoss.nz/@rimu",
    "manuallyApprovesFollowers": false,
    "discoverable": true,
    "indexable": false,
    "published": "2019-05-08T00:00:00Z",
    "memorial": false,
    "devices": "https://mastodon.nzoss.nz/users/rimu/collections/devices",
    "publicKey": {
        "id": "https://mastodon.nzoss.nz/users/rimu#main-key",
        "owner": "https://mastodon.nzoss.nz/users/rimu",
        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n blah blah\n-----END PUBLIC KEY-----\n"
    },
    "tag": [
        {
            "type": "Hashtag",
            "href": "https://mastodon.nzoss.nz/tags/gemini",
            "name": "#gemini"
        }
    ],
    "attachment": [
        {
            "type": "PropertyValue",
            "name": "Photography",
            "value": "<a href=\"https://pixelfed.social/rimu\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">pixelfed.social/rimu</span><span class=\"invisible\"></span></a>"
        }
    ],
    "endpoints": {
        "sharedInbox": "https://mastodon.nzoss.nz/inbox"
    },
    "icon": {
        "type": "Image",
        "mediaType": "image/jpeg",
        "url": "https://mastodon.nzoss.nz/system/accounts/avatars/000/069/798/original/3105d31783a60017.jpg"
    },
    "image": {
        "type": "Image",
        "mediaType": "image/jpeg",
        "url": "https://mastodon.nzoss.nz/system/accounts/headers/000/069/798/original/fa7ac74cec12e2c9.jpg"
    }
}

This has all sorts of good stuff, including their name, description, profile pic, which URL to POST to to send them Activities, which URL to GET to see their followers, and so on.

Using WebFinger for Communties / Groups

In PieFed, WebFinger is used to make local copies of profiles of users but also for communities (the “Group” Actor type. You can WebFinger a community called ‘Positive News’ on kglitch.social with a GET to

https://kglitch.social/.well-known/webfinger?resource=positivenews@kglitch.social

this will tell you that the URL of Positive News is https://kglitch.social/m/positivenews and a GET with the Accept header set will yield

{
    "type": "Group",
    "@context": [
        "https://www.w3.org/ns/activitystreams",
        "https://w3id.org/security/v1"
    ],
    "id": "https://kglitch.social/m/positivenews",
    "name": "Positive News",
    "preferredUsername": "positivenews",
    "inbox": "https://kglitch.social/m/positivenews/inbox",
    "outbox": "https://kglitch.social/m/positivenews/outbox",
    "followers": "https://kglitch.social/m/positivenews/followers",
    "url": "https://kglitch.social/m/positivenews",
    "publicKey": {
        "owner": "https://kglitch.social/m/positivenews",
        "id": "https://kglitch.social/m/positivenews#main-key",
        "publicKeyPem": "removed for brevity"
    },
    "summary": "Let's remember that good things happen all the time",
    "sensitive": false,
    "moderators": "https://kglitch.social/m/positivenews/moderators",
    "postingRestrictedToMods": false,
    "endpoints": {
        "sharedInbox": "https://kglitch.social/f/inbox"
    },
    "published": "2023-07-19T21:40:06+00:00",
    "updated": "2023-11-17T18:39:11+00:00"
}

Communities have inboxes, outboxes and followers just like people/users (a “Person” actor) do. Kglitch.social is a Kbin instance so the JSON is structured the same but in a different order.

NB datetimes are formatted using a T between the date and the time, UTC timezone. In the earlier example from a Mastodon instance the datetimes are formatted differently. I expect there will be a future blog post on that because timezones are always a rough time and especially as JSON has no standard way to represent them (why, why?!!).

The public key is used to verify that Activities that claim to be sent from them actually did come from them. Whenever an Activity it sent to another server there is an encrypted header sent along with it that can only be verified by using their public key. This makes it impossible for a malicious instance to forge Activities. Not all fediverse software verifies these headers(!!) but they should. I’ll write about how this works in another blog post.

“sensitive” indicates a NSFW community. PieFed has added a similar “nsfl” attribute which is for stuff that is not sexy but which you might want to avoid if chilling on the couch trying to enjoy your evening. Gore, or whatever.

“published” is when the community was created while “updated” is when the latest post was made in it.

One comment

  1. […] In Mastodon, every user has their own ‘Inbox’ where content for them gets sent to. However in the ‘threadverse’ (a subset of the fediverse which includes lemmy, kbin, piefed) the content is for everyone to read so most of the AP communication happens using shared inboxes. This means there is a single URL on each server where all the data gets POSTed to. If the instances have not communicated before they need to discover each other’s inbox URLs by using WebFinger, a separate protocol which I have also written about. […]

Leave a Reply

Your email address will not be published. Required fields are marked *