# HTTP Invites Author: Andre 'Staltz' Medeiros Date: 2021-04-26 License: CC0-1.0 ## Abstract 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. ## Terminology 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](https://tools.ietf.org/html/rfc2119). ## Conditions 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](https://github.com/ssb-ngi-pointer/ssb-uri-spec), 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. ## Specification 1. Suppose an SSB user (known as "the client") has the SSB ID `userId` and has an SSB app supporting parsing SSB URIs 1. Suppose the server is hosted at domain `serverHost` and has generated an invite `inviteCode` 1. The invite link corresponding to `inviteCode` **SHOULD** be a URL in the format `https://${serverHost}/join?invite=${inviteCode}` 1. When the client visits that URL in a browser, the server **MUST** respond with HTML such that: 1. If the `inviteCode` is already claimed or otherwise no longer valid, an error page **SHOULD** be rendered as response, and no further steps in this specification apply 1. Otherwise, the `inviteCode` 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 server 1. The client's SSB app **SHOULD** parse the SSB URI and subsequently **SHOULD** send an HTTPS POST request to the endpoint `submissionUrl` with the header `Content-Type` equal `application/json` and the following body: `{"id":"${userId}","invite":"${inviteCode}"}` 1. The server receives the POST request and: 1. If the `inviteCode` is already claimed, the response **SHOULD** be an error, and no further steps in this specification apply 1. Otherwise, the `inviteCode` is now considered *claimed* for `userId`, which means: 1. The server **SHOULD** store the client's `userId` and allow the client to access resources on the server, effectively making the client a recognized member 1. The server **MUST** respond with header `Content-Type` equal `application/json` and body `{"multiserverAddress":"${serverMsAddr}"}` where `${serverMsAddr}` consititutes the server's multiserver address 1. The client receives the `submissionUrl` response, parses `${serverMsAddr}` from the response body, and **MAY** use that multiserver address to create a muxrpc connection with the server 1. If the server receives a muxrpc connection from the client, it **MUST** authorize it and grant them internal access 1. The client is now authenticated The JSON schemas for which the response from the `submissionUrl` **MUST** conform to is shown below. **Successful responses** ```json { "$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** ```json { "$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" ] } ``` ### Programmatic invite façade 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** ```json { "$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** ```json { "$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" ] } ``` ### Example Suppose the client has the SSB ID `@FlieaFef19uJ6jhHwv2CSkFrDLYKJd/SuIS71A5Y2as=.ed25519` and the server is hosted at `scuttlebutt.eu`. Then the invite user journey is: 1. Invite code `39c0ac1850ec9af14f1bb73` was generated by the server 1. The corresponding invite link is `https://scuttlebutt.eu/join?invite=39c0ac1850ec9af14f1bb73` 1. When the client opens that link in a browser, it renders a link to the SSB URI [ssb:experimental?action=claim-http-invite&invite=39c0ac1850ec9af14f1bb73&postTo=https%3A%2F%2Fscuttlebutt.eu%2Fclaiminvite](ssb:experimental?action=claim-http-invite&invite=39c0ac1850ec9af14f1bb73&postTo=https%3A%2F%2Fscuttlebutt.eu%2Fclaiminvite) 1. The client's SSB app processes the SSB URI and makes a POST request to `https://scuttlebutt.eu/claiminvite` with body ``` { "id": "@FlieaFef19uJ6jhHwv2CSkFrDLYKJd/SuIS71A5Y2as=.ed25519", "invite": "39c0ac1850ec9af14f1bb73" } ``` 1. The server accepts the POST request, and responds with the JSON body ``` { "status": "successful", "multiserverAddress": "net:scuttlebutt.eu:8008~shs:zz+n7zuFc4wofIgKeEpXgB+/XQZB43Xj2rrWyD0QM2M=" } ``` 1. The server now recognizes the client as an authorized member for any subsequent secret-handshake and muxrpc connections at the multiserver address `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: ```json { "status": "successful", "invite": "39c0ac1850ec9af14f1bb73", "postTo": "https://scuttlebutt.eu/claiminvite" } ``` After that, the same steps 4, 5, and 6 apply. ## Implementation notes 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](https://stackoverflow.com/questions/28744167/android-deep-linking-use-the-same-link-for-the-app-and-the-play-store)) 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](https://www.manyver.se/ux-research/). ## Security considerations ### Malicious web visitor A web visitor, either human or bot, could attempt brute force visiting all possible invite URLs, in order to authenticate themselves. However, this could easily be mitigated by rate limiting requests by the same IP address. ## References ### Normative - [SIP 1](./001.md) "SSB URIs" ### Informative - [UX Research for Manyverse](https://www.manyver.se/ux-research/) ### Implementation - [ssbc/go-ssb-room](https://github.com/ssbc/go-ssb-room/) server in Go - [ssbc/ssb-http-invite-client](https://github.com/ssbc/ssb-http-invite-client) client library in JavaScript ## Appendix A. List of new SSB URIs - `ssb:experimental?action=claim-http-invite&invite=${inviteCode}&postTo=${submissionUrl}`