aboutsummaryrefslogtreecommitdiffstats
path: root/src/api
Commit message (Collapse)AuthorAgeFilesLines
* dashboard: messages run as they left; a link breathes red for those losttv2 days1-11/+126
| | | | | | | | | | | | | | | | | | A message between this node and a subscribed one ran from this node's counts both ways: one the subscribed node sent ran once this node had counted it received, as it arrived. Each message now runs from its sender where the page reads the sender (this node's counts, a subscribed node's messages), so it runs the way it went, also where it never arrived; only one from a node the page doesn't read runs as it arrived. The console lists them as before. Where the page reads both ends of a link, what one sent the other should count received: a message sent and not received within 5 s makes the link breathe red once (`breatheLink`), its tooltip saying how many. Kinds the paths and identity layers count under addresses are left out, as this node's counts leave them out; a subscribed node's tick that came short (the latest 500 of more) starts the count afresh, so what the page didn't see isn't taken as lost.
* dashboard: messages follow the line they went overtv2 days1-27/+68
| | | | | | | | | | | | Dots and flashes ran straight from sender to receiver, whatever line the message took: a PING over another transport, drawn as a curved line beside the one in use, crossed the map off its line. Each message now runs along the line it went over, as drawn (`route`: the link's quadratic curve, its control point where renderGraph put it): a message naming the address it went to (the paths layer's PINGs and PONGs) over that address's transport's line, any other over the line in use; a flash's streak bends with it.
* dashboard: how hard links pull, on a dynamic maptv2 days1-2/+26
| | | | | | | | On a dynamic map, links pull toward their lengths against the nodes pushing each other apart, at one strength. A `pull` slider before the layout's toggle, shown while dynamic, sets it from a quarter to four times as hard (2 to its value, once at first), kept in the browser; the map moves to the new balance smoothly.
* dashboard: no dotted links for an end the page doesn't readtv2 days1-11/+7
| | | | | | | | | | A link was dotted where an end reported no links. Members used to report their links to every dashboard (LINKS); the page now reads a member's links only while subscribed to it, so a dotted link said no more than that its other end isn't subscribed, which the node shows already (hollow), and most links were dotted. The style and its legend entry go; the link's tooltip says when one end alone reports it.
* dashboard: every kind of message coloured and in the legendtv2 days1-4/+8
| | | | | | | | The map's dots and the console coloured six kinds of message; PROBE, PRUNE/GRAFT, NEIGHBOUR and REFUSALS were drawn in the text colour and missing from the legend. Each kind (`traffic`'s counters) now has a colour, and the legend names them all, saying which messages a kind stands for where its name doesn't.
* swarm: no SIGNATUREStv3 days1-1/+0
| | | | | | | | | | | | | | Signatures sent on their own (SIGNATURES, every second, to each peer not sent them yet) flooded every link: a signature made after its change left, or one that came with changes, went to every neighbour, whether or not it got the change along with its signature elsewhere. With every change of a node signed as it leaves (1217) and signatures going where their changes go (1218), none is left for them to carry: the message, its task and its traffic counter go (wire version 46). Signatures waiting for their changes are offered as before when sync messages bring them, and the changes waiting for signatures judged again with the next batch.
* state: limit refused writes per usertv3 days1-0/+1
| | | | | | | | | | 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.
* store: a write inside a list checked where the rules judge ittv3 days1-6/+8
| | | | | | | | | | | | | | Review 2, N9: the API checked a write's capability on the path asked (servers/list/3/name), the rules on the list it is in (servers/list): a user whose capability lay inside a list had every such write pass the API and refused by the node's own judgement (403, after rolling the document back). inspect::placed places a write's path in the document by the rule the inspector places operations by (Place::child: cut after the first list or text on the way), and the store's writes check capabilities there. One rule for both; a test checks placed against the paths the inspector finds in the change such a write makes.
* dashboard: a user's menu, with its credentials and logging outtv3 days1-17/+138
| | | | | | | | | | | | | | | | | | The user's name, top right, opens a menu: * "credentials…": the user's keys (GET /auth/keys), one added with its .pub line or removed but the last (/auth/keys/{id}); under a strict policy, how to sign them anew (continuum-signed key-add, key-remove); and where its password is: each node's --htpasswd, its operator's. * "log out": this browser's session ends. Where the page got in by what it can't end (the browser's password, the proxy's), it is greyed (aria-disabled, not disabled, so a click still lands), its tooltip saying how to log out, and a click says so in a dialog. The log out button and the text beside the name from 1165 go into it. Checked in Chromium against stubbed endpoints: the menu, both log out cases, the credentials dialog, no errors, nothing the content policy refuses.
* users: a user can't remove its own last keytv3 days1-0/+16
| | | | | | | | A user managing its own keys (1168) could remove the last, and then neither sign nor log in by key. It can't now: DELETE /auth/keys/{id} of the last answers 409, the rules take a user's change of its own keys only with one left after it, and under a strict policy its own entry signed only with one key at least. One who may manage users still may.
* auth: no basic authentication offered to the dashboardtv3 days1-7/+10
| | | | | | | | | What is meant for browsers logs in on its page (/login), with the passwords of --htpasswd; basic authentication's challenge stays for the API, so curl -u keeps working. 1164 kept it from a page's own requests; now nothing under /admin/dashboard is challenged either, whatever the request: a browser opening the page without a user is sent to /login, anything else answered 401 without a challenge.
* refusals: a drop taking much waits for a second member, and showstv3 days2-0/+14
| | | | | | | | | | | | | | | | | | A node told (REFUSALS) that it holds a change the swarm's rules refuse, and whose own rules refuse it too, dropped it and everything built on it on one peer's word, however much that was; its own writes among them went to the quarantine, the others' nowhere. * A drop taking more than 100 changes, or more than ten and a tenth of the document, now waits until a second member names the same change: the node's rules and one peer's word could both be off. Logged as an error, counted in /admin/stats (sync.dropsHeld). * The others' changes dropped are listed with the refused (GET /admin/refused, "taken unjudged, dropped"), for operators to see. The cheap half of asking the others (future-work.md, Telling a malicious change from a wrong policy).
* users: a user manages its own keystv3 days3-1/+118
| | | | | | | | | | | | | | | | | Only a user who may manage users could change a user's keys: a user couldn't add a new laptop's key, or remove a lost one. A user now manages its own keys, whatever else it may do: * GET /auth/keys: its keys, its entry, and the envelope holding it under a strict policy (`signed`), to sign against. * PUT /auth/keys/{id} with an authorized_keys line, DELETE /auth/keys/{id}: one change, as the user. The rules take a write of _continuum/users/<name>/keys/… from the user it names. * Under a strict policy, the user signs its entry with only its keys changed, with a key it holds already (envelope::check takes that from the user itself), PUT to its own entry even without write on the registry; continuum-signed key-add and key-remove do so.
* login: logins by key capped per usertv3 days1-1/+2
| | | | | | | | | | | | 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.
* dashboard: log out of this browser, and say what can't betv3 days2-3/+42
| | | | | | | | | | | | | | The dashboard had no way to log out; only the login page did. It now shows the user beside the connection, and "log out" where the page got in by its session: that ends this browser's session (DELETE /auth/session), other sessions of the user going on, and opens the login page. Where it got in by what it can't end, the browser's password or the proxy's, it says so instead (the token answer now names `login`, how the user proved it). After logging out, a page still let in by the browser's password says so too. A list of a user's sessions, to end one by one, is in future-work.md.
* auth: no password prompt over the dashboard's own requeststv3 days2-5/+10
| | | | | | | | | | | | | Opening a dashboard through its login page showed a 401 on /auth/login and the browser's password prompt. The page asked for its token by the browser's password first (POST /auth/login), with none to send, and the 401 carried a Basic challenge, which a browser answers with its prompt. The page now asks by its session first (POST /auth/session), then by the browser's password or the proxy's. And a 401 to a page's own requests (Sec-Fetch-Dest: empty: fetch, EventSource) carries no challenge: a page has its login of its own. Navigations and clients without the header (curl) are still challenged.
* login: the session cookie Secure behind a TLS proxytv3 days1-1/+54
| | | | | | | | | | | Review finding 12, browsers: the session cookie had no Secure flag. Behind a TLS proxy, the token it carries went over plain HTTP if someone typed http://. POST /auth/session now sets Secure when the request came over TLS, as the proxy says (X-Forwarded-Proto, or Forwarded's proto, of the first hop). A client saying so itself only keeps its own cookie off plain HTTP. operations.md's nginx example sets the header.
* dashboard: a content security policy, framed by nonetv3 days1-4/+57
| | | | | | | | | | | | | | | | | | Review finding 12, browsers: the dashboard holds a bearer token in memory and edits the document, but had no content security policy and could be framed by any page of the same site (another port on the same host, anything sharing a domain behind a proxy), acting with its user's token. The dashboard and the login page now come with a policy that runs only their own inline script (by its hash, computed when first served), loads nothing from elsewhere, and lets no page frame them (frame-ancestors 'none', X-Frame-Options DENY). Fetches stay open to any origin, as the members' dashboards are wherever the member list says. The browser check (check.js) loads the page from a file, without the header, so the policy wants one look in a browser.
* envelope: a signed value names its swarmtv3 days2-1/+3
| | | | | | | | | | | | Review 3.1, part of finding 12: a signed value's payload bound path, user, time and prev, but not the swarm. A user with the same name and key in two swarms could have an envelope replayed from one into the other, wherever the path holds no envelope yet (prev ""). The payload now names the swarm (`swarm`), and a node takes an envelope only for its own. /auth/whoami says the swarm, and continuum-signed and the example in api.md sign it. Wire version 44: nodes before this refuse such envelopes, and those after refuse the old ones.
* dashboard: hand over a token reading the member list onlytv3 days2-5/+5
| | | | | | | | | | | | | Review finding 8, second part: opening another member's dashboard handed it a token reading everything (READ_ALL). A member writes its own dashboard URL, so a malicious one received a read-everything token under the operator's identity: /admin/config, /admin/changes, /admin/log and the rest, on every node the user reaches. The token handed over now reads the member list only (READ_MEMBERS, as sent to subscribed nodes), which is enough to draw the map; the user logs in at the other dashboard to see more. With 1151 it lasts five minutes and isn't refreshed past them.
* login: a token asked to live less ends its session with ittv3 days1-7/+28
| | | | | | | | | | | | Review finding 8, first part: a refreshed token's lifetime came from the refresh request, and it kept the session's end. So the five-minute read-all token a dashboard hands to another member's refreshed into a 12-hour one, renewable for seven days. A token asked to live less (`lifetime`, at login or refresh) now ends its session with it: nobody refreshes it past its end, its holder included, and a session begun from it (POST /auth/session) ends then too.
* access: a node judges its own changes before they leavetv3 days1-0/+1
| | | | | | | | | | | | | | | | | | | | 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).
* dashboard: flashes below labelstv4 days1-3/+5
| | | | Flashes are drawn beneath the nodes' labels.
* dashboard: message flashestv4 days1-31/+93
| | | | | messages: off / dots / flash: a message expands at its own speed and leaves an afterglow fading out in the direction it went.
* dashboard: a token handed over to another member's dashboardtv4 days3-9/+52
| | | | | | | | Opening another member's dashboard passes a token reading only, for five minutes, in the URL's fragment; its login page begins a session with it. Tokens may be asked for with a shorter lifetime. Auth plan M3 (hand-off).
* access: members refused as a wholetv4 days4-3/+49
| | | | | | | | | With signatures required, a member whose own change broke a rule is refused as a whole: sent nothing, shown in /peers (refused), in stats (sync.refusedPeers) and on the dashboard, until a change of its is taken again or the epoch changes. Auth plan M6 (diverged standing).
* auth: the session cookie lets in reads onlytv4 days2-10/+27
| | | | | | | | The cookie lets in GET, HEAD and the session's own requests; anything changing something brings the token itself, which no page of another site or port can make a browser send. The dashboard's writes send it. Auth plan M3 (browsers).
* auth: a login page and browser sessionstv4 days6-18/+226
| | | | | | | | | GET /login without authentication: log in by password or with a token (from a login by key). POST/DELETE /auth/session keep the token as an HttpOnly, SameSite=Strict cookie; the dashboard sends a browser without a user there and renews the session while open. Auth plan M2/M3 (browsers).
* access: values sealed for their readerstv4 days1-0/+11
| | | | | | | | | | cs1.<age file>: a value encrypted with age to its readers' SSH keys, which no node decrypts. _continuum/access/sealed declares paths whose values must be sealed, for named readers or anyone; nodes check the recipients by age's key tags, at the API and on receipt. Auth plan M8, reworked: sealed values instead of containers, readers as users only (nodes hold no keys).
* auth: logging in by keytv4 days2-7/+101
| | | | | | | | 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: signed values judged on receipttv4 days1-1/+5
| | | | | | | | | Inspection gives each operation's kind; under strict every node refuses, at a signed place, anything but an envelope that checks and replaces the one there (deleting first as replacing does), anything within one, and above one anything replacing what holds one. Auth plan M7.
* api: signed values at the storetv4 days2-7/+278
| | | | | | | | | PUT with application/continuum-signed writes an envelope where values are signed (403/409 saying why); reads open envelopes and name the signer (x-continuum-signed-by), ?raw=true shows them. Under strict, unsigned writes reaching a signed value and the users API are 409. Auth plan M7.
* access: logouts apart from users' entriestv4 days1-4/+4
| | | | | | | | _continuum/logouts/<name> holds when a user's tokens were last revoked, written by the user itself or by one who may manage users, so users' entries can be signed by their managers alone. Auth plan M7.
* access: the policy enforcedtv4 days2-0/+42
| | | | | | | | _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.
* remake: forgetting and releasing name their usertv4 days1-4/+11
| | | | | | | Forget records and released quarantines name the user who made them, so they are judged by its capabilities. Auth plan M6.
* signatures: verified changestv4 days1-10/+16
| | | | | | | GET /admin/changes shows whether a signature by its node vouches for each change. Auth plan M5.
* signatures: on their own (SIGNATURES)tv4 days1-0/+1
| | | | | | | Signatures no sync message took go on their own within a second, to each peer not sent them yet (wire version bumped). Auth plan M5.
* signatures: changes signed by their nodetv4 days3-7/+31
| | | | | | | | | | 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.
* access: no manages flagtv4 days2-8/+3
| | | | | | | GET /admin/users no longer lists a manages flag per user: whether a user may manage users follows from its capabilities. Auth plan M1 (cleanup).
* dashboard: subscriptions under patchestv4 days1-5/+17
| | | | | | | Subscribed nodes' data merged into the state as the page patches it, not overwritten by the next patch. Auth plan M3 (fix).
* dashboard: without LINKS; LINKS removedtv4 days4-220/+272
| | | | | | | | | 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: clocks on the pagetv4 days3-143/+50
| | | | | | | | The page compares the clocks the reports on its map carry: this node's peers', and those of the nodes it subscribes to; the node no longer gathers them from LINKS (api/dashboard/clocks.rs goes). Auth plan M3 (preparing LINKS' removal).
* dashboard: the writers' trees as this node knows themtv4 days1-39/+5
| | | | | | | | The tree drawn is computed as every node computes its own part of it: from the member list, and whom it and its neighbours reach, as they told it; no longer from the links members report over LINKS. Auth plan M3 (preparing LINKS' removal).
* dashboard: subscribed nodes' messages in the consoletv4 days3-74/+139
| | | | | | | Each tick's messages of a subscribed node, listed in the console with this node's own, from its own stream. Auth plan M3 (live data from every reachable node).
* dashboard: subscribed nodes' messages as dotstv4 days3-60/+166
| | | | | | | The page reads a subscribed node's message counts from it too, and draws them as dots on the map, as this node's own. Auth plan M3 (live data from every reachable node).
* dashboard: subscriptionstv4 days1-7/+218
| | | | | | | | Double-click a node to subscribe: the page reads its own links from it directly with a narrowed token (reading the member list only), drawn over what this node knows; kept per swarm in the browser. Auth plan M3.
* dashboard: own links over the API, and CORStv4 days3-0/+202
| | | | | | | | | 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 bearerstv4 days5-5/+442
| | | | | | | | | 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: the users endpointstv4 days4-66/+438
| | | | | | | | /admin/users: list, add (one or several), remove, disable, grant and revoke capabilities; each one change, refused if it would lock its writer out. Capabilities as the store's paths name them. Auth plan M1.
* dashboard: what its user may readtv4 days1-3/+69
| | | | | | | The dashboard's document box is read through the store, as its user may, and the page is told only of changes where its user may read. Auth plan M1 (the dashboard).