aboutsummaryrefslogtreecommitdiffstats
path: root/src/api
Commit message (Collapse)AuthorAgeFilesLines
* auth: whoami says what the request may dotv47 hours1-0/+13
| | | | | | | | | `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.
* api: a stream opened by password or a proxy's header ends with its usertv2 days1-38/+68
| | | | | | | | | | | | 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.
* api: a stream listens for its token's end from before it is answeredtv2 days1-17/+33
| | | | | | | | | | | | | Review 4, F9: a stream answered to a token subscribed to the document's changes only when its body was first polled, and looked at the token only after a change came: one revoking it that came between the request's check and the subscription (a node applying what it was behind on) went unseen until the next change of protocol data, or the token's expiry. Now `streams_end` subscribes before the answer is made, and the token is looked at once as the stream begins (`refused_now`, the document and this node's ended sessions), then with every change of protocol data as before. A request that isn't answered with a stream drops its subscription with the answer.
* access: an ended session recorded until the login's endtv2 days1-1/+5
| | | | | | | | | | | | | Review 4, F7: `DELETE /auth/session` recorded the session as ended until the `session_exp` of the token it came with. A token handed to another member's dashboard has its `session_exp` cut to its own end (five minutes), and so has the session that dashboard keeps with it: logging out there recorded an end minutes away, the next remake's snapshot left it out, and the original browser's tokens of the same session, refreshable for days, were taken again. A token now keeps the login's end as it logged in (`Claims::login_exp`), which no refresh changes, and an ended session is recorded until then (or the token's `session_exp`, if later, as for a token from before the field).
* access: a shallow read shows only the way through where the user reads partlytv2 days1-6/+36
| | | | | | | | | | | | | 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.
* access: a token's limit narrows what an endpoint takestv2 days1-19/+62
| | | | | | | | | | | | | | Review 4, F1: `authorize` decided whether a request's endpoint was allowed by the user's full capabilities, and applied a token's limit only afterwards, to the `Access` it left for handlers. The store and the users endpoints read that; nothing else does. So a token saying less than its user may do (the dashboard's hand-over token, reading the member list only, for five minutes) did whatever the user may among the other endpoints, on every node: for an operator, forgetting members, proposing or aborting remakes, exporting the document, the quarantine, the log filter. The limit is now applied first, and the endpoint decided by what is left (`allowed`, its own function now, with a test: the hand-over token of an operator may look, and nothing else).
* api: a stream opened with a token ends with ittv2 days2-0/+82
| | | | | | | | | | | | | | | | | | | | 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 sessiontv2 days1-6/+56
| | | | | | | | | | | | | | | | | | | | 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 includedtv2 days1-2/+18
| | | | | | | | | | | | 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 betweentv2 days4-20/+36
| | | | | | | | | | | | | | | 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 authenticationtv2 days2-18/+55
| | | | | | | | | | | | | | | | | | | | 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 focustv2 days2-6/+77
| | | | | | | | | | | | | | | 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 showntv2 days3-15/+60
| | | | | | | | | | 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 consoletv2 days1-2/+7
| | | | | | | | | | | | | | | 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>`tv3 days2-27/+70
| | | | | | | | | | | | | | | | 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 pathtv3 days1-5/+3
| | | | | | | | | | | | | | | | | | 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.
* api: a log line per HTTP requesttv3 days3-5/+67
| | | | | | | | | | | | | 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 dialogtv3 days1-0/+32
| | | | | | | | | 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.
* access: keep an SSH key's commenttv3 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: no subscription to this node itselftv3 days1-1/+8
| | | | | | | | | | | | A page opened from another node's page takes that page's subscriptions along; one of them can be this node, another there. The page then followed this node as a subscribed one too, and showed its messages twice, from its own log and from the subscription (`→ LLL PING` and `LLL ← RRR PING`, the same message), on the map as in the console. The page follows no subscription of its own node: not when it knows which node it is, and one begun before that stops once it does; its messages, if any came, are left out.
* dashboard: keys edited, the last removed with a password, failures kepttv3 days4-58/+207
| | | | | | | | | | | | | | | | | | | | | 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 dashboardtv3 days1-5/+14
| | | | | | | | | | 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 legendtv3 days1-29/+43
|
* dashboard: an entry's menu left of the fingertv3 days1-4/+12
| | | | | | | Opened by a finger held on an entry, its menu had its top left corner at the finger, under the hand holding it, a right hand mostly. By touch it now has its top right corner there; opened by a pointer, its top left, as before.
* dashboard: the document box edited from its menus alonetv3 days1-45/+19
| | | | | | | | 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 boxtv3 days1-25/+192
| | | | | | | | | | | | | | 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 fromtv3 days2-25/+63
| | | | | | | | | | | | | 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 wenttv3 days1-4/+20
| | | | | | | | | | | | 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.
* dashboard: messages on one timeline, when they wenttv3 days1-28/+120
| | | | | | | | | | | | | | | | | Each node's message stream comes its own way to the page, a subscribed node's over the network to it: messages logged 16 ms apart on three nodes ran over a second apart, as their streams arrived. Messages now run when they went, on this node's clock: each at the time its node logged it, taken back by how far that node's clock is ahead of this node's (the map's clock chain, `aheadOf`), a while behind real time (`replayDelay`): the most any stream lagged in the last 30 s and a tenth of a second more, shown after the sliders (`behind: …`), each stream's lag behind the fastest in its tooltip. The browser's clock is against all streams alike and drops out. A message known only from its receiver (its sender not subscribed) is placed at its arrival, starting a crossing before. Messages lagging more than 10 s (a gap) don't run, nor hold the map behind.
* dashboard: messages as they cometv3 days2-105/+129
| | | | | | | | | | | | | | | | | | The node sent its messages to the page every 2 s (`messages` events, this node's and a subscribed node's), and this node's ran on the map from its counts, which come with the state: the map showed a message 2 to 4 s after it went, the messages of each 2 s spread evenly over the next. The message log now tells those waiting of each message logged (MessageLog::logged_after), and both streams send what was logged a tenth of a second after the first of it (MESSAGES_GATHERED): a busy node about ten events a second, a quiet one each message soon after it went. The page runs messages as their streams bring them, this node's from its own log too (with the addresses its PINGs went to), as far apart as they were sent; more than six on a link and way at once are shared out by kind over the moment, and after a gap only the last 2 s run. The counts still give the rates and what was delivered.
* dashboard: messages run as they left; a link breathes red for those losttv3 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 overtv3 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 maptv3 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 readtv3 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 legendtv3 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.