This pull request implements two new properties on ActivityPub actors: `hidesToPublicFromUnauthedWeb` and `hidesCcPublicFromUnauthedWeb`. As documented, these properties allow actors to signal their preference for whether or not their posts should be hidden from unauthenticated web views (ie., web pages like the GtS frontend, web apps like the Mastodon frontend, web APIs like the Mastodon public timeline API, etc). This allows remote accounts to *opt in* to having their unlisted visibility posts shown in (for example) the replies section of the web view of a GtS thread. In future, we can also use these properties to determine whether we should show boosts of a remote actor's post on a GtS profile, and that sort of thing. In keeping with our stance around privacy by default, GtS assumes `true` for `hidesCcPublicFromUnauthedWeb` if the property is not set on a remote actor, ie., hide unlisted/unlocked posts by default. `hidesToPublicFromUnauthedWeb` is assumed to be `false` if the property is not set on a remote actor, ie., show public posts by default. ~~WIP as I still want to work on the documentation for this a bit.~~ New props are already in the namespace document: https://gotosocial.org/ns Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4315 Reviewed-by: kim <gruf@noreply.codeberg.org> Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
28 KiB
Actors and Actor Properties
Service vs Person actors
GoToSocial serves most accounts as the ActivityStreams Person type described here.
Accounts that users have selected to mark as bot accounts, however, will use the Service type described here.
This type distinction can be used by remote servers to distinguish between bot accounts and "regular" user accounts.
Inbox
GoToSocial implements Inboxes for Actors following the ActivityPub specification here.
Remote servers should deliver Activities to a GoToSocial server by making an HTTP POST request to each Inbox of the desired audience of an Activity, as described here.
GoToSocial accounts do not currently implement a sharedInbox endpoint, though this is subject to change. Deduplication of delivered Activities, in case more than one Actor on a GoToSocial server is in the audience for an Activity, is handled on GoToSocial's side.
POSTs to a GoToSocial Actor's inbox must be appropriately http-signed by the delivering Actor.
Accepted Inbox POST Content-Type headers are:
application/activity+jsonapplication/activity+json; charset=utf-8application/ld+json; profile="https://www.w3.org/ns/activitystreams"
Inbox POST requests that do not use one of the above Content-Type headers will be rejected with HTTP status code 406 - Not Acceptable.
For more information on acceptable content types, see the server-to-server interactions section of the ActivityPub protocol.
GoToSocial will return HTTP status code 202 - Accepted in response to validly-formed and signed Inbox POST requests.
Invalidly-formed Inbox POST requests will receive a 400 - Bad Request HTTP status code in response. The response body may contain more information on why the GoToSocial server considered the request content to be badly formed. Other servers should not retry delivery of the Activity in case of a code 400 response.
Even if GoToSocial returns a 202 status code, it may not continue processing the Activity delivered, depending on the originator(s), target(s) and type of the Activity. ActivityPub is an extensive protocol, and GoToSocial does not cover every combination of Activity and Object.
Outbox
GoToSocial implements Outboxes for Actors (ie., instance accounts) following the ActivityPub specification here.
To get an OrderedCollection of Activities that an Actor has published recently, remote servers can do a GET request to a user's outbox. The address of this will be something like https://example.org/users/whatever/outbox.
The server will return an OrderedCollection of the following structure:
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/whatever/outbox",
"type": "OrderedCollection",
"first": "https://example.org/users/whatever/outbox?page=true"
}
Note that the OrderedCollection itself contains no items. Callers must dereference the first page to start getting items. For example, a GET to https://example.org/users/whatever/outbox?page=true will produce something like the following:
{
"id": "https://example.org/users/whatever/outbox?page=true",
"type": "OrderedCollectionPage",
"next": "https://example.org/users/whatever/outbox?max_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true",
"prev": "https://example.org/users/whatever/outbox?min_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true",
"partOf": "https://example.org/users/whatever/outbox",
"orderedItems": [
{
"id": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7/activity",
"type": "Create",
"actor": "https://example.org/users/whatever",
"published": "2021-10-18T20:06:18Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://example.org/users/whatever/followers"
],
"object": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7"
}
]
}
The orderedItems array will contain up to 30 entries. To get more entries beyond that, the caller can use the next link provided in the response.
Note that in the returned orderedItems, all activity types will be Create. On each activity, the object field will be the AP URI of an original public status created by the Actor who owns the Outbox (ie., a Note with https://www.w3.org/ns/activitystreams#Public in the to field, which is not a reply to another status). Callers can use the returned AP URIs to dereference the content of the notes.
Followers / Following Collections
GoToSocial implements followers and following collections as OrderedCollections. A properly-signed GET request to an Actor's Following collection, for example, will return something like:
{
"@context": "https://www.w3.org/ns/activitystreams",
"first": "https://example.org/users/someone/following?limit=40",
"id": "https://example.org/users/someone/following",
"totalItems": 397,
"type": "OrderedCollection"
}
From there, you can use the first page to start getting items. For example, a GET request to https://example.org/users/someone/following?limit=40 will produce something like:
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/someone/following?limit=40",
"next": "https://example.org/users/someone/following?limit=40&max_id=01V1AY4ZJT4JK1NT271SH2WMGH",
"orderedItems": [
"https://example.org/users/someone_else",
"https://somewhere.else.example.org/users/another_account",
[... 38 more entries here ...]
],
"partOf": "https://example.org/users/someone/following",
"prev": "https://example.org/users/someone/following?limit=40&since_id=021HKBY346X7BPFYANPPJN493P",
"totalItems": 397,
"type": "OrderedCollectionPage"
}
You can then use the next and prev endpoints to page down and up through the OrderedCollection.
!!! Info "Hidden Followers / Following Collections"
GoToSocial allows users to hide their followers/following collections if they wish.
If a user has chosen to hide their collections, then only a stub collection with `totalItems` will be returned, and you will not be able to page through the Actor's followers/following collections.
A `GET` to the following collection of an Actor with hidden collections will look like:
```json
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/someone/following",
"type": "OrderedCollection",
"totalItems": 397
}
```
Profile Fields
Like Mastodon and other fediverse softwares, GoToSocial lets users set key/value pairs on their profile; useful for conveying short pieces of information like links, pronouns, age, etc.
For the sake of compatibility with other implementations, GoToSocial uses the same schema.org PropertyValue extension that Mastodon uses, present as an attachment array value on actors that have fields set. For example, the below JSON shows an account with two PropertyValue fields:
{
"@context": [
"http://joinmastodon.org/ns",
"https://w3id.org/security/v1",
"https://www.w3.org/ns/activitystreams",
"http://schema.org"
],
"attachment": [
{
"name": "should you follow me?",
"type": "PropertyValue",
"value": "maybe!"
},
{
"name": "age",
"type": "PropertyValue",
"value": "120"
}
],
"discoverable": false,
"featured": "http://example.org/users/1happyturtle/collections/featured",
"followers": "http://example.org/users/1happyturtle/followers",
"following": "http://example.org/users/1happyturtle/following",
"id": "http://example.org/users/1happyturtle",
"inbox": "http://example.org/users/1happyturtle/inbox",
"manuallyApprovesFollowers": true,
"name": "happy little turtle :3",
"outbox": "http://example.org/users/1happyturtle/outbox",
"preferredUsername": "1happyturtle",
"publicKey": {
"id": "http://example.org/users/1happyturtle#main-key",
"owner": "http://example.org/users/1happyturtle",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTc6Jpg6LrRPhVQG4KLz\n2+YqEUUtZPd4YR+TKXuCnwEG9ZNGhgP046xa9h3EWzrZXaOhXvkUQgJuRqPrAcfN\nvc8jBHV2xrUeD8pu/MWKEabAsA/tgCv3nUC47HQ3/c12aHfYoPz3ufWsGGnrkhci\nv8PaveJ3LohO5vjCn1yZ00v6osMJMViEZvZQaazyE9A8FwraIexXabDpoy7tkHRg\nA1fvSkg4FeSG1XMcIz2NN7xyUuFACD+XkuOk7UqzRd4cjPUPLxiDwIsTlcgGOd3E\nUFMWVlPxSGjY2hIKa3lEHytaYK9IMYdSuyCsJshd3/yYC9LqxZY2KdlKJ80VOVyh\nyQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"summary": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"tag": [],
"type": "Person",
"url": "http://example.org/@1happyturtle"
}
For actors that have no PropertyValue fields set, the attachment property will not be set at all. That is, the attachment key value will not be present on the actor (not even as an empty array or null value).
While attachment is not technically an ordered collection, GoToSocial--again, in line with what other implementations do--does present attachment PropertyValue fields in the order in which they should to be displayed.
GoToSocial will also parse PropertyValue fields from remote actors discovered by the GoToSocial instance, to allow them to be displayed to users on the GoToSocial instance.
GoToSocial allows up to 6 PropertyValue fields by default, as opposed to Mastodon's default 4.
Featured (aka pinned) Posts
GoToSocial allows users to feature (or 'pin') posts on their profile.
In ActivityPub terms, GoToSocial serves these pinned posts as an OrderedCollection at the endpoint indicated in an Actor's featured field. The value of this field will be set to something like https://example.org/users/some_user/collections/featured.
By making a signed GET request to this endpoint, remote instances can dereference the featured posts collection, which will return an OrderedCollection with a list of post URIs in the orderedItems field.
Example of a featured collection of a user who has pinned multiple Notes:
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/some_user/collections/featured",
"orderedItems": [
"https://example.org/users/some_user/statuses/01GS7VTYH0S77NNXTP6W4G9EAG",
"https://example.org/users/some_user/statuses/01GSFY2SZK9TPCJFQ1WCCPGDRT",
"https://example.org/users/some_user/statuses/01GSCXY70MZCBFMH5EKJW9ENC8"
],
"totalItems": 3,
"type": "OrderedCollection"
}
Example of a user who has pinned one Note:
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/some_user/collections/featured",
"orderedItems": [
"https://example.org/users/some_user/statuses/01GS7VTYH0S77NNXTP6W4G9EAG"
],
"totalItems": 1,
"type": "OrderedCollection"
}
Example with no pinned Notes:
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/some_user/collections/featured",
"orderedItems": [],
"totalItems": 0,
"type": "OrderedCollection"
}
Unlike Mastodon and some other implementations, GoToSocial does not serve full Note representations as orderedItems values. Instead, it provides just the URI of each Note, which the remote server can then dereference (or not, if they already have the Note cached locally).
Some of the URIs served as part of the collection may point to followers-only posts which the requesting Actor won't necessarily have permission to view. Remote servers should make sure to do their own filtering (as with any other post type) to ensure that these posts are only shown to users who are permitted to view them.
Another difference between GoToSocial and other server implementations is that GoToSocial does not send updates to remote servers when a post is pinned or unpinned by a user. Mastodon does this by sending Add and Remove Activity types where the object is the post being pinned or unpinned, and the target is the sending Actor's featured collection. While this conceptually makes sense, it is not in line with what the ActivityPub protocol recommends, since the target of the Activity "is not owned by the receiving server, and thus they can't update it".
Instead, to build a view of a GoToSocial user's pinned posts, it is recommended that remote instances simply poll a GoToSocial Actor's featured collection every so often, and add/remove posts in their cached representation as appropriate.
hidesToPublicFromUnauthedWeb and hidesCcPublicFromUnauthedWeb
GoToSocial uses the properties hidesToPublicFromUnauthedWeb and hidesCcPublicFromUnauthedWeb to indicate whether an actor prefers to hide posts addressed to or cc public from unauthenticated (ie., logged-out) visitors to web pages, web apps, and web APIs.
Some background for this: many ActivityPub server softwares allow unauthenticated visitors to the profile web page of an actor to see a list of posts that an actor has created that are addressed either to or cc public. These are often called "public" posts, and "unlisted", "unlocked", or "quiet public" posts, respectively. GoToSocial provides a settings flag that allows GtS accounts to hide posts from the web view of their profile, as one layer of protection to make it more of a nuisance to scrape/stalk someone with a GtS account.
While this setting works for hiding posts of an actor on their own instance, prior to GoToSocial v0.20.0, this preference was not federated out to other instances, nor was it federated in from other instances. This leads to two problems:
- Many other fedi server softwares permit logged-out visitors, via a web app, to look up profiles of remote accounts, and to see public and unlisted posts created by those accounts. This means that it is trivial to work around the ability of GtS users to hide their posts from the web. For example, say a GtS user at
@someone@gts.example.orglocks down their profile by setting the visibility of posts on their profile to "none"; this prevents visitors togts.example.orgfrom seeing posts, but one could visit eg.mastodon.example.organd, while logged out, look up@someone@gts.example.org, and see all the posts there that have been sent to, or dereferenced by, actors onmastodon.example.org. This makes the GtS user's choice to hide their posts significantly less meaningful. - In an effort to support this extra layer of privacy, by default GoToSocial instances do not show posts from remote instances unless they are addressed
topublic. For example, if someone frommastodon.example.orgwere to reply to a post by@someone@gts.example.org, and the reply was only addressedccpublic instead oftopublic, the GtS instancegts.example.orgwould not show that reply in the web view, as it could not determine the preferences of the user frommastodon.example.orgwith regard to showing the "quiet public" post to logged-out visitors to the web page. This could be frustrating for the GtS user, as they might want to show a more complete picture of the thread that they started, right there on their instance; this could also frustrate the Mastodon user, as are used to their "quiet public" posts being visible on the web even when logged out.
The actor properties hidesToPublicFromUnauthedWeb and hidesCcPublicFromUnauthedWeb are a move towards solving these issues, by allowing actors to signal their preferences for hiding or showing to- and/or cc-public posts to unauthenticated visitors via the web.
For example, the following actor representation indicates that the actor is happy to show both "unlisted" and "public" posts via unauthed web view (this represents the de-facto default for actors on Mastodon and most other server softwares):
{
"@context": [
"https://gotosocial.org/ns",
"https://www.w3.org/ns/activitystreams"
],
"type": "Person",
[... other properties here ...]
"hidesToPublicFromUnauthedWeb": false,
"hidesCcPublicFromUnauthedWeb": false,
[... other properties here ...]
}
By contrast, the following indicates that the actor hides "unlisted" posts but is happy to show "public" posts unauthed (this is the default for actors on GtS instances):
{
"@context": [
"https://gotosocial.org/ns",
"https://www.w3.org/ns/activitystreams"
],
"type": "Person",
[... other properties here ...]
"hidesToPublicFromUnauthedWeb": false,
"hidesCcPublicFromUnauthedWeb": true,
[... other properties here ...]
}
And the following shows that the actor wants to show no posts unauthed at all:
{
"@context": [
"https://gotosocial.org/ns",
"https://www.w3.org/ns/activitystreams"
],
"type": "Person",
[... other properties here ...]
"hidesToPublicFromUnauthedWeb": true,
"hidesCcPublicFromUnauthedWeb": true,
[... other properties here ...]
}
Both hidesToPublicFromUnauthedWeb and hidesCcPublicFromUnauthedWeb are defined in the GoToSocial json-ld @context document.
In line with its emphasis on having people opt-in to greater visibility rather than opt-out, when receiving a post from a remote actor that does not set these flags, GoToSocial assumes hidesToPublicFromUnauthedWeb = false, and hidesCcPublicFromUnauthedWeb = true. That is, the pre-v0.20.x behavior of GoToSocial is still the default for remote servers that don't (yet) use these flags.
!!! note While unusual, it's possible for an actor to also specify that they want to show "unlisted" posts but hide "public" ones:
```json
{
"@context": [
"https://gotosocial.org/ns",
"https://www.w3.org/ns/activitystreams"
],
"type": "Person",
[... other properties here ...]
"hidesToPublicFromUnauthedWeb": true,
"hidesCcPublicFromUnauthedWeb": false,
[... other properties here ...]
}
```
GoToSocial respects these flags for incoming posts, but it does not let accounts set this combination of flags for outgoing posts. It may be desirable for other implementers to also prevent users from being able to set this state, as it doesn't make a lot of sense.
Actor Migration / Aliasing
GoToSocial supports account migration from one instance/server to another through a combination of the Move activity, and the Actor Object properties alsoKnownAs and movedTo.
alsoKnownAs
GoToSocial supports account aliasing using the alsoKnownAs Actor property, which is an accepted ActivityPub extension.
Incoming
On incoming AP messages, GoToSocial looks for the alsoKnownAs property on an Actor to be an array of ActivityPub IDs/URIs of other Actors by which the Actor is also known.
For example:
{
"@context": [
"http://joinmastodon.org/ns",
"https://w3id.org/security/v1",
"https://www.w3.org/ns/activitystreams",
"http://schema.org"
],
"featured": "http://example.org/users/1happyturtle/collections/featured",
"followers": "http://example.org/users/1happyturtle/followers",
"following": "http://example.org/users/1happyturtle/following",
"id": "http://example.org/users/1happyturtle",
"inbox": "http://example.org/users/1happyturtle/inbox",
"manuallyApprovesFollowers": true,
"name": "happy little turtle :3",
"outbox": "http://example.org/users/1happyturtle/outbox",
"preferredUsername": "1happyturtle",
"publicKey": {...},
"summary": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"type": "Person",
"url": "http://example.org/@1happyturtle",
"alsoKnownAs": [
"https://another-server.com/users/1happyturtle",
"https://somewhere-else.org/users/originalTurtle"
]
}
In the above AP JSON, the Actor http://example.org/users/1happyturtle is aliased to the other Actors https://another-server.com/users/1happyturtle and https://somewhere-else.org/users/originalTurtle.
GoToSocial will store incoming alsoKnownAs URIs in the database, but does not (currently) use them for anything except verifying a Move Activity (see below).
Outgoing
GoToSocial users can set multiple alsoKnownAs URIs on their account via the GoToSocial client API. GoToSocial will verify that these alsoKnownAs aliases are valid Actor URIs before storing them in the database and before serializing them in outgoing AP messages.
However, GoToSocial does not verify ownership of those alsoKnownAs URIs by the user setting the aliases before serializing them in outgoing messages; it expects remote servers to do their own verification before trusting any transmitted alsoKnownAs values.
As an example, the user http://example.org/users/1happyturtle, from their GoToSocial instance, might set alsoKnownAs: [ "https://unrelated-server.com/users/someone_else" ] on their account, and GoToSocial will duly transmit this alias to other servers.
In this case, though, https://unrelated-server.com/users/someone_else may not be the same person as 1happyturtle. 1happyturtle may have set this alias by mistake, or maliciously. To properly verify ownership of someone_else by 1happyturtle, a remote server should check that the alsoKnownAs property of the Actor https://unrelated-server.com/users/someone_else contains an entry http://example.org/users/1happyturtle.
In other words, remote servers should not trust alsoKnownAs aliases by default, and should instead ensure that a two-way alias exists between Actors before treating the alias as valid.
!!! info
The reason that GoToSocial does not perform verification of alsoKnownAs values before sending them out to other servers is to avoid a chicken and egg problem. Say that 1happyturtle and someone_else are the same person, one of the two Actors must be able to set alsoKnownAs first, so that the instance of the other Actor can begin processing the alias. If both servers prevent an unverified alias from being serialized in the alsoKnownAs property, then it becomes impossible for either 1happyturtle or someone_else to alias to one another.
movedTo
GoToSocial marks accounts as moved using the movedTo property. Unlike alsoKnownAs this is not an accepted ActivityPub extension, but it has been widely popularized by Mastodon, which also uses it in connection with the Move activity. See the Mastodon docs for more info.
Incoming
For incoming AP messages, GoToSocial looks for the movedTo property on an Actor to be set to a single ActivityPub Actor URI/ID.
For example:
{
"@context": [
"http://joinmastodon.org/ns",
"https://w3id.org/security/v1",
"https://www.w3.org/ns/activitystreams",
"http://schema.org"
],
"featured": "http://example.org/users/1happyturtle/collections/featured",
"followers": "http://example.org/users/1happyturtle/followers",
"following": "http://example.org/users/1happyturtle/following",
"id": "http://example.org/users/1happyturtle",
"inbox": "http://example.org/users/1happyturtle/inbox",
"manuallyApprovesFollowers": true,
"name": "happy little turtle :3",
"outbox": "http://example.org/users/1happyturtle/outbox",
"preferredUsername": "1happyturtle",
"publicKey": {...},
"summary": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"type": "Person",
"url": "http://example.org/@1happyturtle",
"alsoKnownAs": [
"https://another-server.com/users/1happyturtle"
],
"movedTo": "https://another-server.com/users/1happyturtle"
}
In the above JSON, the Actor http://example.org/users/1happyturtle has been aliased to the Actor https://another-server.com/users/1happyturtle and has also moved/migrated to that account.
GoToSocial stores incoming movedTo values in the database, but does not consider an account migration to have been processed unless the Actor doing the Move had previously transmitted a Move activity (see below).
Outgoing
GoToSocial will only set movedTo on outgoing Actors when an account Move has been verified and processed.
Move Activity
To actually trigger account migrations, GoToSocial uses the Move Activity with Actor URI as Object and Target, for example:
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.org/users/1happyturtle/moves/01HR9FDFCAGM7JYPMWNTFRDQE9",
"actor": "https://example.org/users/1happyturtle",
"type": "Move",
"object": "https://example.org/users/1happyturtle",
"target": "https://another-server.com/users/my_new_account_hurray",
"to": "https://example.org/users/1happyturtle/followers"
}
In the above Move, Actor https://example.org/users/1happyturtle indicates that their account is moving to the URI https://another-server.com/users/my_new_account_hurray.
Incoming
On receiving a Move activity in an Actor's Inbox, GoToSocial will first validate the Move by making the following checks:
- Request was signed by
actor. actorandobjectfields are the same (you can'tMovesomeone else's account).actorhas not already moved somewhere else.targetis a valid Actor URI: retrievable, not suspended, not already moved, and on a domain that's not defederated by the GoToSocial instance that received theMove.targethasalsoKnownAsset to theactorthat sent theMove. In this example,https://another-server.com/users/my_new_account_hurraymust have analsoKnownAsvalue that includeshttps://example.org/users/1happyturtle.
If checks pass, then GoToSocial will process the Move by redirecting followers to the new account:
- Select all followers on this GtS instance of the
actordoing theMove. - For each local follower selected in this way, send a follow request from that follower to the
targetof theMove. - Remove all follows targeting the "old"
actor.
The end result of this is that all followers of https://example.org/users/1happyturtle on the receiving instance will now be following https://another-server.com/users/my_new_account_hurray instead.
GoToSocial will also remove all follow and pending follow requests owned by the actor doing the Move; it's up to the target account to send follow requests out again.
To prevent potential DoS vectors, GoToSocial enforces a 7-day cooldown on Moves. Once an account has successfully moved, GoToSocial will not process further moves from the new account until 7 days after the previous move.
Outgoing
Outgoing account migrations use the Move Activity in much the same way. When an Actor on a GoToSocial instance wants to Move, GtS will first check and validate the Move target, and ensure it has an alsoKnownAs entry equal to the Actor doing the Move. On successful validation, a Move message will be sent out to all of the moving Actor's followers, indicating the target of the Move. GoToSocial expects remote instances to transfer the actor's followers to the target.