@jskit-ai/connectors-core 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +551 -0
- package/docs/oauth-callbacks.md +65 -0
- package/docs/online-setup.md +56 -0
- package/docs/setup-command.md +178 -0
- package/migrations/connectors_core_initial.cjs +17 -0
- package/package.json +65 -0
- package/src/server/ConnectorsFeature.js +36 -0
- package/src/server/connectionService.js +664 -0
- package/src/server/credentialProtection.js +34 -0
- package/src/server/environmentReferences.js +13 -0
- package/src/server/errors.js +27 -0
- package/src/server/fileConnectionStore.js +86 -0
- package/src/server/fileStorage.js +2 -0
- package/src/server/index.js +4 -0
- package/src/server/knexConnectionStore.js +65 -0
- package/src/server/storage.js +2 -0
- package/src/shared/configuration.js +225 -0
- package/test/connectionService.test.js +1231 -0
- package/test/fileConnectionStore.test.js +165 -0
- package/test/knexConnectionStore.test.js +178 -0
- package/test/serviceAccount.test.js +192 -0
- package/test/setupCommand.test.js +364 -0
package/README.md
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
# Account connections
|
|
2
|
+
|
|
3
|
+
This package owns portable integration configuration, OAuth authorization and
|
|
4
|
+
refresh, connection state, and optional encrypted persistence. Application code owns
|
|
5
|
+
identity, permission decisions, HTTP routes, environment bindings and database
|
|
6
|
+
operation scripts. The same exports work in a server or an app-owned CLI.
|
|
7
|
+
|
|
8
|
+
The implementation supports **own OAuth registrations** (user consent and
|
|
9
|
+
provider-declared client credentials), provider-declared **service-account credentials**, **API-key connections**
|
|
10
|
+
and provider-declared **no-credential connections**.
|
|
11
|
+
Google Calendar supplies OAuth operations; Resend and Firecrawl supply API-key
|
|
12
|
+
operations through the connectors catalogue. Registrations use `source: "own"`.
|
|
13
|
+
Managed gateway registrations and their assignment fields are rejected during
|
|
14
|
+
configuration validation.
|
|
15
|
+
|
|
16
|
+
See [application-owned OAuth callbacks](docs/oauth-callbacks.md) for callback
|
|
17
|
+
routes, environment bindings and changes of domain or hosting.
|
|
18
|
+
The [application setup guide](docs/online-setup.md) explains credential and
|
|
19
|
+
runtime ownership for hosted editors and direct CLI use.
|
|
20
|
+
|
|
21
|
+
## Pre-release migration to application-owned connections
|
|
22
|
+
|
|
23
|
+
This is V0. The runtime accepts one current configuration format; it has no
|
|
24
|
+
legacy registration reader, gateway adapter or automatic grant conversion.
|
|
25
|
+
`schemaVersion: 1` identifies the current schema, not every historical draft
|
|
26
|
+
that used that number.
|
|
27
|
+
|
|
28
|
+
1. Back up the application's source and private runtime state before changing
|
|
29
|
+
its configuration. Review each named integration's provider, account mode,
|
|
30
|
+
permissions and settings against the current provider definition.
|
|
31
|
+
2. Replace managed registrations with the application's own provider
|
|
32
|
+
registration. Remove `serviceUrlRef`, `serviceCredentialRef` and
|
|
33
|
+
`assignmentRef`; changing only `source` to `own` cannot supply a real client.
|
|
34
|
+
Set the actual client ID, required secret reference and callback reference.
|
|
35
|
+
API-key integrations instead reference the application's own key.
|
|
36
|
+
3. Supply required secrets through the application's existing Env facilities.
|
|
37
|
+
`MISSING` is an incomplete placeholder, not a working credential. Keep
|
|
38
|
+
development and production bindings distinct. Register the exact app-owned
|
|
39
|
+
callback URL with the provider before attempting OAuth consent.
|
|
40
|
+
4. Wire the current connection service into the application's authenticated
|
|
41
|
+
routes and optional setup command. Use stable application/principal IDs and
|
|
42
|
+
private persistent storage outside source or release directories. Retain the
|
|
43
|
+
existing encryption key for records that the application already owns.
|
|
44
|
+
5. Reconnect when changing provider registration or moving away from a service
|
|
45
|
+
that held the grants. Do not copy another service's tokens or infer that
|
|
46
|
+
matching email addresses transfer consent or merge application users.
|
|
47
|
+
6. Validate with `validateIntegrationConfiguration(configuration, { providers })`,
|
|
48
|
+
then verify connection, restart, cancellation and disconnect in the selected
|
|
49
|
+
environment. A source-file migration alone does not establish a connection.
|
|
50
|
+
|
|
51
|
+
Retire obsolete service credentials deliberately after the replacement works.
|
|
52
|
+
Local disconnect removes the selected app connection; provider-side revocation
|
|
53
|
+
and cleanup of old services are separate operator actions.
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
Import `parseIntegrationConfiguration`, `validateIntegrationConfiguration` and
|
|
58
|
+
`integrationsSchema` from `@jskit-ai/connectors-core/shared/configuration`.
|
|
59
|
+
`integrationsSchema.getFieldDefinitions()` exposes the field definitions for
|
|
60
|
+
form inspection. Validate with `validateIntegrationConfiguration`, which also
|
|
61
|
+
checks references, credential combinations and provider scopes. Custom reference
|
|
62
|
+
validators do not currently provide transport JSON Schema export hooks.
|
|
63
|
+
References use a binding namespace such as `env:NAME` or `vault:path`;
|
|
64
|
+
literal HTTP/HTTPS URLs are rejected in reference fields.
|
|
65
|
+
Pass registered provider definitions in `{ providers }` for provider validation.
|
|
66
|
+
Scope entries may declare `required: true`; both CLI and UI validation reject
|
|
67
|
+
configurations missing any required permission. LinkedIn uses this for OpenID
|
|
68
|
+
and profile. Runtime operations still check the permissions actually granted.
|
|
69
|
+
Provider `settingsSchema` validates settings and supplies defaults in the returned
|
|
70
|
+
configuration. It can be a Schema instance or a synchronous function of the current
|
|
71
|
+
settings returning one. `getProviderSettingsSchema(provider, settings)` resolves
|
|
72
|
+
either form for CLI and UI consumers. Redshift uses this to require a workgroup
|
|
73
|
+
or cluster identifier according to deployment type and reject mixed fields.
|
|
74
|
+
Unknown providers fail by default; configuration editors can pass
|
|
75
|
+
`allowUnknownProviders: true` to preserve custom slots while validating known ones.
|
|
76
|
+
Errors expose `code`, `statusCode: 422` and dotted `fieldErrors` for shared forms.
|
|
77
|
+
|
|
78
|
+
`integrations.json` is application source. It contains named integration slots,
|
|
79
|
+
requested scopes, account modes and registration references. A registration
|
|
80
|
+
contains its public client ID and references to secrets and the **full callback
|
|
81
|
+
URL**, including its path. It never contains client secrets, authorization
|
|
82
|
+
codes, access tokens or refresh tokens. Unknown ordinary fields are rejected;
|
|
83
|
+
application-owned data belongs under `extensions` or provider `settings`.
|
|
84
|
+
Version changes require an explicit migration. Editing source does not transfer
|
|
85
|
+
an existing grant or request additional consent automatically.
|
|
86
|
+
|
|
87
|
+
Own registrations can specify `grantType: "client_credentials"`; omitted grant
|
|
88
|
+
types use `authorization_code`. Service accounts require a confidential client
|
|
89
|
+
and secret reference, forbid `callbackUrlRef`, and cannot use `per-user` ownership.
|
|
90
|
+
The provider must declare this flow in `oauthGrantTypes`. Its
|
|
91
|
+
`oauthClientAuthenticationMethods` can be an array or a synchronous function of
|
|
92
|
+
the grant type. `getProviderClientAuthenticationMethods(provider, grantType)`
|
|
93
|
+
resolves that contract for validation and forms. `getProviderScopes(provider,
|
|
94
|
+
settings, grantType)` similarly resolves `scopesForGrantType`, the existing
|
|
95
|
+
settings-dependent scopes, or the static scope list. Databricks uses both to
|
|
96
|
+
keep service credentials and user consent correctly configured.
|
|
97
|
+
|
|
98
|
+
Provider-specific service-account credentials use
|
|
99
|
+
`authentication: { method: "service-account", secretRef: "env:SERVICE_ACCOUNT" }`.
|
|
100
|
+
The reference resolves to a server-only credential string, including multiline
|
|
101
|
+
JSON when required by the provider. This mode requires selected permissions and
|
|
102
|
+
shared or assistant ownership. It does not use a registration, client ID,
|
|
103
|
+
callback URL or app-user identity. The provider validates its credential format;
|
|
104
|
+
the portable file retains only the reference.
|
|
105
|
+
|
|
106
|
+
## Runtime
|
|
107
|
+
|
|
108
|
+
Import `createConnectionService`, `createConnectorsFeature` and
|
|
109
|
+
`createEnvironmentReferenceResolver` from `@jskit-ai/connectors-core/server`.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
const connections = createConnectionService({
|
|
113
|
+
configuration,
|
|
114
|
+
providers: [googleCalendarProvider],
|
|
115
|
+
store,
|
|
116
|
+
resolveReference: createEnvironmentReferenceResolver(process.env),
|
|
117
|
+
authorize: applicationConnectionPolicy
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The required `authorize(context, { integrationId, operation, accountMode, input })`
|
|
122
|
+
returns `{ applicationId, subjectId }` from trusted application identity after
|
|
123
|
+
checking access. Return no owner to deny access. For a personal connection use
|
|
124
|
+
the authenticated app user's stable ID. For a shared account use a stable shared
|
|
125
|
+
subject only after checking membership and the requested operation. For
|
|
126
|
+
assistant access check the assistant's delegated permissions separately.
|
|
127
|
+
The environment resolver treats absent, blank and literal `MISSING` values as
|
|
128
|
+
incomplete bindings. Runtime credential checks also reject `MISSING` returned by
|
|
129
|
+
custom resolvers, and OAuth rejects a placeholder client ID before starting
|
|
130
|
+
consent. These failures report `connector_binding_missing`; they do not create a
|
|
131
|
+
verified connection or send the placeholder to the provider. Configuration may
|
|
132
|
+
retain placeholders while an administrator finishes setup.
|
|
133
|
+
|
|
134
|
+
Never trust owner IDs supplied by a browser. Include deployment/environment
|
|
135
|
+
identity in `applicationId` when several environments share storage.
|
|
136
|
+
|
|
137
|
+
For `invoke`, `input` is a separate copy of the caller's operation input. It is
|
|
138
|
+
omitted for connection-management methods. Check it when permission depends on
|
|
139
|
+
the requested tool, document or action. The service snapshots input before
|
|
140
|
+
awaiting policy, so neither caller nor policy mutation changes what executes.
|
|
141
|
+
For MCP `tools.call`, approve `input.name` and `input.arguments` explicitly;
|
|
142
|
+
membership alone must not grant arbitrary assistant tool execution.
|
|
143
|
+
|
|
144
|
+
Compose assistant-facing routes/tools with `executionMode: "assistant"`. This
|
|
145
|
+
is a trusted server/CLI composition choice, never an option from a request body.
|
|
146
|
+
The default `application` mode retains ordinary app authorization; `accountMode`
|
|
147
|
+
selects connection ownership and does not select the caller's execution mode.
|
|
148
|
+
In assistant mode, `assistantPolicy` on each integration supplies `enabled`,
|
|
149
|
+
`defaultPermission` (`ask`, `always`, `never`), and per-action `actions` overrides.
|
|
150
|
+
Omitted policy defaults to asking. Providers declare available override names in
|
|
151
|
+
`assistantActions`; the shared schema rejects unknown names and invalid values.
|
|
152
|
+
Keep this policy outside credential settings so edits do not require reconnection.
|
|
153
|
+
|
|
154
|
+
The existing `authorize` callback receives `assistantPermission: { action, decision }`
|
|
155
|
+
alongside the operation and copied input. For `ask`, the host must verify an
|
|
156
|
+
approval for that exact request and return `approved: true` with the authorized
|
|
157
|
+
owner. A missing approval raises `connector_approval_required` before provider
|
|
158
|
+
access. The host owns decision UI, persistence, expiry and replay prevention.
|
|
159
|
+
`always` still checks access; `never` or disabled access denies the action.
|
|
160
|
+
Workspace/organization rules remain in the host's authorization boundary and
|
|
161
|
+
cannot be overridden by the file. Do not trust a browser's `approved` flag.
|
|
162
|
+
Status, cancellation and local disconnect remain available through ordinary
|
|
163
|
+
access checks even when assistant actions are disabled. Pending OAuth completion
|
|
164
|
+
is still checked as a connect action; do not reuse an approval indiscriminately.
|
|
165
|
+
|
|
166
|
+
`authorizeAssistantAction({ context, integrationId, action, input })` checks a
|
|
167
|
+
declared host lifecycle action in assistant mode. It executes no provider operation
|
|
168
|
+
and creates no activation state. The host validates and performs its own operation
|
|
169
|
+
after authorization; use `invoke` for actual provider operations. This helper's
|
|
170
|
+
input is copied before authorization just like `invoke` input.
|
|
171
|
+
|
|
172
|
+
All methods take `{ context, integrationId }`:
|
|
173
|
+
|
|
174
|
+
| Method | Additional input | Result |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| `connectClientCredentials` | optional `verificationInput`, `signal` | Verified service account metadata; no browser consent |
|
|
177
|
+
| `connectServiceAccount` | optional `verificationInput`, `signal` | Provider-specific service credentials exchanged for a verified short-lived token |
|
|
178
|
+
| `beginAuthorization` | optional `verificationInput`, `signal` | Authorization URL, expiry and resolved callback URL |
|
|
179
|
+
| `completeAuthorization` | `callbackUrl`, optional `signal` | Verified connection metadata |
|
|
180
|
+
| `cancelAuthorization` | `state` | Cancelled attempt; existing connection preserved |
|
|
181
|
+
| `connectApiKey` | optional `verificationInput`, `signal` | API key verified through a provider read operation |
|
|
182
|
+
| `status` | — | Safe metadata or disconnected |
|
|
183
|
+
| `invoke` | named `operation`, `input`, optional `signal` | Provider operation result |
|
|
184
|
+
| `disconnect` | — | Local connection and pending attempts removed |
|
|
185
|
+
|
|
186
|
+
HTTP callback handlers must recover the same authenticated owner context as the
|
|
187
|
+
connect request. The runtime checks state, PKCE by default, exact callback destination,
|
|
188
|
+
registration identity, expiry and actual granted scopes. It verifies a provider
|
|
189
|
+
operation before reporting connected. Resource-specific providers (such as
|
|
190
|
+
Sheets) require a document ID in `verificationInput` when starting consent.
|
|
191
|
+
That input is validated before consent and retained in the owning attempt. Safe metadata excludes credentials.
|
|
192
|
+
Disconnect is local; it does not revoke the provider's entire shared grant.
|
|
193
|
+
|
|
194
|
+
A provider whose documented confidential flow excludes PKCE can set
|
|
195
|
+
`oauthPkce: false` (Workday). This cannot be used with a public client, and
|
|
196
|
+
pending attempts cannot switch between PKCE and non-PKCE flows. A provider with
|
|
197
|
+
no selectable scopes can use an empty scope list; authorization omits `scope`
|
|
198
|
+
in that case. Providers that offer permissions still require a selection.
|
|
199
|
+
|
|
200
|
+
Own registrations default to `tokenEndpointAuthMethod: "client_secret_post"`.
|
|
201
|
+
A provider can declare `"client_secret_basic"` in
|
|
202
|
+
`oauthClientAuthenticationMethods`; its own registration still requires a
|
|
203
|
+
secret reference. The OAuth library applies HTTP Basic client authentication
|
|
204
|
+
for both code exchange and refresh, without putting the secret in the body.
|
|
205
|
+
A provider may explicitly support `"none"` through
|
|
206
|
+
`oauthClientAuthenticationMethods`. Such registrations require Client ID and
|
|
207
|
+
callback references but forbid `clientSecretRef`; token grants use the OAuth
|
|
208
|
+
library's `None()` method and never resolve a client secret. Provider-owned
|
|
209
|
+
`validateClientId` checks specialized identifiers, such as Canva metadata URLs.
|
|
210
|
+
Authentication-type changes invalidate pending attempts and connected grants.
|
|
211
|
+
`clientIdLabel` and `clientIdHint` provide matching shared form copy. Providers
|
|
212
|
+
without an explicit methods list retain confidential-client authentication.
|
|
213
|
+
|
|
214
|
+
A provider can set `oauthClientIdParameter` when its documented wire protocol
|
|
215
|
+
uses a different field name. TikTok sets `client_key`; the portable file and
|
|
216
|
+
OAuth SDK client metadata still use `clientId` and `client_id`, respectively.
|
|
217
|
+
The runtime substitutes the wire field on authorization and token requests.
|
|
218
|
+
Provider `validateCallbackUrl(url)` can impose additional restrictions after the
|
|
219
|
+
shared URL checks; TikTok requires HTTPS and fewer than 512 characters.
|
|
220
|
+
These are provider-owned protocol rules, not arbitrary configuration overrides.
|
|
221
|
+
|
|
222
|
+
Providers that document granted scopes on the authorization callback can set
|
|
223
|
+
`scopesInAuthorizationResponse: true`. Their callback must contain exactly one
|
|
224
|
+
`scope` value. When the token response omits scopes, the service retains only
|
|
225
|
+
requested permissions present in that callback. Explicit token-response scopes
|
|
226
|
+
take precedence, and a permission required by the verification operation must
|
|
227
|
+
still be granted. Refresh preserves this reduced grant when scopes are omitted.
|
|
228
|
+
|
|
229
|
+
A provider with comma-separated OAuth permissions can set `scopeSeparator: ","`.
|
|
230
|
+
The default remains a space. Providers whose verification response describes
|
|
231
|
+
the grant can implement `grantedScopesFromVerification(result, { clientId })`.
|
|
232
|
+
It must return an array of scope strings and may reject a mismatched client.
|
|
233
|
+
The service retains only scopes present in the request, token/callback grant
|
|
234
|
+
and this verified array. Verification must still have its required permission;
|
|
235
|
+
otherwise the attempt fails without replacing an existing connection.
|
|
236
|
+
Initial OAuth grants are limited to configured permissions, even if a token
|
|
237
|
+
response contains additional scopes. For every OAuth provider, refresh retains
|
|
238
|
+
only scopes already granted and still
|
|
239
|
+
requested by configuration. It may reduce a grant but cannot expand it without
|
|
240
|
+
new consent, including providers without verification metadata.
|
|
241
|
+
|
|
242
|
+
API-key providers declare `apiKey.headers(key, settings)`, `apiKey.queryParameter`,
|
|
243
|
+
`apiKey.bodyParameter` or `apiKey.pathPrefix(key)` in their trusted runtime
|
|
244
|
+
definition. The service resolves the secret reference at request time. Query credentials are encoded and replace existing values only
|
|
245
|
+
after the operation destination passes its HTTPS/origin check. Body credentials
|
|
246
|
+
similarly replace their named JSON field in a new body object without mutating
|
|
247
|
+
operation input. They require an object body and reject GET/HEAD requests.
|
|
248
|
+
Path prefixes are supplied by trusted provider code after destination validation;
|
|
249
|
+
providers validate or encode the key as a path segment. The service changes only
|
|
250
|
+
the pathname and rejects ambiguous prefixes and dot segments. Neither operation
|
|
251
|
+
input nor configuration supplies a credential URL. Redirects are rejected.
|
|
252
|
+
Connection records contain the reference, not the key or authenticated URL,
|
|
253
|
+
and provider errors are reduced to safe connector errors. Application HTTP
|
|
254
|
+
instrumentation must redact authenticated URLs, credential headers and bodies.
|
|
255
|
+
Some read operations need input: PostHog flag verification requires an explicit
|
|
256
|
+
`distinct_id` in `verificationInput`. `connectApiKey` and the JSKIT
|
|
257
|
+
`verifyApiKey` action pass that input through the operation's ordinary validation.
|
|
258
|
+
It is not retained in connection records, and failed verification never creates
|
|
259
|
+
a connected record.
|
|
260
|
+
API keys are resolved on the server for each call. Connection storage retains
|
|
261
|
+
the reference, a private fingerprint of the last successfully used key and its
|
|
262
|
+
verified state. Key rotation does not copy a raw key into configuration or
|
|
263
|
+
connection records. Status reports `reconnect-required` when the Env key differs
|
|
264
|
+
from the verified key, without making a provider request. Successful explicit
|
|
265
|
+
verification or an authorized provider operation binds the replacement key. A
|
|
266
|
+
failed check leaves the last successful binding intact; a changed reference
|
|
267
|
+
requires verification again. The fingerprint is omitted from public status.
|
|
268
|
+
|
|
269
|
+
For pre-release installations with previously saved API-key connections, run
|
|
270
|
+
connection verification again. Records without a verified-key fingerprint report
|
|
271
|
+
`reconnect-required`; no table migration or compatibility adapter is required.
|
|
272
|
+
Do not populate the fingerprint from Env without performing verification.
|
|
273
|
+
|
|
274
|
+
A provider may set `apiKeySecretOptional: true` when an empty credential is a
|
|
275
|
+
documented mode, as with a ClickHouse database user's empty password. Omitting
|
|
276
|
+
`secretRef` then passes an empty string to its header function without resolving
|
|
277
|
+
a binding. An explicit reference still must resolve to a valid string; a missing
|
|
278
|
+
binding never falls back to no credentials. Providers without this flag retain
|
|
279
|
+
the required nonempty-secret contract.
|
|
280
|
+
|
|
281
|
+
`authenticationMethods: ["api-key", "none"]` declares a separate no-credential
|
|
282
|
+
mode. Its configuration is `{ method: "none" }` without either secret or
|
|
283
|
+
registration references. `connectWithoutCredentials({ context, integrationId,
|
|
284
|
+
verificationInput?, signal? })` performs the provider's check operation before
|
|
285
|
+
saving a grant, with no credential header or binding resolution. Authorization,
|
|
286
|
+
ownership, destination validation and file persistence still apply. Mode changes
|
|
287
|
+
invalidate old connections. This mode never substitutes for an application's
|
|
288
|
+
login or bypasses provider-side database permissions.
|
|
289
|
+
Provider `settingsFields[].authenticationMethods` restricts fields to particular
|
|
290
|
+
credential modes; the shared parser rejects values left in an incompatible mode.
|
|
291
|
+
|
|
292
|
+
Provider operations receive `(input, settings)` when constructing a request.
|
|
293
|
+
OAuth providers may supply `oauth(settings)` to select regional authorization
|
|
294
|
+
metadata. `oauthResource` may be a string or function of those settings; when
|
|
295
|
+
present, it is sent as `resource` in consent, code exchange and refresh. The
|
|
296
|
+
provider owns these fixed destinations, and existing settings comparisons
|
|
297
|
+
invalidate attempts/grants after a region change.
|
|
298
|
+
Providers that require the original callback when refreshing, such as Wave,
|
|
299
|
+
set `refreshRequiresRedirectUri: true`. The runtime saves that callback with
|
|
300
|
+
the grant and supplies `redirect_uri` during refresh. Changing the callback
|
|
301
|
+
binding then requires reconnection; configuration cannot redirect an existing
|
|
302
|
+
grant's refresh request to a new callback.
|
|
303
|
+
`oauthHeaders({ clientId })` can add a provider-required application header;
|
|
304
|
+
the runtime supplies the saved registration ID and retains ownership of the
|
|
305
|
+
Bearer header. `normalizeTokenResponse(response, { settings, grantType })` adapts
|
|
306
|
+
code-exchange and refresh replies before OAuth validation, for example Twitch's
|
|
307
|
+
scope arrays or Slack's nested user token. `grantType` is `authorization_code`
|
|
308
|
+
or `refresh_token`. `authorizationScopeParameter(settings)` selects the consent
|
|
309
|
+
permission parameter when a provider uses another name, such as Slack's
|
|
310
|
+
`user_scope`; the default is `scope`.
|
|
311
|
+
These are trusted server-provider hooks, not configuration or browser inputs.
|
|
312
|
+
Normalization must preserve validation and redact provider error bodies.
|
|
313
|
+
Shared definitions can expose `scopesForSettings(settings)` to select applicable
|
|
314
|
+
permissions from their catalogue. The parser rejects inapplicable selections;
|
|
315
|
+
the shared form uses the same function and removes incompatible selections when
|
|
316
|
+
settings change. Slack uses this for its user and bot permission sets.
|
|
317
|
+
Trusted operation request builders can return `headers` for protocol fields
|
|
318
|
+
such as Xero's tenant ID. The connection service adds its credential headers;
|
|
319
|
+
operation inputs must validate identifiers before constructing these headers.
|
|
320
|
+
These headers are provider code, never a free-form client configuration field.
|
|
321
|
+
Providers can supply `apiOrigins(settings)` for destinations derived from validated
|
|
322
|
+
application settings, such as Algolia's application ID. The runtime still checks
|
|
323
|
+
the exact origin before attaching credentials; it does not allow caller-supplied
|
|
324
|
+
URLs. Provider settings can reuse the exported `secretReference` field contract
|
|
325
|
+
from `connectors-core/shared/configuration` for additional credential references.
|
|
326
|
+
Verified connections and pending OAuth attempts retain the validated settings.
|
|
327
|
+
Changing settings, including region or sandbox/live environment, requires a new
|
|
328
|
+
verification and invalidates completion of consent begun with the old settings.
|
|
329
|
+
|
|
330
|
+
A trusted provider may supply `exchange(url, requestOptions, { fetchImpl, request, resolveReference, settings, apiKey })`
|
|
331
|
+
for a protocol such as MCP or AWS Signature Version 4. Operation validation, scopes, ownership and the
|
|
332
|
+
HTTPS/origin check run before this transport receives credential headers.
|
|
333
|
+
It receives the request body and bounded signal, and must enforce those
|
|
334
|
+
credentials' destination on every protocol request, reject redirects and
|
|
335
|
+
preserve cancellation. Ordinary providers retain the shared HTTP client.
|
|
336
|
+
This is a server-code hook, never an executable configuration field.
|
|
337
|
+
A provider may set `requestTimeoutMs` for its documented API timing needs;
|
|
338
|
+
ordinary requests and OAuth grants default to 15 seconds. Canva MCP uses
|
|
339
|
+
60 seconds for design operations. These are trusted provider constants.
|
|
340
|
+
`request` is the existing parsed-JSON HTTP client. `resolveReference` is the
|
|
341
|
+
same authorized application's binding resolver, allowing a provider such as
|
|
342
|
+
Inngest to resolve its separate Event Key without duplicating runtime ownership.
|
|
343
|
+
The provider must select credentials for each destination; Inngest never sends
|
|
344
|
+
its Signing Key to the Event API.
|
|
345
|
+
|
|
346
|
+
`createConnectorsFeature(options)` supplies the ordinary `connectors.core`
|
|
347
|
+
capability and `connectors.status`, `connectors.connect` and
|
|
348
|
+
`connectors.verifyClientCredentials`, `connectors.verifyServiceAccount`, `connectors.verifyApiKey`, `connectors.verifyWithoutCredentials` and
|
|
349
|
+
`connectors.disconnect` actions. Compose it with the application's action
|
|
350
|
+
runtime. Product-specific operations call the service from their own named
|
|
351
|
+
actions. The library does not expose an arbitrary authenticated URL proxy.
|
|
352
|
+
|
|
353
|
+
Client credentials use `oauth4webapi`'s client credentials exchange and the same
|
|
354
|
+
verification, authorization and storage lock as user connections. Expiry renews
|
|
355
|
+
with the resolved application secret; no refresh token is stored for this flow.
|
|
356
|
+
Renewal requests only the existing grant. Changing a service connection's
|
|
357
|
+
configured scopes or grant type requires explicit verification again, and a
|
|
358
|
+
changed grant type also invalidates browser attempts. A failed subsequent API
|
|
359
|
+
request still commits renewed tokens. `invalid_client` and `invalid_grant`
|
|
360
|
+
require reconnection. Trusted providers can set `tokenRefreshLeewayMs`; the
|
|
361
|
+
default is 30 seconds and Databricks uses 40 seconds. This is runtime metadata,
|
|
362
|
+
not an editable source setting.
|
|
363
|
+
|
|
364
|
+
For `service-account` mode, a trusted server provider implements
|
|
365
|
+
`serviceAccountGrant({ credential, settings, scopes, fetchImpl, signal, now })`.
|
|
366
|
+
`now` is the current timestamp in milliseconds. The provider owns credential
|
|
367
|
+
parsing and the provider's grant protocol, pins its token destination, rejects
|
|
368
|
+
redirects and honors the bounded signal. It returns `access_token`,
|
|
369
|
+
`token_type: "Bearer"`, a numeric `expires_in` between zero (exclusive) and
|
|
370
|
+
86,400 seconds, and optionally the granted `scope` string. Refresh tokens are
|
|
371
|
+
rejected. This is provider code, never executable configuration.
|
|
372
|
+
|
|
373
|
+
A provider's `checkOperation` may be an operation name or a function receiving
|
|
374
|
+
an authentication method and returning its verification operation name. An
|
|
375
|
+
operation can declare `authenticationMethods` to reject other credential methods
|
|
376
|
+
before transport. This supports providers whose REST API and MCP service have
|
|
377
|
+
different credentials and verification calls, without mixing their grants.
|
|
378
|
+
|
|
379
|
+
The connection service verifies the grant through `checkOperation`, enforces
|
|
380
|
+
both configured and granted permissions, and stores tokens through the existing
|
|
381
|
+
encrypted store. Each invocation resolves the credential reference; expiry or
|
|
382
|
+
a changed credential fingerprint obtains a replacement token under the same
|
|
383
|
+
connection lock. The credential itself is never saved. Renewal is committed
|
|
384
|
+
even if the following operation fails, and failed renewal never falls back to
|
|
385
|
+
an old token. Changing settings, the reference or requested scopes requires
|
|
386
|
+
verification again. This mode does not provide managed provisioning or browser
|
|
387
|
+
consent. The Firebase adapter supplies its provider-specific JWT bearer exchange;
|
|
388
|
+
the core and provider contracts are verified with controlled service-account fixtures.
|
|
389
|
+
|
|
390
|
+
## File storage
|
|
391
|
+
|
|
392
|
+
Import `createFileConnectionStore` and `createCredentialProtection` from
|
|
393
|
+
`@jskit-ai/connectors-core/server/file-storage`. This entry point does not import
|
|
394
|
+
or require database-runtime. Use `createFileConnectionStore({ directory,
|
|
395
|
+
protection })` with an absolute private directory outside application source.
|
|
396
|
+
Each connection uses an encrypted JSON record containing its grant and pending
|
|
397
|
+
authorization attempts. The same protection contract is described below.
|
|
398
|
+
|
|
399
|
+
The store locks each identity across processes, commits by replacing the whole
|
|
400
|
+
file, and preserves the previous record when a callback or write fails. It
|
|
401
|
+
prunes expired attempts when that connection is accessed. Different owners
|
|
402
|
+
cannot open copied records. Malformed files and symlinks fail without rewriting
|
|
403
|
+
them. Files default to mode 0600 and new directories to 0700; hosts can supply
|
|
404
|
+
`fileMode` and `directoryMode` to match an existing filesystem identity contract.
|
|
405
|
+
It does not change permissions recursively or fix an incorrectly provisioned
|
|
406
|
+
runtime directory. Back up both the private JSON state and its separate key.
|
|
407
|
+
|
|
408
|
+
Locks use `proper-lockfile` with a consistent 60-second stale interval and
|
|
409
|
+
heartbeat. All writers must use this store and must not manually remove live
|
|
410
|
+
locks. A process killed during a provider exchange can still need reconnection;
|
|
411
|
+
local file replacement cannot make a remote token rotation transactional.
|
|
412
|
+
|
|
413
|
+
## Storage and migrations
|
|
414
|
+
|
|
415
|
+
Import `createKnexConnectionStore` and `createCredentialProtection` from
|
|
416
|
+
`@jskit-ai/connectors-core/server/storage`. Use the application's existing Knex
|
|
417
|
+
client, with its selected JSKIT MySQL or PostgreSQL driver.
|
|
418
|
+
|
|
419
|
+
```js
|
|
420
|
+
const protection = createCredentialProtection({
|
|
421
|
+
keys: { current: decoded32ByteSecretKey },
|
|
422
|
+
activeKeyId: "current"
|
|
423
|
+
});
|
|
424
|
+
const store = createKnexConnectionStore({ knex, protection });
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Keys come from server secret storage, separately from the database and source.
|
|
428
|
+
The protection wrapper uses the `jose` library's authenticated JWE encryption;
|
|
429
|
+
each encrypted record is bound to its owning connection. Keep previous named
|
|
430
|
+
keys while existing rows still use them. New writes use `activeKeyId`. Deleting
|
|
431
|
+
an old key before those rows are rewritten makes them unreadable. Applications
|
|
432
|
+
with an existing vault can instead provide compatible `seal(value, binding)`
|
|
433
|
+
and `open(ciphertext, binding)` methods.
|
|
434
|
+
|
|
435
|
+
Install `@jskit-ai/database-runtime` explicitly when using the Knex store. It is
|
|
436
|
+
an optional peer: editing portable configuration does not install or activate a
|
|
437
|
+
database provider. Applications using another store do not need this peer.
|
|
438
|
+
|
|
439
|
+
The package declares its authoritative migration directory in `package.json`.
|
|
440
|
+
Use the normal app-owned `knexfile.js` and migration scripts described by the
|
|
441
|
+
selected database package; do not copy these migrations or run them on startup
|
|
442
|
+
implicitly. Tables contain encrypted credentials/attempts and opaque identity
|
|
443
|
+
keys, with no dependency on a particular user table. Run
|
|
444
|
+
`store.pruneExpiredAttempts()` from the application's existing maintenance task.
|
|
445
|
+
|
|
446
|
+
A custom store implements `withConnection({ owner, integrationId }, work)`.
|
|
447
|
+
It must serialize the whole callback across processes for that identity and
|
|
448
|
+
commit on successful return. The callback receives `connection`, `save(value)`,
|
|
449
|
+
`remove()`, `putAttempt(attempt)`, `latestAttempt({ after })` and
|
|
450
|
+
`consumeAttempt(state)`. `latestAttempt` returns the current owner/slot's
|
|
451
|
+
latest-expiring attempt strictly after the supplied timestamp, or null, without
|
|
452
|
+
consuming it. Consumption must
|
|
453
|
+
affect only the current owner and slot. Removal also invalidates pending
|
|
454
|
+
attempts. Failures returned by the runtime are thrown after commit so failed
|
|
455
|
+
API requests do not roll back rotated refresh tokens or restore consumed codes.
|
|
456
|
+
|
|
457
|
+
## Current limits and proof
|
|
458
|
+
|
|
459
|
+
SQL locking spans bounded provider requests to serialize refresh, completion
|
|
460
|
+
and disconnect. Different connections use different locks. Database connection
|
|
461
|
+
pool sizing and process-crash recovery during provider token rotation need
|
|
462
|
+
deployment testing. A crash between provider rotation and a database commit
|
|
463
|
+
can still require reconnecting; there is no distributed atomic transaction
|
|
464
|
+
with Google.
|
|
465
|
+
|
|
466
|
+
Focused tests cover consent denial, replay, ownership, refresh, cancellation,
|
|
467
|
+
safe errors, configuration and Feature composition. The SQL test covers real
|
|
468
|
+
MariaDB persistence, independent pools, rollback, encryption and migrations.
|
|
469
|
+
File tests cover restart, interrupted callbacks, rejected writes, record binding,
|
|
470
|
+
symlinks, consent expiry and serialization across independent Node processes.
|
|
471
|
+
PostgreSQL and live Google consent are not yet verified. Gmail supplies a
|
|
472
|
+
verified mailbox display label; general account-selection screens and
|
|
473
|
+
provider-wide revocation remain separate work. Managed registrations are rejected.
|
|
474
|
+
|
|
475
|
+
The three new packages also install from local npm tarballs into a clean
|
|
476
|
+
standalone application. Its CLI validation, package migration discovery,
|
|
477
|
+
migration status, disconnected status and repeat migration run were exercised
|
|
478
|
+
against a disposable MariaDB database. These checks do not publish packages.
|
|
479
|
+
|
|
480
|
+
For application wiring see the Google Calendar package's `calendar-cli` pattern.
|
|
481
|
+
|
|
482
|
+
For direct API-key or credential-free connections, operation scopes constrain
|
|
483
|
+
the application configuration; they are not provider consent grants. Both
|
|
484
|
+
verification and invocation enforce those configured limits. `grantedScopes`
|
|
485
|
+
remains empty. OAuth connections additionally enforce the actual stored grant.
|
|
486
|
+
Trusted exchange implementations receive validated `settings` and the resolved
|
|
487
|
+
`apiKey` only for API-key mode, allowing an SDK to sign requests without placing
|
|
488
|
+
raw credentials into transport headers first. These inputs are server-only.
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
## Resuming application setup
|
|
492
|
+
|
|
493
|
+
`resumeAuthorization({ context, integrationId })` checks application connect
|
|
494
|
+
permission and returns the pending `{ authorizationUrl, expiresAt, callbackUrl }`, or null.
|
|
495
|
+
It uses the existing store and does not call the provider. Changing registration,
|
|
496
|
+
callback, scopes or settings invalidates the resumable result. Cancellation and
|
|
497
|
+
completion still consume the exact OAuth state once. A previous connected grant
|
|
498
|
+
remains independent of a new pending attempt.
|
|
499
|
+
|
|
500
|
+
For valid authorization-code bindings, `status()` also reports the resolved
|
|
501
|
+
`callbackUrl`. Applications can display this validated, public URL independently
|
|
502
|
+
of any editor suggestion; credentials and other Env values remain private.
|
|
503
|
+
|
|
504
|
+
Use [the application setup command guide](docs/setup-command.md) when connecting
|
|
505
|
+
an editor or CLI setup screen to this runtime. The app owns that executable and
|
|
506
|
+
its authenticated operator context; this library does not import an editor.
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
Status checks resolve required local bindings without a provider request. Before
|
|
510
|
+
a first connection, missing credentials or invalid callbacks return `unconfigured`
|
|
511
|
+
with `configurationError` set to `connector_binding_missing` or
|
|
512
|
+
`connector_callback_invalid`. For an existing matching connection they report
|
|
513
|
+
`reconnect-required` without deleting or changing the stored grant. Restoring the
|
|
514
|
+
binding can restore its previous status; status alone does not verify a new key.
|
|
515
|
+
Explicit disconnect remains available without working provider credentials.
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
Provider definitions may select `tokenRequestEncoding: "json"` when the token
|
|
519
|
+
endpoint requires JSON instead of OAuth's ordinary form encoding (Notion).
|
|
520
|
+
Only token grant requests use this encoding; API calls keep their existing
|
|
521
|
+
operation format. Authentication headers, redirect policy and abort signal
|
|
522
|
+
remain supplied by the OAuth library. `oauthBasicEncoding: "raw"` selects
|
|
523
|
+
base64 of the literal client ID/secret pair for providers documenting that
|
|
524
|
+
Basic convention; the default remains oauth4webapi's OAuth encoding. These
|
|
525
|
+
are provider implementation choices, not user-editable configuration fields.
|
|
526
|
+
|
|
527
|
+
Provider runtime implementations can declare `runtimeForSettings(settings)`
|
|
528
|
+
for materially different protocols under one catalogue entry (Notion REST/MCP).
|
|
529
|
+
The connection service resolves this trusted implementation after configuration
|
|
530
|
+
validation and before operations; settings never supply executable code.
|
|
531
|
+
`accountModesForSettings(settings)` constrains ownership choices in both
|
|
532
|
+
validation and forms. Client authentication metadata functions also receive
|
|
533
|
+
settings as their second argument. Existing providers keep their static behavior.
|
|
534
|
+
|
|
535
|
+
`dataFromTokenResponse(response, previousData)` lets a provider validate and
|
|
536
|
+
retain required account-specific routing from a processed token response
|
|
537
|
+
(Pipedrive's company API domain). Its return value is stored as private
|
|
538
|
+
`providerData` with the grant, including refreshes; it is omitted from public
|
|
539
|
+
connection status. Operation `request(input, settings, providerData)` and
|
|
540
|
+
`apiOrigins(settings, providerData)` receive this data. The provider must
|
|
541
|
+
validate destinations and account continuity before returning metadata. The
|
|
542
|
+
core still enforces HTTPS, allowed origins and rejection of URL credentials.
|
|
543
|
+
This metadata does not alter shared configuration or accept user-supplied
|
|
544
|
+
executable code; the file store seals it with the rest of the connection.
|
|
545
|
+
|
|
546
|
+
Scope metadata may include `authenticationMethods` when a permission belongs only
|
|
547
|
+
to particular credential modes. `getProviderScopes(provider, settings, grantType,
|
|
548
|
+
authenticationMethod)` filters these choices; configuration validation uses the
|
|
549
|
+
selected method. Forms remove incompatible scopes on method changes and select
|
|
550
|
+
recommended method-specific scopes when entering that mode. Granola uses this
|
|
551
|
+
to keep OAuth identity scopes out of API-key configuration.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Application-owned OAuth callbacks
|
|
2
|
+
|
|
3
|
+
Each application supplies its own provider registration and callback route.
|
|
4
|
+
The editor's hosting domain does not supply a callback or a shared registration.
|
|
5
|
+
The application must keep working when exported or when the editor stops.
|
|
6
|
+
|
|
7
|
+
## Configuration and ownership
|
|
8
|
+
|
|
9
|
+
Set registration `source` to `own`. Put the public client ID in `clientId`,
|
|
10
|
+
the client secret reference in `clientSecretRef`, and the full callback URL
|
|
11
|
+
reference in `callbackUrlRef`. For example, `env:GOOGLE_CALLBACK_URL` resolves
|
|
12
|
+
to an application-controlled URL such as
|
|
13
|
+
`https://example.com/integrations/google/callback`. That example is not a
|
|
14
|
+
provisioned endpoint: application code must implement it before connection.
|
|
15
|
+
|
|
16
|
+
Register the resolved callback with the provider using its required redirect
|
|
17
|
+
rules. Development and production use their own environment bindings and runtime
|
|
18
|
+
state. Never use an editor dashboard URL as an application callback. Providers
|
|
19
|
+
may require separate registrations or allow several explicit redirect URLs;
|
|
20
|
+
follow the provider guide rather than assuming wildcard support.
|
|
21
|
+
|
|
22
|
+
## Application flow
|
|
23
|
+
|
|
24
|
+
1. Authenticate the initiating user or authorized setup operator in the app.
|
|
25
|
+
2. Call `beginAuthorization` with the app-derived owner and integration ID.
|
|
26
|
+
3. Open the returned authorization URL. Keep the pending attempt in the app's
|
|
27
|
+
durable connection store; closing an editor panel must not delete it.
|
|
28
|
+
4. The app callback passes the complete callback URL to `completeAuthorization`
|
|
29
|
+
under the same authenticated owner. The runtime validates the pending attempt,
|
|
30
|
+
exchanges the code, verifies access and stores the grant.
|
|
31
|
+
5. Render safe connection status. Tokens and provider callback parameters do
|
|
32
|
+
not belong in editor messages or source configuration.
|
|
33
|
+
|
|
34
|
+
Application code owns HTTP routes and identity recovery on the callback. A
|
|
35
|
+
browser message is not proof of a successful connection. A CLI can use these
|
|
36
|
+
same library operations with the application's callback and storage.
|
|
37
|
+
`cancelAuthorization` cancels a pending attempt while preserving an existing
|
|
38
|
+
connection. `disconnect` removes local access and invalidates pending consent;
|
|
39
|
+
it does not promise provider-wide revocation.
|
|
40
|
+
|
|
41
|
+
The application authorization callback must distinguish shared-account setup
|
|
42
|
+
from individual app-user connection. Connecting an administrator's mailbox does
|
|
43
|
+
not grant every user access to their own mailbox, nor grant other projects access.
|
|
44
|
+
Application login remains separate from permission to read provider data.
|
|
45
|
+
|
|
46
|
+
## Changing domains or moving the application
|
|
47
|
+
|
|
48
|
+
Before changing domains, configure the new callback at the provider and in the
|
|
49
|
+
application environment. A changed callback invalidates pending authorization;
|
|
50
|
+
start a fresh attempt instead of reusing one begun with the old callback.
|
|
51
|
+
Existing grants can remain usable when the provider does not bind refresh to
|
|
52
|
+
the old redirect URI. Providers whose refresh operation requires that URI report
|
|
53
|
+
reconnection when it changes. Changing the registration's client identity also
|
|
54
|
+
requires reconnection. Users may need to sign into the app again on the new
|
|
55
|
+
domain, independently of whether their provider grant remains usable.
|
|
56
|
+
|
|
57
|
+
Moving source alone does not move grants. Supply the app's environment, durable
|
|
58
|
+
connection storage and encryption keys at the new installation. Preserve its
|
|
59
|
+
application and subject identities. No editor service is needed to refresh tokens.
|
|
60
|
+
|
|
61
|
+
## Verification limits
|
|
62
|
+
|
|
63
|
+
Protocol fixtures cover callback binding, ownership, denial, replay, cancellation
|
|
64
|
+
and registration changes. They do not prove provider approval, a deployed route,
|
|
65
|
+
a real browser consent flow or a complete generated application.
|