feat(oauth): publish integration scopes in docs, and fix the Excel SharePoint scopes - #7440
feat(oauth): publish integration scopes in docs, and fix the Excel SharePoint scopes#7440mzxchandra wants to merge 7 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThe PR extracts OAuth scope metadata into a dependency-free catalog, publishes generated scope documentation, and expands Microsoft Excel permissions for SharePoint-backed workbooks.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/oauth/scopes.ts | Introduces the readonly canonical scope and description catalog, including the expanded Excel SharePoint permissions. |
| apps/sim/lib/oauth/oauth.ts | Replaces inline scope arrays with copied entries from the central catalog and uses the established absolute import alias. |
| scripts/generate-docs.ts | Generates connector-specific OAuth scope documentation and shared provider metadata from the canonical catalog. |
| apps/sim/app/workspace/[workspaceId]/components/connect-oauth-modal/connect-oauth-modal.tsx | Aligns connection-scope handling with the extracted catalog and environment-gated scope data. |
| apps/docs/content/docs/platform/self-hosting/integrations-oauth.mdx | Publishes generated provider registration details and connector-specific OAuth permissions for self-hosters. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Catalog[OAuth scope catalog] --> Runtime[OAuth provider configuration]
Catalog --> Generator[Documentation generator]
Generator --> IntegrationDocs[Integration scope tables]
Generator --> SelfHostingDocs[Self-hosting OAuth reference]
Runtime --> Consent[Provider consent request]
Excel[Microsoft Excel connector] --> Runtime
Reviews (5): Last reviewed commit: "fix(docs): make the generation guard act..." | Re-trigger Greptile
e4b7842 to
cd9794c
Compare
|
@cubic-dev-ai review this PR |
@mzxchandra I have started the AI code review. It will take a few minutes to complete. |
cd9794c to
b2c817e
Compare
|
@cubic-dev-ai review this PR |
@mzxchandra I have started the AI code review. It will take a few minutes to complete. |
|
@cubic-dev-ai review this PR |
@mzxchandra I have started the AI code review. It will take a few minutes to complete. |
`OAUTH_PROVIDERS` imports ~120 React icon components and reads `env` at module load, so nothing outside Next can read the scope sets it declares. That blocks the docs generator from publishing what each integration requests, and it buries a security-relevant surface inside a file that also owns token refresh and error redaction. Move the 61 per-service scope arrays out of `oauth.ts`, and the scope label tables plus `getScopeDescription` out of `utils.ts`, into a new `lib/oauth/scopes.ts` with no imports at all. `oauth.ts` composes from it (`scopes: [...OAUTH_SCOPES.sharepoint]`), and Slack's approval-gated extras stay appended there because whether they are requested depends on an environment flag. Behavior-preserving: every one of the 61 scope lists was verified identical before and after the move. Consumers of the moved exports are repointed at the new module rather than re-exported through `utils.ts`, which the import rules forbid. The new test asserts the two files cover exactly the same services and that every scope in the table is actually requested at authorization time, so the split cannot silently drift.
Nothing in the docs said what a connection grants. A user hit the consent screen cold, and a self-hoster registering an app had to guess which permissions to add before anyone could connect. Generate a `## Scopes` table on every OAuth integration page from `OAUTH_SCOPES`, labelled with the descriptions the connect modal already uses. 51 pages get one. The 6 services that declare no scopes (Notion, ClickUp, Cal.com, Harmonic, Snowflake, NetSuite) are skipped, because a heading over an empty table reads as a generation bug rather than as the provider having nothing to choose. Services that also accept a self-issued credential say the token needs the same access. Replace the hand-maintained provider tables on the self-hosting page with a generated per-app reference: env vars, provider IDs, and scopes grouped by connector. Scopes stay per connector rather than unioned per app, because a union would tell someone who only wants SharePoint to grant their Entra registration the directory-write permissions `microsoft-ad` needs. Scopes shared by every connector of an app are hoisted above the table, which removes 30 repeated Google URLs and 36 Microsoft ones. The hand-maintained version had already drifted: its Microsoft list was missing `microsoft-word`, so a self-hoster following it registered an app that could not connect Word. It also claimed scopes need no pre-declaration, which is wrong for Entra, where every delegated permission must be on the registration and the `microsoft-ad` set needs tenant admin consent. Generation now fails loudly if `oauth.ts` and `scopes.ts` stop agreeing on the service list, if a block points at an unknown OAuth service, or if the self-hosting page loses its generated-block markers. `bun run docs:check` covers all of it.
The Excel block exposes a SharePoint site picker and a drive picker, and
builds `/drives/{driveId}/items/{id}` for arbitrary drives, but the
integration only requested `Files.Read` and `Files.ReadWrite`. Those cover
the signed-in user's own OneDrive, so a workbook in a document library
came back as insufficient privileges and the drive picker's
`GET /sites/{id}/drives` call failed outright.
Add `Files.Read.All`, `Files.ReadWrite.All` and `Sites.Read.All`. Word
already carries the first two for exactly this reason. All are
user-consentable, so this does not push Excel behind admin consent, and
none grants access to anything the signed-in account could not already
open.
Microsoft only grants newly-added scopes on a fresh authorization, so
existing connections must reconnect. No migration needed: the block's
credential selector already computes `requiredScopes` from
`getScopesForService('microsoft-excel')`, so those connections now show
as needing an update on their own.
The shared `Files.*` labels name OneDrive, which reads as the wrong
product on the Excel consent screen, so add Excel-specific wording
alongside the Word overrides that solve the same problem.
… grants Review findings from the first round, all in the generated prose rather than the scope data. Only some self-issued credentials have permissions to pick. An Airtable PAT and a Slack custom bot do; a Wealthbox, Monday or Pipedrive token carries its creating user's access with no picker at all, and a Google service account uses domain-wide delegation over a set the JWT flow filters. Telling every reader to "grant it the same access" sent most of them looking for a setting that does not exist. Say instead that a self-issued credential carries whatever access it was created with. Slack's approval-gated scopes were invisible to a self-hoster. They stay out of the tables so generated output cannot vary by deployment, but anyone who sets `SLACK_EXTENDED_SCOPES` has to add them to the app first, and Slack rejects the entire authorization when the app is not approved for one. Both the integration page and the self-hosting reference now name them and their flag, sourced from a new `ENV_GATED_SCOPES` entry that `getSlackApprovalGatedScopes` also reads, so the prose cannot drift from the grant. `Chat.ReadWrite` and `Mail.ReadWrite` were labelled write-only. Both also read, and these labels show on the consent screen as well as in the docs. Also switch the scopes import in `oauth.ts` to the absolute specifier its sibling OAuth imports use.
Round two returned the same finding on 14 pages: the one sentence covering self-issued credentials still read as if the scope table were something to apply to them. It cannot be one sentence, because the credential models differ in what is true about them. A Google service account is the sharp case. Its access comes from the key and the domain-wide delegation granted in Workspace, and `getServiceAccountToken` strips the two `userinfo` scopes from the JWT because Google rejects user-identity scopes for a service account. Naming those scopes beside that credential describes access it can never hold. A pasted API token is the opposite: most have no permission picker at all and simply carry their creating account's access, so any instruction to grant something sends the reader looking for a setting that does not exist. Carry the `serviceAccountProviderId` through the docs catalog instead of a boolean, and pick the sentence from it. Neither variant tells the reader to match the table. The excluded identity scopes now live in `scopes.ts` and `credential-service.ts` imports them, so the published claim and the filter that makes it true cannot drift apart. The self-hosting reference gains one line saying what every table on it is: the OAuth flow's request, and so what the app registration must allow. A credential issued by hand is not governed by it.
Three review rounds landed on this one sentence, each asking for accuracy the generated form cannot carry. The models genuinely differ: an Airtable PAT and a HubSpot private app are limited by scopes chosen at creation, a Monday token tracks its creator's current permissions rather than a snapshot, an Attio key uses workspace-admin-selected scopes, a Webflow token is bound to one site, Salesforce runs as a configured Run As user, Zoom is a Marketplace app with its own grant, and a Google service account depends on IAM with delegation only when impersonating. Twenty-odd models across thirty-four pages is per-provider prose, and inventing it from a scope table would publish guesses. Remove the sentence. The scope tables are what this PR is for, and they stay exactly as they were. The one general statement worth making survives where it is true and where a reader configuring credentials will meet it: the self-hosting reference already says each table is the OAuth flow's request and that a hand-issued credential carries its own access, which those tables do not govern. Reverts the catalog's `serviceAccountProviderId` field and the `SERVICE_ACCOUNT_EXCLUDED_SCOPES` move along with it, since nothing reads either once the note is gone.
… labels `generateBlockDoc` catches its own errors so one unreadable block does not abandon the other 300, which meant the unknown-OAuth-service guard added here logged and let the run exit 0. A missing page and a successful build is the worst pairing of the two, and CI reads the exit code. Per-block failures are now collected and reported at the end, so the run exits 1 and names the block. Verified by pointing a block at a nonexistent service: exit 1, block named. Individual blocks still do not abort the whole pass. `Connecting X requests these scopes` overstated its own table: a service account or a pasted API token connects the same integration without that flow and is not governed by it. Now `Connecting X through OAuth`. `ChannelMessage.Send` said "Write to Microsoft channels", which reads as general channel write rather than sending a message. `AppRoleAssignment.ReadWrite.All` omitted the read half of the grant. Both show on the consent screen as well as in the docs.
7d41b26 to
69e59f6
Compare
|
@cubic-dev-ai review this PR |
@mzxchandra I have started the AI code review. It will take a few minutes to complete. |
Summary
Docs never said what an OAuth connection actually grants. A user hit the consent screen cold, and a self-hoster registering an app had to guess which permissions to add before anyone could connect. This publishes the real scope sets, straight from the config the runtime uses, and fixes an Excel scope gap found while doing it.
Three commits, each independently valid and
docs:check-clean:1.
refactor(oauth)— extract scope data into a dependency-free module.OAUTH_PROVIDERSimports ~120 React icons and readsenvat module load, so nothing outside Next can read the scopes it declares. Moved the 61 scope arrays out ofoauth.ts, and the label tables plusgetScopeDescriptionout ofutils.ts, intolib/oauth/scopes.tswith zero imports. Behavior-preserving: all 61 scope lists verified identical to staging (the one intentional difference is Excel, in commit 3).2.
feat(docs)— publish the scopes. A## Scopestable on 51 integration pages, plus a generated per-app reference on the self-hosting page (env vars, provider IDs, scopes grouped by connector).3.
fix(excel)— request the SharePoint scopes the file picker needs. See below.The Excel bug
The Excel block exposes a SharePoint site picker (
blocks/blocks/microsoft_excel.ts:795) and a drive picker that callsGET /sites/{id}/drives, and builds/drives/{driveId}/items/{id}for arbitrary drives. But the integration only requestedFiles.Read/Files.ReadWrite, which cover the signed-in user's own OneDrive. A workbook in a document library returned insufficient privileges.Added
Files.Read.All,Files.ReadWrite.All,Sites.Read.All. Word already carries the first two for exactly this reason. All are user-consentable, so Excel does not move behind admin consent.Existing connections must reconnect (Microsoft only grants new scopes on fresh authorization). No migration needed: the block's credential selector already derives
requiredScopesfromgetScopesForService('microsoft-excel'), so stale connections show as needing an update on their own.Drift this replaces
The hand-maintained self-hosting tables were already wrong:
microsoft-word. Anyone following it registered an app that could not connect Word. (Staging hand-patched this and the FAQ counts while this branch was in flight; the generated block supersedes both.)microsoft-adset needs tenant admin consent.Design notes
microsoft-adneeds.Type of Change
Testing
bun run check:audits— 45/45 pass, includingdocs:checkbun run type-check— cleanlib/oauth, the Microsoft blocks, and the connect modalscripts/generate-docs.test.tsdocs:checkverified green at each of the three commits independentlydocs:check, removing the page markers fails generationNew tests cover the module split (the two files must declare the same services, and every table scope must actually be requested), the generated section (env-flag scopes excluded so output cannot vary by deployment, empty-scope services skipped, unknown service throws), and the shared-scope hoisting rule.
Reviewers should focus on: the scope diff in
scopes.tsfor commit 3 (the only intended behavior change), and whether the per-connector grouping on the self-hosting page is the right call versus a union.Checklist
Screenshots/Videos
Integration page (SharePoint) and the self-hosting reference (Google, showing hoisted shared scopes) render as ordinary doc tables; verified locally at
localhost:3001.