Per-tenant Slack workspace OAuth connection

Task 8799 · Done · Coder · created · updated

Operator's ask (verbatim): "Let's build it properly. We need an extremely easy way for each tenant to connect the workplace on the org level and then for each user to be able to bind notifications to them." Why: today the Jaah Slack app is installable only in the operator's own workspace. The control plane dials ONE operator-plane `slack` credential (apps/api/app/slack_socket.py `_load_credential`). Every per-user notification posts with ONE designated bot token (apps/api/app/notify_targets.py `_org_slack_bot_token`, setting `notifications_slack_credential_id`). There is no OAuth install flow, no per-workspace token store and no routing by team_id. A tenant whose people live in their own Slack workspace cannot use Slack at all. Background on the current design: docs/integrations/slack.md and docs/integrations/slack-app-manifest.yaml. Requirements: 1. Org-level connect. An org Owner/Admin connects their Slack workspace in one click: an "Add to Slack" button on an org-level page (e.g. org settings / integrations) runs Slack's OAuth v2 install (oauth.v2.access) and returns to the console showing "Connected to <workspace>". No pasting tokens and no creating a Slack app. Disconnect and reconnect are one click each. Gated by the plan switch `slack_enabled` (apps/api/app/plan_terms.py). 2. Per-workspace token storage. Store each installation's bot token (sealed, as credentials are) against the org and its Slack team_id. One workspace per org is enough for v1. Uninstalling the app in Slack (`app_uninstalled` / `tokens_revoked`) marks the org disconnected. 3. One app, many workspaces, still Socket Mode. The single app-level token keeps carrying events for every installed workspace. Route each inbound envelope (events, interactivity, link_shared) by its team_id to that org's installation. The self-echo filter must hold per workspace (the bot user id of each installation). An event from an unknown team is acked and dropped. 4. Egress per org. Every Slack post about an org's task (alerts, question cards, card rewrites, shipped/assigned cards, unfurls, access-request cards, Connect-code replies) uses that org's workspace bot token through the existing seam `slack_egress.slack_api_post`. Cross-org leakage must be impossible: a task's text never posts into another org's workspace. The operator plane keeps working exactly as today. 5. User binding. Profile → Alerts → Connect keeps the one-time-code flow, but against the user's org workspace: the "Open Slack" link points to the org's workspace (slack.com/app_redirect with that team), and a code is redeemed only from the matching team. Better, if Slack allows it: an "Open Slack" deep link that pre-fills the code, so the user needs one click. Show a clear state when the org has not connected a workspace ("Ask an org admin to connect Slack" / a link for admins). 6. Inbound authorization stays as strict as today (docs/integrations/slack.md § Inbound authorization). Additionally, the acting member's team_id must match the thread's org workspace. 7. Slack-side config. Update slack-app-manifest.yaml (redirect_urls for the OAuth callback on the console host; any extra bot events such as app_uninstalled/tokens_revoked). Document in docs/integrations/slack.md the owner steps to enable public distribution (Manage Distribution → redirect URL → Activate Public Distribution), plus the app's client id/secret credential. Socket Mode apps can't be listed on the Slack Marketplace, so distribution is the shareable install link the console button uses. 8. Tenant docs. Add a how-to page on docs.jaah.ai (apps/docs, follow apps/docs/README.md contract): "Connect Slack" for org admins plus "Get alerts in Slack" for members, with screenshots of the live console. Link it from the relevant overviews. Security surface: OAuth, credentials and cross-tenant routing. Run the autonomous security review lane per the runner charter. Out of scope: Slack Marketplace listing; multiple workspaces per org (Enterprise Grid org-wide installs); changing the notification event vocabulary or the card designs; Telegram/Email. Done means: - On a live stack, an org admin of a non-operator org clicks Add to Slack, completes Slack's consent, and sees "Connected to <workspace>". - A member of that org connects their Slack via Profile → Alerts. A task they own parking on a question DMs them a card in THEIR workspace, and a button click answers the task. - A second org connected to a different workspace receives nothing from the first org (a test proves routing by team_id and egress per org). - The operator plane's existing single-app setup still works unchanged. - The tenant docs page passes `python3 tools/docs_coverage.py` and builds. - `./dc check quick` is green.

Outcome

Per-tenant Slack: Add to Slack OAuth per org, team_id routing on one socket, per-org egress (no fallback), org-scoped Connect codes, tenant docs. Quick gate + 2,782 API tests green; review lanes pass. PR #6564 queued. Live Slack consent unverified (needs Slack login). Follow-up draft #8829. Memory: none applied.

Pull request

Artifacts

Activity

  • Created
  • Run
  • Runner started
  • Runner ended (done)