Metadata-Version: 2.5
Name: recreator-auth
Version: 0.1.4
Summary: Device-flow sign-in and local MCP token storage for re:Creator
Project-URL: Homepage, https://recreator.ilvs.space
Project-URL: Documentation, https://recreator.ilvs.space/llms.txt
Author: Lachy ILVS
License: LicenseRef-ReCreator-Proprietary
Keywords: cli,device-flow,mcp,oauth,rfc8628
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# packages/auth — `recreator auth`

Device-flow sign-in for the CLI, and the one file that holds the MCP token.

```
recreator auth login → device code → browser: email + OTP → approve → rcr_ token at ~/.recreator/token
```

## Why the device flow

OAuth 2.0 Device Authorization Grant ([RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628)),
the same model as `gh auth login`. Chosen over a localhost-callback server
because it needs no free port and no reachable loopback — so it works over SSH
and inside a container, which is where an agent toolkit actually runs.

## The two hops

`/device/token` returns a Better Auth **session** token, not an API key. Sessions
expire and are cookie-cache bound; the MCP server authenticates with an
`@better-auth/api-key` value. So login is two requests:

1. `POST /api/auth/device/code` → `device_code`, `user_code`, `verification_uri`
2. poll `POST /api/auth/device/token` → session token on approval
3. `POST /api/auth/api-key/create` with that session → the raw `rcr_` key,
   **returned exactly once** (the server stores only a SHA-256 digest)

Only the `rcr_` key is written to disk. The session token is discarded.

## Polling rules that are not optional

Encoded in `poll_for_token`, and the reason that function takes an injectable
`sleep` and `monotonic`:

- Honour the server's `interval` (5s as configured).
- `slow_down` adds 5s and **keeps going** — it is not a failure.
- `authorization_pending` keeps going.
- `access_denied` / `expired_token` stop with an actionable message.
- Stop at `expires_in`. The deadline is checked *before* sleeping, so the loop
  never spends an interval to learn what it already knows.

## The token file

`~/.recreator/token`, mode `0600`, in a `0700` directory, created with the final
mode rather than chmod-ed afterwards — an umask-widened file would be
world-readable for the window in between.

Login **refuses to overwrite** an existing token without `--force`, matching the
project's convention for destructive acts.

`~/.recreator/` is shared with unrelated secrets (`registry-token`, `env.sh`,
`resend-api-key`). This package reads and writes the `token` entry and nothing
else. `RECREATOR_TOKEN_FILE` overrides the path, which is how tests stay off a
real credential.

## Client identity

`client_id` is `recreator-cli`, which must appear in the portal's
`DEVICE_CLIENT_IDS` allowlist (`apps/web/src/lib/auth.ts`). The allowlist is
load-bearing: without it the plugin accepts any `client_id`, and a device code
could be minted under an attacker-chosen identity.

## Layout

```
storage.py   the token file: permissions, overwrite refusal, masking
device.py    RFC 8628 protocol + the polling state machine. No I/O beyond HTTP.
session.py   session → rcr_ key exchange, and identity lookup for `status`
login.py     order of operations, printed output, exit codes
```

`device.py` talks to a `Transport` protocol, so the state machine is tested with
a scripted transport and a fake clock — no network, no real sleeping.

## Commands

```bash
recreator auth login                 # device flow, opens a browser
recreator auth login --no-browser    # print the URL instead (SSH, containers)
recreator auth login --force         # replace an existing token
recreator auth status                # masked token + portal validation
recreator auth status --offline      # skip the portal round trip
recreator auth logout                # delete the token
```

`--base-url` points any of these at a preview deployment instead of production.
