fix(cli): point logicsrc login at the real app + add device-code login
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run

`logicsrc login` defaulted to http://localhost:4010 — a dev origin that
doesn't exist on an installed machine, so the printed authorize URL went
nowhere. It now defaults to the hosted credentials app (apps/pwa), reads
the documented $LOGICSRC_API, and only reuses a stored apiUrl once that
identity has actually completed a login (which is how machines got stuck
pointing at localhost). Note logicsrc.com is the marketing site and has
no /cli routes.

The loopback flow is also unusable over SSH: redirect_uri is
http://127.0.0.1:<port>/callback, which resolves to the *browser's*
machine, not the CLI's. Added a device-authorization flow — the CLI
prints a short user_code, the human approves it from any browser:

  POST /cli/device/code   mint device_code + user_code (10 min TTL)
  GET  /cli/device        approve page (login required; typo-tolerant)
  POST /cli/device        approve/deny (CSRF-guarded browser form)
  POST /cli/device/token  CLI polls -> lsk_ API key

device_code is stored sha256-hashed, single-use, with authorization_pending
/ slow_down / access_denied / expired_token poll semantics. The CLI picks
the flow automatically (SSH/CI/no-DISPLAY -> device), with --device/--web
to force it and a fallback to loopback against servers without /cli/device.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-07-28 18:07:49 +00:00
parent eaf0a6162b
commit cf475f0f4c
7 changed files with 311 additions and 16 deletions

View file

@ -179,8 +179,8 @@ re-wraps (seals) it to the new member's public key. The private key lives only i
### CLI
```bash
# One-time: log in by email (registers this device's identity key).
logicsrc login --email you@example.com
# One-time: log in through your browser (registers this device's identity key).
logicsrc login
# Owner: create a team, push a local .env into an encrypted vault, invite people.
logicsrc teams create acme --name "Acme Inc"
@ -188,7 +188,7 @@ logicsrc teams push acme prod --env .env # encrypt + upload
logicsrc teams invite acme teammate@example.com # emails an accept link
# Teammate: accept, then get granted, then pull + decrypt locally.
logicsrc login --email teammate@example.com
logicsrc login
logicsrc teams accept <token-from-email>
# …an existing member runs: logicsrc teams grant acme prod teammate@example.com
logicsrc teams pull acme prod --env .env # download + decrypt
@ -199,6 +199,21 @@ logicsrc teams members acme
logicsrc teams vaults acme
```
`logicsrc login` picks its flow from the machine it runs on:
- **Has its own browser** → loopback OAuth-PKCE: a `127.0.0.1` listener catches
the callback. Force it with `--web`.
- **No browser** (SSH, droplet, container, CI) → device authorization: the CLI
prints a short code, you approve it from a browser on any other machine.
Force it with `--device`. A loopback redirect would be useless here — the
browser's `127.0.0.1` is not the CLI's machine.
- **Unattended**`--token lsk_…` from **Settings ▸ API keys**.
It talks to the hosted credentials app by default. Point it elsewhere (local dev,
self-hosted) with `LOGICSRC_API=http://localhost:8080 logicsrc login` or
`logicsrc login --api-url …`; the chosen origin is remembered in
`~/.logicsrc/identity.json` once login succeeds.
Because `team` is a normal provider, the generic sync surface works too — e.g.
`logicsrc credentials plan --from env --from-path .env --to team --to-project acme
--to-config prod`, then `diff`, `sync`, `audit`, and `rollback` behave exactly as