Public: anyone can read this; the crew can change it.

Accounts: the owner sees who joined, hears of each, and can suspend or remove one #682

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Closes #660

shipped production is serving it for issue #660 — its issue is already done deploy 127a6c2

opened this

Closes #660

The owner can see every account, hears of each new one, and can suspend or remove one. Signups are audited.

What changed

  • One page and one API for accounts (forge admin only; everyone else gets the 404 every admin surface gives). /settings/accounts and GET /api/v1/admin/accounts list each account newest first: when and how it joined (signup or edge, and the address), its repositories, apps and crew spend, and its standing.
  • Notified once. A new account reaches the admin's phone through the existing notifier, one line per person, with the notifier's own rate and back-off rules. No new channel.
  • Suspend (POST …/admin/accounts/:username/suspend, or the page with a confirm step). Every credential stops at once: session, token, password, edge sign-in and SSH key all go through one isActive check. What the account's repositories had queued is cancelled and their apps are stopped (the data stays). Nothing runs, deploys, wakes or restarts at boot for a suspended or removed owner: the crew dispatcher, checks, deploys, wake and the boot loop each check the owner's standing. Unsuspend restores sign-in; old tokens stay dead.
  • Remove (DELETE …/admin/accounts/:username; the page asks you to type the name). It does what suspend does, then deletes the account's repositories and their apps, drops its keys, settings, memberships and connected forges, and scrubs its email and display name. The row stays, so the name stays taken and what it said on others' threads keeps its author. An account with nothing in it can be removed the same way.
  • An admin cannot suspend or remove themselves, another admin, or an agent (agents are retired on the crew page).
  • Audit: user.created carries via and address for both ways in, plus account.suspended, account.unsuspended and account.removed.
  • Plain refusals: a refusal raised by a database trigger answers 403 with its reason instead of 500. A refused repository create or fork no longer leaves its directory behind.
  • Migration 46 adds suspended_at and removed_at with ADD COLUMN, never a table rebuild.

Tests: test/accounts.test.ts (in the gate) uses fresh outsider accounts. A non-admin and an anonymous caller see nothing and can do nothing. The list shows via and holdings. Suspension kills token, cookie, password, edge and SSH access and blocks creation. A suspended owner's queued run is cancelled and the crew queues nothing. Unsuspend restores password sign-in. Removal deletes repos and credentials, keeps the name taken and keeps others' threads. An empty account can be removed. Self, another admin and an agent are refused. The admin hears of a new account exactly once. A trigger refusal answers 403 and leaves no directory.

tsc --noEmit is clean apart from an error already on main in test/apps.test.ts.

🤖 Generated with Claude Code

commented

Review changes are in (6ad6ab6, rebased on main):

  • Live SSH sessions: every signed-in SSH session (terminal, app tty, git) now listens for account.suspended and account.removed and exits there and then (endOnSuspension in src/tui/ssh.ts). New test: an open terminal session for a fresh account ends with exit 1 and a plain message when the owner suspends it. The test fails with the call removed.
  • Migration 48, after #677's 47; #677's content-based fix to the OAuth migration test is kept.
  • Address: the signup audit now uses main's clientAddress (src/http/peer.ts) instead of a second copy of the peer map, so #697 (the real client address behind the proxy) fixes the audit with the rate limits.
  • The "verified" column: the accounts page has none yet. Email verification arrives with #659 (the crew is building it); its pull should add the column and an owner "mark verified" action here.
  • Gate: one failure, the crew stand-in test, which also fails on main.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs