Download OpenAPI specification:Download
Instance: a currently running server process, receiving HTTP requests/communicating with DB/etc.
Server: logical unit of the fed system, consisting of 1 or more instances and a single logical database.
Local server: the server a user is registered with, and accessing with that server's frontend.
Remote server: a server the user is not registered with, and interactions will be proxied through the local server using the protocol we are discussing.
Community: collection of posts related by a common topic on a single server (a subreddit).
Post: basic unit of content in our protocol, can be nested and frontends may choose to render top level posts as primary pieces of content, with nested posts rendered as comments.
User: entity representing a student/member of staff registered with a server. Users cannot be transferred between servers, but can of course view and interact with posts and communities on remote servers.
Caching can be streamlined by the addition of cache control headers in HTTP responses.
Servers SHOULD add cache control headers to their responses and MAY respect cache control headers that they receive.
A summary of cache control headers can be found here.
Implementations SHOULD (as much as possible) be RFC compliant. However this specification outlines a very restricted subset of this RFC that groups can implement without needing a library.
Add Cache-Control: max-age=<seconds>
to HTTP responses, where <seconds>
is how long the client should
cache the resourse for.
To prevent a resource bein cached, set <seconds>
to 0
.
A naive implementation might use a constant value, whereas a smarter implementation might change the value, depending on how likely the resource is to change.
Cache resources for the number of seconds specified in max-age
. After this time, fetch a new
copy.
Without a way to verify that a request has originated from who it is claimed to have, anybody may send requests impersonating users (including administrators).
This propsal is based on a simplified version of the HTTP Signatures protocol.
Digest
HeaderAdd the Digest
header to every
HTTP request. For simplicity, this is restricted to sha-512
.
sha-512
hash the body of the HTTP request.Digest: sha-512=<base64_sha512_hash>
to the HTTP request where
<base64_sha512_hash>
is the value from step 2.Some libraries may give the hash output as hex (rather than binary). In such a case, when Base64 encoding, you must convert from hex to Base64, not binary to Base64.
Signature
HeaderMake the public key available at /fed/key
.
/fed/key
endpoint should return a 501 Not Implemented
error.SubjectPublicKeyInfo
defined in X.509 and encoded using PEM.Construct the following string based on the values from the HTTP request
(note that there is a line ending \n
on all lines apart from the last):
(request-target): post /fed/posts
host: cooldomain.edu:8080
client-host: anotherdomain.edu:7070
user-id: johnsmith
date: Tue, 07 Jun 2021 20:51:35 GMT
digest: SHA-512=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
There is a single space after each colon :
. The part before the colon must be all lower-case. The part
following the colon (after the space) should be taken verbatim from the source (do not change the case).
The order of the key/value pairs MUST be the same as above.
(request-target)
- <HTTP Method> <Request Path>
(separated by a single space).<HTTP Method>
- GET
, POST
, PUT
or DELETE
, made lower-case.<Request Path>
e.g. /fed/posts
.host
- The value from the Host
HTTP header.client-host
- The value from the Client-Host
HTTP header.user-id
- The value from the User-ID
HTTP header. If the User-ID
HTTP header is not included in the request, then this field should be omitted.date
- The value from the Date
HTTP header.digest
- The value from the Digest
HTTP header.The string from step 2 should be rsa-sha512
signed using PKCS #1
(you will almost certainly need to use a library to do this).
The signature from step 3 should be Base64 encoded. See the previous warning.
Send the following header in the HTTP request (only including user-id
if required by the request):
Signature: keyId="rsa-global",algorithm="hs2019",headers="(request-target) host client-host user-id date digest",signature="<base64_signature>"
All fields are static apart from the final signature field, which is the output from step 4.
You MUST NOT verify requests made to the GET
/fed/key
endpoint. All other requests
MUST be verified.
Signature
Headerhttp://<host>/fed/key
, where <host>
is the value from the Host
HTTP header.501
error, then it has not implemented this security proposal.Digest
header).<base64_signature>
from the Signature
header:Signature: keyId="global",algorithm="rsa-sha512",headers="(request-target) host client-host user-id date digest",signature="<base64_signature>"
rsa-sha512
algorithm, verify, using the public key from step 1, that <base64_signature>
is
valid using PKCS #1 (you will almost certainly need to use a library to do this).Digest
Headersha-512
hash the body of the HTTP request.<base64_sha512_hash>
value from the Digest
header:
Digest: sha-512=<base64_sha512_hash>
.Uses the crytography library.
from cryptography.hazmat.primitives import serialization
with open("private.pem", "rb") as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None,
)
public_key = private_key.public_key()
pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
print(pem.decode("utf-8"))
import base64
from cryptography.hazmat.primitives import hashes
digest = hashes.Hash(hashes.SHA512())
digest.update(b"A message")
base64encoded = base64.b64encode(digest.finalize())
print(base64encoded.decode("ascii"))
import base64
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
with open("private.pem", "rb") as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None,
)
message = b"A message I want to sign"
signature = private_key.sign(
message,
padding.PKCS1v15(),
hashes.SHA512()
)
print(base64.b64encode(signature).decode("ascii"))
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
# You should get the public key from `/fed/key`.
with open("public.pem", "rb") as key_file:
public_key = serialization.load_pem_public_key(key_file.read())
# Get the signature from the HTTP header.
encoded_signature = "ij3WqCSE+289DW4KV3Rh//4mZ1dev5I7m6rUrYyvcojmPBhuVzqxJ0XLoWPKtGz6aVYi4k+Ide1zTGUIAOWQEwCiT4WP/GrsYukwgAfgS9q80YgiKIyqVBvc953XLVzgnOT+8X2HQ/LTg+BwP23kLeEXPabxhMN323L+gVWVyoiIUYEf0B34PbPq/KTPqW/rHtup6ovSRfvy8Bqeqmtpmc0gJwR7WnKRYEiVn40yRQDxtO6zSjvmObv5U2BKCjprnOAp5yfKzROkpfqui1yjKMp5RfA+NILGiJSSQwgGe1eG0QOWYoW8JecLOrxBHOJMuFc0wDQ0k9cip/nAc/T5Cw=="
decoded_signature = base64.b64decode(encoded_signature)
# If this throws an InvalidSignature exception, then the signature
# was invalid.
public_key.verify(
decoded_signature,
message,
padding.PKCS1v15(),
hashes.SHA512()
)
const crypto = require("crypto");
const fs = require("fs");
const publicKey = crypto.createPublicKey({
key: fs.readFileSync("private.pem"),
format: "pem"
});
const encodedPublicKey = publicKey.export({
type: "spki",
format: "pem"
});
console.log(encodedPublicKey);
const crypto = require("crypto");
const hash = crypto.createHash("sha512");
hash.update("A message");
const digest = hash.digest("base64");
console.log(digest);
const crypto = require("crypto");
const fs = require("fs");
const privateKey = crypto.createPrivateKey({
key: fs.readFileSync("private.pem"),
format: "pem"
});
const sign = crypto.createSign("SHA512");
sign.write("A message I want to sign");
sign.end();
const signature = sign.sign(privateKey, "base64");
console.log(signature);
const crypto = require("crypto");
const fs = require("fs");
const publicKey = crypto.createPublicKey({
key: fs.readFileSync("private.pem"),
format: "pem"
});
const signature = "ij3WqCSE+289DW4KV3Rh//4mZ1dev5I7m6rUrYyvcojmPBhuVzqxJ0XLoWPKtGz6aVYi4k+Ide1zTGUIAOWQEwCiT4WP/GrsYukwgAfgS9q80YgiKIyqVBvc953XLVzgnOT+8X2HQ/LTg+BwP23kLeEXPabxhMN323L+gVWVyoiIUYEf0B34PbPq/KTPqW/rHtup6ovSRfvy8Bqeqmtpmc0gJwR7WnKRYEiVn40yRQDxtO6zSjvmObv5U2BKCjprnOAp5yfKzROkpfqui1yjKMp5RfA+NILGiJSSQwgGe1eG0QOWYoW8JecLOrxBHOJMuFc0wDQ0k9cip/nAc/T5Cw==";
const verify = crypto.createVerify("SHA512");
verify.write("A message I want to sign");
verify.end();
const result = verify.verify(publicKey, signature, "base64");
console.log(result);
There has been a newly published (November 2020) diverging specification for signing HTTP messages: Signing HTTP Messages. This proposal is based on the predessor specification.
PKCS #1 is used over PSS to simplify implementation.
At the moment, a MITM attack could replace the public key at /fed/key
with another. This can be solved
either by:
Note the reacts paramater in the community response is entierly optional and may not be present. Also when implementing reacts every community must return a like and dislike react. This is so instances which implement an up/down vote system can use these reacts for this. Another item to note about reacts is that they contain a path and a unicode property. Only one of these has to be set. The unicode property should represent a single unicode character to be displayed as that react. With the unicode for a react there is no length limit placed on it becuase calculating the length of a unicode string is hard. For example the character 'đ´ó §ó ˘ó łó Łó ´ó ż' has length 7/9 but only appears as a single character.
id required | string <^[a-zA-Z0-9-_]{1,24}$> ID of the community being requested |
Client-Host required | string The hostname (including port) of the client server. |
{- "id": "cs3099",
- "title": "CS3099: Group Project",
- "description": "CS3099 community for discussion, tutorials and quizzes!",
- "admins": [
- {
- "id": "coolusername123",
- "host": "cooldomain.edu"
}
], - "reacts": [
- {
- "id": "like",
- "name": "like",
- "path": "null",
- "unicode": "đ",
- "positivity": "1"
}, - {
- "id": "dislike",
- "name": "dislike",
- "path": "null",
- "unicode": "đ",
- "positivity": "0"
}
]
}
id required | string <^[a-zA-Z0-9-_]{1,24}$> ID of the community being requested |
Client-Host required | string The hostname (including port) of the client server. |
[- {
- "id": "884f0f3c-04d9-4d93-bb9c-b140ff262a53",
- "modified": 1602868491
}, - {
- "id": "07f3c861-88a9-46f3-bd0e-9346fb6b5342",
- "modified": 1552832552
}, - {
- "id": "802402b4-0644-4282-9e0a-6b179c9f6e58",
- "modified": 1602149423
}
]
limit | integer Filters by the n latest posts |
community | string Filters posts by community |
minDate | integer <unix_timestamp> Filters by minimum creation date |
author | string <^[a-zA-Z0-9-_]{1,24}$> Example: author=coolusername123 Filters by author |
host | string Example: host=cool.servername.net Filters by server hostname |
parentPost | string <uuidv4> Filters by ID of parent post (will exclude posts with no parent) |
includeSubChildrenPosts | boolean Default: true Include children posts, children of children posts and so on. |
contentType | string Example: contentType=markdown Filters by type of post content. |
Client-Host required | string The hostname (including port) of the client server. |
User-ID required | string <^[a-zA-Z0-9-_]{1,24}$> The user making the request. |
[- {
- "id": "5ab3acce-e9d1-4b3a-be97-60d2cbe32a4c",
- "community": "sailing",
- "parentPost": "dafca76d-5883-4eff-959a-d32bc9f72e1a",
- "children": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
], - "title": "Bezos's Wealth Overflows 64-bit Signed Integer, Now Massively In Debt",
- "content": [
- {
- "text": {
- "text": "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit amet consectetur adipisci[ng]velit, sed quia non-numquam [do] eius modi tempora inci[di]dunt, ut labore et dolore magnam aliquam quaerat voluptatem."
}
}
], - "author": {
- "id": "coolusername123",
- "host": "cooldomain.edu"
}, - "modified": 1552832552,
- "created": 1552832584,
- "reacts": [
- {
- "react": {
- "id": "like",
- "name": "like",
- "path": "null",
- "unicode": "đ",
- "positivity": "1"
}, - "reactions": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
]
}, - {
- "react": {
- "id": "dislike",
- "name": "dislike",
- "path": "null",
- "unicode": "đ",
- "positivity": "0"
}, - "reactions": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
]
}, - {
- "react": {
- "id": "cat_jam",
- "name": "cat jam",
- "path": "cooldomain.edu/cat_jam.gif",
- "unicode": "null",
- "positivity": "0.5"
}, - "reactions": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
]
}
]
}
]
If the post is in response to another post, the title
field must be
set to null
.
Servers may decide which types of contnent they will accept as posts
(including comments), however they must at least accept text
content
objects. If the server does not accept a particular type of content
then it should return a 501
error.
If a server can not render markdown
content objects, it should
display it as plain text (as opposed to rejecting the request).
content
can contain multiple objects containing different kinds of
content. For example, markdown
could be combined with a (not yet
implemented) poll
and/or image
type, allowing text, images and a
poll in a single post. However, only a single object of each kind is
allowed and there are restrictions on certain combinations, currently
text
and markdown
are mutually exclusive.
Client-Host required | string The hostname (including port) of the client server. |
User-ID required | string <^[a-zA-Z0-9-_]{1,24}$> The user making the request. |
New post to be added to a community
community required | string <^[a-zA-Z0-9-_]{1,24}$> |
parentPost | string <uuidv4> |
title required | string |
required | Array of PostContentText (object) or PostContentMarkdown (object) or PostContentReact (object) (PostContent) |
{- "community": "sailing",
- "parentPost": "dafca76d-5883-4eff-959a-d32bc9f72e1a",
- "title": "Bezos's Wealth Overflows 64-bit Unsigned Integer, Is Now Homeless",
- "content": [
- {
- "text": {
- "text": "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit amet consectetur adipisci[ng]velit, sed quia non-numquam [do] eius modi tempora inci[di]dunt, ut labore et dolore magnam aliquam quaerat voluptatem."
}
}
]
}
{- "id": "5ab3acce-e9d1-4b3a-be97-60d2cbe32a4c",
- "community": "sailing",
- "parentPost": "dafca76d-5883-4eff-959a-d32bc9f72e1a",
- "children": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
], - "title": "Bezos's Wealth Overflows 64-bit Signed Integer, Now Massively In Debt",
- "content": [
- {
- "text": {
- "text": "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit amet consectetur adipisci[ng]velit, sed quia non-numquam [do] eius modi tempora inci[di]dunt, ut labore et dolore magnam aliquam quaerat voluptatem."
}
}
], - "author": {
- "id": "coolusername123",
- "host": "cooldomain.edu"
}, - "modified": 1552832552,
- "created": 1552832584,
- "reacts": [
- {
- "react": {
- "id": "like",
- "name": "like",
- "path": "null",
- "unicode": "đ",
- "positivity": "1"
}, - "reactions": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
]
}, - {
- "react": {
- "id": "dislike",
- "name": "dislike",
- "path": "null",
- "unicode": "đ",
- "positivity": "0"
}, - "reactions": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
]
}, - {
- "react": {
- "id": "cat_jam",
- "name": "cat jam",
- "path": "cooldomain.edu/cat_jam.gif",
- "unicode": "null",
- "positivity": "0.5"
}, - "reactions": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
]
}
]
}
id required | string <uuidv4> |
Client-Host required | string The hostname (including port) of the client server. |
User-ID required | string <^[a-zA-Z0-9-_]{1,24}$> The user making the request. |
{- "id": "5ab3acce-e9d1-4b3a-be97-60d2cbe32a4c",
- "community": "sailing",
- "parentPost": "dafca76d-5883-4eff-959a-d32bc9f72e1a",
- "children": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
], - "title": "Bezos's Wealth Overflows 64-bit Signed Integer, Now Massively In Debt",
- "content": [
- {
- "text": {
- "text": "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit amet consectetur adipisci[ng]velit, sed quia non-numquam [do] eius modi tempora inci[di]dunt, ut labore et dolore magnam aliquam quaerat voluptatem."
}
}
], - "author": {
- "id": "coolusername123",
- "host": "cooldomain.edu"
}, - "modified": 1552832552,
- "created": 1552832584,
- "reacts": [
- {
- "react": {
- "id": "like",
- "name": "like",
- "path": "null",
- "unicode": "đ",
- "positivity": "1"
}, - "reactions": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
]
}, - {
- "react": {
- "id": "dislike",
- "name": "dislike",
- "path": "null",
- "unicode": "đ",
- "positivity": "0"
}, - "reactions": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
]
}, - {
- "react": {
- "id": "cat_jam",
- "name": "cat jam",
- "path": "cooldomain.edu/cat_jam.gif",
- "unicode": "null",
- "positivity": "0.5"
}, - "reactions": [
- "b78b29f4-88d2-4500-b3f9-704449b262e2",
- "53da9025-0ba3-4966-8703-824c7418172a",
- "d2073b6a-3115-4089-b198-6db799bc53ad"
]
}
]
}
Only allowed if request is made by the server associated with the author or the admins of the community the post belongs to, 403 returned otherwise
id required | string <uuidv4> |
Client-Host required | string The hostname (including port) of the client server. |
User-ID required | string <^[a-zA-Z0-9-_]{1,24}$> The user making the request. |
New post to be added to a community
title required | string |
required | Array of PostContentText (object) or PostContentMarkdown (object) or PostContentReact (object) (PostContent) |
{- "title": "Bezos's Wealth Overflows 64-bit Signed Integer, Now Massively In Debt",
- "content": [
- {
- "text": {
- "text": "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit amet consectetur adipisci[ng]velit, sed quia non-numquam [do] eius modi tempora inci[di]dunt, ut labore et dolore magnam aliquam quaerat voluptatem."
}
}
]
}
{- "title": "A short description of the erorr",
- "message": "A long description of the error, giving instructions on how it can be solved and why it occured."
}
Only allowed if request is made by the server associated with the author or the admins of the community the post belongs to, 403 returned otherwise
id required | string <uuidv4> |
Client-Host required | string The hostname (including port) of the client server. |
User-ID required | string <^[a-zA-Z0-9-_]{1,24}$> The user making the request. |
{- "title": "A short description of the erorr",
- "message": "A long description of the error, giving instructions on how it can be solved and why it occured."
}
id required | string <^[a-zA-Z0-9-_]{1,24}$> ID of the user being requested |
{- "id": "john",
- "about": "A place for a user to write an about / bio",
- "avatarUrl": "cooldomain.edu/media/profile_imgs/avatar.png",
- "posts": [
- {
- "id": "5ab3acce-e9d1-4b3a-be97-60d2cbe32a4c",
- "host": "cooldomain.edu"
}
]
}
id required | string <^[a-zA-Z0-9-_]{1,24}$> ID of the user being messaged |
The message being sent
title required | string |
required | PostContentText (object) or PostContentMarkdown (object) or PostContentReact (object) (PostContent) |
{- "title": "Why ban?",
- "content": {
- "text": {
- "text": "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit amet consectetur adipisci[ng]velit, sed quia non-numquam [do] eius modi tempora inci[di]dunt, ut labore et dolore magnam aliquam quaerat voluptatem."
}
}
}
Returns all of the servers that are known by the current server.
The array should be ordered with more 'important' hosts at the beginning and the least 'important' at the end.
The 'importance' of a server is implementation specific, but some examples include using the total number of posts made to that server by the current server.
[- "cooldomain.edu"
]