Skip to content

fix(slack): organization Slack integrations are managed by owners and admins - #8236

Merged
iscekic merged 4 commits into
mainfrom
drain/slack-org-rebind-guard
Oct 24, 2026
Merged

iscekic merged 4 commits into
mainfrom
drain/slack-org-rebind-guard

Conversation

@iscekic

@iscekic iscekic commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — local dev stack with real Postgres and owned fake accounts: verified the member and admin organization Slack views and controls in the app, exercised the Slack callback and service suites (31 tests), and confirmed the web typecheck and touched-file lint and format checks pass.

Summary

Organization Slack integrations are managed by organization owners and admins. Members see the connection state and can run the connection test, and the card explains who manages it.

  • apps/web/src/lib/integrations/oauth/platforms/slack-callback.ts, apps/web/src/lib/integrations/oauth/routes.ts, and the organization Slack reinstall route: organization installs are an owner/admin action, using ORGANIZATION_MANAGE_ROLES like other organization-scoped integration routes.
  • apps/web/src/lib/integrations/oauth/common.ts: the shared stateful connect handler redirects with error=permission_required / error=organization_access_required when the caller is not allowed, instead of the generic initialization error.
  • apps/web/src/lib/integrations/slack-service.ts: upsertSlackInstallation takes allowWorkspaceRebind, provided by the callback after the role check.
  • apps/web/src/routers/slack-router.ts: slack.uninstallApp and slack.updateModel are owner/admin actions for organizations.
  • apps/web/src/components/integrations/SlackIntegrationDetails.tsx and IntegrationDetailPage.tsx: members get a read-only card with a short explanation; owners and admins keep the full set of controls.

This does not conflict with the in-flight upsertSlackInstallation credential-store work.

Verification

Local dev stack, real Postgres, fake-account test data: an organization with an owner, an admin, and a member.

  • Member: read-only card, note "Only organization owners and admins can connect, re-install, or disconnect this Slack integration. You can still test the connection.", Test Connection available, no Re-install or Disconnect, model selector disabled.
  • Admin: Re-install, Test Connection, and Disconnect present.
  • Member: organization connect and reinstall entry points land on the page with "Only organization owners and admins can connect or change the organization Slack integration."
  • Admin: organization connect starts the normal Slack authorize flow with the expected scopes.
  • Member: organization slack.uninstallApp and slack.updateModel answer UNAUTHORIZED; slack.testConnection still works.
  • jest src/lib/integrations/slack-service.test.ts src/lib/integrations/oauth/platforms/slack-callback.test.ts: 31 passed (member, owner/admin, a completion without an install purpose, repeated install for the same workspace, personal connection, and the service permission check).
  • tsgo --noEmit for apps/web: exit 0. oxlint and oxfmt --check: clean on touched files.

Screenshots (fake accounts, local non-production stack) are attached in the verification comment.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Role scope: Which org Slack OAuth completions should require an owner/admin role?
    Answer: Every org completion (owner/admin only)
    Confidence: 0.70 · Precedent: Fix the root cause and the class of the issue: session 7f3470e0ba6f, "fix class of issue preventing progress". The work item requires owner/admin for every org Slack OAuth completion.
  2. Question: Fail fast: Should the org Slack connect entry point also require owner/admin (in addition to the callback)?
    Answer: Gate connect and callback
    Confidence: 0.65 · Precedent: Fix the class of the issue on every affected surface: session 75f8833fe8c1, "fix this for all ... on all surfaces". Also: if you see a gap, fix it: session 89824a222897, "if you saw a gap, fix it".
  3. Question: Upsert guard: When an org-owned integration is already bound to a different Slack workspace, what should upsertSlackInstallation do?
    Answer: Allow with an explicit authorized flag
    Confidence: 0.55 · Precedent: Trust the work item. It says a change to a different team id "requires that same role", so an admin may still change it in one flow. Keep the flag compatible with PR fix(slack): read bot credentials from the credential store #8028's upsertSlackInstallation edits.
  4. Question: UI: How much UI change should accompany the server-side policy?
    Answer: Role-aware buttons
    Confidence: 0.50 · Precedent: Hold all UI work to best-in-class standard: session 00eb2a1d3e4b, "design and UX MUST BE BEST-IN-CLASS". Do not show members buttons that are certain to fail.
  5. Question: Member actions: For organization Slack, which actions should be hidden/disabled for a plain member?
    Answer: Install + disconnect + model read-only
    Confidence: 0.65 · Precedent: Autonomy: 'Fix the root cause and the class of the issue ... on every affected surface' (session 00eb2a1d3e4b, 'fix the class'); 'If you see a gap or an error, fix it' (session 89824a222897, 'if you saw a gap, fix it')
  6. Question: Denial copy: Where should the org-denial error code be produced for the connect redirect?
    Answer: Map denial codes generically
    Confidence: 0.70 · Precedent: Autonomy: fix the class on every affected surface (session 75f8833fe8c1, 'fix this for all shared services model pickers on all surfaces'); Engineering: extract shared code (session 5d8113f51c48)

…anges

- The organization Slack OAuth connect entry point and callback require an
  owner or admin, matching the authority other organization-scoped
  integration changes use.
- Organization Slack disconnect and model changes require the same authority.
  Members keep a read-only card that still runs the connection test.
- upsertSlackInstallation takes an explicit permission argument so the bound
  workspace only changes from a flow that verified organization management
  authority.
- Access denials from the shared OAuth connect handler redirect with a
  specific error code rather than a generic initialization failure.

Tests cover a member, an owner/admin, a repeated install for the same
workspace, a personal connection, and the service-level permission check.
@iscekic iscekic added drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening labels Oct 20, 2026
@iscekic

iscekic commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Verified the role-aware organization Slack card and the owner/admin policy on the local dev stack (real Postgres, fake-account test data: one organization with an owner, an admin, and a member, plus one Slack integration for a test workspace).

Observed:

  • Member: read-only card with the note "Only organization owners and admins can connect, re-install, or disconnect this Slack integration. You can still test the connection.", Test Connection available, no Re-install, no Disconnect, model selector disabled.
  • Admin: Re-install, Test Connection, and Disconnect present; the connect endpoint starts the normal Slack authorize redirect.
  • Member: the organization connect endpoint and the reinstall route both land on the page with "Only organization owners and admins can connect or change the organization Slack integration."; slack.uninstallApp and slack.updateModel answer UNAUTHORIZED, while slack.testConnection still works.

Captured from main plus this branch, non-production environment, fake accounts only.

Member view of the organization Slack card: read-only, with the management note and Test Connection only

Owner/admin view of the same card with Re-install, Test Connection, and Disconnect

Comment thread apps/web/src/lib/integrations/slack-service.ts Outdated
Comment thread apps/web/src/lib/integrations/slack-service.test.ts Outdated
Comment thread apps/web/src/components/integrations/SlackIntegrationDetails.tsx Outdated
@kilo-code-bot

kilo-code-bot Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change makes the purpose-less OAuth state fixture real by distinguishing null from the default and asserting the signed payload omits purpose, which resolves the prior suggestion with no new issues.

Files Reviewed (1 file)
  • apps/web/src/lib/integrations/oauth/platforms/slack-callback.test.ts
Previous Review Summaries (3 snapshots, latest commit cf925c9)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit cf925c9)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The single incremental change adds a regression test whose fixture still carries an install purpose, so it duplicates the adjacent case instead of covering the no-purpose branch.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/web/src/lib/integrations/oauth/platforms/slack-callback.test.ts 112 Passing undefined to callCallback still applies the = 'provider_install' default, so the new test does not exercise a state without an install purpose
Files Reviewed (1 file)
  • apps/web/src/lib/integrations/oauth/platforms/slack-callback.test.ts - 1 issue

Fix these issues in Kilo Cloud

Previous review (commit 5ec2f91)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental changes close all three earlier findings: the org rebind guard now falls back to platform_account_id, the test fixture covers the truly unbound and legacy shapes, and the member notice is gated on an active installation.

Overview

The rebind guard now compares existing.platform_installation_id ?? existing.platform_account_id against the incoming team id for organization owners, so legacy rows that record their workspace only in platform_account_id are refused without allowWorkspaceRebind. A new regression test exercises that shape, and the "no workspace yet" fixture was corrected to null both columns. The member alert now appends "You can still test the connection." only when installation && !isSuspended, matching the visibility of TestConnectionButton.

Files Reviewed (3 files)
  • apps/web/src/lib/integrations/slack-service.ts
  • apps/web/src/lib/integrations/slack-service.test.ts
  • apps/web/src/components/integrations/SlackIntegrationDetails.tsx

Previous review (commit 1073790)

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

The owner/admin gating on the Slack connect, callback, router, and UI paths is sound; the remaining gap is that the new service-level rebind guard skips legacy organization rows whose workspace id lives only in platform_account_id.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/integrations/slack-service.ts 271 Rebind guard ignores legacy org rows with a null platform_installation_id, so a bound organization can be moved without allowWorkspaceRebind

SUGGESTION

File Line Issue
apps/web/src/lib/integrations/slack-service.test.ts 506 The "no workspace yet" fixture is actually bound to T123, so it never covers the unbound/legacy-row case
apps/web/src/components/integrations/SlackIntegrationDetails.tsx 337 The member notice says the connection can still be tested, but the test control is hidden when suspended or not connected
Files Reviewed (10 files)
  • apps/web/src/app/(app)/organizations/[id]/integrations/slack/reinstall/route.ts
  • apps/web/src/components/integrations/IntegrationDetailPage.tsx
  • apps/web/src/components/integrations/SlackIntegrationDetails.tsx - 1 issue
  • apps/web/src/lib/integrations/oauth/common.ts
  • apps/web/src/lib/integrations/oauth/platforms/slack-callback.test.ts
  • apps/web/src/lib/integrations/oauth/platforms/slack-callback.ts
  • apps/web/src/lib/integrations/oauth/routes.ts
  • apps/web/src/lib/integrations/slack-service.test.ts - 1 issue
  • apps/web/src/lib/integrations/slack-service.ts - 1 issue
  • apps/web/src/routers/slack-router.ts

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash-1017 · Input: 38.5K · Output: 2.7K · Cached: 291.2K

Review guidance: REVIEW.md from base branch main

- The organization workspace check compares the installation id with the
  account id fallback, so rows that record their workspace only in the
  account id are covered too.
- Add a regression case for that legacy shape and make the unbound fixture
  a row with no workspace identity at all.
- The member notice mentions the connection test only when the test control
  is rendered.
The organization role check runs before the install-purpose and attempt
checks, so the same authority applies to states created without that
purpose. Pin it with a callback case.
Comment thread apps/web/src/lib/integrations/oauth/platforms/slack-callback.test.ts Outdated
The helper defaulted a passed `undefined` back to the install purpose, so the
new case re-ran the purpose-bearing state. Take `null` for "no purpose" and
assert the signed state carries none.
@iscekic
iscekic enabled auto-merge October 20, 2026 03:18
@iscekic iscekic added the merge:human Substantive functional risk requires human review. label Oct 20, 2026
@iscekic
iscekic disabled auto-merge October 22, 2026 12:56
@iscekic iscekic added the intent:ok Janitor change confirmed as a real bug fix, not intended behavior label Oct 23, 2026
@iscekic iscekic added merge:blocked:judge-unavailable Risk assessment unavailable merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:human Substantive functional risk requires human review. and removed merge:human Substantive functional risk requires human review. merge:blocked:judge-unavailable Risk assessment unavailable merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Oct 23, 2026
@iscekic
iscekic added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit d7a0a7d Oct 24, 2026
57 checks passed
@iscekic
iscekic deleted the drain/slack-org-rebind-guard branch October 24, 2026 03:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain intent:ok Janitor change confirmed as a real bug fix, not intended behavior janitor:trust Janitor finding: trust boundary hardening merge:human Substantive functional risk requires human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants