Federated forge server
Clone
HTTPS:
git clone https://vervis.peers.community/repos/rjQ3E
SSH:
git clone USERNAME@vervis.peers.community:rjQ3E
Branches
Tags
API.md
This document describes the Client-to-Server API of Vervis. If you’re developing a frontend application, or a forge search engine, or anything else that wants to interact with Vervis instances, and wondering what language Vervis speaks, this is the document for you.
I suppose in the future it should sit on a Docusaurus website or something like that. For now it’s here.
Public Browsing
The /browse
page lists the public actors and resource hosted on the server. However there’s currently no AP version of that page.
Server Information
NodeInfo isn’t implemented yet.
Registration and Authorization
Creating an account on a Vervis instance allows to:
- Create and manipulate resources (such as projects, repositories, teams)
- View non-public information
Register the application
Register client
Send a POST request to the /oauth/apps
endpoint:
curl -X POST \
-F 'client_name=Anvil' \
-F 'redirect_uris=urn:ietf:wg:oauth:2.0:oob' \
-F 'scopes=read' \
-F 'website=https://anvil.forgefed.org' \
-F 'repository=https://codeberg.org/Anvil/Anvil' \
https://vervis.example/oauth/apps
redirect_uris
currently supports specifying only one URI, and there are 2 options:- The special value
urn:ietf:wg:oauth:2.0:oob
, which means out-of-band: In this case, Vervis will respond with an HTML page containing a token that needs to be manually copied - An HTTPS URI: In this case, Vervis will redirect to the given URI
- The special value
- The only supported scope is currently
read
, and despite the misleading name, it allows all API operations
The response, upon success, is a JSON object with 2 text fields:
client_id
client_secret
Keep these stored for future use.
Obtain an Application Access Token
Send a POST request to the /oauth/token
endpoint:
curl -X POST \
-F 'client_id=your_client_id_here' \
-F 'client_secret=your_client_secret_here' \
-F 'redirect_uri=urn:ietf:wg:oauth:2.0:oob' \
-F 'grant_type=client_credentials' \
https://vervis.example/oauth/token
redirect_uri
must be the one defined when registering the application
The response is a JSON object with:
access_token
: Text, keep for later usetoken_type
: Text, always “Bearer”scope
: Text, always “read”created_at
: Integer, time as POSIX seconds
Verify the Application Access Token
You can verify the token works by sending a GET request to the /oauth/verify_credentials
endpoint:
curl \
-H 'Authorization: Bearer our_access_token_here' \
https://vervis.example/oauth/verify_credentials
Register an Account
This section isn’t implemented yet. I’m about to implement it. Putting the API here for review while I’m coding.
Check if registration is enabled
Send a GET request to the /register/enabled
endpoint. A 2xx response indicates it’s enabled, otherwise it’s disabled.
Check username availability
Send a GET request to the /register/available
endpoint:
curl -X GET \
-H 'Authorization: Bearer our_application_access_token_here' \
-F 'username=alice' \
https://vervis.example/register/available
A 2xx response indicates the username is available.
Create a new account
Send a POST request to the /register
endpoint:
curl -X POST \
-H 'Authorization: Bearer our_application_access_token_here' \
-F 'username=alice' \
-F 'passphrase=R6GQJ9HqLtRQ58' \
-F 'email=alice@email.example' \
https://vervis.example/register
A 2xx response indicates the account has been created. A JSON object is returned, with a boolean email_sent
field. If true, a verification email has been sent to the specified email address. If false, it means email verification is disabled on this server, and the account is ready to be used.
Verify account
The email contains a token, which you can send via a POST request to the /register/verify
endpoint, in order to verify and enable the account:
curl -X POST \
-H 'Authorization: Bearer our_application_access_token_here' \
-F 'username=alice' \
-F 'token=pRiW8ayeuN7UBW4qAKg9qRBE0DUVCIof' \
https://vervis.example/register/verify
A 2xx response indicates successful verification.
Log in as Existing User
Obtain authorization code
In a browser, send a GET request to the /oauth/authorize
endpoint:
https://vervis.example/oauth/authorize
?client_id=CLIENT_ID
&scope=read
&redirect_uri=urn:ietf:wg:oauth:2.0:oob
&response_type=code
- If
redirect_uri
is the special one as in the example above, the response will be an HTML page containing the authorization code. - Otherwise, Vervis will redirect to the
redirect_uri
, specifying the authorization code as a query parameter named “code”:
redirect_uri?code=qDFUEaYrRK5c-HNmTCJbAzazwLRInJ7VHFat0wcMgCU
Obtain a User Access Token
Now that we have the code, send a POST request to the /oauth/token
endpoint (which we previously used when registering the application):
curl -X POST \
-F 'client_id=your_client_id_here' \
-F 'client_secret=your_client_secret_here' \
-F 'redirect_uri=urn:ietf:wg:oauth:2.0:oob' \
-F 'grant_type=authorization_code' \
-F 'code=user_authzcode_here' \
-F 'scope=read' \
https://vervis.example/oauth/token
The response is a JSON object with:
access_token
: Text, keep for later usetoken_type
: Text, always “Bearer”scope
: Text, always “read”created_at
: Integer, time as POSIX seconds
Verify the User Access Token & Obtain Actor Object
You can verify the token works by sending a GET request to the /oauth/verify_credentials
endpoint:
curl \
-H 'Authorization: Bearer our_access_token_here' \
https://vervis.example/oauth/verify_credentials
The response is a JSON object that has a url
field, whose value is the HTTPS URI of the user’s Person
ActivityPub JSON object.
Perform Authorized Requests
You can now use the access token via the Authorization
header, as in the curl example above.
- For GET requests, it allows to obtain non-public data
- For POST requests, it allows to publish ActivityPub activities via the user outbox
Getting ActivityPub objects
You can obtain an ActivityPub object by sending GET request to its id
URI, with Content-Type
being application/ld+json; profile="https://www.w3.org/ns/activitystreams
. Unless you’ve been given such a URI, the starting points for discovering objects are:
- Public browsing page (which doesn’t yet have an AP representation)
- The user
Person
object, whose URI can be obtained from/oauth/verify_credentials
as described above
The Person Object
{
"id": "https://fig.fr33domlover.site/people/vDxKn",
"type": "Person",
"preferredUsername": "perelev",
"summary": "Cool person who makes cool stuff",
"inbox": "https://fig.fr33domlover.site/people/vDxKn/inbox",
"outbox": "https://fig.fr33domlover.site/people/vDxKn/outbox",
"followers": "https://fig.fr33domlover.site/people/vDxKn/followers",
"following": "https://fig.fr33domlover.site/people/vDxKn/following",
"sshKey": [
"https://fig.fr33domlover.site/people/vDxKn/ssh-keys/Pn9Yn"
]
}
Receiving Messages
Requests and event notifications are received as ActivityPub Activity
objects in the Person’s inbox
collection. Currently push notifications aren’t implemented, so client applications need to periodically GET the collection and detect whether new items have appeared at the top. The inbox is a (typically paged) reverse-chronologically OrderedCollection
of Activity
objects, as described in the ActivityPub specification.
The ForgeFed specification has a relevant detailed section:
https://forgefed.org/spec/#s2s
Common properties
These would appear in every activity:
id
: ID URI of the activitytype
: One of the ActivityPub or ForgeFed activity typesactor
: ID URI of the actor who published this activity
These would appear in some activities:
capability
: ID URI of aGrant
activity serving as authorizationfulfills
: If the activity was published by an automated process rather than human command, these are ID URI(s) of the activities, to which the automated process was reacting
{
"id": "https://fig.fr33domlover.site/decks/W058b/outbox/nV34D",
"type": "Accept",
"actor": "https://fig.fr33domlover.site/decks/W058b",
"fulfills": [
"https://grape.fr33domlover.site/people/WZpnG/outbox/GQvnR"
],
"object": "https://grape.fr33domlover.site/people/WZpnG/outbox/GQvnR"
}
Accept
The actor
has accepted/approved some activity.
Always:
object
: URI of the activity being accepted
Sometimes:
result
: URI of some new entity created as a result of accepting theobject
Add
The actor
has requested to add some actor to an authorization-related collection. The cases are everything except adding direct collaborators (which use an Invite
activity instead):
- Add a componet to a project
- Add a component/project to a team
- Create a parent-child link between a pair of projects/teams
Always:
object
: URI of object being addedtarget
: URI of the collectioninstrument
: Maximal role (seeRole
type in ForgeFed spec) of authorizations that will be passed through this newly formed link
To determine the case, grab the target
collection’s owning actor, pointed via the collection’s context
field. Now you can example the target
and object
actor types, as well as which field of the target
actor specifies the collection:
components
of aProject
context
(i.e. projects) of a componentsubteams
of aTeam
context
(i.e. parents) of aTeam
subprojects
of aProject
context
(i.e. parents) of aProject
teams
of a component/Project
teamResources
of aTeam
The Add
activity is usually just the first step in a sequence of activities that create the desired authorization link. The activity sequences are described in detail in the specification, e.g. in these sections:
- https://forgefed.org/spec/#associating-projects-and-components
- https://forgefed.org/spec/#adding-and-removing-children-and-parents-to-projects-and-teams
Apply
Create
An actor has published a new object/resource, specified in the object
field:
- A comment on an issue/PR (
Note
) - An issue (
Ticket
) - A component (
TicketTracker
,PatchTracker
,Repository
) Team
Project
Follow
The actor
has requested to follow the actor/object specified by the object
field.
Grant
See https://forgefed.org/spec/#managing-access.
Invite
https://forgefed.org/spec/#Invite
Except target
specifies the collection, not the resource itself. For a Team
, that would be the URI of its members
collection. For other actor types, it would be the URI of the collaborators
collection.
Join
https://forgefed.org/spec/#Join
Except object
specifies the collection, not the resource itself. For a Team
, that would be the URI of its members
collection. For other actor types, it would be the URI of the collaborators
collection.
Offer
Push
https://forgefed.org/spec/#pushing
Reject
Remove
Resolve
Revoke
Undo
Publishing and Manipulating Objects
All object manipulation in Vervis is done using the ActivityPub C2S API, i.e. by POSTing Activity
objects to the user’s outbox
.
To determine the outbox URI, you can HTTP GET the Person object as mentioned above, and grab the URI specified by its outbox
field.
Common properties
There are properties you’d often specify in the Activity
object, that aren’t specific to any activity type.
type
:Activity
actor
: URI of the person actorcapability
: URI of aGrant
activity you’ve received, as authorization for the action you’re requestingto
,cc
,bto
,bcc
: Audience, i.e. list of URIs of actors and actor collections
Accept
Add
Create
Follow
Invite
Join
Offer
Remove
Resolve
Undo
S2S Sequences to Move to the Spec
Using a Factory
Documented in my Vikunja task board)
Issue tracker migration
This involves 2 pieces that happen independently, one can happen without the other:
- Signaling: A tracker says “I moved to a new location, find me there”
- Copying: A tracker is created as a copy of an existing, possibly remote tracker
For signalling:
- Old tracker sends a
Move
activity - New tracker points to old one as an alias, e.g. using
alsoKnownAs
For anonymous copying:
- Client downloads F3 data of some existing tracker, sends it when creating the new tracker
For authenticated copying:
- Server downloads F3 data from old tracker, and officially specifies “I was created as a copy of such and such tracker”
From all this, I’m now implementing only authenticated copying:
- Much like the
forkedFrom
property, let’s have a property specifying a data origin for a new resource actor that isn’t a repo - let’s useorigin
for that. In theCreate
, theorigin
refers to theFactory
, but let’s haveorigin
in theTicketTracker
itself, which refers to the data source, a local or remote tracker. - The author of all the issues and comments will be the new tracker itself
- Every issue and comment uses
origin
to point to the source - The Factory just creates a new Deck, and the Deck, upon being created, does the copying in its
init
method