agent-messenger 2.0.0 → 2.2.0
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/marketplace.json +14 -1
- package/.claude-plugin/plugin.json +4 -2
- package/.env.template +35 -17
- package/README.md +37 -33
- package/bun.lock +6 -6
- package/dist/package.json +11 -3
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.js +35 -28
- package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/ensure-auth.js +6 -6
- package/dist/src/platforms/channeltalk/ensure-auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts +23 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.js +299 -29
- package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
- package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/auth.js +57 -49
- package/dist/src/platforms/discord/commands/auth.js.map +1 -1
- package/dist/src/platforms/discord/ensure-auth.js +3 -3
- package/dist/src/platforms/discord/ensure-auth.js.map +1 -1
- package/dist/src/platforms/discord/token-extractor.d.ts +6 -1
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/discord/token-extractor.js +167 -14
- package/dist/src/platforms/discord/token-extractor.js.map +1 -1
- package/dist/src/platforms/instagram/client.d.ts +2 -0
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +2 -2
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +107 -14
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.js +57 -11
- package/dist/src/platforms/instagram/ensure-auth.js.map +1 -1
- package/dist/src/platforms/instagram/index.d.ts +1 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +1 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts +44 -0
- package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/instagram/token-extractor.js +407 -0
- package/dist/src/platforms/instagram/token-extractor.js.map +1 -0
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +2 -1
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.js +14 -13
- package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js +2 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
- package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/auth.js +6 -5
- package/dist/src/platforms/line/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/auth.js +11 -10
- package/dist/src/platforms/slack/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/token-extractor.d.ts +9 -0
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/slack/token-extractor.js +300 -23
- package/dist/src/platforms/slack/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +9 -8
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +2 -1
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/token-extractor.d.ts +5 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +161 -29
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/telegram/client.d.ts.map +1 -1
- package/dist/src/platforms/telegram/client.js +25 -7
- package/dist/src/platforms/telegram/client.js.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.js +6 -5
- package/dist/src/platforms/telegram/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/app-config.d.ts +7 -0
- package/dist/src/platforms/webex/app-config.d.ts.map +1 -0
- package/dist/src/platforms/webex/app-config.js +20 -0
- package/dist/src/platforms/webex/app-config.js.map +1 -0
- package/dist/src/platforms/webex/cli.d.ts +5 -0
- package/dist/src/platforms/webex/cli.d.ts.map +1 -0
- package/dist/src/platforms/webex/cli.js +32 -0
- package/dist/src/platforms/webex/cli.js.map +1 -0
- package/dist/src/platforms/webex/client.d.ts +55 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -0
- package/dist/src/platforms/webex/client.js +299 -0
- package/dist/src/platforms/webex/client.js.map +1 -0
- package/dist/src/platforms/webex/commands/auth.d.ts +19 -0
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/auth.js +166 -0
- package/dist/src/platforms/webex/commands/auth.js.map +1 -0
- package/dist/src/platforms/webex/commands/index.d.ts +6 -0
- package/dist/src/platforms/webex/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/index.js +6 -0
- package/dist/src/platforms/webex/commands/index.js.map +1 -0
- package/dist/src/platforms/webex/commands/member.d.ts +7 -0
- package/dist/src/platforms/webex/commands/member.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/member.js +34 -0
- package/dist/src/platforms/webex/commands/member.js.map +1 -0
- package/dist/src/platforms/webex/commands/message.d.ts +26 -0
- package/dist/src/platforms/webex/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/message.js +153 -0
- package/dist/src/platforms/webex/commands/message.js.map +1 -0
- package/dist/src/platforms/webex/commands/snapshot.d.ts +9 -0
- package/dist/src/platforms/webex/commands/snapshot.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/snapshot.js +72 -0
- package/dist/src/platforms/webex/commands/snapshot.js.map +1 -0
- package/dist/src/platforms/webex/commands/space.d.ts +11 -0
- package/dist/src/platforms/webex/commands/space.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/space.js +59 -0
- package/dist/src/platforms/webex/commands/space.js.map +1 -0
- package/dist/src/platforms/webex/credential-manager.d.ts +23 -0
- package/dist/src/platforms/webex/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/webex/credential-manager.js +148 -0
- package/dist/src/platforms/webex/credential-manager.js.map +1 -0
- package/dist/src/platforms/webex/ensure-auth.d.ts +2 -0
- package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -0
- package/dist/src/platforms/webex/ensure-auth.js +36 -0
- package/dist/src/platforms/webex/ensure-auth.js.map +1 -0
- package/dist/src/platforms/webex/index.d.ts +8 -0
- package/dist/src/platforms/webex/index.d.ts.map +1 -0
- package/dist/src/platforms/webex/index.js +6 -0
- package/dist/src/platforms/webex/index.js.map +1 -0
- package/dist/src/platforms/webex/token-extractor.d.ts +28 -0
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/webex/token-extractor.js +344 -0
- package/dist/src/platforms/webex/token-extractor.js.map +1 -0
- package/dist/src/platforms/webex/types.d.ts +127 -0
- package/dist/src/platforms/webex/types.d.ts.map +1 -0
- package/dist/src/platforms/webex/types.js +64 -0
- package/dist/src/platforms/webex/types.js.map +1 -0
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +6 -2
- package/dist/src/platforms/whatsapp/client.js.map +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.d.ts +1 -1
- package/dist/src/shared/utils/error-handler.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.js +3 -2
- package/dist/src/shared/utils/error-handler.js.map +1 -1
- package/dist/src/shared/utils/stderr.d.ts +5 -0
- package/dist/src/shared/utils/stderr.d.ts.map +1 -0
- package/dist/src/shared/utils/stderr.js +18 -0
- package/dist/src/shared/utils/stderr.js.map +1 -0
- package/dist/src/tui/adapters/webex-adapter.d.ts +14 -0
- package/dist/src/tui/adapters/webex-adapter.d.ts.map +1 -0
- package/dist/src/tui/adapters/webex-adapter.js +79 -0
- package/dist/src/tui/adapters/webex-adapter.js.map +1 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +2 -0
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/cli/channeltalk.mdx +7 -7
- package/docs/content/docs/cli/discord.mdx +3 -3
- package/docs/content/docs/cli/instagram.mdx +28 -6
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/slack.mdx +2 -2
- package/docs/content/docs/cli/teams.mdx +6 -4
- package/docs/content/docs/cli/webex.mdx +310 -0
- package/docs/content/docs/sdk/meta.json +1 -1
- package/docs/content/docs/sdk/webex.mdx +260 -0
- package/docs/content/docs/tui.mdx +4 -3
- package/docs/src/app/page.tsx +2 -2
- package/e2e/README.md +132 -8
- package/e2e/channeltalk.e2e.test.ts +2 -7
- package/e2e/channeltalkbot.e2e.test.ts +2 -6
- package/e2e/config.ts +172 -10
- package/e2e/helpers.ts +7 -0
- package/e2e/instagram.e2e.test.ts +97 -0
- package/e2e/kakaotalk.e2e.test.ts +74 -0
- package/e2e/line.e2e.test.ts +92 -0
- package/e2e/teams.e2e.test.ts +46 -1
- package/e2e/telegram.e2e.test.ts +84 -0
- package/e2e/webex.e2e.test.ts +190 -0
- package/e2e/whatsapp.e2e.test.ts +90 -0
- package/e2e/whatsappbot.e2e.test.ts +78 -0
- package/package.json +11 -3
- package/skills/agent-channeltalk/SKILL.md +9 -9
- package/skills/agent-channeltalk/references/authentication.md +21 -18
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +5 -5
- package/skills/agent-discord/references/authentication.md +8 -8
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +51 -9
- package/skills/agent-instagram/references/authentication.md +35 -3
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +5 -5
- package/skills/agent-slack/references/authentication.md +8 -8
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +6 -6
- package/skills/agent-teams/references/authentication.md +8 -8
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +406 -0
- package/skills/agent-webex/references/authentication.md +371 -0
- package/skills/agent-webex/references/common-patterns.md +726 -0
- package/skills/agent-webex/templates/monitor-space.sh +165 -0
- package/skills/agent-webex/templates/post-message.sh +170 -0
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/cli.ts +4 -0
- package/src/platforms/channeltalk/commands/auth.test.ts +5 -5
- package/src/platforms/channeltalk/commands/auth.ts +38 -32
- package/src/platforms/channeltalk/ensure-auth.test.ts +6 -6
- package/src/platforms/channeltalk/ensure-auth.ts +6 -6
- package/src/platforms/channeltalk/token-extractor.test.ts +182 -15
- package/src/platforms/channeltalk/token-extractor.ts +344 -30
- package/src/platforms/discord/commands/auth.test.ts +3 -3
- package/src/platforms/discord/commands/auth.ts +58 -54
- package/src/platforms/discord/ensure-auth.test.ts +3 -3
- package/src/platforms/discord/ensure-auth.ts +3 -3
- package/src/platforms/discord/token-extractor.test.ts +199 -27
- package/src/platforms/discord/token-extractor.ts +190 -17
- package/src/platforms/instagram/client.ts +2 -2
- package/src/platforms/instagram/commands/auth.ts +133 -14
- package/src/platforms/instagram/ensure-auth.ts +63 -12
- package/src/platforms/instagram/index.ts +1 -0
- package/src/platforms/instagram/token-extractor.test.ts +424 -0
- package/src/platforms/instagram/token-extractor.ts +478 -0
- package/src/platforms/kakaotalk/client.ts +3 -1
- package/src/platforms/kakaotalk/commands/auth.ts +14 -13
- package/src/platforms/kakaotalk/protocol/connection.ts +3 -1
- package/src/platforms/line/commands/auth.ts +7 -6
- package/src/platforms/slack/cli.test.ts +6 -5
- package/src/platforms/slack/commands/auth.test.ts +11 -7
- package/src/platforms/slack/commands/auth.ts +11 -10
- package/src/platforms/slack/token-extractor.test.ts +98 -1
- package/src/platforms/slack/token-extractor.ts +338 -26
- package/src/platforms/teams/commands/auth.ts +9 -8
- package/src/platforms/teams/ensure-auth.ts +3 -1
- package/src/platforms/teams/token-extractor.test.ts +136 -17
- package/src/platforms/teams/token-extractor.ts +182 -31
- package/src/platforms/telegram/client.test.ts +134 -0
- package/src/platforms/telegram/client.ts +27 -6
- package/src/platforms/telegram/commands/auth.ts +6 -5
- package/src/platforms/webex/app-config.test.ts +98 -0
- package/src/platforms/webex/app-config.ts +31 -0
- package/src/platforms/webex/cli.test.ts +58 -0
- package/src/platforms/webex/cli.ts +39 -0
- package/src/platforms/webex/client.test.ts +743 -0
- package/src/platforms/webex/client.ts +405 -0
- package/src/platforms/webex/commands/auth.test.ts +222 -0
- package/src/platforms/webex/commands/auth.ts +243 -0
- package/src/platforms/webex/commands/index.ts +5 -0
- package/src/platforms/webex/commands/member.test.ts +112 -0
- package/src/platforms/webex/commands/member.ts +45 -0
- package/src/platforms/webex/commands/message.test.ts +235 -0
- package/src/platforms/webex/commands/message.ts +204 -0
- package/src/platforms/webex/commands/snapshot.test.ts +105 -0
- package/src/platforms/webex/commands/snapshot.ts +91 -0
- package/src/platforms/webex/commands/space.test.ts +216 -0
- package/src/platforms/webex/commands/space.ts +74 -0
- package/src/platforms/webex/credential-manager.test.ts +314 -0
- package/src/platforms/webex/credential-manager.ts +197 -0
- package/src/platforms/webex/ensure-auth.test.ts +89 -0
- package/src/platforms/webex/ensure-auth.ts +38 -0
- package/src/platforms/webex/index.test.ts +25 -0
- package/src/platforms/webex/index.ts +19 -0
- package/src/platforms/webex/token-extractor.test.ts +327 -0
- package/src/platforms/webex/token-extractor.ts +393 -0
- package/src/platforms/webex/types.test.ts +307 -0
- package/src/platforms/webex/types.ts +129 -0
- package/src/platforms/whatsapp/client.ts +11 -7
- package/src/shared/utils/derived-key-cache.ts +1 -1
- package/src/shared/utils/error-handler.ts +4 -2
- package/src/shared/utils/stderr.ts +22 -0
- package/src/tui/adapters/webex-adapter.ts +103 -0
- package/src/tui/app.ts +2 -0
|
@@ -2,6 +2,7 @@ import { Command } from 'commander'
|
|
|
2
2
|
|
|
3
3
|
import { handleError } from '@/shared/utils/error-handler'
|
|
4
4
|
import { formatOutput } from '@/shared/utils/output'
|
|
5
|
+
import { debug } from '@/shared/utils/stderr'
|
|
5
6
|
|
|
6
7
|
import { TeamsClient } from '../client'
|
|
7
8
|
import { TeamsCredentialManager } from '../credential-manager'
|
|
@@ -35,7 +36,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
if (options.debug) {
|
|
38
|
-
|
|
39
|
+
debug('[debug] Extracting Teams tokens from all accounts...')
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
const extracted = await extractor.extract()
|
|
@@ -54,7 +55,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
if (options.debug) {
|
|
57
|
-
|
|
58
|
+
debug(`[debug] Found ${extracted.length} account(s)`)
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
const credManager = new TeamsCredentialManager()
|
|
@@ -67,7 +68,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
67
68
|
|
|
68
69
|
for (const { token, accountType } of extracted) {
|
|
69
70
|
if (options.debug) {
|
|
70
|
-
|
|
71
|
+
debug(`[debug] Validating ${accountType} account token...`)
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
try {
|
|
@@ -76,7 +77,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
76
77
|
const teams = await client.listTeams()
|
|
77
78
|
|
|
78
79
|
if (options.debug) {
|
|
79
|
-
|
|
80
|
+
debug(`[debug] ✓ ${accountType}: ${authInfo.displayName} (${teams.length} team(s))`)
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
const teamMap: Record<string, { team_id: string; team_name: string }> = {}
|
|
@@ -107,7 +108,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
107
108
|
const errorMessage = (error as Error).message
|
|
108
109
|
const is401 = errorMessage.includes('401') || errorMessage.includes('Unauthorized')
|
|
109
110
|
if (options.debug) {
|
|
110
|
-
|
|
111
|
+
debug(`[debug] ✗ ${accountType}: ${errorMessage}`)
|
|
111
112
|
}
|
|
112
113
|
if (extracted.length === 1) {
|
|
113
114
|
console.log(
|
|
@@ -139,7 +140,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
139
140
|
await credManager.saveConfig(config)
|
|
140
141
|
|
|
141
142
|
if (options.debug) {
|
|
142
|
-
|
|
143
|
+
debug('[debug] ✓ Credentials saved')
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
console.log(
|
|
@@ -158,7 +159,7 @@ export async function extractAction(options: { pretty?: boolean; debug?: boolean
|
|
|
158
159
|
|
|
159
160
|
async function extractManualToken(token: string, options: { pretty?: boolean; debug?: boolean }): Promise<void> {
|
|
160
161
|
if (options.debug) {
|
|
161
|
-
|
|
162
|
+
debug(`[debug] Using provided token: ${token.substring(0, 20)}...`)
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
try {
|
|
@@ -202,7 +203,7 @@ async function extractManualToken(token: string, options: { pretty?: boolean; de
|
|
|
202
203
|
await credManager.saveConfig(config)
|
|
203
204
|
|
|
204
205
|
if (options.debug) {
|
|
205
|
-
|
|
206
|
+
debug('[debug] ✓ Credentials saved')
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
console.log(
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { warn } from '@/shared/utils/stderr'
|
|
2
|
+
|
|
1
3
|
import { TeamsClient } from './client'
|
|
2
4
|
import { TeamsCredentialManager } from './credential-manager'
|
|
3
5
|
import { TeamsTokenExtractor } from './token-extractor'
|
|
@@ -47,7 +49,7 @@ export async function ensureTeamsAuth(): Promise<void> {
|
|
|
47
49
|
newConfig.current_account = accountType
|
|
48
50
|
}
|
|
49
51
|
} catch (error) {
|
|
50
|
-
|
|
52
|
+
warn(`[agent-teams] Skipping ${accountType} account: ${(error as Error).message}`)
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -11,10 +11,10 @@ describe('TeamsTokenExtractor', () => {
|
|
|
11
11
|
extractor = new TeamsTokenExtractor()
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
describe('
|
|
15
|
-
test('returns darwin paths on macOS
|
|
14
|
+
describe('getDesktopCookiesPaths', () => {
|
|
15
|
+
test('returns darwin desktop paths on macOS', () => {
|
|
16
16
|
const darwinExtractor = new TeamsTokenExtractor('darwin')
|
|
17
|
-
const paths = darwinExtractor.
|
|
17
|
+
const paths = darwinExtractor.getDesktopCookiesPaths()
|
|
18
18
|
|
|
19
19
|
expect(paths).toEqual([
|
|
20
20
|
{
|
|
@@ -75,9 +75,9 @@ describe('TeamsTokenExtractor', () => {
|
|
|
75
75
|
])
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
-
test('returns linux
|
|
78
|
+
test('returns linux desktop path on Linux', () => {
|
|
79
79
|
const linuxExtractor = new TeamsTokenExtractor('linux')
|
|
80
|
-
const paths = linuxExtractor.
|
|
80
|
+
const paths = linuxExtractor.getDesktopCookiesPaths()
|
|
81
81
|
|
|
82
82
|
expect(paths).toEqual([
|
|
83
83
|
{
|
|
@@ -87,9 +87,9 @@ describe('TeamsTokenExtractor', () => {
|
|
|
87
87
|
])
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
-
test('returns win32 paths on Windows
|
|
90
|
+
test('returns win32 desktop paths on Windows', () => {
|
|
91
91
|
const winExtractor = new TeamsTokenExtractor('win32')
|
|
92
|
-
const paths = winExtractor.
|
|
92
|
+
const paths = winExtractor.getDesktopCookiesPaths()
|
|
93
93
|
|
|
94
94
|
const localAppData = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local')
|
|
95
95
|
const appdata = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming')
|
|
@@ -143,6 +143,105 @@ describe('TeamsTokenExtractor', () => {
|
|
|
143
143
|
])
|
|
144
144
|
})
|
|
145
145
|
|
|
146
|
+
test('returns empty array for unsupported platform', () => {
|
|
147
|
+
const unsupportedExtractor = new TeamsTokenExtractor('freebsd' as NodeJS.Platform)
|
|
148
|
+
expect(unsupportedExtractor.getDesktopCookiesPaths()).toEqual([])
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('getBrowserCookiesPaths', () => {
|
|
153
|
+
test('returns browser cookie paths on macOS (at least Default profile per browser)', () => {
|
|
154
|
+
const darwinExtractor = new TeamsTokenExtractor('darwin')
|
|
155
|
+
const paths = darwinExtractor.getBrowserCookiesPaths()
|
|
156
|
+
|
|
157
|
+
const chromeBase = join(homedir(), 'Library', 'Application Support', 'Google', 'Chrome')
|
|
158
|
+
expect(paths).toContainEqual({
|
|
159
|
+
path: join(chromeBase, 'Default', 'Cookies'),
|
|
160
|
+
accountType: 'work',
|
|
161
|
+
})
|
|
162
|
+
expect(paths).toContainEqual({
|
|
163
|
+
path: join(chromeBase, 'Default', 'Network', 'Cookies'),
|
|
164
|
+
accountType: 'work',
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('returns browser cookie paths on Linux', () => {
|
|
169
|
+
const linuxExtractor = new TeamsTokenExtractor('linux')
|
|
170
|
+
const paths = linuxExtractor.getBrowserCookiesPaths()
|
|
171
|
+
|
|
172
|
+
const chromeBase = join(homedir(), '.config', 'google-chrome')
|
|
173
|
+
expect(paths).toContainEqual({
|
|
174
|
+
path: join(chromeBase, 'Default', 'Cookies'),
|
|
175
|
+
accountType: 'work',
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('returns browser cookie paths on Windows', () => {
|
|
180
|
+
const winExtractor = new TeamsTokenExtractor('win32')
|
|
181
|
+
const paths = winExtractor.getBrowserCookiesPaths()
|
|
182
|
+
|
|
183
|
+
const localAppData = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local')
|
|
184
|
+
const chromeBase = join(localAppData, 'Google', 'Chrome', 'User Data')
|
|
185
|
+
expect(paths).toContainEqual({
|
|
186
|
+
path: join(chromeBase, 'Default', 'Cookies'),
|
|
187
|
+
accountType: 'work',
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('returns empty array for unsupported platform', () => {
|
|
192
|
+
const unsupportedExtractor = new TeamsTokenExtractor('freebsd' as NodeJS.Platform)
|
|
193
|
+
expect(unsupportedExtractor.getBrowserCookiesPaths()).toEqual([])
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('all browser paths have accountType work', () => {
|
|
197
|
+
const darwinExtractor = new TeamsTokenExtractor('darwin')
|
|
198
|
+
const paths = darwinExtractor.getBrowserCookiesPaths()
|
|
199
|
+
expect(paths.every((p) => p.accountType === 'work')).toBe(true)
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe('getTeamsCookiesPaths', () => {
|
|
204
|
+
test('returns darwin paths on macOS with desktop paths first', () => {
|
|
205
|
+
const darwinExtractor = new TeamsTokenExtractor('darwin')
|
|
206
|
+
const paths = darwinExtractor.getTeamsCookiesPaths()
|
|
207
|
+
const desktopPaths = darwinExtractor.getDesktopCookiesPaths()
|
|
208
|
+
|
|
209
|
+
expect(paths.slice(0, desktopPaths.length)).toEqual(desktopPaths)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
test('browser paths come after desktop paths on macOS', () => {
|
|
213
|
+
const darwinExtractor = new TeamsTokenExtractor('darwin')
|
|
214
|
+
const paths = darwinExtractor.getTeamsCookiesPaths()
|
|
215
|
+
const desktopPaths = darwinExtractor.getDesktopCookiesPaths()
|
|
216
|
+
const browserPaths = darwinExtractor.getBrowserCookiesPaths()
|
|
217
|
+
|
|
218
|
+
expect(paths.length).toBe(desktopPaths.length + browserPaths.length)
|
|
219
|
+
expect(paths.slice(desktopPaths.length)).toEqual(browserPaths)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('returns linux paths with desktop first then browser paths', () => {
|
|
223
|
+
const linuxExtractor = new TeamsTokenExtractor('linux')
|
|
224
|
+
const paths = linuxExtractor.getTeamsCookiesPaths()
|
|
225
|
+
const desktopPaths = linuxExtractor.getDesktopCookiesPaths()
|
|
226
|
+
|
|
227
|
+
expect(paths.slice(0, 1)).toEqual([
|
|
228
|
+
{
|
|
229
|
+
path: join(homedir(), '.config', 'Microsoft', 'Microsoft Teams', 'Cookies'),
|
|
230
|
+
accountType: 'work',
|
|
231
|
+
},
|
|
232
|
+
])
|
|
233
|
+
expect(paths.length).toBeGreaterThan(desktopPaths.length)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('returns win32 paths with desktop first then browser paths', () => {
|
|
237
|
+
const winExtractor = new TeamsTokenExtractor('win32')
|
|
238
|
+
const paths = winExtractor.getTeamsCookiesPaths()
|
|
239
|
+
const desktopPaths = winExtractor.getDesktopCookiesPaths()
|
|
240
|
+
|
|
241
|
+
expect(paths.slice(0, desktopPaths.length)).toEqual(desktopPaths)
|
|
242
|
+
expect(paths.length).toBeGreaterThan(desktopPaths.length)
|
|
243
|
+
})
|
|
244
|
+
|
|
146
245
|
test('returns empty array for unsupported platform', () => {
|
|
147
246
|
const unsupportedExtractor = new TeamsTokenExtractor('freebsd' as NodeJS.Platform)
|
|
148
247
|
const paths = unsupportedExtractor.getTeamsCookiesPaths()
|
|
@@ -176,18 +275,38 @@ describe('TeamsTokenExtractor', () => {
|
|
|
176
275
|
})
|
|
177
276
|
|
|
178
277
|
describe('getKeychainVariants', () => {
|
|
179
|
-
test('
|
|
278
|
+
test('includes Teams-specific keychain entries', () => {
|
|
180
279
|
const macExtractor = new TeamsTokenExtractor('darwin')
|
|
280
|
+
const variants = macExtractor.getKeychainVariants()
|
|
181
281
|
|
|
182
|
-
expect(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
282
|
+
expect(variants).toContainEqual({ service: 'Microsoft Teams Safe Storage', account: 'Microsoft Teams' })
|
|
283
|
+
expect(variants).toContainEqual({
|
|
284
|
+
service: 'Microsoft Teams (work or school) Safe Storage',
|
|
285
|
+
account: 'Microsoft Teams (work or school)',
|
|
286
|
+
})
|
|
287
|
+
expect(variants).toContainEqual({ service: 'Microsoft Edge Safe Storage', account: 'Microsoft Edge' })
|
|
288
|
+
expect(variants).toContainEqual({ service: 'Teams Safe Storage', account: 'Teams' })
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test('includes browser keychain entries appended after Teams entries', () => {
|
|
292
|
+
const macExtractor = new TeamsTokenExtractor('darwin')
|
|
293
|
+
const variants = macExtractor.getKeychainVariants()
|
|
294
|
+
|
|
295
|
+
expect(variants).toContainEqual({ service: 'Chrome Safe Storage', account: 'Chrome' })
|
|
296
|
+
expect(variants).toContainEqual({ service: 'Chrome Canary Safe Storage', account: 'Chrome Canary' })
|
|
297
|
+
expect(variants).toContainEqual({ service: 'Arc Safe Storage', account: 'Arc' })
|
|
298
|
+
expect(variants).toContainEqual({ service: 'Brave Safe Storage', account: 'Brave' })
|
|
299
|
+
expect(variants).toContainEqual({ service: 'Vivaldi Safe Storage', account: 'Vivaldi' })
|
|
300
|
+
expect(variants).toContainEqual({ service: 'Chromium Safe Storage', account: 'Chromium' })
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
test('Teams entries come before browser entries', () => {
|
|
304
|
+
const macExtractor = new TeamsTokenExtractor('darwin')
|
|
305
|
+
const variants = macExtractor.getKeychainVariants()
|
|
306
|
+
|
|
307
|
+
const teamsIdx = variants.findIndex((v) => v.service === 'Microsoft Teams Safe Storage')
|
|
308
|
+
const chromeIdx = variants.findIndex((v) => v.service === 'Chrome Safe Storage')
|
|
309
|
+
expect(teamsIdx).toBeLessThan(chromeIdx)
|
|
191
310
|
})
|
|
192
311
|
})
|
|
193
312
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process'
|
|
2
2
|
import { createDecipheriv, pbkdf2Sync } from 'node:crypto'
|
|
3
|
-
import { copyFileSync, existsSync, readFileSync, unlinkSync } from 'node:fs'
|
|
3
|
+
import { copyFileSync, existsSync, readFileSync, readdirSync, unlinkSync } from 'node:fs'
|
|
4
4
|
import { createRequire } from 'node:module'
|
|
5
5
|
import { homedir, tmpdir } from 'node:os'
|
|
6
6
|
import { join } from 'node:path'
|
|
@@ -26,6 +26,13 @@ interface KeychainVariant {
|
|
|
26
26
|
account: string
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
interface BrowserConfig {
|
|
30
|
+
name: string
|
|
31
|
+
darwin: string
|
|
32
|
+
linux: string
|
|
33
|
+
win32: string
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
const TEAMS_PROCESS_NAMES: Record<string, string> = {
|
|
30
37
|
darwin: 'Microsoft Teams',
|
|
31
38
|
win32: 'Teams.exe',
|
|
@@ -41,6 +48,51 @@ const TEAMS_HOST_PATTERNS = [
|
|
|
41
48
|
'.microsoft.com',
|
|
42
49
|
]
|
|
43
50
|
|
|
51
|
+
const BROWSERS: BrowserConfig[] = [
|
|
52
|
+
{
|
|
53
|
+
name: 'Chrome',
|
|
54
|
+
darwin: join('Google', 'Chrome'),
|
|
55
|
+
linux: 'google-chrome',
|
|
56
|
+
win32: join('Google', 'Chrome', 'User Data'),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Chrome Canary',
|
|
60
|
+
darwin: join('Google', 'Chrome Canary'),
|
|
61
|
+
linux: 'google-chrome-unstable',
|
|
62
|
+
win32: join('Google', 'Chrome SxS', 'User Data'),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'Edge',
|
|
66
|
+
darwin: 'Microsoft Edge',
|
|
67
|
+
linux: 'microsoft-edge',
|
|
68
|
+
win32: join('Microsoft', 'Edge', 'User Data'),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'Arc',
|
|
72
|
+
darwin: join('Arc', 'User Data'),
|
|
73
|
+
linux: '',
|
|
74
|
+
win32: join('Arc', 'User Data'),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'Brave',
|
|
78
|
+
darwin: join('BraveSoftware', 'Brave-Browser'),
|
|
79
|
+
linux: join('BraveSoftware', 'Brave-Browser'),
|
|
80
|
+
win32: join('BraveSoftware', 'Brave-Browser', 'User Data'),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'Vivaldi',
|
|
84
|
+
darwin: 'Vivaldi',
|
|
85
|
+
linux: 'vivaldi',
|
|
86
|
+
win32: join('Vivaldi', 'User Data'),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'Chromium',
|
|
90
|
+
darwin: 'Chromium',
|
|
91
|
+
linux: 'chromium',
|
|
92
|
+
win32: join('Chromium', 'User Data'),
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
|
|
44
96
|
export class TeamsTokenExtractor {
|
|
45
97
|
private platform: NodeJS.Platform
|
|
46
98
|
private keyCache: DerivedKeyCache
|
|
@@ -51,7 +103,7 @@ export class TeamsTokenExtractor {
|
|
|
51
103
|
this.keyCache = keyCache ?? new DerivedKeyCache()
|
|
52
104
|
}
|
|
53
105
|
|
|
54
|
-
|
|
106
|
+
getDesktopCookiesPaths(): TeamsCookiePath[] {
|
|
55
107
|
switch (this.platform) {
|
|
56
108
|
case 'darwin': {
|
|
57
109
|
const ebWebViewBase = join(
|
|
@@ -107,6 +159,83 @@ export class TeamsTokenExtractor {
|
|
|
107
159
|
}
|
|
108
160
|
}
|
|
109
161
|
|
|
162
|
+
getBrowserCookiesPaths(): TeamsCookiePath[] {
|
|
163
|
+
const paths: TeamsCookiePath[] = []
|
|
164
|
+
|
|
165
|
+
for (const browser of BROWSERS) {
|
|
166
|
+
const browserBase = this.getBrowserBasePath(browser)
|
|
167
|
+
if (!browserBase) continue
|
|
168
|
+
|
|
169
|
+
const profileDirs = this.discoverProfileDirs(browserBase)
|
|
170
|
+
for (const profileDir of profileDirs) {
|
|
171
|
+
paths.push({ path: join(profileDir, 'Cookies'), accountType: 'work' })
|
|
172
|
+
paths.push({ path: join(profileDir, 'Network', 'Cookies'), accountType: 'work' })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return paths
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
getTeamsCookiesPaths(): TeamsCookiePath[] {
|
|
180
|
+
const desktopPaths = this.getDesktopCookiesPaths()
|
|
181
|
+
const browserPaths = this.getBrowserCookiesPaths()
|
|
182
|
+
return [...desktopPaths, ...browserPaths]
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private getBrowserBasePath(browser: BrowserConfig): string | null {
|
|
186
|
+
let relative: string
|
|
187
|
+
|
|
188
|
+
switch (this.platform) {
|
|
189
|
+
case 'darwin':
|
|
190
|
+
relative = browser.darwin
|
|
191
|
+
if (!relative) return null
|
|
192
|
+
return join(homedir(), 'Library', 'Application Support', relative)
|
|
193
|
+
case 'linux':
|
|
194
|
+
relative = browser.linux
|
|
195
|
+
if (!relative) return null
|
|
196
|
+
return join(homedir(), '.config', relative)
|
|
197
|
+
case 'win32':
|
|
198
|
+
relative = browser.win32
|
|
199
|
+
if (!relative) return null
|
|
200
|
+
return join(
|
|
201
|
+
process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'),
|
|
202
|
+
relative,
|
|
203
|
+
)
|
|
204
|
+
default:
|
|
205
|
+
return null
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private discoverProfileDirs(browserBase: string): string[] {
|
|
210
|
+
const dirs: string[] = []
|
|
211
|
+
|
|
212
|
+
dirs.push(join(browserBase, 'Default'))
|
|
213
|
+
|
|
214
|
+
if (!existsSync(browserBase)) return dirs
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const entries = readdirSync(browserBase, { withFileTypes: true })
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
if (!entry.isDirectory()) continue
|
|
220
|
+
if (!/^Profile \d+$/i.test(entry.name)) continue
|
|
221
|
+
dirs.push(join(browserBase, entry.name))
|
|
222
|
+
}
|
|
223
|
+
} catch {}
|
|
224
|
+
|
|
225
|
+
return dirs
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private findLocalStateForCookiePath(cookiePath: string): string | null {
|
|
229
|
+
const parts = cookiePath.split(/[/\\]/)
|
|
230
|
+
for (let levels = 2; levels <= 4; levels++) {
|
|
231
|
+
if (parts.length < levels) break
|
|
232
|
+
const base = parts.slice(0, parts.length - levels).join('/')
|
|
233
|
+
const candidate = join(base, 'Local State')
|
|
234
|
+
if (existsSync(candidate)) return candidate
|
|
235
|
+
}
|
|
236
|
+
return null
|
|
237
|
+
}
|
|
238
|
+
|
|
110
239
|
getLocalStatePath(): string {
|
|
111
240
|
switch (this.platform) {
|
|
112
241
|
case 'darwin':
|
|
@@ -136,17 +265,21 @@ export class TeamsTokenExtractor {
|
|
|
136
265
|
|
|
137
266
|
getKeychainVariants(): KeychainVariant[] {
|
|
138
267
|
return [
|
|
139
|
-
//
|
|
268
|
+
// Teams-specific keychain entries
|
|
140
269
|
{ service: 'Microsoft Teams Safe Storage', account: 'Microsoft Teams' },
|
|
141
|
-
// Work/school variant
|
|
142
270
|
{
|
|
143
271
|
service: 'Microsoft Teams (work or school) Safe Storage',
|
|
144
272
|
account: 'Microsoft Teams (work or school)',
|
|
145
273
|
},
|
|
146
|
-
// Edge WebView2 fallback
|
|
147
274
|
{ service: 'Microsoft Edge Safe Storage', account: 'Microsoft Edge' },
|
|
148
|
-
// Classic Teams fallback
|
|
149
275
|
{ service: 'Teams Safe Storage', account: 'Teams' },
|
|
276
|
+
// Browser keychain entries for fallback
|
|
277
|
+
{ service: 'Chrome Safe Storage', account: 'Chrome' },
|
|
278
|
+
{ service: 'Chrome Canary Safe Storage', account: 'Chrome Canary' },
|
|
279
|
+
{ service: 'Arc Safe Storage', account: 'Arc' },
|
|
280
|
+
{ service: 'Brave Safe Storage', account: 'Brave' },
|
|
281
|
+
{ service: 'Vivaldi Safe Storage', account: 'Vivaldi' },
|
|
282
|
+
{ service: 'Chromium Safe Storage', account: 'Chromium' },
|
|
150
283
|
]
|
|
151
284
|
}
|
|
152
285
|
|
|
@@ -206,7 +339,13 @@ export class TeamsTokenExtractor {
|
|
|
206
339
|
|
|
207
340
|
try {
|
|
208
341
|
this.copyDatabaseToTemp(dbPath, tempPath)
|
|
209
|
-
|
|
342
|
+
// For Windows: find the Local State relative to the cookie path so browser cookies
|
|
343
|
+
// use the browser's own Local State instead of the Teams app Local State.
|
|
344
|
+
const localStatePath =
|
|
345
|
+
this.platform === 'win32'
|
|
346
|
+
? (this.findLocalStateForCookiePath(dbPath) ?? this.getLocalStatePath())
|
|
347
|
+
: undefined
|
|
348
|
+
const token = await this.extractFromSQLite(tempPath, localStatePath)
|
|
210
349
|
this.cleanupTempFile(tempPath)
|
|
211
350
|
return token
|
|
212
351
|
} catch {
|
|
@@ -230,7 +369,7 @@ export class TeamsTokenExtractor {
|
|
|
230
369
|
}
|
|
231
370
|
}
|
|
232
371
|
|
|
233
|
-
private async extractFromSQLite(dbPath: string): Promise<string | null> {
|
|
372
|
+
private async extractFromSQLite(dbPath: string, localStatePath?: string): Promise<string | null> {
|
|
234
373
|
try {
|
|
235
374
|
for (const hostPattern of TEAMS_HOST_PATTERNS) {
|
|
236
375
|
const sql = `
|
|
@@ -257,7 +396,7 @@ export class TeamsTokenExtractor {
|
|
|
257
396
|
}
|
|
258
397
|
|
|
259
398
|
if (row?.encrypted_value) {
|
|
260
|
-
const decrypted = this.decryptCookie(Buffer.from(row.encrypted_value))
|
|
399
|
+
const decrypted = this.decryptCookie(Buffer.from(row.encrypted_value), localStatePath)
|
|
261
400
|
if (decrypted && this.isValidSkypeToken(decrypted)) {
|
|
262
401
|
return decrypted
|
|
263
402
|
}
|
|
@@ -270,14 +409,14 @@ export class TeamsTokenExtractor {
|
|
|
270
409
|
}
|
|
271
410
|
}
|
|
272
411
|
|
|
273
|
-
private decryptCookie(encryptedValue: Buffer): string | null {
|
|
412
|
+
private decryptCookie(encryptedValue: Buffer, localStatePath?: string): string | null {
|
|
274
413
|
if (!this.isEncryptedValue(encryptedValue)) {
|
|
275
414
|
// Not encrypted, return as-is
|
|
276
415
|
return encryptedValue.toString('utf8')
|
|
277
416
|
}
|
|
278
417
|
|
|
279
418
|
if (this.platform === 'win32') {
|
|
280
|
-
return this.decryptWindowsCookie(encryptedValue)
|
|
419
|
+
return this.decryptWindowsCookie(encryptedValue, localStatePath)
|
|
281
420
|
} else if (this.platform === 'darwin') {
|
|
282
421
|
return this.decryptMacCookie(encryptedValue)
|
|
283
422
|
} else if (this.platform === 'linux') {
|
|
@@ -287,12 +426,12 @@ export class TeamsTokenExtractor {
|
|
|
287
426
|
return null
|
|
288
427
|
}
|
|
289
428
|
|
|
290
|
-
private decryptWindowsCookie(encryptedData: Buffer): string | null {
|
|
429
|
+
private decryptWindowsCookie(encryptedData: Buffer, localStatePath?: string): string | null {
|
|
291
430
|
try {
|
|
292
|
-
const
|
|
293
|
-
if (!existsSync(
|
|
431
|
+
const statePath = localStatePath ?? this.getLocalStatePath()
|
|
432
|
+
if (!existsSync(statePath)) return null
|
|
294
433
|
|
|
295
|
-
const localState = JSON.parse(readFileSync(
|
|
434
|
+
const localState = JSON.parse(readFileSync(statePath, 'utf8'))
|
|
296
435
|
const encryptedKey = Buffer.from(localState.os_crypt.encrypted_key, 'base64')
|
|
297
436
|
|
|
298
437
|
// Remove DPAPI prefix (5 bytes)
|
|
@@ -329,16 +468,20 @@ export class TeamsTokenExtractor {
|
|
|
329
468
|
if (decrypted) return decrypted
|
|
330
469
|
}
|
|
331
470
|
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
471
|
+
for (const variant of this.getKeychainVariants()) {
|
|
472
|
+
const password = this.execSecurityCommand(variant.service, variant.account)
|
|
473
|
+
if (!password) continue
|
|
474
|
+
|
|
475
|
+
const key = pbkdf2Sync(password, 'saltysalt', 1003, 16, 'sha1')
|
|
476
|
+
const decrypted = this.decryptAESCBC(encryptedData, key)
|
|
477
|
+
if (decrypted) {
|
|
478
|
+
this.cachedKey = key
|
|
479
|
+
this.keyCache.set('teams', key).catch(() => {})
|
|
480
|
+
return decrypted
|
|
481
|
+
}
|
|
340
482
|
}
|
|
341
|
-
|
|
483
|
+
|
|
484
|
+
return null
|
|
342
485
|
}
|
|
343
486
|
|
|
344
487
|
private decryptLinuxCookie(encryptedData: Buffer): string | null {
|
|
@@ -348,13 +491,10 @@ export class TeamsTokenExtractor {
|
|
|
348
491
|
}
|
|
349
492
|
|
|
350
493
|
private getKeychainPassword(): string | null {
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
for (const variant of variants) {
|
|
494
|
+
for (const variant of this.getKeychainVariants()) {
|
|
354
495
|
const password = this.execSecurityCommand(variant.service, variant.account)
|
|
355
496
|
if (password) return password
|
|
356
497
|
}
|
|
357
|
-
|
|
358
498
|
return null
|
|
359
499
|
}
|
|
360
500
|
|
|
@@ -363,9 +503,10 @@ export class TeamsTokenExtractor {
|
|
|
363
503
|
// Escape double quotes in service/account to prevent command injection
|
|
364
504
|
const safeService = service.replace(/"/g, '\\"')
|
|
365
505
|
const safeAccount = account.replace(/"/g, '\\"')
|
|
366
|
-
const result = execSync(
|
|
367
|
-
|
|
368
|
-
|
|
506
|
+
const result = execSync(
|
|
507
|
+
`security find-generic-password -s "${safeService}" -a "${safeAccount}" -w 2>/dev/null`,
|
|
508
|
+
{ encoding: 'utf8' },
|
|
509
|
+
)
|
|
369
510
|
return result.trim()
|
|
370
511
|
} catch {
|
|
371
512
|
return null
|
|
@@ -381,6 +522,16 @@ export class TeamsTokenExtractor {
|
|
|
381
522
|
decipher.setAutoPadding(true)
|
|
382
523
|
|
|
383
524
|
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()])
|
|
525
|
+
|
|
526
|
+
// Chromium v130+ prepends a 32-byte integrity hash before the actual cookie value.
|
|
527
|
+
// Detect by checking if the first bytes contain non-printable characters.
|
|
528
|
+
if (decrypted.length > 32) {
|
|
529
|
+
const hasNonPrintablePrefix = decrypted.subarray(0, 32).some((b) => b < 0x20 || b > 0x7e)
|
|
530
|
+
if (hasNonPrintablePrefix) {
|
|
531
|
+
return decrypted.subarray(32).toString('utf8')
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
384
535
|
const decryptedStr = decrypted.toString('utf8')
|
|
385
536
|
|
|
386
537
|
// Chromium v24+ prepends a 32-byte integrity hash before the actual value
|