@every-app/sdk 0.3.0-rc.5 → 0.3.0-rc.6
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/dist/cloudflare/catalogSettings.generated.d.ts +1 -16
- package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -1
- package/dist/cloudflare/catalogSettings.generated.js +0 -49
- package/dist/cloudflare/worker.d.ts +14 -0
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +19 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/internal/aiDefault.d.ts +70 -7
- package/dist/internal/aiDefault.d.ts.map +1 -1
- package/dist/internal/aiDefault.js +145 -15
- package/dist/internal/aiProviders.d.ts +3 -1
- package/dist/internal/aiProviders.d.ts.map +1 -1
- package/dist/internal/aiProviders.js +12 -3
- package/dist/internal/appSettings.d.ts +65 -5
- package/dist/internal/appSettings.d.ts.map +1 -1
- package/dist/internal/appSettings.js +141 -23
- package/dist/internal/capabilities.d.ts +52 -433
- package/dist/internal/capabilities.d.ts.map +1 -1
- package/dist/internal/capabilities.js +25 -558
- package/dist/internal/catalog/index.d.ts +6 -6
- package/dist/internal/catalog/inference.d.ts +6 -6
- package/dist/internal/catalog/inference.d.ts.map +1 -1
- package/dist/internal/catalog/inference.js +9 -6
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +1 -0
- package/dist/internal/gatewaySettings.d.ts +0 -6
- package/dist/internal/gatewaySettings.d.ts.map +1 -1
- package/dist/internal/gatewaySettings.js +1 -14
- package/dist/internal/identity.d.ts +12 -1
- package/dist/internal/identity.d.ts.map +1 -1
- package/dist/internal/identity.js +11 -1
- package/dist/internal/oauthClient.d.ts +10 -12
- package/dist/internal/oauthClient.d.ts.map +1 -1
- package/dist/internal/oauthClient.js +20 -81
- package/dist/internal/oauthPresets.d.ts +7 -3
- package/dist/internal/oauthPresets.d.ts.map +1 -1
- package/dist/internal/oauthPresets.js +533 -3
- package/dist/internal/oauthSpec.d.ts +44 -1
- package/dist/internal/oauthSpec.d.ts.map +1 -1
- package/dist/internal/oauthSpec.js +147 -34
- package/dist/internal/protocol.d.ts +14 -0
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +7 -0
- package/dist/internal/providerRegistry.d.ts +61 -1
- package/dist/internal/providerRegistry.d.ts.map +1 -1
- package/dist/internal/providerRegistry.js +127 -6
- package/dist/internal/providerSpec.d.ts +14 -7
- package/dist/internal/providerSpec.d.ts.map +1 -1
- package/dist/internal/providerSpec.js +306 -7
- package/dist/manifest.d.ts +25 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/server/ai.d.ts +45 -0
- package/dist/server/ai.d.ts.map +1 -1
- package/dist/server/ai.js +77 -1
- package/dist/server/aiBinding.d.ts.map +1 -1
- package/dist/server/aiBinding.js +3 -3
- package/dist/server/authorizeUrl.d.ts +1 -1
- package/dist/server/authorizeUrl.d.ts.map +1 -1
- package/dist/server/authorizeUrl.js +2 -6
- package/dist/server/emailBinding.js +1 -1
- package/dist/server/emit.js +1 -1
- package/dist/server/gatewayFetch.d.ts.map +1 -1
- package/dist/server/gatewayFetch.js +20 -7
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -2
- package/dist/server/integrationFetch.d.ts +4 -31
- package/dist/server/integrationFetch.d.ts.map +1 -1
- package/dist/server/integrationFetch.js +44 -42
- package/dist/server/listMembers.js +1 -1
- package/dist/server/refusal.d.ts +55 -0
- package/dist/server/refusal.d.ts.map +1 -0
- package/dist/server/refusal.js +193 -0
- package/dist/server/sendEmail.js +1 -1
- package/package.json +1 -1
|
@@ -264,6 +264,62 @@ function routes(value, path, hosts, problems) {
|
|
|
264
264
|
});
|
|
265
265
|
return out;
|
|
266
266
|
}
|
|
267
|
+
/** The fields one scope set may carry, in a spec's `scopeSets` or an overlay. */
|
|
268
|
+
const SCOPE_SET_FIELDS = ["label", "access", "scopes", "userScopes", "routes"];
|
|
269
|
+
/**
|
|
270
|
+
* One scope set (`{ label, access, scopes, userScopes?, routes? }`) at
|
|
271
|
+
* `at`, its routes on `hosts` alone; undefined when a field is wrong,
|
|
272
|
+
* every problem reported. `overlay`: a set added to a built-in client
|
|
273
|
+
* must name a scope and say what it unlocks (a set with no routes unlocks
|
|
274
|
+
* nothing on the held-token path); a spec's own set may leave both empty
|
|
275
|
+
* (Notion's sets carry no scope string).
|
|
276
|
+
*/
|
|
277
|
+
export function parseScopeSet(raw, at, hosts, problems, { overlay = false } = {}) {
|
|
278
|
+
if (!isRecord(raw)) {
|
|
279
|
+
problems.push({ path: at, message: "must be { label, access, scopes }" });
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
for (const field of Object.keys(raw)) {
|
|
283
|
+
if (!SCOPE_SET_FIELDS.includes(field)) {
|
|
284
|
+
problems.push({ path: `${at}.${field}`, message: "unknown field" });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const label = printable(raw["label"], `${at}.label`, problems, 80);
|
|
288
|
+
if (raw["label"] === undefined) {
|
|
289
|
+
problems.push({ path: `${at}.label`, message: "required" });
|
|
290
|
+
}
|
|
291
|
+
const access = raw["access"];
|
|
292
|
+
if (access !== "read" && access !== "write") {
|
|
293
|
+
problems.push({ path: `${at}.access`, message: "must be read or write" });
|
|
294
|
+
}
|
|
295
|
+
const scopes = scopeList(raw["scopes"], `${at}.scopes`, problems);
|
|
296
|
+
if (raw["scopes"] === undefined) {
|
|
297
|
+
problems.push({ path: `${at}.scopes`, message: "required" });
|
|
298
|
+
}
|
|
299
|
+
else if (overlay && scopes && scopes.length === 0) {
|
|
300
|
+
problems.push({ path: `${at}.scopes`, message: "must list a scope" });
|
|
301
|
+
}
|
|
302
|
+
const userScopes = scopeList(raw["userScopes"], `${at}.userScopes`, problems);
|
|
303
|
+
const parsedRoutes = routes(raw["routes"], `${at}.routes`, hosts, problems);
|
|
304
|
+
if (overlay && (!parsedRoutes || parsedRoutes.length === 0)) {
|
|
305
|
+
problems.push({
|
|
306
|
+
path: `${at}.routes`,
|
|
307
|
+
message: "required: at least one { host, paths } the set unlocks, on one of the client's API hosts",
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (label === undefined ||
|
|
311
|
+
(access !== "read" && access !== "write") ||
|
|
312
|
+
scopes === undefined) {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
label,
|
|
317
|
+
access,
|
|
318
|
+
scopes,
|
|
319
|
+
...(userScopes === undefined ? {} : { userScopes }),
|
|
320
|
+
routes: parsedRoutes ?? [],
|
|
321
|
+
};
|
|
322
|
+
}
|
|
267
323
|
function scopeSets(value, hosts, problems) {
|
|
268
324
|
if (!isRecord(value)) {
|
|
269
325
|
problems.push({
|
|
@@ -290,43 +346,101 @@ function scopeSets(value, hosts, problems) {
|
|
|
290
346
|
});
|
|
291
347
|
continue;
|
|
292
348
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
349
|
+
const set = parseScopeSet(raw, at, hosts, problems);
|
|
350
|
+
if (set)
|
|
351
|
+
out[name] = set;
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
355
|
+
/** The fields a scope-set overlay carries: a set's, with its `name` and the client it belongs to. */
|
|
356
|
+
const OVERLAY_FIELDS = ["provider", "name", ...SCOPE_SET_FIELDS];
|
|
357
|
+
/**
|
|
358
|
+
* A scope set an operator adds to a built-in OAuth client
|
|
359
|
+
* (`everyapp provider scope-set add google-workspace gmail-send.json`):
|
|
360
|
+
* `{ "name": "gmail.send", "label", "access", "scopes", "routes" }`, held
|
|
361
|
+
* to the rules a built-in set is (the set's shape, routes on the client's
|
|
362
|
+
* `api` hosts alone, anchored `posts`) plus two of its own: `access` is
|
|
363
|
+
* said outright (a write set is never implied by its scopes) and `routes`
|
|
364
|
+
* is required. A `provider` field, when present, must be `id`. The name
|
|
365
|
+
* may not be one of the client's own sets: an overlay adds, never
|
|
366
|
+
* replaces. Nothing here reads a value.
|
|
367
|
+
*/
|
|
368
|
+
export function parseScopeSetOverlay(input, client) {
|
|
369
|
+
const problems = [];
|
|
370
|
+
if (!isRecord(input)) {
|
|
371
|
+
return {
|
|
372
|
+
ok: false,
|
|
373
|
+
problems: [{ path: "(root)", message: "must be a JSON object" }],
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
for (const field of Object.keys(input)) {
|
|
377
|
+
if (!OVERLAY_FIELDS.includes(field)) {
|
|
378
|
+
problems.push({ path: field, message: "unknown field" });
|
|
313
379
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
380
|
+
}
|
|
381
|
+
const policy = client.capability.scopePolicy;
|
|
382
|
+
if (!policy) {
|
|
383
|
+
problems.push({
|
|
384
|
+
path: "(root)",
|
|
385
|
+
message: `${client.id} has no scope sets: the whole client is the grant`,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
if (input["provider"] !== undefined && input["provider"] !== client.id) {
|
|
389
|
+
problems.push({
|
|
390
|
+
path: "provider",
|
|
391
|
+
message: `is ${String(input["provider"])}, not ${client.id}`,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const name = input["name"];
|
|
395
|
+
if (typeof name !== "string" || !SCOPE_SET_NAME.test(name)) {
|
|
396
|
+
problems.push({
|
|
397
|
+
path: "name",
|
|
398
|
+
message: "required: identity, or <area>.<access> in lowercase (gmail.send)",
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
else if (policy && Object.hasOwn(policy.sets, name)) {
|
|
402
|
+
problems.push({
|
|
403
|
+
path: "name",
|
|
404
|
+
message: `${name} is one of ${client.id}'s own sets; an overlay adds a set, never replaces one`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
// The set's own fields alone: an unknown one was reported above.
|
|
408
|
+
const fields = Object.fromEntries(Object.entries(input).filter(([field]) => SCOPE_SET_FIELDS.includes(field)));
|
|
409
|
+
const set = parseScopeSet(fields, "(set)", client.capability.api, problems, {
|
|
410
|
+
overlay: true,
|
|
411
|
+
});
|
|
412
|
+
if (problems.length > 0 || !set || typeof name !== "string") {
|
|
413
|
+
return { ok: false, problems };
|
|
414
|
+
}
|
|
415
|
+
return { ok: true, name, set };
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* `capability` with `sets` added to its scope policy: the sets after the
|
|
419
|
+
* client's own, in the order given, and each set's scopes added to the
|
|
420
|
+
* authorize URL's list (what the operator's client must be allowed), as
|
|
421
|
+
* `parseOAuthUserSpec` builds it. A name the client already has is left
|
|
422
|
+
* as the client's (the registry refuses such an overlay before this).
|
|
423
|
+
*/
|
|
424
|
+
export function withScopeSets(capability, sets) {
|
|
425
|
+
const policy = capability.scopePolicy;
|
|
426
|
+
if (!policy)
|
|
427
|
+
return capability;
|
|
428
|
+
const merged = { ...policy.sets };
|
|
429
|
+
const scopes = [...capability.authorize.scopes];
|
|
430
|
+
for (const [name, set] of Object.entries(sets)) {
|
|
431
|
+
if (Object.hasOwn(merged, name))
|
|
319
432
|
continue;
|
|
433
|
+
merged[name] = set;
|
|
434
|
+
for (const scope of set.scopes) {
|
|
435
|
+
if (!scopes.includes(scope))
|
|
436
|
+
scopes.push(scope);
|
|
320
437
|
}
|
|
321
|
-
out[name] = {
|
|
322
|
-
label,
|
|
323
|
-
access,
|
|
324
|
-
scopes,
|
|
325
|
-
...(userScopes === undefined ? {} : { userScopes }),
|
|
326
|
-
routes: parsedRoutes ?? [],
|
|
327
|
-
};
|
|
328
438
|
}
|
|
329
|
-
return
|
|
439
|
+
return {
|
|
440
|
+
...capability,
|
|
441
|
+
authorize: { ...capability.authorize, scopes },
|
|
442
|
+
scopePolicy: { ...policy, sets: merged },
|
|
443
|
+
};
|
|
330
444
|
}
|
|
331
445
|
function identity(value, hosts, problems) {
|
|
332
446
|
if (value === undefined)
|
|
@@ -551,7 +665,6 @@ export function parseOAuthUserSpec(input, common, problems) {
|
|
|
551
665
|
authorize: { url: authorize.url, scopes: union },
|
|
552
666
|
scopePolicy: policy,
|
|
553
667
|
api: [...common.hosts],
|
|
554
|
-
redirectPaths: [],
|
|
555
668
|
flow,
|
|
556
669
|
...(common.readMethods === undefined
|
|
557
670
|
? {}
|
|
@@ -103,6 +103,11 @@ export interface IdentitySubject {
|
|
|
103
103
|
email: string;
|
|
104
104
|
orgId: string;
|
|
105
105
|
orgRole: string;
|
|
106
|
+
/**
|
|
107
|
+
* The organization's name as its owners set it (`org_name`), so an app
|
|
108
|
+
* can show "Acme" rather than an id. Left out when the minter has none.
|
|
109
|
+
*/
|
|
110
|
+
orgName?: string;
|
|
106
111
|
}
|
|
107
112
|
/**
|
|
108
113
|
* A member's role in the organization, as the identity token carries it.
|
|
@@ -122,6 +127,13 @@ export interface EveryAppUser {
|
|
|
122
127
|
email: string;
|
|
123
128
|
orgId: string;
|
|
124
129
|
orgRole: OrgRole;
|
|
130
|
+
/**
|
|
131
|
+
* The organization's display name (the `org_name` claim), as its owners
|
|
132
|
+
* set it: trimmed, at most 64 characters, no control characters. Show
|
|
133
|
+
* it; never key data by it (names change, `orgId` doesn't). Undefined
|
|
134
|
+
* when the token carries none.
|
|
135
|
+
*/
|
|
136
|
+
orgName?: string;
|
|
125
137
|
channel: IdentityChannel;
|
|
126
138
|
/** The acting principal (user, or `mcp:<client>` / agent). */
|
|
127
139
|
actor: IdentityActor;
|
|
@@ -138,6 +150,8 @@ export interface IdentityClaims extends JWTPayload {
|
|
|
138
150
|
email: string;
|
|
139
151
|
org_id: string;
|
|
140
152
|
org_role: string;
|
|
153
|
+
/** The organization's display name; absent when the minter had none. */
|
|
154
|
+
org_name?: string;
|
|
141
155
|
chan: IdentityChannel;
|
|
142
156
|
act: IdentityActor;
|
|
143
157
|
scopes: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/internal/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,eAAO,MAAM,YAAY,UAAU,CAAC;AACpC,eAAO,MAAM,eAAe,sBAAsB,CAAC;AACnD,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAC3D;;;;GAIG;AACH,eAAO,MAAM,UAAU,kCAAkC,CAAC;AAC1D,eAAO,MAAM,eAAe,wBAAwB,CAAC;AACrD,eAAO,MAAM,aAAa,sBAAsB,CAAC;AACjD;;;;GAIG;AACH,eAAO,MAAM,aAAa,sBAAsB,CAAC;AACjD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,qBAAqB,CAAC;AACvD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,mBAAmB,CAAC;AAC3C;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,uBAAuB,CAAC;AACnD,mDAAmD;AACnD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,cAAc,CAAC;AACxD,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,8CAA8C;AAC9C,eAAO,MAAM,UAAU,oBAAoB,CAAC;AAC5C;;;;;GAKG;AACH,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAChD,qEAAqE;AACrE,eAAO,MAAM,eAAe,kCAAkC,CAAC;AAC/D,mFAAmF;AACnF,eAAO,MAAM,UAAU,6BAA6B,CAAC;AACrD;;;GAGG;AACH,eAAO,MAAM,UAAU,oBAAoB,CAAC;AAC5C,qFAAqF;AACrF,eAAO,MAAM,OAAO,iBAAiB,CAAC;AACtC;;;;GAIG;AACH,eAAO,MAAM,UAAU,qBAAqB,CAAC;AAC7C;;;;GAIG;AACH,eAAO,MAAM,UAAU,YAAY,CAAC;AACpC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAC1C,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,WAAW,CAAC;AAEzC,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AAC9D,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC7D,2DAA2D;AAC3D,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE9C,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/internal/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -107,6 +107,7 @@ export function identitySubjectToClaims(subject, opts) {
|
|
|
107
107
|
email: subject.email,
|
|
108
108
|
org_id: subject.orgId,
|
|
109
109
|
org_role: subject.orgRole,
|
|
110
|
+
...(subject.orgName ? { org_name: subject.orgName } : {}),
|
|
110
111
|
chan: opts.channel ?? "web",
|
|
111
112
|
act: opts.actor ?? { sub: subject.sub },
|
|
112
113
|
scopes: opts.scopes ?? ["*"],
|
|
@@ -141,6 +142,11 @@ export function identityClaimsToEveryAppUser(payload) {
|
|
|
141
142
|
if (!sub || !email || !orgId || !orgRole) {
|
|
142
143
|
throw new ProtocolClaimsError("identity token missing required claims");
|
|
143
144
|
}
|
|
145
|
+
// Display only, so a token without it (or with a malformed one) is still
|
|
146
|
+
// an identity: the app falls back to no name.
|
|
147
|
+
const orgName = typeof payload.org_name === "string" && payload.org_name
|
|
148
|
+
? payload.org_name
|
|
149
|
+
: undefined;
|
|
144
150
|
const chan = payload.chan ?? "web";
|
|
145
151
|
const act = payload.act ?? { sub };
|
|
146
152
|
const scopes = Array.isArray(payload.scopes)
|
|
@@ -152,6 +158,7 @@ export function identityClaimsToEveryAppUser(payload) {
|
|
|
152
158
|
email,
|
|
153
159
|
orgId,
|
|
154
160
|
orgRole: toOrgRole(orgRole),
|
|
161
|
+
...(orgName ? { orgName } : {}),
|
|
155
162
|
channel: chan,
|
|
156
163
|
actor: { sub: act.sub ?? sub },
|
|
157
164
|
scopes,
|
|
@@ -19,8 +19,19 @@
|
|
|
19
19
|
* an IP literal, localhost, or a `.local`, `.internal` or `.arpa` name)
|
|
20
20
|
* and not one the gateway serves itself (`refuseHost`, which knows the
|
|
21
21
|
* gateway's own host and `APP_BASE_DOMAIN`).
|
|
22
|
+
*
|
|
23
|
+
* A second kind of row, a scope-set overlay (`provider_scope_sets`,
|
|
24
|
+
* `everyapp provider scope-set add`), adds a named scope set to a
|
|
25
|
+
* built-in OAuth client that has scope sets (Google, Microsoft, Slack):
|
|
26
|
+
* its scopes, and the routes on the client's own API hosts it unlocks,
|
|
27
|
+
* checked by the rules a built-in set is checked by
|
|
28
|
+
* (`providers.scope-set-overlay-within-client`). An overlay never replaces
|
|
29
|
+
* one of the client's own sets, never names a host outside the client's
|
|
30
|
+
* `api`, and never applies to a registered row (an operator replaces the
|
|
31
|
+
* row's spec instead). Apps list an overlay set under `scopes` as they
|
|
32
|
+
* list a built-in one.
|
|
22
33
|
*/
|
|
23
|
-
import { type Capability, type CapabilityLookup } from "./capabilities.js";
|
|
34
|
+
import { type Capability, type CapabilityLookup, type OAuthClientCapability, type OAuthScopeSet } from "./capabilities.js";
|
|
24
35
|
import { type RegistrableCapability } from "./providerSpec.js";
|
|
25
36
|
/** A `providers` row as the gateway or the CLI read it. */
|
|
26
37
|
export interface ProviderRow {
|
|
@@ -39,6 +50,33 @@ export interface ProviderRow {
|
|
|
39
50
|
/** When, in epoch milliseconds. */
|
|
40
51
|
addedAt?: number | null;
|
|
41
52
|
}
|
|
53
|
+
/** A `provider_scope_sets` row as the gateway or the CLI read it. */
|
|
54
|
+
export interface ScopeSetRow {
|
|
55
|
+
/** The built-in OAuth client the set belongs to (`google-workspace`). */
|
|
56
|
+
providerId: string;
|
|
57
|
+
/** The set's name, what an app lists under `scopes` (`gmail.send`). */
|
|
58
|
+
name: string;
|
|
59
|
+
/** The set's spec, as JSON text or already parsed (`parseScopeSetOverlay`'s shape). */
|
|
60
|
+
spec: unknown;
|
|
61
|
+
addedBy?: string | null;
|
|
62
|
+
addedAt?: number | null;
|
|
63
|
+
}
|
|
64
|
+
/** One overlay the registry serves, as `provider show` reports it. */
|
|
65
|
+
export interface RegisteredScopeSet {
|
|
66
|
+
providerId: string;
|
|
67
|
+
name: string;
|
|
68
|
+
set: OAuthScopeSet;
|
|
69
|
+
addedBy: string | null;
|
|
70
|
+
addedAt: number | null;
|
|
71
|
+
}
|
|
72
|
+
export type ScopeSetRefusalCode = "SCOPE_SET_INVALID" | "SCOPE_SET_PROVIDER_UNKNOWN" | "SCOPE_SET_BUILTIN";
|
|
73
|
+
/** Why an overlay row is not a scope set on this gateway. */
|
|
74
|
+
export interface ScopeSetRefusal {
|
|
75
|
+
providerId: string;
|
|
76
|
+
name: string;
|
|
77
|
+
code: ScopeSetRefusalCode;
|
|
78
|
+
message: string;
|
|
79
|
+
}
|
|
42
80
|
/**
|
|
43
81
|
* The part of a row a key is set for: its hosts, its host setting and its
|
|
44
82
|
* auth, as one canonical string (sorted keys, sorted hosts), so that
|
|
@@ -85,6 +123,12 @@ export interface ProviderRegistry extends CapabilityLookup {
|
|
|
85
123
|
rows(): readonly RegisteredProvider[];
|
|
86
124
|
/** The rows this registry left out, each with why. */
|
|
87
125
|
readonly rejected: readonly RowRefusal[];
|
|
126
|
+
/** The scope-set overlays this registry applied to built-in clients, in row order. */
|
|
127
|
+
scopeSets(): readonly RegisteredScopeSet[];
|
|
128
|
+
/** Where a client's scope set comes from: its catalog entry, an overlay row, or nowhere. */
|
|
129
|
+
scopeSetSource(providerId: string, name: string): "catalog" | "row" | undefined;
|
|
130
|
+
/** The overlay rows this registry left out, each with why. */
|
|
131
|
+
readonly rejectedScopeSets: readonly ScopeSetRefusal[];
|
|
88
132
|
}
|
|
89
133
|
export interface ProviderRegistryOptions {
|
|
90
134
|
/**
|
|
@@ -93,6 +137,8 @@ export interface ProviderRegistryOptions {
|
|
|
93
137
|
* the rest (IP literals, localhost, `.internal`).
|
|
94
138
|
*/
|
|
95
139
|
refuseHost?: (host: string) => string | null;
|
|
140
|
+
/** The scope-set overlays (`provider_scope_sets`), applied to the catalog's OAuth clients. */
|
|
141
|
+
scopeSets?: readonly ScopeSetRow[];
|
|
96
142
|
}
|
|
97
143
|
/** The prefix a provider's own Gateway settings carry: `stripe` → `STRIPE_`, `tiktok-business` → `TIKTOK_BUSINESS_`. */
|
|
98
144
|
export declare function providerSettingPrefix(id: string): string;
|
|
@@ -148,6 +194,20 @@ export declare function resolveProviderRow(row: ProviderRow, context: {
|
|
|
148
194
|
ok: false;
|
|
149
195
|
refusal: RowRefusal;
|
|
150
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* The built-in OAuth client an overlay may extend: a catalog entry of mode
|
|
199
|
+
* `oauth_client` with a scope policy. Null for anything else (an API-key
|
|
200
|
+
* entry, a client without sets, a registered row, an unknown id).
|
|
201
|
+
*/
|
|
202
|
+
export declare function overlayableClient(providerId: string): OAuthClientCapability | null;
|
|
203
|
+
/** An overlay row's spec parsed and checked against its client: the set, or the refusal. */
|
|
204
|
+
export declare function resolveScopeSetRow(row: ScopeSetRow): {
|
|
205
|
+
ok: true;
|
|
206
|
+
set: OAuthScopeSet;
|
|
207
|
+
} | {
|
|
208
|
+
ok: false;
|
|
209
|
+
refusal: ScopeSetRefusal;
|
|
210
|
+
};
|
|
151
211
|
/**
|
|
152
212
|
* The catalog and `rows` as one registry. Rows are taken in order; a row
|
|
153
213
|
* whose settings another earlier row holds is the one refused.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providerRegistry.d.ts","sourceRoot":"","sources":["../../src/internal/providerRegistry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"providerRegistry.d.ts","sourceRoot":"","sources":["../../src/internal/providerRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAS3B,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,mBAAmB,CAAC;AAE3B,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,IAAI,EAAE,OAAO,CAAC;IACd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uGAAuG;IACvG,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,aAAa,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,MAAM,mBAAmB,GAC3B,mBAAmB,GACnB,4BAA4B,GAC5B,mBAAmB,CAAC;AAExB,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,qBAAqB,GAAG,MAAM,CA2B5E;AAoBD,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,qBAAqB,CAAC;IAClC,uEAAuE;IACvE,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GACtB,uBAAuB,GACvB,kBAAkB,GAClB,uBAAuB,GACvB,uBAAuB,CAAC;AAE5B,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACxD,mFAAmF;IACnF,GAAG,IAAI,SAAS,MAAM,EAAE,CAAC;IACzB,OAAO,IAAI,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;IACpD,6EAA6E;IAC7E,YAAY,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC;;;;OAIG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,4EAA4E;IAC5E,eAAe,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,8DAA8D;IAC9D,IAAI,IAAI,SAAS,kBAAkB,EAAE,CAAC;IACtC,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;IACzC,sFAAsF;IACtF,SAAS,IAAI,SAAS,kBAAkB,EAAE,CAAC;IAC3C,4FAA4F;IAC5F,cAAc,CACZ,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;IACjC,8DAA8D;IAC9D,QAAQ,CAAC,iBAAiB,EAAE,SAAS,eAAe,EAAE,CAAC;CACxD;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC7C,8FAA8F;IAC9F,SAAS,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACpC;AAOD,wHAAwH;AACxH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS7D;AAED,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAcD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,YAAY,GAClB,MAAM,GAAG,IAAI,CAYf;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAC/B,MAAM,GAAG,IAAI,CAwBf;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,qBAAqB,EACjC,OAAO,EAAE;IACP,oCAAoC;IACpC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC9C,GACA,UAAU,GAAG,IAAI,CAuBnB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,qBAAqB,GAAG,MAAM,EAAE,CAqB5E;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE;IACP,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC9C,GAEC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,qBAAqB,CAAA;CAAE,GAC/C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAyCrC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,GACjB,qBAAqB,GAAG,IAAI,CAQ9B;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,WAAW,GACf;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,eAAe,CAAA;CAAE,CAoC5E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,GAAE,SAAS,WAAW,EAAO,EACjC,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,CAiIlB;AAED,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB,EAAE,gBAAqC,CAAC"}
|
|
@@ -19,9 +19,21 @@
|
|
|
19
19
|
* an IP literal, localhost, or a `.local`, `.internal` or `.arpa` name)
|
|
20
20
|
* and not one the gateway serves itself (`refuseHost`, which knows the
|
|
21
21
|
* gateway's own host and `APP_BASE_DOMAIN`).
|
|
22
|
+
*
|
|
23
|
+
* A second kind of row, a scope-set overlay (`provider_scope_sets`,
|
|
24
|
+
* `everyapp provider scope-set add`), adds a named scope set to a
|
|
25
|
+
* built-in OAuth client that has scope sets (Google, Microsoft, Slack):
|
|
26
|
+
* its scopes, and the routes on the client's own API hosts it unlocks,
|
|
27
|
+
* checked by the rules a built-in set is checked by
|
|
28
|
+
* (`providers.scope-set-overlay-within-client`). An overlay never replaces
|
|
29
|
+
* one of the client's own sets, never names a host outside the client's
|
|
30
|
+
* `api`, and never applies to a registered row (an operator replaces the
|
|
31
|
+
* row's spec instead). Apps list an overlay set under `scopes` as they
|
|
32
|
+
* list a built-in one.
|
|
22
33
|
*/
|
|
23
34
|
import { CAPABILITIES, CAPABILITY_IDS, capabilitySettings, } from "./capabilities.js";
|
|
24
35
|
import { isDevOnlyGatewaySetting, isInstallOwnedGatewaySetting, isReservedAppSecretName, isSettableGatewaySetting, } from "./gatewaySettings.js";
|
|
36
|
+
import { parseScopeSetOverlay, withScopeSets } from "./oauthSpec.js";
|
|
25
37
|
import { isSignInSettingName } from "./signInProviders.js";
|
|
26
38
|
import { describeSpecProblems, parseProviderSpec, PROVIDER_ID, } from "./providerSpec.js";
|
|
27
39
|
/**
|
|
@@ -35,6 +47,15 @@ import { describeSpecProblems, parseProviderSpec, PROVIDER_ID, } from "./provide
|
|
|
35
47
|
* re-points `tokenUrl` has to paste the secret again.
|
|
36
48
|
*/
|
|
37
49
|
export function providerBindingKey(capability) {
|
|
50
|
+
if (capability.mode === "ai") {
|
|
51
|
+
// An `ai` row's key is bound to where the proxy sends it: its base URL and the key's placement.
|
|
52
|
+
return JSON.stringify({
|
|
53
|
+
kind: "ai",
|
|
54
|
+
auth: sortedAuth(capability.auth),
|
|
55
|
+
origin: capability.origin,
|
|
56
|
+
basePath: capability.basePath ?? null,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
38
59
|
if (capability.mode === "oauth_client") {
|
|
39
60
|
return JSON.stringify({
|
|
40
61
|
kind: "oauth2-user",
|
|
@@ -47,7 +68,15 @@ export function providerBindingKey(capability) {
|
|
|
47
68
|
hosts: [...capability.api].sort(),
|
|
48
69
|
});
|
|
49
70
|
}
|
|
50
|
-
|
|
71
|
+
return JSON.stringify({
|
|
72
|
+
auth: sortedAuth(capability.auth),
|
|
73
|
+
hostSetting: capability.hostSetting ?? null,
|
|
74
|
+
hosts: [...capability.hosts].sort(),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** An auth block with its keys sorted, one level down too, for a canonical key. */
|
|
78
|
+
function sortedAuth(auth) {
|
|
79
|
+
return Object.fromEntries(Object.entries(auth)
|
|
51
80
|
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
52
81
|
.map(([field, value]) => [
|
|
53
82
|
field,
|
|
@@ -55,11 +84,6 @@ export function providerBindingKey(capability) {
|
|
|
55
84
|
? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))
|
|
56
85
|
: value,
|
|
57
86
|
]));
|
|
58
|
-
return JSON.stringify({
|
|
59
|
-
auth,
|
|
60
|
-
hostSetting: capability.hostSetting ?? null,
|
|
61
|
-
hosts: [...capability.hosts].sort(),
|
|
62
|
-
});
|
|
63
87
|
}
|
|
64
88
|
/** The reserved app id no provider may take. */
|
|
65
89
|
const RESERVED_IDS = new Set(["everyapp"]);
|
|
@@ -179,6 +203,8 @@ export function checkProviderRow(id, capability, context) {
|
|
|
179
203
|
export function registrableHosts(capability) {
|
|
180
204
|
if (capability.mode === "api_key")
|
|
181
205
|
return [...capability.hosts];
|
|
206
|
+
if (capability.mode === "ai")
|
|
207
|
+
return [new URL(capability.origin).hostname.toLowerCase()];
|
|
182
208
|
const hosts = [...capability.api];
|
|
183
209
|
const urls = [
|
|
184
210
|
capability.authorize.url,
|
|
@@ -244,12 +270,92 @@ export function resolveProviderRow(row, context) {
|
|
|
244
270
|
? { ok: false, refusal }
|
|
245
271
|
: { ok: true, capability: parsed.capability };
|
|
246
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* The built-in OAuth client an overlay may extend: a catalog entry of mode
|
|
275
|
+
* `oauth_client` with a scope policy. Null for anything else (an API-key
|
|
276
|
+
* entry, a client without sets, a registered row, an unknown id).
|
|
277
|
+
*/
|
|
278
|
+
export function overlayableClient(providerId) {
|
|
279
|
+
if (!CAPABILITY_IDS.includes(providerId))
|
|
280
|
+
return null;
|
|
281
|
+
const capability = CAPABILITIES[providerId];
|
|
282
|
+
return capability.mode === "oauth_client" && capability.scopePolicy
|
|
283
|
+
? capability
|
|
284
|
+
: null;
|
|
285
|
+
}
|
|
286
|
+
/** An overlay row's spec parsed and checked against its client: the set, or the refusal. */
|
|
287
|
+
export function resolveScopeSetRow(row) {
|
|
288
|
+
const refuse = (code, message) => ({
|
|
289
|
+
ok: false,
|
|
290
|
+
refusal: { providerId: row.providerId, name: row.name, code, message },
|
|
291
|
+
});
|
|
292
|
+
const client = overlayableClient(row.providerId);
|
|
293
|
+
if (!client) {
|
|
294
|
+
return refuse("SCOPE_SET_PROVIDER_UNKNOWN", `${row.providerId} isn't a built-in OAuth client with scope sets (google-workspace, microsoft-365, slack); a registered provider's sets are in its spec`);
|
|
295
|
+
}
|
|
296
|
+
if (Object.hasOwn(client.scopePolicy.sets, row.name)) {
|
|
297
|
+
return refuse("SCOPE_SET_BUILTIN", `${row.name} is one of ${row.providerId}'s own sets; an overlay adds a set, never replaces one`);
|
|
298
|
+
}
|
|
299
|
+
let spec = row.spec;
|
|
300
|
+
if (typeof spec === "string") {
|
|
301
|
+
try {
|
|
302
|
+
spec = JSON.parse(spec);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return refuse("SCOPE_SET_INVALID", "the stored spec isn't JSON");
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const parsed = parseScopeSetOverlay(typeof spec === "object" && spec !== null && !Array.isArray(spec)
|
|
309
|
+
? { ...spec, name: row.name }
|
|
310
|
+
: spec, { id: row.providerId, capability: client });
|
|
311
|
+
if (!parsed.ok) {
|
|
312
|
+
return refuse("SCOPE_SET_INVALID", describeSpecProblems(parsed.problems));
|
|
313
|
+
}
|
|
314
|
+
return { ok: true, set: parsed.set };
|
|
315
|
+
}
|
|
247
316
|
/**
|
|
248
317
|
* The catalog and `rows` as one registry. Rows are taken in order; a row
|
|
249
318
|
* whose settings another earlier row holds is the one refused.
|
|
250
319
|
*/
|
|
251
320
|
export function providerRegistry(rows = [], options = {}) {
|
|
252
321
|
const catalog = new Map(CAPABILITY_IDS.map((id) => [id, CAPABILITIES[id]]));
|
|
322
|
+
// Scope-set overlays on the catalog's OAuth clients, before any row is
|
|
323
|
+
// looked at: a row never shadows the catalog, with or without overlays.
|
|
324
|
+
const overlays = [];
|
|
325
|
+
const rejectedScopeSets = [];
|
|
326
|
+
const overlaySets = new Map();
|
|
327
|
+
for (const row of options.scopeSets ?? []) {
|
|
328
|
+
if (overlays.some((each) => each.providerId === row.providerId && each.name === row.name)) {
|
|
329
|
+
rejectedScopeSets.push({
|
|
330
|
+
providerId: row.providerId,
|
|
331
|
+
name: row.name,
|
|
332
|
+
code: "SCOPE_SET_INVALID",
|
|
333
|
+
message: "listed twice",
|
|
334
|
+
});
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const resolved = resolveScopeSetRow(row);
|
|
338
|
+
if (!resolved.ok) {
|
|
339
|
+
rejectedScopeSets.push(resolved.refusal);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
overlays.push({
|
|
343
|
+
providerId: row.providerId,
|
|
344
|
+
name: row.name,
|
|
345
|
+
set: resolved.set,
|
|
346
|
+
addedBy: row.addedBy ?? null,
|
|
347
|
+
addedAt: row.addedAt ?? null,
|
|
348
|
+
});
|
|
349
|
+
const sets = overlaySets.get(row.providerId) ?? {};
|
|
350
|
+
sets[row.name] = resolved.set;
|
|
351
|
+
overlaySets.set(row.providerId, sets);
|
|
352
|
+
}
|
|
353
|
+
for (const [id, sets] of overlaySets) {
|
|
354
|
+
const client = catalog.get(id);
|
|
355
|
+
if (client?.mode === "oauth_client") {
|
|
356
|
+
catalog.set(id, withScopeSets(client, sets));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
253
359
|
const accepted = new Map();
|
|
254
360
|
const rejected = [];
|
|
255
361
|
const rowSettings = new Set();
|
|
@@ -305,6 +411,21 @@ export function providerRegistry(rows = [], options = {}) {
|
|
|
305
411
|
},
|
|
306
412
|
rows: () => [...accepted.values()],
|
|
307
413
|
rejected,
|
|
414
|
+
scopeSets: () => [...overlays],
|
|
415
|
+
scopeSetSource: (providerId, name) => {
|
|
416
|
+
if (overlays.some((each) => each.providerId === providerId && each.name === name)) {
|
|
417
|
+
return "row";
|
|
418
|
+
}
|
|
419
|
+
const capability = CAPABILITY_IDS.includes(providerId)
|
|
420
|
+
? CAPABILITIES[providerId]
|
|
421
|
+
: undefined;
|
|
422
|
+
return capability?.mode === "oauth_client" &&
|
|
423
|
+
capability.scopePolicy &&
|
|
424
|
+
Object.hasOwn(capability.scopePolicy.sets, name)
|
|
425
|
+
? "catalog"
|
|
426
|
+
: undefined;
|
|
427
|
+
},
|
|
428
|
+
rejectedScopeSets,
|
|
308
429
|
};
|
|
309
430
|
}
|
|
310
431
|
/** The catalog alone, as a registry: what every surface reads without a gateway's rows. */
|
|
@@ -6,30 +6,37 @@
|
|
|
6
6
|
* JSON into an {@link ApiKeyCapability} or names every problem; nothing
|
|
7
7
|
* here reads a value: a spec names Gateway settings, never holds one.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* or basic)
|
|
9
|
+
* Three kinds parse: API-key providers (`auth.kind` bearer, header, query
|
|
10
|
+
* or basic), OAuth providers (`auth.kind` oauth2-user, the half in
|
|
11
11
|
* ./oauthSpec.ts: the Gateway runs the flow and holds the tokens,
|
|
12
|
-
* docs/design/2026-10-oauth-providers.md)
|
|
13
|
-
*
|
|
12
|
+
* docs/design/2026-10-oauth-providers.md), and OpenAI-compatible model
|
|
13
|
+
* providers (`mode: "ai"`: a `baseUrl`, the key's placement, the models
|
|
14
|
+
* it serves and the default, its inference routes; the AI proxy serves a
|
|
15
|
+
* row exactly as it serves a catalog `ai` entry, inference POSTs alone,
|
|
16
|
+
* `store: false` on chat, the usage tap, so a new model vendor is a
|
|
17
|
+
* registered spec, not a catalog release).
|
|
14
18
|
*
|
|
15
19
|
* The rules that make a row safe on a particular gateway (its secret names
|
|
16
20
|
* are its own, its hosts are public, it shadows no catalog entry) are the
|
|
17
21
|
* registry's (./providerRegistry.ts), run after this parse.
|
|
18
22
|
*/
|
|
19
|
-
import { type ApiKeyCapability, type OAuthClientCapability } from "./capabilities.js";
|
|
23
|
+
import { type AiProviderCapability, type ApiKeyCapability, type OAuthClientCapability } from "./capabilities.js";
|
|
20
24
|
/** A provider id: lowercase, a letter first, 2 to 40 characters. */
|
|
21
25
|
export declare const PROVIDER_ID: RegExp;
|
|
22
26
|
/** A Gateway setting name a provider may hold: upper case, 3 to 64 characters. */
|
|
23
27
|
export declare const PROVIDER_SECRET_NAME: RegExp;
|
|
24
28
|
/** A bare lowercase host with at least one dot: labels of letters, digits and hyphens. */
|
|
25
29
|
export declare const PROVIDER_HOST: RegExp;
|
|
30
|
+
/** What an `ai` row serves by default: chat, and embeddings when it names an embeddings model. */
|
|
31
|
+
export declare const AI_SPEC_CHAT_ROUTE = "/v1/chat/completions";
|
|
32
|
+
export declare const AI_SPEC_EMBEDDINGS_ROUTE = "/v1/embeddings";
|
|
26
33
|
export interface ProviderSpecProblem {
|
|
27
34
|
/** Dotted: `auth.secret`, `hosts.1`, or `(root)`. */
|
|
28
35
|
path: string;
|
|
29
36
|
message: string;
|
|
30
37
|
}
|
|
31
|
-
/** What a row or a preset may be: an API key,
|
|
32
|
-
export type RegistrableCapability = ApiKeyCapability | OAuthClientCapability;
|
|
38
|
+
/** What a row or a preset may be: an API key, an OAuth provider with a `flow`, or an OpenAI-compatible model provider. */
|
|
39
|
+
export type RegistrableCapability = ApiKeyCapability | OAuthClientCapability | AiProviderCapability;
|
|
33
40
|
export type ParsedProviderSpec = {
|
|
34
41
|
ok: true;
|
|
35
42
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providerSpec.d.ts","sourceRoot":"","sources":["../../src/internal/providerSpec.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"providerSpec.d.ts","sourceRoot":"","sources":["../../src/internal/providerSpec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAGL,KAAK,oBAAoB,EAEzB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAM3B,MAAM,mBAAmB,CAAC;AAG3B,oEAAoE;AACpE,eAAO,MAAM,WAAW,QAA2B,CAAC;AACpD,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,QAA2B,CAAC;AAC7D,0FAA0F;AAC1F,eAAO,MAAM,aAAa,QACoD,CAAC;AA4C/E,kGAAkG;AAClG,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AACzD,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AA+EzD,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,0HAA0H;AAC1H,MAAM,MAAM,qBAAqB,GAC7B,gBAAgB,GAChB,qBAAqB,GACrB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,qBAAqB,CAAA;CAAE,GAC3D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC;AA+iBnD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CA2NpE;AAkOD,wFAAwF;AACxF,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,GACvC,MAAM,CAIR;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,qBAAqB,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuGzB;AAED,kGAAkG;AAClG,wBAAgB,cAAc,CAC5B,UAAU,EAAE,qBAAqB,GAChC,SAAS,MAAM,EAAE,CAKnB"}
|