fix(slack): use SLACK_USER_TOKEN when connecting to Slack (#28103)
* fix(slack): use SLACK_USER_TOKEN when connecting to Slack (closes #26480) * test(slack): fix account fixture typing for user token source --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -26,6 +26,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/Cron: clarify `cron list` output by renaming `Agent` to `Agent ID` and adding a `Model` column for isolated agent-turn jobs. (#26259)
|
||||
- Cron/Delivery: disable the agent messaging tool when `delivery.mode` is `"none"` so cron output is not sent to Telegram or other channels. (#21808)
|
||||
- Feishu/Reply media attachments: send Feishu reply `mediaUrl`/`mediaUrls` payloads as attachments alongside text/streamed replies in the reply dispatcher, including legacy fallback when `mediaUrls` is empty. (#28959)
|
||||
- Slack/User-token resolution: normalize Slack account user-token sourcing through resolved account metadata (`SLACK_USER_TOKEN` env + config) so monitor reads, Slack actions, directory lookups, onboarding allow-from resolution, and capabilities probing consistently use the effective user token. (#28103) Thanks @Glucksberg.
|
||||
- Feishu/Reaction notifications: add `channels.feishu.reactionNotifications` (`off | own | all`, default `own`) so operators can disable reaction ingress or allow all verified reaction events (not only bot-authored message reactions). (#28529)
|
||||
- Feishu/Outbound session routing: stop assuming bare `oc_` identifiers are always group chats, honor explicit `dm:`/`group:` prefixes for `oc_` chat IDs, and default ambiguous bare `oc_` targets to direct routing to avoid DM session misclassification. (#10407) Thanks @Bermudarat.
|
||||
- Feishu/Group session routing: add configurable group session scopes (`group`, `group_sender`, `group_topic`, `group_topic_sender`) with legacy `topicSessionMode=enabled` compatibility so Feishu group conversations can isolate sessions by sender/topic as configured. (#17798)
|
||||
|
||||
@@ -108,7 +108,7 @@ export async function handleSlackAction(
|
||||
const account = resolveSlackAccount({ cfg, accountId });
|
||||
const actionConfig = account.actions ?? cfg.channels?.slack?.actions;
|
||||
const isActionEnabled = createActionGate(actionConfig);
|
||||
const userToken = account.config.userToken?.trim() || undefined;
|
||||
const userToken = account.userToken;
|
||||
const botToken = account.botToken?.trim();
|
||||
const allowUserWrites = account.config.userTokenReadOnly === false;
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ async function resolveSlackNames(params: {
|
||||
entries: string[];
|
||||
}) {
|
||||
const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const token = account.config.userToken?.trim() || account.botToken?.trim();
|
||||
const token = account.userToken || account.botToken?.trim();
|
||||
if (!token) {
|
||||
return new Map<string, string>();
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ async function promptSlackAllowFrom(params: {
|
||||
defaultAccountId: resolveDefaultSlackAccountId(params.cfg),
|
||||
});
|
||||
const resolved = resolveSlackAccount({ cfg: params.cfg, accountId });
|
||||
const token = resolved.config.userToken ?? resolved.config.botToken ?? "";
|
||||
const token = resolved.userToken ?? resolved.botToken ?? "";
|
||||
const existing =
|
||||
params.cfg.channels?.slack?.allowFrom ?? params.cfg.channels?.slack?.dm?.allowFrom ?? [];
|
||||
const parseId = (value: string) =>
|
||||
|
||||
@@ -90,6 +90,7 @@ describe("channelsCapabilitiesCommand", () => {
|
||||
account: {
|
||||
accountId: "default",
|
||||
botToken: "xoxb-bot",
|
||||
userToken: "xoxp-user",
|
||||
config: { userToken: "xoxp-user" },
|
||||
},
|
||||
probe: { ok: true, bot: { name: "openclaw" }, team: { name: "team" } },
|
||||
|
||||
@@ -381,9 +381,7 @@ async function resolveChannelReports(params: {
|
||||
let slackScopes: ChannelCapabilitiesReport["slackScopes"];
|
||||
if (plugin.id === "slack" && configured && enabled) {
|
||||
const botToken = (resolvedAccount as { botToken?: string }).botToken?.trim();
|
||||
const userToken = (
|
||||
resolvedAccount as { config?: { userToken?: string } }
|
||||
).config?.userToken?.trim();
|
||||
const userToken = (resolvedAccount as { userToken?: string }).userToken?.trim();
|
||||
const scopeReports: NonNullable<ChannelCapabilitiesReport["slackScopes"]> = [];
|
||||
if (botToken) {
|
||||
scopeReports.push({
|
||||
|
||||
@@ -161,9 +161,7 @@ async function resolveSlackChannelType(params: {
|
||||
return "channel";
|
||||
}
|
||||
|
||||
const token =
|
||||
account.botToken?.trim() ||
|
||||
(typeof account.config.userToken === "string" ? account.config.userToken.trim() : "");
|
||||
const token = account.botToken?.trim() || account.userToken || "";
|
||||
if (!token) {
|
||||
SLACK_CHANNEL_TYPE_CACHE.set(`${account.accountId}:${channelId}`, "unknown");
|
||||
return "unknown";
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { SlackAccountConfig } from "../config/types.js";
|
||||
import { resolveAccountEntry } from "../routing/account-lookup.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||
import { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
|
||||
import { resolveSlackAppToken, resolveSlackBotToken, resolveSlackUserToken } from "./token.js";
|
||||
|
||||
export type SlackTokenSource = "env" | "config" | "none";
|
||||
|
||||
@@ -14,8 +14,10 @@ export type ResolvedSlackAccount = {
|
||||
name?: string;
|
||||
botToken?: string;
|
||||
appToken?: string;
|
||||
userToken?: string;
|
||||
botTokenSource: SlackTokenSource;
|
||||
appTokenSource: SlackTokenSource;
|
||||
userTokenSource: SlackTokenSource;
|
||||
config: SlackAccountConfig;
|
||||
groupPolicy?: SlackAccountConfig["groupPolicy"];
|
||||
textChunkLimit?: SlackAccountConfig["textChunkLimit"];
|
||||
@@ -61,12 +63,16 @@ export function resolveSlackAccount(params: {
|
||||
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
|
||||
const envBot = allowEnv ? resolveSlackBotToken(process.env.SLACK_BOT_TOKEN) : undefined;
|
||||
const envApp = allowEnv ? resolveSlackAppToken(process.env.SLACK_APP_TOKEN) : undefined;
|
||||
const envUser = allowEnv ? resolveSlackUserToken(process.env.SLACK_USER_TOKEN) : undefined;
|
||||
const configBot = resolveSlackBotToken(merged.botToken);
|
||||
const configApp = resolveSlackAppToken(merged.appToken);
|
||||
const configUser = resolveSlackUserToken(merged.userToken);
|
||||
const botToken = configBot ?? envBot;
|
||||
const appToken = configApp ?? envApp;
|
||||
const userToken = configUser ?? envUser;
|
||||
const botTokenSource: SlackTokenSource = configBot ? "config" : envBot ? "env" : "none";
|
||||
const appTokenSource: SlackTokenSource = configApp ? "config" : envApp ? "env" : "none";
|
||||
const userTokenSource: SlackTokenSource = configUser ? "config" : envUser ? "env" : "none";
|
||||
|
||||
return {
|
||||
accountId,
|
||||
@@ -74,8 +80,10 @@ export function resolveSlackAccount(params: {
|
||||
name: merged.name?.trim() || undefined,
|
||||
botToken,
|
||||
appToken,
|
||||
userToken,
|
||||
botTokenSource,
|
||||
appTokenSource,
|
||||
userTokenSource,
|
||||
config: merged,
|
||||
groupPolicy: merged.groupPolicy,
|
||||
textChunkLimit: merged.textChunkLimit,
|
||||
|
||||
@@ -36,8 +36,7 @@ type SlackListChannelsResponse = {
|
||||
|
||||
function resolveReadToken(params: DirectoryConfigParams): string | undefined {
|
||||
const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
const userToken = account.config.userToken?.trim() || undefined;
|
||||
return userToken ?? account.botToken?.trim();
|
||||
return account.userToken ?? account.botToken?.trim();
|
||||
}
|
||||
|
||||
function normalizeQuery(value?: string | null): string {
|
||||
|
||||
@@ -101,6 +101,7 @@ describe("slack prepareSlackMessage inbound contract", () => {
|
||||
enabled: true,
|
||||
botTokenSource: "config",
|
||||
appTokenSource: "config",
|
||||
userTokenSource: "none",
|
||||
config: {},
|
||||
};
|
||||
|
||||
@@ -119,6 +120,7 @@ describe("slack prepareSlackMessage inbound contract", () => {
|
||||
enabled: true,
|
||||
botTokenSource: "config",
|
||||
appTokenSource: "config",
|
||||
userTokenSource: "none",
|
||||
config,
|
||||
replyToMode: config.replyToMode,
|
||||
replyToModeByChatType: config.replyToModeByChatType,
|
||||
@@ -165,6 +167,7 @@ describe("slack prepareSlackMessage inbound contract", () => {
|
||||
enabled: true,
|
||||
botTokenSource: "config",
|
||||
appTokenSource: "config",
|
||||
userTokenSource: "none",
|
||||
config: {
|
||||
replyToMode: "all",
|
||||
thread: { initialHistoryLimit: 20 },
|
||||
@@ -378,6 +381,7 @@ describe("slack prepareSlackMessage inbound contract", () => {
|
||||
enabled: true,
|
||||
botTokenSource: "config",
|
||||
appTokenSource: "config",
|
||||
userTokenSource: "none",
|
||||
config: {},
|
||||
};
|
||||
|
||||
@@ -461,6 +465,7 @@ describe("slack prepareSlackMessage inbound contract", () => {
|
||||
enabled: true,
|
||||
botTokenSource: "config",
|
||||
appTokenSource: "config",
|
||||
userTokenSource: "none",
|
||||
config: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
log: (message) => runtime.log?.(warn(message)),
|
||||
});
|
||||
|
||||
const resolveToken = slackCfg.userToken?.trim() || botToken;
|
||||
const resolveToken = account.userToken || botToken;
|
||||
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
||||
const reactionMode = slackCfg.reactionNotifications ?? "own";
|
||||
const reactionAllowlist = slackCfg.reactionAllowlist ?? [];
|
||||
|
||||
@@ -10,3 +10,7 @@ export function resolveSlackBotToken(raw?: string): string | undefined {
|
||||
export function resolveSlackAppToken(raw?: string): string | undefined {
|
||||
return normalizeSlackToken(raw);
|
||||
}
|
||||
|
||||
export function resolveSlackUserToken(raw?: string): string | undefined {
|
||||
return normalizeSlackToken(raw);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user