aboutsummaryrefslogtreecommitdiffstats
path: root/docs
Commit message (Collapse)AuthorAgeFilesLines
* docs: federation: voters named at a remake, versions, what is uncoveredtv11 hours1-127/+216
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | What the maintainer asked of the design: * Who votes is named by the remake's proposal: all users at its base, as recommended, or a list. So those who vote say who votes next, and nobody votes by a rule of who created it. What that costs is said with it: every voter named counts toward what a decision takes, and all users is whoever brought users in. * What a policy no longer covers is uncovered at once where the decision holds: shown aside by every node, taken away by its user only, changed by nobody, and dropped once it has been seen there for `uncovered` (a number; 0: at once). Within an epoch a rule moves nothing in the document; the remake moves it. * Being seen takes nothing but a node that is up: its stamp is renewed about daily whatever else is written. A node left running keeps its voter present; naming the voters deals with that. * A release of a vote is best effort. * Continuum changing under a swarm: versions agree by what they compute, not by a number. Every stamp carries the digest of the snapshot its node would build there; a remake is taken up by the ID each node built; a node checks itself against the swarm before it writes with a new version; a change of the rules is a change of policy, and a node knows a policy by everything in it; recorded vectors every version passes. What can't change, what changes freely, and when it is a fork. * Who may connect is outside the design; the swarm key is a note. The attacks are 84. future-work.md: several roots, present by the user's own signature, a guide to moving to another swarm.
* docs: federation: lines, time from the stamps, the last decision holdstv12 hours1-493/+624
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | What the maintainer asked of the design, and what it changed: * Lines. A user is in while it has a line: a chain of creations and vouches from the root. When its creator goes, one that another vouched for stays without doing anything. The witnesses of its claims are the users on the lines it stands on: its creation's, and those of vouches it accepted (`accept`), so nobody gets a say about a user's names by vouching for it unasked. Why only its signer takes a vouch back is said where unvouching is. * Lengths of time are read between two stamps of one node, with what that node had seen at each: a voter is absent once a third of the voters saw it silent for long, a user gone once they saw it out. No clock is set against another, and why a third. * Decisions. A proposal is decided as soon as the nodes of two thirds of the voters hold its votes: at once, not at the end of a window. Any number are open at a time, within a limit a voter. Of the decisions of one matter the last holds, by which proposal was made in sight of which, so two decided at once stop nothing. A remake's base is named by its coordinator, a node of the proposer. * The policy changes by a decision, as a whole, with no remake: a remake throws history away and does nothing else. What a policy no longer covers doesn't count, and a remake keeps it aside for an epoch or drops it, as the policy says. Going apart by a fork stays for those a decision leaves behind. * What a remake leaves out, so that the document's size goes by what stands: a release is a statement in its epoch, the remake deletes. * Names are written as they are used, in the document and in the rules alike, and compared by their segments: structure, not meaning. A rule may leave a name to the voters to give. * Who vouched for whom changes nothing in who connects to whom: a node keeps its seeds and its user's other nodes as neighbours. The attacks are 83, the stages fifteen. future-work.md: guides to write (keys, the root, starting a swarm, voting, a registry), policies merged.
* docs: federation: the fractions are numbers, a decision can be refusedtv14 hours1-121/+169
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | What the maintainer answered to the design's last two questions, and what follows from it: * The fractions are numbers of the policy like the others, changed by a decision to any value: the share of the voters present a decision takes (two thirds recommended), the share of all of them (half), and the share whose stamps make the swarm's time and a user gone (a third). What is promised of decisions is argued for the recommended ones; Why it holds says what other values carry. * Refusing a decision: a node follows every decision that counts unless its operator has it refuse, by bounds on the policy's numbers or by hand. It then moves to the epoch built at the decision's history without it, which is the same for every node that refuses the same decision: those who think alike go on together without arranging it. While a proposal's votes run, a node whose bounds it breaks says so. A remake or a change of policy is refused by staying. * A decision gives a user name with its namespace, as designed. * No open question is left. The options not taken that the questions listed are among the roads not taken: first users voting as soon as created, voters let in without a remake, other reaches of an unvouch, a gone user coming back, first keys by those who vouch, a registry of only what is signed, numbers the design fixes. The attacks are 78; the stages name them by their numbers. future-work.md: bounds on more than numbers.
* docs: federation: numbers without constraint, names by a decisiontv14 hours1-171/+164
| | | | | | | | | | | | | | | | | | | | | | | | | | What the maintainer answered to the questions the design had left: * Every quantity of the design is a number of the policy, in one table with what the design recommends (the times, the limits, how much a remake's base may lack, how long the root's node lets a claim wait). The genesis policy sets each, and a decision changes any to any value: nothing constrains them. The fractions aren't numbers. * While a user is out, the voters free its names or give them to another by a decision, whatever `gone` says; a policy without `gone` has the names wait until a vouch or a decision. * Nobody is put out by a vote, said where unvouching is, with what there is against a user that harms within the rules. * A node keeps the epochs it left for as long as its operator says, `archiveRetention` at least, and says which; so a node coming back, or one joining from the genesis root, walks every epoch where an archive is within reach, and shows where the walk doesn't end at its peers' epoch. * Settled, and listed so: who votes, from where an unvouch undoes, gone for good, first keys by a decision, what the root holds unsigned. Two questions remain: whether the fractions are numbers too, and whether a decision gives a user name with its namespace. future-work.md: an archive of every epoch.
* docs: federation: the answers to its open questionstv14 hours1-218/+387
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | What the maintainer answered to the design's open questions, in the design: * The numbers. The limits and the times are the policy's numbers, set in the genesis policy and changed by a decision: 64 keys, 64 nodes and 256 addresses a user for a start; `window`, `absent`, `gone`, `archiveRetention`. A lowered limit takes nothing away. * A user nobody vouches for is out, and its names wait for it: nobody else claims them. Out for 30 days, as the stamps of a third of the voters show, it is gone: its names are free, and no vouch brings it back. So whoever relied on a vouch that an unvouch or a cut took back has that long to find another, and loses nothing meanwhile. * First keys by a decision. The voters are above every user's first keys: they give a user that lost them new ones, with a cut for the old. The promises say so, and what isn't promised: a name against two thirds of the voters. * What a decision is made of (a proposal, a vote, its release, the decision) is taken in any node's change, so a user without a node votes at whatever node it reaches, or by a vote carried to one. Such a user names another party's node to carry for it. * Coming back after remakes: by the archives on nobody's word, epoch by epoch, as far as peers keep them; else by an ID the operator gives, never because peers advertise an epoch. A statement is carried over from the epoch before only; older ones are quarantined for the user to sign again. * Resolving reads the decisions first: nothing below changes them. * The questions that remain are put with their options: who has a vote and from when, whether the voters put a user out against those who vouch for it, from where an unvouch undoes, whether gone is for good, the price of first keys by a decision, and what the root holds unsigned. The attacks are 76; the stages name them by their numbers. future-work.md: barring a user by a decision, and voting made easy for a user without a node.
* docs: a design for a swarm kept by parties that don't trust each othertv14 hours1-0/+1253
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | federation.md designs what the krebs registry needs of continuum: a swarm several parties keep, any of them possibly malicious. Nothing of it is built. It is a mode a swarm is started in: one that isn't federated stays as it is. * The parties, what an adversary can do, and what is promised to an honest node: only valid changes; the same document and the same resolved registry for the same changes; values signed by their users; one holder per name; a claim nobody takes once the root's node saw it; no waiting for anyone to write; and, with fewer than a third of the voters present malicious, decisions by two thirds of them. And what isn't: one history under a partition, a name never contested, decisions against a third, against keys stolen later, against a malicious voter on both sides of a long partition, or with half the voters gone; a swarm no forked actor splits. * The design, on the strict policy. The policy is in the root. Users come by statements that only add; a user is its creation, its keys a tree, its nodes those it names. Vouching and the line; unvouching, with what exactly it takes back. A namespace per user. Names of declared kinds claimed, with rules and limits as data, some kept for a user without a claim. Of two claims out of each other's sight, one is late where its own side can be shown to have seen the other first; where that leaves not exactly one, the name is contested, and a release gives it up. Cuts: what a key signed outside a set of changes stops counting, whenever it shows up; a key is cut off by one above it only. Bans change what those who follow read, not what anyone takes. * Decisions: votes signed by the voters, over days, by numbers the document holds and a decision changes. Who votes is in an epoch's root change: the root alone at first, then, with each remake, the users a root created, so nobody fills the votes without two thirds of those there agreeing. A time read from the document, by which those away for long don't count; a decision taken up by the voters' nodes while its votes run, and counting with two thirds of those present and more than half of all, so that honest voters never make two however long a partition lasts. The root's seat given anew by a decision, and what a root finds when it comes back. Remakes: the base is the decision's history, every node builds the snapshot from the registry as it resolves, statements are carried over, and what counted at the base is final. A change of policy as a fork each operator follows or not, as Bitcoin changes its rules; it may name who votes on its side, and is the way out for those a decision leaves behind. * What every node judges and how the registry resolves, how a partition shows and how a side comes back, links along the line, tokens of one's own party only. Why the promises hold, the roads not taken, what members are trusted with then, what is continuum's and what the application's, 69 attacks with the tests that show them, the registry's layout with moving in and writing out, fourteen stages, and ten open questions with a choice for each. future-work.md gets what the design leaves.
* document file: say which versions read a file, and the Opened recordtv26 hours1-1/+3
| | | | | | | | | | | The document file's format names itself in its first line (continuum-document/2) and each record's kind in its first byte. A record of a kind the reading version doesn't know makes the file unusable: the node doesn't start on it unless told to move it aside (--force badDocumentFile). lifecycle.md says so: a file one version wrote is read only by versions knowing every kind of record in it. The format's comment names the Opened record a node appends as it starts on the file, which undoes the clean stop's mark.
* sync: a delta's signatures as a blob of their owntv28 hours1-1/+4
| | | | | | | | | | | | | | | | | | | | | | | | A sync message larger than the threshold for its peer (16 chunks, lower on a slow link or after a message to it failed whole) goes as a delta: the peer fetches its changes in chunks, from any node holding them. Its signatures, each writer's latest with changes in it, went in the envelope offering it: up to 65536 of them, 6 MiB, in one message, the size the delta exists to avoid. Nor did an Automerge message count its signatures (one per writer in a whole document pulled) towards the threshold. A delta's signatures are now a blob of their own: the offerer packs and keeps them (Blobs::keep, the latest 64), and the spec names them by BLAKE3 hash and size (DeltaSpec::signatures, None where no signature vouches for its changes). They are served as content (BlobId::Content), so their hash checks them. The receiver fetches them from the offerer alone, as another node holding the changes holds signatures of its own, before the changes, and offers them under the write lock just before judging the changes, so they wait for them as before. The envelope of a delta carries none. An Automerge message's signatures count towards its size: past the threshold, it goes as a delta. Wire version 49.
* screen: write as a fresh actor when another node writes as this onetv30 hours1-1/+7
| | | | | | | | | | | | | | | | | | | With --keep-actor, a node on a document file that ends with a clean stop writes on as the actor the file names. Two nodes started on copies of one file (a copied data directory, a restored disk image while the original runs) both do, and both make that actor's next change number: peers holding one refuse the other as forged, and everything built on it. screen::apply_bytes, the way every received change comes in, now looks for a change of the actor the node writes as that the node doesn't hold: only another node can have made it. The node then writes on as a fresh actor at once, logs a warning, and its callers add the actor to the node's own (RemakeState::add_own_actor), which the document file's header follows. The change is judged as any other. lifecycle.md says not to start a copy of a node's data directory with --keep-actor, and what the switch leaves: what both wrote before stays refused where the other's came first.
* document file: undo the clean stop's mark before deciding the actortv30 hours1-2/+3
| | | | | | | | | | | | | | At start, a node on a document file appends an Opened record before it writes anything, so that a Closed record before it no longer says the file holds every change of the actor it names. It does so now before deciding which actor to write as, and DocumentFile::open says whether it could: where the append fails (the disk full, permissions), the file still ends with Closed, and later appends likely fail too. A crash, or a clean stop whose last append failed (close then sees the file behind and writes no mark), would leave that Closed standing for a run the file never got. So with --keep-actor, the node writes on as the actor of before only where the file was marked as opened; else it writes as a fresh actor and warns.
* access: a token with a limit changes no credentialstv31 hours1-3/+5
| | | | | | | | | | | | | | | A token's limit narrows every endpoint but those under /auth, which any registered user may call. Among them, PUT and DELETE /auth/keys/{id} change the keys the user logs in with, and POST /auth/logout ends every token of the user. With the token a dashboard hands to another member's (the member list, read, five minutes), its holder could add a key of its own and log in by it as the user, with everything the user may do. These now take a password or a token without a limit (Need::Credentials, refused where Caps::limited). A narrowed token still refreshes within its limit, says who it is, lists the user's keys and ends its own session (DELETE /auth/session): a dashboard opened from another member's logs out with it.
* example.toml: keep-actortv3 days1-0/+9
| | | | | | | With the other options of the document file: what it does, what it saves (a writer per node and per crash or drop, rather than per start), and what it must not meet: two nodes started from copies of one document file, or a node from an older copy of its own, which would write as the same actor.
* stats: the writers of the documenttv3 days1-0/+1
| | | | | | | | | | | How many writers (Automerge actors) the document's changes have matters: a writer is a node's run (each start, rejoin or drop makes one, a start keeping the one of before with `--keep-actor` after a clean stop), and every transfer of the whole history carries a signature for each. `/admin/stats` now says it (`document.writers`, and `document_writers` for Prometheus), counted from the changes' metadata alongside the saved size's measurement in the background (at least a minute apart), as that takes time proportional to the document too; an export, which measures the size alone, keeps the count of before. The dashboard's figures show it with the rest.
* document file: a clean stop marked, and `--keep-actor` writes on as the same ↵tv3 days1-1/+6
| | | | | | | | | | | | | | | | | | | | | | actor A node writes as a fresh Automerge actor at each start, so no change number of an actor comes twice: one sent before a crash but not yet appended to the document file would otherwise be made again, differently. An epoch thus gathers a writer per start, and every transfer of its history carries a signature per writer. The document file now says when it holds everything the node wrote: * at a clean stop, after the last append, an empty `Closed` record, only where the file is up to the document's heads (ready, not rejoining); * as a node starts on the file, before anything is written, an empty `Opened` record, so a crash from then on leaves a file without the mark; * the header names the actor the node writes with (`current`). With `--keep-actor` (off by default; needs `--document-file`), a node starting on a file that ends with `Closed` writes on as that actor. A crash, a drop, a switch or a rejoin still starts a fresh one, so an epoch's writers come to one per node plus one per such event. lifecycle.md says so.
* auth: whoami says what the request may dotv3 days2-3/+5
| | | | | | | | | `GET /auth/whoami` lists the user's capabilities (`caps`) and its token's `limit`, if any, side by side; what the request may do is what both give, as endpoints decide it. It says so now (`may`): the capabilities, or with a limit each capability met with each of the limit's (`Capability::meet`): at the deeper of the two paths, a `*` of one taking the other's key there, with the rights both name; nothing where the paths part, no right is shared, or the path is protocol data one of them doesn't name.
* docs: what is postponed or rejected stays in its own two filestv3 days3-7/+5
| | | | | | | | | Docs and comments outside future-work.md and review-rejections.md say what the code does, and point nowhere else for what it doesn't: the architecture page's map, the transports' forwarders, keys with `/`, a strict swarm's registry, trust.md's "Not yet" (now saying what isn't protected, in a sentence), the topology bench, and the comments of the change benchmarks, capabilities' grantor, tokens, dissemination, refusals and topology.
* signatures: a transfer carries one signature per writer in ittv3 days1-6/+9
| | | | | | | | | | | | | | | | | | | | | | Under a policy requiring signatures, a node takes a change only once a signature vouches for it: one of the change itself, or of a later change of the same writer. A writer is a node's run, each start, rejoin or drop making a fresh actor, so an epoch gathers writers for as long as it lasts, and a transfer of its whole history (a joiner pulling the document, a delta) must carry a signature for each of them, or the joiner waits for the rest for good. A message's signatures are now chosen by what it carries: * changes, up to 256 signed among them: each one's signature; * more (a whole document pulled): each writer's latest signed change among them, found by the document's change metadata (`Signatures::for_changes`), vouching for the rest; * a delta: each writer with changes in it (`get_changes_meta(from)`), its latest verified signature (`for_delta`). A message carries up to 65536 (6 MiB), and as many wait at the receiver for their changes (a delta's come after it). So a transfer leaves writers unvouched only in an epoch with more than 65536 runs writing since it began; a remake starts the count again. Wire version 48.
* docs: a strict swarm's registry against a minority of members, not a majoritytv3 days1-1/+7
| | | | | | | | | | Review 4, R3 partly: nodes refuse a strict remake of a strict swarm by its kind, a label the proposal and the snapshot's metadata carry. A majority of members labelling one `compact`, its snapshot holding the registry it wants, gets it followed: nodes whose own build differs take the quorum's snapshot. access.md says outright that a strict swarm's registry, policy and bans change by its users' signatures against a minority of members; comparing a downloaded snapshot with the node's own build at the signed places, and refusing the switch where they differ, goes to future-work.md.
* docs: basic authentication sends the password unaskedtv3 days1-1/+4
| | | | | | | Review 4, 1269's note: the node challenges for a password (`WWW-Authenticate`) only what isn't a browser, by `Sec-Fetch-*` or a `Mozilla/…` user agent. A client calling itself Mozilla that waits for a challenge before sending its password would get none: api.md says to send the password with the request, as `curl -u` does, and why.
* docs: how long a broken path takes to see, with PINGs taking turnstv3 days1-1/+5
| | | | | | | Review 4, 1258's note: the glossary says that a path whose way out broke while the peer's PINGs still come is found by this node's own unanswered PINGs, which with the turns come every one to two intervals, up to twice as long as before, and what still finds it at once.
* docs: what the request log carriestv3 days1-1/+4
| | | | | | | Review 4, 1257's note: measurements.md now says that no endpoint takes a secret in its query, but every path read or written is in the request log, and paths are data here, so the lines go wherever the node's log goes; and that a user who may set the node's log filter may turn them on.
* access: a key's comment 256 bytes at mosttv3 days1-1/+1
| | | | | | | Review 4, 1250's note: a key's comment was kept as given, unbounded but by the request limit (2 MiB), in protocol data every node holds and the dashboard shows. It says whose and where the key is: at most 256 bytes now (`sshsig::MAX_COMMENT`), a longer one refused with the key.
* docs: how signatures travel, as they do nowtv3 days1-2/+10
| | | | | | | | | | Review 4, appendix: api.md's Signed changes said a node passes on each node's latest signature (true of deltas, not of messages carrying changes) and that every change arrives vouched for. It now says what a message carries (its changes' signatures, each run's latest past 4096 and for a delta), that a node signs each of its runs' latest unvouched change at start and when its document is swapped (1277), and the two cases that still arrive unvouched (a change vouched for only by a refused one, postponed; more than 4096 runs writing in an epoch).
* screen: that a policy was held kept across a swap of the documenttv3 days1-2/+2
| | | | | | | | | | | Review 4, F12: `Screening::of` started screening over when the document was swapped (a drop, a switch, a rejoin), the root pin alone carried on, so whether the document had held a policy (`policed`, 1211) was forgotten there too, not only across a restart as documented: after a drop with the policy removed meanwhile, late changes written under it went in unjudged again. It is carried on now, as the root pin is; what belongs to the old document (changes waiting, refusals, drops held) still starts over. access.md says so.
* api: a stream opened by password or a proxy's header ends with its usertv3 days1-1/+2
| | | | | | | | | | | | Review 4, F11: only streams answered to a token ended with it (1273); one opened by password (basic authentication) or a proxy's header went on after its user was disabled or removed. `streams_end` now wraps those too, by the user `basic_auth` leaves on the answer: such a stream ends once the document no longer has its user, enabled (`token::user_taken`, the part of a token's check about its user, which `still_taken` uses too), looked at as the stream begins and as protocol data changes. No expiry: a password has none.
* roots: only the seeds' roots end the wait for themtv3 days1-3/+6
| | | | | | | | | | | | | | Review 4, R2 partly (F8): a joining node waited for "each of its seeds" to name the swarm's root as a count, of every admitted peer that named one. Node identities are self-made and admission is the swarm key: a member answering a joiner under as many identities as it has seeds, naming its own root within the wait, made the joiner pin it before the real seeds answered, and refuse them after as another history. Now the seeds are the nodes proven at the seeds' addresses (`roots::seed_found`, as `bootstrap::resolve_seeds` finds them), and only their roots count toward the wait and start its clock. Another peer naming a root still disputes it (no root while they differ), so such a member can keep a joiner from joining, as any member can, but not hand it another root. access.md says so.
* access: a shallow read shows only the way through where the user reads partlytv3 days1-1/+3
| | | | | | | | | | | | | Review 4, F4: `?shallow=true` kept every entry the user may at least pass through, and a shallow read inlines short scalars' values with their types and sizes. A scalar at a level a `*` passes through (a capability on `data/*/public`, an entry `data/count: 42`) showed its value though no capability covers it; before `*`, only a scalar where a capability expected a map did. Now an entry the user may read only partly is shown only as the way through it is: a map, with no more than its type (not its size, the number of its keys); anything else there (a scalar, a list, which a deep read leaves out too) isn't shown. Entries it may read whole are as they were.
* api: a stream opened with a token ends with ittv4 days1-1/+4
| | | | | | | | | | | | | | | | | | | | 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 sessiontv4 days2-3/+12
| | | | | | | | | | | | | | | | | | | | 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.
* dashboard: nothing shown or read after logging out, back button includedtv4 days1-1/+3
| | | | | | | | | | | | After "log out" the page went to the login page, and the browser's back button brought it back as it was (kept whole in its history): the map, the peers and the console went on updating, by the stream it still had open and the subscriptions' token it still held, though the session had ended (the document box, reading anew, got 401s). Now leaving for the login page (`toLogin`: logging out, or the login ending) stops the page's reading first: its stream, its subscriptions, its timers and tokens. And a page the browser brings back whole (`pageshow`, `persisted`) loads again instead, so it reads with the session it has now, and without one goes to the login page.
* dashboard: going to another node's dashboard without its login page in betweentv4 days1-4/+6
| | | | | | | | | | | | | | | Going to another member's dashboard showed its login page for a moment: a browser without a session there was sent from `/admin/dashboard` to `/login` (by `auth::basic_auth`), which began a session with the token handed over and went on to the dashboard. Now the dashboard's page is served without authentication, as the login page is (it holds nothing: what it shows it reads with the browser's session). Opened with a token handed over, the page begins the session with it (`POST /auth/session`) before it reads anything; without a session, `obtainTokens` goes to the login page, as the redirect did. A request without credentials is refused at once rather than after the 1.5 s that slow down guessing passwords: nothing is guessed there, and a page without a session goes to the login page without waiting twice for it.
* dashboard: tokens renewed after sleep too, never basic authenticationtv4 days1-5/+7
| | | | | | | | | | | | | | | | | | | | The page renewed its token half way through its life by a timer. A timer fires late where the computer slept or the tab was in the background, by when the token (and the session cookie, which lives as long) may have ended: the page then fell back to `POST /auth/login`, which takes a password the browser keeps (basic authentication), and without one retried every 30 s, logged out without saying so. Now whether the token is half way through its life is looked at again when the page shows and every 30 s (`refreshIfDue`, one renewal at a time), so it is renewed while it still can be, until the login ends (`--session-lifetime`). Where the login ended, or there is none (400, 401, 403), the page goes to the login page; a node away is tried again in 30 s. And the page no longer gets in by a password the browser keeps: only by its session or through a proxy naming the user (`throughProxy`, `login: "header"`). The node never challenges a browser for a password either (`WWW-Authenticate`, the browser's prompt): not only on the dashboard's paths and for a page's own requests, but for any request with `Sec-Fetch-*` or a `Mozilla/…` user agent. curl sends the password unasked with `-u`, as before.
* dashboard: a subscribed node's own figures when it is in focustv4 days2-7/+13
| | | | | | | | | | | | | | | A node in focus showed this node's `/peers` entry for it and the links it reports: what this node knows of it, never what it says of itself, though the page subscribes to it. Now a subscribed node in focus shows its own figures first (`Node:`), as this node's are (its `/admin/stats`), then this node's entry for it (`as this node sees it`) and its links, each a branch. A link in focus shows each end's paths to the other as that end has them: this node's, and a subscribed node's from its `/peers`. The page reads them from the node every 2 s while it is in focus (`readFigures`, `figuresOf`), with its narrow token, which may look at a node as any user with a capability may; pages of other origins may now call `/admin/stats` and `/peers` (`cors::open`), as they do a node's links and messages. Where they can't be read, the box says why.
* dashboard: a subscribed node's message written out, read from it when showntv4 days3-3/+32
| | | | | | | | | | A subscribed node's messages come without being written out (their stream leaves the text out: up to 8 KiB each, 500 at a time), so the figures box said only when, both ends, type and size of one. Now the node answers for one message by its number, `GET /admin/dashboard/messages/own/{seq}` (`{"text": …}`, `404` once it isn't kept), which pages of other origins may call as they do its streams (`cors::open`), and the page reads it when the message is shown, with its narrow token, and keeps it on the message. Where it can't be read, the box says why.
* dashboard: the transport each message went by, in the consoletv4 days1-1/+3
| | | | | | | | | | | | | | | The console said with whom a message went, not how: the message log was noted above the identity layer (`CountingTransport`), where a message is to or from a node, and the address it went to (the paths layer picks one, `Paths::send_first`) or came from is gone. Only the paths layer's own PINGs and PONGs, logged under their addresses, had one. Now the identity layer notes the node's messages (`MessageLog::set_addressed`), with the address each went to (`IdentityTransport::send_proven` returns it) or came from; `CountingTransport` leaves them to it, and still notes them where there is no identity layer (tests). A console line ends with the transport (the address's scheme, in its colour on the map, the address in the tooltip), and the figures box shows the address. A subscribed node's messages carry theirs too, and the map runs every message along its transport's line, not only the PINGs and PONGs.
* access: what a node holds alone by `_continuum/local/<node ID>/<name>`tv4 days2-6/+12
| | | | | | | | | | | | | | | | Setting a node's log filter took `write` on `_continuum`, all of the protocol data: whoever could change one node's logging could change the swarm's members, remakes and users too. Now what a node holds alone has paths of its own for capabilities, under `_continuum/local/<node ID>/`: `log` (`/admin/log`), `config` (`/admin/config`) and `debug` (`/debug`, always `write`), `read` to look and `write` to act (`api::access::needed`, which takes the node's ID). So `_continuum/local/<node ID>` is everything on one node, `_continuum/local/*/log` the log filter on every node, and holders of `_continuum` keep it all. Nothing is stored there: the store refuses writes under `_continuum/local`, and the rules refuse a change touching it, as for epoch metadata. The quarantine stays with `_continuum`: its entries are everyone's writes, which a node-local capability mustn't show.
* access: `*` for any one key in a capability's pathtv4 days1-3/+5
| | | | | | | | | | | | | | | | | | A capability's path covered by prefix only, so what is the same at every key of a map (each node's log filter, say, at `_continuum/local/<id>/log` from the next patch on) took a capability per key. Now a `*` segment stands for any one key, as it already does in signed paths and sealed places (`envelope`, `sealed`, which now share one `agree`): * covering (`Capability::covers`): the pattern no longer than the path, and agreeing with it segment by segment; * listings (`reaches`): pattern and path agreeing on the segments both have, so every key on the way shows, nothing past where the pattern differs; * narrowing (`Capability::holds`, for a token's limit narrowed again and a refresh's): at or below, a `*` only under a `*`: a limit naming one key never widens to all. A `*` first is outside protocol data, so it never covers `_continuum`, as `[]` doesn't. A key that is `*` itself can't be named alone. Every node judges changes by capabilities, so all must read them alike: they run the same version.
* paths: PINGs of a path take turns with the peer'stv4 days1-1/+3
| | | | | | | | | | | | | | | | | | The node's keepalives take turns between the two sides (`PeerState::keepalive_due_at`), but the paths layer pinged every path in use every `probe_every` (the keepalive interval, 30 s at least) whatever the peer did: a PING from the peer over the path didn't count. So both sides pinged each path every interval, often within a second of each other (seen on the dashboard: a PING answered, and our own PING right after it). Now a PING from the peer over a path is noted (`PathState::peer_probe`), and a path is pinged again (`rested`) an interval after the peer's PING, if that came last; after ours, an interval and a half while the peer pings too (its PING within two intervals before ours), so its turn comes first; else an interval. One exchange per interval goes over a path, as the measurements' "two keepalives per neighbour per interval" says, and each side measures every other interval; a shorter `--keepalive` measures more often. A path whose way out broke while the peer's PINGs still come in is found by our own PINGs half as often; a failed send or unanswered traffic (`suspect`) still pings at once.
* api: a log line per HTTP requesttv4 days1-1/+3
| | | | | | | | | | | | | Requests weren't logged at all, short of what a handler logs itself. Now a middleware, outermost on the API (and on a relay-only node's), logs a line per request as web servers do: method and path (with the query), status, the milliseconds until the answer began (a stream goes on after), where it came from (the address, or `unix`), and the user (`-` where none was authenticated). The user comes back on the response: `basic_auth` puts it there, for a token as for a password or a trusted proxy's header. At debug, under the target `http` (`RUST_LOG=info,http=debug`, or `PUT /admin/log`): an open dashboard alone would fill the log at info. With the target off, the request passes untouched.
* dashboard: the replay window and archive retention in the remake dialogtv4 days1-1/+3
| | | | | | | | | The remake dialog set the kind, its input, the settle time and the switch delay, but not the replay window or the archive retention. Now each has two fields, epochs and an age, their defaults as placeholders: both empty sends nothing (the node's defaults), one given sends that limit alone, as the API reads it; in the body, or for an import in the query (`replayWindow`, `archiveRetention`). Epochs that aren't a whole number are refused in the page.
* remake: replayWindow and archiveRetention in an octet-stream import's querytv4 days2-3/+14
| | | | | | | An import uploaded as `application/octet-stream` takes `rules`, `baseHeads`, `settle` and `switchDelay` in the query, but not the retentions: for those the document had to go base64-encoded in the JSON form. Now `replayWindow` and `archiveRetention` go in the query too, as JSON like the rules (`{"epochs":1,"age":"7d"}`, as in the body).
* remake: leave out earlier epochs' metadata past the replay windowtv4 days1-1/+1
| | | | | | | | | | | | | | | | | | | | | Every snapshot carried the metadata of all earlier epochs (`_continuum/epochs`) forward, so it grew by an entry, a clock per actor among it, with every remake. Only a rejoining node reads an earlier entry: a node on epoch P reads P+1's (its clock, when it was proposed, its replay window) and every one up to the swarm's epoch (kind, translation, whether it was strict). It replays only while P+1's replay window covers it; beyond, its writes are quarantined whatever the entries say. So a snapshot now leaves out the generations older than the oldest whose replay window still covers a node on the epoch before it (`snapshot::left_out`), judged by the remake's `proposedAt`: the same on every node, and earlier than any rejoining node's clock, so nothing a node can still replay through goes. Metadata that doesn't read stays, with all after it. A strict remake's metadata stays for good: by it a rejoining node, however far behind, tells a strict remake of a swarm strict already (`catchup::prepare`, which reads the generations it finds). There is at most one, short of such a remake. No new key and no change to the rejoin: a node beyond the window that finds some entries and not others quarantines with "cannot translate" rather than "outside the replay window".
* access: keep an SSH key's commenttv4 days1-2/+2
| | | | | | | | A key's line was stored as the key alone (`ssh-ed25519 <base64>`), whatever comment it came with, both from `PUT /auth/keys/<name>` and from `--bootstrap-keys`. Now its comment is kept after it (`PublicKey::kept_line`), the spaces between its words made one. The comment checks nothing: every use of a key parses the line, and the parse reads the key alone.
* dashboard: keys edited, the last removed with a password, failures kepttv4 days3-8/+20
| | | | | | | | | | | | | | | | | | | | | The credentials dialog listed a user's keys whole, to add one or remove one but the last, and any failure replaced the dialog with the error, the form lost. * A key is shown short: its type, the first and last four characters of what tells it apart (an Ed25519 key's base64 after the part all of them share), its comment; the whole line in its tooltip. * A key can be edited, its name and its line: saved under the name, a new name written first and the old one removed. * A user with a password on this node (`--htpasswd`) may remove its last key: it logs in by the password. GET /auth/keys says so (`password`), and DELETE /auth/keys/{id} takes it then; the rules no longer keep the last key on every node (no other node can tell who has a password where; under a strict policy the signed entry keeps one still). * What fails says why beside where it was asked (the add form, the edited row, the removed one), which stays as it was, to be set right and tried again.
* dashboard: a node's menu on a right-click, and going to its dashboardtv4 days1-2/+3
| | | | | | | | | | A right press on a node started dragging it, as any press did: the map took hold of the pointer, so the right-click went to the map and the node's menu didn't open, and letting go put the node in focus. The right button now opens the node's menu alone. Its entry for the node's dashboard is `go to its dashboard`, greyed where the node publishes none (and on this node's own), its tooltip saying where it goes or why not.
* dashboard: move pull slider to link legendtv4 days1-6/+8
|
* dashboard: the document box edited from its menus alonetv4 days1-14/+13
| | | | | | | | With the entries' menus (1238), editing had three ways in: the double-click, the `+` on a map's or list's row, and the menu. The double-click and the `+` go; the menu does all of it. The title, which stands for the root (its row isn't shown), now takes the right-click too: its menu adds a key at the top level.
* dashboard: an entry's menu in the document boxtv4 days1-0/+5
| | | | | | | | | | | | | | The document box edited an entry by a double-click and added to a map or list by the `+` on its row; a key couldn't be renamed, and a list's `+` always asked for an index. A right-click on an entry, or a finger held on it half a second, opens its menu: `Edit value` (as the double-click), `Rename key` for a map's key, and on a map `Add a key`, on a list `Append an item` or `Insert an item at…`. Renaming reads the value, writes it under the new key and deletes the old one (two writes, the value as JSON; a key that exists already isn't overwritten). Inside a field being edited, and where an entry offers nothing, the browser's own menu shows.
* dashboard: where a stream's lag comes fromtv4 days1-4/+7
| | | | | | | | | | | | | A stream's lag in the tooltip of `behind: …` was one figure, from when a node logged a message to when the page had it: a second and a half for a node a LAN away said nothing of where it went. The node now stamps each `messages` event with when it sent it (`{"sent": …, "messages": […]}`), and the tooltip splits each stream's lag: how long its batches waited in the node, from logging the first message to sending the batch (the node's own clock: exact), and how long they took on the way, from sending to arriving. This node's own stream is always listed, beside the three slowest.
* dashboard: the console in the order messages wenttv4 days1-3/+7
| | | | | | | | | | | | The console appended messages as their streams brought them, a subscribed node's later than this node's by its path, each line with its node's own clock's time: lines of one moment came apart, and their times read out of order. Each message is now listed by when its node logged it, on this node's clock, as the map runs them (`aheadOf`): one a slower stream brought late goes in among those of its moment. The time shown is on this node's clock; a subscribed node's own clock's time is in its tooltip.