@wildo-ai/external-connectors-models 1.1.5 → 1.1.7
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/esm/.native.tsbuildinfo +1 -0
- package/dist/esm/companion-exports.d.ts.map +1 -1
- package/dist/esm/companion-exports.js.map +1 -1
- package/dist/esm/core/commons.external-connectors-models.schemas.d.ts +10 -0
- package/dist/esm/core/commons.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/core/commons.external-connectors-models.schemas.js +10 -0
- package/dist/esm/core/commons.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/core/oauth2.enums.d.ts.map +1 -1
- package/dist/esm/core/oauth2.enums.js.map +1 -1
- package/dist/esm/core/operation-classification.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/core/operation-classification.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/core/operations.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/core/operations.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/core/security.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/core/security.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/protocols/agent-client-protocol.external-connectors-models.schemas.d.ts +52 -0
- package/dist/esm/protocols/agent-client-protocol.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/agent-client-protocol.external-connectors-models.schemas.js +58 -1
- package/dist/esm/protocols/agent-client-protocol.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/billing.standard-interface.schemas.d.ts +129 -4
- package/dist/esm/protocols/billing.standard-interface.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/billing.standard-interface.schemas.js.map +1 -1
- package/dist/esm/protocols/document-extraction.external-connectors-models.schemas.d.ts +56 -1
- package/dist/esm/protocols/document-extraction.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/document-extraction.external-connectors-models.schemas.js +25 -0
- package/dist/esm/protocols/document-extraction.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/email-inbound.standard-interface.schemas.d.ts +229 -0
- package/dist/esm/protocols/email-inbound.standard-interface.schemas.d.ts.map +1 -0
- package/dist/esm/protocols/email-inbound.standard-interface.schemas.js +160 -0
- package/dist/esm/protocols/email-inbound.standard-interface.schemas.js.map +1 -0
- package/dist/esm/protocols/email.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/email.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/email.standard-interface.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/email.standard-interface.schemas.js.map +1 -1
- package/dist/esm/protocols/embeddings.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/embeddings.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/file-storage.standard-interface.d.ts +179 -0
- package/dist/esm/protocols/file-storage.standard-interface.d.ts.map +1 -0
- package/dist/esm/protocols/file-storage.standard-interface.js +79 -0
- package/dist/esm/protocols/file-storage.standard-interface.js.map +1 -0
- package/dist/esm/protocols/frontend.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/frontend.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/hooks-capability.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/hooks-capability.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/hooks.external-connectors-models.schemas.d.ts +19 -1
- package/dist/esm/protocols/hooks.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/hooks.external-connectors-models.schemas.js +11 -0
- package/dist/esm/protocols/hooks.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/image.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/image.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/llm.external-connectors-models.schemas.d.ts +8 -0
- package/dist/esm/protocols/llm.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/llm.external-connectors-models.schemas.js +10 -0
- package/dist/esm/protocols/llm.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/oauth2-provider-assurance.definitions.d.ts.map +1 -1
- package/dist/esm/protocols/oauth2-provider-assurance.definitions.js.map +1 -1
- package/dist/esm/protocols/oauth2-provider-assurance.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/oauth2-provider-assurance.schemas.js.map +1 -1
- package/dist/esm/protocols/oauth2.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/oauth2.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/provider-oauth2-token-endpoint-hosts.external-connectors-models.d.ts +39 -0
- package/dist/esm/protocols/provider-oauth2-token-endpoint-hosts.external-connectors-models.d.ts.map +1 -0
- package/dist/esm/protocols/provider-oauth2-token-endpoint-hosts.external-connectors-models.js +66 -0
- package/dist/esm/protocols/provider-oauth2-token-endpoint-hosts.external-connectors-models.js.map +1 -0
- package/dist/esm/protocols/rest-operations.external-connectors-models.schemas.d.ts +337 -5
- package/dist/esm/protocols/rest-operations.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/rest-operations.external-connectors-models.schemas.js +324 -1
- package/dist/esm/protocols/rest-operations.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/sms.external-connectors-models.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/sms.external-connectors-models.schemas.js.map +1 -1
- package/dist/esm/protocols/sms.standard-interface.schemas.d.ts.map +1 -1
- package/dist/esm/protocols/sms.standard-interface.schemas.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -102,6 +102,289 @@ export var ProviderRestPaginationStyle;
|
|
|
102
102
|
/** The vendor returns its own continuation URL (`@odata.nextLink`, `links.next`). */
|
|
103
103
|
ProviderRestPaginationStyle["LINK"] = "link";
|
|
104
104
|
})(ProviderRestPaginationStyle || (ProviderRestPaginationStyle = {}));
|
|
105
|
+
/** @wildo_source:part:start external-connectors.models.connection-rate-budgets facet:layer:shared facet:family:provider facet:audience:app-developer */
|
|
106
|
+
/**
|
|
107
|
+
* WHOSE allowance a vendor counts a request against, under one connection (#1729).
|
|
108
|
+
*
|
|
109
|
+
* A connection is one application registration at the vendor (one OAuth client, one API project) — the
|
|
110
|
+
* base provider's, however many refs spend it. Vendors meter on two axes of it, and a provider declares the
|
|
111
|
+
* one each of its limits uses:
|
|
112
|
+
*
|
|
113
|
+
* | vendor wording | member |
|
|
114
|
+
* |---|---|
|
|
115
|
+
* | Microsoft Graph "per app across all tenants"; Google "per minute per project" | {@link CONNECTION} |
|
|
116
|
+
* | Google "per minute per user per project"; Dataverse "for each user"; Graph "per app per tenant" / "per app and mailbox" when the grant owner IS that tenant or mailbox | {@link OWNER} |
|
|
117
|
+
*
|
|
118
|
+
* spec-cite: https://learn.microsoft.com/en-us/graph/throttling-limits — the global-limits table ("Per app across all
|
|
119
|
+
* tenants") and the scope list "per app across all tenants, per tenant for all apps, per app per tenant"; and
|
|
120
|
+
* https://developers.google.com/workspace/drive/api/guides/limits — "per minute per project" / "per minute per user per
|
|
121
|
+
* project". Fetched 2026-09-28.
|
|
122
|
+
*
|
|
123
|
+
* Deliberately NOT a member: "per tenant for all apps" (Graph). It counts OTHER applications' calls, which this
|
|
124
|
+
* deployment cannot see, so no local counter can hold it; the vendor's `429` is the only honest signal there.
|
|
125
|
+
*/
|
|
126
|
+
export var ProviderRateBudgetPartition;
|
|
127
|
+
(function (ProviderRateBudgetPartition) {
|
|
128
|
+
/** One allowance for the whole connection — every ref, every account it acts for, every process. */
|
|
129
|
+
ProviderRateBudgetPartition["CONNECTION"] = "connection";
|
|
130
|
+
/** One allowance per account the connection acts for — the grant's owner (a person, an organization, the deployment). */
|
|
131
|
+
ProviderRateBudgetPartition["OWNER"] = "owner";
|
|
132
|
+
})(ProviderRateBudgetPartition || (ProviderRateBudgetPartition = {}));
|
|
133
|
+
/**
|
|
134
|
+
* Where a budget was COUNTED, as the call that spent it observed (#1729): `shared` when the deployment's shared store
|
|
135
|
+
* (Redis) counted it for every process of the application — backend replicas, minions, workers — and `process` when
|
|
136
|
+
* only this process did, because no shared store is configured here or it did not answer. Recorded on the call's audit
|
|
137
|
+
* row, so a per-process count is visible rather than implied.
|
|
138
|
+
*/
|
|
139
|
+
export var RateBudgetCounting;
|
|
140
|
+
(function (RateBudgetCounting) {
|
|
141
|
+
RateBudgetCounting["SHARED"] = "shared";
|
|
142
|
+
RateBudgetCounting["PROCESS"] = "process";
|
|
143
|
+
})(RateBudgetCounting || (RateBudgetCounting = {}));
|
|
144
|
+
/**
|
|
145
|
+
* The defects of a connection's rate-budget declarations: a non-positive number, and a contract-level list beside
|
|
146
|
+
* `oauth2.audiences` — which would apply to nothing, since every call of such a connection is on one audience. A pure
|
|
147
|
+
* function so the registry refuses it at boot and a test pins it without a host.
|
|
148
|
+
*/
|
|
149
|
+
export function describeProviderRateBudgetDefects(input) {
|
|
150
|
+
const defects = [];
|
|
151
|
+
const check = (where, declarations) => {
|
|
152
|
+
for (const [index, declaration] of (declarations ?? []).entries()) {
|
|
153
|
+
if (!Number.isInteger(declaration.maxRequests) || declaration.maxRequests < 1) {
|
|
154
|
+
defects.push(`${where}[${index}].maxRequests must be a positive integer (got ${String(declaration.maxRequests)})`);
|
|
155
|
+
}
|
|
156
|
+
if (!Number.isFinite(declaration.windowSeconds) || declaration.windowSeconds <= 0) {
|
|
157
|
+
defects.push(`${where}[${index}].windowSeconds must be positive (got ${String(declaration.windowSeconds)})`);
|
|
158
|
+
}
|
|
159
|
+
if (!Object.values(ProviderRateBudgetPartition).includes(declaration.partition)) {
|
|
160
|
+
defects.push(`${where}[${index}].partition '${String(declaration.partition)}' is not a ProviderRateBudgetPartition`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
check('rest.rateBudgets', input.contractRateBudgets);
|
|
165
|
+
for (const [id, audience] of Object.entries(input.audiences ?? {}))
|
|
166
|
+
check(`oauth2.audiences.${id}.rateBudgets`, audience.rateBudgets);
|
|
167
|
+
if ((input.contractRateBudgets?.length ?? 0) > 0 && Object.keys(input.audiences ?? {}).length > 0) {
|
|
168
|
+
defects.push('rest.rateBudgets is declared beside oauth2.audiences and would apply to no call — declare each limit on the audience it meters');
|
|
169
|
+
}
|
|
170
|
+
return defects;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The declarations that apply to one call: the audience's own when the call's token is for an audience, else the
|
|
174
|
+
* contract's. Nothing is inherited between the two — see {@link describeProviderRateBudgetDefects}.
|
|
175
|
+
*/
|
|
176
|
+
export function selectProviderRateBudgetDeclarations(input) {
|
|
177
|
+
return input.audience !== undefined ? input.audience.rateBudgets ?? [] : input.contractRateBudgets ?? [];
|
|
178
|
+
}
|
|
179
|
+
/** @wildo_source:part:start external-connectors.models.oauth2-audiences facet:layer:shared facet:family:provider facet:audience:app-developer */
|
|
180
|
+
/** The placeholder a {@link ProviderRestOAuth2Audience.mintScopes} entry uses for the https origin of the request's host. */
|
|
181
|
+
export const PROVIDER_OAUTH2_AUDIENCE_ORIGIN_PLACEHOLDER = '{audienceOrigin}';
|
|
182
|
+
/** Whether `host` is admitted by one audience's hosts: an exact value, or `<labels><suffix>` for a declared suffix. */
|
|
183
|
+
export function providerOAuth2AudienceAdmitsHost(audience, host) {
|
|
184
|
+
const candidate = host.toLowerCase();
|
|
185
|
+
if (audience.hosts.values.some((value) => value.toLowerCase() === candidate))
|
|
186
|
+
return true;
|
|
187
|
+
return (audience.hosts.hostSuffixes ?? []).some((suffix) => {
|
|
188
|
+
const lowered = suffix.toLowerCase();
|
|
189
|
+
if (!candidate.endsWith(lowered) || candidate.length === lowered.length)
|
|
190
|
+
return false;
|
|
191
|
+
return PROVIDER_OAUTH2_DNS_NAME_PATTERN.test(candidate.slice(0, candidate.length - lowered.length));
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The ONE audience whose hosts admit `host`, or `undefined` when none does. The declaration guarantees at most one
|
|
196
|
+
* ({@link describeProviderOAuth2AudienceDefects} refuses overlapping hosts), so the answer never depends on order.
|
|
197
|
+
*/
|
|
198
|
+
export function selectProviderOAuth2AudienceForHost(oauth2, host) {
|
|
199
|
+
for (const [id, audience] of Object.entries(oauth2.audiences ?? {})) {
|
|
200
|
+
if (providerOAuth2AudienceAdmitsHost(audience, host))
|
|
201
|
+
return { id, audience, primary: id === oauth2.primaryAudience };
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The scopes that mint `audience`'s token for a request to `origin` — `{audienceOrigin}` replaced by that origin.
|
|
207
|
+
* The caller has already admitted the origin's host through {@link selectProviderOAuth2AudienceForHost}; nothing a
|
|
208
|
+
* caller supplies reaches a scope any other way.
|
|
209
|
+
*/
|
|
210
|
+
export function resolveProviderOAuth2AudienceMintScopes(audience, origin) {
|
|
211
|
+
return audience.mintScopes.map((scope) => (scope.startsWith(PROVIDER_OAUTH2_AUDIENCE_ORIGIN_PLACEHOLDER)
|
|
212
|
+
? `${origin}${scope.slice(PROVIDER_OAUTH2_AUDIENCE_ORIGIN_PLACEHOLDER.length)}`
|
|
213
|
+
: scope));
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The declared audience a scope a connection might REQUEST names (#1671): its id, or `undefined` when the scope names a
|
|
217
|
+
* resource none of the connection's audiences covers. Only meaningful when `audiences` is declared.
|
|
218
|
+
*
|
|
219
|
+
* A scope that is an absolute URI names the resource a token is minted for (`https://graph.microsoft.com/Mail.Send`,
|
|
220
|
+
* `https://contoso.crm.dynamics.com/user_impersonation`): the resource of one of an audience's fixed mint scopes (the
|
|
221
|
+
* mint scope without its last path segment), or — for a mint scope beginning `{audienceOrigin}` — an https origin whose
|
|
222
|
+
* host the audience admits. A scope that is not an absolute URI (`offline_access`, `Files.Read`) names no resource, and
|
|
223
|
+
* the vendor reads it against its default one, the PRIMARY audience: "omitting the resource identifier in the `scope`
|
|
224
|
+
* parameter defaults to Microsoft Graph".
|
|
225
|
+
* spec-cite: https://learn.microsoft.com/en-us/entra/identity-platform/consent-types-developer — the note under
|
|
226
|
+
* "Admin Consent for Delegated Permissions", fetched 2026-09-27.
|
|
227
|
+
*/
|
|
228
|
+
export function findProviderOAuth2ScopeAudience(oauth2, scope) {
|
|
229
|
+
const audiences = oauth2.audiences ?? {};
|
|
230
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(scope))
|
|
231
|
+
return oauth2.primaryAudience;
|
|
232
|
+
// The https origin's host, read by the same shape the audiences declare (a DNS name, no port, no credentials) rather
|
|
233
|
+
// than by a URL parser: this package runs in a browser-neutral program with no URL global, and a scope that is not
|
|
234
|
+
// that shape names no host an audience could admit.
|
|
235
|
+
const origin = /^https:\/\/([^/?#:@]+)(?:\/|$)/i.exec(scope);
|
|
236
|
+
const host = origin?.[1]?.toLowerCase();
|
|
237
|
+
for (const [id, audience] of Object.entries(audiences)) {
|
|
238
|
+
for (const mintScope of audience.mintScopes) {
|
|
239
|
+
if (mintScope.startsWith(PROVIDER_OAUTH2_AUDIENCE_ORIGIN_PLACEHOLDER)) {
|
|
240
|
+
if (host !== undefined && PROVIDER_OAUTH2_DNS_NAME_PATTERN.test(host) && providerOAuth2AudienceAdmitsHost(audience, host))
|
|
241
|
+
return id;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const resource = mintScope.slice(0, mintScope.lastIndexOf('/'));
|
|
245
|
+
if (resource.length > 0 && scope.startsWith(`${resource}/`))
|
|
246
|
+
return id;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Whether a scope a connection might REQUEST names a resource among the connection's declared audiences (#1671) — see
|
|
253
|
+
* {@link findProviderOAuth2ScopeAudience}. Asking a person to consent to it is asking them to let this application reach
|
|
254
|
+
* THAT resource, so a scope from a member ref or an opted-in augmentation (#1672) is requestable only when it names one
|
|
255
|
+
* the connection declares. `true` without looking further when the connection declares no audiences: it mints one token
|
|
256
|
+
* for one resource and its scopes are the vendor's own vocabulary (Google's scope URIs name permissions, not resources).
|
|
257
|
+
*/
|
|
258
|
+
export function providerOAuth2ScopeNamesDeclaredAudience(oauth2, scope) {
|
|
259
|
+
if (oauth2.audiences === undefined)
|
|
260
|
+
return true;
|
|
261
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(scope))
|
|
262
|
+
return true;
|
|
263
|
+
return findProviderOAuth2ScopeAudience(oauth2, scope) !== undefined;
|
|
264
|
+
}
|
|
265
|
+
/** @wildo_source:part:end external-connectors.models.oauth2-audiences */
|
|
266
|
+
/** @wildo_source:part:start external-connectors.models.oauth2-incremental-consent facet:layer:shared facet:family:provider facet:audience:app-developer */
|
|
267
|
+
/**
|
|
268
|
+
* How a vendor extends an existing delegated grant with more scopes — see {@link ProviderRestOAuth2.incrementalConsent}.
|
|
269
|
+
*/
|
|
270
|
+
export var ProviderOAuth2IncrementalConsentForm;
|
|
271
|
+
(function (ProviderOAuth2IncrementalConsentForm) {
|
|
272
|
+
/**
|
|
273
|
+
* The vendor keeps what the person already consented to, and a new authorization request naming only the added
|
|
274
|
+
* scopes ADDS them: Microsoft's delegated permissions "will be granted in addition to any other permissions that
|
|
275
|
+
* were already granted for that app (i.e. incremental)".
|
|
276
|
+
* spec-cite: https://learn.microsoft.com/en-us/entra/identity-platform/consent-types-developer — "Incremental and
|
|
277
|
+
* dynamic user consent", fetched 2026-09-27.
|
|
278
|
+
*/
|
|
279
|
+
ProviderOAuth2IncrementalConsentForm["ADDITIVE"] = "ADDITIVE";
|
|
280
|
+
/**
|
|
281
|
+
* The vendor adds the new scopes to the existing grant only when the request carries `include_granted_scopes=true`:
|
|
282
|
+
* "If you set this parameter's value to `true` and the authorization request is granted, then the new access token
|
|
283
|
+
* will also cover any scopes to which the user previously granted the application access."
|
|
284
|
+
* spec-cite: https://developers.google.com/identity/protocols/oauth2/web-server#incrementalAuth — the
|
|
285
|
+
* `include_granted_scopes` parameter row, fetched 2026-09-27.
|
|
286
|
+
*/
|
|
287
|
+
ProviderOAuth2IncrementalConsentForm["INCLUDE_GRANTED_SCOPES"] = "INCLUDE_GRANTED_SCOPES";
|
|
288
|
+
})(ProviderOAuth2IncrementalConsentForm || (ProviderOAuth2IncrementalConsentForm = {}));
|
|
289
|
+
/** @wildo_source:part:end external-connectors.models.oauth2-incremental-consent */
|
|
290
|
+
/**
|
|
291
|
+
* Why a declared audience set cannot be honoured, or an empty list. Checked where a provider is composed, so a
|
|
292
|
+
* malformed declaration never reaches a request:
|
|
293
|
+
* - `primaryAudience` names no declared audience, or is missing while audiences are declared;
|
|
294
|
+
* - the primary audience's mint scopes are not among the authorization's own `scopes` (its token is the one the
|
|
295
|
+
* code redemption returns, so the authorization must be FOR that resource) or use the origin placeholder;
|
|
296
|
+
* - an audience with no host or no mint scope, a suffix without a leading dot, an origin placeholder anywhere but
|
|
297
|
+
* the start of a scope, or any other `{placeholder}` in a mint scope;
|
|
298
|
+
* - two audiences admitting one host — a request host must select exactly one token.
|
|
299
|
+
*/
|
|
300
|
+
export function describeProviderOAuth2AudienceDefects(oauth2) {
|
|
301
|
+
const audiences = oauth2.audiences;
|
|
302
|
+
if (audiences === undefined) {
|
|
303
|
+
return oauth2.primaryAudience === undefined ? [] : [`primaryAudience '${oauth2.primaryAudience}' is declared with no audiences`];
|
|
304
|
+
}
|
|
305
|
+
const defects = [];
|
|
306
|
+
const primary = oauth2.primaryAudience === undefined ? undefined : audiences[oauth2.primaryAudience];
|
|
307
|
+
if (primary === undefined)
|
|
308
|
+
defects.push(`primaryAudience '${String(oauth2.primaryAudience)}' names no declared audience`);
|
|
309
|
+
for (const [id, audience] of Object.entries(audiences)) {
|
|
310
|
+
if (audience.hosts.values.length === 0 && (audience.hosts.hostSuffixes ?? []).length === 0)
|
|
311
|
+
defects.push(`audience '${id}' admits no host`);
|
|
312
|
+
for (const suffix of audience.hosts.hostSuffixes ?? []) {
|
|
313
|
+
if (!suffix.startsWith('.') || !PROVIDER_OAUTH2_DNS_NAME_PATTERN.test(suffix.slice(1).toLowerCase()))
|
|
314
|
+
defects.push(`audience '${id}' declares the host suffix '${suffix}', which is not '.<dns-name>'`);
|
|
315
|
+
}
|
|
316
|
+
for (const value of audience.hosts.values) {
|
|
317
|
+
if (!PROVIDER_OAUTH2_DNS_NAME_PATTERN.test(value.toLowerCase()))
|
|
318
|
+
defects.push(`audience '${id}' declares the host '${value}', which is not a DNS name`);
|
|
319
|
+
}
|
|
320
|
+
if (audience.mintScopes.length === 0)
|
|
321
|
+
defects.push(`audience '${id}' declares no mint scope`);
|
|
322
|
+
for (const scope of audience.mintScopes) {
|
|
323
|
+
const rest = scope.startsWith(PROVIDER_OAUTH2_AUDIENCE_ORIGIN_PLACEHOLDER) ? scope.slice(PROVIDER_OAUTH2_AUDIENCE_ORIGIN_PLACEHOLDER.length) : scope;
|
|
324
|
+
if (rest.includes('{') || rest.includes('}'))
|
|
325
|
+
defects.push(`audience '${id}' mint scope '${scope}' carries a placeholder other than a leading ${PROVIDER_OAUTH2_AUDIENCE_ORIGIN_PLACEHOLDER}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (primary !== undefined) {
|
|
329
|
+
for (const scope of primary.mintScopes) {
|
|
330
|
+
if (scope.startsWith(PROVIDER_OAUTH2_AUDIENCE_ORIGIN_PLACEHOLDER))
|
|
331
|
+
defects.push(`the primary audience '${oauth2.primaryAudience}' mint scope '${scope}' names a per-request origin; the authorization must be for one fixed resource`);
|
|
332
|
+
else if (!(oauth2.scopes ?? []).includes(scope))
|
|
333
|
+
defects.push(`the primary audience '${oauth2.primaryAudience}' mint scope '${scope}' is not among the authorization's own scopes`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const entries = Object.entries(audiences);
|
|
337
|
+
for (let left = 0; left < entries.length; left += 1) {
|
|
338
|
+
for (let right = left + 1; right < entries.length; right += 1) {
|
|
339
|
+
const [leftId, leftAudience] = entries[left];
|
|
340
|
+
const [rightId, rightAudience] = entries[right];
|
|
341
|
+
const overlap = leftAudience.hosts.values.some((host) => providerOAuth2AudienceAdmitsHost(rightAudience, host))
|
|
342
|
+
|| rightAudience.hosts.values.some((host) => providerOAuth2AudienceAdmitsHost(leftAudience, host))
|
|
343
|
+
|| (leftAudience.hosts.hostSuffixes ?? []).some((suffix) => (rightAudience.hosts.hostSuffixes ?? []).some((other) => suffix.toLowerCase().endsWith(other.toLowerCase()) || other.toLowerCase().endsWith(suffix.toLowerCase())));
|
|
344
|
+
if (overlap)
|
|
345
|
+
defects.push(`audiences '${leftId}' and '${rightId}' admit a common host; a request host must select one token`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return defects;
|
|
349
|
+
}
|
|
350
|
+
/** How a placeholder's value is compared with a declared one. */
|
|
351
|
+
export var ProviderOAuth2ValueComparison;
|
|
352
|
+
(function (ProviderOAuth2ValueComparison) {
|
|
353
|
+
/** A host or an origin: DNS names compare case-insensitively. spec-cite: RFC 4343 §2. */
|
|
354
|
+
ProviderOAuth2ValueComparison["CASE_INSENSITIVE"] = "CASE_INSENSITIVE";
|
|
355
|
+
/** A scope value: scope strings are case-sensitive. spec-cite: RFC 6749 §3.3. */
|
|
356
|
+
ProviderOAuth2ValueComparison["EXACT"] = "EXACT";
|
|
357
|
+
})(ProviderOAuth2ValueComparison || (ProviderOAuth2ValueComparison = {}));
|
|
358
|
+
/**
|
|
359
|
+
* The declared reason a value is unsupported, or `undefined` when it is not declared unsupported. Compared as the
|
|
360
|
+
* placeholder's values are: case-insensitively for a host or origin, exactly for a scope value.
|
|
361
|
+
*/
|
|
362
|
+
export function findProviderOAuth2UnsupportedValueReason(unsupportedValues, value, comparison) {
|
|
363
|
+
if (unsupportedValues === undefined)
|
|
364
|
+
return undefined;
|
|
365
|
+
const key = comparison === ProviderOAuth2ValueComparison.EXACT
|
|
366
|
+
? (Object.hasOwn(unsupportedValues, value) ? value : undefined)
|
|
367
|
+
: Object.keys(unsupportedValues).find((candidate) => candidate.toLowerCase() === value.toLowerCase());
|
|
368
|
+
return key === undefined ? undefined : unsupportedValues[key];
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Why a declared unsupported-value map contradicts the admitted set it sits beside, or an empty list: a value both
|
|
372
|
+
* admitted and declined would make the refusal reason a lie, and a declined value with no reason says nothing.
|
|
373
|
+
*/
|
|
374
|
+
function describeProviderOAuth2UnsupportedValueDefects(unsupportedValues, admitted, comparison) {
|
|
375
|
+
if (unsupportedValues === undefined)
|
|
376
|
+
return [];
|
|
377
|
+
const defects = [];
|
|
378
|
+
const normalise = (value) => (comparison === ProviderOAuth2ValueComparison.EXACT ? value : value.toLowerCase());
|
|
379
|
+
const admittedSet = new Set(admitted.map(normalise));
|
|
380
|
+
for (const [value, reason] of Object.entries(unsupportedValues)) {
|
|
381
|
+
if (admittedSet.has(normalise(value)))
|
|
382
|
+
defects.push(`'${value}' is both admitted and declared unsupported`);
|
|
383
|
+
if (typeof reason !== 'string' || reason.trim() === '')
|
|
384
|
+
defects.push(`unsupported value '${value}' states no reason`);
|
|
385
|
+
}
|
|
386
|
+
return defects;
|
|
387
|
+
}
|
|
105
388
|
/**
|
|
106
389
|
* Where a `{placeholder}` sits inside one OAuth2 SCOPE entry. See {@link classifyProviderOAuth2ScopePlaceholder}.
|
|
107
390
|
*
|
|
@@ -191,6 +474,7 @@ export function describeProviderOAuth2ScopeValueAllowanceDefects(allowance) {
|
|
|
191
474
|
defects.push(`'${String(value)}' is not a space-delimited list of scope tokens`);
|
|
192
475
|
}
|
|
193
476
|
}
|
|
477
|
+
defects.push(...describeProviderOAuth2UnsupportedValueDefects(allowance.unsupportedValues, allowance.values, ProviderOAuth2ValueComparison.EXACT));
|
|
194
478
|
return defects;
|
|
195
479
|
}
|
|
196
480
|
/**
|
|
@@ -218,7 +502,7 @@ export var ProviderOAuth2EndpointPlaceholderPosition;
|
|
|
218
502
|
export var ProviderOAuth2EndpointHostAllowanceKind;
|
|
219
503
|
(function (ProviderOAuth2EndpointHostAllowanceKind) {
|
|
220
504
|
/**
|
|
221
|
-
* Chosen at connect time from values the provider DECLARES — Microsoft's
|
|
505
|
+
* Chosen at connect time from values the provider DECLARES — Microsoft's commercial login cloud,
|
|
222
506
|
* Zoho's data centres. A value outside the set is refused; a declared value is substituted
|
|
223
507
|
* verbatim (it is a hostname or origin, not a path segment to encode).
|
|
224
508
|
*
|
|
@@ -248,6 +532,21 @@ export var ProviderOAuth2EndpointHostAllowanceKind;
|
|
|
248
532
|
* names. Admitted on a HOST placeholder that is the whole host, with a DNS-name value.
|
|
249
533
|
*/
|
|
250
534
|
ProviderOAuth2EndpointHostAllowanceKind["DEPLOYMENT_CONFIGURED"] = "DEPLOYMENT_CONFIGURED";
|
|
535
|
+
/**
|
|
536
|
+
* DERIVED from another connect-time answer through a declared table (#1937): Microsoft Graph's operation host follows
|
|
537
|
+
* the login cloud the connection chose (`login.microsoftonline.com` → `graph.microsoft.com`), because a token minted by
|
|
538
|
+
* one national cloud is not accepted by another. The Microsoft source also maps the US Government login cloud; that
|
|
539
|
+
* row is declined (`unsupportedValues`), because US Government clouds are out of scope (decision of 2026-09-29).
|
|
540
|
+
*
|
|
541
|
+
* Never asked of a person and never taken from a request: the value is looked up from the ANSWER the connection
|
|
542
|
+
* stored for `answer`, and an answer the table does not name is refused. `whenUnanswered` is the host for a connection
|
|
543
|
+
* that answered nothing (the application's own pasted credential); absent, such a connection is refused. The table is
|
|
544
|
+
* the allow-list, so no other host can result. Admitted on a HOST placeholder that is the whole host.
|
|
545
|
+
*
|
|
546
|
+
* spec-cite: https://learn.microsoft.com/en-us/graph/deployments — "Access tokens acquired for a national cloud
|
|
547
|
+
* deployment are not interchangeable with those acquired for the global service or any other national cloud."
|
|
548
|
+
*/
|
|
549
|
+
ProviderOAuth2EndpointHostAllowanceKind["DERIVED_FROM_CONNECTION_ANSWER"] = "DERIVED_FROM_CONNECTION_ANSWER";
|
|
251
550
|
})(ProviderOAuth2EndpointHostAllowanceKind || (ProviderOAuth2EndpointHostAllowanceKind = {}));
|
|
252
551
|
/**
|
|
253
552
|
* Why an OAuth2 endpoint TEMPLATE cannot be resolved safely, whatever values are supplied.
|
|
@@ -310,6 +609,23 @@ export function describeProviderOAuth2EndpointHostAllowanceDefects(placeholder,
|
|
|
310
609
|
}
|
|
311
610
|
return defects;
|
|
312
611
|
}
|
|
612
|
+
if (allowance.kind === ProviderOAuth2EndpointHostAllowanceKind.DERIVED_FROM_CONNECTION_ANSWER) {
|
|
613
|
+
if (placeholder.position !== ProviderOAuth2EndpointPlaceholderPosition.HOST || !placeholder.occupiesWholeHost) {
|
|
614
|
+
defects.push('DERIVED_FROM_CONNECTION_ANSWER fills a WHOLE HOST only, and this placeholder is not one');
|
|
615
|
+
}
|
|
616
|
+
if (typeof allowance.answer !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(allowance.answer)) {
|
|
617
|
+
defects.push(`answer '${String(allowance.answer)}' is not a placeholder name`);
|
|
618
|
+
}
|
|
619
|
+
const hosts = Object.values(allowance.hosts ?? {});
|
|
620
|
+
if (hosts.length === 0)
|
|
621
|
+
defects.push('DERIVED_FROM_CONNECTION_ANSWER maps no answer to a host, which admits nothing');
|
|
622
|
+
for (const host of [...hosts, ...(allowance.whenUnanswered === undefined ? [] : [allowance.whenUnanswered])]) {
|
|
623
|
+
if (typeof host !== 'string' || !PROVIDER_OAUTH2_DNS_NAME_PATTERN.test(host.toLowerCase()))
|
|
624
|
+
defects.push(`'${String(host)}' is not a DNS name`);
|
|
625
|
+
}
|
|
626
|
+
defects.push(...describeProviderOAuth2UnsupportedValueDefects(allowance.unsupportedValues, Object.keys(allowance.hosts ?? {}), ProviderOAuth2ValueComparison.CASE_INSENSITIVE));
|
|
627
|
+
return defects;
|
|
628
|
+
}
|
|
313
629
|
if (allowance.kind === ProviderOAuth2EndpointHostAllowanceKind.DEPLOYMENT_CONFIGURED) {
|
|
314
630
|
if (placeholder.position !== ProviderOAuth2EndpointPlaceholderPosition.HOST || !placeholder.occupiesWholeHost) {
|
|
315
631
|
defects.push('DEPLOYMENT_CONFIGURED fills a WHOLE HOST only, and this placeholder is not one');
|
|
@@ -354,6 +670,13 @@ export function describeProviderOAuth2EndpointHostAllowanceDefects(placeholder,
|
|
|
354
670
|
defects.push(`host suffix '${suffix}' must begin with '.' and name at least two DNS labels (a bare '.com' admits every host under a TLD)`);
|
|
355
671
|
}
|
|
356
672
|
}
|
|
673
|
+
defects.push(...describeProviderOAuth2UnsupportedValueDefects(allowance.unsupportedValues, allowance.values, ProviderOAuth2ValueComparison.CASE_INSENSITIVE));
|
|
674
|
+
for (const declined of Object.keys(allowance.unsupportedValues ?? {})) {
|
|
675
|
+
// A declined value a declared SUFFIX would still admit is admitted, whatever the reason says.
|
|
676
|
+
const coveringSuffix = (allowance.hostSuffixes ?? []).find((suffix) => declined.toLowerCase().endsWith(suffix.toLowerCase()));
|
|
677
|
+
if (coveringSuffix !== undefined)
|
|
678
|
+
defects.push(`'${declined}' is declared unsupported but the host suffix '${coveringSuffix}' admits it`);
|
|
679
|
+
}
|
|
357
680
|
return defects;
|
|
358
681
|
}
|
|
359
682
|
/**
|