mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-30 06:32:24 +03:00
refactor(api)!: remove the shared operator secret, and let hosts sync their own
A single secret that turned any request into an operator was the only credential several routes accepted, and it had no caller left: the device pairing it existed for is on hold, and nothing in this tree or any client sent it. What remained was a key that bypassed authentication entirely, required to boot, and checked by nobody. Every route behind it had a better answer available: - Library and game sync move to host credentials. Both took a `userId` in the body, which meant one secret could write into anybody's library. A host now says which of its enrolled users a batch is for, and that claim is checked against the Steam sign-ins it actually holds — one box carries several people's accounts, so the pair is the unit. - Download-state reporting narrows to hosts alone, and the body that could name a different host is gone. Which host is reporting comes from its own credentials, and a body that still names one is refused rather than ignored. - Linking a Steam account is always for the caller. - Creating a game by hand is deleted; syncing already upserts the catalogue. - Reading the waitlist is deleted. Every address on it belongs to someone who has not agreed to anything, and answering it over HTTP made that list something a leaked key could drain. - The pairing-code routes are deleted with the flow they served. The domain module and its table stay, so returning to it is a route file rather than a migration. Nothing in the API now accepts a credential that stands for more than one caller: every request resolves to a specific user or a specific host, which is what lets a route say "the caller's own library" and mean it. BREAKING CHANGE: the `x-nestri-admin-token` header is no longer accepted and `ADMIN_SHARED_SECRET` is no longer read. `POST /games`, `GET /waitlist` and the `/pairing-code` routes are gone; `POST /games/sync` and `POST /library/sync` now require host credentials and take `userId` in the body; `POST /steam/link` no longer accepts `userId`; `POST /games/download-state` no longer accepts `hostId`.
This commit is contained in:
+11
-17
@@ -14,14 +14,14 @@ src/<parent>/
|
||||
|
||||
### Sub-modules nested under parents
|
||||
|
||||
| File | Namespace | Why |
|
||||
| ----------------------- | --------------- | ------------------------------------- |
|
||||
| `user/linked-account.*` | `LinkedAccount` | A user's OAuth/gaming identities |
|
||||
| `user/fingerprint.*` | `Fingerprint` | SSH key fingerprints |
|
||||
| `game/download.*` | `GameDownload` | Per-host game depot downloads |
|
||||
| `user/library.*` | `Library` | User's owned games with playtime |
|
||||
| `team/member.*` | `Member` | Team membership with role |
|
||||
| `game/depot.*` | `Depot` | Platform-specific game content depots |
|
||||
| File | Namespace | Why |
|
||||
| ----------------------- | --------------- | --------------------------------------- |
|
||||
| `user/linked-account.*` | `LinkedAccount` | A user's OAuth/gaming identities |
|
||||
| `user/fingerprint.*` | `Fingerprint` | SSH key fingerprints |
|
||||
| `game/download.*` | `GameDownload` | Per-host game depot downloads |
|
||||
| `user/library.*` | `Library` | User's owned games with playtime |
|
||||
| `team/member.*` | `Member` | Team membership with role |
|
||||
| `game/depot.*` | `Depot` | Platform-specific game content depots |
|
||||
| `steam/enrolment.*` | `Enrolment` | Which host holds a Steam token for whom |
|
||||
|
||||
Existing top-level modules: `user/`, `team/`, `game/`, `pairing-code/`, `steam/`, `auth/`, `db/`.
|
||||
@@ -573,25 +573,19 @@ A Cloudflare Worker using `@nestri/auth` (OpenAuth). Entry point is the `success
|
||||
4. If no memberships, calls `Team.createPersonal({ displayName })`
|
||||
5. Issues JWT via `context.subject('user', { userID, linkedAccountID })`
|
||||
|
||||
### Admin Auth via Shared Secret
|
||||
|
||||
Server-to-server calls can authenticate as an `admin` actor by setting the `x-nestri-admin-token` header to the value of `ADMIN_SHARED_SECRET`. This bypasses JWT auth entirely and grants a system-level actor with no user scope — useful for operations like adding games to the DB, syncing data, or other admin tasks.
|
||||
|
||||
Configure `ADMIN_SHARED_SECRET` in `.env`. It has no default anywhere — a known value here is an authentication bypass, so nothing falls back to one.
|
||||
|
||||
### Auth Middleware (`apps/api/app/middleware/auth.ts`)
|
||||
|
||||
Hono middleware that runs on every API request:
|
||||
|
||||
1. Checks `x-nestri-admin-token` header — if it matches `Env.get().ADMIN_SHARED_SECRET`, sets actor to `admin` and proceeds immediately
|
||||
2. Otherwise, reads `Authorization: Bearer <token>` header
|
||||
1. Checks `x-nestri-machine-id` / `x-nestri-machine-secret` — a registered host authenticates as itself, and bad credentials fall through to `public` rather than erroring
|
||||
2. Otherwise, reads `Authorization: Bearer <token>` header — a personal access token is resolved from the database, anything else is verified as a JWT
|
||||
3. Verifies via `client.verify(subjects, token)` from `@nestri/auth/client`
|
||||
4. If valid `user` subject:
|
||||
- Checks `x-nestri-team` header for team-scoped access
|
||||
- If team header present, verifies membership via `Member.findByTeamAndUser`
|
||||
- Sets actor to `member` (with role) or `user` type
|
||||
5. If no token/invalid: sets actor to `public` type
|
||||
6. Exports `notPublic` guard middleware — throws `VisibleError('authentication', UNAUTHORIZED, …)` if actor is `public`. Caught by `onError` → 401 JSON response. The `admin` actor passes this guard (it's not `public`), so admin routes can use `.use(notPublic)` like any other protected route.
|
||||
6. Exports `notPublic` guard middleware — throws `VisibleError('authentication', UNAUTHORIZED, …)` if actor is `public`. Caught by `onError` → 401 JSON response. It admits machines too; what stops a host acting as its owner is `Actor.userID`, which refuses a `machine` outright.
|
||||
|
||||
### OpenAuth Subjects (`src/auth/subjects.ts`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user