Repository navigation
Description
Stage 1 of #860 — the slice that is implementable now without redesigning the plugin runtime. #860 keeps the full problem statement and the stage-2 ambition (explicit caller/session composition, no header impersonation); that redesign is deliberately out of scope here.
The class is confirmed alive: #881's `adminAddMember` bug was the same shape — a hand-rolled in-process client whose forwarded headers hit a permission model that could not authorize it, failing far from the construction site with a misleading error.
Problem
The runtime client factory is a blind context spread — `packages/every-plugin/src/runtime/index.ts` hands whatever the caller passes straight into `createRouterClient`. The auth plugin re-derives identity only from `reqHeaders` (`createRequireAuth`, `plugins/auth/src/middleware.ts`, via `createHeaders(context.reqHeaders)`), and trusts `context.user` — never `context.userId`. So:
- `plugins.auth.client({ userId, user })` without `reqHeaders` → empty `Headers` → `getSession` returns null → generic `UNAUTHORIZED` with `data: { apiKeyProvided: false }`, which reads as an API-key configuration problem
- passing only `{ userId }` does not help at all — that key is never read by the auth guard
- the failure surfaces on the first authed route call, far from the factory call that dropped the credentials
The docs actively teach the wrong thing:
- `packages/everything-dev/skills/api-and-auth/SKILL.md` shows a callable `plugins.auth({ context })` form — the actual injected shape is `{ client, router }` (`plugins.auth.client({ ... })`)
- `packages/better-near-auth/skills/auth-plugin/SKILL.md` repeats the callable + `{ context }`-wrapping form
- the correct pattern exists only in host code (`host/src/services/auth.ts` — `buildPluginContext` always forwards `reqHeaders`) and is hand-rolled per consumer (citynode's local `memberAuthClient`)
What we want (stage 1)
-
Named failure — `createRequireAuth` distinguishes no credentials forwarded at all (no `reqHeaders`, no host-resolved `user`, no API-key header) from credentials present but rejected, and the former fails with a message that names the actual fix:
in-process caller forwarded no credentials — pass `reqHeaders` from the caller's request (or the host-resolved `user`); `client({ userId })` alone carries no credentials
Contract error shapes stay as-is (`apiKeyProvided` is contract-constrained); this is a message/data refinement in `plugins/auth/src/middleware.ts`, not a contract change.
-
One canonical pattern, documented — the skills show the single derivation and drop the nonexistent callable form:
```ts
const auth = plugins.auth.client({ reqHeaders: context.reqHeaders, ...authContext });
```Files: `packages/everything-dev/skills/api-and-auth/SKILL.md`, `packages/better-near-auth/skills/auth-plugin/SKILL.md` (and the client/tanstack skills where the same drift appears).
-
Dev-mode construction guard (optional but preferred) — `every-plugin`'s `createClient` warns when a provided context lacks keys the plugin's declared context schema marks required. Generic (the runtime stays auth-agnostic), catches the footgun at construction time in dev. This is the only `packages/*` change here → needs a changeset.
Acceptance
- An integration test calls an authed route via `createClient({ userId, user })` without `reqHeaders` and asserts the new named error (the auth: in-process client construction is a silent footgun — credential-less clients fail far away with a misleading error #860 repro, pinned)
- Errors point at the real cause (missing forwarded credentials), never `apiKeyProvided`, for the no-credentials case
- The skills show one canonical derivation and no longer reference the callable `plugins.auth({ context })` form
- If the runtime guard lands: a unit test in `packages/every-plugin` pins the dev-mode warning
Out of scope (stage 2 — tracked on #860)
Direct invocation with an explicit caller/session object, removing the header-forwarding class of bug entirely. That touches the every-plugin runtime contract, host injection, and every plugin — separate effort, separate changeset train.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
feat(auth,every-plugin): name the credential-less in-process client f…