Accounts, so the board knows who did what
You will be able to turn on sign-in for a Commission server several people share, create accounts for them, and have every action recorded against the person who took it rather than against the process.
Audience: whoever runs commission serve for a team. Assumes: a project
with some work in it, as built in planning.md.
You probably do not need this
Commission defaults to auth.mode: open, which means the server acts as one
identity — whoever started it — and asks nobody to sign in. On a laptop that is
correct, and it stays the default forever. A single user should not have to run
an account system to look at their own board.
Turn accounts on when the answer to “who did this?” stops being obvious: when more than one person can reach the server, or when it is not on localhost.
Create the people first
Turning on session mode before anybody has an account locks everyone out,
including you. Accounts come first:
commission user add dana --name "Dana Ruiz" --password "correct horse battery staple"
commission user listThe first account is created by whoever is holding the terminal, and becomes an owner. It has to work that way: creating an account needs permission, permission comes from a role, and a role is set on an account — so if the first one were gated there would be no command that could ever make the second.
It is also the only account created this way, because it is the only one where the person typing the password is the person who will use it.
Passwords need twelve characters and nothing else. Length is the only rule that
reliably helps, and the character-class rules everyone else imposes mostly
produce Passw0rd!.
Everybody else gets a link, not a password
commission user add --password for somebody else means you choose their
password and then have to send it to them — over Slack, in plain text, where it
stays forever and is readable by everyone in the channel. Hashing it in the
database does nothing about that copy.
So invite them instead:
COMMISSION_ACTOR=dana commission user invite priya --name "Priya Raman" --role maintainerThat creates the account and prints one URL:
http://localhost:4400/invite/cmi1_8WH72ZEMRX4u3_E9D7rYUiDGuWSIhUAet1JHUU8HLgY
send that to Priya Raman — it works once, and expires 2026-08-06
they choose their own password, so you never learn itPriya opens it in a browser, types a password twice, and is signed in on the board. No terminal, and nobody else ever knows her password — which is the whole point. She needs no account to reach that page; it is the one page in Commission that a request with no credential may use.
The link is safe to send over the same channels a password was not:
- single use — redeeming it spends it, so a link sitting in a mailbox after she has joined is inert. If a second person opens it, they get a page saying it has already been used and telling them to say so.
- it expires, in seven days by default (
--days 30if a week is not enough for somebody on holiday). - re-inviting replaces it. Sent it to the wrong address? Run the command again: the new link works and the previous one stops.
Commission sends no mail. --email records an address on the account, and
nothing is ever sent to it — delivery is whatever you already use to talk to the
person, and Commission never learns whether the address is real.
The URL in the link is guessed from where commission serve listens by default.
If the dashboard is somewhere else, say so — once, in the environment, rather
than as a flag you have to remember:
COMMISSION_WEB_URL=https://commission.example.com COMMISSION_ACTOR=dana commission user invite jae --role observerThere is no self-registration. Commission is installed by a team rather than
signed up for, so every account traces back to somebody who could already
administer the install — and commission user list shows who has been invited
and not yet arrived, as invited.
Somebody locked out
Re-invite them. It issues a fresh link, they choose a new password, and the old one stops working when they do:
COMMISSION_ACTOR=dana commission user invite priyaThe blunter instrument still exists for when you cannot wait, and it has the cost this whole section exists to avoid — you will know their password:
COMMISSION_ACTOR=dana commission user passwd priya --password "a brand new passphrase here"Neither one ends the sessions the old password opened. If the reason for the change is that it leaked, end them explicitly:
COMMISSION_ACTOR=dana commission user signout priyaWho may do what
| observer | contributor | maintainer | owner | |
|---|---|---|---|---|
| read the board | ● | ● | ● | ● |
note, ask, need | ● | ● | ● | ● |
| claim and move work | ● | ● | ● | |
| approve and verify | ● | ● | ● | |
| edit and cancel | ● | ● | ● | |
| manage accounts | ● | ● |
observer is the role this exists for: a PM sees everything and can raise
anything, without moving work around. It is not a lesser engineer — it is a
different job, and commission need is a real lever rather than a consolation.
The other rows look generous, and that is deliberate: everything except account management already worked for everybody before roles existed, and an upgrade does not get to quietly withdraw permissions from a team that never asked for them. A team that wants tighter separation makes people observers, or waits for the enforcement to tighten in a release that says so.
COMMISSION_ACTOR=dana commission user role priya --role observerTurn it on
{ "auth": { "mode": "session" } }in .commission.json, or COMMISSION_AUTH_MODE=session for a single run. Then
commission serve as before. Visiting any page now redirects to /login, and
the API answers 401 with the two ways to get a credential.
The one exception is /invite/<token>, which has to work for somebody who has no
credential yet — that is what an invite is. It grants nothing but the ability to
set that one account’s first password, and only until the link is redeemed or
expires.
Reads are gated too. Before this there was no read authorisation of any kind — every GET on both the API and the web view was open to anyone who could reach the port. If that was acceptable because the port was not reachable, it is worth checking whether that is still true.
Scripts and agents get tokens, not passwords
Something running in CI cannot sit at a login form. Issue it a token:
COMMISSION_ACTOR=dana commission user token priya --label "release pipeline"The token is printed once, on stdout, so it can be piped straight into a
secrets store. Only its hash is kept — a lost token is reissued, never
recovered. Send it as Authorization: Bearer <token> and every action it takes
is attributed to the person who owns it, exactly as if they had done it.
Give each script its own labelled token. They are cheap, and revoking one should never mean revoking all of them.
What is actually stored
Nothing you could use to sign in as somebody.
| stored as | |
|---|---|
| password | argon2id hash |
| API token | sha256 hash |
| session | sha256 hash of the token, plus its expiry |
| invite link | sha256 hash of the secret, plus its expiry and whether it is spent |
A sessions table containing the tokens it issued would be a table whose backup is a set of live logins, and whoever read it could act as anybody in the company. Storing the digest costs one comparison and removes that entirely.
Sessions are rows rather than self-contained signed tokens, which is what makes
commission user signout take effect immediately instead of whenever the token
would have expired.
What changes for anything already talking to the API
In open mode: nothing. Byte for byte.
In session mode, a POST /v1/... is recorded as the caller, where it used
to be recorded as the server process. That is the entire point — but it means an
integration that relied on everything being attributed to one identity will see
its own name on its work instead. It is a change in what gets recorded, not in
what is permitted — see the table above for that.
The whole sequence
commission user add dana --name "Dana Ruiz" --password "correct horse battery staple"
COMMISSION_ACTOR=dana commission user invite priya --name "Priya Raman" --role maintainer
COMMISSION_ACTOR=dana commission user role priya --role observer
COMMISSION_ACTOR=dana commission user token priya --label "release pipeline"
commission user list
COMMISSION_ACTOR=dana commission user signout priya
This page is docs/guides/accounts.md in the Commission
repository, rendered in place — the site keeps no copy of it. The repository is private, so there is no edit link to follow.