2024-07-04 18:45:10 +02:00
|
|
|
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:
|
|
|
|
|
|
|
|
1. Create and manipulate resources (such as projects, repositories, teams)
|
|
|
|
2. View non-public information
|
|
|
|
|
|
|
|
## Register the application
|
|
|
|
|
|
|
|
### Register client
|
|
|
|
|
|
|
|
Send a POST request to the `/oauth/apps` endpoint:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
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 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:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
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 use
|
|
|
|
- `token_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:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
curl \
|
|
|
|
-H 'Authorization: Bearer our_access_token_here' \
|
|
|
|
https://vervis.example/oauth/verify_credentials
|
|
|
|
```
|
|
|
|
|
|
|
|
## Register an Account
|
|
|
|
|
2024-07-05 20:38:00 +02:00
|
|
|
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:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
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:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
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
|
|
|
|
|
2024-07-05 22:27:35 +02:00
|
|
|
The email contains a token, which you can send via a POST request to the
|
2024-07-05 20:38:00 +02:00
|
|
|
`/register/verify` endpoint, in order to verify and enable the account:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
curl -X POST \
|
|
|
|
-H 'Authorization: Bearer our_application_access_token_here' \
|
2024-07-05 22:27:35 +02:00
|
|
|
-F 'username=alice' \
|
2024-07-05 20:38:00 +02:00
|
|
|
-F 'token=pRiW8ayeuN7UBW4qAKg9qRBE0DUVCIof' \
|
|
|
|
https://vervis.example/register/verify
|
|
|
|
```
|
|
|
|
|
|
|
|
A 2xx response indicates successful verification.
|
2024-07-04 18:45:10 +02:00
|
|
|
|
|
|
|
## 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):
|
|
|
|
|
|
|
|
```sh
|
|
|
|
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 use
|
|
|
|
- `token_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:
|
|
|
|
|
|
|
|
```sh
|
|
|
|
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
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
# The Person Object
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"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.
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
The ForgeFed specification has a relevant detailed section:
|
|
|
|
|
|
|
|
<https://forgefed.org/spec/#s2s>
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Common properties
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
These would appear in every activity:
|
2024-07-18 23:54:16 +02:00
|
|
|
|
|
|
|
- `id`: ID URI of the activity
|
|
|
|
- `type`: One of the ActivityPub or ForgeFed activity types
|
|
|
|
- `actor`: ID URI of the actor who published this activity
|
|
|
|
|
|
|
|
These would appear in some activities:
|
|
|
|
|
|
|
|
- `capability`: ID URI of a `Grant` activity serving as authorization
|
|
|
|
- `fulfills`: 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
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"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
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
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 the
|
|
|
|
`object`
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Add
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
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 added
|
|
|
|
- `target`: URI of the collection
|
|
|
|
- `instrument`: Maximal role (see `Role` 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 a `Project`
|
|
|
|
- `context` (i.e. projects) of a component
|
|
|
|
- `subteams` of a `Team`
|
|
|
|
- `context` (i.e. parents) of a `Team`
|
|
|
|
- `subprojects` of a `Project`
|
|
|
|
- `context` (i.e. parents) of a `Project`
|
|
|
|
- `teams` of a component/`Project`
|
|
|
|
- `teamResources` of a `Team`
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Apply
|
|
|
|
|
|
|
|
## Create
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
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`
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Follow
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
The `actor` has requested to follow the actor/object specified by the `object`
|
|
|
|
field.
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Grant
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
See <https://forgefed.org/spec/#managing-access>.
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Invite
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
<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.
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Join
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
<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.
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Offer
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
- <https://forgefed.org/spec/#opening-issue>
|
|
|
|
- <https://forgefed.org/spec/#opening-mr>
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Push
|
|
|
|
|
2024-07-19 17:08:44 +02:00
|
|
|
<https://forgefed.org/spec/#pushing>
|
|
|
|
|
2024-07-18 23:54:16 +02:00
|
|
|
## Reject
|
|
|
|
|
|
|
|
## Remove
|
|
|
|
|
|
|
|
## Resolve
|
|
|
|
|
|
|
|
## Revoke
|
|
|
|
|
|
|
|
## Undo
|
|
|
|
|
2024-07-04 18:45:10 +02:00
|
|
|
# Publishing and Manipulating Objects
|
|
|
|
|
|
|
|
All object manipulation in Vervis is done using the ActivityPub C2S API, i.e.
|
2024-07-18 23:54:16 +02:00
|
|
|
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 actor
|
|
|
|
- `capability`: URI of a `Grant` activity you've received, as authorization for
|
|
|
|
the action you're requesting
|
|
|
|
- `to`, `cc`, `bto`, `bcc`: Audience, i.e. list of URIs of actors and actor
|
|
|
|
collections
|
|
|
|
|
|
|
|
## Accept
|
|
|
|
|
|
|
|
## Add
|
|
|
|
|
|
|
|
## Create
|
|
|
|
|
|
|
|
## Follow
|
|
|
|
|
|
|
|
## Invite
|
|
|
|
|
|
|
|
## Join
|
|
|
|
|
|
|
|
## Offer
|
|
|
|
|
|
|
|
## Remove
|
|
|
|
|
|
|
|
## Resolve
|
|
|
|
|
|
|
|
## Undo
|