Skip to content

feat(site): add a scope allowlist field to the OAuth2 app form - #29360

Draft
BobbyHo wants to merge 8 commits into
coder-plat-475-admin-oauth-app-scope-allowlistfrom
plat-475-2-form
Draft

BobbyHo wants to merge 8 commits into
coder-plat-475-admin-oauth-app-scope-allowlistfrom
plat-475-2-form

Conversation

@BobbyHo

@BobbyHo BobbyHo commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Adds a scope picker to the OAuth2 app form. UI half of PLAT-475; the API half is #29313.

  • Multi-select over the scope catalog from /api/v2/auth/scopes.
  • The form holds the selection as an array and joins it into the scope string on submit. The page views are unchanged.
  • While the catalog is loading or failed, only the picker is disabled. The other fields stay editable and the app's stored scopes are sent back unchanged, so a catalog error cannot clear an allowlist.
  • Existing apps with no allowlist open with an empty picker and stay unrestricted. Saving them sends scope: "", which changes nothing.
  • Stored scopes that are not in the catalog still show as selected and are preserved on save.

Depends on #29313. Rebase onto main after it merges.

- Multi-select in the OAuth2 app form, backed by /api/v2/auth/scopes.
- Form holds the selection as an array and joins it on submit, so the
  page views keep passing the form payload straight to the API.
- Picker disables while the catalog is loading or errored; the other
  fields stay editable and the app's existing scopes are preserved.
@linear-code

linear-code Bot commented Sep 15, 2026

Copy link
Copy Markdown

PLAT-475

@BobbyHo

BobbyHo commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Chat: Review in progress (15/15 reviewers complete) | View chat
Requested: 2026-09-15 16:57 UTC by @BobbyHo

deep-review v0.9.0 | Round 1 | 34ba077..6bfdd88

Last posted: Round 1, 7 findings (4 P3, 2 Nit, 1 Note), COMMENT. Review

Finding inventory

Finding inventory - PR #29360

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Open oauth2.ts:45 Catalog query omits staleTime; refetches every form mount despite "fixed" comment R1 Nami P3, Meruem Nit, Pariston Nit Yes
CRF-2 P3 Open OAuth2AppForm.tsx:211 emptyIndicator "All scopes selected" shows on any zero-match filter, states a false state R1 Hisoka P3, Leorio P3, Mafuuu Note, Nami Note Yes
CRF-3 P3 Open OAuth2AppForm.tsx:203 Catalog load error disables picker with no retry affordance; must reload page R1 Chopper P3, Leorio Note Yes
CRF-4 P3 Open OAuth2AppForm.test.tsx:66 Stored out-of-catalog scope preservation untested for successfully-loaded catalog R1 Chopper P3, Bisky Note, Mafu-san Note, Pariston Note Yes
CRF-5 Nit Open OAuth2AppForm.tsx:37 onSubmit typed PostOAuth2ProviderAppRequest but edit path feeds a PUT mutation R1 Chopper Nit, Meruem Note Yes
CRF-6 Nit Open entities.ts:4992 Mock comment restates identifier before its one ordering invariant R1 Gon P2 (downgraded) Yes
CRF-7 Note Open OAuth2AppForm.tsx:229 Create path: submit stays enabled on catalog error, app created unrestricted R1 Meruem Note, Hisoka Note Yes

Round log

Round 1

Netero-only first pass: no findings, tsc/biome/vitest all pass. Panel of 14 (Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Nami, ging-ts, ging-react, Gon, Leorio, Komugi, Chopper, Robin, Meruem + Meruem wildcard). No P0-P2 confirmed after cross-check. 4 P3, 2 Nit, 1 Note. Reviewed against 34ba077..6bfdd88.

Cross-check notes:

  • CRF-1: three reviewers converged; severity spread P3/Nit/Nit. Higher wins (comment states an invariant the code does not implement, not just a perf nit).
  • CRF-2: Hisoka reproduced the false state by typing a non-existent scope. Sharper than the empty-catalog-only framing (Mafuuu/Nami Note). Held at P3.
  • CRF-3: folds Leorio's "error text names failure but no corrective action" into Chopper's no-retry finding; same location, same fix.
  • CRF-4: Chopper P3 vs three Notes. Keep-at-P3 argument: the guarantee's realistic trigger (older binary / API-set scope now absent from catalog) is untested and a regression would silently alter the effective grant. Held at P3 per higher-wins; Bisky's structural-sibling reasoning noted.
  • CRF-6: Gon rated P2. Downgraded to Nit: the comment is factually accurate (Mafu-san verified the ordering claim as a trust signal); a single verbose clause on a test mock is a style nit, not a P2 defect. Wrote the keep-at-P2 case first: none holds, no defect consequence.
  • CRF-7: distinct from CRF-3 (create path vs edit path). Both reviewers call it a defensible fail-open tradeoff; kept as Note needing a human decision, not a defect.
  • No findings: ging-ts, ging-react, Komugi, Robin.
About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped UI half of the scope allowlist. The core contract holds under scrutiny: the split/join round-trip, the catalog-error path that refuses to clear an existing allowlist, and the disabled-only-the-picker design all traced correctly and the three vitest cases pass. Test density is high (199 test / 97 production lines) and the safety invariant (a catalog outage cannot silently drop an allowlist) is pinned by both a story and a specific-assertion test. Netero's mechanical pass, ging-ts, ging-react, Komugi, and Robin found nothing.

No P0-P2 after cross-check. 4 P3, 2 Nit, 1 Note. Nothing blocks; the P3s are worth addressing before merge because this code has no follow-up owner.

Themes: one query-cache mismatch where the comment claims immutability the code does not implement (CRF-1, three reviewers converged), one empty-state message that states a false state on a reachable interaction (CRF-2, reproduced by Hisoka), the catalog-error UX offering no retry short of a full reload (CRF-3), and a stated preservation guarantee whose realistic trigger is untested (CRF-4).

As Bisky put it: "every stone I picked up is real ... nothing fake, nothing unrunnable, nothing fragile." The findings below are edges on a genuinely solid change.


site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppForm.tsx:229

Note [CRF-7] On a catalog load failure during create, submit stays enabled and produces an unrestricted app. (Meruem Note, Hisoka Note)

submitDisabled (line 102-103) does not include scopesQuery.isError, so when the catalog fails to load the picker is disabled and empty while the submit button stays active. On the create path (app undefined) the initial scope is [], so an admin who cannot see the catalog can still create an app, and it is created with no allowlist (unrestricted).

The error text makes the failure visible and an empty allowlist is a documented valid state, so this is defensible fail-open behavior, not a regression (without this feature all apps are already unrestricted). Flagging it as a human decision: if an allowlist should be settable before an app is created, block submit while scopesQuery.isError on the create path; otherwise this is fine as-is.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread site/src/api/queries/oauth2.ts
Comment thread site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppForm.tsx Outdated
Comment thread site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppForm.tsx Outdated
Comment thread site/src/testHelpers/entities.ts Outdated
- Spread disabledRefetchOptions so a fetched catalog is kept for the
  session, matching the comment that says it never changes.
- A failed fetch still retries on the next mount.
…load

- Without it the picker stayed disabled until a full page reload, which
  also drops any unsaved edits on the form.
- Test covers retry followed by a successful selection and submit.
- The create page sends the form output as a POST and the edit page
  as a PUT, so the callback type is now the intersection of both
  request types instead of the POST type alone.
- Trim the scope catalog fixture comment.
- The catalog loads, omits one of the app's stored scopes, and a
  name-only save still sends that scope back.
- The existing coverage only exercised a catalog that failed to load.
- "All scopes selected" also showed when a typed search matched
  nothing, or when the catalog was empty. "No matching scopes" is true
  in every case the dropdown is empty.
…cope-allowlist' into plat-475-2-form

# Conflicts:
#	site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.stories.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant