diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/MESHBAY_DESIGN.md | 82 |
1 files changed, 73 insertions, 9 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index 371163e..4dcd81c 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -420,14 +420,54 @@ being bob on his first connection: | Safety-number comparison | Nothing — but it needs two humans reading digits at the worst moment | correct, unusable as a default | | **One-time pairing code** | **Nothing: the code never reaches the hub** | **adopted** | -**An invitation link is the one code that names no account**, because it goes to -someone who may not have one yet. At the node it is a bearer code — a member of one -group, once, never an operator, never for someone already in the group, at most -twenty outstanding per group, cancellable (`MESHBAY_NODE_PROTOCOL.md` §8.6). Who may -bring it is decided before the node is reached: the hub admits only the account whose -verified address the inviter named, and the client sends the code only to the node -the link names, once that node has signed its challenge (§5.2). The node half is -built; the hub ticket and the client are not yet (§15.2, `docs/invite-links.md`). +**Invitation links.** A link lets somebody who has no account yet register and land +in the group without typing a code. The code above cannot do that: it names an +account when it is issued, and someone who has not registered has none. So a link +carries **two secrets with two jobs**: + +| Half | Secret | Checked by | Grants | +|---|---|---|---| +| Hub ticket | 128 random bits, stored as `sha256` | the hub, against the redeeming account's **verified address** — the blind index of the address the inviter typed | membership, i.e. the right to *reach* the node (§7.1). Nothing about keys | +| Node code | the ordinary 40-bit code, with `kind = "link"` | the issuing node only | pinning and the group key, as for any code | + +``` +https://<hub>/#/invite?v=1&g=<group_id>&t=<ticket>&n=<node_pk, base64url>&c=<CODE> +``` + +Everything is after `#`, so no part of it is sent to the hub in a request; the page +reads it and rewrites the address to `#/invite` at once. `g` lets the client check +that the group the hub answers is the one the inviter meant; `n` names **the node that +issued the code**, which is the only node holding it. + +At the node the code is therefore a **bearer code**, and everything else about it is +fixed: a member of one named group, once, never an operator, not spendable by +someone already an active member of that group, at most twenty outstanding per +group, cancellable by a handle unrelated to the code (`MESHBAY_NODE_PROTOCOL.md` +§8.6). `kind` is a column rather than an empty account id, so a row bound to nobody +can never be read as bound to anybody (**AV1**). + +The rules the client follows, each of which a test holds: + +- it sends the code **only to the node the link names, and only once that node has + signed its challenge with that key** (§5.2). Without the signature the code went to + whichever peer answered signaling, and in a group hosted twice, to whichever host + answered first; +- it never sends the code to the hub (unless the inviter asked the hub to mail it — + below), and it keeps the pending invitation in the tab's `sessionStorage` only, + cleared on success, refusal, expiry and sign-out; +- joining is **one explicit click** after the invitation is shown — group, inviter — + never automatic, or a link would add anyone to any group without asking; +- a link whose origin is not this hub is refused (the desktop client accepts one + pasted in), so a ticket is never sent to another hub. + +What it holds against, per the convention at the top. **A third party holding the +link** — a messaging service that previews it, a forwarded mail — gets nothing +without the invitee's mailbox. **An active hub** is not stopped: it verifies the +addresses it compares, and could already be anybody. **A hijacked signaling path** +no longer receives the code on a native client; a browser is still **T3**, exactly as +for a typed code. What it costs: the hub learns that an owner invited an address +(as a blind index and a masked form, until the link is used or expires). Addresses +are compared case-insensitively and nothing more, so `+tags` and dots must match. **Mailing the code is the inviter's choice, and it gives up the row above.** The Members tab can ask the hub to mail the code to the invitee's address on file @@ -439,6 +479,15 @@ checked by default, and is **remembered per account** (the `invite_email` preference), so an operator who unticks it once is not asked to again. Unticked, the hub is never called and the table above holds exactly. The CLI mails nothing. +The same box sits under the link form, sharing the same preference. Ticked, the hub +mails the link to the address the ticket is bound to — no second address, and +**the hub writes the link itself** from its own origin, the ticket it has just +minted, and the node key and code it is handed, each checked against its exact +shape, so a hub-signed mail cannot be made to carry any other URL. Because that mail +reaches an address the hub may have no relationship with, at the request of anyone +who owns a group, it is also counted per sending account (**AV29**). A refused mail +does not cancel the link, which is shown either way. + **Delegation is designed and deferred.** `invite_create` is authorised as a *role* check against the roster rather than an equality test against the operator, and the `delegate` role value is reserved, so a group admin who does not run the node @@ -1752,6 +1801,20 @@ staff exempt. Public groups are the ones that cost other people something: they appear in the directory and are brokered to strangers. The check is at creation only, which is correct because the update endpoint refuses to change visibility. +**An invitation link's hub half is a ticket, and it grants membership to one +account.** `group_invite_links` holds `sha256(ticket)`, the invitee's address as +the same blind index `users` has, and a masked form for the owner's list — never +the address. Redeeming requires a signed-in account whose verified address has +that index; anyone else holding the ticket is told only that it is not for this +account, and an unknown, used, expired or cancelled ticket, or a group no longer +active, is one uniform refusal. Creating a link says nothing about whether the +address has an account (**M1**). At most twenty outstanding per group, as on the +node; a lifetime clamped to thirty days; a node token may create one for its own +operator's group (the CLI) and may not ask for mail. What the binding holds +against, per the convention: **third parties** — a messaging service that +previews the link, a forwarded mail — and not this hub, which verifies the +addresses it compares and could already be anybody. + **Which nodes host a group is answered to its members.** For a public group that is everyone, which is what public means; for a private one it is the membership row and nothing else. Answering any authenticated account — as it did while only @@ -3048,6 +3111,7 @@ had already been asked. | **AV25** | **Which nodes host a group is answered to its members** (§7.3). Only the public case checked, so a private group told any authenticated account that knew its id which machines hosted it — and an ex-member knows that id for ever | | **AV26** | **A sign-in lockout refuses passphrase sign-in and nothing else** (§7.7). It is keyed by username, usernames are public, and so anyone can spend somebody else's attempts. Open sessions, renewal and device sign-in are untouched and a reset code ends it, which bounds what a stranger buys to one forced sign-in. The lockout is a DoS primitive by construction; this is the ceiling on it | | **AV27** | **A free-text third-party search is bounded per member and per node** (§6.5). `tmdb_search_req` spends the *operator's* credential, which TMDB rates and the whole group's automatic matching depends on, so one member holding a search box degrades the library for everyone. Per member and not per connection — three tabs is one person — and kept in the group context so a reconnect does not reset it. The refusal is an error, because an empty result list is what "no such film" looks like | +| **AV29** | **An invitation link is bounded on both halves and its mail on the sender** (§3.4, §7.3). Twenty outstanding per group on the node (bearer codes) and on the hub (tickets); and because a link mail reaches an address the hub has no relationship with, at the request of anyone who owns a group, it is counted **per sending account per day** (`mail.invite_link_daily_cap`, 10), under the recipient and instance bounds and outside the recovery reserve (`invite_link` is not a recovery purpose) | | **AV28** | **How many node keys one account may announce is bounded** (§7.2). Each is a row plus an IP-log row under a one-year retention, so an account in a loop writes a year of storage on the operator's disk having paid only for signatures. Proof of possession (**M8**) settles whose key it is and not how many. Counted only where a row is added: re-announcing a key already held keeps working at the ceiling, or a node that reached it could never refresh its address again | ### 13.6 Chat design findings @@ -3200,7 +3264,7 @@ process runs it — `systemctl --user` on Linux, Task Scheduler on Windows. | — | DLNA/UPnP casting (§11.4) | | — | **Bitmap subtitles** (PGS, VOBSUB — about a fifth of the embedded streams). No WebVTT without OCR; they are not listed rather than listed and blank. Burn-in covers them and costs `-c:v copy`, which is what the eight-slot sizing assumes never happens | | — | Delegation (§3.4) | -| — | **Invitation links** (§3.4). The node half is built — the unbound code, its signed ops and the join path; the hub ticket bound to an address and the client that reads the link are not (`docs/invite-links.md`) | +| — | **Invitation links** (§3.4, §7.3). The node half (the unbound code, its signed ops, the join path) and the hub half (the ticket bound to a verified address, its mail) are built; the client that creates and reads a link, and the CLI's `--link`, are not | | — | Tier 3 roster attestation (§3.3) | | — | Android client | | — | **Federation between two hubs.** The protocol is written and switched off in the code (§7.6); what is not built is one run between two machines | |