Files
Felitendo c1c478bf25 feat: make private mode an account rather than a code
Private mode asked for a six-digit code, minted per person and traded for a pass
that a browser kept in a cookie and the app in its keychain. Accounts arrived
later, for the boards and the setup backup, and from that point the code was a
second credential doing a worse version of the same job. It said you may look and
nothing about who was looking, so it could not carry what somebody wrote, it
could not be signed out of from another device, and six digits is a space small
enough that it had to be defended by a throttle doing most of the work.

So the account is the credential now. The landing page asks for a name and a
password instead of drawing a keypad, the feed and the token downloads want the
same session, and the store's own screen for being shut out is the sign-in it
already had. One credential, on both sides of the wire, and the same one
somebody needs before they can post or keep a backup.

What changes on the way in is who may register. Public: anybody makes an account
from the app, as before. Private: an operator mints an invitation and sends the
link, and opening it is the only way a new account happens. One link, one
account, spent the moment it works, so a forwarded link is not a second way in.
The redeem is filtered on being unspent, so two people racing one link end with
one account and one refusal. It is shown once, it can be withdrawn while nobody
has used it, and it expires if nobody does.

The token is 128 bits and stored as a digest, which is the opposite of the choice
the codes had to make: a code was a candidate list somebody could work through
offline, so it needed a slow hash and could not be looked up, where a token is no
list at all.

Withdrawing a link is deliberately not the same action as removing somebody. Once
it has been redeemed there is an account, and taking that away is muting or
deleting it on the board's own screen, where the things they wrote are in view.

This locks out every device a code had let in, and there is no way around it: a
pass records that a code was answered on some device and nothing about who held
it, which is exactly the gap accounts close. The two tables are dropped in
pre-push.sql, where every destructive change here is written out. A public
deployment is untouched.

The throttle stays and gains a second half. The ladder against the address is
what it was, minus a ceiling that came down when the codes did; the ladder
against the name being signed in to already existed for accounts and now bounds
somebody working through one person's password from anywhere they like. Only a
wrong answer climbs either, so being told a name is taken is not a step towards
a lockout.

Both consoles move together, as they have to: the invitations, the accounts and
the devices are the same five sections in the same order in the dashboard and on
the phone. The alert for a new device compares a moment rather than an id, since
a session's key is the digest of a token and there is no id to compare.
2026-09-01 09:27:34 +02:00
..