agent-messenger 2.32.0 → 2.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bun.lock +7 -0
- package/dist/package.json +2 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.js +67 -18
- package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
- package/dist/src/platforms/discord/commands/channel.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/channel.js +3 -2
- package/dist/src/platforms/discord/commands/channel.js.map +1 -1
- package/dist/src/platforms/discord/commands/snapshot.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/snapshot.js +7 -6
- package/dist/src/platforms/discord/commands/snapshot.js.map +1 -1
- package/dist/src/platforms/discord/types.d.ts +17 -0
- package/dist/src/platforms/discord/types.d.ts.map +1 -1
- package/dist/src/platforms/discord/types.js +36 -0
- package/dist/src/platforms/discord/types.js.map +1 -1
- package/dist/src/platforms/slack/index.d.ts +1 -0
- package/dist/src/platforms/slack/index.d.ts.map +1 -1
- package/dist/src/platforms/slack/index.js.map +1 -1
- package/dist/src/platforms/slack/qr-confirmation.d.ts +19 -0
- package/dist/src/platforms/slack/qr-confirmation.d.ts.map +1 -0
- package/dist/src/platforms/slack/qr-confirmation.js +68 -0
- package/dist/src/platforms/slack/qr-confirmation.js.map +1 -0
- package/dist/src/platforms/slack/qr-cookie-jar.d.ts +7 -0
- package/dist/src/platforms/slack/qr-cookie-jar.d.ts.map +1 -0
- package/dist/src/platforms/slack/qr-cookie-jar.js +48 -0
- package/dist/src/platforms/slack/qr-cookie-jar.js.map +1 -0
- package/dist/src/platforms/slack/qr-http-login.d.ts +2 -1
- package/dist/src/platforms/slack/qr-http-login.d.ts.map +1 -1
- package/dist/src/platforms/slack/qr-http-login.js +88 -53
- package/dist/src/platforms/slack/qr-http-login.js.map +1 -1
- package/docs/content/docs/cli/channeltalk.mdx +4 -4
- package/package.json +2 -1
- package/skills/agent-channeltalk/SKILL.md +5 -5
- package/skills/agent-channeltalk/references/authentication.md +9 -5
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +8 -4
- package/skills/agent-discord/references/common-patterns.md +7 -3
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-imessage/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +25 -2
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/channeltalk/token-extractor.test.ts +235 -0
- package/src/platforms/channeltalk/token-extractor.ts +81 -22
- package/src/platforms/discord/commands/channel.test.ts +33 -23
- package/src/platforms/discord/commands/channel.ts +3 -2
- package/src/platforms/discord/commands/snapshot.test.ts +80 -2
- package/src/platforms/discord/commands/snapshot.ts +7 -6
- package/src/platforms/discord/types.test.ts +45 -0
- package/src/platforms/discord/types.ts +41 -0
- package/src/platforms/slack/index.ts +1 -0
- package/src/platforms/slack/qr-confirmation.ts +86 -0
- package/src/platforms/slack/qr-cookie-jar.ts +54 -0
- package/src/platforms/slack/qr-http-login-2fa.test.ts +398 -0
- package/src/platforms/slack/qr-http-login.test.ts +10 -18
- package/src/platforms/slack/qr-http-login.ts +109 -52
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { SlackError } from './client'
|
|
2
2
|
import { refreshTokenFromWeb } from './ensure-auth'
|
|
3
|
+
import {
|
|
4
|
+
buildConfirmationRequest,
|
|
5
|
+
expectedConfirmationPath,
|
|
6
|
+
parseConfirmationPage,
|
|
7
|
+
type SlackConfirmationCodeRequest,
|
|
8
|
+
} from './qr-confirmation'
|
|
9
|
+
import { setCookieNames, SlackCookieJar } from './qr-cookie-jar'
|
|
3
10
|
import { decodeSlackQr } from './qr-login'
|
|
4
11
|
|
|
5
12
|
export interface QrSession {
|
|
@@ -12,10 +19,9 @@ export interface QrLoginOptions {
|
|
|
12
19
|
fetchImpl?: typeof fetch
|
|
13
20
|
maxRedirects?: number
|
|
14
21
|
debug?: (message: string) => void
|
|
22
|
+
requestConfirmationCode?: SlackConfirmationCodeRequest
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
const BROWSER_USER_AGENT =
|
|
18
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36'
|
|
19
25
|
const DEFAULT_MAX_REDIRECTS = 10
|
|
20
26
|
|
|
21
27
|
export async function loginWithQr(dataUrl: string, options: QrLoginOptions = {}): Promise<QrSession> {
|
|
@@ -23,7 +29,7 @@ export async function loginWithQr(dataUrl: string, options: QrLoginOptions = {})
|
|
|
23
29
|
const debug = options.debug
|
|
24
30
|
debug?.(`Decoded QR for workspace ${login.workspace}`)
|
|
25
31
|
|
|
26
|
-
const { cookie, denialReason, ssoProvider } = await captureDCookie(login
|
|
32
|
+
const { cookie, denialReason, ssoProvider } = await captureDCookie(login, options)
|
|
27
33
|
if (!cookie) {
|
|
28
34
|
throw new SlackError(
|
|
29
35
|
qrSessionFailureMessage(denialReason, { workspace: login.workspace, ssoProvider }),
|
|
@@ -103,16 +109,20 @@ interface CookieCapture {
|
|
|
103
109
|
ssoProvider: string | null
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
async function captureDCookie(
|
|
112
|
+
async function captureDCookie(
|
|
113
|
+
login: ReturnType<typeof decodeSlackQr>,
|
|
114
|
+
options: QrLoginOptions,
|
|
115
|
+
): Promise<CookieCapture> {
|
|
107
116
|
const doFetch = options.fetchImpl ?? fetch
|
|
108
117
|
const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS
|
|
109
118
|
const debug = options.debug
|
|
110
119
|
|
|
111
|
-
let url =
|
|
120
|
+
let url = login.url
|
|
112
121
|
let dCookie: string | null = null
|
|
113
122
|
let sessionDenialReason: string | null = null
|
|
114
123
|
let ssoProvider: string | null = null
|
|
115
|
-
const cookieJar
|
|
124
|
+
const cookieJar = new SlackCookieJar()
|
|
125
|
+
const sessionCookieUrl = tokenUrl(login.workspace)
|
|
116
126
|
|
|
117
127
|
for (let hop = 0; hop < maxRedirects; hop++) {
|
|
118
128
|
// Only ever send captured cookies (including the d session cookie) to Slack
|
|
@@ -123,31 +133,56 @@ async function captureDCookie(startUrl: string, options: QrLoginOptions): Promis
|
|
|
123
133
|
break
|
|
124
134
|
}
|
|
125
135
|
|
|
126
|
-
const response = await doFetch
|
|
127
|
-
|
|
128
|
-
headers: {
|
|
129
|
-
'User-Agent': BROWSER_USER_AGENT,
|
|
130
|
-
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
131
|
-
'Accept-Language': 'en-US,en;q=0.9',
|
|
132
|
-
...(cookieJar.length ? { Cookie: cookieJar.join('; ') } : {}),
|
|
133
|
-
},
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
const cookieNames: string[] = []
|
|
137
|
-
for (const setCookie of getSetCookies(response)) {
|
|
138
|
-
const value = parseDCookie(setCookie)
|
|
139
|
-
if (value) dCookie = value
|
|
140
|
-
const name = setCookie.split('=')[0]?.trim()
|
|
141
|
-
if (name) cookieNames.push(name)
|
|
142
|
-
const pair = setCookie.split(';')[0]?.trim()
|
|
143
|
-
if (pair) cookieJar.push(pair)
|
|
144
|
-
}
|
|
136
|
+
const response = await cookieJar.fetch(doFetch, url)
|
|
137
|
+
dCookie = await cookieJar.sessionCookie(sessionCookieUrl)
|
|
145
138
|
|
|
139
|
+
const cookieNames = setCookieNames(response)
|
|
146
140
|
debug?.(`hop ${hop}: ${response.status} set-cookie=[${cookieNames.join(',') || 'none'}]`)
|
|
147
141
|
|
|
148
142
|
const location = response.status >= 300 && response.status < 400 ? response.headers.get('location') : null
|
|
149
143
|
if (!location) {
|
|
150
|
-
if (!dCookie)
|
|
144
|
+
if (!dCookie) {
|
|
145
|
+
const body = await response.text()
|
|
146
|
+
const props = parseConfirmationPage(body)
|
|
147
|
+
if (props && options.requestConfirmationCode) {
|
|
148
|
+
const request = buildConfirmationRequest(login, url, props, '')
|
|
149
|
+
if (!request) {
|
|
150
|
+
sessionDenialReason = 'invalid_confirmation_origin'
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
const code = await options.requestConfirmationCode({
|
|
154
|
+
email: props.emailAddress,
|
|
155
|
+
type: props.twoFactorType,
|
|
156
|
+
})
|
|
157
|
+
if (code.trim() === '') {
|
|
158
|
+
sessionDenialReason = 'confirmation_failed'
|
|
159
|
+
break
|
|
160
|
+
}
|
|
161
|
+
const confirmedRequest = buildConfirmationRequest(login, url, props, code)
|
|
162
|
+
if (!confirmedRequest) {
|
|
163
|
+
sessionDenialReason = 'invalid_confirmation_origin'
|
|
164
|
+
break
|
|
165
|
+
}
|
|
166
|
+
const confirmed = await cookieJar.fetch(doFetch, confirmedRequest.url, {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
body: confirmedRequest.body,
|
|
169
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
170
|
+
})
|
|
171
|
+
debug?.(`confirmation submit: ${confirmed.status}`)
|
|
172
|
+
dCookie = await followConfirmationRedirects(
|
|
173
|
+
confirmed,
|
|
174
|
+
confirmedRequest.url,
|
|
175
|
+
sessionCookieUrl,
|
|
176
|
+
cookieJar,
|
|
177
|
+
doFetch,
|
|
178
|
+
maxRedirects,
|
|
179
|
+
debug,
|
|
180
|
+
)
|
|
181
|
+
if (!dCookie) sessionDenialReason = 'confirmation_failed'
|
|
182
|
+
} else {
|
|
183
|
+
sessionDenialReason = classifySessionDenialBody(body)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
151
186
|
break
|
|
152
187
|
}
|
|
153
188
|
|
|
@@ -163,12 +198,55 @@ async function captureDCookie(startUrl: string, options: QrLoginOptions): Promis
|
|
|
163
198
|
debug?.(`hop ${hop}: redirect target is not a Slack host (${new URL(next).hostname}), stopping`)
|
|
164
199
|
break
|
|
165
200
|
}
|
|
201
|
+
if (!dCookie && options.requestConfirmationCode && isWorkspaceConfirmationUrl(next, login) === false) {
|
|
202
|
+
sessionDenialReason = 'invalid_confirmation_origin'
|
|
203
|
+
break
|
|
204
|
+
}
|
|
166
205
|
url = next
|
|
167
206
|
}
|
|
168
207
|
|
|
169
208
|
return { cookie: dCookie, denialReason: sessionDenialReason, ssoProvider }
|
|
170
209
|
}
|
|
171
210
|
|
|
211
|
+
function isWorkspaceConfirmationUrl(rawUrl: string, login: ReturnType<typeof decodeSlackQr>): boolean | null {
|
|
212
|
+
const expected = expectedConfirmationPath(login)
|
|
213
|
+
if (!expected) return null
|
|
214
|
+
const url = new URL(rawUrl)
|
|
215
|
+
if (url.pathname !== expected) return null
|
|
216
|
+
return url.hostname === `${login.workspace}.slack.com`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function followConfirmationRedirects(
|
|
220
|
+
response: Response,
|
|
221
|
+
responseUrl: string,
|
|
222
|
+
sessionCookieUrl: string,
|
|
223
|
+
cookieJar: SlackCookieJar,
|
|
224
|
+
doFetch: typeof fetch,
|
|
225
|
+
maxRedirects: number,
|
|
226
|
+
debug?: (message: string) => void,
|
|
227
|
+
): Promise<string | null> {
|
|
228
|
+
let currentResponse = response
|
|
229
|
+
let currentUrl = responseUrl
|
|
230
|
+
let currentSessionCookie = await cookieJar.sessionCookie(sessionCookieUrl)
|
|
231
|
+
for (let hop = 0; hop < maxRedirects; hop++) {
|
|
232
|
+
if (currentSessionCookie) return currentSessionCookie
|
|
233
|
+
const location =
|
|
234
|
+
currentResponse.status >= 300 && currentResponse.status < 400 ? currentResponse.headers.get('location') : null
|
|
235
|
+
if (!location) return null
|
|
236
|
+
const next = new URL(location, currentUrl).toString()
|
|
237
|
+
if (!isSlackHost(next)) return null
|
|
238
|
+
currentUrl = next
|
|
239
|
+
currentResponse = await cookieJar.fetch(doFetch, currentUrl)
|
|
240
|
+
currentSessionCookie = await cookieJar.sessionCookie(sessionCookieUrl)
|
|
241
|
+
debug?.(`confirmation hop ${hop}: ${currentResponse.status}`)
|
|
242
|
+
}
|
|
243
|
+
return currentSessionCookie
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function tokenUrl(workspace: string): string {
|
|
247
|
+
return `https://${workspace}.slack.com/ssb/redirect`
|
|
248
|
+
}
|
|
249
|
+
|
|
172
250
|
function ssoProviderFromUrl(rawUrl: string): string | null {
|
|
173
251
|
let hostname: string
|
|
174
252
|
try {
|
|
@@ -185,19 +263,12 @@ function ssoProviderFromUrl(rawUrl: string): string | null {
|
|
|
185
263
|
return null
|
|
186
264
|
}
|
|
187
265
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return 'link_expired'
|
|
193
|
-
}
|
|
194
|
-
if (/sign in on your other device/i.test(body) || /open Slack/i.test(body) || /confirm/i.test(body)) {
|
|
195
|
-
return 'awaiting_device_confirmation'
|
|
196
|
-
}
|
|
197
|
-
return null
|
|
198
|
-
} catch {
|
|
199
|
-
return null
|
|
266
|
+
function classifySessionDenialBody(body: string): string | null {
|
|
267
|
+
if (/Link Expired/i.test(body) || /trouble signing you in/i.test(body)) return 'link_expired'
|
|
268
|
+
if (/sign in on your other device/i.test(body) || /open Slack/i.test(body) || /confirm/i.test(body)) {
|
|
269
|
+
return 'awaiting_device_confirmation'
|
|
200
270
|
}
|
|
271
|
+
return null
|
|
201
272
|
}
|
|
202
273
|
|
|
203
274
|
export function isSlackHost(rawUrl: string): boolean {
|
|
@@ -209,17 +280,3 @@ export function isSlackHost(rawUrl: string): boolean {
|
|
|
209
280
|
return false
|
|
210
281
|
}
|
|
211
282
|
}
|
|
212
|
-
|
|
213
|
-
export function parseDCookie(setCookieHeader: string): string | null {
|
|
214
|
-
const match = setCookieHeader.match(/(?:^|,\s*)d=(xoxd-[^;]+)/)
|
|
215
|
-
return match ? match[1] : null
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function getSetCookies(response: Response): string[] {
|
|
219
|
-
const withGetter = response.headers as Headers & { getSetCookie?: () => string[] }
|
|
220
|
-
if (typeof withGetter.getSetCookie === 'function') {
|
|
221
|
-
return withGetter.getSetCookie()
|
|
222
|
-
}
|
|
223
|
-
const single = response.headers.get('set-cookie')
|
|
224
|
-
return single ? [single] : []
|
|
225
|
-
}
|