@every-app/sdk 0.3.0-rc.14 → 0.3.0-rc.15

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 (35) hide show
  1. package/README.md +8 -6
  2. package/dist/cloudflare/catalogSettings.generated.d.ts +17 -7
  3. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -1
  4. package/dist/cloudflare/catalogSettings.generated.js +40 -12
  5. package/dist/cloudflare/worker.d.ts +18 -7
  6. package/dist/cloudflare/worker.d.ts.map +1 -1
  7. package/dist/internal/appSettings.d.ts.map +1 -1
  8. package/dist/internal/appSettings.js +4 -1
  9. package/dist/internal/capabilities.d.ts +5 -29
  10. package/dist/internal/capabilities.d.ts.map +1 -1
  11. package/dist/internal/capabilities.js +4 -24
  12. package/dist/internal/devProviders.d.ts +1 -19
  13. package/dist/internal/devProviders.d.ts.map +1 -1
  14. package/dist/internal/devProviders.js +7 -1
  15. package/dist/internal/oauthPresets.d.ts +351 -14
  16. package/dist/internal/oauthPresets.d.ts.map +1 -1
  17. package/dist/internal/oauthPresets.js +155 -22
  18. package/dist/internal/oauthSpec.d.ts +2 -46
  19. package/dist/internal/oauthSpec.d.ts.map +1 -1
  20. package/dist/internal/oauthSpec.js +0 -57
  21. package/dist/internal/remoteMcp.d.ts.map +1 -1
  22. package/dist/internal/remoteMcp.js +4 -3
  23. package/dist/internal/tools.d.ts +3 -1
  24. package/dist/internal/tools.d.ts.map +1 -1
  25. package/dist/server/index.d.ts +1 -1
  26. package/dist/server/index.d.ts.map +1 -1
  27. package/dist/server/integrationFetch.d.ts +7 -7
  28. package/dist/server/integrationFetch.d.ts.map +1 -1
  29. package/dist/server/integrationFetch.js +4 -2
  30. package/dist/server/tools.d.ts +1 -1
  31. package/dist/server/tools.js +1 -1
  32. package/package.json +1 -1
  33. package/dist/internal/oauthBuiltIns.d.ts +0 -101
  34. package/dist/internal/oauthBuiltIns.d.ts.map +0 -1
  35. package/dist/internal/oauthBuiltIns.js +0 -122
@@ -1,13 +1,17 @@
1
1
  /**
2
- * OAuth presets: ready `oauth2-user` specs, in the JSON shape `everyapp
3
- * provider add` takes, for providers people most often want a "Connect"
4
- * button for. A preset becomes a `providers` row when added (`everyapp
5
- * provider add --oauth github-oauth`, the Admin dialog,
6
- * `everyapp_provider_add` with `preset`), so an operator may `provider
7
- * show` and edit a copy; nothing here is a hidden catalog entry, and no
8
- * gateway reserves a preset's setting names until it is added. Google,
9
- * Microsoft and Slack are built in (./oauthBuiltIns.ts) and are offered
10
- * in the same picker.
2
+ * The OAuth providers, as presets: ready `oauth2-user` specs in the JSON
3
+ * shape `everyapp provider add` takes. Every OAuth provider a Gateway
4
+ * serves is a `providers` row, and a preset is that row's spec, written
5
+ * when an app whose manifest names the preset's id is deployed or
6
+ * installed, or when an operator adds it (`everyapp provider add
7
+ * google-workspace`, the Admin dialog, `everyapp_provider_add` with
8
+ * `preset`). An operator may `provider show` and edit the row's copy. No
9
+ * gateway reserves a preset's setting names until its row is written.
10
+ * What differs between providers is data here: the endpoints, the API
11
+ * hosts, how scopes are separated and prefixed, the extra authorize
12
+ * parameters, how the account is named, and the scopes a token may carry
13
+ * beyond what the app asked. No code reads a provider id to decide
14
+ * anything.
11
15
  *
12
16
  * Each preset parses with `parseProviderSpec` (oauthPresets.test.ts) and
13
17
  * names a console where the client is made, the API hosts the token is
@@ -15,18 +19,351 @@
15
19
  * holds is each app's `everyapp.config.ts`, never the preset. Endpoints
16
20
  * were checked against the providers' docs on 2026-10-25; only GitHub's
17
21
  * and Google's have been tried against a live connect.
22
+ *
23
+ * A row is a copy: changing a preset here changes no Gateway that has
24
+ * written its row already. A change that must reach them ships with a D1
25
+ * data migration (as 0032 wrote the first rows) that rewrites the rows
26
+ * still equal to the old spec and moves `bound_to` (and a pasted client's
27
+ * `oauth_clients.bound_to`) from the old binding key to the new one, so
28
+ * the client keeps being read and `isPresetRow` keeps matching.
18
29
  */
30
+ import type { CapabilityLookup } from "./capabilities.js";
19
31
  import { type RegistrableCapability } from "./providerSpec.js";
20
32
  /** A preset's spec as JSON: the shape of ./oauthSpec.ts. */
21
33
  export type OAuthPresetSpec = Readonly<Record<string, unknown>>;
34
+ declare const PRESETS: {
35
+ readonly "google-workspace": {
36
+ readonly id: "google-workspace";
37
+ readonly name: "Google";
38
+ readonly description: "Gmail, Calendar, Drive, Search Console, Analytics and the other Google APIs, as the person who connects.";
39
+ readonly docs: "https://console.cloud.google.com/apis/credentials";
40
+ readonly api: "https://developers.google.com/identity/protocols/oauth2/scopes";
41
+ readonly hosts: readonly ["gmail.googleapis.com", "www.googleapis.com", "searchconsole.googleapis.com", "analyticsdata.googleapis.com", "analyticsadmin.googleapis.com", "openidconnect.googleapis.com"];
42
+ readonly auth: {
43
+ readonly kind: "oauth2-user";
44
+ readonly authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth";
45
+ readonly tokenUrl: "https://oauth2.googleapis.com/token";
46
+ readonly revokeUrl: "https://oauth2.googleapis.com/revoke";
47
+ readonly clientId: "GOOGLE_WORKSPACE_CLIENT_ID";
48
+ readonly clientSecret: "GOOGLE_WORKSPACE_CLIENT_SECRET";
49
+ readonly inject: "form";
50
+ readonly pkce: true;
51
+ readonly scopeSeparator: "space";
52
+ readonly scopePrefix: "https://www.googleapis.com/auth/";
53
+ readonly authorizeParams: {
54
+ readonly access_type: "offline";
55
+ readonly prompt: "consent";
56
+ };
57
+ readonly identity: {
58
+ readonly url: "https://openidconnect.googleapis.com/v1/userinfo";
59
+ readonly label: "email";
60
+ readonly id: "sub";
61
+ readonly scopes: readonly ["openid", "email"];
62
+ };
63
+ readonly tolerated: readonly ["profile", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"];
64
+ };
65
+ readonly hint: "A Web application OAuth client in Google Cloud, with the Gateway's callback as its redirect URI and the scopes your apps list on its consent screen.";
66
+ };
67
+ readonly "microsoft-365": {
68
+ readonly id: "microsoft-365";
69
+ readonly name: "Microsoft 365";
70
+ readonly description: "Outlook mail and calendar, OneDrive and the rest of Microsoft Graph, as the person who connects.";
71
+ readonly docs: "https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade";
72
+ readonly api: "https://learn.microsoft.com/graph/permissions-reference";
73
+ readonly hosts: readonly ["graph.microsoft.com"];
74
+ readonly auth: {
75
+ readonly kind: "oauth2-user";
76
+ readonly authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
77
+ readonly tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token";
78
+ readonly clientId: "MICROSOFT_365_CLIENT_ID";
79
+ readonly clientSecret: "MICROSOFT_365_CLIENT_SECRET";
80
+ readonly inject: "form";
81
+ readonly pkce: true;
82
+ readonly scopeSeparator: "space";
83
+ readonly scopePrefix: "https://graph.microsoft.com/";
84
+ readonly identity: {
85
+ readonly url: "https://graph.microsoft.com/v1.0/me";
86
+ readonly label: "userPrincipalName";
87
+ readonly id: "id";
88
+ readonly scopes: readonly ["User.Read", "offline_access"];
89
+ };
90
+ readonly tolerated: readonly ["openid", "profile", "email"];
91
+ };
92
+ readonly hint: "An app registration in Entra for accounts in any organization and personal accounts, with the Gateway's callback as a Web redirect URI. If two apps share this client with different scopes, Microsoft may hand back both apps' scopes and the Gateway refuses the token: give each app its own client.";
93
+ };
94
+ readonly slack: {
95
+ readonly id: "slack";
96
+ readonly name: "Slack";
97
+ readonly description: "Channels, members and messages, as the Slack app.";
98
+ readonly docs: "https://api.slack.com/apps";
99
+ readonly api: "https://api.slack.com/scopes";
100
+ readonly hosts: readonly ["slack.com"];
101
+ readonly auth: {
102
+ readonly kind: "oauth2-user";
103
+ readonly authorizeUrl: "https://slack.com/oauth/v2/authorize";
104
+ readonly tokenUrl: "https://slack.com/api/oauth.v2.access";
105
+ readonly revokeUrl: "https://slack.com/api/auth.revoke";
106
+ readonly clientId: "SLACK_CLIENT_ID";
107
+ readonly clientSecret: "SLACK_CLIENT_SECRET";
108
+ readonly inject: "form";
109
+ readonly pkce: false;
110
+ readonly scopeSeparator: "comma";
111
+ readonly identity: {
112
+ readonly url: "https://slack.com/api/auth.test";
113
+ readonly label: "team";
114
+ readonly id: "team_id";
115
+ };
116
+ };
117
+ readonly hint: "A Slack app with the Gateway's callback as its redirect URL and the bot scopes your apps list. The Gateway takes its Client ID and Client Secret, not a bot token (xoxb-).";
118
+ };
119
+ readonly "github-oauth": {
120
+ readonly id: "github-oauth";
121
+ readonly name: "GitHub";
122
+ readonly description: "Repositories, issues, pull requests and the person's profile.";
123
+ readonly docs: "https://github.com/settings/developers";
124
+ readonly api: "https://docs.github.com/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps";
125
+ readonly hosts: readonly ["api.github.com"];
126
+ readonly auth: {
127
+ readonly kind: "oauth2-user";
128
+ readonly authorizeUrl: "https://github.com/login/oauth/authorize";
129
+ readonly tokenUrl: "https://github.com/login/oauth/access_token";
130
+ readonly revokeUrl: "https://api.github.com/applications/{client_id}/grant";
131
+ readonly revokeMethod: "DELETE";
132
+ readonly clientId: "GITHUB_OAUTH_CLIENT_ID";
133
+ readonly clientSecret: "GITHUB_OAUTH_CLIENT_SECRET";
134
+ readonly inject: "form";
135
+ readonly pkce: true;
136
+ readonly scopeSeparator: "comma";
137
+ readonly tokenHeaders: {
138
+ readonly Accept: "application/json";
139
+ };
140
+ readonly identity: {
141
+ readonly url: "https://api.github.com/user";
142
+ readonly label: "login";
143
+ readonly id: "id";
144
+ };
145
+ };
146
+ readonly hint: "Register an OAuth App (not a GitHub App) and paste its client id and secret. GitHub's `repo` scope is read and write.";
147
+ };
148
+ readonly "notion-oauth": {
149
+ readonly id: "notion-oauth";
150
+ readonly name: "Notion";
151
+ readonly description: "Pages and databases the person shares with the integration.";
152
+ readonly docs: "https://www.notion.so/my-integrations";
153
+ readonly api: "https://developers.notion.com/reference";
154
+ readonly hosts: readonly ["api.notion.com"];
155
+ readonly auth: {
156
+ readonly kind: "oauth2-user";
157
+ readonly authorizeUrl: "https://api.notion.com/v1/oauth/authorize";
158
+ readonly tokenUrl: "https://api.notion.com/v1/oauth/token";
159
+ readonly clientId: "NOTION_OAUTH_CLIENT_ID";
160
+ readonly clientSecret: "NOTION_OAUTH_CLIENT_SECRET";
161
+ readonly inject: "basic";
162
+ readonly pkce: false;
163
+ readonly scopeSeparator: "space";
164
+ readonly authorizeParams: {
165
+ readonly owner: "user";
166
+ };
167
+ };
168
+ readonly hint: "Notion has no scopes: an app lists scopes: [] and the person picks the pages to share on Notion's consent screen. Make the integration public to get a client id and secret. Apps send `Notion-Version` on every call.";
169
+ };
170
+ readonly "linear-oauth": {
171
+ readonly id: "linear-oauth";
172
+ readonly name: "Linear";
173
+ readonly description: "Issues, projects and teams, over Linear's GraphQL API.";
174
+ readonly docs: "https://linear.app/settings/api/applications";
175
+ readonly api: "https://developers.linear.app/docs/oauth/authentication";
176
+ readonly hosts: readonly ["api.linear.app"];
177
+ readonly auth: {
178
+ readonly kind: "oauth2-user";
179
+ readonly authorizeUrl: "https://linear.app/oauth/authorize";
180
+ readonly tokenUrl: "https://api.linear.app/oauth/token";
181
+ readonly revokeUrl: "https://api.linear.app/oauth/revoke";
182
+ readonly clientId: "LINEAR_OAUTH_CLIENT_ID";
183
+ readonly clientSecret: "LINEAR_OAUTH_CLIENT_SECRET";
184
+ readonly inject: "form";
185
+ readonly pkce: false;
186
+ readonly scopeSeparator: "comma";
187
+ };
188
+ readonly hint: "Linear's scopes are read, write, issues:create and comments:create; its API is GraphQL over POST.";
189
+ };
190
+ readonly "gitlab-oauth": {
191
+ readonly id: "gitlab-oauth";
192
+ readonly name: "GitLab";
193
+ readonly description: "Projects, merge requests and issues on gitlab.com.";
194
+ readonly docs: "https://gitlab.com/-/user_settings/applications";
195
+ readonly api: "https://docs.gitlab.com/integration/oauth_provider/#view-all-authorized-applications";
196
+ readonly hosts: readonly ["gitlab.com"];
197
+ readonly auth: {
198
+ readonly kind: "oauth2-user";
199
+ readonly authorizeUrl: "https://gitlab.com/oauth/authorize";
200
+ readonly tokenUrl: "https://gitlab.com/oauth/token";
201
+ readonly revokeUrl: "https://gitlab.com/oauth/revoke";
202
+ readonly clientId: "GITLAB_OAUTH_CLIENT_ID";
203
+ readonly clientSecret: "GITLAB_OAUTH_CLIENT_SECRET";
204
+ readonly inject: "form";
205
+ readonly pkce: true;
206
+ readonly scopeSeparator: "space";
207
+ readonly identity: {
208
+ readonly url: "https://gitlab.com/api/v4/user";
209
+ readonly label: "username";
210
+ readonly id: "id";
211
+ readonly scopes: readonly ["read_user"];
212
+ };
213
+ };
214
+ readonly hint: "A self-managed GitLab needs a custom provider with its own host in place of gitlab.com.";
215
+ };
216
+ readonly atlassian: {
217
+ readonly id: "atlassian";
218
+ readonly name: "Atlassian (Jira and Confluence)";
219
+ readonly description: "Jira issues and Confluence pages on the sites the person grants.";
220
+ readonly docs: "https://developer.atlassian.com/console/myapps/";
221
+ readonly api: "https://developer.atlassian.com/cloud/jira/platform/scopes-for-oauth-2-3LO-and-forge-apps/";
222
+ readonly hosts: readonly ["api.atlassian.com"];
223
+ readonly auth: {
224
+ readonly kind: "oauth2-user";
225
+ readonly authorizeUrl: "https://auth.atlassian.com/authorize";
226
+ readonly tokenUrl: "https://auth.atlassian.com/oauth/token";
227
+ readonly clientId: "ATLASSIAN_CLIENT_ID";
228
+ readonly clientSecret: "ATLASSIAN_CLIENT_SECRET";
229
+ readonly inject: "form";
230
+ readonly pkce: false;
231
+ readonly scopeSeparator: "space";
232
+ readonly authorizeParams: {
233
+ readonly audience: "api.atlassian.com";
234
+ readonly prompt: "consent";
235
+ };
236
+ readonly identity: {
237
+ readonly url: "https://api.atlassian.com/me";
238
+ readonly label: "email";
239
+ readonly id: "account_id";
240
+ readonly scopes: readonly ["read:me", "offline_access"];
241
+ };
242
+ };
243
+ readonly hint: "Calls go to api.atlassian.com/ex/jira/<cloud id>/… ; the cloud id comes from /oauth/token/accessible-resources.";
244
+ };
245
+ readonly "hubspot-oauth": {
246
+ readonly id: "hubspot-oauth";
247
+ readonly name: "HubSpot";
248
+ readonly description: "CRM contacts, companies and deals.";
249
+ readonly docs: "https://developers.hubspot.com/";
250
+ readonly api: "https://developers.hubspot.com/docs/api/working-with-oauth";
251
+ readonly hosts: readonly ["api.hubapi.com"];
252
+ readonly auth: {
253
+ readonly kind: "oauth2-user";
254
+ readonly authorizeUrl: "https://app.hubspot.com/oauth/authorize";
255
+ readonly tokenUrl: "https://api.hubapi.com/oauth/v1/token";
256
+ readonly clientId: "HUBSPOT_OAUTH_CLIENT_ID";
257
+ readonly clientSecret: "HUBSPOT_OAUTH_CLIENT_SECRET";
258
+ readonly inject: "form";
259
+ readonly pkce: false;
260
+ readonly scopeSeparator: "space";
261
+ };
262
+ readonly hint: "The app's scopes in HubSpot's developer console must include the scopes apps list, or HubSpot refuses the authorize URL.";
263
+ };
264
+ readonly zoom: {
265
+ readonly id: "zoom";
266
+ readonly name: "Zoom";
267
+ readonly description: "The person's meetings and recordings.";
268
+ readonly docs: "https://marketplace.zoom.us/develop/create";
269
+ readonly api: "https://developers.zoom.us/docs/integrations/oauth-scopes-overview/";
270
+ readonly hosts: readonly ["api.zoom.us"];
271
+ readonly auth: {
272
+ readonly kind: "oauth2-user";
273
+ readonly authorizeUrl: "https://zoom.us/oauth/authorize";
274
+ readonly tokenUrl: "https://zoom.us/oauth/token";
275
+ readonly revokeUrl: "https://zoom.us/oauth/revoke";
276
+ readonly clientId: "ZOOM_CLIENT_ID";
277
+ readonly clientSecret: "ZOOM_CLIENT_SECRET";
278
+ readonly inject: "basic";
279
+ readonly pkce: true;
280
+ readonly scopeSeparator: "space";
281
+ readonly identity: {
282
+ readonly url: "https://api.zoom.us/v2/users/me";
283
+ readonly label: "email";
284
+ readonly id: "id";
285
+ readonly scopes: readonly ["user:read:user"];
286
+ };
287
+ };
288
+ readonly hint: "A General app with user-managed OAuth, using Zoom's granular scope names.";
289
+ };
290
+ readonly dropbox: {
291
+ readonly id: "dropbox";
292
+ readonly name: "Dropbox";
293
+ readonly description: "Files and folders in the person's Dropbox.";
294
+ readonly docs: "https://www.dropbox.com/developers/apps";
295
+ readonly api: "https://developers.dropbox.com/oauth-guide";
296
+ readonly hosts: readonly ["api.dropboxapi.com", "content.dropboxapi.com"];
297
+ readonly auth: {
298
+ readonly kind: "oauth2-user";
299
+ readonly authorizeUrl: "https://www.dropbox.com/oauth2/authorize";
300
+ readonly tokenUrl: "https://api.dropboxapi.com/oauth2/token";
301
+ readonly revokeUrl: "https://api.dropboxapi.com/2/auth/token/revoke";
302
+ readonly clientId: "DROPBOX_OAUTH_CLIENT_ID";
303
+ readonly clientSecret: "DROPBOX_OAUTH_CLIENT_SECRET";
304
+ readonly inject: "form";
305
+ readonly pkce: true;
306
+ readonly scopeSeparator: "space";
307
+ readonly authorizeParams: {
308
+ readonly token_access_type: "offline";
309
+ };
310
+ readonly tolerated: readonly ["account_info.read"];
311
+ };
312
+ readonly hint: "`token_access_type=offline` is what yields a refresh token.";
313
+ };
314
+ readonly spotify: {
315
+ readonly id: "spotify";
316
+ readonly name: "Spotify";
317
+ readonly description: "The person's library, playlists and listening.";
318
+ readonly docs: "https://developer.spotify.com/dashboard";
319
+ readonly api: "https://developer.spotify.com/documentation/web-api/concepts/scopes";
320
+ readonly hosts: readonly ["api.spotify.com"];
321
+ readonly auth: {
322
+ readonly kind: "oauth2-user";
323
+ readonly authorizeUrl: "https://accounts.spotify.com/authorize";
324
+ readonly tokenUrl: "https://accounts.spotify.com/api/token";
325
+ readonly clientId: "SPOTIFY_CLIENT_ID";
326
+ readonly clientSecret: "SPOTIFY_CLIENT_SECRET";
327
+ readonly inject: "basic";
328
+ readonly pkce: true;
329
+ readonly scopeSeparator: "space";
330
+ readonly identity: {
331
+ readonly url: "https://api.spotify.com/v1/me";
332
+ readonly label: "display_name";
333
+ readonly id: "id";
334
+ };
335
+ };
336
+ };
337
+ };
338
+ /** The id of an OAuth provider the SDK ships a preset for (`google-workspace`, `github-oauth`, ...). */
339
+ export type OAuthPresetId = keyof typeof PRESETS;
340
+ /** Every preset by id, in the order the picker shows them. */
22
341
  export declare const OAUTH_PRESETS: Readonly<Record<string, OAuthPresetSpec>>;
342
+ /** The setup guide for OAuth provider `id`'s client, when its preset has one (`setup` in an app's settings report). */
343
+ export declare function oauthSetupGuide(id: string): string | undefined;
23
344
  /** The preset ids, in the order the picker shows them. */
24
345
  export declare function oauthPresetIds(): string[];
25
346
  /** A preset's parsed capability, for the picker's labels; throws for an unknown id (the test parses every preset). */
26
347
  export declare function oauthPreset(id: string): RegistrableCapability | null;
27
- /** Whether `capability` is an OAuth provider the Gateway runs the flow for. */
28
- export declare function isOAuthProvider(capability: {
29
- mode: string;
30
- flow?: unknown;
31
- } | undefined): boolean;
348
+ /**
349
+ * Whether `capability`, a Gateway's row for `id`, is that preset's: its
350
+ * client would go to the preset's endpoints and hosts under the preset's
351
+ * setting names (the same binding key). Adding the preset again takes the
352
+ * client for such a row; a row of another spec under a preset's id is the
353
+ * operator's own and is never taken for the preset.
354
+ */
355
+ export declare function isPresetRow(id: string, capability: RegistrableCapability): boolean;
356
+ /**
357
+ * The preset ids among `ids` (an app's capabilities) that `lookup` (a
358
+ * Gateway's registry) doesn't have: the rows a deploy or an install
359
+ * writes from their presets before it checks the manifest.
360
+ */
361
+ export declare function unregisteredPresetIds(ids: Iterable<string>, lookup: Pick<CapabilityLookup, "get">): string[];
362
+ /**
363
+ * `lookup` with every preset it lacks, as the row it would be: what a
364
+ * local run (`everyapp dev`) and a check made before a Gateway is asked
365
+ * read, since a deploy writes the row of any preset an app names.
366
+ */
367
+ export declare function withOAuthPresets(lookup: CapabilityLookup): CapabilityLookup;
368
+ export {};
32
369
  //# sourceMappingURL=oauthPresets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauthPresets.d.ts","sourceRoot":"","sources":["../../src/internal/oauthPresets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,mBAAmB,CAAC;AAE3B,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEhE,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAwNnE,CAAC;AAEF,0DAA0D;AAC1D,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED,sHAAsH;AACtH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAUpE;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAC7B,UAAU,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,GACvD,OAAO,CAET"}
1
+ {"version":3,"file":"oauthPresets.d.ts","sourceRoot":"","sources":["../../src/internal/oauthPresets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,mBAAmB,CAAC;AAE3B,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEhE,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwUuC,CAAC;AAErD,wGAAwG;AACxG,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,OAAO,CAAC;AAEjD,8DAA8D;AAC9D,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAW,CAAC;AAShF,uHAAuH;AACvH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE9D;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED,sHAAsH;AACtH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAUpE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,qBAAqB,GAChC,OAAO,CAMT;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,EACrB,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,GACpC,MAAM,EAAE,CAIV;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAQ3E"}
@@ -1,23 +1,115 @@
1
- /**
2
- * OAuth presets: ready `oauth2-user` specs, in the JSON shape `everyapp
3
- * provider add` takes, for providers people most often want a "Connect"
4
- * button for. A preset becomes a `providers` row when added (`everyapp
5
- * provider add --oauth github-oauth`, the Admin dialog,
6
- * `everyapp_provider_add` with `preset`), so an operator may `provider
7
- * show` and edit a copy; nothing here is a hidden catalog entry, and no
8
- * gateway reserves a preset's setting names until it is added. Google,
9
- * Microsoft and Slack are built in (./oauthBuiltIns.ts) and are offered
10
- * in the same picker.
11
- *
12
- * Each preset parses with `parseProviderSpec` (oauthPresets.test.ts) and
13
- * names a console where the client is made, the API hosts the token is
14
- * sent to, and how the provider spells scopes. Which scopes a connection
15
- * holds is each app's `everyapp.config.ts`, never the preset. Endpoints
16
- * were checked against the providers' docs on 2026-10-25; only GitHub's
17
- * and Google's have been tried against a live connect.
18
- */
1
+ import { providerBindingKey } from "./providerRegistry.js";
19
2
  import { parseProviderSpec, } from "./providerSpec.js";
20
- export const OAUTH_PRESETS = {
3
+ const PRESETS = {
4
+ // One Google Cloud project's OAuth client: Gmail, Calendar, Search
5
+ // Console, Analytics and any other Google API an app names a scope for.
6
+ "google-workspace": {
7
+ id: "google-workspace",
8
+ name: "Google",
9
+ description: "Gmail, Calendar, Drive, Search Console, Analytics and the other Google APIs, as the person who connects.",
10
+ docs: "https://console.cloud.google.com/apis/credentials",
11
+ api: "https://developers.google.com/identity/protocols/oauth2/scopes",
12
+ hosts: [
13
+ "gmail.googleapis.com",
14
+ "www.googleapis.com",
15
+ "searchconsole.googleapis.com",
16
+ "analyticsdata.googleapis.com",
17
+ "analyticsadmin.googleapis.com",
18
+ "openidconnect.googleapis.com",
19
+ ],
20
+ auth: {
21
+ kind: "oauth2-user",
22
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
23
+ tokenUrl: "https://oauth2.googleapis.com/token",
24
+ // A disconnect revokes the grant: POST form `token=` (RFC 7009), the
25
+ // refresh token once the access token has expired.
26
+ revokeUrl: "https://oauth2.googleapis.com/revoke",
27
+ clientId: "GOOGLE_WORKSPACE_CLIENT_ID",
28
+ clientSecret: "GOOGLE_WORKSPACE_CLIENT_SECRET",
29
+ inject: "form",
30
+ pkce: true,
31
+ scopeSeparator: "space",
32
+ // An app writes `gmail.modify`; the authorize URL carries the full
33
+ // `https://www.googleapis.com/auth/gmail.modify`.
34
+ scopePrefix: "https://www.googleapis.com/auth/",
35
+ // A refresh token needs `access_type=offline`; `prompt=consent`
36
+ // makes Google issue one on every connect.
37
+ authorizeParams: { access_type: "offline", prompt: "consent" },
38
+ identity: {
39
+ url: "https://openidconnect.googleapis.com/v1/userinfo",
40
+ label: "email",
41
+ id: "sub",
42
+ scopes: ["openid", "email"],
43
+ },
44
+ // Google answers `email` and `profile` as the userinfo URLs, and may
45
+ // add `profile` beside `openid`.
46
+ tolerated: [
47
+ "profile",
48
+ "https://www.googleapis.com/auth/userinfo.email",
49
+ "https://www.googleapis.com/auth/userinfo.profile",
50
+ ],
51
+ },
52
+ hint: "A Web application OAuth client in Google Cloud, with the Gateway's callback as its redirect URI and the scopes your apps list on its consent screen.",
53
+ },
54
+ // The multi-tenant Entra endpoints: register the client for accounts in
55
+ // any organization and personal accounts.
56
+ "microsoft-365": {
57
+ id: "microsoft-365",
58
+ name: "Microsoft 365",
59
+ description: "Outlook mail and calendar, OneDrive and the rest of Microsoft Graph, as the person who connects.",
60
+ docs: "https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
61
+ api: "https://learn.microsoft.com/graph/permissions-reference",
62
+ hosts: ["graph.microsoft.com"],
63
+ auth: {
64
+ kind: "oauth2-user",
65
+ authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
66
+ tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
67
+ clientId: "MICROSOFT_365_CLIENT_ID",
68
+ clientSecret: "MICROSOFT_365_CLIENT_SECRET",
69
+ inject: "form",
70
+ pkce: true,
71
+ scopeSeparator: "space",
72
+ // An app writes `Mail.Read`; Graph's scopes are fully qualified on
73
+ // the wire and may come back either way.
74
+ scopePrefix: "https://graph.microsoft.com/",
75
+ identity: {
76
+ url: "https://graph.microsoft.com/v1.0/me",
77
+ label: "userPrincipalName",
78
+ id: "id",
79
+ // `offline_access` is what makes Entra issue a refresh token.
80
+ scopes: ["User.Read", "offline_access"],
81
+ },
82
+ tolerated: ["openid", "profile", "email"],
83
+ },
84
+ hint: "An app registration in Entra for accounts in any organization and personal accounts, with the Gateway's callback as a Web redirect URI. If two apps share this client with different scopes, Microsoft may hand back both apps' scopes and the Gateway refuses the token: give each app its own client.",
85
+ },
86
+ // A Slack app's bot token: the top-level token of `oauth.v2.access`.
87
+ slack: {
88
+ id: "slack",
89
+ name: "Slack",
90
+ description: "Channels, members and messages, as the Slack app.",
91
+ docs: "https://api.slack.com/apps",
92
+ api: "https://api.slack.com/scopes",
93
+ hosts: ["slack.com"],
94
+ auth: {
95
+ kind: "oauth2-user",
96
+ authorizeUrl: "https://slack.com/oauth/v2/authorize",
97
+ tokenUrl: "https://slack.com/api/oauth.v2.access",
98
+ revokeUrl: "https://slack.com/api/auth.revoke",
99
+ clientId: "SLACK_CLIENT_ID",
100
+ clientSecret: "SLACK_CLIENT_SECRET",
101
+ inject: "form",
102
+ // Slack refuses `code_challenge`.
103
+ pkce: false,
104
+ scopeSeparator: "comma",
105
+ identity: {
106
+ url: "https://slack.com/api/auth.test",
107
+ label: "team",
108
+ id: "team_id",
109
+ },
110
+ },
111
+ hint: "A Slack app with the Gateway's callback as its redirect URL and the bot scopes your apps list. The Gateway takes its Client ID and Client Secret, not a bot token (xoxb-).",
112
+ },
21
113
  "github-oauth": {
22
114
  id: "github-oauth",
23
115
  name: "GitHub",
@@ -232,6 +324,18 @@ export const OAUTH_PRESETS = {
232
324
  },
233
325
  },
234
326
  };
327
+ /** Every preset by id, in the order the picker shows them. */
328
+ export const OAUTH_PRESETS = PRESETS;
329
+ /** The everyapp skill's guides for a preset's client, where one exists. */
330
+ const SETUP_GUIDES = {
331
+ "google-workspace": "setup/google.md",
332
+ "microsoft-365": "setup/microsoft-365.md",
333
+ slack: "setup/slack.md",
334
+ };
335
+ /** The setup guide for OAuth provider `id`'s client, when its preset has one (`setup` in an app's settings report). */
336
+ export function oauthSetupGuide(id) {
337
+ return SETUP_GUIDES[id];
338
+ }
235
339
  /** The preset ids, in the order the picker shows them. */
236
340
  export function oauthPresetIds() {
237
341
  return Object.keys(OAUTH_PRESETS);
@@ -247,7 +351,36 @@ export function oauthPreset(id) {
247
351
  }
248
352
  return parsed.capability;
249
353
  }
250
- /** Whether `capability` is an OAuth provider the Gateway runs the flow for. */
251
- export function isOAuthProvider(capability) {
252
- return capability?.mode === "oauth_client" && capability.flow !== undefined;
354
+ /**
355
+ * Whether `capability`, a Gateway's row for `id`, is that preset's: its
356
+ * client would go to the preset's endpoints and hosts under the preset's
357
+ * setting names (the same binding key). Adding the preset again takes the
358
+ * client for such a row; a row of another spec under a preset's id is the
359
+ * operator's own and is never taken for the preset.
360
+ */
361
+ export function isPresetRow(id, capability) {
362
+ const preset = oauthPreset(id);
363
+ return (preset !== null &&
364
+ providerBindingKey(capability) === providerBindingKey(preset));
365
+ }
366
+ /**
367
+ * The preset ids among `ids` (an app's capabilities) that `lookup` (a
368
+ * Gateway's registry) doesn't have: the rows a deploy or an install
369
+ * writes from their presets before it checks the manifest.
370
+ */
371
+ export function unregisteredPresetIds(ids, lookup) {
372
+ return [...new Set(ids)].filter((id) => Object.hasOwn(OAUTH_PRESETS, id) && lookup.get(id) === undefined);
373
+ }
374
+ /**
375
+ * `lookup` with every preset it lacks, as the row it would be: what a
376
+ * local run (`everyapp dev`) and a check made before a Gateway is asked
377
+ * read, since a deploy writes the row of any preset an app names.
378
+ */
379
+ export function withOAuthPresets(lookup) {
380
+ const presets = oauthPresetIds().filter((id) => !lookup.get(id));
381
+ return {
382
+ get: (id) => lookup.get(id) ??
383
+ (presets.includes(id) ? (oauthPreset(id) ?? undefined) : undefined),
384
+ ids: () => [...lookup.ids(), ...presets],
385
+ };
253
386
  }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * The `oauth2-user` half of a provider spec: a registered OAuth provider,
3
- * a preset (./oauthPresets.ts) or a built-in one (./oauthBuiltIns.ts), as
4
- * JSON, turned into an {@link OAuthClientCapability} whose `flow` the
3
+ * one written from a preset (./oauthPresets.ts), as JSON, turned into an {@link OAuthClientCapability} whose `flow` the
5
4
  * Gateway runs. `parseProviderSpec` (./providerSpec.ts) hands the common
6
5
  * fields here once it sees `auth.kind: "oauth2-user"`. Nothing here reads
7
6
  * a value: a spec names Gateway settings, never holds one.
@@ -12,11 +11,8 @@
12
11
  * identity URL with the scopes it needs, and the scopes a token may carry
13
12
  * beyond what was asked (`tolerated`). Which scopes an app gets is its own
14
13
  * `everyapp.config.ts`, never the spec.
15
- *
16
- * Only type imports from ./capabilities.ts: the catalog builds its
17
- * built-in entries with {@link oauthCapabilityFromSpec} at load.
18
14
  */
19
- import type { OAuthClientCapability, OAuthClientInjection, OAuthScopeSeparator } from "./capabilities.js";
15
+ import type { OAuthClientCapability } from "./capabilities.js";
20
16
  export interface SpecProblem {
21
17
  path: string;
22
18
  message: string;
@@ -55,46 +51,6 @@ export interface ParsedOAuthSpec {
55
51
  * the capability with its `flow`.
56
52
  */
57
53
  export declare function parseOAuthUserSpec(input: Record<string, unknown>, common: OAuthSpecCommon, problems: SpecProblem[]): ParsedOAuthSpec | undefined;
58
- /** The JSON shape of an `oauth2-user` spec, as the built-ins and presets write it. */
59
- export interface OAuthSpecJson {
60
- readonly id: string;
61
- readonly name: string;
62
- readonly description?: string;
63
- readonly docs: string;
64
- readonly api?: string;
65
- readonly hosts: readonly string[];
66
- readonly auth: {
67
- readonly kind: "oauth2-user";
68
- readonly authorizeUrl: string;
69
- readonly tokenUrl: string;
70
- readonly revokeUrl?: string;
71
- readonly revokeMethod?: "POST" | "DELETE";
72
- readonly clientId: string;
73
- readonly clientSecret?: string;
74
- readonly inject?: OAuthClientInjection;
75
- readonly pkce?: boolean;
76
- readonly scopeSeparator?: OAuthScopeSeparator;
77
- readonly scopePrefix?: string;
78
- readonly authorizeParams?: Readonly<Record<string, string>>;
79
- readonly tokenHeaders?: Readonly<Record<string, string>>;
80
- readonly identity?: {
81
- readonly url: string;
82
- readonly label: string;
83
- readonly id?: string;
84
- readonly scopes?: readonly string[];
85
- };
86
- readonly tolerated?: readonly string[];
87
- };
88
- readonly hint?: string;
89
- readonly example?: string;
90
- readonly tags?: readonly string[];
91
- }
92
- /**
93
- * A spec already known to be good (a built-in, checked by its test) as
94
- * its capability, without the parse: what the catalog runs at load, where
95
- * calling the full parser would be an import cycle.
96
- */
97
- export declare function oauthCapabilityFromSpec(spec: OAuthSpecJson): OAuthClientCapability;
98
54
  /**
99
55
  * The JSON spec of an OAuth capability (`provider show`, a preset's round
100
56
  * trip): `parseProviderSpec` of it gives the capability back.