feat(slack): add userToken for read-only access to DMs and private channels (#981)
- Add userToken and userTokenReadOnly (default: true) config fields
- Implement token routing: reads prefer user token, writes use bot token
- Add tests for token routing logic
- Update documentation with required OAuth scopes
User tokens enable reading DMs and private channels without requiring
bot membership. The userTokenReadOnly flag (true by default) ensures
the user token can only be used for reads, preventing accidental
sends as the user.
Required user token scopes:
- channels:history, channels:read
- groups:history, groups:read
- im:history, im:read
- mpim:history, mpim:read
- users:read, reactions:read, pins:read, emoji:read, search:read
2026-01-15 16:11:33 -08:00
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
|
import { validateConfigObject } from "./config.js";
|
|
|
|
|
|
|
|
|
|
describe("Slack token config fields", () => {
|
|
|
|
|
it("accepts user token config fields", () => {
|
|
|
|
|
const res = validateConfigObject({
|
|
|
|
|
channels: {
|
|
|
|
|
slack: {
|
|
|
|
|
botToken: "xoxb-any",
|
|
|
|
|
appToken: "xapp-any",
|
|
|
|
|
userToken: "xoxp-any",
|
|
|
|
|
userTokenReadOnly: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
expect(res.ok).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("accepts account-level user token config", () => {
|
|
|
|
|
const res = validateConfigObject({
|
|
|
|
|
channels: {
|
|
|
|
|
slack: {
|
|
|
|
|
accounts: {
|
|
|
|
|
work: {
|
|
|
|
|
botToken: "xoxb-any",
|
|
|
|
|
appToken: "xapp-any",
|
|
|
|
|
userToken: "xoxp-any",
|
|
|
|
|
userTokenReadOnly: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
expect(res.ok).toBe(true);
|
|
|
|
|
});
|
2026-02-16 01:45:10 +00:00
|
|
|
|
|
|
|
|
it("rejects invalid userTokenReadOnly types", () => {
|
|
|
|
|
const res = validateConfigObject({
|
|
|
|
|
channels: {
|
|
|
|
|
slack: {
|
|
|
|
|
botToken: "xoxb-any",
|
|
|
|
|
appToken: "xapp-any",
|
|
|
|
|
userToken: "xoxp-any",
|
|
|
|
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
|
|
|
userTokenReadOnly: "no" as any,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
expect(res.ok).toBe(false);
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
expect(res.issues.some((iss) => iss.path.includes("userTokenReadOnly"))).toBe(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("rejects invalid userToken types", () => {
|
|
|
|
|
const res = validateConfigObject({
|
|
|
|
|
channels: {
|
|
|
|
|
slack: {
|
|
|
|
|
botToken: "xoxb-any",
|
|
|
|
|
appToken: "xapp-any",
|
|
|
|
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
|
|
|
userToken: 123 as any,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
expect(res.ok).toBe(false);
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
expect(res.issues.some((iss) => iss.path.includes("userToken"))).toBe(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
feat(slack): add userToken for read-only access to DMs and private channels (#981)
- Add userToken and userTokenReadOnly (default: true) config fields
- Implement token routing: reads prefer user token, writes use bot token
- Add tests for token routing logic
- Update documentation with required OAuth scopes
User tokens enable reading DMs and private channels without requiring
bot membership. The userTokenReadOnly flag (true by default) ensures
the user token can only be used for reads, preventing accidental
sends as the user.
Required user token scopes:
- channels:history, channels:read
- groups:history, groups:read
- im:history, im:read
- mpim:history, mpim:read
- users:read, reactions:read, pins:read, emoji:read, search:read
2026-01-15 16:11:33 -08:00
|
|
|
});
|