Skip to content

feat(webhooks): allow org members read-only access to org webhooks#1043

Merged
sejori merged 2 commits into
mainfrom
feat/webhooks-readonly-org-members
May 5, 2026
Merged

feat(webhooks): allow org members read-only access to org webhooks#1043
sejori merged 2 commits into
mainfrom
feat/webhooks-readonly-org-members

Conversation

@sejori
Copy link
Copy Markdown
Contributor

@sejori sejori commented May 5, 2026

Summary

Currently, only owner/admin members of an org can see the org's webhooks. Plain members have no way to spot when a webhook has been auto-disabled by the circuit breaker or when deliveries are failing — they can't notify an admin without first noticing something has gone wrong.

This PR adds a read-only path:

  • Backend (dwctl/src/api/handlers/webhooks.rs): list_webhooks and get_webhook fall through to is_org_member when the caller has no direct/own permission. Org-scoped webhooks live at user_id = org_id, so this lets any member of that org user read the list. Mutating handlers (create, update, delete, rotate_secret) keep the existing can_manage_org_resource check, so writes still require owner/admin.
  • Frontend (NotificationSettings): new readOnly prop hides email + low-balance toggles, the Add Webhook button, and the per-row Switch/Edit/Delete controls. Renders the same webhook list — URL, description, scope, event types, enabled state, auto-disable warning — without any controls.
  • Frontend (MyOrganization): always renders NotificationSettings, passes readOnly={!canManage}. Owners/admins see no change; plain members now see a read-only "Webhooks" card.

Out of scope (intentional)

  • Platform-scope webhooks. Org members should not see platform-scope webhooks (created by PlatformManagers, fire on platform-wide events). Since the existing per-user list endpoint only returns webhooks owned by user_id, and the dashboard only ever requests user_id = active_organization_id, members never see platform-scope webhooks through this path.
  • Cross-org isolation. The is_org_member check is keyed on the org user being requested, so a member of org A cannot enumerate org B's webhooks.
  • Other members' personal webhooks. Members only see webhooks owned by the org user itself, not webhooks personally owned by other members.
  • Secrets. The existing WebhookResponse type omits the secret field; only create and rotate-secret ever return WebhookWithSecretResponse, and both remain admin-only.

Test plan

Backend (8 new integration tests in dwctl/src/api/handlers/webhooks.rs):

  • test_org_member_can_list_org_webhooks_without_secrets — member lists the org's webhooks; raw JSON has no secret field
  • test_org_member_can_get_single_org_webhook — single-webhook GET also works and omits secret
  • test_non_member_cannot_list_org_webhooks — outsider gets 403
  • test_org_member_cannot_create_org_webhook — POST → 403
  • test_org_member_cannot_update_org_webhook — PATCH → 403
  • test_org_member_cannot_delete_org_webhook — DELETE → 403
  • test_org_member_cannot_rotate_org_webhook_secret — POST /rotate-secret → 403
  • test_org_admin_can_still_manage_webhooks — regression guard for the existing owner/admin write path

Frontend:

  • MyOrganization.test.tsx — updated the "regular member" case to assert the "Webhooks" heading is present and admin-only controls (Add webhook, Email notifications switch) are absent. Owner/admin tests unchanged and still pass.
  • Full dashboard suite (498 tests) green; tsc -b --noEmit and eslint --max-warnings 0 clean.
  • Manual browser smoke test — not done; recommend a quick eyeball before merge with a member-role user on the My Organization page.

Org members previously had no visibility into webhooks owned by the
organization, leaving them unable to spot delivery failures and notify
admins. Add a read-only path: list_webhooks and get_webhook fall through
to is_org_member when no direct or own-resource permission applies.
Mutating handlers (create/update/delete/rotate-secret) keep the existing
can_manage_org_resource check, so writes still require owner/admin.

Frontend: NotificationSettings gains a readOnly prop that hides email
and low-balance toggles, the Add Webhook button, and the per-row
Switch/Edit/Delete controls. MyOrganization now always renders the
panel and passes readOnly={!canManage}, so members see the same webhook
list (URL, events, scope, enabled state, auto-disable warning) without
any controls or secrets.
Copilot AI review requested due to automatic review settings May 5, 2026 11:26
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 5, 2026

Deploying control-layer with  Cloudflare Pages  Cloudflare Pages

Latest commit: 513833d
Status: ✅  Deploy successful!
Preview URL: https://6838df98.control-layer.pages.dev
Branch Preview URL: https://feat-webhooks-readonly-org-m.control-layer.pages.dev

View logs

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends webhook visibility so non-admin organization members can read (list/get) their organization’s webhooks in both the API and dashboard, while keeping all webhook mutations restricted to org owners/admins.

Changes:

  • Backend: list_webhooks / get_webhook now fall back to permissions::is_org_member(...) when the caller lacks ReadAll/ReadOwn.
  • Backend: adds integration tests covering org-member read access, outsider denial, and mutation denial/regression.
  • Frontend: adds a readOnly rendering mode to NotificationSettings and always renders it in MyOrganization (members see a read-only “Webhooks” view).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
dwctl/src/api/handlers/webhooks.rs Relaxes read authorization for org-owned webhooks to any org member; adds integration tests for the new access rules.
dashboard/src/components/features/organizations/MyOrganization.tsx Always renders NotificationSettings, passing readOnly for non-admin members.
dashboard/src/components/features/organizations/MyOrganization.test.tsx Updates member-role test expectations for the new read-only webhooks UI.
dashboard/src/components/features/notifications/NotificationSettings.tsx Adds readOnly mode to hide notification toggles and webhook mutation controls while still showing the webhook list.
Comments suppressed due to low confidence (1)

dashboard/src/components/features/notifications/NotificationSettings.tsx:173

  • In readOnly mode this component still executes useUser(userId), but none of the fetched user fields are rendered/used (email/low-balance controls are hidden and no mutations can be triggered). This adds an unnecessary request for regular org members and could also surface avoidable 403s if user-read rules change. Consider adding an enabled option to useUser (similar to useUsers) and disabling the query when readOnly is true, or splitting the webhook-only view into a smaller component.
export const NotificationSettings: React.FC<NotificationSettingsProps> = ({
  userId,
  showPlatformScope = false,
  isOrganization = false,
  readOnly = false,
}) => {
  const { data: user, refetch: refetchUser } = useUser(userId);
  const updateUserMutation = useUpdateUser();
  const updateOrgMutation = useUpdateOrganization();
  const navigate = useNavigate();

  const { data: webhooks } = useWebhooks(userId);
  const createWebhookMutation = useCreateWebhook();

Comment thread dwctl/src/api/handlers/webhooks.rs Outdated
Comment on lines +57 to +67
.await
.map_err(Error::Database)?;
if !can_org {
if !is_member {
Comment thread dwctl/src/api/handlers/webhooks.rs Outdated
Comment on lines +254 to +264
.await
.map_err(Error::Database)?;
if !can_org {
if !is_member {
- Update OpenAPI descriptions on list_webhooks and get_webhook to mention
  org-member read access, so API consumers aren't misled by stale docs.
- Inline comments now explain the three-tier authorization (admin / own /
  org member) so the rule is documented next to the check.
- Skip the useUser fetch in NotificationSettings when readOnly: every
  consumer of the user object lives inside the email/low-balance section,
  which read-only callers don't render. Adds an `enabled` option to
  useUser, mirroring the existing useUsers pattern, so a member viewing
  the org's webhooks doesn't fire a needless GET /users/{org_id}.
@sejori
Copy link
Copy Markdown
Contributor Author

sejori commented May 5, 2026

/build-staging

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Staging image feat-webhooks-readonly-org-members-rc built and pushed. A staging PR has been created in the internal repo — comment /deploy-staging there to deploy.

@sejori sejori merged commit 4bf5b54 into main May 5, 2026
7 checks passed
sejori pushed a commit that referenced this pull request May 5, 2026
🤖 I have created a release *beep* *boop*
---


##
[8.46.0](v8.45.0...v8.46.0)
(2026-05-05)


### Features

* **webhooks:** allow org members read-only access to org webhooks
([#1043](#1043))
([4bf5b54](4bf5b54))


### Bug Fixes

* **deps:** clear all 36 open Dependabot vulnerabilities
([#1045](#1045))
([fa79316](fa79316))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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.

2 participants