@every-app/sdk 0.3.0-rc.8 → 0.3.0
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 +75 -74
- package/dist/cloudflare/catalogSettings.generated.d.ts +20 -6
- package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -1
- package/dist/cloudflare/catalogSettings.generated.js +45 -14
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.d.ts +3 -0
- package/dist/cloudflare/sentinel.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.js +10 -0
- package/dist/cloudflare/worker.d.ts +67 -23
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +68 -32
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/internal/aiDefault.d.ts +36 -79
- package/dist/internal/aiDefault.d.ts.map +1 -1
- package/dist/internal/aiDefault.js +47 -149
- package/dist/internal/aiProviders.d.ts +1 -1
- package/dist/internal/aiProviders.d.ts.map +1 -1
- package/dist/internal/aiProviders.js +2 -2
- package/dist/internal/appSettings.d.ts +140 -89
- package/dist/internal/appSettings.d.ts.map +1 -1
- package/dist/internal/appSettings.js +264 -156
- package/dist/internal/capabilities.d.ts +145 -503
- package/dist/internal/capabilities.d.ts.map +1 -1
- package/dist/internal/capabilities.js +29 -550
- package/dist/internal/catalog/commerce.js +1 -1
- package/dist/internal/catalog/comms.d.ts +37 -0
- package/dist/internal/catalog/comms.d.ts.map +1 -1
- package/dist/internal/catalog/comms.js +43 -0
- package/dist/internal/catalog/index.d.ts +72 -0
- package/dist/internal/catalog/index.d.ts.map +1 -1
- package/dist/internal/catalog/infra.d.ts +35 -0
- package/dist/internal/catalog/infra.d.ts.map +1 -1
- package/dist/internal/catalog/infra.js +65 -0
- package/dist/internal/devProviders.d.ts +1 -19
- package/dist/internal/devProviders.d.ts.map +1 -1
- package/dist/internal/devProviders.js +7 -1
- package/dist/internal/events.d.ts +52 -1
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +66 -2
- package/dist/internal/gatewaySettings.d.ts +6 -16
- package/dist/internal/gatewaySettings.d.ts.map +1 -1
- package/dist/internal/gatewaySettings.js +6 -24
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/mcpOAuth.d.ts +210 -0
- package/dist/internal/mcpOAuth.d.ts.map +1 -0
- package/dist/internal/mcpOAuth.js +735 -0
- package/dist/internal/oauthClient.d.ts +7 -47
- package/dist/internal/oauthClient.d.ts.map +1 -1
- package/dist/internal/oauthClient.js +1 -115
- package/dist/internal/oauthPresets.d.ts +356 -20
- package/dist/internal/oauthPresets.d.ts.map +1 -1
- package/dist/internal/oauthPresets.js +183 -861
- package/dist/internal/oauthScopes.d.ts +43 -155
- package/dist/internal/oauthScopes.d.ts.map +1 -1
- package/dist/internal/oauthScopes.js +82 -475
- package/dist/internal/oauthSpec.d.ts +28 -71
- package/dist/internal/oauthSpec.d.ts.map +1 -1
- package/dist/internal/oauthSpec.js +172 -448
- package/dist/internal/protocol.d.ts +39 -15
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +40 -7
- package/dist/internal/providerApps.d.ts +2 -2
- package/dist/internal/providerApps.js +2 -2
- package/dist/internal/providerRegistry.d.ts +5 -64
- package/dist/internal/providerRegistry.d.ts.map +1 -1
- package/dist/internal/providerRegistry.js +19 -118
- package/dist/internal/providerSpec.d.ts.map +1 -1
- package/dist/internal/providerSpec.js +14 -36
- package/dist/internal/remoteMcp.d.ts +191 -47
- package/dist/internal/remoteMcp.d.ts.map +1 -1
- package/dist/internal/remoteMcp.js +338 -133
- package/dist/internal/signInProviders.d.ts +1 -1
- package/dist/internal/signInProviders.d.ts.map +1 -1
- package/dist/internal/tools.d.ts +15 -8
- package/dist/internal/tools.d.ts.map +1 -1
- package/dist/internal/tools.js +9 -8
- package/dist/manifest.d.ts +96 -28
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +1 -1
- package/dist/server/ai.d.ts +1 -93
- package/dist/server/ai.d.ts.map +1 -1
- package/dist/server/ai.js +17 -111
- package/dist/server/attribution.d.ts +10 -1
- package/dist/server/attribution.d.ts.map +1 -1
- package/dist/server/attribution.js +7 -1
- package/dist/server/connectUrl.d.ts +4 -9
- package/dist/server/connectUrl.d.ts.map +1 -1
- package/dist/server/connectUrl.js +17 -14
- package/dist/server/context.d.ts +12 -14
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +23 -25
- package/dist/server/emit.d.ts.map +1 -1
- package/dist/server/emit.js +2 -2
- package/dist/server/env.d.ts +3 -0
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/eventsEndpoint.d.ts +48 -0
- package/dist/server/eventsEndpoint.d.ts.map +1 -0
- package/dist/server/eventsEndpoint.js +145 -0
- package/dist/server/everyApp.d.ts +16 -26
- package/dist/server/everyApp.d.ts.map +1 -1
- package/dist/server/everyApp.js +22 -46
- package/dist/server/gatewayFetch.d.ts +19 -1
- package/dist/server/gatewayFetch.d.ts.map +1 -1
- package/dist/server/gatewayFetch.js +45 -3
- package/dist/server/index.d.ts +6 -9
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -8
- package/dist/server/integrationFetch.d.ts +60 -72
- package/dist/server/integrationFetch.d.ts.map +1 -1
- package/dist/server/integrationFetch.js +124 -397
- package/dist/server/listMembers.d.ts.map +1 -1
- package/dist/server/listMembers.js +2 -2
- package/dist/server/refusal.d.ts +0 -9
- package/dist/server/refusal.d.ts.map +1 -1
- package/dist/server/refusal.js +0 -22
- package/dist/server/sendEmail.d.ts.map +1 -1
- package/dist/server/sendEmail.js +2 -2
- package/dist/server/tools.d.ts +17 -6
- package/dist/server/tools.d.ts.map +1 -1
- package/dist/server/tools.js +14 -4
- package/dist/server/toolsEndpoint.d.ts +2 -1
- package/dist/server/toolsEndpoint.d.ts.map +1 -1
- package/dist/server/toolsEndpoint.js +4 -3
- package/dist/server/webhooks.d.ts +56 -0
- package/dist/server/webhooks.d.ts.map +1 -0
- package/dist/server/webhooks.js +205 -0
- package/dist/testing/index.d.ts +2 -3
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -6
- package/package.json +6 -1
- package/dist/server/aiBinding.d.ts +0 -68
- package/dist/server/aiBinding.d.ts.map +0 -1
- package/dist/server/aiBinding.js +0 -294
- package/dist/server/authorizeUrl.d.ts +0 -43
- package/dist/server/authorizeUrl.d.ts.map +0 -1
- package/dist/server/authorizeUrl.js +0 -106
- package/dist/server/emailBinding.d.ts +0 -72
- package/dist/server/emailBinding.d.ts.map +0 -1
- package/dist/server/emailBinding.js +0 -124
- package/dist/server/fromMcpTools.d.ts +0 -89
- package/dist/server/fromMcpTools.d.ts.map +0 -1
- package/dist/server/fromMcpTools.js +0 -192
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* The `oauth2-user` half of a provider spec
|
|
3
|
-
* (docs/design/2026-10-oauth-providers.md section 3): a registered OAuth
|
|
4
|
-
* provider, or a preset (./oauthPresets.ts), as JSON, turned into the
|
|
5
|
-
* same {@link OAuthClientCapability} a catalog client is, with a `flow`
|
|
6
|
-
* the Gateway runs itself. `parseProviderSpec` (./providerSpec.ts) hands
|
|
7
|
-
* the common fields here once it sees `auth.kind: "oauth2-user"`; nothing
|
|
8
|
-
* here reads a value: a spec names Gateway settings, never holds one.
|
|
9
|
-
*/
|
|
10
|
-
import { DEFAULT_WRITE_METHODS, } from "./capabilities.js";
|
|
11
|
-
/** A scope set's name: `identity`, or `<area>.<access>` (`repo.read`), lowercase. */
|
|
12
|
-
export const SCOPE_SET_NAME = /^[a-z][a-z0-9-]{0,31}(?:\.[a-z][a-z0-9-]{0,31})?$/;
|
|
13
|
-
const DIALECTS = [
|
|
14
|
-
"space",
|
|
15
|
-
"comma",
|
|
16
|
-
"google",
|
|
17
|
-
"microsoft",
|
|
18
|
-
"slack",
|
|
19
|
-
];
|
|
1
|
+
const SEPARATORS = ["space", "comma"];
|
|
20
2
|
const INJECTIONS = ["form", "basic"];
|
|
21
3
|
const HEADER_NAME = /^[A-Za-z0-9-]{1,64}$/;
|
|
22
4
|
/** Headers the Gateway sets on the token request itself, or never sends. */
|
|
@@ -27,17 +9,27 @@ const RESERVED_TOKEN_HEADERS = new Set([
|
|
|
27
9
|
"host",
|
|
28
10
|
"cookie",
|
|
29
11
|
]);
|
|
12
|
+
/**
|
|
13
|
+
* Authorize parameters the Gateway sets itself, and
|
|
14
|
+
* `include_granted_scopes`, which would widen a token to every scope the
|
|
15
|
+
* person ever granted the client (the mint check would refuse it anyway).
|
|
16
|
+
*/
|
|
17
|
+
const RESERVED_AUTHORIZE_PARAMS = [
|
|
18
|
+
"client_id",
|
|
19
|
+
"redirect_uri",
|
|
20
|
+
"response_type",
|
|
21
|
+
"scope",
|
|
22
|
+
"state",
|
|
23
|
+
"code_challenge",
|
|
24
|
+
"code_challenge_method",
|
|
25
|
+
"include_granted_scopes",
|
|
26
|
+
];
|
|
30
27
|
const PARAM_NAME = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
31
28
|
/** A dot path into a JSON answer, at most four segments. */
|
|
32
29
|
const DOT_PATH = /^[A-Za-z_][A-Za-z0-9_]{0,63}(?:\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/;
|
|
33
30
|
const UNPRINTABLE = /[\p{Cc}\p{Cf}]/u;
|
|
34
|
-
const MAX_SETS = 20;
|
|
35
31
|
const MAX_SCOPES = 40;
|
|
36
|
-
const MAX_ROUTES = 20;
|
|
37
|
-
const MAX_PATHS = 50;
|
|
38
32
|
const MAX_PARAMS = 10;
|
|
39
|
-
/** Segments no set unlocks on any host: batch tunnels carry whole requests inside. */
|
|
40
|
-
export const DEFAULT_DENIED_SEGMENTS = ["batch", "$batch"];
|
|
41
33
|
/** The fields of `auth` when its kind is `oauth2-user`. */
|
|
42
34
|
export const OAUTH_AUTH_FIELDS = [
|
|
43
35
|
"kind",
|
|
@@ -49,11 +41,32 @@ export const OAUTH_AUTH_FIELDS = [
|
|
|
49
41
|
"clientSecret",
|
|
50
42
|
"inject",
|
|
51
43
|
"pkce",
|
|
52
|
-
"
|
|
44
|
+
"scopeSeparator",
|
|
45
|
+
"scopePrefix",
|
|
53
46
|
"authorizeParams",
|
|
54
47
|
"tokenHeaders",
|
|
55
48
|
"identity",
|
|
49
|
+
"tolerated",
|
|
50
|
+
"resource",
|
|
51
|
+
"issuer",
|
|
56
52
|
];
|
|
53
|
+
/**
|
|
54
|
+
* Fields an `oauth2-user` spec had before scopes moved to each app's
|
|
55
|
+
* `everyapp.config.ts`, each with what to write instead: refused with
|
|
56
|
+
* that message rather than "unknown field".
|
|
57
|
+
*/
|
|
58
|
+
export const REMOVED_OAUTH_FIELDS = {
|
|
59
|
+
scopeSets: "scope sets are gone: each app lists the provider's own scopes in its everyapp.config.ts. Remove scopeSets.",
|
|
60
|
+
alwaysRequested: "alwaysRequested is gone: put scopes every connect needs (a refresh token's, the identity URL's) in auth.identity.scopes.",
|
|
61
|
+
tolerated: "tolerated moved into auth: auth.tolerated lists the scopes a token may carry beyond what was asked.",
|
|
62
|
+
access: "access is gone for an OAuth provider: the scopes an app lists are the grant, for every method.",
|
|
63
|
+
readMethods: "readMethods is gone for an OAuth provider: the scopes an app lists are the grant, for every method.",
|
|
64
|
+
writeMethods: "writeMethods is gone for an OAuth provider: the scopes an app lists are the grant, for every method.",
|
|
65
|
+
};
|
|
66
|
+
/** `auth` fields renamed, each with its new name. */
|
|
67
|
+
const RENAMED_AUTH_FIELDS = {
|
|
68
|
+
scopeDialect: "renamed auth.scopeSeparator: space or comma (a provider's prefix goes in auth.scopePrefix)",
|
|
69
|
+
};
|
|
57
70
|
function isRecord(value) {
|
|
58
71
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
59
72
|
}
|
|
@@ -99,7 +112,9 @@ function endpoint(value, path, problems, { query }) {
|
|
|
99
112
|
parsed.hash) {
|
|
100
113
|
problems.push({
|
|
101
114
|
path,
|
|
102
|
-
message:
|
|
115
|
+
message: parsed.port
|
|
116
|
+
? `must be an https URL on the default port (${parsed.port} is not: the Gateway's OAuth requests go to port 443 only), with no credentials or fragment`
|
|
117
|
+
: "must be an https URL with no port, credentials or fragment",
|
|
103
118
|
});
|
|
104
119
|
return undefined;
|
|
105
120
|
}
|
|
@@ -109,8 +124,44 @@ function endpoint(value, path, problems, { query }) {
|
|
|
109
124
|
}
|
|
110
125
|
return { url, host: parsed.hostname.toLowerCase() };
|
|
111
126
|
}
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
/**
|
|
128
|
+
* An https URL that names something rather than being fetched (an RFC
|
|
129
|
+
* 8707 resource, an RFC 8414 issuer): a port is allowed, credentials and a
|
|
130
|
+
* fragment are not, and an issuer takes no query either.
|
|
131
|
+
*/
|
|
132
|
+
function identifierUrl(value, path, problems, { query }) {
|
|
133
|
+
if (value === undefined)
|
|
134
|
+
return undefined;
|
|
135
|
+
const url = printable(value, path, problems, 2048);
|
|
136
|
+
if (url === undefined)
|
|
137
|
+
return undefined;
|
|
138
|
+
let parsed;
|
|
139
|
+
try {
|
|
140
|
+
parsed = new URL(url);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
problems.push({ path, message: "must be an https URL" });
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
if (parsed.protocol !== "https:" ||
|
|
147
|
+
parsed.username ||
|
|
148
|
+
parsed.password ||
|
|
149
|
+
parsed.hash ||
|
|
150
|
+
url.includes("#")) {
|
|
151
|
+
problems.push({
|
|
152
|
+
path,
|
|
153
|
+
message: "must be an https URL with no credentials or fragment",
|
|
154
|
+
});
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
if (!query && parsed.search) {
|
|
158
|
+
problems.push({ path, message: "may not carry a query string" });
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return url;
|
|
162
|
+
}
|
|
163
|
+
function settingName(value, path, problems) {
|
|
164
|
+
if (typeof value !== "string" || !/^[A-Z][A-Z0-9_]{2,63}$/.test(value)) {
|
|
114
165
|
problems.push({
|
|
115
166
|
path,
|
|
116
167
|
message: "must name a Gateway setting: upper case letters, digits and underscores, a letter first, 3 to 64 characters (GITHUB_CLIENT_ID)",
|
|
@@ -141,7 +192,8 @@ function stringMap(value, path, problems, each) {
|
|
|
141
192
|
}
|
|
142
193
|
return out;
|
|
143
194
|
}
|
|
144
|
-
|
|
195
|
+
/** A list of provider scope strings: each one scope, with no space or comma, each once. */
|
|
196
|
+
export function scopeList(value, path, problems) {
|
|
145
197
|
if (value === undefined)
|
|
146
198
|
return undefined;
|
|
147
199
|
if (!Array.isArray(value) || value.length > MAX_SCOPES) {
|
|
@@ -170,290 +222,18 @@ function scopeList(value, path, problems) {
|
|
|
170
222
|
}
|
|
171
223
|
return out;
|
|
172
224
|
}
|
|
173
|
-
function routes(value, path, hosts, problems) {
|
|
174
|
-
if (value === undefined)
|
|
175
|
-
return undefined;
|
|
176
|
-
if (!Array.isArray(value) || value.length > MAX_ROUTES) {
|
|
177
|
-
problems.push({
|
|
178
|
-
path,
|
|
179
|
-
message: `must be a list of at most ${MAX_ROUTES} routes`,
|
|
180
|
-
});
|
|
181
|
-
return undefined;
|
|
182
|
-
}
|
|
183
|
-
const out = [];
|
|
184
|
-
value.forEach((item, index) => {
|
|
185
|
-
const at = `${path}.${index}`;
|
|
186
|
-
if (!isRecord(item)) {
|
|
187
|
-
problems.push({ path: at, message: "must be { host, paths, posts? }" });
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
for (const field of Object.keys(item)) {
|
|
191
|
-
if (!["host", "paths", "posts"].includes(field)) {
|
|
192
|
-
problems.push({ path: `${at}.${field}`, message: "unknown field" });
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
const host = printable(item["host"], `${at}.host`, problems, 253);
|
|
196
|
-
if (host !== undefined && !hosts.includes(host)) {
|
|
197
|
-
problems.push({ path: `${at}.host`, message: "must be one of hosts" });
|
|
198
|
-
}
|
|
199
|
-
const paths = [];
|
|
200
|
-
if (!Array.isArray(item["paths"]) || item["paths"].length > MAX_PATHS) {
|
|
201
|
-
problems.push({
|
|
202
|
-
path: `${at}.paths`,
|
|
203
|
-
message: `must be a list of at most ${MAX_PATHS} paths`,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
item["paths"].forEach((entry, offset) => {
|
|
208
|
-
const text = printable(entry, `${at}.paths.${offset}`, problems, 500);
|
|
209
|
-
if (text === undefined)
|
|
210
|
-
return;
|
|
211
|
-
if (!text.startsWith("/") ||
|
|
212
|
-
text.includes("//") ||
|
|
213
|
-
text.includes("\\") ||
|
|
214
|
-
text.includes("%") ||
|
|
215
|
-
/(^|\/)\.\.?(\/|$)/.test(text)) {
|
|
216
|
-
problems.push({
|
|
217
|
-
path: `${at}.paths.${offset}`,
|
|
218
|
-
message: "must start with / and hold no //, backslash, percent-escape or dot segment",
|
|
219
|
-
});
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
paths.push(text);
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
const posts = [];
|
|
226
|
-
if (item["posts"] !== undefined) {
|
|
227
|
-
if (!Array.isArray(item["posts"]) || item["posts"].length > MAX_PATHS) {
|
|
228
|
-
problems.push({
|
|
229
|
-
path: `${at}.posts`,
|
|
230
|
-
message: `must be a list of at most ${MAX_PATHS} anchored patterns`,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
item["posts"].forEach((entry, offset) => {
|
|
235
|
-
const text = printable(entry, `${at}.posts.${offset}`, problems, 500);
|
|
236
|
-
if (text === undefined)
|
|
237
|
-
return;
|
|
238
|
-
if (!text.startsWith("^") || !text.endsWith("$")) {
|
|
239
|
-
problems.push({
|
|
240
|
-
path: `${at}.posts.${offset}`,
|
|
241
|
-
message: "must be anchored: start with ^ and end with $",
|
|
242
|
-
});
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
try {
|
|
246
|
-
posts.push(new RegExp(text));
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
problems.push({
|
|
250
|
-
path: `${at}.posts.${offset}`,
|
|
251
|
-
message: "is not a valid pattern",
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (host === undefined)
|
|
258
|
-
return;
|
|
259
|
-
out.push({
|
|
260
|
-
host,
|
|
261
|
-
paths,
|
|
262
|
-
...(item["posts"] === undefined ? {} : { posts }),
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
return out;
|
|
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
|
-
}
|
|
323
|
-
function scopeSets(value, hosts, problems) {
|
|
324
|
-
if (!isRecord(value)) {
|
|
325
|
-
problems.push({
|
|
326
|
-
path: "scopeSets",
|
|
327
|
-
message: 'required: at least one named set, { "repo.read": { "label", "access", "scopes" } }',
|
|
328
|
-
});
|
|
329
|
-
return undefined;
|
|
330
|
-
}
|
|
331
|
-
const entries = Object.entries(value);
|
|
332
|
-
if (entries.length === 0 || entries.length > MAX_SETS) {
|
|
333
|
-
problems.push({
|
|
334
|
-
path: "scopeSets",
|
|
335
|
-
message: `must name 1 to ${MAX_SETS} sets`,
|
|
336
|
-
});
|
|
337
|
-
return undefined;
|
|
338
|
-
}
|
|
339
|
-
const out = {};
|
|
340
|
-
for (const [name, raw] of entries) {
|
|
341
|
-
const at = `scopeSets.${name}`;
|
|
342
|
-
if (!SCOPE_SET_NAME.test(name)) {
|
|
343
|
-
problems.push({
|
|
344
|
-
path: at,
|
|
345
|
-
message: "a set is named identity, or <area>.<access> in lowercase (repo.read)",
|
|
346
|
-
});
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
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" });
|
|
379
|
-
}
|
|
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))
|
|
432
|
-
continue;
|
|
433
|
-
merged[name] = set;
|
|
434
|
-
for (const scope of set.scopes) {
|
|
435
|
-
if (!scopes.includes(scope))
|
|
436
|
-
scopes.push(scope);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
...capability,
|
|
441
|
-
authorize: { ...capability.authorize, scopes },
|
|
442
|
-
scopePolicy: { ...policy, sets: merged },
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
225
|
function identity(value, hosts, problems) {
|
|
446
226
|
if (value === undefined)
|
|
447
227
|
return undefined;
|
|
448
228
|
if (!isRecord(value)) {
|
|
449
229
|
problems.push({
|
|
450
230
|
path: "auth.identity",
|
|
451
|
-
message: "must be { url, label, id? }",
|
|
231
|
+
message: "must be { url, label, id?, scopes? }",
|
|
452
232
|
});
|
|
453
233
|
return undefined;
|
|
454
234
|
}
|
|
455
235
|
for (const field of Object.keys(value)) {
|
|
456
|
-
if (!["url", "label", "id"].includes(field)) {
|
|
236
|
+
if (!["url", "label", "id", "scopes"].includes(field)) {
|
|
457
237
|
problems.push({
|
|
458
238
|
path: `auth.identity.${field}`,
|
|
459
239
|
message: "unknown field",
|
|
@@ -489,30 +269,34 @@ function identity(value, hosts, problems) {
|
|
|
489
269
|
message: "must be a JSON field name, or a dotted path to a nested one",
|
|
490
270
|
});
|
|
491
271
|
}
|
|
272
|
+
const scopes = scopeList(value["scopes"], "auth.identity.scopes", problems);
|
|
492
273
|
if (!url || label === undefined)
|
|
493
274
|
return undefined;
|
|
494
|
-
return {
|
|
275
|
+
return {
|
|
276
|
+
identity: { url: url.url, label, ...(id === undefined ? {} : { id }) },
|
|
277
|
+
scopes: scopes ?? [],
|
|
278
|
+
};
|
|
495
279
|
}
|
|
496
280
|
/**
|
|
497
|
-
* Parse the OAuth half of a spec whose `auth.kind` is `oauth2-user
|
|
498
|
-
*
|
|
499
|
-
* the
|
|
500
|
-
* success the catalog-shaped capability with its `flow`.
|
|
281
|
+
* Parse the OAuth half of a spec whose `auth.kind` is `oauth2-user`, with
|
|
282
|
+
* the common fields already parsed. Every problem is reported; on success
|
|
283
|
+
* the capability with its `flow`.
|
|
501
284
|
*/
|
|
502
285
|
export function parseOAuthUserSpec(input, common, problems) {
|
|
503
|
-
const settingName_ = (value, path, into) => settingName(value, path, into, /^[A-Z][A-Z0-9_]{2,63}$/);
|
|
504
286
|
const auth = input["auth"];
|
|
505
287
|
for (const field of Object.keys(auth)) {
|
|
506
|
-
|
|
288
|
+
const renamed = RENAMED_AUTH_FIELDS[field];
|
|
289
|
+
if (renamed) {
|
|
290
|
+
problems.push({ path: `auth.${field}`, message: renamed });
|
|
291
|
+
}
|
|
292
|
+
else if (!OAUTH_AUTH_FIELDS.includes(field)) {
|
|
507
293
|
problems.push({
|
|
508
294
|
path: `auth.${field}`,
|
|
509
295
|
message: "not a field of auth.kind oauth2-user",
|
|
510
296
|
});
|
|
511
297
|
}
|
|
512
298
|
}
|
|
513
|
-
const authorize = endpoint(auth["authorizeUrl"], "auth.authorizeUrl", problems, {
|
|
514
|
-
query: true,
|
|
515
|
-
});
|
|
299
|
+
const authorize = endpoint(auth["authorizeUrl"], "auth.authorizeUrl", problems, { query: true });
|
|
516
300
|
if (auth["authorizeUrl"] === undefined) {
|
|
517
301
|
problems.push({ path: "auth.authorizeUrl", message: "required" });
|
|
518
302
|
}
|
|
@@ -535,15 +319,12 @@ export function parseOAuthUserSpec(input, common, problems) {
|
|
|
535
319
|
});
|
|
536
320
|
}
|
|
537
321
|
if (revokeMethod !== undefined && revoke === undefined) {
|
|
538
|
-
problems.push({
|
|
539
|
-
path: "auth.revokeMethod",
|
|
540
|
-
message: "needs a revokeUrl",
|
|
541
|
-
});
|
|
322
|
+
problems.push({ path: "auth.revokeMethod", message: "needs a revokeUrl" });
|
|
542
323
|
}
|
|
543
|
-
const clientId =
|
|
324
|
+
const clientId = settingName(auth["clientId"], "auth.clientId", problems);
|
|
544
325
|
const clientSecret = auth["clientSecret"] === undefined
|
|
545
326
|
? undefined
|
|
546
|
-
:
|
|
327
|
+
: settingName(auth["clientSecret"], "auth.clientSecret", problems);
|
|
547
328
|
if (clientSecret !== undefined && clientSecret === clientId) {
|
|
548
329
|
problems.push({
|
|
549
330
|
path: "auth.clientSecret",
|
|
@@ -561,29 +342,32 @@ export function parseOAuthUserSpec(input, common, problems) {
|
|
|
561
342
|
if (typeof pkce !== "boolean") {
|
|
562
343
|
problems.push({ path: "auth.pkce", message: "must be true or false" });
|
|
563
344
|
}
|
|
564
|
-
const
|
|
565
|
-
if (!
|
|
345
|
+
const separator = auth["scopeSeparator"] ?? "space";
|
|
346
|
+
if (!SEPARATORS.includes(separator)) {
|
|
347
|
+
problems.push({
|
|
348
|
+
path: "auth.scopeSeparator",
|
|
349
|
+
message: `must be one of ${SEPARATORS.join(", ")}`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
const prefix = endpoint(auth["scopePrefix"], "auth.scopePrefix", problems, {
|
|
353
|
+
query: false,
|
|
354
|
+
});
|
|
355
|
+
if (prefix && !prefix.url.endsWith("/")) {
|
|
566
356
|
problems.push({
|
|
567
|
-
path: "auth.
|
|
568
|
-
message:
|
|
357
|
+
path: "auth.scopePrefix",
|
|
358
|
+
message: "must end in / (https://www.googleapis.com/auth/): an app's scope is joined onto it",
|
|
569
359
|
});
|
|
570
360
|
}
|
|
571
361
|
const authorizeParams = stringMap(auth["authorizeParams"], "auth.authorizeParams", problems, (name, at) => {
|
|
572
362
|
if (!PARAM_NAME.test(name)) {
|
|
573
363
|
problems.push({ path: at, message: "must be a query parameter name" });
|
|
574
364
|
}
|
|
575
|
-
if (
|
|
576
|
-
"client_id",
|
|
577
|
-
"redirect_uri",
|
|
578
|
-
"response_type",
|
|
579
|
-
"scope",
|
|
580
|
-
"state",
|
|
581
|
-
"code_challenge",
|
|
582
|
-
"code_challenge_method",
|
|
583
|
-
].includes(name)) {
|
|
365
|
+
if (RESERVED_AUTHORIZE_PARAMS.includes(name)) {
|
|
584
366
|
problems.push({
|
|
585
367
|
path: at,
|
|
586
|
-
message:
|
|
368
|
+
message: name === "include_granted_scopes"
|
|
369
|
+
? "include_granted_scopes would widen the token beyond the app's scopes; the Gateway never sends it"
|
|
370
|
+
: `${name} is a parameter the Gateway sets itself`,
|
|
587
371
|
});
|
|
588
372
|
}
|
|
589
373
|
});
|
|
@@ -600,78 +384,61 @@ export function parseOAuthUserSpec(input, common, problems) {
|
|
|
600
384
|
}
|
|
601
385
|
});
|
|
602
386
|
const parsedIdentity = identity(auth["identity"], common.hosts, problems);
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
387
|
+
const tolerated = scopeList(auth["tolerated"], "auth.tolerated", problems);
|
|
388
|
+
const resource = identifierUrl(auth["resource"], "auth.resource", problems, {
|
|
389
|
+
query: true,
|
|
390
|
+
});
|
|
391
|
+
const issuer = identifierUrl(auth["issuer"], "auth.issuer", problems, {
|
|
392
|
+
query: false,
|
|
393
|
+
});
|
|
606
394
|
if (!authorize ||
|
|
607
395
|
!token ||
|
|
608
396
|
clientId === undefined ||
|
|
609
|
-
!sets ||
|
|
610
397
|
typeof pkce !== "boolean" ||
|
|
611
|
-
!
|
|
398
|
+
!SEPARATORS.includes(separator) ||
|
|
612
399
|
!INJECTIONS.includes(inject)) {
|
|
613
400
|
return undefined;
|
|
614
401
|
}
|
|
615
|
-
const union = [];
|
|
616
|
-
const add = (scopes) => {
|
|
617
|
-
for (const scope of scopes)
|
|
618
|
-
if (!union.includes(scope))
|
|
619
|
-
union.push(scope);
|
|
620
|
-
};
|
|
621
|
-
for (const set of Object.values(sets)) {
|
|
622
|
-
add(set.scopes);
|
|
623
|
-
add(set.userScopes ?? []);
|
|
624
|
-
}
|
|
625
|
-
add(alwaysRequested ?? []);
|
|
626
|
-
const policy = {
|
|
627
|
-
sets,
|
|
628
|
-
tolerated: tolerated ?? [],
|
|
629
|
-
alwaysRequested: alwaysRequested ?? [],
|
|
630
|
-
dialect: dialect,
|
|
631
|
-
grantTypes: ["authorization_code", "refresh_token"],
|
|
632
|
-
requestScope: "keep",
|
|
633
|
-
deniedSegments: [...DEFAULT_DENIED_SEGMENTS],
|
|
634
|
-
};
|
|
635
402
|
const flow = {
|
|
403
|
+
authorizeUrl: authorize.url,
|
|
636
404
|
tokenUrl: token.url,
|
|
637
405
|
...(revoke === undefined ? {} : { revokeUrl: revoke.url }),
|
|
638
406
|
...(revokeMethod === undefined
|
|
639
407
|
? {}
|
|
640
408
|
: { revokeMethod: revokeMethod }),
|
|
409
|
+
inject: inject,
|
|
641
410
|
pkce,
|
|
642
|
-
|
|
411
|
+
scopeSeparator: separator,
|
|
412
|
+
...(prefix === undefined ? {} : { scopePrefix: prefix.url }),
|
|
643
413
|
...(authorizeParams === undefined ? {} : { authorizeParams }),
|
|
644
414
|
...(tokenHeaders === undefined ? {} : { tokenHeaders }),
|
|
645
|
-
...(parsedIdentity === undefined
|
|
415
|
+
...(parsedIdentity === undefined
|
|
416
|
+
? {}
|
|
417
|
+
: { identity: parsedIdentity.identity }),
|
|
418
|
+
identityScopes: parsedIdentity?.scopes ?? [],
|
|
419
|
+
tolerated: tolerated ?? [],
|
|
420
|
+
...(resource === undefined ? {} : { resource }),
|
|
421
|
+
...(issuer === undefined ? {} : { issuer }),
|
|
646
422
|
};
|
|
647
|
-
const
|
|
648
|
-
|
|
423
|
+
const capability = oauthCapabilityOf(common, {
|
|
424
|
+
clientId,
|
|
425
|
+
...(clientSecret === undefined ? {} : { clientSecret }),
|
|
426
|
+
}, flow);
|
|
427
|
+
const hosts = [...common.hosts];
|
|
428
|
+
for (const host of [authorize.host, token.host, revoke?.host]) {
|
|
429
|
+
if (host && !hosts.includes(host))
|
|
430
|
+
hosts.push(host);
|
|
431
|
+
}
|
|
432
|
+
return { capability, hosts };
|
|
433
|
+
}
|
|
434
|
+
function oauthCapabilityOf(common, secrets, flow) {
|
|
435
|
+
return {
|
|
649
436
|
mode: "oauth_client",
|
|
650
437
|
label: common.name,
|
|
651
|
-
secrets
|
|
652
|
-
clientId,
|
|
653
|
-
...(clientSecret === undefined ? {} : { clientSecret }),
|
|
654
|
-
},
|
|
655
|
-
token: [
|
|
656
|
-
{
|
|
657
|
-
host: token.host,
|
|
658
|
-
path: tokenPath,
|
|
659
|
-
methods: ["POST"],
|
|
660
|
-
inject: inject,
|
|
661
|
-
},
|
|
662
|
-
],
|
|
663
|
-
access: common.access ?? ["read"],
|
|
438
|
+
secrets,
|
|
664
439
|
console: common.docs,
|
|
665
|
-
authorize: { url: authorize.url, scopes: union },
|
|
666
|
-
scopePolicy: policy,
|
|
667
440
|
api: [...common.hosts],
|
|
668
441
|
flow,
|
|
669
|
-
...(common.readMethods === undefined
|
|
670
|
-
? {}
|
|
671
|
-
: { readMethods: common.readMethods }),
|
|
672
|
-
...(common.writeMethods === undefined
|
|
673
|
-
? {}
|
|
674
|
-
: { writeMethods: common.writeMethods }),
|
|
675
442
|
...(common.description === undefined
|
|
676
443
|
? {}
|
|
677
444
|
: { description: common.description }),
|
|
@@ -680,24 +447,21 @@ export function parseOAuthUserSpec(input, common, problems) {
|
|
|
680
447
|
...(common.example === undefined ? {} : { example: common.example }),
|
|
681
448
|
...(common.tags === undefined ? {} : { tags: common.tags }),
|
|
682
449
|
};
|
|
683
|
-
const hosts = [...common.hosts];
|
|
684
|
-
for (const host of [authorize.host, token.host, revoke?.host]) {
|
|
685
|
-
if (host && !hosts.includes(host))
|
|
686
|
-
hosts.push(host);
|
|
687
|
-
}
|
|
688
|
-
return { capability, hosts };
|
|
689
450
|
}
|
|
690
451
|
/**
|
|
691
|
-
* The JSON spec of an OAuth capability
|
|
692
|
-
*
|
|
693
|
-
* capability back. A catalog client without a `flow` has no spec.
|
|
452
|
+
* The JSON spec of an OAuth capability (`provider show`, a preset's round
|
|
453
|
+
* trip): `parseProviderSpec` of it gives the capability back.
|
|
694
454
|
*/
|
|
695
455
|
export function oauthSpecOf(id, capability) {
|
|
696
|
-
const flow = capability
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
456
|
+
const { flow } = capability;
|
|
457
|
+
const identity = flow.identity === undefined
|
|
458
|
+
? undefined
|
|
459
|
+
: {
|
|
460
|
+
...flow.identity,
|
|
461
|
+
...(flow.identityScopes.length === 0
|
|
462
|
+
? {}
|
|
463
|
+
: { scopes: [...flow.identityScopes] }),
|
|
464
|
+
};
|
|
701
465
|
return {
|
|
702
466
|
id,
|
|
703
467
|
name: capability.label,
|
|
@@ -711,7 +475,7 @@ export function oauthSpecOf(id, capability) {
|
|
|
711
475
|
hosts: [...capability.api],
|
|
712
476
|
auth: {
|
|
713
477
|
kind: "oauth2-user",
|
|
714
|
-
authorizeUrl:
|
|
478
|
+
authorizeUrl: flow.authorizeUrl,
|
|
715
479
|
tokenUrl: flow.tokenUrl,
|
|
716
480
|
...(flow.revokeUrl === undefined ? {} : { revokeUrl: flow.revokeUrl }),
|
|
717
481
|
...(flow.revokeMethod === undefined
|
|
@@ -721,57 +485,25 @@ export function oauthSpecOf(id, capability) {
|
|
|
721
485
|
...(capability.secrets.clientSecret === undefined
|
|
722
486
|
? {}
|
|
723
487
|
: { clientSecret: capability.secrets.clientSecret }),
|
|
724
|
-
inject,
|
|
488
|
+
inject: flow.inject,
|
|
725
489
|
pkce: flow.pkce,
|
|
726
|
-
|
|
490
|
+
scopeSeparator: flow.scopeSeparator,
|
|
491
|
+
...(flow.scopePrefix === undefined
|
|
492
|
+
? {}
|
|
493
|
+
: { scopePrefix: flow.scopePrefix }),
|
|
727
494
|
...(flow.authorizeParams === undefined
|
|
728
495
|
? {}
|
|
729
496
|
: { authorizeParams: { ...flow.authorizeParams } }),
|
|
730
497
|
...(flow.tokenHeaders === undefined
|
|
731
498
|
? {}
|
|
732
499
|
: { tokenHeaders: { ...flow.tokenHeaders } }),
|
|
733
|
-
...(
|
|
500
|
+
...(identity === undefined ? {} : { identity }),
|
|
501
|
+
...(flow.tolerated.length === 0
|
|
734
502
|
? {}
|
|
735
|
-
: {
|
|
503
|
+
: { tolerated: [...flow.tolerated] }),
|
|
504
|
+
...(flow.resource === undefined ? {} : { resource: flow.resource }),
|
|
505
|
+
...(flow.issuer === undefined ? {} : { issuer: flow.issuer }),
|
|
736
506
|
},
|
|
737
|
-
scopeSets: Object.fromEntries(Object.entries(policy.sets).map(([name, set]) => [
|
|
738
|
-
name,
|
|
739
|
-
{
|
|
740
|
-
label: set.label,
|
|
741
|
-
access: set.access,
|
|
742
|
-
scopes: [...set.scopes],
|
|
743
|
-
...(set.userScopes === undefined
|
|
744
|
-
? {}
|
|
745
|
-
: { userScopes: [...set.userScopes] }),
|
|
746
|
-
...(set.routes.length === 0
|
|
747
|
-
? {}
|
|
748
|
-
: {
|
|
749
|
-
routes: set.routes.map((route) => ({
|
|
750
|
-
host: route.host,
|
|
751
|
-
paths: [...route.paths],
|
|
752
|
-
// `source` escapes `/` as `\/`; the spec wrote it bare.
|
|
753
|
-
...(route.posts === undefined
|
|
754
|
-
? {}
|
|
755
|
-
: {
|
|
756
|
-
posts: route.posts.map((pattern) => pattern.source.replace(/\\\//g, "/")),
|
|
757
|
-
}),
|
|
758
|
-
})),
|
|
759
|
-
}),
|
|
760
|
-
},
|
|
761
|
-
])),
|
|
762
|
-
...(policy.alwaysRequested.length === 0
|
|
763
|
-
? {}
|
|
764
|
-
: { alwaysRequested: [...policy.alwaysRequested] }),
|
|
765
|
-
...(policy.tolerated.length === 0
|
|
766
|
-
? {}
|
|
767
|
-
: { tolerated: [...policy.tolerated] }),
|
|
768
|
-
access: [...capability.access],
|
|
769
|
-
...(capability.readMethods === undefined
|
|
770
|
-
? {}
|
|
771
|
-
: { readMethods: [...capability.readMethods] }),
|
|
772
|
-
...(capability.writeMethods === undefined
|
|
773
|
-
? {}
|
|
774
|
-
: { writeMethods: [...capability.writeMethods] }),
|
|
775
507
|
...(capability.hint === undefined ? {} : { hint: capability.hint }),
|
|
776
508
|
...(capability.example === undefined
|
|
777
509
|
? {}
|
|
@@ -779,11 +511,3 @@ export function oauthSpecOf(id, capability) {
|
|
|
779
511
|
...(capability.tags === undefined ? {} : { tags: [...capability.tags] }),
|
|
780
512
|
};
|
|
781
513
|
}
|
|
782
|
-
/** The methods a held-token write may use on `capability` beyond its reads. */
|
|
783
|
-
export function oauthWriteMethods(capability) {
|
|
784
|
-
return capability.writeMethods ?? DEFAULT_WRITE_METHODS;
|
|
785
|
-
}
|
|
786
|
-
/** The methods a read grant may use on `capability` (GET and HEAD unless the spec says). */
|
|
787
|
-
export function oauthReadMethods(capability) {
|
|
788
|
-
return capability.readMethods ?? ["GET", "HEAD"];
|
|
789
|
-
}
|