mirror of
https://github.com/versia-pub/docs.git
synced 2025-12-06 06:18:19 +01:00
docs: 📝 Add docs about federation
This commit is contained in:
parent
0856fd4fd9
commit
d1fd5c585c
78
app/federation/http/page.mdx
Normal file
78
app/federation/http/page.mdx
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
export const metadata = {
|
||||||
|
title: 'HTTP',
|
||||||
|
description:
|
||||||
|
'How Lysand uses the HTTP protocol for all communications between instances.',
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP
|
||||||
|
|
||||||
|
Lysand 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.
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
ALL kinds of HTTP requests/responses between instances **MUST** include a [Signature](/signatures), signed with either the relevant [User](/entities/users)'s private key or the [Server Actor](/entities/server-actor)'s private key.
|
||||||
|
|
||||||
|
## Requests
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Properties>
|
||||||
|
<Property name="Accept" type="string" required={true}>
|
||||||
|
Must include `application/json`.
|
||||||
|
</Property>
|
||||||
|
<Property name="Content-Type" type="string" required={true}>
|
||||||
|
Must include `application/json; charset=utf-8`, if the request has a body.
|
||||||
|
</Property>
|
||||||
|
<Property name="Signature" type="string" required={false} typeLink="/signatures">
|
||||||
|
Request signature, if the request is signed.
|
||||||
|
</Property>
|
||||||
|
<Property name="Date" type="ISO8601" required={true}>
|
||||||
|
Date and time of the request.
|
||||||
|
</Property>
|
||||||
|
<Property name="User-Agent" type="string" required={false}>
|
||||||
|
A string identifying the software making the request.
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Col>
|
||||||
|
<Col sticky>
|
||||||
|
```http {{ 'title': 'Example Request' }}
|
||||||
|
POST /users/1/inbox HTTP/1.1
|
||||||
|
Accept: application/json
|
||||||
|
Signature: keyId="https://example.com/users/1",algorithm="ed25519",headers="(request-target) host date digest",signature="..."
|
||||||
|
Date: Thu, 01 Jan 1970 00:00:00 GMT
|
||||||
|
User-Agent: CoolServer/1.0 (https://coolserver.com)
|
||||||
|
```
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
## Responses
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Properties>
|
||||||
|
<Property name="Content-Type" type="string" required={true}>
|
||||||
|
Must include `application/json; charset=utf-8`.
|
||||||
|
</Property>
|
||||||
|
<Property name="Signature" type="string" required={false} typeLink="/signatures">
|
||||||
|
Response signature, if the response is signed.
|
||||||
|
</Property>
|
||||||
|
<Property name="Date" type="ISO8601" required={true}>
|
||||||
|
Date and time of the response.
|
||||||
|
</Property>
|
||||||
|
<Property name="Cache-Control" type="string" required={false}>
|
||||||
|
Must include `no-store` on entities that can be edited directly without a `Patch`, such as `Users`.
|
||||||
|
|
||||||
|
**SHOULD** include a large `max-age` on entities that are not expected to change frequently, such as `Notes`, or CDN resources.
|
||||||
|
</Property>
|
||||||
|
</Properties>
|
||||||
|
</Col>
|
||||||
|
<Col sticky>
|
||||||
|
```http {{ 'title': 'Example Response' }}
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json; charset=utf-8
|
||||||
|
Date: Thu, 01 Jan 1970 00:00:00 GMT
|
||||||
|
Signature: keyId="https://example.com/users/1",algorithm="ed25519",headers="(request-target) host date digest",signature="..."
|
||||||
|
Cache-Control: no-store
|
||||||
|
```
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
18
app/federation/page.mdx
Normal file
18
app/federation/page.mdx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Guides, Guide } from '@/components/Guides';
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: 'Federation',
|
||||||
|
description:
|
||||||
|
'Description of federation behavior in Lysand.',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Federation
|
||||||
|
|
||||||
|
Being a federation protocol, Lysand defines a set of rules for exchanging data between instances. This document outlines the behavior of instances in a Lysand federated network. {{ className: 'lead' }}
|
||||||
|
|
||||||
|
Federation is built on the [HyperText Transfer Protocol (HTTP)](https://tools.ietf.org/html/rfc7230) and the [JavaScript Object Notation (JSON)](https://tools.ietf.org/html/rfc7159) data format. Instances communicate with each other by sending and receiving JSON payloads over HTTP.
|
||||||
|
|
||||||
|
<Guides>
|
||||||
|
<Guide name="HTTP Guidelines" href="/federation/http" description="Guidelines for HTTP communication in Lysand." />
|
||||||
|
<Guide name="Validation" href="/federation/validation" description="Validation rules for Lysand implementations." />
|
||||||
|
</Guides>
|
||||||
31
app/federation/validation/page.mdx
Normal file
31
app/federation/validation/page.mdx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
export const metadata = {
|
||||||
|
title: 'Validation',
|
||||||
|
description:
|
||||||
|
'Validation rules for Lysand implementations.',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validation
|
||||||
|
|
||||||
|
Implementations **MUST** strictly validate all incoming data to ensure that it is well-formed and adheres to the Lysand Protocol. If a request is invalid, the server **MUST** return a `400 Bad Request` 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.
|
||||||
|
|
||||||
|
For example, if your implementation disallows using HTML in posts, you should not strip HTML from posts coming from other instances. You *may* choose to display them differently, but you should not modify the data itself.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
Things that should be validated include, but are not limited to:
|
||||||
|
|
||||||
|
- The presence of **all required fields**.
|
||||||
|
- The **format** of all fields (integers should not be strings, dates should be in ISO 8601 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 and well-documented.
|
||||||
|
- 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.
|
||||||
|
- 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 Lysand ecosystem.
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Guides } from '@/components/Guides'
|
|
||||||
import { Resources } from '@/components/Resources'
|
import { Resources } from '@/components/Resources'
|
||||||
import { HeroPattern } from '@/components/HeroPattern'
|
import { HeroPattern } from '@/components/HeroPattern'
|
||||||
|
|
||||||
|
|
@ -50,6 +49,4 @@ The Lysand Protocol is heavily inspired by the [ActivityPub](https://www.w3.org/
|
||||||
- **Signatures**: Most types of interactions **must** be signed with a private key. Unlike other protocols, signatures are **mandatory**, not optional.
|
- **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 Lysand server should be easy. Documentation is aimed at developers first.
|
- **Developer-Friendliness**: Understanding and implementing your own Lysand server should be easy. Documentation is aimed at developers first.
|
||||||
|
|
||||||
{/* <Guides /> */}
|
|
||||||
|
|
||||||
<Resources />
|
<Resources />
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ const Page: FC = () => {
|
||||||
children: (
|
children: (
|
||||||
<>
|
<>
|
||||||
<div className="relative z-10 max-w-2xl lg:pt-6">
|
<div className="relative z-10 max-w-2xl lg:pt-6">
|
||||||
<h1 className="text-5xl font-semibold tracking-tight text-brand-600 dark:text-brand-400">
|
<h1 className="text-5xl font-semibold tracking-tight leading-3 text-brand-600 dark:text-brand-400">
|
||||||
Lysand
|
Lysand
|
||||||
</h1>
|
</h1>
|
||||||
<h1 className="text-4xl sm:text-5xl font-semibold tracking-tight">
|
<h1 className="text-4xl sm:text-5xl font-semibold tracking-tight">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type { FC } from "react";
|
||||||
|
|
||||||
export const ExperimentalWarning: FC = () => (
|
export const ExperimentalWarning: FC = () => (
|
||||||
<>
|
<>
|
||||||
<div className="pointer-events-none z-50 fixed inset-x-0 bottom-0 sm:flex sm:justify-start sm:px-6 sm:pb-5 lg:px-8">
|
<aside className="pointer-events-none z-50 fixed inset-x-0 bottom-0 sm:flex sm:justify-start sm:px-6 sm:pb-5 lg:px-8">
|
||||||
<div className="pointer-events-auto flex items-center justify-between gap-x-6 bg-zinc-900 sm:dark:shadow-brand-600 shadow-glow px-6 py-2.5 sm:rounded-md ring-1 ring-white/10 sm:py-3 sm:pl-4 sm:pr-3.5">
|
<div className="pointer-events-auto flex items-center justify-between gap-x-6 bg-zinc-900 sm:dark:shadow-brand-600 shadow-glow px-6 py-2.5 sm:rounded-md ring-1 ring-white/10 sm:py-3 sm:pl-4 sm:pr-3.5">
|
||||||
<p className="text-sm leading-6 text-white">
|
<p className="text-sm leading-6 text-white">
|
||||||
<strong className="font-semibold">Warning!</strong>
|
<strong className="font-semibold">Warning!</strong>
|
||||||
|
|
@ -16,6 +16,6 @@ export const ExperimentalWarning: FC = () => (
|
||||||
This site is experimental and under active development.
|
This site is experimental and under active development.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</aside>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,12 @@ function SocialLink({
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Link href={href} className="group">
|
<Link
|
||||||
|
href={href}
|
||||||
|
className="group"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
<span className="sr-only">{children}</span>
|
<span className="sr-only">{children}</span>
|
||||||
<Icon
|
<Icon
|
||||||
icon={icon}
|
icon={icon}
|
||||||
|
|
@ -112,8 +117,11 @@ function SmallPrint() {
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<SocialLink href="#" icon="mdi:github">
|
<SocialLink
|
||||||
Follow us on GitHub
|
href="https://github.com/lysand-org"
|
||||||
|
icon="mdi:github"
|
||||||
|
>
|
||||||
|
Find us on GitHub
|
||||||
</SocialLink>
|
</SocialLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,38 @@
|
||||||
|
import type { ReactNode } from "react";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
import { Heading } from "./Heading";
|
import { Heading } from "./Heading";
|
||||||
|
|
||||||
const guides = [
|
export function Guides({ children }: { children: ReactNode }) {
|
||||||
{
|
|
||||||
href: "/authentication",
|
|
||||||
name: "Authentication",
|
|
||||||
description: "Learn how to authenticate your API requests.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function Guides() {
|
|
||||||
return (
|
return (
|
||||||
<div className="my-16 xl:max-w-none">
|
<div className="my-16 xl:max-w-none">
|
||||||
<Heading level={2} id="guides">
|
<Heading level={2} id="guides">
|
||||||
Guides
|
Guides
|
||||||
</Heading>
|
</Heading>
|
||||||
<div className="not-prose mt-4 grid grid-cols-1 gap-8 border-t border-zinc-900/5 pt-10 sm:grid-cols-2 xl:grid-cols-4 dark:border-white/5">
|
<div className="not-prose mt-4 grid grid-cols-1 gap-8 border-t border-zinc-900/5 pt-10 sm:grid-cols-2 xl:grid-cols-4 dark:border-white/5">
|
||||||
{guides.map((guide) => (
|
{children}
|
||||||
<div key={guide.href}>
|
|
||||||
<h3 className="text-sm font-semibold text-zinc-900 dark:text-white">
|
|
||||||
{guide.name}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
|
|
||||||
{guide.description}
|
|
||||||
</p>
|
|
||||||
<p className="mt-4">
|
|
||||||
<Button
|
|
||||||
href={guide.href}
|
|
||||||
variant="text"
|
|
||||||
arrow="right"
|
|
||||||
>
|
|
||||||
Read more
|
|
||||||
</Button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Guide({
|
||||||
|
href,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
}: { href: string; name: string; description: string }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-semibold text-zinc-900 dark:text-white">
|
||||||
|
{name}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
<p className="mt-4">
|
||||||
|
<Button href={href} variant="text" arrow="right">
|
||||||
|
Read more
|
||||||
|
</Button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,10 @@ export const Header = forwardRef<ElementRef<"div">, { className?: string }>(
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-5">
|
<div className="flex items-center gap-5">
|
||||||
<nav className="hidden md:block">
|
<nav
|
||||||
|
className="hidden md:block"
|
||||||
|
aria-label="Main navigation"
|
||||||
|
>
|
||||||
<ul className="flex items-center gap-8">
|
<ul className="flex items-center gap-8">
|
||||||
<TopLevelNavItem href="/">API</TopLevelNavItem>
|
<TopLevelNavItem href="/">API</TopLevelNavItem>
|
||||||
<TopLevelNavItem href="#">
|
<TopLevelNavItem href="#">
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogPanel,
|
DialogPanel,
|
||||||
|
DialogTitle,
|
||||||
Transition,
|
Transition,
|
||||||
TransitionChild,
|
TransitionChild,
|
||||||
} from "@headlessui/react";
|
} from "@headlessui/react";
|
||||||
|
|
@ -109,6 +110,9 @@ function MobileNavigationDialog({
|
||||||
</TransitionChild>
|
</TransitionChild>
|
||||||
|
|
||||||
<DialogPanel>
|
<DialogPanel>
|
||||||
|
<DialogTitle className="sr-only">
|
||||||
|
Mobile navigation
|
||||||
|
</DialogTitle>
|
||||||
<TransitionChild
|
<TransitionChild
|
||||||
enter="duration-300 ease-out"
|
enter="duration-300 ease-out"
|
||||||
enterFrom="opacity-0"
|
enterFrom="opacity-0"
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,14 @@ export const navigation: NavGroup[] = [
|
||||||
{ title: "SDKs", href: "/sdks" },
|
{ title: "SDKs", href: "/sdks" },
|
||||||
{ title: "Entities", href: "/entities" },
|
{ title: "Entities", href: "/entities" },
|
||||||
{ title: "Signatures", href: "/signatures" },
|
{ title: "Signatures", href: "/signatures" },
|
||||||
|
{ title: "Federation", href: "/federation" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Federation",
|
||||||
|
links: [
|
||||||
|
{ title: "HTTP", href: "/federation/http" },
|
||||||
|
{ title: "Validation", href: "/federation/validation" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -270,7 +278,7 @@ export const navigation: NavGroup[] = [
|
||||||
|
|
||||||
export function Navigation(props: ComponentPropsWithoutRef<"nav">) {
|
export function Navigation(props: ComponentPropsWithoutRef<"nav">) {
|
||||||
return (
|
return (
|
||||||
<nav {...props}>
|
<nav {...props} aria-label="Side navigation">
|
||||||
<ul>
|
<ul>
|
||||||
<TopLevelNavItem href="/">API</TopLevelNavItem>
|
<TopLevelNavItem href="/">API</TopLevelNavItem>
|
||||||
<TopLevelNavItem href="#">Support</TopLevelNavItem>
|
<TopLevelNavItem href="#">Support</TopLevelNavItem>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogPanel,
|
DialogPanel,
|
||||||
|
DialogTitle,
|
||||||
Transition,
|
Transition,
|
||||||
TransitionChild,
|
TransitionChild,
|
||||||
} from "@headlessui/react";
|
} from "@headlessui/react";
|
||||||
|
|
@ -412,6 +413,9 @@ function SearchDialog({
|
||||||
>
|
>
|
||||||
<DialogPanel className="mx-auto transform-gpu overflow-hidden rounded-lg bg-zinc-50 shadow-xl ring-1 ring-zinc-900/7.5 sm:max-w-xl dark:bg-zinc-900 dark:ring-zinc-800">
|
<DialogPanel className="mx-auto transform-gpu overflow-hidden rounded-lg bg-zinc-50 shadow-xl ring-1 ring-zinc-900/7.5 sm:max-w-xl dark:bg-zinc-900 dark:ring-zinc-800">
|
||||||
<div {...autocomplete.getRootProps({})}>
|
<div {...autocomplete.getRootProps({})}>
|
||||||
|
<DialogTitle className="sr-only">
|
||||||
|
Search
|
||||||
|
</DialogTitle>
|
||||||
<form
|
<form
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
{...autocomplete.getFormProps({
|
{...autocomplete.getFormProps({
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ const highlighter = await createHighlighter({
|
||||||
"bash",
|
"bash",
|
||||||
"php",
|
"php",
|
||||||
"python",
|
"python",
|
||||||
|
"http",
|
||||||
],
|
],
|
||||||
themes: [],
|
themes: [],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ export default function typographyStyles({ theme }: PluginUtils) {
|
||||||
fontSize: theme("fontSize.2xl")[0],
|
fontSize: theme("fontSize.2xl")[0],
|
||||||
...theme("fontSize.2xl")[1],
|
...theme("fontSize.2xl")[1],
|
||||||
marginTop: theme("spacing.16"),
|
marginTop: theme("spacing.16"),
|
||||||
marginBottom: theme("spacing.4"),
|
marginBottom: theme("spacing.8"),
|
||||||
},
|
},
|
||||||
h3: {
|
h3: {
|
||||||
color: "var(--tw-prose-headings)",
|
color: "var(--tw-prose-headings)",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue