@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.
Files changed (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -64
  3. package/dist/cloudflare/catalogSettings.generated.d.ts +84 -0
  4. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
  5. package/dist/cloudflare/catalogSettings.generated.js +175 -0
  6. package/dist/cloudflare/index.d.ts +3 -2
  7. package/dist/cloudflare/index.d.ts.map +1 -1
  8. package/dist/cloudflare/index.js +4 -1
  9. package/dist/cloudflare/sentinel.d.ts +60 -2
  10. package/dist/cloudflare/sentinel.d.ts.map +1 -1
  11. package/dist/cloudflare/sentinel.js +37 -2
  12. package/dist/cloudflare/worker.d.ts +391 -63
  13. package/dist/cloudflare/worker.d.ts.map +1 -1
  14. package/dist/cloudflare/worker.js +394 -108
  15. package/dist/dev/index.d.ts +1 -1
  16. package/dist/dev/index.d.ts.map +1 -1
  17. package/dist/dev/index.js +1 -1
  18. package/dist/dev/keypair.d.ts +2 -0
  19. package/dist/dev/keypair.d.ts.map +1 -1
  20. package/dist/dev/keypair.js +2 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +5 -0
  24. package/dist/internal/aiDefault.d.ts +205 -0
  25. package/dist/internal/aiDefault.d.ts.map +1 -0
  26. package/dist/internal/aiDefault.js +335 -0
  27. package/dist/internal/aiProviders.d.ts +7 -2
  28. package/dist/internal/aiProviders.d.ts.map +1 -1
  29. package/dist/internal/aiProviders.js +19 -6
  30. package/dist/internal/appSettings.d.ts +82 -4
  31. package/dist/internal/appSettings.d.ts.map +1 -1
  32. package/dist/internal/appSettings.js +204 -29
  33. package/dist/internal/audit.d.ts +108 -0
  34. package/dist/internal/audit.d.ts.map +1 -0
  35. package/dist/internal/audit.js +235 -0
  36. package/dist/internal/capabilities.d.ts +2261 -815
  37. package/dist/internal/capabilities.d.ts.map +1 -1
  38. package/dist/internal/capabilities.js +122 -1034
  39. package/dist/internal/catalog/commerce.d.ts +394 -0
  40. package/dist/internal/catalog/commerce.d.ts.map +1 -0
  41. package/dist/internal/catalog/commerce.js +477 -0
  42. package/dist/internal/catalog/comms.d.ts +375 -0
  43. package/dist/internal/catalog/comms.d.ts.map +1 -0
  44. package/dist/internal/catalog/comms.js +520 -0
  45. package/dist/internal/catalog/index.d.ts +2008 -0
  46. package/dist/internal/catalog/index.d.ts.map +1 -0
  47. package/dist/internal/catalog/index.js +19 -0
  48. package/dist/internal/catalog/inference.d.ts +393 -0
  49. package/dist/internal/catalog/inference.d.ts.map +1 -0
  50. package/dist/internal/catalog/inference.js +460 -0
  51. package/dist/internal/catalog/infra.d.ts +458 -0
  52. package/dist/internal/catalog/infra.d.ts.map +1 -0
  53. package/dist/internal/catalog/infra.js +608 -0
  54. package/dist/internal/catalog/search.d.ts +400 -0
  55. package/dist/internal/catalog/search.d.ts.map +1 -0
  56. package/dist/internal/catalog/search.js +463 -0
  57. package/dist/internal/devProviders.d.ts +2 -2
  58. package/dist/internal/devProviders.d.ts.map +1 -1
  59. package/dist/internal/devProviders.js +19 -1
  60. package/dist/internal/events.d.ts +65 -3
  61. package/dist/internal/events.d.ts.map +1 -1
  62. package/dist/internal/events.js +110 -5
  63. package/dist/internal/gatewaySettings.d.ts +3 -2
  64. package/dist/internal/gatewaySettings.d.ts.map +1 -1
  65. package/dist/internal/gatewaySettings.js +12 -6
  66. package/dist/internal/identity.d.ts +41 -4
  67. package/dist/internal/identity.d.ts.map +1 -1
  68. package/dist/internal/identity.js +48 -10
  69. package/dist/internal/index.d.ts +6 -1
  70. package/dist/internal/index.d.ts.map +1 -1
  71. package/dist/internal/index.js +6 -1
  72. package/dist/internal/members.d.ts +3 -2
  73. package/dist/internal/members.d.ts.map +1 -1
  74. package/dist/internal/oauthBuiltIns.d.ts +101 -0
  75. package/dist/internal/oauthBuiltIns.d.ts.map +1 -0
  76. package/dist/internal/oauthBuiltIns.js +122 -0
  77. package/dist/internal/oauthClient.d.ts +8 -50
  78. package/dist/internal/oauthClient.d.ts.map +1 -1
  79. package/dist/internal/oauthClient.js +2 -177
  80. package/dist/internal/oauthPresets.d.ts +32 -0
  81. package/dist/internal/oauthPresets.d.ts.map +1 -0
  82. package/dist/internal/oauthPresets.js +253 -0
  83. package/dist/internal/oauthScopes.d.ts +43 -100
  84. package/dist/internal/oauthScopes.d.ts.map +1 -1
  85. package/dist/internal/oauthScopes.js +83 -382
  86. package/dist/internal/oauthSpec.d.ts +103 -0
  87. package/dist/internal/oauthSpec.d.ts.map +1 -0
  88. package/dist/internal/oauthSpec.js +520 -0
  89. package/dist/internal/protocol.d.ts +97 -5
  90. package/dist/internal/protocol.d.ts.map +1 -1
  91. package/dist/internal/protocol.js +89 -1
  92. package/dist/internal/providerRegistry.d.ts +17 -6
  93. package/dist/internal/providerRegistry.d.ts.map +1 -1
  94. package/dist/internal/providerRegistry.js +71 -8
  95. package/dist/internal/providerSpec.d.ts +18 -7
  96. package/dist/internal/providerSpec.d.ts.map +1 -1
  97. package/dist/internal/providerSpec.js +420 -34
  98. package/dist/internal/remoteMcp.d.ts.map +1 -1
  99. package/dist/internal/remoteMcp.js +1 -5
  100. package/dist/internal/signInProviders.d.ts +104 -0
  101. package/dist/internal/signInProviders.d.ts.map +1 -0
  102. package/dist/internal/signInProviders.js +245 -0
  103. package/dist/internal/tools.d.ts +5 -4
  104. package/dist/internal/tools.d.ts.map +1 -1
  105. package/dist/internal/tools.js +4 -3
  106. package/dist/manifest.d.ts +267 -0
  107. package/dist/manifest.d.ts.map +1 -0
  108. package/dist/manifest.js +30 -0
  109. package/dist/server/ai.d.ts +111 -0
  110. package/dist/server/ai.d.ts.map +1 -0
  111. package/dist/server/ai.js +145 -0
  112. package/dist/server/aiBinding.d.ts +68 -0
  113. package/dist/server/aiBinding.d.ts.map +1 -0
  114. package/dist/server/aiBinding.js +294 -0
  115. package/dist/server/attribution.d.ts +18 -5
  116. package/dist/server/attribution.d.ts.map +1 -1
  117. package/dist/server/attribution.js +19 -5
  118. package/dist/server/connectUrl.d.ts +16 -0
  119. package/dist/server/connectUrl.d.ts.map +1 -0
  120. package/dist/server/connectUrl.js +40 -0
  121. package/dist/server/context.d.ts +68 -0
  122. package/dist/server/context.d.ts.map +1 -0
  123. package/dist/server/context.js +117 -0
  124. package/dist/server/emailBinding.d.ts +72 -0
  125. package/dist/server/emailBinding.d.ts.map +1 -0
  126. package/dist/server/emailBinding.js +124 -0
  127. package/dist/server/emit.d.ts +1 -1
  128. package/dist/server/emit.d.ts.map +1 -1
  129. package/dist/server/emit.js +7 -7
  130. package/dist/server/env.d.ts +44 -0
  131. package/dist/server/env.d.ts.map +1 -0
  132. package/dist/server/env.js +1 -0
  133. package/dist/server/eventsEndpoint.d.ts +48 -0
  134. package/dist/server/eventsEndpoint.d.ts.map +1 -0
  135. package/dist/server/eventsEndpoint.js +145 -0
  136. package/dist/server/everyApp.d.ts +40 -11
  137. package/dist/server/everyApp.d.ts.map +1 -1
  138. package/dist/server/everyApp.js +116 -6
  139. package/dist/server/fromMcpTools.d.ts +89 -0
  140. package/dist/server/fromMcpTools.d.ts.map +1 -0
  141. package/dist/server/fromMcpTools.js +192 -0
  142. package/dist/server/gatewayFetch.d.ts +42 -18
  143. package/dist/server/gatewayFetch.d.ts.map +1 -1
  144. package/dist/server/gatewayFetch.js +138 -19
  145. package/dist/server/gatewayUrl.d.ts +13 -0
  146. package/dist/server/gatewayUrl.d.ts.map +1 -0
  147. package/dist/server/gatewayUrl.js +34 -0
  148. package/dist/server/index.d.ts +16 -4
  149. package/dist/server/index.d.ts.map +1 -1
  150. package/dist/server/index.js +13 -3
  151. package/dist/server/integrationFetch.d.ts +92 -73
  152. package/dist/server/integrationFetch.d.ts.map +1 -1
  153. package/dist/server/integrationFetch.js +206 -319
  154. package/dist/server/listMembers.d.ts +1 -1
  155. package/dist/server/listMembers.d.ts.map +1 -1
  156. package/dist/server/listMembers.js +8 -11
  157. package/dist/server/refusal.d.ts +55 -0
  158. package/dist/server/refusal.d.ts.map +1 -0
  159. package/dist/server/refusal.js +193 -0
  160. package/dist/server/sendEmail.d.ts +1 -1
  161. package/dist/server/sendEmail.d.ts.map +1 -1
  162. package/dist/server/sendEmail.js +7 -7
  163. package/dist/server/tools.d.ts +7 -2
  164. package/dist/server/tools.d.ts.map +1 -1
  165. package/dist/server/tools.js +2 -1
  166. package/dist/server/toolsEndpoint.d.ts +3 -1
  167. package/dist/server/toolsEndpoint.d.ts.map +1 -1
  168. package/dist/server/toolsEndpoint.js +19 -7
  169. package/dist/server/webhooks.d.ts +56 -0
  170. package/dist/server/webhooks.d.ts.map +1 -0
  171. package/dist/server/webhooks.js +205 -0
  172. package/dist/tanstack-start/index.d.ts +9 -0
  173. package/dist/tanstack-start/index.d.ts.map +1 -0
  174. package/dist/tanstack-start/index.js +29 -0
  175. package/dist/testing/index.d.ts +93 -0
  176. package/dist/testing/index.d.ts.map +1 -0
  177. package/dist/testing/index.js +156 -0
  178. package/package.json +35 -14
  179. package/dist/server/authorizeUrl.d.ts +0 -43
  180. package/dist/server/authorizeUrl.d.ts.map +0 -1
  181. 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`: a provider's OAuth client (a social network's), held
20
- * the same way. The gateway completes the token endpoint calls only,
21
- * putting the client on them; the app builds the authorize URL from the
22
- * public client id (`integrationStatus()`), keeps the user tokens and
23
- * calls the provider's API with them itself. A client shared by apps
24
- * with different needs carries a `scopePolicy`: named scope sets an app
25
- * lists in its manifest entry's `scopes`, which ./oauthScopes.ts turns
26
- * into what the app may ask the provider for and what a token it is
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
- // Social networks' OAuth clients (docs/capabilities.md "OAuth clients"):
227
- // the gateway completes their token endpoints only. Postiz connects its
228
- // channels with them; one client serves every organization on the gateway.
229
- linkedin: {
230
- mode: "oauth_client",
231
- label: "LinkedIn",
232
- secrets: {
233
- clientId: "LINKEDIN_CLIENT_ID",
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
- return [capability.auth.secret, ...(capability.alsoNeeds ?? [])];
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
- return authorizationOf(capability.auth, settings);
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 path
1317
- * that isn't one of an OAuth client's token endpoints, or a route a service
1318
- * capability doesn't declare. `settings` resolves an OAuth client's
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 = oauthClientHosts(capability, settings).includes(host)
504
+ const upstream = capability.api.includes(host)
1344
505
  ? httpsUpstream(host, path ?? "/", search)
1345
506
  : null;
1346
- const endpoint = upstream && tokenEndpointFor(capability, upstream, settings);
1347
- if (!upstream || !endpoint)
507
+ if (!upstream)
1348
508
  return null;
1349
- return { kind: "oauth_client", id, capability, upstream, endpoint };
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> [<HOST SETTING>]`.
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(" ")}`;