diff --git a/Authentication-and-Scopes.md b/Authentication-and-Scopes.md new file mode 100644 index 0000000..cf39c6b --- /dev/null +++ b/Authentication-and-Scopes.md @@ -0,0 +1,72 @@ +# Authentication & Scopes + +familiar authenticates to EVE Online through **EVE SSO** using the **PKCE** flow (no client secret), and stores credentials for **many characters** in one encrypted file. This page covers the flow, the storage, and the full scope list. + +Source: [`auth.py`](https://git.hiddenden.cafe/Hiddenden/familiar/src/branch/main/src/familiar/auth.py) and [`config.py`](https://git.hiddenden.cafe/Hiddenden/familiar/src/branch/main/src/familiar/config.py). + +## The login flow (PKCE) + +`familiar login` runs `interactive_login()`: + +1. Generate a PKCE `code_verifier` / `code_challenge` (S256) and a random `state`. +2. Build the EVE SSO authorize URL for the configured app + scopes and open the browser. +3. Run a tiny local HTTP server on `localhost:8765/callback` to capture the returned `code` and `state`. +4. Verify `state` matches (CSRF guard) — abort if not. +5. Exchange the `code` for tokens (PKCE, **no client secret**). +6. Decode the access token's JWT claims to get the character id (`sub`), name, and granted scopes — and persist the bundle. + +Because it's PKCE, the EVE application's **Client ID is not a secret** and ships as a config default. + +## Token storage + +- Credentials are a **dict of characters keyed by id**: `{ "characters": { "": , ... } }`. +- The whole store is **Fernet-encrypted** (symmetric key in a sibling `key` file) and written with restricted permissions (`0o600` where supported). +- A **legacy single-character file** is automatically migrated to the multi-character format on load. +- Each bundle holds the rotating `refresh_token`, the current `access_token`, `expires_at`, the character id/name, and the granted scopes. + +## Token refresh + +The MCP server never logs in interactively — it only calls `get_auth(character_id)`: + +- If the cached access token is still valid (> 60s from expiry), return it. +- Otherwise use the **rotating refresh token** to get a fresh access token (and a new refresh token), save the updated bundle, and return the new access token. + +## Multiple characters + +Run `familiar login` once per character (switch EVE accounts in the browser between runs) — they all live in one store. Helpers: + +- `list_characters()` — id, name, scopes for each, sorted by name. +- `resolve_character(name_or_id)` — maps a name or id to a logged-in character id. +- `remove_character(name_or_id | None)` — forget one character, or all. + +This is what lets authenticated read tools default to running across **all** logged-in characters (see the per-character pattern in **[Architecture](Architecture)**). + +> Changing the requested scopes requires logging in again — the granted scopes are baked into the stored token. + +## Scopes + +familiar requests **73** scopes split into read and write sets in `config.py`. The split lets a caller opt into a read-only subset (via `FAMILIAR_SCOPES`) while the default grants the full set. **All requested scopes must also be enabled on the EVE application itself.** + +### Read scopes (selected highlights) + +`publicData`, plus character scopes for location/online/ship, clones & implants, fatigue, skills & skill queue, wallet, assets, industry & mining jobs, market orders, contracts, blueprints, contacts, standings, loyalty, medals, titles, agents research, chat channels, notifications, FW stats, freelance jobs, corp roles, mail, calendar, fittings, fleet, killmails, activities, access lists; structure/universe/market scopes (`search_structures`, `read_structures`, structure markets, customs offices); alliance contacts; and the full set of **corp** read scopes (membership, member tracking, divisions, titles, medals, standings, contacts, structures, starbases, facilities, container logs, blueprints, FW stats, projects, freelance jobs, corp wallet(s), corp assets, corp industry & mining jobs, corp orders, corp contracts, corp killmails). + +### Write scopes + +These power the opt-in write actions (see **[Write Safety](Write-Safety)**): + +| Scope | Enables | +|-------|---------| +| `esi-mail.organize_mail.v1` | mark mail read/unread | +| `esi-mail.send_mail.v1` | send mail | +| `esi-calendar.respond_calendar_events.v1` | respond to calendar events | +| `esi-characters.write_contacts.v1` | add/remove contacts | +| `esi-fittings.write_fittings.v1` | create/delete fittings | +| `esi-fleets.write_fleet.v1` | fleet invite / update | +| `esi-ui.open_window.v1` | open in-game windows | +| `esi-ui.write_waypoint.v1` | set autopilot waypoints | +| `esi-planets.manage_planets.v1` | PI (read + write) | + +## EVE application + +The app is registered on [developers.eveonline.com](https://developers.eveonline.com/). Its Client ID, callback host/port (`localhost:8765`), and SSO endpoints are all defined in `config.py` and overridable by environment variable — see **[Configuration](Configuration)**.