Public Access
Merge main into the desktop redesign branch
Four conflicts. Three were two people adding to the same spot, and one was a real collision: main gave the connect bar a REMEMBER tick in the same pass that this branch took the connect bar off the hosts screen. REMEMBER is now in the drawer, beside the password box it qualifies. Nothing about the feature changed — RemembersConnectPassword, its refusal to fire until the remote has accepted the password, and the six tests over it are main's untouched — only where it is drawn. The move improves it slightly and it is worth saying why rather than claiming a merge was neutral: the bar had one row and had to fit the box, the tick, the authentication note and CONNECT along it, which is why the tick was a bare "REMEMBER" in tracked capitals. A column has room to put it under the box as a sentence, where it reads as a property of the password rather than as a fourth control in a row. MainWindowViewModel: both sides added members after ShowTerminal — the desktop's three fixed tabs here, the phone's connect menu on main. They do not interact, so both are kept, each under its own heading. TeamsScreen: main added the team's own RENAME, HAND OVER and ARCHIVE, a member's LastActive, the role a new member arrives as, and the KEY HOLDERS list. This branch had only bumped the file's font sizes a point. Resolved by taking main's file whole and re-running the bump over it, so the new controls join the scale rather than sitting a point below everything around them. README: both sides described a different head's third pass in the same paragraph. Both kept. Two things checked rather than assumed, because this branch moved the furniture the merged commits sit on. The chrome heights main's terminal work touched are the phone's, not the desktop's — 44, 42 and 24 are unchanged, so the layout harness's budget still describes the window. And main's keychain DELETE did not reach VaultScreen.axaml, whose header this branch rearranged, so the five buttons that overflowed at the larger type are still five. 2415 tests pass, up from 2369 by the 46 main brought.
This commit is contained in:
@@ -227,8 +227,8 @@ bastion — the last needs jump hosts the connection layer has not got. All thre
|
||||
|
||||
### Working as a team
|
||||
|
||||
**TEAMS** in the nav rail creates a team, adds members and shares vaults. One distinction runs through the
|
||||
whole screen and is worth having before you use it.
|
||||
**TEAMS** in the nav rail creates a team, adds and invites members, shares vaults, hands a team over and
|
||||
archives one. One distinction runs through the whole screen and is worth having before you use it.
|
||||
|
||||
**Adding somebody to a team and giving them a key are two different acts, and only the first is something
|
||||
the server can do.** Adding a member changes what the server will *serve* them: the team's vaults appear in
|
||||
@@ -245,7 +245,58 @@ published in a log every other client also reads. **It does not prove the key is
|
||||
Compare the fingerprint with them over something this server does not carry; that is the only step that
|
||||
closes it, and the success message says so every time.
|
||||
|
||||
Three limits, stated rather than discovered:
|
||||
**Somebody with no account here yet can be invited, and nothing is sent.** There is one button — **ADD
|
||||
MEMBER** — and it does whichever of the two applies, because which one applies is a fact about the server's
|
||||
account table rather than about what you are trying to do. If the directory knows the address, that account
|
||||
is added straight away. If it does not, the address is invited instead, and the status line says which
|
||||
happened, because the difference decides what you do next.
|
||||
|
||||
An invitation is a standing instruction rather than a message: the next account that signs in with that
|
||||
address joins this team, at the role you chose. There is no link and no token, because this server has no
|
||||
outbound mail path and does not pretend otherwise — telling them to go and sign in is your job, over a
|
||||
channel this server does not carry, and a link nobody can deliver would be worse than no link. An
|
||||
invitation lasts fourteen days, so an address handed on to whoever takes the job next does not carry a
|
||||
standing offer for ever; it can be withdrawn until it is taken up; and like adding a member it grants
|
||||
nothing readable, so somebody still has to press SHARE KEY afterwards.
|
||||
|
||||
The one thing the merged button costs is worth knowing. Adding an account the directory knows also hands
|
||||
you the public key you are about to verify and wrap a vault to, and an invitation cannot do that because
|
||||
there may be no key yet. So when you are adding somebody *in order to* share a vault with them, the useful
|
||||
sequence is still the same one: add them, see them appear in the members list, then share.
|
||||
|
||||
Inviting an address that already belongs to a member of the team is refused and says so. Inviting one that
|
||||
merely *has* an account here is not — that would make this a way of asking the server which addresses have
|
||||
accounts, which is not a question anybody willing to create a team first should be able to put to it. Such
|
||||
an invitation simply gets claimed sooner: within the hour, on the same sweep that records they were here,
|
||||
rather than waiting for a first sign-in that has already happened.
|
||||
|
||||
**An invitation is only claimed if your identity provider says the address is verified, and there is no way
|
||||
to relax that.** The access token has to carry `email_verified` as true. Anything else — false, missing, or
|
||||
sent under another name — claims nothing at all, and no setting turns that off: an invitation decides what
|
||||
the server will serve, and one that could be taken by anybody able to obtain a token asserting somebody
|
||||
else's address is a way into a team. **If your invitations never activate, this is the first thing to
|
||||
check.** They sit at *pending* rather than failing, the server logs a warning each time it declines to
|
||||
claim one, and the two fixes are on your side: set `Oidc:EmailVerifiedClaim` to whatever your provider
|
||||
calls the claim if it is not `email_verified`, and make sure the provider puts it in the **access** token
|
||||
rather than only in the ID token or the userinfo response.
|
||||
|
||||
**Ownership is sole, and handing it over is one act.** Transferring names an existing active member: they
|
||||
become owner and you become an admin, in a single transaction. Not two role changes — promoting first
|
||||
leaves the team owned twice, demoting first leaves it owned by nobody, and there is nobody with the
|
||||
authority to finish a transfer that stopped in the middle. You are demoted rather than removed, so you keep
|
||||
your vault key grants; removing you would revoke them and flag every team vault for rekey, and somebody
|
||||
handing over a team is usually staying in it.
|
||||
|
||||
**Archiving a team is refused while it owns a vault, and that is a limit rather than a rough edge.** A team
|
||||
vault is readable *because* of membership, so archiving a team that still owned vaults would take them away
|
||||
from everybody holding a key — including you — quietly and all at once. Nothing in this product deletes a
|
||||
vault, so there is no order of operations that gets past the refusal today, and it says so with a count of
|
||||
what is in the way rather than failing vaguely. Archiving an empty team takes its memberships and its
|
||||
outstanding invitations with it, in one transaction. Its name can be changed whenever you like; its slug
|
||||
cannot, because a slug is unique only among live teams and a rename could take one an archived team is
|
||||
still holding.
|
||||
|
||||
Four limits, stated rather than discovered:
|
||||
|
||||
- **Removing a member is not retroactive.** It revokes their grants and flags the team's vaults for rekey,
|
||||
and blocks future reads. Everything they already pulled is on their machine. Rotate the SSH credentials
|
||||
@@ -256,6 +307,12 @@ Three limits, stated rather than discovered:
|
||||
then trust silently for a host you defined. The cost is that each member approves a team host's key once
|
||||
on each of their machines. Team vaults' pins are still *listed* on the Vault screen, so you can see what
|
||||
has been trusted.
|
||||
- **LAST ACTIVE is coarse on purpose.** The server records it at most once per account per hour, so a value
|
||||
an hour old means "recently" and not "at that moment". That is the granularity the question is really
|
||||
asked at — whether somebody is still using this deployment — and writing it on every request would put an
|
||||
UPDATE on the hot path of every authenticated call for a number nobody reads that closely. It is shown
|
||||
as roughly-when rather than to the minute, because showing it to the minute would be reading a precision
|
||||
into it that is not there.
|
||||
|
||||
Items are filed into one vault at a time. When more than one vault is writable, the host and vault editors
|
||||
show a picker; it defaults to your personal vault and never moves on its own, because an item put in a team
|
||||
@@ -275,6 +332,12 @@ that design now; the desktop's own v2 is a 190-pixel labelled nav rail in place
|
||||
search box in the titlebar, and session tabs as pills. Its light theme is not built — see
|
||||
[`docs/design-import-gaps.md`](docs/design-import-gaps.md) — so the application is dark on both.
|
||||
|
||||
**A connected phone shows one bar and then the terminal.** The header, the session strip and the bottom
|
||||
bar are collapsed while a shell is up, and a single 35-pixel row replaces them: back on the left, the
|
||||
sessions as pills, and a `+` on the right offering the three connections this application can make — a
|
||||
shell, a host's files over SFTP, or a bucket. The system back gesture does what the arrow does, and lowers
|
||||
that menu first if it is open.
|
||||
|
||||
Widening the rail moved the desktop window's minimum from `880x560` to `1016x574`, which leaves every
|
||||
screen exactly the width it was designed against.
|
||||
|
||||
@@ -288,9 +351,18 @@ design's blue-tinted `#E3E7F4`, and the type scale is a point larger.
|
||||
File transfer **is** here now, in the shape scoped storage allows: one remote pane and the queue, over
|
||||
either an SFTP host or a bucket. There is no local pane, because there is no browsable local filesystem to
|
||||
put in one — moving a file *in* from the phone needs the system document picker and is the next piece of
|
||||
work rather than a thing the screen pretends to do. What is still absent is a host editor and a keychain
|
||||
item editor, so hosts and keys are created on the desktop and sync down; pins, teams and import have no
|
||||
phone screen either. Importing an `~/.ssh/config` has no meaning on a phone at all.
|
||||
work rather than a thing the screen pretends to do. Hosts and groups are made and corrected here now, from
|
||||
a floating + on the Hosts screen, and both editors are cards in the list's own row rather than dialogs, so
|
||||
the form never covers the thing it is about. The keychain has no editor of its own: SSH keys and buckets are
|
||||
created on the desktop and sync down, and the phone will delete an item — behind the same counted
|
||||
confirmation — without offering to change it. What this head does make, it makes where the need arises
|
||||
rather than in an editor: a tag from inside a host's editor, and a credential from the connect bar's
|
||||
remember tick, which stores the password just typed and moves the host onto it. Renaming either is still a
|
||||
desktop job. Pins and import have no phone screen either, and importing an `~/.ssh/config` has no meaning
|
||||
on a phone at all. **TEAMS does have one**, behind MORE, and it is there for a reason the design could not
|
||||
have anticipated: an invitation is claimed by signing in, so somebody being told they have been put in a
|
||||
team is at least as likely to be holding a phone as sitting at a desktop, and a membership visible only on
|
||||
a head they have not installed is a membership they cannot see.
|
||||
|
||||
**Port forwarding is not built anywhere**, and the phone's More screen says so in a paragraph rather than
|
||||
leaving a gap. The v2 design draws a whole screen for it; nothing in the SSH layer forwards anything, so
|
||||
@@ -492,7 +564,10 @@ keychain plus a terminal — and the spike that gates all of it.
|
||||
append-only key log served for clients to verify against, team-owned vaults, and vault key grants
|
||||
wrapped by a client and stored opaquely by the server. `VaultAccessService` now resolves team
|
||||
membership to permissions, so a viewer may pull and may not push; the desktop client reads and syncs
|
||||
every vault it holds a key for, and a real TEAMS screen replaces the placeholder. See
|
||||
every vault it holds a key for, and a real TEAMS screen replaces the placeholder. A team can be renamed,
|
||||
handed to another member, and archived once it owns no vaults; a member row carries when that account was
|
||||
last here; and an address with no account on this deployment can be invited, joining the moment somebody
|
||||
signs in with it. See
|
||||
[Working as a team](#working-as-a-team) for the one distinction the whole design rests on, and the limits worth
|
||||
knowing before you rely on it; the reasoning is in
|
||||
[ADR 0009](docs/adr/0009-team-access-model.md).
|
||||
@@ -501,8 +576,14 @@ keychain plus a terminal — and the spike that gates all of it.
|
||||
every team vault `RekeyRequired`, and nothing acts on that flag. A rekey re-wraps every item's data key
|
||||
under a fresh vault key and can only be performed by a client that holds the current one; that is M5's
|
||||
key rotation. Until it lands the flag is what the interface reads to say a rotation is owed, which is
|
||||
more honest than a button that only appears to do it. Ownership transfer is absent for the same kind of
|
||||
reason — the owner cannot be removed or demoted, because nothing can appoint a replacement.
|
||||
more honest than a button that only appears to do it.
|
||||
|
||||
**Ownership transfer is here, and it is one write rather than two.** The member you name becomes owner
|
||||
and you become an admin, in a single transaction — because ownership is sole, so promoting first leaves
|
||||
the team owned twice and demoting first leaves it owned by nobody, and there is nobody left with the
|
||||
authority to finish a transfer that stopped in the middle. Nothing else is touched: you keep your vault
|
||||
key grants, because removing the outgoing owner would revoke them and flag every team vault for rekey,
|
||||
which is a much larger act than the one being asked for.
|
||||
- **M4 — hardening and ops**, packaging, self-hosting guide.
|
||||
- **M5 — multi-provider OIDC**, key rotation, per-item content keys.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user