mirror of
https://github.com/versia-pub/docs.git
synced 2025-12-06 06:18:19 +01:00
docs: ♻️ Rewrite various docs pages, add null fields everywhere they were missing, make some Note and User fields mandatory
This commit is contained in:
parent
e6f7a27d3e
commit
e9b5ccd76c
|
|
@ -11,23 +11,44 @@ Versia defines a very simple API for server-to-server communication, mainly fetc
|
|||
|
||||
Every Versia API endpoint is prefixed with `/.versia/vX/`, where `X` is the version of the API. The current version is `0.6`, so the current API prefix is `/.versia/v0.6/`. This versioning is used to avoid breaking changes in the future, and to allow for backwards compatibility.
|
||||
|
||||
Requests not encrypted with TLS (aka HTTPS) **MUST NOT** be sent or responded to. All implementations are required to use TLS 1.2 or higher, and to support HTTP/2.
|
||||
Requests not encrypted with TLS (aka HTTPS) **must not** be sent or responded to. All implementations are required to use TLS 1.2 or higher, and to support HTTP/2.
|
||||
|
||||
<Note>
|
||||
Implementations have no obligations to support more than one Versia version. It is recommended to always support the latest version, as it helps avoid bogging down the network with old versions.
|
||||
Implementations have no obligation to support more than one Versia version. It is recommended to always support the latest version, as it helps avoid bogging down the network with old versions.
|
||||
</Note>
|
||||
|
||||
## Signatures
|
||||
|
||||
- All API requests **MUST** be signed, unless otherwise specified. This includes `POST` and `GET` requests.
|
||||
- All API responses **MUST** be signed, unless otherwise specified.
|
||||
|
||||
The signature mechanism is defined in the [Signatures](/signatures) document.
|
||||
API requests/responses between instances **must** include a [Signature](/signatures), signed with the [instance's private key](/entities/instance-metadata), as defined in [Signatures](/signatures).
|
||||
|
||||
## Encoding
|
||||
|
||||
"URL-encoding" is the mechanism used to encode data containing special characters in URLs. When this specification refers to "URL-encoding", it means the encoding defined in [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1).
|
||||
|
||||
## Domain
|
||||
|
||||
Versia defines a **domain** as the hostname of an instance, which is used to identify the instance in the network. It **must** be a valid hostname, and cannot include a port number (the HTTPS port is always 443).
|
||||
|
||||
Defining a domain is very complicated, so please refer to the following examples:
|
||||
|
||||
``` {{ "title": "Valid Domains" }}
|
||||
example.com
|
||||
test.localhost
|
||||
sus.example.org
|
||||
xn--ls8h.la
|
||||
1.0.1.7.0.8.0.0.0.0.7.4.0.1.0.0.2.ip6.arpa # We know what you did
|
||||
```
|
||||
|
||||
``` {{ "title": "Invalid Domains" }}
|
||||
example.com:3000 # ❌️ Invalid: port number is not allowed
|
||||
test..localhost # ❌️ Invalid: double dot is not allowed
|
||||
example.com. # ❌️ Invalid: trailing dot is not allowed
|
||||
test.org/ # ❌️ Invalid: trailing slash is not allowed
|
||||
test.org/sus # ❌️ Invalid: path is not allowed
|
||||
💩.la # ❌️ Invalid: IDNs must be Punycode encoded
|
||||
https://bob.org # ❌️ Invalid: protocol is not allowed
|
||||
```
|
||||
|
||||
## Redirects
|
||||
|
||||
API endpoints **MUST NOT** redirect to other endpoints, with the following exceptions:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export const metadata = {
|
|||
Must be `GET`.
|
||||
</Property>
|
||||
<Property name="response">
|
||||
Must be the instance's metadata, as defined in the [Instance Metadata](/entities/instance-metadata) document.
|
||||
Instance's metadata, as defined in the [Instance Metadata](/entities/instance-metadata) document.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ export const metadata = {
|
|||
Must be `GET`.
|
||||
</Property>
|
||||
<Property name="response">
|
||||
Must be the entity's data as JSON, as defined in its [Entity](/entities) definition document.
|
||||
Entity data as JSON, as defined in its [Entity](/entities) definition document.
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
|
@ -151,9 +151,9 @@ Accept: application/vnd.versia+json
|
|||
|
||||
## Inbox
|
||||
|
||||
The inbox endpoint is used for other instances to send entities to this instance. It is a single endpoint that can receive messages for every user (also known as a shared inbox).
|
||||
The inbox endpoint is used for instances to send entities to each other. It is a single endpoint that can receive messages for every user (also known as a shared inbox).
|
||||
|
||||
The delivery mechanism is described further in the [Federation](/federation) document.
|
||||
The delivery mechanism is described further in the [Federation](/federation#inboxes) document.
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ This page lists changes since Working Draft 3. {{ className: 'lead' }}
|
|||
- Added [References](/types#reference) as a way to refer to other entities.
|
||||
- Mandated the usage of `null` for optional fields that are not set, instead of omitting them.
|
||||
- Changed usage of "`null` authors" to simply being an optional field.
|
||||
- Added more guidelines on [JSON data handling](/json).
|
||||
- Clarified the meaning of [Domain](/api/basics#domain) in the context of Versia.
|
||||
- Changes to [Note](/entities/note):
|
||||
- `is_sensitive`, `attachments`, `mentions` and `previews` are now required.
|
||||
- Changes to [User](/entities/user):
|
||||
- `fields`, `manually_approves_followers` and `indexable` are now required.
|
||||
- Removed "short pronoun" from [Vanity Extension](/extensions/vanity) (union types are tricky to parse in strongly typed languages).
|
||||
|
||||
## Since WD 4
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ export const metadata = {
|
|||
|
||||
# Delete
|
||||
|
||||
Signals the deletion of an entity. It is a [**Transient Entity**](/entities#transient-entities). {{ className: 'lead' }}
|
||||
Signals the deletion of an entity. {{ className: 'lead' }}
|
||||
|
||||
## Authorization
|
||||
|
||||
Implementations **must** ensure that the author of the `Delete` entity has the authorization to delete the target entity.
|
||||
|
||||
Having the authorization is defined as:
|
||||
- The author is the creator of the target entity (including [delegation](/federation/delegation)).
|
||||
- The author is the creator of the target entity (including [delegation](/extensions/delegation)).
|
||||
- The author is the instance.
|
||||
|
||||
## Entity Definition
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ Contains metadata about a Versia instance, such as capabilities and endpoints. {
|
|||
<Property name="description" type="string" required={false}>
|
||||
Long description of the instance, for humans. Should be around 100-200 words.
|
||||
</Property>
|
||||
<Property name="host" type="string" required={true}>
|
||||
Hostname of the instance. Includes the port if it is not the default (i.e. `443` for HTTPS).
|
||||
<Property name="domain" type="Domain" typeLink="/api/basics#domain" required={true}>
|
||||
Domain of the instance, used for federation.
|
||||
</Property>
|
||||
<Property name="public_key" type="PublicKey" required={true}>
|
||||
Public key of the instance.
|
||||
|
|
@ -96,7 +96,7 @@ Contains metadata about a Versia instance, such as capabilities and endpoints. {
|
|||
]
|
||||
},
|
||||
"description": "Server for Jim's Jolly Jimjams, a social network for fans of Jimjams.",
|
||||
"host": "social.jimjams.com",
|
||||
"domain": "social.jimjams.com",
|
||||
"logo": {
|
||||
"image/png": {
|
||||
"content": "https://social.jimjams.com/files/logo.png"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Notes represent a piece of content on a Versia instance. They can be posted by [
|
|||
<Row>
|
||||
<Col>
|
||||
<Properties name="Note">
|
||||
<Property name="attachments" type="ContentFormat[]" required={false} typeLink="/structures/content-format">
|
||||
<Property name="attachments" type="ContentFormat[]" required={true} typeLink="/structures/content-format">
|
||||
Media attachments to the note. May be any format. **Must** be remote.
|
||||
</Property>
|
||||
<Property name="author" type="Reference" required={true} typeLink="/types#reference">
|
||||
|
|
@ -61,13 +61,13 @@ Notes represent a piece of content on a Versia instance. They can be posted by [
|
|||
If the implementation does not support the [Groups Extension](/extensions/groups), any value other than `public` or `followers` should be treated as `null`.
|
||||
</Note>
|
||||
</Property>
|
||||
<Property name="is_sensitive" type="boolean" required={false}>
|
||||
<Property name="is_sensitive" type="boolean" required={true}>
|
||||
Whether the note contains "sensitive content". This can be used with `subject` as a "content warning" feature.
|
||||
</Property>
|
||||
<Property name="mentions" type="Reference[]" required={false} typeLink="/types#reference">
|
||||
<Property name="mentions" type="Reference[]" required={true} typeLink="/types#reference">
|
||||
[References](/types#reference) to [Users](/entities/user) that should be notified of the note. Similar to Twitter's `@` mentions. The note may also contain mentions in the content, however only the mentions in this field should trigger notifications.
|
||||
</Property>
|
||||
<Property name="previews" type="LinkPreview[]" required={false}>
|
||||
<Property name="previews" type="LinkPreview[]" required={true}>
|
||||
Previews for any links in the publication. This is to avoid the [stampeding mastodon problem](https://github.com/mastodon/mastodon/issues/23662) where a link preview is fetched by every instance that sees the publication, creating an accidental DDOS attack.
|
||||
|
||||
```typescript
|
||||
|
|
@ -108,6 +108,9 @@ Notes represent a piece of content on a Versia instance. They can be posted by [
|
|||
"content": "https://cdn.versia.social/29e810bf4707fef373d886af322089d5db300fce66e4e073efc26827f10825f6/image.webp",
|
||||
"remote": true,
|
||||
"thumbhash": "1QcSHQRnh493V4dIh4eXh1h4kJUI",
|
||||
"description": null,
|
||||
"fps": null,
|
||||
"duration": null,
|
||||
"height": 960,
|
||||
"size": 221275,
|
||||
"hash": {
|
||||
|
|
@ -120,6 +123,12 @@ Notes represent a piece of content on a Versia instance. They can be posted by [
|
|||
"text/plain": {
|
||||
"content": "https://cdn.lysand.org/68c02dd11c179ef4d170b05393f6e72133dd0ad733f40d41b42363d8784e8d5d/fire.txt",
|
||||
"remote": true,
|
||||
"description": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
"size": 8,
|
||||
"hash": {
|
||||
"sha256": "68c02dd11c179ef4d170b05393f6e72133dd0ad733f40d41b42363d8784e8d5d"
|
||||
|
|
@ -150,7 +159,10 @@ Notes represent a piece of content on a Versia instance. They can be posted by [
|
|||
"group": "public",
|
||||
"is_sensitive": false,
|
||||
"mentions": [],
|
||||
"subject": "Versia development"
|
||||
"subject": "Versia development",
|
||||
"previews": [],
|
||||
"quotes": null,
|
||||
"replies_to": null
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ export const metadata = {
|
|||
|
||||
# Entities
|
||||
|
||||
Entities are the foundation of the Versia protocol. A similar concept to entities are the [ActivityStreams](https://www.w3.org/TR/activitystreams-core/) objects, which are used to represent activities in the [ActivityPub](https://www.w3.org/TR/activitypub/) protocol. {{ className: 'lead' }}
|
||||
Entities are the foundation of the Versia protocol. They are similar to: {{ className: 'lead' }}
|
||||
|
||||
## Entity Definition
|
||||
- [ActivityStreams](https://www.w3.org/TR/activitystreams-core/) objects
|
||||
- [Matrix](https://matrix.org/) events.
|
||||
|
||||
## Definition
|
||||
|
||||
An entity is a simple JSON object that represents a core data structure in Versia. Entities are used to represent various types of data, such as users, notes, and more. Each entity has a unique `id` property that is used to identify it within the instance.
|
||||
|
||||
|
|
@ -31,11 +34,11 @@ Any field in an entity not marked as `required` may be set to `null`. These fiel
|
|||
<Property name="type" type="string" required={true}>
|
||||
Type of the entity. Custom types must follow [Extension Naming](/extensions#naming).
|
||||
</Property>
|
||||
<Property name="created_at" type="RFC3339" required={true} typeLink="/types#rfc3339">
|
||||
<Property name="created_at" type="RFC3339" required={true} typeLink="/types#rfc-3339">
|
||||
Date and time when the entity was created. Must be an [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) timestamp.
|
||||
|
||||
<Note>
|
||||
Handling of dates that are valid but obviously incorrect (e.g. in the future) is left to the Implementation's discretion.
|
||||
Handling of dates that are valid but obviously incorrect (e.g. in the future) is left to the Implementation's discretion. Sending a [Note](/entities/note) created on `13-12-1337` is very funny, but don't be surprised if it is rejected.
|
||||
</Note>
|
||||
</Property>
|
||||
<Property name="$schema" type="string" required={false}>
|
||||
|
|
@ -86,14 +89,6 @@ Any field in an entity not marked as `required` may be set to `null`. These fiel
|
|||
|
||||
## Transient Entities
|
||||
|
||||
Some entities are transient, meaning they cannot be refetched using the [Federation API](/api/endpoints). These entities are used for actions that do not require a permanent record, such as deletions or migrations. Transient Entities do not have an `id` field.
|
||||
Some entities are **transient**, meaning they cannot be fetched using the [Federation API](/api/endpoints). These entities are used for actions that do not require a permanent record, such as deletions or migrations. Transient Entities do not have an `id` field.
|
||||
|
||||
Implementations **must not** rely on other implementations to store transient entities in their database.
|
||||
|
||||
## Serialization
|
||||
|
||||
When serialized to a string, the JSON representation of an entity must follow the following rules:
|
||||
- Keys must be sorted lexicographically.
|
||||
- Must use UTF-8 encoding.
|
||||
- Must be **signed** using the the [instance's private key](/entities/instance-metadata).
|
||||
- Must use Unix-style `\n` line endings (LF).
|
||||
|
|
@ -35,7 +35,7 @@ Usernames can be changed by the user, so it is recommended to use `id` for long-
|
|||
|
||||
### Instance
|
||||
|
||||
Instance **must** be the host of the instance the user is on (hostname with optional port).
|
||||
Instance **must** be the [Domain](/api/basics#domain) that the user is on.
|
||||
|
||||
## Entity Definition
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ Instance **must** be the host of the instance the user is on (hostname with opti
|
|||
<Property name="display_name" type="string" required={false}>
|
||||
Display name, as shown to other users. May contain emojis and any Unicode character.
|
||||
</Property>
|
||||
<Property name="fields" type="Field[]" required={false}>
|
||||
<Property name="fields" type="Field[]" required={true}>
|
||||
Custom key/value pairs. For example, metadata like socials or pronouns. Must be text format (`text/*`).
|
||||
|
||||
```typescript
|
||||
|
|
@ -70,11 +70,11 @@ Instance **must** be the host of the instance the user is on (hostname with opti
|
|||
<Property name="header" type="ContentFormat" required={false} typeLink="/structures/content-format">
|
||||
A header image for the user's profile. Also known as a cover photo or a banner. Must be an image format (`image/*`).
|
||||
</Property>
|
||||
<Property name="manually_approves_followers" type="boolean" required={false}>
|
||||
If `true`, the user must approve any new followers manually. If `false`, followers are automatically approved. This does not affect federation, and is meant to be used for clients to display correct UI. Defaults to `false`.
|
||||
<Property name="manually_approves_followers" type="boolean" required={true}>
|
||||
If `true`, the user must approve any new followers manually. If `false`, followers are automatically approved. This does not affect federation, and is meant to be used for clients to display correct UI.
|
||||
</Property>
|
||||
<Property name="indexable" type="boolean" required={false}>
|
||||
User consent to be indexed by search engines. If `false`, the user's profile should not be indexed. Defaults to `true`.
|
||||
<Property name="indexable" type="boolean" required={true}>
|
||||
User consent to be indexed by search engines. If `false`, the user's profile should not be indexed.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,13 @@ The Custom Emojis extension adds support for adding personalized emojis to feder
|
|||
"content": "https://cdn.example.com/emojis/happy_face.webp",
|
||||
"remote": true,
|
||||
"description": "A happy emoji smiling.",
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -95,7 +102,8 @@ Custom Emojis can be added to any entity with text content. The extension ID is
|
|||
"content": "Hello, world :happy_face:!"
|
||||
}
|
||||
},
|
||||
"extensions": { // [!code focus:16]
|
||||
...
|
||||
"extensions": { // [!code focus:23]
|
||||
"pub.versia:custom_emojis": {
|
||||
"emojis": [
|
||||
{
|
||||
|
|
@ -105,6 +113,13 @@ Custom Emojis can be added to any entity with text content. The extension ID is
|
|||
"content": "https://cdn.example.com/emojis/happy_face.webp",
|
||||
"remote": true,
|
||||
"description": "A happy emoji smiling.",
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Refer to [Note](/entities/note#entity-definition)'s `group` property for how not
|
|||
|
||||
Text only (`text/plain`, `text/html`, etc).
|
||||
</Property>
|
||||
<Property name="open" type="boolean" required={false}>
|
||||
<Property name="open" type="boolean" required={true}>
|
||||
Whether the group is open to all users or requires approval to join.
|
||||
|
||||
<Note>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ The entity defined in this document must be inserted in the `pub.versia:interact
|
|||
"content": "Hello, world :happy_face:!"
|
||||
}
|
||||
},
|
||||
...
|
||||
"extensions": { // [!code focus:9]
|
||||
"pub.versia:interaction_controls": {
|
||||
"reply": {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ Extensions **must not** be designed in a way that makes them required to underst
|
|||
|
||||
Versia extension names are composed of two parts:
|
||||
- The domain name of the extension author, in reverse order. Example: `pub.versia`
|
||||
- The extension name, separated by a colon. `snake_case`. Example: `likes`
|
||||
- The extension name, separated by a colon, in `snake_case`. Example: `likes`
|
||||
|
||||
``` {{ title: "Example Extension Name" }}
|
||||
pub.versia:likes
|
||||
|
|
@ -30,7 +30,7 @@ pub.versia:likes
|
|||
### Custom entities
|
||||
|
||||
Custom entities are named in the same way, but with an additional part:
|
||||
- The entity name, separated by a slash. `PascalCase`. Example: `Like`
|
||||
- The entity name, separated by a slash, in `PascalCase`. Example: `Like`
|
||||
|
||||
``` {{ title: "Example Custom Entity Type" }}
|
||||
pub.versia:likes/Like
|
||||
|
|
@ -49,9 +49,7 @@ Extensions can be found in two places: an [Entity](/entities#entity-definition)'
|
|||
Custom extensions to the entity.
|
||||
|
||||
- `key`: The extension name.
|
||||
- `value`: Extension data. Can be any JSON-serializable data.
|
||||
|
||||
Extension data can be any JSON-serializable data.
|
||||
- `value`: Extension data. Can be any JSON object, following [Versia JSON rules](/json).
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
|
@ -63,6 +61,7 @@ Extensions can be found in two places: an [Entity](/entities#entity-definition)'
|
|||
"id": "ed480922-b095-4f09-9da5-c995be8f5960",
|
||||
"name": null,
|
||||
"description": null,
|
||||
"open": true,
|
||||
"extensions": { // [!code focus:100]
|
||||
"com.example:gps": {
|
||||
"location": {
|
||||
|
|
@ -85,7 +84,7 @@ Extensions can be found in two places: an [Entity](/entities#entity-definition)'
|
|||
<Col>
|
||||
<Properties name="CustomEntity">
|
||||
<Property name="type" type="string" required={true}>
|
||||
The extension type. [Must follow naming conventions](#naming).
|
||||
The extension type, [following naming conventions](#naming).
|
||||
</Property>
|
||||
<Property name="other">
|
||||
Other properties of the custom entity. These are specific to the extension, and should be documented by the extension author.
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Note that there is no `question` field: the question should be included in the `
|
|||
<Property name="multiple_choice" type="boolean" required="true">
|
||||
Whether the poll allows multiple votes to be cast for different options.
|
||||
</Property>
|
||||
<Property name="expires_at" type="RFC3339" typeLink="/types#rfc3339">
|
||||
<Property name="expires_at" type="RFC3339" typeLink="/types#rfc-3339">
|
||||
RFC 3339 timestamp of when the poll ends and no more votes can be cast. If not present, the poll does not expire.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
|
@ -49,6 +49,7 @@ Note that there is no `question` field: the question should be included in the `
|
|||
"content": "What is your favourite color?"
|
||||
}
|
||||
},
|
||||
...
|
||||
"extensions": { // [!code focus:28]
|
||||
"pub.versia:polls": {
|
||||
"options": [
|
||||
|
|
|
|||
|
|
@ -42,13 +42,10 @@ All properties are optional.
|
|||
Audio format (e.g. `audio/mpeg`).
|
||||
</Property>
|
||||
<Property name="pronouns" type="{ [key: LanguageCode]: Pronoun[] }" required={false}>
|
||||
An array of internationalized pronouns the user uses. Can be represented as a string or an object.
|
||||
An array of internationalized pronouns the user uses.
|
||||
|
||||
```typescript
|
||||
/* e.g. "he/him" */
|
||||
type ShortPronoun = string;
|
||||
|
||||
interface LongPronoun {
|
||||
interface Pronoun {
|
||||
subject: string;
|
||||
object: string;
|
||||
dependent_possessive: string;
|
||||
|
|
@ -56,12 +53,11 @@ All properties are optional.
|
|||
reflexive: string;
|
||||
}
|
||||
|
||||
type Pronoun = ShortPronoun | LongPronoun;
|
||||
/* Example: en-US or fr */
|
||||
type LanguageCode = string;
|
||||
```
|
||||
</Property>
|
||||
<Property name="birthday" type="RFC3339" required={false} typeLink="/types#rfc3339">
|
||||
<Property name="birthday" type="RFC3339" required={false} typeLink="/types#rfc-3339">
|
||||
User's birthday. If year is left out or set to `0000`, implementations **SHOULD** not display the year.
|
||||
</Property>
|
||||
<Property name="location" type="string" required={false}>
|
||||
|
|
@ -92,6 +88,14 @@ All properties are optional.
|
|||
"image/png": {
|
||||
"content": "https://cdn.example.com/ab5081cf-b11f-408f-92c2-7c246f290593/cat_ears.png",
|
||||
"remote": true,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -99,23 +103,53 @@ All properties are optional.
|
|||
"image/png": {
|
||||
"content": "https://cdn.example.com/d8c42be1-d0f7-43ef-b4ab-5f614e1beba4/rounded_square.jpeg",
|
||||
"remote": true,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"image/png": {
|
||||
"content": "https://cdn.example.com/6492ddcd-311e-4921-9567-41b497762b09/untitled-file-0019822.png",
|
||||
"remote": true,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
},
|
||||
"audio": {
|
||||
"audio/mpeg": {
|
||||
"content": "https://cdn.example.com/4da2f0d4-4728-4819-83e4-d614e4c5bebc/michael-jackson-thriller.mp3",
|
||||
"remote": true,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
},
|
||||
"pronouns": {
|
||||
"en-us": [
|
||||
"he/him",
|
||||
{
|
||||
"subject": "he",
|
||||
"object": "him",
|
||||
"dependent_possessive": "his",
|
||||
"independent_possessive": "his",
|
||||
"reflexive": "himself"
|
||||
},
|
||||
{
|
||||
"subject": "they",
|
||||
"object": "them",
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ Accept: application/jrd+json
|
|||
|
||||
## Instance Discovery
|
||||
|
||||
Instaance metadata can be accessed by making a `GET` request to the instance's Versia metadata endpoint, documented in the [Endpoints](/api/endpoints#instance-metadata) document.
|
||||
Instance metadata can be accessed by making a `GET` request to the instance's Versia metadata endpoint, documented in the [Endpoints](/api/endpoints#instance-metadata) document.
|
||||
|
||||
### Example
|
||||
|
||||
|
|
@ -80,7 +80,10 @@ Accept: application/vnd.versia+json
|
|||
"pub.versia:reports"
|
||||
]
|
||||
},
|
||||
"host": "versia.social",
|
||||
"created_at": "2021-07-01T00:00:00Z"
|
||||
"domain": "versia.social",
|
||||
"created_at": "2021-07-01T00:00:00Z",
|
||||
"description": null,
|
||||
"logo": null,
|
||||
"banner": null
|
||||
}
|
||||
```
|
||||
|
|
@ -50,7 +50,7 @@ curl https://b.social/.well-known/webfinger?resource=acct:joe@b.social -H "Accep
|
|||
<Note>
|
||||
In a real Versia implementation, usernames would **not** be included in user profile's URL, as they can be changed. Instead, the `id` could be used.
|
||||
|
||||
This is done for simplicity in this example.
|
||||
This is done for brevity here.
|
||||
</Note>
|
||||
|
||||
### Fetching the User
|
||||
|
|
@ -58,10 +58,10 @@ curl https://b.social/.well-known/webfinger?resource=acct:joe@b.social -H "Accep
|
|||
`a.social` fetches the user profile of `joe` from `b.social` using the URL provided in the WebFinger response.
|
||||
|
||||
```bash
|
||||
# The request is signed by a.social's instance private key
|
||||
curl https://b.social/.versia/entities/User/joe \
|
||||
-H "Accept: application/vnd.versia+json" \
|
||||
-H "User-Agent: CoolServer/1.0 (https://coolserver.com)" \
|
||||
# The request is signed by a.social's instance private key
|
||||
-H "Versia-Signature: /CjB2L9bcvRg+uP19B4/rqy7Ji9/cqMFPlL3GVCIndnQjYyOpBzJEAl9weDnXm7Jrqa3y6sBC+EYWKThO2r9Bw==" \
|
||||
-H "Versia-Signed-By: a.social" \
|
||||
-H "Versia-Signed-At: 1729241687"
|
||||
|
|
@ -71,17 +71,30 @@ curl https://b.social/.versia/entities/User/joe \
|
|||
|
||||
```json
|
||||
{
|
||||
"id": "bde22zi3ca8762", // [!code focus:10]
|
||||
"id": "bde22zi3ca8762", // [!code focus:3]
|
||||
"type": "User",
|
||||
"created_at": "2024-10-13T18:48:19Z",
|
||||
"avatar": {
|
||||
"image/webp": {
|
||||
"content": "https://cdn.b.social/avatars/joe.webp",
|
||||
"remote": true
|
||||
"remote": true,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
}, // [!code focus:3]
|
||||
"display_name": "Joe Swanson (Winter Arc :gigachad:)",
|
||||
"username": "joe",
|
||||
"bio": null,
|
||||
"header": null,
|
||||
"fields": [],
|
||||
"manually_approves_followers": false,
|
||||
"indexable": true,
|
||||
"extensions": {
|
||||
"pub.versia:custom_emojis": {
|
||||
"emojis": [
|
||||
|
|
@ -90,7 +103,15 @@ curl https://b.social/.versia/entities/User/joe \
|
|||
"content": {
|
||||
"image/png": {
|
||||
"content": "https://cdn.b.social/emojis/gigachad.png",
|
||||
"remote": true
|
||||
"remote": true,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -112,21 +133,44 @@ Finally, `a.social` serializes the note to send it to `joe`.
|
|||
"type": "Note",
|
||||
"created_at": "2024-12-01T12:19:06Z",
|
||||
"author": "alice",
|
||||
"category": "microblog", // [!code focus:11]
|
||||
"category": "microblog",
|
||||
"content": {
|
||||
"text/html": {
|
||||
"text/html": { // [!code focus:12]
|
||||
"content": "Hello, <a class=\"u-url mention\" href=\"https://b.social/users/bde22zi3ca8762\">@joe@b.social</a>! How are you doing today?",
|
||||
"remote": false,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
},
|
||||
"text/plain": {
|
||||
"content": "Hello, @joe@b.social! How are you doing today?",
|
||||
"remote": false,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
},
|
||||
"group": "public",
|
||||
"mentions": [ // [!code focus:3]
|
||||
"b.social:bde22zi3ca8762"
|
||||
]
|
||||
],
|
||||
"attachments": [],
|
||||
"is_sensitive": false,
|
||||
"previews": [],
|
||||
"replies_to": null,
|
||||
"quotes": null,
|
||||
"subject": null,
|
||||
"device": null,
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export const metadata = {
|
|||
|
||||
Versia uses the HTTP protocol for all communications between instances. HTTP requests must conform to certain standards to ensure compatibility between different implementations, as well as to ensure the security and integrity of the data being exchanged.
|
||||
|
||||
ALL kinds of HTTP requests/responses between instances **MUST** include a [Signature](/signatures), signed with either the relevant [User](/entities/user)'s private key or the [instance's private key](/entities/instance-metadata).
|
||||
HTTP requests/responses between instances **must** include a [Signature](/signatures), signed with the [instance's private key](/entities/instance-metadata), as defined in [Signatures](/signatures).
|
||||
|
||||
## Requests
|
||||
|
||||
|
|
@ -16,22 +16,26 @@ ALL kinds of HTTP requests/responses between instances **MUST** include a [Signa
|
|||
<Col>
|
||||
<Properties name="HTTP Request">
|
||||
<Property name="Accept" type="string" required={true}>
|
||||
Must include `application/vnd.versia+json`, unless specified otherwise.
|
||||
Must be `application/vnd.versia+json`, unless specified otherwise. Basic `application/json` is **not** allowed.
|
||||
</Property>
|
||||
<Property name="Content-Type" type="string" required={true}>
|
||||
Must include `application/vnd.versia+json; charset=utf-8`, if the request has a body.
|
||||
Must be `application/vnd.versia+json; charset=utf-8` if the request has a body.
|
||||
</Property>
|
||||
<Property name="Versia-Signature" type="string" required={false}>
|
||||
<Property name="Versia-Signature" type="string" required={true}>
|
||||
See [Signatures](/signatures) for more information.
|
||||
</Property>
|
||||
<Property name="Versia-Signed-By" type="URI" required={false} typeLink="/types#uri">
|
||||
<Property name="Versia-Signed-By" type="Domain" required={true} typeLink="/api/basics#domain">
|
||||
See [Signatures](/signatures).
|
||||
</Property>
|
||||
<Property name="Versia-Signed-At" type="string" required={false}>
|
||||
<Property name="Versia-Signed-At" type="number" required={true}>
|
||||
See [Signatures](/signatures).
|
||||
</Property>
|
||||
<Property name="User-Agent" type="string" required={false}>
|
||||
A string identifying the software making the request.
|
||||
A string identifying the software making the request. Should contain the name of the software, its version, and a link to its homepage.
|
||||
|
||||
``` {{ 'title': 'Example User-Agent' }}
|
||||
CoolServer/1.0 (https://coolserver.com)
|
||||
```
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
|
@ -50,9 +54,9 @@ ALL kinds of HTTP requests/responses between instances **MUST** include a [Signa
|
|||
|
||||
## Rate limits
|
||||
|
||||
Implementations **MUST** respect the rate limits of remote instances.
|
||||
Implementations **must** respect the rate limits of remote instances.
|
||||
|
||||
IETF draft [draft-polli-ratelimit-headers-02](https://www.ietf.org/archive/id/draft-polli-ratelimit-headers-02.html) **MUST** be used to communicate rate limits. Other rate limit headers/formats are not allowed.
|
||||
IETF draft [draft-polli-ratelimit-headers-02](https://www.ietf.org/archive/id/draft-polli-ratelimit-headers-02.html) **must** be used to communicate rate limits. Other rate limit headers/formats are not allowed.
|
||||
|
||||
<Note>
|
||||
This IETF draft is, well, a draft. However, there are no standards for rate limiting in HTTP, so this is the best we have.
|
||||
|
|
@ -66,13 +70,13 @@ IETF draft [draft-polli-ratelimit-headers-02](https://www.ietf.org/archive/id/dr
|
|||
<Property name="Content-Type" type="string" required={true}>
|
||||
Must include `application/vnd.versia+json; charset=utf-8`, unless specified otherwise.
|
||||
</Property>
|
||||
<Property name="Versia-Signature" type="string" required={false}>
|
||||
<Property name="Versia-Signature" type="string" required={true}>
|
||||
See [Signatures](/signatures) for more information.
|
||||
</Property>
|
||||
<Property name="Versia-Signed-By" type="URI" required={false} typeLink="/types#uri">
|
||||
<Property name="Versia-Signed-By" type="Domain" required={true} typeLink="/api/basics#domain">
|
||||
See [Signatures](/signatures).
|
||||
</Property>
|
||||
<Property name="Versia-Signed-At" type="string" required={false}>
|
||||
<Property name="Versia-Signed-At" type="number" required={true}>
|
||||
See [Signatures](/signatures).
|
||||
</Property>
|
||||
</Properties>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Ever [Instance](/entities/instance-metadata) has a personal HTTP endpoint called
|
|||
|
||||
Let's consider the following example:
|
||||
|
||||
> Alice, on the instance `alice.example`, sends a message to Bob on the instance `bob.example`.
|
||||
Alice, on the instance `alice.example`, sends a message to Bob on the instance `bob.example`.
|
||||
|
||||
To perform this action, Alice's instance sends a `POST` request to Bob's instance's inbox endpoint. This request contains the [Note](/entities/note) entity that Alice wants to send to Bob, with [the appropriate metadata](/federation/http) to ensure the message is valid.
|
||||
|
||||
|
|
@ -39,4 +39,12 @@ Bob's instance receives the message and processes it according to the rules defi
|
|||
|
||||
In addition to inboxes, every user has an outbox (at `/.versia/v0.6/entities/User/<id>/collections/outbox`). The outbox is simply a [Collection](/structures/collection) of all the messages that a user has sent. When a user sends a message to another user, a copy of that message is accessible in the sender's outbox.
|
||||
|
||||
<Warning>
|
||||
Implementations **should** filter out entities that are not relevant to the request author when returning the outbox.
|
||||
|
||||
For example, if Alice sends a [Note](/entities/note) to Bob on `direct` visibility:
|
||||
- Alice's instance **should** return the [Note](/entities/note) in response to a request from Bob's instance.
|
||||
- Alice's instance **should not** display the [Note](/entities/note) in her outbox in response to a request from Joe's instance.
|
||||
</Warning>
|
||||
|
||||
Outboxes are very useful for "backfilling" data when a new instance joins the network. By resolving the outboxes of all new users it encounters, a new instance can quickly catch up on all old messages.
|
||||
|
|
@ -6,7 +6,7 @@ export const metadata = {
|
|||
|
||||
# Validation
|
||||
|
||||
Implementations **MUST** strictly validate all incoming data to ensure that it is well-formed and adheres to the Versia Protocol. If a request is invalid, the instance **MUST** return a `400 Bad Request` HTTP status code.
|
||||
Implementations **MUST** strictly validate all incoming data to ensure that it is well-formed and adheres to the Versia Protocol. If a request is invalid, the instance **MUST** return a `422 Unprocessable Entity` HTTP status code.
|
||||
|
||||
<Note>
|
||||
Remember that while *your* implementation may disallow or restrict some user input, other implementations may not. You **should not** apply those restrictions to data coming from other instances.
|
||||
|
|
@ -16,13 +16,13 @@ Implementations **MUST** strictly validate all incoming data to ensure that it i
|
|||
|
||||
Things that should be validated include, but are not limited to:
|
||||
|
||||
- The presence of **all required fields**.
|
||||
- The presence of **all required fields**, and the presence of `null` on optional fields.
|
||||
- The **format** of all fields (integers should not be strings, timestamps should be in RFC 3339 format, etc.).
|
||||
- The presence of **all required headers**.
|
||||
- The presence of a **valid signature**.
|
||||
- The **length** of all fields (for example, the `username` field on a `User` entity) should be at least 1 character long.
|
||||
- Do not set arbitrary limits on the length of fields that other instances may send you. For example, a `bio` field should not be limited to 160 characters, even if your own implementation has such a limit.
|
||||
- If you do set limits, they should be reasonable, well-documented and should allow Users to easily view the remote original, by, for example, linking to it.
|
||||
- If you do set limits, they should be reasonable, well-documented and should allow Users to easily view the remote original, by, for example, linking to it. A limit of `100 000` characters for a `bio` field is acceptable, but a limit of `160` characters is not.
|
||||
- The **type**, **precision** and **scale** of all numeric fields.
|
||||
- For example, a `size` field on a `ContentFormat` structure should be a positive integer, not a negative number or a floating-point number.
|
||||
<Note>
|
||||
|
|
@ -30,7 +30,7 @@ Things that should be validated include, but are not limited to:
|
|||
|
||||
Using the same type with a higher bit count, for example using a u128 instead of a u64, is acceptable. Beware of performance impacts this may cause.
|
||||
</Note>
|
||||
- The **validity** of all URLs and URIs (run them through your favorite URL parser, optionally fetch the linked URL).
|
||||
- The **validity** of all URLs and URIs (run them through your favorite URL parser).
|
||||
- The **time** of all dates and times (people should not be born in the future, or in the year 0).
|
||||
|
||||
It is your implementation's duty to reject data from other instances that does not adhere to the strict spec. **This is crucial to ensure the integrity of your instance and the network as a whole**. Allowing data that is technically valid but semantically incorrect can lead to the degradation of the entire Versia ecosystem.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { HeroPattern } from '@/components/HeroPattern'
|
|||
|
||||
export const metadata = {
|
||||
title: 'Versia Documentation',
|
||||
description: 'Introduction to the Versia Protocol, a communication medium for federated applications, leveraging the HTTP stack.',
|
||||
description: 'Introduction to the Versia Protocol, a communication medium for federated applications, using the HTTP stack.',
|
||||
}
|
||||
|
||||
export const sections = [
|
||||
|
|
@ -16,7 +16,7 @@ export const sections = [
|
|||
|
||||
# Versia Federation Protocol
|
||||
|
||||
The Versia Protocol is designed as a communication medium for federated applications, leveraging the HTTP stack. Its simplicity ensures ease of implementation and comprehension. {{ className: 'lead' }}
|
||||
The Versia Protocol is designed as a communication medium for federated applications, using the HTTP stack. Its simplicity ensures ease of implementation and comprehension. {{ className: 'lead' }}
|
||||
|
||||
<div className="not-prose mb-16 mt-6 flex gap-3">
|
||||
<Button href="/entities" arrow="right">
|
||||
|
|
@ -45,9 +45,8 @@ The Versia Protocol uses the following terms:
|
|||
|
||||
The Versia Protocol is heavily inspired by the [ActivityPub](https://www.w3.org/TR/activitypub/) specification. It is designed to be simple and easy to implement, with a focus on the following concepts:
|
||||
- **Simple Structures**: Entities are represented as JSON objects. No JSON-LD, no complex data structures, just plain JSON.
|
||||
- As structures are kept simple, they are easy to parse with strongly-typed languages like Rust.
|
||||
- **Modularity**: The protocol is divided into a **core protocol** and **extensions**. Implementations can choose to support only the core protocol or add extensions as needed.
|
||||
- **Namespacing**: To avoid extension conflicts, all extensions are namespaced.
|
||||
- **Signatures**: Most types of interactions **must** be signed with a private key. Unlike other protocols, signatures are **mandatory**, not optional.
|
||||
- **Developer-Friendliness**: Understanding and implementing your own Versia server should be easy. Documentation is aimed at developers first.
|
||||
|
||||
<Resources />
|
||||
|
|
|
|||
119
app/json/page.mdx
Normal file
119
app/json/page.mdx
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
export const metadata = {
|
||||
title: 'JSON',
|
||||
description:
|
||||
'Guidelines on JSON handling and serialization',
|
||||
}
|
||||
|
||||
# JSON
|
||||
|
||||
Identical data structures can be represented in many different ways in JSON. This document describes the rules that must be followed when using JSON in the Versia protocol.
|
||||
|
||||
## Encoding
|
||||
|
||||
All JSON data **must** be encoded in UTF-8. This is the only encoding supported by the Versia protocol.
|
||||
|
||||
Line endings must be in Unix-style `\n` (LF) format. The use of Windows-style `\r\n` (CRLF) line endings is not allowed.
|
||||
|
||||
## Keys
|
||||
|
||||
Keys in JSON must be strings, cannot be empty, and may only contain the following characters:
|
||||
- `a-z`
|
||||
- `A-Z`
|
||||
- `0-9`
|
||||
- `-`, `_`, `.`, `#`, `/`, `:`
|
||||
|
||||
Duplicate keys are **not** allowed.
|
||||
|
||||
``` json {{ "title": "Valid Keys" }}
|
||||
{
|
||||
"valid-key": "value",
|
||||
"valid_key": "value",
|
||||
"valid.key": "value",
|
||||
"valid#key": "value",
|
||||
"valid/key": "value",
|
||||
"valid:key": "value"
|
||||
}
|
||||
```
|
||||
|
||||
```json {{ "title": "Invalid Keys" }}
|
||||
{
|
||||
"invalid key": "value", // ❌️ Invalid: space is not allowed
|
||||
"invalid@key": "value", // ❌️ Invalid: @ is not allowed
|
||||
"": "value", // ❌️ Invalid: empty key is not allowed
|
||||
9: "value", // ❌️ How did you even serialize this??
|
||||
}
|
||||
```
|
||||
|
||||
## Values
|
||||
|
||||
### Numbers
|
||||
|
||||
Numbers in JSON must be represented as decimal numbers. They can be integers or floating-point numbers, but must follow the following rules:
|
||||
|
||||
```json {{ "title": "Must not use scientific notation" }}
|
||||
{
|
||||
"value": 1e3, // ❌️ Invalid
|
||||
"value2": 1000 // ✅ Valid
|
||||
}
|
||||
```
|
||||
|
||||
```json {{ "title": "Must not have leading zeros" }}
|
||||
{
|
||||
"value": 01, // ❌️ Invalid
|
||||
"value2": 1 // ✅ Valid
|
||||
}
|
||||
```
|
||||
|
||||
```json {{ "title": "Must not have trailing decimal points" }}
|
||||
{
|
||||
"value": 1., // ❌️ Invalid
|
||||
"value2": 1 // ✅ Valid
|
||||
}
|
||||
```
|
||||
|
||||
```json {{ "title": "Must not have trailing zeros after the decimal point" }}
|
||||
{
|
||||
"value": 1.00, // ❌️ Invalid
|
||||
"value2": 1.0, // ❌️ Invalid
|
||||
"value3": 1 // ✅ Valid
|
||||
}
|
||||
```
|
||||
|
||||
```json {{ "title": "Must not have leading decimal points" }}
|
||||
{
|
||||
"value": .1, // ❌️ Invalid
|
||||
"value2": 0.1 // ✅ Valid
|
||||
}
|
||||
```
|
||||
|
||||
```json {{ "title": "Must not use numbers outside the range -2⁵³ to 2⁵³ (included)." }}
|
||||
{
|
||||
"value": 18014398509481984, // ❌️ Invalid
|
||||
"value2": -20873930 // ✅ Valid
|
||||
}
|
||||
```
|
||||
|
||||
```json {{ "title": "Must not use the negative zero" }}
|
||||
{
|
||||
"value": -0, // ❌️ Invalid
|
||||
"value2": 0 // ✅ Valid
|
||||
}
|
||||
```
|
||||
|
||||
```json {{ "title": "Must not use Infinity or NaN" }}
|
||||
{
|
||||
"value": Infinity, // ❌️ Invalid
|
||||
"value2": NaN // ❌️ Invalid
|
||||
}
|
||||
```
|
||||
|
||||
```json {{ "title": "Must not use hexadecimal or octal notation" }}
|
||||
{
|
||||
"value": 0x1A, // ❌️ Invalid
|
||||
"value2": 0o17 // ❌️ Invalid
|
||||
}
|
||||
```
|
||||
|
||||
### Optional Fields
|
||||
|
||||
Fields that are not marked as **required** may be set to `null`. These fields **must not** be omitted: they must be present with a `null` value if not set.
|
||||
|
|
@ -32,8 +32,8 @@ Versia Links are a way to reference entities in the Versia Protocol, in a way th
|
|||
- `share/:instance/:id`: Share a note.
|
||||
- `compose/:text`: Compose a new note. Text passed at the end will be added in the note compose field. Used for "Share with Versia" buttons on websites.
|
||||
</Property>
|
||||
<Property name="instance" type="string" required={false}>
|
||||
Instance hosting the referenced content, including the port if it is not the default (i.e. `443` for HTTPS).
|
||||
<Property name="instance" type="Domain" typeLink="/api/basics#domain" required={false}>
|
||||
Instance hosting the referenced content.
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
|
|
@ -57,7 +57,7 @@ Versia Links are a way to reference entities in the Versia Protocol, in a way th
|
|||
|
||||
Versia clients **should** register themselves as handlers for the `web+versia://` scheme in the user's operating system. When a Versia Link is clicked, the client should open the entity in the client's interface.
|
||||
|
||||
The default client ("frontend") on a Versia instance **should** also display Versia links for logged-out users on:
|
||||
The default web client ("frontend") on a Versia instance **should** also display Versia links for logged-out users on:
|
||||
- Profiles
|
||||
- Notes
|
||||
- Groups
|
||||
|
|
|
|||
25
app/page.tsx
25
app/page.tsx
|
|
@ -10,7 +10,7 @@ import type { FC } from "react";
|
|||
export const metadata: Metadata = {
|
||||
title: "Versia Documentation",
|
||||
description:
|
||||
"Introduction to the Versia Protocol, a communication medium for federated applications, leveraging the HTTP stack.",
|
||||
"Introduction to the Versia Protocol, a communication medium for federated applications, using the HTTP stack.",
|
||||
};
|
||||
|
||||
const Page: FC = () => {
|
||||
|
|
@ -225,29 +225,6 @@ const Page: FC = () => {
|
|||
},
|
||||
]}
|
||||
/>
|
||||
<TeamMember
|
||||
name="Anna"
|
||||
username="devminer"
|
||||
avatarUrl="https://i.imgur.com/grHNY7G.png"
|
||||
bio="Golang SDK, spec design."
|
||||
socials={[
|
||||
{
|
||||
name: "Website",
|
||||
icon: "bx:link",
|
||||
url: "https://devminer.xyz/",
|
||||
},
|
||||
{
|
||||
name: "GitHub",
|
||||
icon: "bxl:github",
|
||||
url: "https://github.com/TheDevMinerTV",
|
||||
},
|
||||
{
|
||||
name: "Matrix",
|
||||
icon: "simple-icons:matrix",
|
||||
url: "https://matrix.to/#/@devminer:devminer.xyz",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 id="team">Thanks</h2>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,6 @@ Developer-facing features (e.g. shared inboxes, reply backfilling, etc.) should
|
|||
|
||||
## Branching
|
||||
|
||||
There should be **as little branching as possible**. This is reminiscent of the "one canonical way" principle: if there are multiple ways to do something, or a thing can be processed multiple ways, it should be simplified.
|
||||
There should be **as little branching logic as possible**. This is reminiscent of the "one canonical way" principle: if there are multiple ways to do something, or a thing can be processed multiple ways, it should be simplified.
|
||||
|
||||
If there is a need for branching, nesting should be avoided: branches should be as flat as possible.
|
||||
If there is a need for branching, nesting should be avoided: logic branches should be as flat as possible.
|
||||
|
|
@ -16,7 +16,7 @@ export const metadata = {
|
|||
|
||||
Versia's security model is designed to ensure the integrity and authenticity of data, with a simple but robust system of cryptographic signatures. This system is designed to be easy to implement and understand, while still providing strong guarantees.
|
||||
|
||||
However, **it is of critical importance to understand the limitations of this system**. Versia's security model is designed to prevent impersonation attacks and data tampering, but it does not provide confidentiality. This means that while you can trust the data you receive, you should not assume that it is private.
|
||||
However, **it is very important to understand the limitations of this system**. Versia's security model is designed to prevent impersonation attacks and data tampering, but it does not provide confidentiality. This means that while you can trust the data you receive, you should not assume that it is private.
|
||||
|
||||
<Note>
|
||||
There are three main kinds of security that are commonly discussed in the context of cryptography:
|
||||
|
|
@ -47,13 +47,12 @@ There are several reasons why confidentiality is not covered as part of this spe
|
|||
- Email
|
||||
- The Web
|
||||
- Twitter
|
||||
- TCP
|
||||
|
||||
Confidentiality is **several orders of magnitude more complex** than integrity and authenticity. It requires a completely different set of tools and assumptions, which are not feasible to reliably implement in a complex federated system (if you've ever seen `** Unable to decrypt: The sender's device has not sent us the keys for this message. **`, you know what we're talking about).
|
||||
|
||||
Furthermore, adding more complex cryptography would make the specification significantly more complex, which would likely make it harder to implement for smaller developers with less resources.
|
||||
|
||||
Additionally, we would like to avoid re-creating what would essentially be a shoddier version of much stronger centralized platforms like [Signal](https://signal.org/). We feel, just like many others before us, that it is better to leave the confidentiality to the experts.
|
||||
Additionally, we would like to avoid re-creating what would essentially be a shoddier version of much stronger centralized platforms like [Signal](https://signal.org/). We feel that it is better to leave the confidentiality to the experts.
|
||||
|
||||
## Conclusion
|
||||
|
||||
|
|
|
|||
|
|
@ -18,15 +18,15 @@ Versia uses cryptographic signatures to ensure the integrity and authenticity of
|
|||
|
||||
A signature consists of a series of headers in an HTTP request. The following headers are used:
|
||||
- **`Versia-Signature`**: The signature itself, encoded in base64.
|
||||
- **`Versia-Signed-By`**: Hostname of the instance that signed the request. This **MUST** be a hostname reachable over HTTPS (including port number if applicable), and **MUST NOT** be a URL.
|
||||
- **`Versia-Signed-At`**: The current Unix timestamp, in seconds (no milliseconds), when the request was signed. Timezone must be UTC, like all Unix timestamps.
|
||||
- **`Versia-Signed-By`**: [Domain](/api/basics#domain) of the instance authoring the request.
|
||||
- **`Versia-Signed-At`**: The current Unix timestamp, in seconds (integer), when the request was signed. Timezone must be UTC, like all Unix timestamps.
|
||||
|
||||
Signatures are **required on ALL federation traffic**. If a request does not have a signature, it **MUST** be rejected. Specifically, signatures must be put on:
|
||||
- **All POST requests**.
|
||||
- **All GET requests**.
|
||||
- **All responses to GET requests** (for example, when fetching a user's profile). In this case, the HTTP method used in the signature string must be `GET`.
|
||||
Signatures must be put on:
|
||||
- **All `POST` requests**.
|
||||
- **All `GET` requests**.
|
||||
- **All responses to `GET` requests** (for example, when fetching a user's profile).
|
||||
|
||||
If a signature fails, is missing or is invalid, the instance **MUST** return a `401 Unauthorized` HTTP status code. If the signature timestamp is too old or too new (more than 5 minutes from the current time), the instance **MUST** return a `422 Unprocessable Entity` status code.
|
||||
If a signature fails, is missing or is invalid, the instance **must** return a `401 Unauthorized` HTTP status code. If the signature is too old or too new (more than 5 minutes from the current time), the instance **must** return a `422 Unprocessable Entity` status code.
|
||||
|
||||
### Calculating the Signature
|
||||
|
||||
|
|
@ -36,12 +36,12 @@ $0 $1 $2 $3
|
|||
```
|
||||
|
||||
Where:
|
||||
- `$0` is the HTTP method (e.g. `GET`, `POST`) in lowercase.
|
||||
- `$1` is the path of the request, in standard URI format (don't forget to URL-encode it).
|
||||
- `$2` is the Unix timestamp when the request was signed, in UTC seconds.
|
||||
- `$0` is the HTTP method (e.g. `GET`, `POST`) in lowercase. If signing a *response*, use the method of the original request.
|
||||
- `$1` is the request pathname, URL-encoded.
|
||||
- `$2` is the Unix timestamp when the request was signed, in UTC seconds (integer).
|
||||
- `$3` is the SHA-256 hash of the request body, encoded in base64. (if it's a `GET` request, this should be the hash of an empty string)
|
||||
|
||||
Sign this string using the user's private key. The resulting signature should be encoded in base64.
|
||||
Sign this string using the instance's private key with the [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) algorithm. The resulting bytes **must** be encoded in base64.
|
||||
|
||||
Example:
|
||||
```
|
||||
|
|
@ -50,12 +50,12 @@ post /notes 1729243417 n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
|
|||
|
||||
### Verifying the Signature
|
||||
|
||||
To verify a signature, the instance must:
|
||||
To verify a signature, the verifying instance must:
|
||||
- Recreate the string as described above.
|
||||
- Extract the signature provided in the `Versia-Signature` header.
|
||||
- Check that the `Versia-Signed-At` timestamp is within 5 minutes of the current time.
|
||||
- Decode the signature from base64.
|
||||
- Perform a signature verification using the user's public key.
|
||||
- Decode the signature from base64 to the raw bytes.
|
||||
- Perform a signature verification using the sender instance's public key.
|
||||
|
||||
### Example
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ The following example is written in TypeScript using the WebCrypto API.
|
|||
}
|
||||
```
|
||||
|
||||
Bob can be found at `https://bob.com/.versia/v0.6/entities/User/bf44e6ad-7c0a-4560-9938-cf3fd4066511`. His instance's ed25519 private key, encoded in Base64 PKCS8, is `MC4CAQAwBQYDK2VwBCIEILrNXhbWxC/MhKQDsJOAAF1FH/R+Am5G/eZKnqNum5ro`.
|
||||
Bob can be found at `https://bob.com/.versia/v0.6/entities/User/bf44e6ad-7c0a-4560-9938-cf3fd4066511`. His instance's ed25519 private key, encoded in Base64 [PKCS8](https://en.wikipedia.org/wiki/PKCS_8), is `MC4CAQAwBQYDK2VwBCIEILrNXhbWxC/MhKQDsJOAAF1FH/R+Am5G/eZKnqNum5ro`.
|
||||
|
||||
Here's how Bob would sign the request:
|
||||
```typescript
|
||||
|
|
@ -131,7 +131,7 @@ On Alice's side, she would verify the signature using Bob's instance's public ke
|
|||
const method = request.method.toLowerCase();
|
||||
const path = new URL(request.url).pathname;
|
||||
const signature = request.headers.get("Versia-Signature");
|
||||
const timestamp = request.headers.get("Versia-Signed-At");
|
||||
const timestamp = Number(request.headers.get("Versia-Signed-At")) * 1000; // Convert to milliseconds
|
||||
|
||||
// Check if timestamp is within 5 minutes of the current time
|
||||
if (Math.abs(Date.now() - timestamp) > 300_000) {
|
||||
|
|
@ -160,10 +160,10 @@ if (!isVerified) {
|
|||
|
||||
## Exporting the Public Key
|
||||
|
||||
Public keys are always encoded using `base64` and must be in SPKI format. You will need to look up the appropriate method for your cryptographic library to convert the key to this format.
|
||||
Public keys are always encoded using `base64` and must be in [SPKI](https://en.wikipedia.org/wiki/Simple_public-key_infrastructure) format. You will need to look up the appropriate method for your cryptographic library to convert the key to this format.
|
||||
|
||||
<Note>
|
||||
This is **not** the same as the key's raw bytes.
|
||||
This is **not** merely the key's raw bytes encoded as base64. You must export the key in [SPKI](https://en.wikipedia.org/wiki/Simple_public-key_infrastructure) format, *then* encode it as base64.
|
||||
|
||||
This is also not the commonly used "PEM" format.
|
||||
</Note>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ Collections are a way to represent paginated groups of entities. They are used e
|
|||
"type": "Note",
|
||||
"uri": "https://versia.social/notes/456df8ed-daf1-4062-abab-491071c7b8dd",
|
||||
"created_at": "2024-04-09T01:38:51.743Z",
|
||||
...
|
||||
"collections": {
|
||||
"replies": "https://versia.social/notes/456df8ed-daf1-4062-abab-491071c7b8dd/replies",
|
||||
"quotes": "https://versia.social/notes/456df8ed-daf1-4062-abab-491071c7b8dd/quotes"
|
||||
|
|
|
|||
|
|
@ -127,7 +127,9 @@ It is a good idea to provide at least two versions of an image (if possible): on
|
|||
},
|
||||
"thumbhash": "3OcRJYB4d3h/iIeHeEh3eIhw+j2w",
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
"height": 1080,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -136,15 +138,39 @@ It is a good idea to provide at least two versions of an image (if possible): on
|
|||
{
|
||||
"text/plain": {
|
||||
"content": "The consequences of today are determined by the actions of the past. To change your future, alter your decisions today.",
|
||||
"remote": false
|
||||
"remote": false,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
},
|
||||
"text/markdown": {
|
||||
"content": "> The consequences of today are determined by the actions of the past.\n> To change your future, alter your decisions today.",
|
||||
"remote": false
|
||||
"remote": false,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
},
|
||||
"text/html": {
|
||||
"content": "<blockquote><p>The consequences of today are determined by the actions of the past.</p><p>To change your future, alter your decisions today.</p></blockquote>",
|
||||
"remote": false
|
||||
"remote": false,
|
||||
"description": null,
|
||||
"size": null,
|
||||
"hash": null,
|
||||
"thumbhash": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"fps": null,
|
||||
"duration": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ A **reference** is a way to refer to any entity within the Versia network. It is
|
|||
For example: `[2001:db8::1]:3000`.
|
||||
</Warning>
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
These two examples are equivalent if the instance is `example.com`:
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { Heading } from "./Heading";
|
|||
|
||||
const libraries = [
|
||||
{
|
||||
href: "https://github.com/versia-pub/api/tree/main/federation",
|
||||
name: "@versia/federation",
|
||||
href: "https://github.com/versia-pub/server/tree/main/packages/sdk",
|
||||
name: "@versia/sdk",
|
||||
description:
|
||||
"Fully-featured federation toolkit with validation, signatures, parsing, and more.",
|
||||
logo: logoTypescript,
|
||||
|
|
|
|||
|
|
@ -250,6 +250,7 @@ export const navigation: NavGroup[] = [
|
|||
{ title: "Introduction", href: "/introduction" },
|
||||
{ title: "SDKs", href: "/sdks" },
|
||||
{ title: "Entities", href: "/entities" },
|
||||
{ title: "JSON", href: "/json" },
|
||||
{ title: "Signatures", href: "/signatures" },
|
||||
{ title: "Security", href: "/security" },
|
||||
{ title: "Federation", href: "/federation" },
|
||||
|
|
@ -321,7 +322,6 @@ export const navigation: NavGroup[] = [
|
|||
{ title: "Reports", href: "/extensions/reports" },
|
||||
{ title: "Share", href: "/extensions/share" },
|
||||
{ title: "Vanity", href: "/extensions/vanity" },
|
||||
{ title: "WebSockets", href: "/extensions/websockets" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in a new issue