Revision: 2021-04-26
Author: Andre Medeiros contact@staltz.com
License: This work is licensed under a Creative Commons Attribution 4.0 International License.
As part of the process of onboarding to SSB, new users often need to connect to a "pub server" or "room server" where content can be retrieved from. These servers often employ an access control system based on invite tokens, to prevent access to undesired actors from the public internet. The invite system deployed by these servers has been a convoluted algorithm repurposing secret-handshake to create an ephemeral muxrpc connection only for the initial remote procedure call to claim the invite token. In this document, we describe a simpler HTTP-based invite-token system for pubs and rooms that applies before any secret-handshake connection is built.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
This specification makes clear assumptions about the setup involved peers authenticating.
Server: an SSB peer, known as the "server", MUST have an internet-public host address, MUST be accessible for secret-handshake connections under a multiserver address, and MUST support HTTPS requests as well as it MUST NOT support plain HTTP.
Client: another SSB peer, known as the "client", SHOULD be able to open a secret-handshake and muxrpc connection with the server. The user controlling this SSB peer also MUST control a web browser used to make requests to the server. The client's browser and operating system SHOULD support hyperlinks to SSB URIs, redirecting them to SSB applications that recognize and parse SSB URIs. The client's SSB application employed during SSB HTTP Authentication MUST be able to recognize and parse SSB URIs.
userId
and has an SSB app supporting parsing SSB URIsserverHost
and has generated an invite inviteCode
inviteCode
SHOULD be a URL in the format https://${serverHost}/join?invite=${inviteCode}
inviteCode
is already claimed or otherwise no longer valid, an error page SHOULD be rendered as response, and no further steps in this specification applyinviteCode
is unclaimed, and the following SSB URI MUST be rendered on the response page: ssb:experimental?action=claim-http-invite&invite=${inviteCode}&postTo=${submissionUrl}
where ${submissionUrl}
is another URL on the serversubmissionUrl
with the header Content-Type
equal application/json
and the following body: {"id":"${userId}","invite":"${inviteCode}"}
inviteCode
is already claimed, the response SHOULD be an error, and no further steps in this specification applyinviteCode
is now considered claimed for userId
, which means:
userId
and allow the client to access resources on the server, effectively making the client a recognized memberContent-Type
equal application/json
and body {"multiserverAddress":"${serverMsAddr}"}
where ${serverMsAddr}
consititutes the server's multiserver addresssubmissionUrl
response, parses ${serverMsAddr}
from the response body, and MAY use that multiserver address to create a muxrpc connection with the serverThe JSON schemas for which the response from the submissionUrl
MUST conform to is shown below.
Successful responses
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/ssb-ngi-pointer/ssb-http-invite#claimed-json-endpoint-success",
"type": "object",
"properties": {
"status": {
"title": "Response status tag",
"description": "Indicates the completion status of this response",
"type": "string",
"pattern": "^(successful)$"
},
"multiserverAddress": {
"title": "Multiserver address of the server",
"description": "Should conform to https://github.com/ssbc/multiserver-address",
"type": "string"
}
},
"required": [
"status",
"multiserverAddress"
]
}
Failed responses
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/ssb-ngi-pointer/ssb-http-invite#claimed-json-endpoint-error",
"type": "object",
"properties": {
"status": {
"title": "Response status tag",
"description": "Indicates the completion status of this response",
"type": "string"
},
"error": {
"title": "Response error",
"description": "Describes the specific error that occurred",
"type": "string"
}
},
"required": [
"status",
"error"
]
}
As an additional endpoint for programmatic purposes, if the query parameter encoding=json
is added to the invite link (for illustration: https://${serverHost}/join?invite=${inviteCode}&encoding=json
), then the server SHOULD return a JSON response. The JSON body MUST conform to the following schemas:
Successful responses
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/ssb-ngi-pointer/ssb-http-invite#invite-json-endpoint-success",
"type": "object",
"properties": {
"status": {
"title": "Response status tag",
"description": "Indicates the completion status of this response",
"type": "string",
"pattern": "^(successful)$"
},
"invite": {
"title": "Invite code",
"description": "Sequence of bytes that acts as a token to accept the invite",
"type": "string"
},
"postTo": {
"title": "Submission URL",
"description": "URL where clients should submit POST requests with a JSON body",
"type": "string"
}
},
"required": [
"status",
"invite",
"postTo"
]
}
Failed responses
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/ssb-ngi-pointer/ssb-http-invite#invite-json-endpoint-error",
"type": "object",
"properties": {
"status": {
"title": "Response status tag",
"description": "Indicates the completion status of this response",
"type": "string"
},
"error": {
"title": "Response error",
"description": "Describes the specific error that occurred",
"type": "string"
}
},
"required": [
"status",
"error"
]
}
Suppose the client has the SSB ID @FlieaFef19uJ6jhHwv2CSkFrDLYKJd/SuIS71A5Y2as=.ed25519
and the server is hosted at scuttlebutt.eu
. Then the invite user journey is:
39c0ac1850ec9af14f1bb73
was generated by the serverhttps://scuttlebutt.eu/join?invite=39c0ac1850ec9af14f1bb73
https://scuttlebutt.eu/claiminvite
with body
{
"id": "@FlieaFef19uJ6jhHwv2CSkFrDLYKJd/SuIS71A5Y2as=.ed25519",
"invite": "39c0ac1850ec9af14f1bb73"
}
{
"status": "successful",
"multiserverAddress": "net:scuttlebutt.eu:8008~shs:zz+n7zuFc4wofIgKeEpXgB+/XQZB43Xj2rrWyD0QM2M="
}
net:scuttlebutt.eu:8008~shs:zz+n7zuFc4wofIgKeEpXgB+/XQZB43Xj2rrWyD0QM2M=
The JSON endpoint https://scuttlebutt.eu/join?invite=39c0ac1850ec9af14f1bb73&encoding=json
is an alternative to the SSB URI, and would respond with the following JSON:
{
"status": "successful",
"invite": "39c0ac1850ec9af14f1bb73",
"postTo": "https://scuttlebutt.eu/claiminvite"
}
After that, the same steps 4, 5, and 6 apply.
The rendering of the invite façade HTML is unspecified on purpose. Implementors can choose to present the SSB URI either as a link, or as a code to be copied and pasted, or as an automatic redirect.
Furthermore, the invite page is a good place to render instructions on how to install an SSB app, in case the invitee is uninitiated in SSB and this is their entry point.
Specifically, these instructions can also use mobile operating systems deep linking capabilities. For instance, suppose the page recommends installing Manyverse: the page could link to join.manyver.se
(with additional query parameters to pass on the invite code), which in turn uses Android Deep Linking redirect (see this technical possibility) to open Manyverse (if it's installed) or open Google Play Store (to install the app). Same idea should apply for mobile apps, say "Imaginary App" using the fixed URL "join.imaginary.app". Desktop apps are different as they can be installed without an app store. This paragraph was informed by Wouter Moraal's UX Research for Manyverse.
A web visitor, either human or bot, could attempt brute force visiting all possible invite URLs, in order to force themselves to become an internal user. However, this could easily be mitigated by rate limiting requests by the same IP address.
ssb:experimental?action=claim-http-invite&invite=${inviteCode}&postTo=${submissionUrl}