# Admin sign-in `/admin` (reported posts, profiles, season points, the Boosted lane, community pairs) is for the wallets in the `ADMIN_WALLETS` secret. Code: `app/src/lib/admin/`. ## Signing in Without an admin session, `/admin` is a sign-in card and nothing else: no panel and no admin data is rendered. 1. Connect an admin wallet and press **Sign in**. 2. The server issues a nonce (`POST /api/admin/nonce`, single use, 5 minutes) with its own time. 3. The wallet signs a standard Sign-In with Ethereum message (EIP-4361) for `openlaunch.lol`, on Base. Wallets that support the standard compare its domain with the site asking for the signature and warn on a mismatch, so a look-alike site cannot quietly collect an openlaunch sign-in. 4. `POST /api/admin/session` accepts only the exact text the site builds: this domain and URL, Base, the fixed statement, a nonce this server issued, issued within 5 minutes. The signer must be in `ADMIN_WALLETS`. The nonce must be open before the signature is checked (a made-up one costs no RPC) and is spent only after it checks out. Smart wallets are checked on Base. ## The session - An 8-hour session, never extended. A random 32-byte token sits in a `__Host-ol_admin` cookie: HttpOnly (no page script can read it), Secure, SameSite=Strict, Path=/, no Domain (bound to openlaunch.lol exactly, never basebid.lol). - The database (`bb_admin_sessions`) keeps only the token's SHA-256, a rough device label ("Chrome · macOS") and the times. No IP, no user agent. - Every request checks that the session exists, is not ended, has not expired, and that its wallet is still in `ADMIN_WALLETS`. A request never ends sessions itself: while a secret change rolls out, a machine still on the old list would end a newly added admin's. Ending a removed wallet's sessions is done at boot (see Ops). - At most 5 sessions per wallet; signing in again ends the oldest. - **Sessions** on `/admin` lists this wallet's sessions. Each can be ended, or all of them with **Sign out everywhere**. Sign out ends the session on the server, not only in the browser. - Sign-in, sign-out and every admin action only accept requests from the site's own pages (the Origin header), on top of SameSite=Strict. An action must name the wallet that signed it; one without is refused, never treated as a read. Sign-in is limited to 10 a minute per IP and per wallet. - Opening `/admin` from a link on another site sends no Strict cookie on that first load. The card asks the server from inside the page and loads the panels when a session is there, so there is no second signature. ## What needs what | | Session | Its own wallet signature | |---|---|---| | The admin page and the admin reads (reported posts, Boost stats, the profile queue, the season boards) | yes | no | | Every admin action (hide, list, review, season actions, profile moderation) | yes, same wallet | yes, single-use nonce | | A token's creator muting their own token's thread | no | yes | A stolen cookie cannot act without the wallet, and a phished action signature cannot act without the cookie. ## Ops - **Remove an admin**: take the wallet out of `ADMIN_WALLETS` (`fly secrets set ADMIN_WALLETS=… -a basebid`). The secret change restarts the machines, and at boot every session of a wallet no longer listed is ended for good (retried if the database is briefly unreachable): adding the wallet back later revives none of them. Until a machine restarts it still has the old list, so the swap takes as long as the restart (a minute or so). - **Add an admin**: add the wallet to `ADMIN_WALLETS` the same way, and sign in once both machines have restarted. - **Adding back a removed wallet**, or if the log shows `[admin] session sweep failed`: end that wallet's old sessions first, `UPDATE bb_admin_sessions SET revoked_at = now() WHERE wallet = '' AND revoked_at IS NULL;` (the boot sweep normally did it already; this covers a sweep that failed all its tries). - **End every session at once** (a lost device): `UPDATE bb_admin_sessions SET revoked_at = now() WHERE revoked_at IS NULL;` - Sign-ins and sign-outs are logged as `[admin] signed in: ()` and `[admin] signed out: …`.