Skip to content

Add slack user level authentication#32

Open
ajinkyasraj wants to merge 6 commits into
mainfrom
worktree-slack-user-connector
Open

Add slack user level authentication#32
ajinkyasraj wants to merge 6 commits into
mainfrom
worktree-slack-user-connector

Conversation

@ajinkyasraj

Copy link
Copy Markdown
Collaborator

Add a user-identity option to the Slack connector

Slack connections can now authenticate as the installing human (a xoxp- user token), in addition to the existing bot identity (xoxb-). This is additive — no bot functionality is removed; a connection simply picks one of two identities via auth_kind.

Why

A bot token only sees channels the app's bot user is invited to and cannot use Slack's search.messages API (Slack rejects bot tokens there). Some agent workflows need a human's full reach — every channel/DM the person can see, plus workspace search. A user-token connection provides that, scoped by a Sieve role + policy.

Identity model

Identity auth_kind Token Reach Acts as
Bot (unchanged) oauth / token xoxb-… channels the bot is invited to; no search the app's bot user
User (new) user_token xoxp-… every channel/DM the user can see + search_messages the installing human

Both identities expose the same curated operation set; they differ in reach and attribution. Bot remains the least-privilege default.

What changed

Connector (internal/connectors/slack)

  • config.go: new KindUserToken constant + UserToken field; validate()/accessToken() branch on the kind (user kind requires an xoxp-/xoxe. prefix).
  • ops.go: search_messages now runs for real on user-token connections (with page→cursor pagination); bot connections still return the typed connector.ErrOperationNotEnabled sentinel (→ HTTP 501 on REST, operation_not_enabled: tool error on MCP).
  • slack.go: declares the user_token setup field so the registry architecture invariant stays satisfied.

Admin UI / web (internal/web)

  • Two new routes: POST /connections/slack/oauth/user/start (OAuth install requesting user_scope, incl. search:read) and POST /connections/slack/user-token (paste a xoxp- User OAuth Token).
  • slackOAuthExchange distinguishes a user install from a bot install by the presence of authed_user.access_token in the oauth.v2.access response — no side-channel flag.
  • Reauth handles re-pasting a user token and re-running the matching OAuth identity (reads the stored auth_kind).
  • connections.html: "Install via OAuth (as user)" button and "Add via user token" form on the Slack card, alongside the existing bot options.

Test mock (internal/testing/mockslack)

  • Serves search.messages for user tokens (xoxp-/xoxe.) and keeps the not_allowed_token_type rejection for bot tokens.

Docs

  • docs/connectors-slack.md, docs/connections-guide.md, and the CLAUDE.md Slack note updated for the bot-vs-user choice, the four install paths, and the revised search_messages behavior.

Testing

  • go build ./... clean.
  • go test ./internal/connectors/slack/ ./internal/web/ ./cmd/sieve/ all pass, including:
    • user-token validate/parse/accessToken cases and end-to-end search_messages (incl. pagination) against the mock;
    • user-token paste (happy + bad-prefix), user OAuth callback persisting auth_kind=user_token;
    • a regression test asserting the bot OAuth path still yields auth_kind=oauth;
    • the registry architecture invariant (user_token declared on Meta()).

Backward compatibility

Fully backward compatible. Existing bot connections (auth_kind oauth/token) are untouched; all existing Slack install/paste/reauth flows behave as before.

Slack connections can now authenticate as the installing human (xoxp-
user token, auth_kind=user_token) in addition to the existing bot
identity (xoxb-, auth_kind=oauth|token). A user connection carries that
person's full permissions and unblocks search_messages, which Slack's
search.messages API only accepts with a user token.

- config.go: KindUserToken constant, UserToken field, validate +
  accessToken branches (require xoxp-/xoxe. prefix).
- ops.go: search_messages runs for real on user-token connections with
  page->cursor pagination; bot connections still return the typed
  ErrOperationNotEnabled sentinel.
- slack.go: declare the user_token setup field so the registry
  architecture invariant stays satisfied.
- mockslack: serve search.messages for user tokens (xoxp-/xoxe.) and
  keep the not_allowed_token_type rejection for bot tokens.
- tests: user-token validate/parse/accessToken cases and end-to-end
  search (incl. pagination) against the mock.
Expose the user-token identity in the admin UI alongside the existing
bot install paths (no bot functionality removed).

- Two new routes: POST /connections/slack/oauth/user/start (OAuth
  install requesting user_scope incl. search:read) and POST
  /connections/slack/user-token (paste an xoxp- User OAuth Token).
- slackOAuthExchange detects a user install by the presence of
  authed_user.access_token in the oauth.v2.access response and persists
  auth_kind=user_token; the bot path is otherwise unchanged.
- Reauth supports re-pasting a user token and re-running the matching
  OAuth identity (reads the stored auth_kind).
- connections.html: "Install via OAuth (as user)" button and "Add via
  user token" form on the Slack card.
- tests: user-token paste happy/bad-prefix, user OAuth callback, a
  regression test asserting the bot OAuth path still yields auth_kind=
  oauth, and card-render checks for the new forms.
- connectors-slack.md: "Two identities" section, two new install
  options (user OAuth + user-token paste), revised search_messages and
  limitations notes, bot-vs-user guidance.
- connections-guide.md: bot-vs-user quick reference and updated search
  caveat.
- CLAUDE.md: update the Slack specifics note (auth_kind identities,
  search gating, the four web install routes, callback discrimination).
@ajinkyasraj ajinkyasraj requested a review from murbard June 22, 2026 08:09
ajinkyasraj and others added 3 commits June 22, 2026 08:16
bot_token was declared Required but not Secret. On the generic edit
form, a Required non-secret field rejects an empty submission ("Bot
token is required"), so editing any Slack connection forced the operator
to re-enter the bot token — and a user_token connection (which has no
bot token) could not be saved at all.

A bot token is a secret credential and should be declared as one:
Secret=true makes an empty edit submission mean "keep stored" and stops
the value from being echoed back into the form. Create still requires it
(unaffected; Slack create is a bespoke flow anyway).

Tests: a user connection now round-trips through the edit form with both
secrets left blank, and the user-token install path rejects agent
bearer tokens (parity with the bot-token path).
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