Commit f485e4a
authored
Unify auth methods across OpenAPI, GraphQL, and MCP on a shared placements model (#943)
* Add @executor-js/http-auth: shared placements-based auth-method model
One vocabulary for the HTTP protocol plugins (openapi/graphql/mcp): an
apikey method carries N header/query placements, each bound to its own
credential variable (or sharing one), plus literal placements for static
values. Includes the renderer, required-variable extraction, slug
normalization, and catalog projection helpers.
Core gains the carrier-agnostic getValues seam on ResolveToolsInput
(multi-input analog of getValue) and the catalog wire schema now carries
placement variable/literal so multi-input methods survive the HTTP
boundary.
Removes the dead http-source and graphql-greenfield packages (zero
consumers; they forked this exact logic).
* MCP: adopt the shared placements auth model
McpAuthMethod becomes none | apikey(placements) | oauth2 — one method can
mix header and query placements, each rendered from its own credential
input (or sharing one). buildConnectorInput renders via the shared
renderer over credential.values; invokeTool fails explicitly with
connection_value_missing instead of dialing unauthenticated; discovery
keeps its tolerant skip. The addServer 'auth' shorthand stays as input
sugar.
Pre-canonical stored shapes (singular auth, retired header/query kinds,
openapi-shaped v1-migration templates) are rewritten by the new one-off
migrate-config module — runtime decodes only the canonical shape. Slugs
and variable names are preserved verbatim (connection.template /
item_ids contracts).
The react codec collapses onto the new shared-auth-method-codec in
packages/react, deleting the first-named-placement truncation; the edit
screen diffs placements deeply. Wire tests pin the extreme case: one
method = bearer header + team-id query param, asserted on what the
server received.
* OpenAPI: adopt the shared placements auth model
The apiKey arm of Authentication becomes the shared ApiKeyAuthMethod
(placements bound to per-input variables) — the variable()-templated
header/query records are retired. The oauth arm keeps its core shape
(type: "oauth", stored endpoints + scopes). renderAuthTemplate and
requiredTemplateVariables now sit on the shared renderer; the catalog
projection and react codec collapse onto the shared helpers.
Stored legacy templates are rewritten by the one-off migrate-config
module; equivalence tests pin that migrated templates render
byte-identically to the legacy renderer (multi-variable Datadog-style,
literal-only values, prefix extraction). Slugs and variable names are
preserved verbatim.
* Wire the one-off auth-config migration + unified-auth e2e scenarios
The migration planner (and a shared SQLite runner) live in http-auth;
each app routes by plugin_id to the plugins' migrate functions: local
runs it at boot after the v1->v2 migration (desktop inherits), selfhost
at boot beside its defensive column adds, cloud as an out-of-band script
(db:migrate-auth:prod) run before deploying canonical-only code.
e2e: scenarios move to canonical wire shapes and gain the extreme case —
one MCP source declaring [oauth][bearer header + team-id query (two
inputs)][bearer header][query token], one connection per method, every
invocation made through the real MCP execute surface and asserted
against what a recording MCP server received. The selfhost browser
suite gains the 2-input connect modal scenario (one field per input,
both rendered together on the wire).
* GraphQL: adopt the shared placements auth model
GraphqlAuthMethod becomes none | apikey(placements) | oauth2(header?,
prefix?) — one method can mix header and query placements, each rendered
from its own credential input. The local renderer is deleted: apikey
renders through the shared renderer, oauth2 through oauthBearerPlacement
(custom header/prefix overrides preserved). Introspection, discovery,
and invoke run on credential.values; a missing input fails explicitly,
naming the variable.
Pre-canonical stored shapes (retired {kind:'apiKey', in, name} entries,
openapi-shaped v1-migration templates, the singular auth field — incl.
the v1 output that wrote BOTH auth:{oauth2} and an apiKey array) are
rewritten by the one-off migrate-config module. The react codec
collapses onto shared-auth-method-codec, deleting the
one-template-per-placement workaround. Wire tests pin the extreme case
(bearer header + team-id query on one method) against a recording
GraphQL server, on both introspection and invocation.
* Sweep remaining consumers onto the canonical auth shapes
Examples (docs quickstart + regenerated snippet, promise-sdk,
all-plugins), the kernel dynamic-worker integration test, and the
local/cloud auth-tool-failure suites move off the retired
variable()-templated apiKey shape. Boundary lint suppressions for the
migration runner/script; changelog stub for the new http-auth package.
* e2e: make the sandbox tool-result assertion shape-agnostic
* Changeset + repo notes for the unified auth model
* Fold http-auth into @executor-js/sdk as the /http-auth subpath
Carrier-agnosticism is a composition property, not a location one: core
never imports the module, so it doesn't need its own package. The
vocabulary ships as @executor-js/sdk/http-auth (browser-safe subpath,
own tsup entry); the standalone package, its workspace entry, deps, and
fixed-group/changeset references are gone.
* Accept request-shaped auth authoring on every plugin
Auth inputs take two dialects, normalized to the one stored model: the
canonical placements shape, and the request-shaped template that reads
like the request it produces —
{ type: "apiKey",
headers: { Authorization: ["Bearer ", variable("token")] },
queryParams: { team_id: [variable("team_id")] } }
variable() is exported from each plugin. Authoring is strict where the
migration is tolerant: a template value renders at most ONE variable, as
the FINAL part — exotic interleavings are rejected at decode with a
pointed message instead of silently flattened (only the one-off
migration flattens, for data that already exists).
The dialect is accepted by addServer/addIntegration/addSpec/configure
on all three plugins (typed clients included — the docs quickstart and
the auth-tool-failure suites author this way again), expanded before
merge so stored configs stay canonical. Wire tests pin that an authored
template renders identically to its canonical twin.
* One apikey authoring dialect: request-shaped in, placements out
Auth INPUTS accept exactly one apikey shape — the request-shaped
template (headers/queryParams records with variable() slots) — expanded
to canonical placements at the boundary. Stored configs and the catalog
keep reading as placements; apiKeyAuthTemplateFromMethod serializes them
back, so every read-modify-write surface (edit screen replace, custom-
method remove, add flows) round-trips through one serializer instead of
the wire accepting two dialects.
Published schemas now teach a single authoring shape; canonical
placement inputs are rejected with a schema error pointing at the
dialect.
* One discriminator: OpenAPI oauth templates re-key to kind:"oauth2"
Every auth method is now kind-keyed — the core OAuthAuthentication shape
drops its legacy type:"oauth" spelling, so the Authentication union
narrows on .kind alone and the '"kind" in template' guards are gone.
The one-off config migration rewrites stored type:"oauth" entries
(slug and endpoints verbatim); oauth INPUTS are wire-typed (plain slug)
and branded in the normalize pass, letting the HTTP handlers pass
payloads through untouched.
* Migration script: TLS for direct connections + post-migration decode audit
The script connects to PlanetScale directly (no Hyperdrive), which
requires TLS. The plan now also audits the SIMULATED final state of
every protocol-plugin row — rewritten or untouched — against the new
runtime's canonical decoders and refuses to write if any row would read
as 'no usable config' after deploy.
Verified against prod: 343 rows, 302 rewrite, audit clean.1 parent 60ab842 commit f485e4a
122 files changed
Lines changed: 4629 additions & 5854 deletions
File tree
- .changeset
- apps
- cloud
- scripts
- host-selfhost/src
- db
- local/src
- db
- docs/snippets/sdk/quickstart
- e2e
- scenarios
- selfhost
- examples
- all-plugins/src
- docs-sdk-quickstart/src
- promise-sdk/src
- packages
- core
- api/src/integrations
- sdk
- src
- http-auth
- kernel/runtime-dynamic-worker/src
- plugins
- graphql-greenfield
- src
- api
- sdk
- testing
- graphql/src
- api
- react
- sdk
- http-source
- src
- react
- sdk
- mcp/src
- api
- react
- sdk
- openapi/src
- api
- react
- sdk
- react/src/lib
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
51 | 54 | | |
52 | 55 | | |
53 | 56 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
| 17 | + | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
7 | 9 | | |
| 10 | + | |
8 | 11 | | |
9 | 12 | | |
10 | 13 | | |
| |||
54 | 57 | | |
55 | 58 | | |
56 | 59 | | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
57 | 65 | | |
58 | 66 | | |
59 | 67 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| 13 | + | |
12 | 14 | | |
13 | 15 | | |
14 | 16 | | |
| |||
167 | 169 | | |
168 | 170 | | |
169 | 171 | | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
170 | 181 | | |
171 | 182 | | |
172 | 183 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
0 commit comments