@every-app/sdk 0.3.0-rc.1 → 0.3.0-rc.11
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/LICENSE +21 -0
- package/README.md +62 -64
- package/dist/cloudflare/catalogSettings.generated.d.ts +84 -0
- package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
- package/dist/cloudflare/catalogSettings.generated.js +175 -0
- package/dist/cloudflare/index.d.ts +3 -2
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/index.js +4 -1
- package/dist/cloudflare/sentinel.d.ts +60 -2
- package/dist/cloudflare/sentinel.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.js +37 -2
- package/dist/cloudflare/worker.d.ts +391 -63
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +394 -108
- package/dist/dev/index.d.ts +1 -1
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/index.js +1 -1
- package/dist/dev/keypair.d.ts +2 -0
- package/dist/dev/keypair.d.ts.map +1 -1
- package/dist/dev/keypair.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/internal/aiDefault.d.ts +205 -0
- package/dist/internal/aiDefault.d.ts.map +1 -0
- package/dist/internal/aiDefault.js +335 -0
- package/dist/internal/aiProviders.d.ts +7 -2
- package/dist/internal/aiProviders.d.ts.map +1 -1
- package/dist/internal/aiProviders.js +19 -6
- package/dist/internal/appSettings.d.ts +82 -4
- package/dist/internal/appSettings.d.ts.map +1 -1
- package/dist/internal/appSettings.js +204 -29
- package/dist/internal/audit.d.ts +108 -0
- package/dist/internal/audit.d.ts.map +1 -0
- package/dist/internal/audit.js +235 -0
- package/dist/internal/capabilities.d.ts +2261 -815
- package/dist/internal/capabilities.d.ts.map +1 -1
- package/dist/internal/capabilities.js +122 -1034
- package/dist/internal/catalog/commerce.d.ts +394 -0
- package/dist/internal/catalog/commerce.d.ts.map +1 -0
- package/dist/internal/catalog/commerce.js +477 -0
- package/dist/internal/catalog/comms.d.ts +375 -0
- package/dist/internal/catalog/comms.d.ts.map +1 -0
- package/dist/internal/catalog/comms.js +520 -0
- package/dist/internal/catalog/index.d.ts +2008 -0
- package/dist/internal/catalog/index.d.ts.map +1 -0
- package/dist/internal/catalog/index.js +19 -0
- package/dist/internal/catalog/inference.d.ts +393 -0
- package/dist/internal/catalog/inference.d.ts.map +1 -0
- package/dist/internal/catalog/inference.js +460 -0
- package/dist/internal/catalog/infra.d.ts +458 -0
- package/dist/internal/catalog/infra.d.ts.map +1 -0
- package/dist/internal/catalog/infra.js +608 -0
- package/dist/internal/catalog/search.d.ts +400 -0
- package/dist/internal/catalog/search.d.ts.map +1 -0
- package/dist/internal/catalog/search.js +463 -0
- package/dist/internal/devProviders.d.ts +2 -2
- package/dist/internal/devProviders.d.ts.map +1 -1
- package/dist/internal/devProviders.js +19 -1
- package/dist/internal/events.d.ts +65 -3
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +110 -5
- package/dist/internal/gatewaySettings.d.ts +3 -2
- package/dist/internal/gatewaySettings.d.ts.map +1 -1
- package/dist/internal/gatewaySettings.js +12 -6
- package/dist/internal/identity.d.ts +41 -4
- package/dist/internal/identity.d.ts.map +1 -1
- package/dist/internal/identity.js +48 -10
- package/dist/internal/index.d.ts +6 -1
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +6 -1
- package/dist/internal/members.d.ts +3 -2
- package/dist/internal/members.d.ts.map +1 -1
- package/dist/internal/oauthBuiltIns.d.ts +101 -0
- package/dist/internal/oauthBuiltIns.d.ts.map +1 -0
- package/dist/internal/oauthBuiltIns.js +122 -0
- package/dist/internal/oauthClient.d.ts +8 -50
- package/dist/internal/oauthClient.d.ts.map +1 -1
- package/dist/internal/oauthClient.js +2 -177
- package/dist/internal/oauthPresets.d.ts +32 -0
- package/dist/internal/oauthPresets.d.ts.map +1 -0
- package/dist/internal/oauthPresets.js +253 -0
- package/dist/internal/oauthScopes.d.ts +43 -100
- package/dist/internal/oauthScopes.d.ts.map +1 -1
- package/dist/internal/oauthScopes.js +83 -382
- package/dist/internal/oauthSpec.d.ts +103 -0
- package/dist/internal/oauthSpec.d.ts.map +1 -0
- package/dist/internal/oauthSpec.js +520 -0
- package/dist/internal/protocol.d.ts +97 -5
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +89 -1
- package/dist/internal/providerRegistry.d.ts +17 -6
- package/dist/internal/providerRegistry.d.ts.map +1 -1
- package/dist/internal/providerRegistry.js +71 -8
- package/dist/internal/providerSpec.d.ts +18 -7
- package/dist/internal/providerSpec.d.ts.map +1 -1
- package/dist/internal/providerSpec.js +420 -34
- package/dist/internal/remoteMcp.d.ts.map +1 -1
- package/dist/internal/remoteMcp.js +1 -5
- package/dist/internal/signInProviders.d.ts +104 -0
- package/dist/internal/signInProviders.d.ts.map +1 -0
- package/dist/internal/signInProviders.js +245 -0
- package/dist/internal/tools.d.ts +5 -4
- package/dist/internal/tools.d.ts.map +1 -1
- package/dist/internal/tools.js +4 -3
- package/dist/manifest.d.ts +267 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +30 -0
- package/dist/server/ai.d.ts +111 -0
- package/dist/server/ai.d.ts.map +1 -0
- package/dist/server/ai.js +145 -0
- package/dist/server/aiBinding.d.ts +68 -0
- package/dist/server/aiBinding.d.ts.map +1 -0
- package/dist/server/aiBinding.js +294 -0
- package/dist/server/attribution.d.ts +18 -5
- package/dist/server/attribution.d.ts.map +1 -1
- package/dist/server/attribution.js +19 -5
- package/dist/server/connectUrl.d.ts +16 -0
- package/dist/server/connectUrl.d.ts.map +1 -0
- package/dist/server/connectUrl.js +40 -0
- package/dist/server/context.d.ts +68 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +117 -0
- package/dist/server/emailBinding.d.ts +72 -0
- package/dist/server/emailBinding.d.ts.map +1 -0
- package/dist/server/emailBinding.js +124 -0
- package/dist/server/emit.d.ts +1 -1
- package/dist/server/emit.d.ts.map +1 -1
- package/dist/server/emit.js +7 -7
- package/dist/server/env.d.ts +44 -0
- package/dist/server/env.d.ts.map +1 -0
- package/dist/server/env.js +1 -0
- 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 +40 -11
- package/dist/server/everyApp.d.ts.map +1 -1
- package/dist/server/everyApp.js +116 -6
- package/dist/server/fromMcpTools.d.ts +89 -0
- package/dist/server/fromMcpTools.d.ts.map +1 -0
- package/dist/server/fromMcpTools.js +192 -0
- package/dist/server/gatewayFetch.d.ts +42 -18
- package/dist/server/gatewayFetch.d.ts.map +1 -1
- package/dist/server/gatewayFetch.js +138 -19
- package/dist/server/gatewayUrl.d.ts +13 -0
- package/dist/server/gatewayUrl.d.ts.map +1 -0
- package/dist/server/gatewayUrl.js +34 -0
- package/dist/server/index.d.ts +16 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +13 -3
- package/dist/server/integrationFetch.d.ts +92 -73
- package/dist/server/integrationFetch.d.ts.map +1 -1
- package/dist/server/integrationFetch.js +206 -319
- package/dist/server/listMembers.d.ts +1 -1
- package/dist/server/listMembers.d.ts.map +1 -1
- package/dist/server/listMembers.js +8 -11
- 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.d.ts +1 -1
- package/dist/server/sendEmail.d.ts.map +1 -1
- package/dist/server/sendEmail.js +7 -7
- package/dist/server/tools.d.ts +7 -2
- package/dist/server/tools.d.ts.map +1 -1
- package/dist/server/tools.js +2 -1
- package/dist/server/toolsEndpoint.d.ts +3 -1
- package/dist/server/toolsEndpoint.d.ts.map +1 -1
- package/dist/server/toolsEndpoint.js +19 -7
- 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/tanstack-start/index.d.ts +9 -0
- package/dist/tanstack-start/index.d.ts.map +1 -0
- package/dist/tanstack-start/index.js +29 -0
- package/dist/testing/index.d.ts +93 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +156 -0
- package/package.json +35 -14
- package/dist/server/authorizeUrl.d.ts +0 -43
- package/dist/server/authorizeUrl.d.ts.map +0 -1
- package/dist/server/authorizeUrl.js +0 -110
|
@@ -16,15 +16,14 @@
|
|
|
16
16
|
* proxy's rules: inference routes only, the model allowlist, the usage
|
|
17
17
|
* tap. Never a capability: its key would reach the account's credits
|
|
18
18
|
* and keys on the same host.
|
|
19
|
-
* - `oauth_client`:
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* handed may carry.
|
|
19
|
+
* - `oauth_client`: an OAuth provider (Google, Microsoft, Slack built
|
|
20
|
+
* in; GitHub and others as presets, ./oauthPresets.ts) whose client the
|
|
21
|
+
* gateway holds and whose flow it runs. A person connects in an app,
|
|
22
|
+
* for exactly the provider scopes the app's manifest entry lists
|
|
23
|
+
* (`scopes`); the gateway keeps the tokens and puts the person's token
|
|
24
|
+
* on the app's calls to the provider's `api` hosts. Each provider is a
|
|
25
|
+
* spec (./oauthSpec.ts), its differences data; ./oauthScopes.ts holds
|
|
26
|
+
* the one set of scope rules.
|
|
28
27
|
* - `service`: something the gateway does itself, with no upstream host
|
|
29
28
|
* an app names (sending email, listing the organization's members). An
|
|
30
29
|
* app lists it the same way and calls its routes,
|
|
@@ -43,6 +42,23 @@ export const DEFAULT_WRITE_METHODS = [
|
|
|
43
42
|
"PATCH",
|
|
44
43
|
"DELETE",
|
|
45
44
|
];
|
|
45
|
+
import { CATALOG_ENTRIES } from "./catalog/index.js";
|
|
46
|
+
import { BUILT_IN_OAUTH_SPECS } from "./oauthBuiltIns.js";
|
|
47
|
+
import { oauthCapabilityFromSpec } from "./oauthSpec.js";
|
|
48
|
+
/**
|
|
49
|
+
* A built-in OAuth provider's catalog entry from its spec, its setting
|
|
50
|
+
* names kept literal so `CapabilitySecret` names them.
|
|
51
|
+
*/
|
|
52
|
+
function builtInOAuth(spec, setup) {
|
|
53
|
+
return {
|
|
54
|
+
...oauthCapabilityFromSpec(spec),
|
|
55
|
+
secrets: {
|
|
56
|
+
clientId: spec.auth.clientId,
|
|
57
|
+
clientSecret: spec.auth.clientSecret,
|
|
58
|
+
},
|
|
59
|
+
setup,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
46
62
|
export const CAPABILITIES = {
|
|
47
63
|
// The AI providers, in the order the AI proxy's table had them.
|
|
48
64
|
openai: {
|
|
@@ -69,6 +85,12 @@ export const CAPABILITIES = {
|
|
|
69
85
|
// More than inference needs both the operator (AI_PROXY_EXTRA_ROUTES)
|
|
70
86
|
// and the app's manifest (`ai.routes`): OpenAI paths only.
|
|
71
87
|
extraRoutes: true,
|
|
88
|
+
// Tool calling on chat completions needs `reasoning_effort: "none"`
|
|
89
|
+
// on the GPT-6 family (the responses route takes tools as it is).
|
|
90
|
+
chat: { path: "/v1/chat/completions", seedModel: "gpt-6-luna" },
|
|
91
|
+
embeddings: { path: "/v1/embeddings", seedModel: "text-embedding-3-small" },
|
|
92
|
+
// AI Gateway's OpenAI endpoint takes the path without `/v1`.
|
|
93
|
+
aig: { slug: "openai", trim: "/v1" },
|
|
72
94
|
hint: "A secret key from the API keys page; the Gateway puts it on every call as a bearer token.",
|
|
73
95
|
verified: { on: "2026-10-25", how: "live" },
|
|
74
96
|
},
|
|
@@ -89,7 +111,24 @@ export const CAPABILITIES = {
|
|
|
89
111
|
"/api/v1/responses",
|
|
90
112
|
"/api/v1/embeddings",
|
|
91
113
|
],
|
|
114
|
+
chat: {
|
|
115
|
+
path: "/api/v1/chat/completions",
|
|
116
|
+
seedModel: "anthropic/claude-haiku-5.5",
|
|
117
|
+
},
|
|
118
|
+
embeddings: {
|
|
119
|
+
path: "/api/v1/embeddings",
|
|
120
|
+
seedModel: "openai/text-embedding-3-small",
|
|
121
|
+
},
|
|
122
|
+
// AI Gateway's OpenRouter endpoint takes the path without `/api`.
|
|
123
|
+
aig: { slug: "openrouter", trim: "/api" },
|
|
92
124
|
hint: "An API key from OpenRouter's keys page; the Gateway puts it on every call as a bearer token.",
|
|
125
|
+
// Every answer's `usage.cost` is the call's price in USD, what
|
|
126
|
+
// OpenRouter charges the key: read off the tail with the tokens.
|
|
127
|
+
cost: {
|
|
128
|
+
unit: "token",
|
|
129
|
+
responseField: "usage.cost",
|
|
130
|
+
note: "The answer's usage.cost is the call's price in USD, per model.",
|
|
131
|
+
},
|
|
93
132
|
verified: { on: "2026-10-25", how: "live" },
|
|
94
133
|
},
|
|
95
134
|
// Workers AI's OpenAI-compatible REST API, in the gateway's own account
|
|
@@ -114,9 +153,55 @@ export const CAPABILITIES = {
|
|
|
114
153
|
"/v1/responses",
|
|
115
154
|
],
|
|
116
155
|
normalize: "workers-ai",
|
|
156
|
+
chat: {
|
|
157
|
+
path: "/v1/chat/completions",
|
|
158
|
+
seedModel: "@cf/meta/llama-4-scout-17b-16e-instruct",
|
|
159
|
+
},
|
|
160
|
+
embeddings: { path: "/v1/embeddings", seedModel: "@cf/baai/bge-m3" },
|
|
161
|
+
// Workers AI's REST API is AI Gateway's own host: the URL stays and
|
|
162
|
+
// `cf-aig-gateway-id` names the gateway.
|
|
163
|
+
aig: { slug: "workers-ai", header: true },
|
|
117
164
|
hint: "A Cloudflare API token with Workers AI Read (the Workers AI page's prefilled token); the account is the Gateway's own.",
|
|
118
165
|
verified: { on: "2026-10-25", how: "live" },
|
|
119
166
|
},
|
|
167
|
+
// Cloudflare AI Gateway: a proxy in front of the other AI providers
|
|
168
|
+
// (logs, caching, rate limits, spend), with no models of its own. With
|
|
169
|
+
// AI_GATEWAY_ID set, every call to an `ai` entry that has an `aig` slug
|
|
170
|
+
// goes through it, the provider's own key still on the call; the
|
|
171
|
+
// gateway's own token is optional (an authenticated gateway requires
|
|
172
|
+
// it). Never listed under `providers`: it fronts the entries apps list.
|
|
173
|
+
"ai-gateway": {
|
|
174
|
+
mode: "ai",
|
|
175
|
+
kind: "proxy",
|
|
176
|
+
label: "Cloudflare AI Gateway",
|
|
177
|
+
description: "Cloudflare's proxy in front of the other AI providers: logs, caching, rate limits and spend, with each provider's own key.",
|
|
178
|
+
auth: {
|
|
179
|
+
kind: "header",
|
|
180
|
+
header: "cf-aig-authorization",
|
|
181
|
+
prefix: "Bearer ",
|
|
182
|
+
secret: "AI_GATEWAY_API_KEY",
|
|
183
|
+
},
|
|
184
|
+
authOptional: true,
|
|
185
|
+
docs: "https://dash.cloudflare.com/?to=/:account/ai/ai-gateway",
|
|
186
|
+
api: "https://developers.cloudflare.com/ai-gateway/",
|
|
187
|
+
setup: "setup/ai-gateway.md",
|
|
188
|
+
origin: "https://gateway.ai.cloudflare.com",
|
|
189
|
+
alsoNeeds: ["CLOUDFLARE_ACCOUNT_ID", "AI_GATEWAY_ID"],
|
|
190
|
+
basePath: "/v1/{CLOUDFLARE_ACCOUNT_ID}/{AI_GATEWAY_ID}",
|
|
191
|
+
inferenceRoutes: [],
|
|
192
|
+
hint: "The gateway's id from the AI Gateway page (everyapp ai gateway on makes one named everyapp); AI_GATEWAY_API_KEY only for an authenticated gateway, sent as cf-aig-authorization.",
|
|
193
|
+
secrets: [
|
|
194
|
+
{
|
|
195
|
+
name: "AI_GATEWAY_ID",
|
|
196
|
+
hint: "The AI Gateway's id (its name in the dashboard), in the Gateway's own Cloudflare account.",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "AI_GATEWAY_API_KEY",
|
|
200
|
+
hint: "An AI Gateway token with Run permission, only when the gateway is authenticated; leave unset for an open gateway.",
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
verified: { on: "2026-10-25", how: "docs" },
|
|
204
|
+
},
|
|
120
205
|
dataforseo: {
|
|
121
206
|
mode: "api_key",
|
|
122
207
|
label: "DataForSEO",
|
|
@@ -223,947 +308,14 @@ export const CAPABILITIES = {
|
|
|
223
308
|
settings: [],
|
|
224
309
|
optionalSettings: [],
|
|
225
310
|
},
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
clientSecret: "LINKEDIN_CLIENT_SECRET",
|
|
235
|
-
},
|
|
236
|
-
token: [
|
|
237
|
-
{
|
|
238
|
-
host: "www.linkedin.com",
|
|
239
|
-
path: "/oauth/v2/accessToken",
|
|
240
|
-
methods: ["POST"],
|
|
241
|
-
inject: "form",
|
|
242
|
-
},
|
|
243
|
-
],
|
|
244
|
-
access: ["write"],
|
|
245
|
-
console: "https://www.linkedin.com/developers/apps",
|
|
246
|
-
authorize: {
|
|
247
|
-
url: "https://www.linkedin.com/oauth/v2/authorization",
|
|
248
|
-
scopes: [
|
|
249
|
-
"openid",
|
|
250
|
-
"profile",
|
|
251
|
-
"w_member_social",
|
|
252
|
-
"r_basicprofile",
|
|
253
|
-
"rw_organization_admin",
|
|
254
|
-
"w_organization_social",
|
|
255
|
-
"r_organization_social",
|
|
256
|
-
],
|
|
257
|
-
},
|
|
258
|
-
api: ["api.linkedin.com"],
|
|
259
|
-
// Profiles and pages: both callbacks complete on the one client.
|
|
260
|
-
redirectPaths: [
|
|
261
|
-
"/integrations/social/linkedin",
|
|
262
|
-
"/integrations/social/linkedin-page",
|
|
263
|
-
],
|
|
264
|
-
},
|
|
265
|
-
facebook: {
|
|
266
|
-
mode: "oauth_client",
|
|
267
|
-
label: "Facebook",
|
|
268
|
-
secrets: {
|
|
269
|
-
clientId: "FACEBOOK_APP_ID",
|
|
270
|
-
clientSecret: "FACEBOOK_APP_SECRET",
|
|
271
|
-
},
|
|
272
|
-
token: [
|
|
273
|
-
{
|
|
274
|
-
// The code exchange and the long-lived token exchange, both on a
|
|
275
|
-
// versioned path (/v25.0/oauth/access_token).
|
|
276
|
-
host: "graph.facebook.com",
|
|
277
|
-
path: /^\/v\d+\.\d+\/oauth\/access_token$/,
|
|
278
|
-
methods: ["GET", "POST"],
|
|
279
|
-
inject: "query",
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
|
-
access: ["write"],
|
|
283
|
-
console: "https://developers.facebook.com/apps",
|
|
284
|
-
authorize: {
|
|
285
|
-
url: "https://www.facebook.com/v25.0/dialog/oauth",
|
|
286
|
-
// Facebook pages, and Instagram business accounts through them.
|
|
287
|
-
scopes: [
|
|
288
|
-
"pages_show_list",
|
|
289
|
-
"business_management",
|
|
290
|
-
"pages_manage_posts",
|
|
291
|
-
"pages_manage_engagement",
|
|
292
|
-
"pages_read_engagement",
|
|
293
|
-
"read_insights",
|
|
294
|
-
"instagram_basic",
|
|
295
|
-
"instagram_content_publish",
|
|
296
|
-
"instagram_manage_comments",
|
|
297
|
-
"instagram_manage_insights",
|
|
298
|
-
],
|
|
299
|
-
},
|
|
300
|
-
api: ["graph.facebook.com", "graph.instagram.com"],
|
|
301
|
-
redirectPaths: [
|
|
302
|
-
"/integrations/social/facebook",
|
|
303
|
-
"/integrations/social/instagram",
|
|
304
|
-
],
|
|
305
|
-
},
|
|
306
|
-
instagram: {
|
|
307
|
-
mode: "oauth_client",
|
|
308
|
-
label: "Instagram",
|
|
309
|
-
secrets: {
|
|
310
|
-
clientId: "INSTAGRAM_APP_ID",
|
|
311
|
-
clientSecret: "INSTAGRAM_APP_SECRET",
|
|
312
|
-
},
|
|
313
|
-
token: [
|
|
314
|
-
// The code exchange, then the long-lived token exchange.
|
|
315
|
-
{
|
|
316
|
-
host: "api.instagram.com",
|
|
317
|
-
path: "/oauth/access_token",
|
|
318
|
-
methods: ["POST"],
|
|
319
|
-
inject: "form",
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
host: "graph.instagram.com",
|
|
323
|
-
path: "/access_token",
|
|
324
|
-
methods: ["GET"],
|
|
325
|
-
inject: "query",
|
|
326
|
-
},
|
|
327
|
-
],
|
|
328
|
-
access: ["write"],
|
|
329
|
-
console: "https://developers.facebook.com/apps",
|
|
330
|
-
authorize: {
|
|
331
|
-
url: "https://www.instagram.com/oauth/authorize",
|
|
332
|
-
scopes: [
|
|
333
|
-
"instagram_business_basic",
|
|
334
|
-
"instagram_business_content_publish",
|
|
335
|
-
"instagram_business_manage_comments",
|
|
336
|
-
"instagram_business_manage_insights",
|
|
337
|
-
],
|
|
338
|
-
},
|
|
339
|
-
api: ["graph.instagram.com"],
|
|
340
|
-
redirectPaths: ["/integrations/social/instagram-standalone"],
|
|
341
|
-
},
|
|
342
|
-
threads: {
|
|
343
|
-
mode: "oauth_client",
|
|
344
|
-
label: "Threads",
|
|
345
|
-
secrets: { clientId: "THREADS_APP_ID", clientSecret: "THREADS_APP_SECRET" },
|
|
346
|
-
token: [
|
|
347
|
-
// The code exchange, then the long-lived token exchange.
|
|
348
|
-
{
|
|
349
|
-
host: "graph.threads.net",
|
|
350
|
-
path: "/oauth/access_token",
|
|
351
|
-
methods: ["GET", "POST"],
|
|
352
|
-
inject: "query",
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
host: "graph.threads.net",
|
|
356
|
-
path: "/access_token",
|
|
357
|
-
methods: ["GET"],
|
|
358
|
-
inject: "query",
|
|
359
|
-
},
|
|
360
|
-
],
|
|
361
|
-
access: ["write"],
|
|
362
|
-
console: "https://developers.facebook.com/apps",
|
|
363
|
-
authorize: {
|
|
364
|
-
url: "https://threads.net/oauth/authorize",
|
|
365
|
-
scopes: [
|
|
366
|
-
"threads_basic",
|
|
367
|
-
"threads_content_publish",
|
|
368
|
-
"threads_manage_replies",
|
|
369
|
-
"threads_manage_insights",
|
|
370
|
-
],
|
|
371
|
-
},
|
|
372
|
-
api: ["graph.threads.net"],
|
|
373
|
-
redirectPaths: ["/integrations/social/threads"],
|
|
374
|
-
},
|
|
375
|
-
youtube: {
|
|
376
|
-
mode: "oauth_client",
|
|
377
|
-
label: "YouTube",
|
|
378
|
-
secrets: {
|
|
379
|
-
clientId: "YOUTUBE_CLIENT_ID",
|
|
380
|
-
clientSecret: "YOUTUBE_CLIENT_SECRET",
|
|
381
|
-
},
|
|
382
|
-
token: [
|
|
383
|
-
{
|
|
384
|
-
host: "oauth2.googleapis.com",
|
|
385
|
-
path: "/token",
|
|
386
|
-
methods: ["POST"],
|
|
387
|
-
inject: "form",
|
|
388
|
-
},
|
|
389
|
-
],
|
|
390
|
-
access: ["write"],
|
|
391
|
-
console: "https://console.cloud.google.com/apis/credentials",
|
|
392
|
-
setup: "setup/google.md",
|
|
393
|
-
authorize: {
|
|
394
|
-
url: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
395
|
-
// YouTube channels, and Google Business Profiles on the same client.
|
|
396
|
-
scopes: [
|
|
397
|
-
"https://www.googleapis.com/auth/userinfo.profile",
|
|
398
|
-
"https://www.googleapis.com/auth/userinfo.email",
|
|
399
|
-
"https://www.googleapis.com/auth/youtube",
|
|
400
|
-
"https://www.googleapis.com/auth/youtube.force-ssl",
|
|
401
|
-
"https://www.googleapis.com/auth/youtube.readonly",
|
|
402
|
-
"https://www.googleapis.com/auth/youtube.upload",
|
|
403
|
-
"https://www.googleapis.com/auth/youtubepartner",
|
|
404
|
-
"https://www.googleapis.com/auth/yt-analytics.readonly",
|
|
405
|
-
"https://www.googleapis.com/auth/business.manage",
|
|
406
|
-
],
|
|
407
|
-
},
|
|
408
|
-
api: [
|
|
409
|
-
"www.googleapis.com",
|
|
410
|
-
"youtubeanalytics.googleapis.com",
|
|
411
|
-
"mybusinessaccountmanagement.googleapis.com",
|
|
412
|
-
"mybusinessbusinessinformation.googleapis.com",
|
|
413
|
-
"mybusiness.googleapis.com",
|
|
414
|
-
"businessprofileperformance.googleapis.com",
|
|
415
|
-
],
|
|
416
|
-
redirectPaths: ["/integrations/social/youtube", "/integrations/social/gmb"],
|
|
417
|
-
},
|
|
418
|
-
tiktok: {
|
|
419
|
-
mode: "oauth_client",
|
|
420
|
-
label: "TikTok",
|
|
421
|
-
secrets: {
|
|
422
|
-
clientId: "TIKTOK_CLIENT_ID",
|
|
423
|
-
clientSecret: "TIKTOK_CLIENT_SECRET",
|
|
424
|
-
},
|
|
425
|
-
token: [
|
|
426
|
-
{
|
|
427
|
-
host: "open.tiktokapis.com",
|
|
428
|
-
path: "/v2/oauth/token/",
|
|
429
|
-
methods: ["POST"],
|
|
430
|
-
inject: "form",
|
|
431
|
-
// TikTok calls the client id its client key.
|
|
432
|
-
params: { clientId: "client_key" },
|
|
433
|
-
},
|
|
434
|
-
],
|
|
435
|
-
access: ["write"],
|
|
436
|
-
console: "https://developers.tiktok.com/apps",
|
|
437
|
-
authorize: {
|
|
438
|
-
url: "https://www.tiktok.com/v2/auth/authorize/",
|
|
439
|
-
scopes: [
|
|
440
|
-
"video.list",
|
|
441
|
-
"user.info.basic",
|
|
442
|
-
"video.publish",
|
|
443
|
-
"video.upload",
|
|
444
|
-
"user.info.profile",
|
|
445
|
-
"user.info.stats",
|
|
446
|
-
],
|
|
447
|
-
},
|
|
448
|
-
api: ["open.tiktokapis.com"],
|
|
449
|
-
redirectPaths: ["/integrations/social/tiktok"],
|
|
450
|
-
},
|
|
451
|
-
"tiktok-business": {
|
|
452
|
-
mode: "oauth_client",
|
|
453
|
-
label: "TikTok Business",
|
|
454
|
-
secrets: {
|
|
455
|
-
clientId: "TIKTOK_BUSINESS_CLIENT_ID",
|
|
456
|
-
clientSecret: "TIKTOK_BUSINESS_CLIENT_SECRET",
|
|
457
|
-
},
|
|
458
|
-
token: [
|
|
459
|
-
{
|
|
460
|
-
host: "business-api.tiktok.com",
|
|
461
|
-
path: "/open_api/v1.3/tt_user/oauth2/token/",
|
|
462
|
-
methods: ["POST"],
|
|
463
|
-
inject: "json",
|
|
464
|
-
},
|
|
465
|
-
{
|
|
466
|
-
host: "business-api.tiktok.com",
|
|
467
|
-
path: "/open_api/v1.3/tt_user/oauth2/refresh_token/",
|
|
468
|
-
methods: ["POST"],
|
|
469
|
-
inject: "json",
|
|
470
|
-
},
|
|
471
|
-
],
|
|
472
|
-
access: ["write"],
|
|
473
|
-
console: "https://business-api.tiktok.com/portal/apps",
|
|
474
|
-
authorize: {
|
|
475
|
-
url: "https://www.tiktok.com/v2/auth/authorize/",
|
|
476
|
-
scopes: [
|
|
477
|
-
"user.info.basic",
|
|
478
|
-
"user.info.username",
|
|
479
|
-
"user.info.stats",
|
|
480
|
-
"video.list",
|
|
481
|
-
"video.upload",
|
|
482
|
-
"video.publish",
|
|
483
|
-
],
|
|
484
|
-
},
|
|
485
|
-
api: ["business-api.tiktok.com"],
|
|
486
|
-
// TikTok Business wants the trailing slash.
|
|
487
|
-
redirectPaths: ["/integrations/social/tiktok-business/"],
|
|
488
|
-
},
|
|
489
|
-
reddit: {
|
|
490
|
-
mode: "oauth_client",
|
|
491
|
-
label: "Reddit",
|
|
492
|
-
secrets: {
|
|
493
|
-
clientId: "REDDIT_CLIENT_ID",
|
|
494
|
-
clientSecret: "REDDIT_CLIENT_SECRET",
|
|
495
|
-
},
|
|
496
|
-
token: [
|
|
497
|
-
{
|
|
498
|
-
host: "www.reddit.com",
|
|
499
|
-
path: "/api/v1/access_token",
|
|
500
|
-
methods: ["POST"],
|
|
501
|
-
inject: "basic",
|
|
502
|
-
},
|
|
503
|
-
],
|
|
504
|
-
access: ["write"],
|
|
505
|
-
console: "https://www.reddit.com/prefs/apps",
|
|
506
|
-
authorize: {
|
|
507
|
-
url: "https://www.reddit.com/api/v1/authorize",
|
|
508
|
-
scopes: ["read", "identity", "submit", "flair"],
|
|
509
|
-
},
|
|
510
|
-
api: ["oauth.reddit.com"],
|
|
511
|
-
redirectPaths: ["/integrations/social/reddit"],
|
|
512
|
-
},
|
|
513
|
-
pinterest: {
|
|
514
|
-
mode: "oauth_client",
|
|
515
|
-
label: "Pinterest",
|
|
516
|
-
secrets: {
|
|
517
|
-
clientId: "PINTEREST_CLIENT_ID",
|
|
518
|
-
clientSecret: "PINTEREST_CLIENT_SECRET",
|
|
519
|
-
},
|
|
520
|
-
token: [
|
|
521
|
-
{
|
|
522
|
-
host: "api.pinterest.com",
|
|
523
|
-
path: "/v5/oauth/token",
|
|
524
|
-
methods: ["POST"],
|
|
525
|
-
inject: "basic",
|
|
526
|
-
},
|
|
527
|
-
],
|
|
528
|
-
access: ["write"],
|
|
529
|
-
console: "https://developers.pinterest.com/apps/",
|
|
530
|
-
authorize: {
|
|
531
|
-
url: "https://www.pinterest.com/oauth/",
|
|
532
|
-
scopes: [
|
|
533
|
-
"boards:read",
|
|
534
|
-
"boards:write",
|
|
535
|
-
"pins:read",
|
|
536
|
-
"pins:write",
|
|
537
|
-
"user_accounts:read",
|
|
538
|
-
],
|
|
539
|
-
},
|
|
540
|
-
api: ["api.pinterest.com"],
|
|
541
|
-
redirectPaths: ["/integrations/social/pinterest"],
|
|
542
|
-
},
|
|
543
|
-
discord: {
|
|
544
|
-
mode: "oauth_client",
|
|
545
|
-
label: "Discord",
|
|
546
|
-
secrets: {
|
|
547
|
-
clientId: "DISCORD_CLIENT_ID",
|
|
548
|
-
clientSecret: "DISCORD_CLIENT_SECRET",
|
|
549
|
-
},
|
|
550
|
-
token: [
|
|
551
|
-
{
|
|
552
|
-
host: "discord.com",
|
|
553
|
-
path: "/api/oauth2/token",
|
|
554
|
-
methods: ["POST"],
|
|
555
|
-
inject: "basic",
|
|
556
|
-
},
|
|
557
|
-
],
|
|
558
|
-
access: ["write"],
|
|
559
|
-
console: "https://discord.com/developers/applications",
|
|
560
|
-
// The server install; posting uses the bot's token, an app secret.
|
|
561
|
-
authorize: {
|
|
562
|
-
url: "https://discord.com/oauth2/authorize",
|
|
563
|
-
scopes: ["bot", "identify", "guilds"],
|
|
564
|
-
},
|
|
565
|
-
api: ["discord.com"],
|
|
566
|
-
redirectPaths: ["/integrations/social/discord"],
|
|
567
|
-
},
|
|
568
|
-
slack: {
|
|
569
|
-
mode: "oauth_client",
|
|
570
|
-
label: "Slack",
|
|
571
|
-
secrets: {
|
|
572
|
-
clientId: "SLACK_CLIENT_ID",
|
|
573
|
-
clientSecret: "SLACK_CLIENT_SECRET",
|
|
574
|
-
},
|
|
575
|
-
token: [
|
|
576
|
-
{
|
|
577
|
-
host: "slack.com",
|
|
578
|
-
path: "/api/oauth.v2.access",
|
|
579
|
-
methods: ["POST"],
|
|
580
|
-
inject: "form",
|
|
581
|
-
},
|
|
582
|
-
],
|
|
583
|
-
access: ["write"],
|
|
584
|
-
console: "https://api.slack.com/apps",
|
|
585
|
-
setup: "setup/slack.md",
|
|
586
|
-
authorize: {
|
|
587
|
-
url: "https://slack.com/oauth/v2/authorize",
|
|
588
|
-
// The sets' union: the bot scopes in set order, then the one user
|
|
589
|
-
// scope no bot set carries. Postiz lists `channels.read`,
|
|
590
|
-
// `users.read`, `messages.write`, `channels.join` and `files.write`
|
|
591
|
-
// (it posts to channels it joins and uploads images); Open CRM adds
|
|
592
|
-
// `users.email`, `channels.manage` and `user-channels.manage` (its
|
|
593
|
-
// team agents match members to Slack users by email, create and
|
|
594
|
-
// manage the channels they post to, open DMs, and the installing
|
|
595
|
-
// person invites the bot to private channels).
|
|
596
|
-
scopes: [
|
|
597
|
-
"channels:read",
|
|
598
|
-
"groups:read",
|
|
599
|
-
"users:read",
|
|
600
|
-
"users:read.email",
|
|
601
|
-
"chat:write",
|
|
602
|
-
"chat:write.public",
|
|
603
|
-
"chat:write.customize",
|
|
604
|
-
"channels:join",
|
|
605
|
-
"channels:manage",
|
|
606
|
-
"groups:write",
|
|
607
|
-
"channels:write.invites",
|
|
608
|
-
"groups:write.invites",
|
|
609
|
-
"im:write",
|
|
610
|
-
"files:write",
|
|
611
|
-
"channels:write",
|
|
612
|
-
],
|
|
613
|
-
},
|
|
614
|
-
scopePolicy: {
|
|
615
|
-
dialect: "slack",
|
|
616
|
-
// `oauth.v2.access` takes `code` with no `grant_type`; an app that
|
|
617
|
-
// opts into token rotation refreshes with `refresh_token`.
|
|
618
|
-
grantTypes: [null, "refresh_token"],
|
|
619
|
-
// Slack reads no `scope` on the token leg (the installation's scopes
|
|
620
|
-
// were fixed on the authorize URL), so the request goes as sent.
|
|
621
|
-
requestScope: "keep",
|
|
622
|
-
alwaysRequested: [],
|
|
623
|
-
tolerated: [],
|
|
624
|
-
deniedSegments: [],
|
|
625
|
-
// Slack authorizes by method name, not verb, and every method is one
|
|
626
|
-
// exact `/api/<method>` path on the host that also serves the token
|
|
627
|
-
// endpoint (which no set lists). A read set names its methods in
|
|
628
|
-
// `paths` (GET) and `posts` (POST) alike; a write set in `posts`
|
|
629
|
-
// only, with the one listing among them in `paths` too.
|
|
630
|
-
sets: {
|
|
631
|
-
"channels.read": {
|
|
632
|
-
label: "Channels (read-only)",
|
|
633
|
-
access: "read",
|
|
634
|
-
scopes: ["channels:read", "groups:read"],
|
|
635
|
-
routes: [
|
|
636
|
-
{
|
|
637
|
-
host: "slack.com",
|
|
638
|
-
paths: [
|
|
639
|
-
"/api/conversations.list",
|
|
640
|
-
"/api/conversations.info",
|
|
641
|
-
"/api/conversations.members",
|
|
642
|
-
"/api/users.conversations",
|
|
643
|
-
],
|
|
644
|
-
posts: [
|
|
645
|
-
/^\/api\/conversations\.(list|info|members)$/,
|
|
646
|
-
/^\/api\/users\.conversations$/,
|
|
647
|
-
],
|
|
648
|
-
},
|
|
649
|
-
],
|
|
650
|
-
},
|
|
651
|
-
"users.read": {
|
|
652
|
-
label: "Members (read-only)",
|
|
653
|
-
access: "read",
|
|
654
|
-
scopes: ["users:read"],
|
|
655
|
-
routes: [
|
|
656
|
-
{
|
|
657
|
-
host: "slack.com",
|
|
658
|
-
paths: [
|
|
659
|
-
"/api/users.list",
|
|
660
|
-
"/api/users.info",
|
|
661
|
-
"/api/users.getPresence",
|
|
662
|
-
"/api/bots.info",
|
|
663
|
-
],
|
|
664
|
-
posts: [
|
|
665
|
-
/^\/api\/users\.(list|info|getPresence)$/,
|
|
666
|
-
/^\/api\/bots\.info$/,
|
|
667
|
-
],
|
|
668
|
-
},
|
|
669
|
-
],
|
|
670
|
-
},
|
|
671
|
-
"users.email": {
|
|
672
|
-
// Slack grants it only beside `users:read`: declare both sets.
|
|
673
|
-
label: "Members' email addresses (read-only)",
|
|
674
|
-
access: "read",
|
|
675
|
-
scopes: ["users:read.email"],
|
|
676
|
-
routes: [
|
|
677
|
-
{
|
|
678
|
-
host: "slack.com",
|
|
679
|
-
paths: ["/api/users.lookupByEmail"],
|
|
680
|
-
posts: [/^\/api\/users\.lookupByEmail$/],
|
|
681
|
-
},
|
|
682
|
-
],
|
|
683
|
-
},
|
|
684
|
-
"messages.write": {
|
|
685
|
-
label: "Messages (post as the app)",
|
|
686
|
-
access: "write",
|
|
687
|
-
scopes: ["chat:write", "chat:write.public", "chat:write.customize"],
|
|
688
|
-
routes: [
|
|
689
|
-
{
|
|
690
|
-
host: "slack.com",
|
|
691
|
-
// The scheduled-messages listing is the one read among them.
|
|
692
|
-
paths: ["/api/chat.scheduledMessages.list"],
|
|
693
|
-
posts: [
|
|
694
|
-
/^\/api\/chat\.(postMessage|postEphemeral|update|delete|scheduleMessage|deleteScheduledMessage|scheduledMessages\.list)$/,
|
|
695
|
-
],
|
|
696
|
-
},
|
|
697
|
-
],
|
|
698
|
-
},
|
|
699
|
-
"channels.join": {
|
|
700
|
-
label: "Channels (join public ones)",
|
|
701
|
-
access: "write",
|
|
702
|
-
scopes: ["channels:join"],
|
|
703
|
-
routes: [
|
|
704
|
-
{
|
|
705
|
-
host: "slack.com",
|
|
706
|
-
paths: [],
|
|
707
|
-
posts: [/^\/api\/conversations\.join$/],
|
|
708
|
-
},
|
|
709
|
-
],
|
|
710
|
-
},
|
|
711
|
-
"channels.manage": {
|
|
712
|
-
label: "Channels (create, manage and invite to; open DMs)",
|
|
713
|
-
access: "write",
|
|
714
|
-
scopes: [
|
|
715
|
-
"channels:manage",
|
|
716
|
-
"groups:write",
|
|
717
|
-
"channels:write.invites",
|
|
718
|
-
"groups:write.invites",
|
|
719
|
-
"im:write",
|
|
720
|
-
],
|
|
721
|
-
routes: [
|
|
722
|
-
{
|
|
723
|
-
host: "slack.com",
|
|
724
|
-
paths: [],
|
|
725
|
-
posts: [
|
|
726
|
-
/^\/api\/conversations\.(create|rename|archive|unarchive|invite|open|kick|leave|setTopic|setPurpose|mark)$/,
|
|
727
|
-
],
|
|
728
|
-
},
|
|
729
|
-
],
|
|
730
|
-
},
|
|
731
|
-
"files.write": {
|
|
732
|
-
label: "Files (upload)",
|
|
733
|
-
access: "write",
|
|
734
|
-
scopes: ["files:write"],
|
|
735
|
-
routes: [
|
|
736
|
-
{
|
|
737
|
-
host: "slack.com",
|
|
738
|
-
paths: [],
|
|
739
|
-
// The external upload flow; the retired `files.upload` stays off.
|
|
740
|
-
posts: [
|
|
741
|
-
/^\/api\/files\.(getUploadURLExternal|completeUploadExternal)$/,
|
|
742
|
-
],
|
|
743
|
-
},
|
|
744
|
-
],
|
|
745
|
-
},
|
|
746
|
-
"user-channels.manage": {
|
|
747
|
-
label: "Channels (as the installing person: read, manage, invite to)",
|
|
748
|
-
access: "write",
|
|
749
|
-
scopes: [],
|
|
750
|
-
// Slack's `user_scope`: issued to the person's own token, which
|
|
751
|
-
// Open CRM uses to invite the bot to private channels.
|
|
752
|
-
userScopes: [
|
|
753
|
-
"channels:read",
|
|
754
|
-
"channels:write",
|
|
755
|
-
"groups:read",
|
|
756
|
-
"groups:write",
|
|
757
|
-
],
|
|
758
|
-
routes: [
|
|
759
|
-
{
|
|
760
|
-
host: "slack.com",
|
|
761
|
-
paths: [
|
|
762
|
-
"/api/conversations.list",
|
|
763
|
-
"/api/conversations.info",
|
|
764
|
-
"/api/conversations.members",
|
|
765
|
-
"/api/users.conversations",
|
|
766
|
-
],
|
|
767
|
-
posts: [
|
|
768
|
-
/^\/api\/conversations\.(list|info|members|create|rename|archive|unarchive|invite|kick|leave|setTopic|setPurpose|mark)$/,
|
|
769
|
-
/^\/api\/users\.conversations$/,
|
|
770
|
-
],
|
|
771
|
-
},
|
|
772
|
-
],
|
|
773
|
-
},
|
|
774
|
-
},
|
|
775
|
-
},
|
|
776
|
-
api: ["slack.com"],
|
|
777
|
-
redirectPaths: [
|
|
778
|
-
"/integrations/social/slack",
|
|
779
|
-
"/integrations/slack/callback",
|
|
780
|
-
],
|
|
781
|
-
},
|
|
782
|
-
mastodon: {
|
|
783
|
-
mode: "oauth_client",
|
|
784
|
-
label: "Mastodon",
|
|
785
|
-
secrets: {
|
|
786
|
-
clientId: "MASTODON_CLIENT_ID",
|
|
787
|
-
clientSecret: "MASTODON_CLIENT_SECRET",
|
|
788
|
-
},
|
|
789
|
-
// On the instance the operator registered the client on.
|
|
790
|
-
token: [{ path: "/oauth/token", methods: ["POST"], inject: "form" }],
|
|
791
|
-
hostSetting: "MASTODON_URL",
|
|
792
|
-
defaultOrigin: "https://mastodon.social",
|
|
793
|
-
access: ["write"],
|
|
794
|
-
console: "/settings/applications",
|
|
795
|
-
authorize: {
|
|
796
|
-
url: "/oauth/authorize",
|
|
797
|
-
scopes: ["write:statuses", "profile", "write:media"],
|
|
798
|
-
},
|
|
799
|
-
api: [],
|
|
800
|
-
redirectPaths: ["/integrations/social/mastodon"],
|
|
801
|
-
},
|
|
802
|
-
kick: {
|
|
803
|
-
mode: "oauth_client",
|
|
804
|
-
label: "Kick",
|
|
805
|
-
secrets: { clientId: "KICK_CLIENT_ID", clientSecret: "KICK_CLIENT_SECRET" },
|
|
806
|
-
token: [
|
|
807
|
-
{
|
|
808
|
-
host: "id.kick.com",
|
|
809
|
-
path: "/oauth/token",
|
|
810
|
-
methods: ["POST"],
|
|
811
|
-
inject: "form",
|
|
812
|
-
},
|
|
813
|
-
],
|
|
814
|
-
access: ["write"],
|
|
815
|
-
console: "https://kick.com/settings/developer",
|
|
816
|
-
authorize: {
|
|
817
|
-
url: "https://id.kick.com/oauth/authorize",
|
|
818
|
-
scopes: ["chat:write", "user:read", "channel:read"],
|
|
819
|
-
},
|
|
820
|
-
api: ["api.kick.com"],
|
|
821
|
-
redirectPaths: ["/integrations/social/kick"],
|
|
822
|
-
},
|
|
823
|
-
twitch: {
|
|
824
|
-
mode: "oauth_client",
|
|
825
|
-
label: "Twitch",
|
|
826
|
-
secrets: {
|
|
827
|
-
clientId: "TWITCH_CLIENT_ID",
|
|
828
|
-
clientSecret: "TWITCH_CLIENT_SECRET",
|
|
829
|
-
},
|
|
830
|
-
token: [
|
|
831
|
-
{
|
|
832
|
-
host: "id.twitch.tv",
|
|
833
|
-
path: "/oauth2/token",
|
|
834
|
-
methods: ["POST"],
|
|
835
|
-
inject: "form",
|
|
836
|
-
},
|
|
837
|
-
],
|
|
838
|
-
access: ["write"],
|
|
839
|
-
console: "https://dev.twitch.tv/console/apps",
|
|
840
|
-
authorize: {
|
|
841
|
-
url: "https://id.twitch.tv/oauth2/authorize",
|
|
842
|
-
scopes: [
|
|
843
|
-
"user:write:chat",
|
|
844
|
-
"user:read:chat",
|
|
845
|
-
"moderator:manage:announcements",
|
|
846
|
-
],
|
|
847
|
-
},
|
|
848
|
-
// Helix also wants the public client id, as Client-Id, on every call.
|
|
849
|
-
api: ["api.twitch.tv"],
|
|
850
|
-
redirectPaths: ["/integrations/social/twitch"],
|
|
851
|
-
},
|
|
852
|
-
tumblr: {
|
|
853
|
-
mode: "oauth_client",
|
|
854
|
-
label: "Tumblr",
|
|
855
|
-
secrets: {
|
|
856
|
-
clientId: "TUMBLR_CLIENT_ID",
|
|
857
|
-
clientSecret: "TUMBLR_CLIENT_SECRET",
|
|
858
|
-
},
|
|
859
|
-
token: [
|
|
860
|
-
{
|
|
861
|
-
host: "api.tumblr.com",
|
|
862
|
-
path: "/v2/oauth2/token",
|
|
863
|
-
methods: ["POST"],
|
|
864
|
-
inject: "form",
|
|
865
|
-
},
|
|
866
|
-
],
|
|
867
|
-
access: ["write"],
|
|
868
|
-
console: "https://www.tumblr.com/oauth/apps",
|
|
869
|
-
authorize: {
|
|
870
|
-
url: "https://www.tumblr.com/oauth2/authorize",
|
|
871
|
-
scopes: ["write", "offline_access"],
|
|
872
|
-
},
|
|
873
|
-
api: ["api.tumblr.com"],
|
|
874
|
-
redirectPaths: ["/integrations/social/tumblr"],
|
|
875
|
-
},
|
|
876
|
-
dribbble: {
|
|
877
|
-
mode: "oauth_client",
|
|
878
|
-
label: "Dribbble",
|
|
879
|
-
secrets: {
|
|
880
|
-
clientId: "DRIBBBLE_CLIENT_ID",
|
|
881
|
-
clientSecret: "DRIBBBLE_CLIENT_SECRET",
|
|
882
|
-
},
|
|
883
|
-
token: [
|
|
884
|
-
{
|
|
885
|
-
// A POST that takes the client in its query string.
|
|
886
|
-
host: "dribbble.com",
|
|
887
|
-
path: "/oauth/token",
|
|
888
|
-
methods: ["POST"],
|
|
889
|
-
inject: "query",
|
|
890
|
-
},
|
|
891
|
-
],
|
|
892
|
-
access: ["write"],
|
|
893
|
-
console: "https://dribbble.com/account/applications",
|
|
894
|
-
authorize: {
|
|
895
|
-
url: "https://dribbble.com/oauth/authorize",
|
|
896
|
-
scopes: ["public", "upload"],
|
|
897
|
-
},
|
|
898
|
-
api: ["api.dribbble.com"],
|
|
899
|
-
redirectPaths: ["/integrations/social/dribbble"],
|
|
900
|
-
},
|
|
901
|
-
// Clients several apps share with different needs, so each app lists the
|
|
902
|
-
// scope sets it uses: Open CRM reads a member's own mailbox and calendar
|
|
903
|
-
// (`gmail.read`, `calendar.read`; `identity`, `mail.read`), OpenSEO reads
|
|
904
|
-
// a site's Search Console and Analytics (`identity`, `search-console.read`,
|
|
905
|
-
// `analytics.read`). Every set is read-only. Google's is one Cloud project
|
|
906
|
-
// apart from YouTube's; Gmail's read scope is restricted and needs
|
|
907
|
-
// Google's verification, and whether Search Console and Analytics share
|
|
908
|
-
// that consent screen with it is the operator's choice when verifying.
|
|
909
|
-
"google-workspace": {
|
|
910
|
-
mode: "oauth_client",
|
|
911
|
-
label: "Google (Gmail, Calendar, Search Console and Analytics)",
|
|
912
|
-
secrets: {
|
|
913
|
-
clientId: "GOOGLE_WORKSPACE_CLIENT_ID",
|
|
914
|
-
clientSecret: "GOOGLE_WORKSPACE_CLIENT_SECRET",
|
|
915
|
-
},
|
|
916
|
-
token: [
|
|
917
|
-
{
|
|
918
|
-
host: "oauth2.googleapis.com",
|
|
919
|
-
path: "/token",
|
|
920
|
-
methods: ["POST"],
|
|
921
|
-
inject: "form",
|
|
922
|
-
},
|
|
923
|
-
],
|
|
924
|
-
access: ["write"],
|
|
925
|
-
console: "https://console.cloud.google.com/apis/credentials",
|
|
926
|
-
setup: "setup/google.md",
|
|
927
|
-
authorize: {
|
|
928
|
-
url: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
929
|
-
scopes: [
|
|
930
|
-
"openid",
|
|
931
|
-
"email",
|
|
932
|
-
"profile",
|
|
933
|
-
"https://www.googleapis.com/auth/gmail.readonly",
|
|
934
|
-
"https://www.googleapis.com/auth/calendar.readonly",
|
|
935
|
-
"https://www.googleapis.com/auth/webmasters.readonly",
|
|
936
|
-
"https://www.googleapis.com/auth/analytics.readonly",
|
|
937
|
-
],
|
|
938
|
-
},
|
|
939
|
-
scopePolicy: {
|
|
940
|
-
dialect: "google",
|
|
941
|
-
// Google documents no scope on either token leg: the response's
|
|
942
|
-
// `scope` is where the gateway learns what a token holds.
|
|
943
|
-
grantTypes: ["authorization_code", "refresh_token"],
|
|
944
|
-
requestScope: "drop",
|
|
945
|
-
alwaysRequested: [],
|
|
946
|
-
// Both spellings: Google documents `email`/`profile` and the userinfo
|
|
947
|
-
// URLs as the same grants, and echoes the URLs.
|
|
948
|
-
tolerated: [
|
|
949
|
-
"openid",
|
|
950
|
-
"email",
|
|
951
|
-
"profile",
|
|
952
|
-
"https://www.googleapis.com/auth/userinfo.email",
|
|
953
|
-
"https://www.googleapis.com/auth/userinfo.profile",
|
|
954
|
-
],
|
|
955
|
-
// Gmail's and Calendar's `/batch/<api>/<version>` carry whole requests
|
|
956
|
-
// inside; Analytics' measurement protocol secrets are readable under
|
|
957
|
-
// the read-only scope.
|
|
958
|
-
deniedSegments: ["batch", "measurementProtocolSecrets"],
|
|
959
|
-
sets: {
|
|
960
|
-
identity: {
|
|
961
|
-
label: "Google account (who you are)",
|
|
962
|
-
access: "read",
|
|
963
|
-
scopes: ["openid", "email", "profile"],
|
|
964
|
-
routes: [
|
|
965
|
-
{ host: "openidconnect.googleapis.com", paths: ["/v1/userinfo"] },
|
|
966
|
-
{
|
|
967
|
-
host: "www.googleapis.com",
|
|
968
|
-
paths: [
|
|
969
|
-
"/oauth2/v2/userinfo",
|
|
970
|
-
"/oauth2/v3/userinfo",
|
|
971
|
-
"/userinfo/v2/me",
|
|
972
|
-
],
|
|
973
|
-
},
|
|
974
|
-
],
|
|
975
|
-
},
|
|
976
|
-
"gmail.read": {
|
|
977
|
-
label: "Gmail (read-only)",
|
|
978
|
-
access: "read",
|
|
979
|
-
scopes: ["https://www.googleapis.com/auth/gmail.readonly"],
|
|
980
|
-
routes: [
|
|
981
|
-
{
|
|
982
|
-
host: "gmail.googleapis.com",
|
|
983
|
-
paths: ["/gmail/v1/users/"],
|
|
984
|
-
// Push notifications start and stop; `messages/send`, drafts
|
|
985
|
-
// and `modify` are the other POSTs under the same prefix.
|
|
986
|
-
posts: [/^\/gmail\/v1\/users\/[^/]+\/(watch|stop)$/],
|
|
987
|
-
},
|
|
988
|
-
],
|
|
989
|
-
},
|
|
990
|
-
"calendar.read": {
|
|
991
|
-
label: "Google Calendar (read-only)",
|
|
992
|
-
access: "read",
|
|
993
|
-
scopes: ["https://www.googleapis.com/auth/calendar.readonly"],
|
|
994
|
-
routes: [
|
|
995
|
-
{
|
|
996
|
-
host: "www.googleapis.com",
|
|
997
|
-
paths: ["/calendar/v3/"],
|
|
998
|
-
posts: [
|
|
999
|
-
/^\/calendar\/v3\/freeBusy$/,
|
|
1000
|
-
/^\/calendar\/v3\/calendars\/[^/]+\/events\/watch$/,
|
|
1001
|
-
/^\/calendar\/v3\/users\/me\/calendarList\/watch$/,
|
|
1002
|
-
/^\/calendar\/v3\/channels\/stop$/,
|
|
1003
|
-
],
|
|
1004
|
-
},
|
|
1005
|
-
],
|
|
1006
|
-
},
|
|
1007
|
-
"search-console.read": {
|
|
1008
|
-
label: "Search Console (read-only)",
|
|
1009
|
-
access: "read",
|
|
1010
|
-
scopes: ["https://www.googleapis.com/auth/webmasters.readonly"],
|
|
1011
|
-
// The v3 paths answer on both hosts; URL inspection is v1 only.
|
|
1012
|
-
// A site is one segment, `https%3A%2F%2Fexample.com%2F` or
|
|
1013
|
-
// `sc-domain%3Aexample.com`, which is why routes match the raw path.
|
|
1014
|
-
routes: [
|
|
1015
|
-
{
|
|
1016
|
-
host: "www.googleapis.com",
|
|
1017
|
-
paths: ["/webmasters/v3/"],
|
|
1018
|
-
posts: [
|
|
1019
|
-
/^\/webmasters\/v3\/sites\/[^/]+\/searchAnalytics\/query$/,
|
|
1020
|
-
],
|
|
1021
|
-
},
|
|
1022
|
-
{
|
|
1023
|
-
host: "searchconsole.googleapis.com",
|
|
1024
|
-
paths: ["/webmasters/v3/"],
|
|
1025
|
-
posts: [
|
|
1026
|
-
/^\/webmasters\/v3\/sites\/[^/]+\/searchAnalytics\/query$/,
|
|
1027
|
-
/^\/v1\/urlInspection\/index:inspect$/,
|
|
1028
|
-
],
|
|
1029
|
-
},
|
|
1030
|
-
],
|
|
1031
|
-
},
|
|
1032
|
-
"analytics.read": {
|
|
1033
|
-
label: "Google Analytics (read-only)",
|
|
1034
|
-
access: "read",
|
|
1035
|
-
scopes: ["https://www.googleapis.com/auth/analytics.readonly"],
|
|
1036
|
-
routes: [
|
|
1037
|
-
{
|
|
1038
|
-
// Reports are POSTed queries.
|
|
1039
|
-
host: "analyticsdata.googleapis.com",
|
|
1040
|
-
paths: ["/v1beta/properties/"],
|
|
1041
|
-
posts: [
|
|
1042
|
-
/^\/v1beta\/properties\/[^/]+:(runReport|batchRunReports|runPivotReport|batchRunPivotReports|runRealtimeReport|checkCompatibility)$/,
|
|
1043
|
-
],
|
|
1044
|
-
},
|
|
1045
|
-
{
|
|
1046
|
-
// Reads only: accounts, properties and what hangs off them
|
|
1047
|
-
// (data streams, key events, enhanced measurement settings).
|
|
1048
|
-
host: "analyticsadmin.googleapis.com",
|
|
1049
|
-
paths: [
|
|
1050
|
-
"/v1beta/accountSummaries",
|
|
1051
|
-
"/v1beta/accounts",
|
|
1052
|
-
"/v1beta/accounts/",
|
|
1053
|
-
"/v1beta/properties",
|
|
1054
|
-
"/v1beta/properties/",
|
|
1055
|
-
"/v1alpha/properties/",
|
|
1056
|
-
],
|
|
1057
|
-
},
|
|
1058
|
-
],
|
|
1059
|
-
},
|
|
1060
|
-
},
|
|
1061
|
-
},
|
|
1062
|
-
api: [
|
|
1063
|
-
"gmail.googleapis.com",
|
|
1064
|
-
"www.googleapis.com",
|
|
1065
|
-
"searchconsole.googleapis.com",
|
|
1066
|
-
"analyticsdata.googleapis.com",
|
|
1067
|
-
"analyticsadmin.googleapis.com",
|
|
1068
|
-
"openidconnect.googleapis.com",
|
|
1069
|
-
],
|
|
1070
|
-
redirectPaths: ["/integrations/google/callback"],
|
|
1071
|
-
},
|
|
1072
|
-
"microsoft-365": {
|
|
1073
|
-
mode: "oauth_client",
|
|
1074
|
-
label: "Microsoft 365 (Outlook mail and calendar)",
|
|
1075
|
-
secrets: {
|
|
1076
|
-
clientId: "MICROSOFT_365_CLIENT_ID",
|
|
1077
|
-
clientSecret: "MICROSOFT_365_CLIENT_SECRET",
|
|
1078
|
-
},
|
|
1079
|
-
token: [
|
|
1080
|
-
{
|
|
1081
|
-
// The multi-tenant endpoint: register the client for accounts in
|
|
1082
|
-
// any organization and personal accounts. A single-tenant client
|
|
1083
|
-
// answers only on its tenant's path, which this does not complete.
|
|
1084
|
-
host: "login.microsoftonline.com",
|
|
1085
|
-
path: "/common/oauth2/v2.0/token",
|
|
1086
|
-
methods: ["POST"],
|
|
1087
|
-
inject: "form",
|
|
1088
|
-
},
|
|
1089
|
-
],
|
|
1090
|
-
access: ["write"],
|
|
1091
|
-
console: "https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
|
|
1092
|
-
setup: "setup/microsoft-365.md",
|
|
1093
|
-
authorize: {
|
|
1094
|
-
url: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
|
1095
|
-
scopes: ["offline_access", "User.Read", "Mail.Read", "Calendars.Read"],
|
|
1096
|
-
},
|
|
1097
|
-
scopePolicy: {
|
|
1098
|
-
dialect: "microsoft",
|
|
1099
|
-
grantTypes: ["authorization_code", "refresh_token"],
|
|
1100
|
-
// The token leg's `scope` is Microsoft's extension for naming the
|
|
1101
|
-
// resource; the gateway sets it to the app's list so a token covers
|
|
1102
|
-
// Graph alone.
|
|
1103
|
-
requestScope: "replace",
|
|
1104
|
-
// Refresh tokens; Entra never echoes it in a response's `scope`.
|
|
1105
|
-
alwaysRequested: ["offline_access"],
|
|
1106
|
-
// Compared case-insensitively, as Microsoft spells them both ways.
|
|
1107
|
-
tolerated: ["openid", "profile", "email", "offline_access", "User.Read"],
|
|
1108
|
-
// `$batch` carries whole requests inside; `beta` is another API.
|
|
1109
|
-
deniedSegments: ["$batch", "beta"],
|
|
1110
|
-
// `/v1.0/me/…` only: `/v1.0/users/<id>/…` reaches shared mailboxes
|
|
1111
|
-
// and calendars under the same permissions.
|
|
1112
|
-
sets: {
|
|
1113
|
-
identity: {
|
|
1114
|
-
label: "Microsoft account (who you are)",
|
|
1115
|
-
access: "read",
|
|
1116
|
-
scopes: ["User.Read"],
|
|
1117
|
-
routes: [
|
|
1118
|
-
{
|
|
1119
|
-
host: "graph.microsoft.com",
|
|
1120
|
-
paths: ["/v1.0/me", "/oidc/userinfo"],
|
|
1121
|
-
},
|
|
1122
|
-
],
|
|
1123
|
-
},
|
|
1124
|
-
"mail.read": {
|
|
1125
|
-
label: "Outlook mail (read-only)",
|
|
1126
|
-
access: "read",
|
|
1127
|
-
scopes: ["Mail.Read"],
|
|
1128
|
-
routes: [
|
|
1129
|
-
{
|
|
1130
|
-
host: "graph.microsoft.com",
|
|
1131
|
-
paths: [
|
|
1132
|
-
"/v1.0/me/messages",
|
|
1133
|
-
"/v1.0/me/messages/",
|
|
1134
|
-
"/v1.0/me/mailFolders",
|
|
1135
|
-
"/v1.0/me/mailFolders/",
|
|
1136
|
-
],
|
|
1137
|
-
},
|
|
1138
|
-
],
|
|
1139
|
-
},
|
|
1140
|
-
"calendar.read": {
|
|
1141
|
-
label: "Outlook calendar (read-only)",
|
|
1142
|
-
access: "read",
|
|
1143
|
-
scopes: ["Calendars.Read"],
|
|
1144
|
-
routes: [
|
|
1145
|
-
{
|
|
1146
|
-
host: "graph.microsoft.com",
|
|
1147
|
-
paths: [
|
|
1148
|
-
"/v1.0/me/calendar",
|
|
1149
|
-
"/v1.0/me/calendar/",
|
|
1150
|
-
"/v1.0/me/calendars",
|
|
1151
|
-
"/v1.0/me/calendars/",
|
|
1152
|
-
"/v1.0/me/events",
|
|
1153
|
-
"/v1.0/me/events/",
|
|
1154
|
-
"/v1.0/me/calendarView",
|
|
1155
|
-
"/v1.0/me/calendarView/",
|
|
1156
|
-
],
|
|
1157
|
-
// Free/busy is a POSTed query.
|
|
1158
|
-
posts: [/^\/v1\.0\/me\/calendar\/getSchedule$/],
|
|
1159
|
-
},
|
|
1160
|
-
],
|
|
1161
|
-
},
|
|
1162
|
-
},
|
|
1163
|
-
},
|
|
1164
|
-
api: ["graph.microsoft.com"],
|
|
1165
|
-
redirectPaths: ["/integrations/microsoft/callback"],
|
|
1166
|
-
},
|
|
311
|
+
// The OAuth providers every Gateway has built in, from their JSON specs
|
|
312
|
+
// (./oauthBuiltIns.ts): what differs between them is data there.
|
|
313
|
+
slack: builtInOAuth(BUILT_IN_OAUTH_SPECS.slack, "setup/slack.md"),
|
|
314
|
+
"google-workspace": builtInOAuth(BUILT_IN_OAUTH_SPECS["google-workspace"], "setup/google.md"),
|
|
315
|
+
"microsoft-365": builtInOAuth(BUILT_IN_OAUTH_SPECS["microsoft-365"], "setup/microsoft-365.md"),
|
|
316
|
+
// The first fifty beyond the entries above (catalog/*.ts, PR 3 of the
|
|
317
|
+
// provider registry), after them so the derived lists keep their order.
|
|
318
|
+
...CATALOG_ENTRIES,
|
|
1167
319
|
};
|
|
1168
320
|
/** The Gateway settings an API-key or AI provider's `auth` names: the key first, then a Basic username held as a setting. */
|
|
1169
321
|
export function authSettingNames(auth) {
|
|
@@ -1184,7 +336,12 @@ export function capabilitySettings(capability) {
|
|
|
1184
336
|
...(capability.hostSetting ? [capability.hostSetting] : []),
|
|
1185
337
|
];
|
|
1186
338
|
case "ai":
|
|
1187
|
-
|
|
339
|
+
// Every setting `auth` names, as for an API-key provider: a Basic
|
|
340
|
+
// username held as a setting is checked, bound and listed like the key.
|
|
341
|
+
return [
|
|
342
|
+
...authSettingNames(capability.auth),
|
|
343
|
+
...(capability.alsoNeeds ?? []),
|
|
344
|
+
];
|
|
1188
345
|
case "service":
|
|
1189
346
|
return [...capability.settings, ...capability.optionalSettings];
|
|
1190
347
|
case "oauth_client":
|
|
@@ -1193,7 +350,6 @@ export function capabilitySettings(capability) {
|
|
|
1193
350
|
...(capability.secrets.clientSecret
|
|
1194
351
|
? [capability.secrets.clientSecret]
|
|
1195
352
|
: []),
|
|
1196
|
-
...(capability.hostSetting ? [capability.hostSetting] : []),
|
|
1197
353
|
];
|
|
1198
354
|
}
|
|
1199
355
|
}
|
|
@@ -1218,7 +374,13 @@ export function capabilityIdsOf(lookup) {
|
|
|
1218
374
|
* to encode, or taken as already encoded when it holds no colon.
|
|
1219
375
|
*/
|
|
1220
376
|
export function authorizationFor(capability, settings) {
|
|
1221
|
-
|
|
377
|
+
// Only the settings the entry declares are visible: whatever `auth` reads
|
|
378
|
+
// beyond them (a setting no row check saw) is unset, and the call fails closed.
|
|
379
|
+
const declared = {};
|
|
380
|
+
for (const name of capabilitySettings(capability)) {
|
|
381
|
+
declared[name] = settings[name];
|
|
382
|
+
}
|
|
383
|
+
return authorizationOf(capability.auth, declared);
|
|
1222
384
|
}
|
|
1223
385
|
/** {@link authorizationFor} for an `auth` block alone (the SDK's AI fallback holds the block, not the entry). */
|
|
1224
386
|
export function authorizationOf(auth, settings) {
|
|
@@ -1313,10 +475,9 @@ export function serviceRoutePath(id, route) {
|
|
|
1313
475
|
/**
|
|
1314
476
|
* The capability call an AppGateway path names, or null when the path names
|
|
1315
477
|
* an unknown capability (or an AI provider, which has its own route), a
|
|
1316
|
-
* host outside the provider's, a path that could leave that host, a
|
|
1317
|
-
*
|
|
1318
|
-
*
|
|
1319
|
-
* `hostSetting` (Mastodon's instance); `lookup` is where the id is found,
|
|
478
|
+
* host outside the provider's, a path that could leave that host, or a
|
|
479
|
+
* route a service capability doesn't declare. `settings` resolves an API-key provider's
|
|
480
|
+
* `hostSetting` (a Shopify store's host); `lookup` is where the id is found,
|
|
1320
481
|
* the catalog alone by default.
|
|
1321
482
|
*/
|
|
1322
483
|
export function parseIntegrationProxyPath(pathname, search, settings, lookup = CATALOG) {
|
|
@@ -1340,13 +501,12 @@ export function parseIntegrationProxyPath(pathname, search, settings, lookup = C
|
|
|
1340
501
|
};
|
|
1341
502
|
}
|
|
1342
503
|
if (capability.mode === "oauth_client") {
|
|
1343
|
-
const upstream =
|
|
504
|
+
const upstream = capability.api.includes(host)
|
|
1344
505
|
? httpsUpstream(host, path ?? "/", search)
|
|
1345
506
|
: null;
|
|
1346
|
-
|
|
1347
|
-
if (!upstream || !endpoint)
|
|
507
|
+
if (!upstream)
|
|
1348
508
|
return null;
|
|
1349
|
-
return { kind: "oauth_client", id, capability, upstream
|
|
509
|
+
return { kind: "oauth_client", id, capability, upstream };
|
|
1350
510
|
}
|
|
1351
511
|
if (capability.mode !== "api_key")
|
|
1352
512
|
return null;
|
|
@@ -1367,81 +527,9 @@ function httpsUpstream(host, path, search) {
|
|
|
1367
527
|
return null;
|
|
1368
528
|
return upstream;
|
|
1369
529
|
}
|
|
1370
|
-
/**
|
|
1371
|
-
* The origin an OAuth client's `hostSetting` names (its default when the
|
|
1372
|
-
* setting is unset), or null for a client with a fixed host or a setting
|
|
1373
|
-
* that isn't an https origin. A bare host is taken as https.
|
|
1374
|
-
*/
|
|
1375
|
-
export function oauthClientOrigin(capability, settings) {
|
|
1376
|
-
if (!capability.hostSetting)
|
|
1377
|
-
return null;
|
|
1378
|
-
const set = settings?.[capability.hostSetting];
|
|
1379
|
-
const value = typeof set === "string" && set.trim()
|
|
1380
|
-
? set.trim()
|
|
1381
|
-
: capability.defaultOrigin;
|
|
1382
|
-
if (!value)
|
|
1383
|
-
return null;
|
|
1384
|
-
let url;
|
|
1385
|
-
try {
|
|
1386
|
-
url = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`);
|
|
1387
|
-
}
|
|
1388
|
-
catch {
|
|
1389
|
-
return null;
|
|
1390
|
-
}
|
|
1391
|
-
// An origin and nothing else: the paths are the provider's own.
|
|
1392
|
-
const bare = url.pathname === "/" &&
|
|
1393
|
-
!url.search &&
|
|
1394
|
-
!url.hash &&
|
|
1395
|
-
!url.username &&
|
|
1396
|
-
!url.password;
|
|
1397
|
-
return url.protocol === "https:" && bare ? url.origin : null;
|
|
1398
|
-
}
|
|
1399
|
-
/** The host an OAuth client's `hostSetting` names, or null. */
|
|
1400
|
-
function settingHost(capability, settings) {
|
|
1401
|
-
const origin = oauthClientOrigin(capability, settings);
|
|
1402
|
-
return origin ? new URL(origin).host : null;
|
|
1403
|
-
}
|
|
1404
|
-
/** The hosts an OAuth client's token endpoints are on. */
|
|
1405
|
-
function oauthClientHosts(capability, settings) {
|
|
1406
|
-
const fromSetting = settingHost(capability, settings);
|
|
1407
|
-
return [
|
|
1408
|
-
...new Set(capability.token.flatMap(({ host }) => {
|
|
1409
|
-
const resolved = host ?? fromSetting;
|
|
1410
|
-
return resolved ? [resolved] : [];
|
|
1411
|
-
})),
|
|
1412
|
-
];
|
|
1413
|
-
}
|
|
1414
|
-
/**
|
|
1415
|
-
* The token endpoint `upstream` is, by its exact host and path (a RegExp
|
|
1416
|
-
* for a versioned one), or null. Its method is the caller's to check.
|
|
1417
|
-
*/
|
|
1418
|
-
export function tokenEndpointFor(capability, upstream, settings) {
|
|
1419
|
-
if (upstream.protocol !== "https:")
|
|
1420
|
-
return null;
|
|
1421
|
-
const fromSetting = settingHost(capability, settings);
|
|
1422
|
-
return (capability.token.find(({ host, path }) => (host ?? fromSetting) === upstream.host &&
|
|
1423
|
-
(typeof path === "string"
|
|
1424
|
-
? path === upstream.pathname
|
|
1425
|
-
: path.test(upstream.pathname))) ?? null);
|
|
1426
|
-
}
|
|
1427
|
-
/** An OAuth client's token endpoints, as `POST https://host/path` lines for messages. */
|
|
1428
|
-
export function describeTokenEndpoints(capability) {
|
|
1429
|
-
return capability.token
|
|
1430
|
-
.map(({ host, path, methods }) => `${methods.join("/")} https://${host ?? `<${capability.hostSetting}>`}${tokenPathText(path)}`)
|
|
1431
|
-
.join(", ");
|
|
1432
|
-
}
|
|
1433
|
-
/** A token endpoint's path as people read it: a versioned one as `/v<version>/oauth/access_token`. */
|
|
1434
|
-
export function tokenPathText(path) {
|
|
1435
|
-
if (typeof path === "string")
|
|
1436
|
-
return path;
|
|
1437
|
-
return path.source
|
|
1438
|
-
.replace(/^\^|\$$/g, "")
|
|
1439
|
-
.replace(/v\\d\+\\\.\\d\+/g, "v<version>")
|
|
1440
|
-
.replace(/\\(.)/g, "$1");
|
|
1441
|
-
}
|
|
1442
530
|
/**
|
|
1443
531
|
* The command that gives the gateway `capability`'s client:
|
|
1444
|
-
* `pnpm everyapp secret set <ID> <SECRET
|
|
532
|
+
* `pnpm everyapp secret set <ID> <SECRET>`.
|
|
1445
533
|
*/
|
|
1446
534
|
export function oauthClientSecretSetCommand(capability) {
|
|
1447
535
|
return `pnpm everyapp secret set ${capabilitySettings(capability).join(" ")}`;
|