Skip to content

Staff accounts

Staff in the admin lists everyone who can sign in, invites colleagues, and removes them. The admin was a single shared account until this existed: one password for a two-person shop, and no way to revoke access when someone left.

Access is granted by a single-use invitation. Only a hash of the token is stored, exactly as a password would be, and the raw token exists only in the emailed link; it works once and expires after 72 hours. When SMTP is not configured, the link is returned to the inviting admin to pass on, so a self-hosted store without email can still add a colleague.

Removing someone destroys their sessions immediately rather than waiting for a cookie to expire, which is most of the point. You cannot remove your own account, and you cannot remove the last owner; a store with no owner has nobody who can add one back.

An administrator who forgets their password can reset it by email from /admin/login: a single-use, hour-long token, and every session the account had is destroyed on reset. When SMTP is not configured the link is logged rather than sent, so a self-hosted store can still recover an account by reading the API’s log.

Registration, login and reset answer identically for a known and an unknown email.

role is recorded (owner or staff) but does not gate anything: every administrator can do everything, and the UI says so. Gating it would multiply the permission surface across every route and needs its own security-test matrix, which is a separate decision. The column exists now so that decision is not also a migration.

argon2id hashes in the database, never in a file the server rewrites at runtime.