aboutsummaryrefslogtreecommitdiffstats
path: root/src/state/mod.rs
Commit message (Collapse)AuthorAgeFilesLines
* sync: a write between signing and making a message signed tootv40 hours1-6/+12
| | | | | | | | | | | Review 4, F10: a sync message signed the node's latest change under the document's write lock (`sign_sent`), then was made under its read lock: a write committed in between went in the message unsigned, and under a policy requiring signatures waited at the peer until the next signed change came with it, or a resend. Now the message is made under a read lock taken after signing, and if a write came in between (`AppState::sign_due` again under that lock), signed again: a few times at most, past them it goes as before, its signature with the next message.
* api: a stream opened with a token ends with ittv44 hours1-0/+3
| | | | | | | | | | | | | | | | | | | | A token was checked when a request came, and a stream answered to it (the dashboard's events, a subscription's links and messages, the store's streams) then went on for as long as the client kept it open: past the token's expiry, past its user's tokens being revoked, its user disabled or removed, its session ended. Now a middleware inside `login::bearer` (`login::streams_end`) ends a streaming answer (`text/event-stream`, `application/x-ndjson`) to a request with a token at the token's deadline, its expiry, or as soon as the document no longer takes it (`token::still_taken`, the checks `verify` makes of the document, now its own function): looked at when a change touches protocol data (the root, a switch, among it), and when a session ends on the node itself in a swarm without a registry (`AppState::session_ended`). No polling: a timer for the expiry, and the changes as they come. The client reconnects with the token it has then; a refreshed token is a new one, so a stream lasts a token's lifetime at most (12 hours by default). `futures` is a dependency of every build now (for `take_until`), not only of libp2p's and Tor's.
* access: ending a single sessiontv44 hours1-0/+5
| | | | | | | | | | | | | | | | | | | | Logging out of the dashboard (`DELETE /auth/session`) only cleared the cookie: the session's tokens stayed taken until they expired, by every node. Ending them took `POST /auth/logout`, which ends all of a user's sessions. Now `DELETE /auth/session` ends the session of the token it comes with (the cookie, or a bearer token): where the swarm has a registry, it is recorded as `_continuum/sessions/<user>/<session>` (`registry::session_path`), its value until when the session would have lasted, and every node refuses the session's tokens once the change reaches it (`token::Refused::Ended`): the page's own, its narrow tokens, and one handed to another member's dashboard, all refreshed from it and keeping its ID. The rules take the entry from the user itself or one who manages users, as a logout. A remake's snapshot leaves out entries past that time, a user's map where none is left: their tokens are expired anyway. Without a registry the document can't record it: the node ending it refuses the session's tokens itself (`AppState::ended_sessions`, until it restarts), others take them until they expire, as a logout there is refused.
* signatures: sign what is senttv2 days1-25/+22
| | | | | | | | | | | | | | | | | | | A node signed its changes no rule signs a while after them (--sign-every 1s): the change left at once, unsigned, and its signature followed on its own a second later, to every neighbour, over every link. A few writes made a cascade of SIGNATURES. Under a policy requiring signatures it signed every change as it was made instead. A node now signs its latest change, if no signature vouches for it, as a sync message takes it (--sign-every sent, the default): the signature leaves in the same message, and goes where the change goes. One signature per message, however many changes it carries, each vouching for those before it: a slow writer's every change is signed, a fast writer's batches are, at no threshold to pick. A policy requiring signatures signs what is sent too, even with `none`. `all` still signs every change as it is made; changes to protocol data, listed paths and ?signed=true writes are signed as they are made, as before. The interval, and the task signing on it, go.
* state: limit refused writes per usertv3 days1-0/+24
| | | | | | | | | | A write every node would refuse is rolled back by forking the document at the heads before it, under the write lock, at a cost that grows with the document's history. A user could ask for such writes in a loop. Count each user's refused writes over the last minute. Past --refused-writes-per-minute (default 10), its writes are answered 429 without being tried, until the oldest refusal leaves the minute.
* roots: an epoch's root pinned, and named in every handshaketv3 days1-0/+4
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Review 2, N7, the care it raised: a joining node's empty document took as the first epoch's root whatever dependency-less change came first, from whichever peer it joined through; the first epoch's ID is all zeros, so nothing pinned it. A remake's epoch has its ID, the snapshot change's hash, which the rules hold its root to. * Every node names its epoch's root in HELLO and HELLO_REPLY (the epoch's ID for a remake's, its genesis change for the first; wire version 45). A node whose document starts from another root of the same epoch refuses the handshake, logging it: another history under the swarm's ID, a forged root or a genesis started again. * A joining node with an empty document takes as its root only the one it was given (--genesis-root, new), else the one its peers name, and none while they name different ones (access::screen::RootPin, kept in Screening across documents): one peer hands it another root only if it is all the joiner hears, or never, with --genesis-root. A root refused is listed with the refused, what builds on it with it. * The genesis node logs the root (`The swarm's root`); GET /admin/epochs shows it (`root`). A genesis started again under a remembered swarm ID used to merge with the nodes of it still running; they now refuse it (another root), and the docs say to join them with seeds instead. The epoch's identity stays as it is (generation 0 all zeros): records and epoch metadata name it so, and every comparison of epochs with them.
* access: a change judged by the policy it was written under, locally tootv3 days1-1/+5
| | | | | | | | | Review 2, N10: the judgement before a change leaves (1140) and a replay's (1141) skipped changes when the document *with* the change had no policy it enforces. A write removing the policy, or setting one the node doesn't know, then left unjudged, while receivers judge it at its dependencies, where the policy was enforced. Both now read the policy at the heads before the change, as receivers do.
* remake: no --follow-strict-remakes; a strict remake failing says whotv3 days1-3/+1
| | | | | | | | | | | | | | | | | A strict remake takes a majority of members whose --strict-keys take its manifest's signer: no node votes for one without, so --follow-strict-remakes (1173) goes. A node that doesn't vote still switches once the others commit, as with any remake. So that an operator can tell why one fails: a strict remake aborted for want of a majority (the voting period over, or a majority out of reach) says in its reason (GET /admin/remakes/{id}, and the coordinator's log) which members didn't vote, and that a member votes for one only with its manifest signed by a key of its --strict-keys. Each of those logs why itself (`Not voting for a strict remake`). The sim a_strict_remake_too_few_take: only a takes the manifest; the remake aborts, naming b and c.
* remake: turning a running swarm strict, keeping what an operator signedtv3 days1-0/+4
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Until a swarm is strict, the user a change names is its node's word, so any member could have planted registry entries, policy, bans or user-signed values as any user, plain or signed by a key it put there itself; writing the strict policy trusted all of it as it was. A remake of a new kind, `strict`, turns a swarm strict instead: * Its content is a manifest (access::strict): the swarm, the strict policy, and each value kept at the places values are signed, by the SHA-256 of its JSON, signed by an operator (ssh-keygen -Y sign -n continuum-strict). * Every node builds the next epoch as compaction does, keeping at the signed places only the values the manifest names, as they were at the base, and writing its policy (RemakeKind::protocol, the one kind that changes protocol data). Writes after the base reach the new epoch only by replays, judged by the strict policy. * A node votes for one only with its manifest signed by one of --strict-keys, keys given out of the document; without any, for none (logged once, `Not voting for a strict remake`), unless --follow-strict-remakes. A coordinator proposes only what it would vote for (403). Once a majority commits one, every node switches, as with any remake: refusing to switch would only cut a node off. * GET /admin/strict-manifest gives the manifest keeping everything held now, the values for review and the heads they are at; continuum-signed strict shows them, signs once confirmed and proposes at those heads. The sim a_swarm_turning_strict_through_a_remake: a member plants a registry entry as a manager; the operator's manifest leaves it out; a and b, given the key, commit; c, without, follows; the entry is gone and the policy strict everywhere.
* login: logins by key capped per usertv3 days1-0/+4
| | | | | | | | | | | | Review 6: a node remembers the challenges used until they expire, 10 000 at most, and refused every login by key once full. One user holding a key could make 10 000 logins within five minutes and keep everyone else out. A user may now log in by key at most --key-logins-per-user times (100) on a node within a challenge's lifetime, past which that user waits and others don't; the 10 000 stay as the cap for all users together. The lifetime, five minutes until now, is --challenge-lifetime.
* access: a node judges its own changes before they leavetv3 days1-3/+51
| | | | | | | | | | | | | | | | | | | | The API checks the path a request asks for; the rules judge everything the change does. Where they disagreed, the honest node's change was refused by every other node, and with signatures required the node was refused as a whole. The plan had both gates run the same judgement. Under a policy, each change a node makes (its own writes and those through the API) is now judged by rules::judge as receivers will judge it, before it is signed or sent; one every node would refuse is rolled back (the document forked at the heads before it, same actor: the change never left) and the request refused with 403 (StoreError:: Refused). rules::judge_held, judging a change the document holds, moves from swarm::refusals to access::rules for both. The simulations that play a member past its own checks write with a helper that bypasses them (forge). Review finding 3 (the same judgement at both gates).
* auth: logging in by keytv3 days1-0/+2
| | | | | | | | POST /auth/challenge and /auth/login/key: a node-signed challenge, valid five minutes and taken once, signed with one of the user's SSH keys (ssh-keygen -Y sign -n continuum-login), for a token. Auth plan M7 (key login, M2b).
* access: a node registers before it writestv3 days1-19/+83
| | | | | | | | | Under a policy, a change by a node that isn't a member is refused with all built on it: a node's registration is written before any write of its own or through the API, not after (the registration itself exempt). Auth plan M6 (fix).
* access: signatures requiredtv3 days1-1/+4
| | | | | | | | "signatures": "required": a change counts only once a signature by its committer vouches for it, and waits until then; every node then signs every change it makes. Auth plan M6.
* access: the policy enforcedtv3 days1-0/+11
| | | | | | | | _continuum/access/policy ({"version": 1}) has every node screen every change it receives, on every path changes come in. A policy a node doesn't know stops its writes through the API. GET /admin/refused. Auth plan M6.
* signatures: with sync messagestv3 days1-3/+7
| | | | | | | Sync messages carry, for each actor's writes, the latest signature the sender checked; the receiver checks it once it holds the change. Auth plan M5.
* signatures: --sign-everytv3 days1-3/+30
| | | | | | | | Changes left unsigned are vouched for by the node's next signature: --sign-every 1s (default) signs the latest within a second, all signs every change, none only those that must be. Auth plan M5.
* signatures: changes signed by their nodetv3 days1-5/+25
| | | | | | | | | | Every node signs the changes it commits with its node key, an Ed25519 signature over the change's hash, kept beside the document. One signature on an actor's latest change vouches for its earlier ones. Protocol data and paths listed in _continuum/access/signed are signed, and writes with ?signed=true. Auth plan M5.
* dashboard: without LINKS; LINKS removedtv3 days1-2/+0
| | | | | | | | | The dashboard reads every member's links from the members themselves; LINKS and LINKS_REPLY, swarm::links and their wire tags go (wire version bumped). Nodes the browser can't reach are drawn from what the reachable ones measure. Auth plan M3.
* dashboard: own links over the API, and CORStv3 days1-0/+2
| | | | | | | | | GET /admin/dashboard/links/own and its event stream: a node's own links, as LINKS_REPLY carried them, for other members' dashboards with a token. CORS for the members' dashboard origins and --cors-origins, without credentials. Auth plan M3.
* auth: logging in, and tokens as bearerstv3 days1-0/+4
| | | | | | | | | POST /auth/login (by htpasswd or the proxy), /auth/refresh, /auth/logout, GET /auth/whoami; every request may bring Authorization: Bearer instead of a password. --token-lifetime and --session-lifetime. Auth plan M2.
* access: first users at genesistv3 days1-0/+2
| | | | | | | | --bootstrap-users gives a new swarm a registry from its first change, each first user with everything; other users may do nothing until added. Without it the swarm has no registry and works as before. Auth plan M1.
* HyParView (slim)tv5 days1-4/+21
|
* dissemination traittv5 days1-0/+2
|
* topology traittv5 days1-0/+2
|
* working links recordedtv5 days1-0/+2
|
* push by defaulttv6 days1-4/+0
|
* push changes experimenttv6 days1-0/+4
|
* slow round back offtv7 days1-0/+2
|
* announce what trees misstv8 days1-0/+3
|
* add writer treestv8 days1-3/+4
|
* measure 2-hop neighbourhoodtv8 days1-0/+2
|
* add stream heartbeattv8 days1-0/+3
|
* dashboard: double-click opens peer dashboardtv8 days1-0/+3
|
* api: add x-continuum-heads to writestv9 days1-6/+10
|
* record node ID and API user for every writetv9 days1-1/+24
|
* introduce per-transport sync reply timeouttv10 days1-1/+1
|
* peer worker forget records on changetv10 days1-0/+3
|
* peer worker sleeps until deadline, not fixed timetv10 days1-0/+5
|
* bootstrap: replace pull by progress notifytv10 days1-0/+4
|
* document: allow writes during rejointv10 days1-4/+17
|
* LWW everywhere, mostlytv10 days1-0/+2
|
* state: drop epoch_txtv10 days1-5/+4
| | | | The stamp is the only lock-free epoch label.
* document: reject writes during rejointv10 days1-3/+9
|
* use Automerge as Document instead of AutoCommittv11 days1-9/+5
| | | | This allows a lot of operations to drop mut.
* move SyncSession to documenttv11 days1-3/+0
|
* move all automerge references to documenttv11 days1-19/+0
|
* sync messages are bytes outside the sessiontv11 days1-1/+1
| | | | | | | | SyncPayload is now an opaque byte container in transport. SyncSession::generate returns the encoded payload, state::decode_sync(&payload) decodes it off the document lock (as before), and receive takes the decoded message, which is a type only the session can open. transport/mod.rs no longer imports Automerge.
* commits go through the replicatv11 days1-13/+4
|
* rename AutoMerge to Documenttv11 days1-5/+5
|