From 4898a8419d333f6b3ce92c7c61471bd2feaa2b5c Mon Sep 17 00:00:00 2001 From: Gaspard Wierzbinski Date: Tue, 9 Apr 2024 04:03:30 -1000 Subject: [PATCH 1/7] Update HTTP wording --- docs/spec.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/spec.md b/docs/spec.md index f8e172a..cd8eca2 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -51,7 +51,7 @@ All JSON objects disseminated during federation **MUST** be handled as follows: ## Requests and Responses -All HTTP requests **MUST** be transmitted over HTTPS. Servers **MUST NOT** accept HTTP requests, unless for development purposes (e.g., if a server is operating on localhost or another local network). +All Hypertext Transfer Protocol requests MUST be transmitted using the Hypertext Transfer Protocol Secure Extension. Servers MUST NOT accept requests without TLS (HTTPS), except for development purposes (e.g., if a server is operating on localhost or another local network). Servers should support HTTP/2 and HTTP/3 for enhanced performance and security. Servers **MUST** support HTTP/1.1 at a minimum. @@ -72,4 +72,4 @@ All responses **MUST** include at least the following headers: - `Signature` if the response body is signed (which is typically the case) - `Cache-Control: no-store` on entities that can be edited directly without using a [Patch](objects/patch), such as [Actors](objects/actors) - A cache header with a `max-age` of at least 5 minutes for entities that are not expected to change frequently, such as [Notes](objects/publications) -- A cache header with a large `max-age` for media files when served by a CDN or other caching service under the server's control \ No newline at end of file +- A cache header with a large `max-age` for media files when served by a CDN or other caching service under the server's control From 9d913fd76a8eadc5f93e343b65b641b75a2a8c87 Mon Sep 17 00:00:00 2001 From: Gaspard Wierzbinski Date: Tue, 9 Apr 2024 15:24:44 -1000 Subject: [PATCH 2/7] Update endpoints.md --- docs/federation/endpoints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/federation/endpoints.md b/docs/federation/endpoints.md index e74bec1..553a9d8 100644 --- a/docs/federation/endpoints.md +++ b/docs/federation/endpoints.md @@ -48,7 +48,7 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" - }' https://example.com/users/uuid/inbox ``` -The server **MUST** respond with a `200 OK` response code if the operation was successful. +The server **MUST** respond with a `201 CREATED` response code if the operation was successful. ## User Outbox From ab07e886400738c248263c1c9f8aa97233abf531 Mon Sep 17 00:00:00 2001 From: Gaspard Wierzbinski Date: Tue, 9 Apr 2024 16:31:44 -1000 Subject: [PATCH 3/7] Clarify cryptography --- docs/cryptography/signing.md | 142 ++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/docs/cryptography/signing.md b/docs/cryptography/signing.md index 843836a..8031a82 100644 --- a/docs/cryptography/signing.md +++ b/docs/cryptography/signing.md @@ -28,7 +28,7 @@ The signature is calculated as follows: ``` (request-target): post /users/uuid/inbox host: example.com -date: Fri, 01 Jan 2021 00:00:00 GMT +date: 2024-04-10T01:27:24.880Z digest: SHA-256=base64_digest ``` @@ -40,66 +40,59 @@ The `digest` field **MUST** be the SHA-256 digest of the request body, base64-en The `date` field **MUST** be the date and time that the request was sent, formatted as follows (ISO 8601): ``` -Fri, 01 Jan 2021 00:00:00 GMT +2024-04-10T01:27:24.880Z ``` -The `host` field **MUST** be the domain of the server that is receiving the request. +The `host` field **MUST** be the host of the server that is receiving the request. The `request-target` field **MUST** be the request target of the request, formatted as follows: ``` post /users/uuid/inbox ``` -Where `/users/uuid/inbox` is the path of the request. +Where `/users/uuid/inbox` is the path of the request (this will depend on implementations). -Here is an example of signing a request using TypeScript and the WebCrypto API: +Let's imagine a user at `example.com` wants to send something to a user at `receiver.com`'s inbox. + +Here is an example of signing a request using TypeScript and the WebCrypto API (replace `status_author_private_key`, `full_lysand_object_as_string` and sample text appropriate): ```typescript -/** - * Convert a string into an ArrayBuffer - * from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String - */ -const str2ab = (str: string) => { - const buf = new ArrayBuffer(str.length); - const bufView = new Uint8Array(buf); - for (let i = 0, strLen = str.length; i < strLen; i++) { - bufView[i] = str.charCodeAt(i); - } - return buf; -}; - const privateKey = await crypto.subtle.importKey( - "pkcs8", - str2ab(atob("base64_private_key")), - "Ed25519", - false, - ["sign"] + "pkcs8", + Uint8Array.from(atob(status_author_private_key), (c) => + c.charCodeAt(0), + ), + "Ed25519", + false, + ["sign"], ); const digest = await crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode("request_body") + "SHA-256", + new TextEncoder().encode(full_lysand_object_as_string), ); -const userInbox = new URL("..."); +const userInbox = new URL( + "https://receiver.com/users/22a56612-9909-48ca-84af-548b28db6fd5/inbox" +); const date = new Date(); const signature = await crypto.subtle.sign( - "Ed25519", - privateKey, - new TextEncoder().encode( - `(request-target): post ${userInbox.pathname}\n` + - `host: ${userInbox.host}\n` + - `date: ${date.toUTCString()}\n` + - `digest: SHA-256=${btoa( - String.fromCharCode(...new Uint8Array(digest)) - )}\n` - ) + "Ed25519", + privateKey, + new TextEncoder().encode( + `(request-target): post ${userInbox.pathname}\n` + + `host: ${userInbox.host}\n` + + `date: ${date.toISOString()}\n` + + `digest: SHA-256=${btoa( + String.fromCharCode(...new Uint8Array(digest)), + )}\n`, + ), ); const signatureBase64 = btoa( - String.fromCharCode(...new Uint8Array(signature)) + String.fromCharCode(...new Uint8Array(signature)), ); ``` @@ -112,46 +105,75 @@ await fetch("https://example.com/users/uuid/inbox", { method: "POST", headers: { "Content-Type": "application/json", - Date: date.toUTCString(), - Origin: "https://example.com", - Signature: `keyId="${...}",algorithm="ed25519",headers="(request-target) host date digest",signature="${signatureBase64}"`, + Date: date.toISOString(), + Origin: "example.com", + Signature: `keyId="https://example.com/users/caf18716-800d-4c88-843d-4947ab39ca0f",algorithm="ed25519",headers="(request-target) host date digest",signature="${signatureBase64}"`, }, - body: JSON.stringify({ - // ... - }) + body: full_lysand_object_as_string, }); ``` Example of validation on the server side: ```typescript -// request is a Request object containing the previous request -// public_key is the user's public key in raw base64 format +// req is a Request object +const signatureHeader = req.headers.get("Signature"); +const origin = req.headers.get("Origin"); +const date = req.headers.get("Date"); -const signatureHeader = request.headers.get("Signature"); +if (!signatureHeader) { + return errorResponse("Missing Signature header", 400); +} -const signature = signatureHeader.split("signature=")[1].replace(/"/g, ""); +if (!origin) { + return errorResponse("Missing Origin header", 400); +} -const origin = request.headers.get("Origin"); +if (!date) { + return errorResponse("Missing Date header", 400); +} -const date = request.headers.get("Date"); +const signature = signatureHeader + .split("signature=")[1] + .replace(/"/g, ""); const digest = await crypto.subtle.digest( - "SHA-256", - new TextEncoder().encode(await request.text()) + "SHA-256", + new TextEncoder().encode(JSON.stringify(body)), ); -const expectedSignedString = `(request-target): ${request.method.toLowerCase()} ${request.url}\n` + - `host: ${request.url}\n` + - `date: ${date}\n` + - `digest: SHA-256=${btoa(digest)}`; +const keyId = signatureHeader + .split("keyId=")[1] + .split(",")[0] + .replace(/"/g, ""); + +// TODO: Fetch sender using WebFinger if not found +const sender = ... // Get sender from your database via its URI (inside the keyId variable) + +const public_key = await crypto.subtle.importKey( + "spki", + Uint8Array.from(atob(sender.publicKey), (c) => c.charCodeAt(0)), + "Ed25519", + false, + ["verify"], +); + +const expectedSignedString = + `(request-target): ${req.method.toLowerCase()} ${ + new URL(req.url).pathname + }\n` + + `host: ${new URL(req.url).host}\n` + + `date: ${date}\n` + + `digest: SHA-256=${btoa( + String.fromCharCode(...new Uint8Array(digest)), + )}\n`; // Check if signed string is valid const isValid = await crypto.subtle.verify( - "Ed25519", - publicKey, - new TextEncoder().encode(signature), - new TextEncoder().encode(expectedSignedString) + "Ed25519", + public_key, + Uint8Array.from(atob(signature), (c) => c.charCodeAt(0)), + new TextEncoder().encode(expectedSignedString), ); if (!isValid) { @@ -170,4 +192,4 @@ When implementing cryptography in Lysand, it is important to consider the follow - **Key Export**: Do not export private keys to untrusted environments, but allow users to export their private keys to secure locations. - **Key Import**: Allow users to import private keys when creating new account, but ensure that the keys are not exposed to unauthorized parties. -Most implementations should not roll their own cryptography, but instead use well-established libraries such as the WebCrypto API. (See the [WebCrypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) documentation for more information). Libraries written in unsafe languages, such as C, or that are a frequent source of security issues (e.g., OpenSSL) should be avoided. \ No newline at end of file +Most implementations should not roll their own cryptography, but instead use well-established libraries such as the WebCrypto API. (See the [WebCrypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) documentation for more information). Libraries written in unsafe languages, such as C, or that are a frequent source of security issues (e.g., OpenSSL) should be avoided. From dc1b8c744733beb4d39cfda463aa6755e0312698 Mon Sep 17 00:00:00 2001 From: Gaspard Wierzbinski Date: Tue, 9 Apr 2024 16:33:52 -1000 Subject: [PATCH 4/7] Fix a mistake in cryptography --- docs/cryptography/signing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cryptography/signing.md b/docs/cryptography/signing.md index 8031a82..d3191f4 100644 --- a/docs/cryptography/signing.md +++ b/docs/cryptography/signing.md @@ -101,7 +101,7 @@ const signatureBase64 = btoa( The request can then be sent with the `Signature`, `Origin` and `Date` headers as follows: ```ts -await fetch("https://example.com/users/uuid/inbox", { +await fetch("https://receiver.com/users/22a56612-9909-48ca-84af-548b28db6fd5/inbox", { method: "POST", headers: { "Content-Type": "application/json", From 437f01f0551a289c88adc0b0587d4bce447b0987 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Tue, 9 Apr 2024 16:41:55 -1000 Subject: [PATCH 5/7] Fix types --- Dockerfile | 28 ++++++++++++++++++++++------ docs/objects.md | 16 +++++++++++----- docs/objects/actions.md | 11 ++++++++++- docs/objects/announce.md | 3 +-- docs/objects/dislike.md | 3 +-- docs/objects/follow-accept.md | 3 +-- docs/objects/follow-reject.md | 3 +-- docs/objects/follow.md | 3 +-- docs/objects/like.md | 3 +-- docs/objects/publications.md | 15 ++++++++++++++- docs/objects/server-metadata.md | 22 +++++++++++++++++++++- docs/objects/undo.md | 12 +++++++++++- docs/objects/user.md | 3 +++ 13 files changed, 98 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6ca4da4..26e3a4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,24 @@ -FROM oven/bun:alpine +FROM oven/bun:alpine as base + +# Install dependencies into temp directory +# This will cache them and speed up future builds +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lockb /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile + +# Install with --production (exclude devDependencies) +RUN mkdir -p /temp/prod +COPY package.json bun.lockb /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + +FROM base AS builder COPY . /app +RUN cd /app && bun install +RUN cd /app && bun docs:build -RUN cd ./app && bun install -RUN cd ./app && bun docs:build - -FROM oven/bun:alpine +FROM base AS final COPY --from=builder /app/.vitepress/dist/ /app @@ -14,4 +27,7 @@ LABEL org.opencontainers.image.source "https://github.com/lysand-org/docs" LABEL org.opencontainers.image.vendor "Lysand.org" LABEL org.opencontainers.image.licenses "MIT" LABEL org.opencontainers.image.title "Lysand Docs" -LABEL org.opencontainers.image.description "Documentation for Lysand" \ No newline at end of file +LABEL org.opencontainers.image.description "Documentation for Lysand" + +WORKDIR /app +CMD ["bun", "docs:serve"] \ No newline at end of file diff --git a/docs/objects.md b/docs/objects.md index d7b2cf5..5dcb26b 100644 --- a/docs/objects.md +++ b/docs/objects.md @@ -51,11 +51,17 @@ This document uses TypeScript to define the types of the entities in a clear and ```typescript interface Entity { - id: string; - created_at: string; - uri: string; - type: string; -}; + id: string; + created_at: string; + uri: string; + type: string; + extensions?: { + "org.lysand:custom_emojis"?: { + emojis: Emoji[]; + }; + [key: string]: object | undefined; + }; +} ``` The `Entity` type is the base type for all entities in the Lysand protocol. It includes the `id`, `created_at`, `uri`, and `type` attributes. diff --git a/docs/objects/actions.md b/docs/objects/actions.md index 673dbbf..733a72d 100644 --- a/docs/objects/actions.md +++ b/docs/objects/actions.md @@ -40,4 +40,13 @@ This approach helps prevent potential misuse of the protocol to determine if a u | :----- | :----- | :------- | | author | String | Yes | -URI of the [Actor](./actors) who initiated the action. \ No newline at end of file +URI of the [Actor](./actors) who initiated the action. + +## Types + +```typescript +interface Action extends Entity { + type: "Like" | "Dislike" | "Follow" | "FollowAccept" | "FollowReject" | "Announce" | "Undo"; + author: string +} +``` \ No newline at end of file diff --git a/docs/objects/announce.md b/docs/objects/announce.md index 3d7da0a..668056f 100644 --- a/docs/objects/announce.md +++ b/docs/objects/announce.md @@ -36,9 +36,8 @@ URI of the object being announced. Must be of type [Note](./note) ## Types ```typescript -interface Announce extends Entity { +interface Announce extends Action { type: "Announce"; - author: string; object: string; } ``` \ No newline at end of file diff --git a/docs/objects/dislike.md b/docs/objects/dislike.md index dd773b1..8b7002c 100644 --- a/docs/objects/dislike.md +++ b/docs/objects/dislike.md @@ -35,9 +35,8 @@ URI of the object being disliked. Must be of type [Note](./note) ## Types ```typescript -interface Dislike extends Entity { +interface Dislike extends Action { type: "Dislike"; - author: string; object: string; } ``` \ No newline at end of file diff --git a/docs/objects/follow-accept.md b/docs/objects/follow-accept.md index 48b7c2c..d595c28 100644 --- a/docs/objects/follow-accept.md +++ b/docs/objects/follow-accept.md @@ -35,9 +35,8 @@ URI of the [User](./user) who tried to follow the author ## Types ```typescript -interface FollowAccept extends Entity { +interface FollowAccept extends Action { type: "FollowAccept"; - author: string; follower: string; } ``` \ No newline at end of file diff --git a/docs/objects/follow-reject.md b/docs/objects/follow-reject.md index 6b64bf0..aed6449 100644 --- a/docs/objects/follow-reject.md +++ b/docs/objects/follow-reject.md @@ -35,9 +35,8 @@ URI of the [User](./user) who tried to follow the author. ## Types ```typescript -interface FollowReject extends Entity { +interface FollowReject extends Action { type: "FollowReject"; - author: string; follower: string; } ``` diff --git a/docs/objects/follow.md b/docs/objects/follow.md index 64ebc5f..7d907bd 100644 --- a/docs/objects/follow.md +++ b/docs/objects/follow.md @@ -35,9 +35,8 @@ URI of the [User](./user) who is being follow requested. ## Types ```typescript -interface Follow extends Entity { +interface Follow extends Action { type: "Follow"; - author: string; followee: string; } ``` diff --git a/docs/objects/like.md b/docs/objects/like.md index bdd555c..b80ebba 100644 --- a/docs/objects/like.md +++ b/docs/objects/like.md @@ -35,9 +35,8 @@ URI of the object being liked. Must be of type [Note](./note) ## Types ```typescript -interface Like extends Entity { +interface Like extends Action { type: "Like"; - author: string; object: string; } ``` diff --git a/docs/objects/publications.md b/docs/objects/publications.md index 8566527..7e2242c 100644 --- a/docs/objects/publications.md +++ b/docs/objects/publications.md @@ -212,7 +212,7 @@ Servers **MUST** respect the visibility of the publication and **MUST NOT** show ## Types ```typescript -interface Publication { +interface Publication extends Entity { type: "Note" | "Patch"; author: string; content?: ContentFormat; @@ -223,6 +223,19 @@ interface Publication { subject?: string; is_sensitive?: boolean; visibility: Visibility; + extensions?: Entity["extensions"] & { + "org.lysand:reactions"?: { + reactions: string; + }; + "org.lysand:polls"?: { + poll: { + options: ContentFormat[]; + votes: number[]; + multiple_choice?: boolean; + expires_at: string; + }; + }; + }; } ``` diff --git a/docs/objects/server-metadata.md b/docs/objects/server-metadata.md index 235a3f1..376069d 100644 --- a/docs/objects/server-metadata.md +++ b/docs/objects/server-metadata.md @@ -151,4 +151,24 @@ Clients should display the most modern format that they support, such as WebP, A | :------------------- | :-------------- | :----------------------------- | | supported_extensions | Array of String | Yes, can be empty array (`[]`) | -List of extension names that the server supports, in namespaced format (`"org.lysand:reactions"`). \ No newline at end of file +List of extension names that the server supports, in namespaced format (`"org.lysand:reactions"`). + +## Types + +```typescript +interface ServerMetadata { + type: "ServerMetadata"; + name: string; + version: string; + description?: string; + website?: string; + moderators?: string[]; + admins?: string[]; + logo?: ContentFormat; + banner?: ContentFormat; + supported_extensions: string[]; + extensions?: { + [key: string]: object | undefined; + }; +} +``` \ No newline at end of file diff --git a/docs/objects/undo.md b/docs/objects/undo.md index 057ed17..0643fb0 100644 --- a/docs/objects/undo.md +++ b/docs/objects/undo.md @@ -40,4 +40,14 @@ URI of the [Actor](./actors) who initiated the action. | :----- | :----- | :------- | | object | String | Yes | -URI of the object being undone. The object **MUST** be an [Action](./actions) or a [Note](./note). To undo [Patch](./patch) objects, use a subsequent [Patch](./patch) or delete the original [Note](./note). \ No newline at end of file +URI of the object being undone. The object **MUST** be an [Action](./actions) or a [Note](./note). To undo [Patch](./patch) objects, use a subsequent [Patch](./patch) or delete the original [Note](./note). + +## Types + +```typescript +interface Undo extends Entity { + type: "Undo"; + author: string; + object: string; +} +``` \ No newline at end of file diff --git a/docs/objects/user.md b/docs/objects/user.md index 10707f9..a78bc3c 100644 --- a/docs/objects/user.md +++ b/docs/objects/user.md @@ -332,6 +332,9 @@ interface User extends Entity { dislikes: string; inbox: string; outbox: string; + extensions?: Entity["extensions"] & { + "org.lysand:vanity"?: VanityExtension; + }; } ``` From edff5d28117e6759067c24521f340dcfd347e022 Mon Sep 17 00:00:00 2001 From: Gaspard Wierzbinski Date: Tue, 9 Apr 2024 18:11:26 -1000 Subject: [PATCH 6/7] Update user-discovery.md --- docs/federation/user-discovery.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/federation/user-discovery.md b/docs/federation/user-discovery.md index 44704cb..96ce849 100644 --- a/docs/federation/user-discovery.md +++ b/docs/federation/user-discovery.md @@ -16,15 +16,15 @@ The document **MUST** contain the following information, as specified by the Web The `template` field **MUST** be the URI of the server's WebFinger endpoint, which is usually `https://example.com/.well-known/webfinger?resource={uri}`. -The `resource` field **MUST** be the URI of the user that the server is trying to discover (in the format `acct:uuid@example.com`) +The `resource` field **MUST** be the URI of the user that the server is trying to discover (in the format `acct:identifier@example.com`) Breaking down this URI, we get the following: - `acct`: The protocol of the URI. This is always `acct` for Lysand. -- `uuid`: The UUID of the user that the server is trying to discover. +- `identifier`: Either the UUID or the username of the user that the server is trying to discover. - `example.com`: The domain of the server that the user is on. This is usually the domain of the server. This can also be a subdomain of the server, such as `lysand.example.com`. -This format is reminiscent of the `acct` format used by ActivityPub, but with a UUID instead of a username. Users will typically not use the `id` of an actor to identify it, but instead its `username`: servers **MUST** only use the `id` to identify actors. +This format is reminiscent of the `acct` format used by ActivityPub, but with either a UUID or a username instead of just an username. Users will typically not use the `id` of an actor to identify it, but instead its `username`: servers **MUST** only use the `id` to identify actors. --- @@ -39,7 +39,7 @@ The requesting server **MUST** send the following headers with the request: The requestinng server **MUST** send the following query parameters with the request: -- `resource`: The URI of the user that the server is trying to discover (in the format `acct:uuid@example.com` (replace `uuid` with the user's ID) +- `resource`: The URI of the user that the server is trying to discover (in the format `acct:identifier@example.com` (replace `identifier` with the user's ID or username) --- @@ -47,7 +47,7 @@ The server **MUST** respond with a `200 OK` response code, and a JSON object in ```json5 { - "subject": "acct:uuid@example.com", + "subject": "acct:identifier@example.com", "links": [ { "rel": "self", From 4417579367255c296329b5fd1d79b1e6925c5141 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Thu, 11 Apr 2024 19:34:08 -1000 Subject: [PATCH 7/7] Update Vitepress version --- bun.lockb | Bin 44895 -> 61216 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index d72ca56292a6f57bfd714d2a0b7e20de99fdb4e8..d8a9601a4d7f8b8520656d09f55ebb38307a192a 100755 GIT binary patch delta 17711 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 CcJ7$~ delta 9242 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 diff --git a/package.json b/package.json index 299f651..1e5ec50 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,6 @@ "docs:preview": "vitepress preview" }, "devDependencies": { - "vitepress": "1.0.0-rc.45" + "vitepress": "^1.1.0" } } \ No newline at end of file