Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .changeset/26710-signed-connector-auth-aliyun.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
'@objectstack/spec': minor
'@objectstack/service-automation': minor
'@objectstack/connector-rest': minor
'@objectstack/connector-openapi': minor
---

feat(spec): `signed` joins declarative connector-instance auth, with `aliyun-acs3-hmac-sha256` as its first scheme (ADR-0143 D2.3, #26710)

Clause-②: yes (widening)
Metadata-protocol: added

<!-- adr-0087: not-required (no-migration-prescription) One member is added to the instance-auth union and optional members to the provider context and materialization. Nothing an author or a provider wrote before is refused or re-shaped, so there is nothing for a migration to rewrite. -->

An upstream that authenticates every request by a signature over the request itself, rather than by a token it is sent, had no way to be declared on a connector instance. ADR-0143 D2.3 adds that kind, `signed`, with a closed list of schemes. A scheme joins the list only together with its signer and its vendor test vectors. This release ships one scheme: Alibaba Cloud's OpenAPI V3 signature.

- **`@objectstack/spec`**
- `ConnectorInstanceAuthSchema` gains `{ type: 'signed', scheme, keyId, credentialRef }`, a closed shape for each scheme. `keyId` is the public key identifier. The signing key is only ever a `credentialRef`.
- The publish door refuses an unknown or missing `scheme` at `auth.scheme`, naming the value it got and the schemes that ship. It refuses an inline `secret`, `privateKey`, `secretKey` or `accessKeySecret` by name, with the `credentialRef` prescription, and an authored `signature`. It refuses `signed` on `provider: 'mcp'` at `auth.type`.
- `@objectstack/spec/integration` gains the request-signer seam: `ConnectorRequestSigner` (`sign`, plus an optional `verifyResponse` for schemes that sign their responses), `ConnectorProviderContext.requestSigner`, and `ConnectorMaterialization.requestSignerApplied`.
- `connectorFetchOptions` takes an optional signer and signs every attempt `resilientFetch` makes, retries included. `readConnectorResponse` runs the scheme's response verification before parsing.
- With no signer, both behave exactly as before.
- **`@objectstack/service-automation`**
- A `signed` instance's key resolves through the same credential chain as the static kinds, with the same degrade on an unresolved reference.
- The key is bound to the scheme's signer at materialization, and the provider receives only the signer.
- The service refuses to serve a `signed` instance on a provider that does not acknowledge the signer, so such an instance never calls its upstream unsigned.
- A datasource that borrows a `signed` instance through `external.connector` is refused with `422`.
- **`@objectstack/connector-rest`, `@objectstack/connector-openapi`**
- Both apply a handed request signer to every request and acknowledge it.
- A static credential together with a signer is refused.
- `bearer`, `api-key` and `basic` requests are unchanged.
55 changes: 55 additions & 0 deletions content/docs/automation/connectors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,60 @@ them, declare a static kind (`bearer`, `api-key` or `basic`) to dispatch now;
don't emulate a grant by putting a short-lived access token behind a `bearer`
ref.

### Request signing: `signed`

Some upstreams take no token at all: every request carries a signature over its
own method, path, query, headers and body, computed with a key the caller never
sends. The `signed` kind declares that (ADR-0143 D2.3). The metadata names the
**scheme** and the public **key id**; the signing key itself is a
`credentialRef`, resolved through the same chain as every other kind.

```ts
// Alibaba Cloud OpenAPI, V3 signature (ACS3-HMAC-SHA256):
auth: {
type: 'signed',
scheme: 'aliyun-acs3-hmac-sha256',
keyId: 'YourAccessKeyId', // the AccessKey ID: public, stays in metadata
credentialRef: 'ALIYUN_ACCESS_KEY_SECRET',
}
```

What the platform does with it:

- **Every request is signed, each attempt fresh.** A retry is signed again with
a new nonce and date, so a scheme that refuses a replayed nonce accepts it.
- **The key never reaches the provider.** The automation service binds the
resolved key to the scheme's signer at materialization and hands the
provider the signer only. A datasource cannot borrow a `signed` instance
(`external.connector` is refused with `422`): a driver cannot sign per
request.
- **Schemes that sign their responses are verified** before a result is
returned; a response that does not verify fails the dispatch.
- **`aliyun-acs3-hmac-sha256`** signs `host`, `content-type` and every
`x-acs-*` header, and sets `x-acs-date`, `x-acs-signature-nonce`,
`x-acs-content-sha256` and `Authorization`. The API's own action and version
are yours to send, as `x-acs-action` / `x-acs-version` in `defaultHeaders`
(rest) or in each call's headers.

What the publish door refuses:

- **An unknown `scheme`**, at `auth.scheme`, naming the scheme it got and the
ones this platform ships. A scheme joins the list only together with its
signer and its test vectors, so a scheme the server cannot run is never
accepted.
- **An inline key**: `secret`, `privateKey`, `secretKey` or `accessKeySecret`
is refused by name, pointing at `credentialRef`. An authored `signature` is
refused too: it is computed for every request.
- **`provider: 'mcp'`**, at `auth.type`: an MCP server is not signed per
request. Declare `provider: 'rest'` or `provider: 'openapi'`.

`signed` and a static credential do not combine: a request is authenticated
one way. An `openapi` instance's remote `spec` URL is fetched unsigned, as
before; only the API calls are signed. A third-party provider factory serves a
`signed` instance only when it says it applies the signer
(`requestSignerApplied` on its materialization); one that does not is refused at
boot rather than allowed to call the upstream unsigned.

### The runtime shape: `authentication`

The `authentication` field is the **runtime** auth config, with four static
Expand Down Expand Up @@ -517,6 +571,7 @@ retry):
| `provider` names no installed factory | **Hard boot error** naming the entry, the provider, and the plugin that supplies it — add the executor to `plugins:` |
| Invalid `providerConfig` (missing `baseUrl` / `spec` / `transport`, wrong shape) | **Hard boot error** from the provider factory |
| `auth` is a grant-bearing kind (`oauth2`, `app-credential`) on a server that does not yet run its per-dispatch resolution | **Not fatal**: the instance registers **degraded**, naming the kind, and the upstream is never called without its credential; a datasource borrowing it is refused with `422` |
| `auth` is `signed` on a provider that does not say it applies the request signer | **Hard boot error** naming the instance, the scheme and the provider: its calls would go out unsigned — declare it through `rest` or `openapi` |
| `credentialRef` resolves to nothing | **Not fatal**: the instance registers **degraded**, naming the reference — set it (`PUT …/credential-refs/:ref`), and it materializes without a restart |
| The upstream refuses the credential (`401` / `403`) | **Not fatal**: the instance registers **degraded**. A stored credential is marked `rejected` and waits until it is set again (or a restart); an environment or vault credential is retried on the backoff |
| Instance `name` collides with a plugin-registered connector | **Hard boot error** — there is no silent precedence between the two worlds |
Expand Down
10 changes: 5 additions & 5 deletions content/docs/references/index.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Protocol reference — every schema by module
navTitle: Protocol Reference
description: Every schema published by @objectstack/spec — 1566 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1569 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All @@ -25,7 +25,7 @@ counts are sums of the rows they head. Regenerate with
| [Automation Protocol](/docs/references/automation) | 15 | 77 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
| [Data Protocol](/docs/references/data) | 31 | 174 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
| [Integration Protocol](/docs/references/integration) | 1 | 18 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
| [Integration Protocol](/docs/references/integration) | 1 | 21 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
| [Kernel Protocol](/docs/references/kernel) | 29 | 151 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
| [Marketplace Protocol](/docs/references/marketplace) | 4 | 30 | The package & marketplace format — package identity and versions, listing, publish, review, search, install, template manifests. |
| [QA Protocol](/docs/references/qa) | 1 | 8 | Declarative test suites — scenarios, steps, actions and assertions. |
Expand All @@ -34,7 +34,7 @@ counts are sums of the rows they head. Regenerate with
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 37 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 172 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **203** | **1566** | 14 protocol modules |
| **Total** | **203** | **1569** | 14 protocol modules |

---

Expand Down Expand Up @@ -191,13 +191,13 @@ Users and accounts, organizations, positions, SCIM provisioning.

## Integration Protocol

**Source:** `packages/spec/src/integration/` · **Import:** `@objectstack/spec/integration` · **1 page, 18 schemas**
**Source:** `packages/spec/src/integration/` · **Import:** `@objectstack/spec/integration` · **1 page, 21 schemas**

The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances.

| File | Schemas |
| :--- | :--- |
| [`connector.zod.ts`](/docs/references/integration/connector) | `Connector`, `ConnectorAction`, `ConnectorActionEffect`, `ConnectorAppCredentialVendor`, `ConnectorInstanceAPIKeyAuth`, `ConnectorInstanceAppCredentialAuth`, `ConnectorInstanceAuth`, `ConnectorInstanceBasicAuth`, `ConnectorInstanceBearerAuth`, `ConnectorInstanceNoAuth`, `ConnectorInstanceOAuth2Auth`, `ConnectorInstanceOAuth2AuthorizationCodeAuth`, `ConnectorInstanceOAuth2ClientCredentialsAuth`, `ConnectorOAuth2Grant`, `ConnectorRetryStrategy`, `ConnectorType`, `DeclarativeConnectorEntry`, `RetryConfig` |
| [`connector.zod.ts`](/docs/references/integration/connector) | `Connector`, `ConnectorAction`, `ConnectorActionEffect`, `ConnectorAppCredentialVendor`, `ConnectorInstanceAPIKeyAuth`, `ConnectorInstanceAppCredentialAuth`, `ConnectorInstanceAuth`, `ConnectorInstanceBasicAuth`, `ConnectorInstanceBearerAuth`, `ConnectorInstanceNoAuth`, `ConnectorInstanceOAuth2Auth`, `ConnectorInstanceOAuth2AuthorizationCodeAuth`, `ConnectorInstanceOAuth2ClientCredentialsAuth`, `ConnectorInstanceSignedAliyunAcs3Auth`, `ConnectorInstanceSignedAuth`, `ConnectorOAuth2Grant`, `ConnectorRetryStrategy`, `ConnectorSigningScheme`, `ConnectorType`, `DeclarativeConnectorEntry`, `RetryConfig` |

---

Expand Down
Loading
Loading