msteams-mcp 0.2.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.
Potentially problematic release.
This version of msteams-mcp might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/__fixtures__/api-responses.d.ts +254 -0
- package/dist/__fixtures__/api-responses.js +245 -0
- package/dist/api/calendar-api.d.ts +66 -0
- package/dist/api/calendar-api.js +179 -0
- package/dist/api/chatsvc-api.d.ts +352 -0
- package/dist/api/chatsvc-api.js +1100 -0
- package/dist/api/csa-api.d.ts +64 -0
- package/dist/api/csa-api.js +200 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.js +7 -0
- package/dist/api/substrate-api.d.ts +50 -0
- package/dist/api/substrate-api.js +305 -0
- package/dist/auth/crypto.d.ts +32 -0
- package/dist/auth/crypto.js +66 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.js +7 -0
- package/dist/auth/session-store.d.ts +87 -0
- package/dist/auth/session-store.js +230 -0
- package/dist/auth/token-extractor.d.ts +185 -0
- package/dist/auth/token-extractor.js +674 -0
- package/dist/auth/token-refresh.d.ts +25 -0
- package/dist/auth/token-refresh.js +85 -0
- package/dist/browser/auth.d.ts +53 -0
- package/dist/browser/auth.js +603 -0
- package/dist/browser/context.d.ts +40 -0
- package/dist/browser/context.js +122 -0
- package/dist/constants.d.ts +104 -0
- package/dist/constants.js +195 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +12 -0
- package/dist/research/auth-research.d.ts +10 -0
- package/dist/research/auth-research.js +175 -0
- package/dist/research/explore.d.ts +11 -0
- package/dist/research/explore.js +270 -0
- package/dist/research/search-research.d.ts +17 -0
- package/dist/research/search-research.js +317 -0
- package/dist/server.d.ts +66 -0
- package/dist/server.js +295 -0
- package/dist/test/debug-search.d.ts +10 -0
- package/dist/test/debug-search.js +147 -0
- package/dist/test/mcp-harness.d.ts +17 -0
- package/dist/test/mcp-harness.js +474 -0
- package/dist/tools/auth-tools.d.ts +26 -0
- package/dist/tools/auth-tools.js +191 -0
- package/dist/tools/index.d.ts +56 -0
- package/dist/tools/index.js +34 -0
- package/dist/tools/meeting-tools.d.ts +33 -0
- package/dist/tools/meeting-tools.js +64 -0
- package/dist/tools/message-tools.d.ts +269 -0
- package/dist/tools/message-tools.js +856 -0
- package/dist/tools/people-tools.d.ts +46 -0
- package/dist/tools/people-tools.js +112 -0
- package/dist/tools/registry.d.ts +23 -0
- package/dist/tools/registry.js +63 -0
- package/dist/tools/search-tools.d.ts +91 -0
- package/dist/tools/search-tools.js +222 -0
- package/dist/types/errors.d.ts +58 -0
- package/dist/types/errors.js +132 -0
- package/dist/types/result.d.ts +43 -0
- package/dist/types/result.js +51 -0
- package/dist/types/server.d.ts +27 -0
- package/dist/types/server.js +7 -0
- package/dist/types/teams.d.ts +85 -0
- package/dist/types/teams.js +4 -0
- package/dist/utils/api-config.d.ts +103 -0
- package/dist/utils/api-config.js +158 -0
- package/dist/utils/auth-guards.d.ts +67 -0
- package/dist/utils/auth-guards.js +147 -0
- package/dist/utils/http.d.ts +29 -0
- package/dist/utils/http.js +112 -0
- package/dist/utils/parsers.d.ts +247 -0
- package/dist/utils/parsers.js +731 -0
- package/dist/utils/parsers.test.d.ts +7 -0
- package/dist/utils/parsers.test.js +511 -0
- package/package.json +62 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encryption utilities for credential storage.
|
|
3
|
+
*
|
|
4
|
+
* Uses machine-specific key derivation to encrypt sensitive data at rest.
|
|
5
|
+
* This is not foolproof security, but significantly raises the bar compared
|
|
6
|
+
* to plaintext storage.
|
|
7
|
+
*/
|
|
8
|
+
import * as crypto from 'crypto';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
/** Algorithm for encryption. */
|
|
11
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
12
|
+
/** Salt for key derivation. */
|
|
13
|
+
const SALT = 'teams-mcp-credential-salt-v1';
|
|
14
|
+
/** Derives an encryption key from machine-specific values. */
|
|
15
|
+
function deriveKey() {
|
|
16
|
+
// Combine hostname and username for machine-specific key
|
|
17
|
+
const machineId = `${os.hostname()}:${os.userInfo().username}`;
|
|
18
|
+
return crypto.scryptSync(machineId, SALT, 32);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Encrypts a string value.
|
|
22
|
+
*/
|
|
23
|
+
export function encrypt(plaintext) {
|
|
24
|
+
const key = deriveKey();
|
|
25
|
+
const iv = crypto.randomBytes(16);
|
|
26
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
27
|
+
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
|
|
28
|
+
encrypted += cipher.final('hex');
|
|
29
|
+
const tag = cipher.getAuthTag();
|
|
30
|
+
return {
|
|
31
|
+
iv: iv.toString('hex'),
|
|
32
|
+
content: encrypted,
|
|
33
|
+
tag: tag.toString('hex'),
|
|
34
|
+
version: 1,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Decrypts encrypted data.
|
|
39
|
+
*
|
|
40
|
+
* @throws Error if decryption fails (wrong machine, corrupted data, etc.)
|
|
41
|
+
*/
|
|
42
|
+
export function decrypt(data) {
|
|
43
|
+
if (data.version !== 1) {
|
|
44
|
+
throw new Error(`Unsupported encryption version: ${data.version}`);
|
|
45
|
+
}
|
|
46
|
+
const key = deriveKey();
|
|
47
|
+
const iv = Buffer.from(data.iv, 'hex');
|
|
48
|
+
const tag = Buffer.from(data.tag, 'hex');
|
|
49
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
50
|
+
decipher.setAuthTag(tag);
|
|
51
|
+
let decrypted = decipher.update(data.content, 'hex', 'utf8');
|
|
52
|
+
decrypted += decipher.final('utf8');
|
|
53
|
+
return decrypted;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Checks if data looks like encrypted format.
|
|
57
|
+
*/
|
|
58
|
+
export function isEncrypted(data) {
|
|
59
|
+
if (!data || typeof data !== 'object')
|
|
60
|
+
return false;
|
|
61
|
+
const obj = data;
|
|
62
|
+
return (typeof obj.iv === 'string' &&
|
|
63
|
+
typeof obj.content === 'string' &&
|
|
64
|
+
typeof obj.tag === 'string' &&
|
|
65
|
+
typeof obj.version === 'number');
|
|
66
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure session state storage.
|
|
3
|
+
*
|
|
4
|
+
* Handles reading and writing session state with:
|
|
5
|
+
* - Encryption at rest
|
|
6
|
+
* - Restricted file permissions
|
|
7
|
+
* - Automatic migration from plaintext
|
|
8
|
+
*
|
|
9
|
+
* Session files are stored in a user-specific config directory (~/.teams-mcp-server/)
|
|
10
|
+
* to ensure consistency regardless of how the server is invoked (npx, global install, etc.).
|
|
11
|
+
*/
|
|
12
|
+
export declare const PROJECT_ROOT: string;
|
|
13
|
+
export declare const CONFIG_DIR: string;
|
|
14
|
+
export declare const USER_DATA_DIR: string;
|
|
15
|
+
export declare const SESSION_STATE_PATH: string;
|
|
16
|
+
export declare const TOKEN_CACHE_PATH: string;
|
|
17
|
+
/** Session state as stored by Playwright. */
|
|
18
|
+
export interface SessionState {
|
|
19
|
+
cookies: Array<{
|
|
20
|
+
name: string;
|
|
21
|
+
value: string;
|
|
22
|
+
domain?: string;
|
|
23
|
+
path?: string;
|
|
24
|
+
expires?: number;
|
|
25
|
+
httpOnly?: boolean;
|
|
26
|
+
secure?: boolean;
|
|
27
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
28
|
+
}>;
|
|
29
|
+
origins: Array<{
|
|
30
|
+
origin: string;
|
|
31
|
+
localStorage: Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
value: string;
|
|
34
|
+
}>;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
/** Token cache structure. */
|
|
38
|
+
export interface TokenCache {
|
|
39
|
+
substrateToken: string;
|
|
40
|
+
substrateTokenExpiry: number;
|
|
41
|
+
extractedAt: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Ensures the user data directory exists.
|
|
45
|
+
*/
|
|
46
|
+
export declare function ensureUserDataDir(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Checks if session state file exists.
|
|
49
|
+
*/
|
|
50
|
+
export declare function hasSessionState(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Reads the session state.
|
|
53
|
+
*/
|
|
54
|
+
export declare function readSessionState(): SessionState | null;
|
|
55
|
+
/**
|
|
56
|
+
* Writes the session state securely.
|
|
57
|
+
*/
|
|
58
|
+
export declare function writeSessionState(state: SessionState): void;
|
|
59
|
+
/**
|
|
60
|
+
* Deletes the session state file.
|
|
61
|
+
*/
|
|
62
|
+
export declare function clearSessionState(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Gets the age of the session state in hours.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getSessionAge(): number | null;
|
|
67
|
+
/**
|
|
68
|
+
* Checks if session is likely expired (>12 hours old).
|
|
69
|
+
*/
|
|
70
|
+
export declare function isSessionLikelyExpired(): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Reads the token cache.
|
|
73
|
+
*/
|
|
74
|
+
export declare function readTokenCache(): TokenCache | null;
|
|
75
|
+
/**
|
|
76
|
+
* Writes the token cache securely.
|
|
77
|
+
*/
|
|
78
|
+
export declare function writeTokenCache(cache: TokenCache): void;
|
|
79
|
+
/**
|
|
80
|
+
* Clears the token cache.
|
|
81
|
+
*/
|
|
82
|
+
export declare function clearTokenCache(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Gets the Teams origin from session state.
|
|
85
|
+
* Checks multiple known Teams domains to support government clouds.
|
|
86
|
+
*/
|
|
87
|
+
export declare function getTeamsOrigin(state: SessionState): SessionState['origins'][number] | null;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure session state storage.
|
|
3
|
+
*
|
|
4
|
+
* Handles reading and writing session state with:
|
|
5
|
+
* - Encryption at rest
|
|
6
|
+
* - Restricted file permissions
|
|
7
|
+
* - Automatic migration from plaintext
|
|
8
|
+
*
|
|
9
|
+
* Session files are stored in a user-specific config directory (~/.teams-mcp-server/)
|
|
10
|
+
* to ensure consistency regardless of how the server is invoked (npx, global install, etc.).
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
import * as os from 'os';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { encrypt, decrypt, isEncrypted } from './crypto.js';
|
|
17
|
+
import { SESSION_EXPIRY_HOURS } from '../constants.js';
|
|
18
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
/**
|
|
20
|
+
* Gets the user's home directory with fallback.
|
|
21
|
+
* os.homedir() can throw in rare edge cases (missing env vars, broken passwd).
|
|
22
|
+
*/
|
|
23
|
+
function getHomeDirSafe() {
|
|
24
|
+
try {
|
|
25
|
+
return os.homedir();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Gets the user-specific config directory for teams-mcp-server.
|
|
33
|
+
* - Windows: %APPDATA%\teams-mcp-server\ (e.g., C:\Users\name\AppData\Roaming\teams-mcp-server\)
|
|
34
|
+
* - macOS/Linux: ~/.teams-mcp-server/
|
|
35
|
+
* - Fallback: ./teams-mcp-server-data/ relative to package (legacy behaviour)
|
|
36
|
+
*/
|
|
37
|
+
function getConfigDir() {
|
|
38
|
+
const homeDir = getHomeDirSafe();
|
|
39
|
+
if (process.platform === 'win32') {
|
|
40
|
+
const appData = process.env.APPDATA || (homeDir ? path.join(homeDir, 'AppData', 'Roaming') : null);
|
|
41
|
+
if (appData) {
|
|
42
|
+
return path.join(appData, 'teams-mcp-server');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else if (homeDir) {
|
|
46
|
+
return path.join(homeDir, '.teams-mcp-server');
|
|
47
|
+
}
|
|
48
|
+
// Fallback to package-relative directory if home directory unavailable
|
|
49
|
+
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
50
|
+
return path.join(projectRoot, 'teams-mcp-server-data');
|
|
51
|
+
}
|
|
52
|
+
export const PROJECT_ROOT = path.resolve(__dirname, '../..');
|
|
53
|
+
export const CONFIG_DIR = getConfigDir();
|
|
54
|
+
export const USER_DATA_DIR = path.join(CONFIG_DIR, '.user-data');
|
|
55
|
+
export const SESSION_STATE_PATH = path.join(CONFIG_DIR, 'session-state.json');
|
|
56
|
+
export const TOKEN_CACHE_PATH = path.join(CONFIG_DIR, 'token-cache.json');
|
|
57
|
+
// Legacy paths for migration
|
|
58
|
+
const LEGACY_SESSION_PATH = path.join(PROJECT_ROOT, 'session-state.json');
|
|
59
|
+
const LEGACY_TOKEN_CACHE_PATH = path.join(PROJECT_ROOT, 'token-cache.json');
|
|
60
|
+
/** File permission mode: owner read/write only. */
|
|
61
|
+
const SECURE_FILE_MODE = 0o600;
|
|
62
|
+
/**
|
|
63
|
+
* Ensures the config directory exists with secure permissions.
|
|
64
|
+
*/
|
|
65
|
+
function ensureConfigDir() {
|
|
66
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
67
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Migrates a file from legacy location to new config directory.
|
|
72
|
+
* Only migrates if legacy exists and new location doesn't.
|
|
73
|
+
*/
|
|
74
|
+
function migrateIfNeeded(legacyPath, newPath) {
|
|
75
|
+
if (fs.existsSync(legacyPath) && !fs.existsSync(newPath)) {
|
|
76
|
+
ensureConfigDir();
|
|
77
|
+
try {
|
|
78
|
+
fs.copyFileSync(legacyPath, newPath);
|
|
79
|
+
fs.chmodSync(newPath, SECURE_FILE_MODE);
|
|
80
|
+
// Remove legacy file after successful copy
|
|
81
|
+
fs.unlinkSync(legacyPath);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
// Log migration errors for debugging, but continue - will just create new session
|
|
85
|
+
console.error(`Failed to migrate ${path.basename(legacyPath)}:`, error instanceof Error ? error.message : error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Ensures the user data directory exists.
|
|
91
|
+
*/
|
|
92
|
+
export function ensureUserDataDir() {
|
|
93
|
+
ensureConfigDir();
|
|
94
|
+
if (!fs.existsSync(USER_DATA_DIR)) {
|
|
95
|
+
fs.mkdirSync(USER_DATA_DIR, { recursive: true, mode: 0o700 });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Writes data securely with encryption and file permissions.
|
|
100
|
+
*/
|
|
101
|
+
function writeSecure(filePath, data) {
|
|
102
|
+
const json = JSON.stringify(data, null, 2);
|
|
103
|
+
const encrypted = encrypt(json);
|
|
104
|
+
fs.writeFileSync(filePath, JSON.stringify(encrypted, null, 2), {
|
|
105
|
+
mode: SECURE_FILE_MODE,
|
|
106
|
+
encoding: 'utf8',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Reads data securely, handling both encrypted and legacy plaintext.
|
|
111
|
+
*/
|
|
112
|
+
function readSecure(filePath) {
|
|
113
|
+
if (!fs.existsSync(filePath)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
118
|
+
const parsed = JSON.parse(content);
|
|
119
|
+
// Check if this is encrypted data
|
|
120
|
+
if (isEncrypted(parsed)) {
|
|
121
|
+
const decrypted = decrypt(parsed);
|
|
122
|
+
return JSON.parse(decrypted);
|
|
123
|
+
}
|
|
124
|
+
// Legacy plaintext - migrate to encrypted
|
|
125
|
+
writeSecure(filePath, parsed);
|
|
126
|
+
return parsed;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// If decryption fails (different machine, corrupted), return null
|
|
130
|
+
console.error(`Failed to read ${filePath}:`, error instanceof Error ? error.message : error);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Checks if session state file exists.
|
|
136
|
+
*/
|
|
137
|
+
export function hasSessionState() {
|
|
138
|
+
migrateIfNeeded(LEGACY_SESSION_PATH, SESSION_STATE_PATH);
|
|
139
|
+
return fs.existsSync(SESSION_STATE_PATH);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Reads the session state.
|
|
143
|
+
*/
|
|
144
|
+
export function readSessionState() {
|
|
145
|
+
migrateIfNeeded(LEGACY_SESSION_PATH, SESSION_STATE_PATH);
|
|
146
|
+
return readSecure(SESSION_STATE_PATH);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Writes the session state securely.
|
|
150
|
+
*/
|
|
151
|
+
export function writeSessionState(state) {
|
|
152
|
+
ensureConfigDir();
|
|
153
|
+
writeSecure(SESSION_STATE_PATH, state);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Deletes the session state file.
|
|
157
|
+
*/
|
|
158
|
+
export function clearSessionState() {
|
|
159
|
+
if (fs.existsSync(SESSION_STATE_PATH)) {
|
|
160
|
+
fs.unlinkSync(SESSION_STATE_PATH);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Gets the age of the session state in hours.
|
|
165
|
+
*/
|
|
166
|
+
export function getSessionAge() {
|
|
167
|
+
if (!hasSessionState()) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const stats = fs.statSync(SESSION_STATE_PATH);
|
|
171
|
+
const ageMs = Date.now() - stats.mtimeMs;
|
|
172
|
+
return ageMs / (1000 * 60 * 60);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Checks if session is likely expired (>12 hours old).
|
|
176
|
+
*/
|
|
177
|
+
export function isSessionLikelyExpired() {
|
|
178
|
+
const age = getSessionAge();
|
|
179
|
+
if (age === null)
|
|
180
|
+
return true;
|
|
181
|
+
return age > SESSION_EXPIRY_HOURS;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Reads the token cache.
|
|
185
|
+
*/
|
|
186
|
+
export function readTokenCache() {
|
|
187
|
+
migrateIfNeeded(LEGACY_TOKEN_CACHE_PATH, TOKEN_CACHE_PATH);
|
|
188
|
+
return readSecure(TOKEN_CACHE_PATH);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Writes the token cache securely.
|
|
192
|
+
*/
|
|
193
|
+
export function writeTokenCache(cache) {
|
|
194
|
+
ensureConfigDir();
|
|
195
|
+
writeSecure(TOKEN_CACHE_PATH, cache);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Clears the token cache.
|
|
199
|
+
*/
|
|
200
|
+
export function clearTokenCache() {
|
|
201
|
+
if (fs.existsSync(TOKEN_CACHE_PATH)) {
|
|
202
|
+
fs.unlinkSync(TOKEN_CACHE_PATH);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Known Teams origins (commercial and government clouds).
|
|
207
|
+
* Used to find the correct origin in session state.
|
|
208
|
+
*/
|
|
209
|
+
const TEAMS_ORIGINS = [
|
|
210
|
+
'https://teams.microsoft.com', // Commercial
|
|
211
|
+
'https://teams.microsoft.us', // GCC-High
|
|
212
|
+
'https://dod.teams.microsoft.us', // DoD
|
|
213
|
+
'https://teams.cloud.microsoft', // New Teams URL
|
|
214
|
+
];
|
|
215
|
+
/**
|
|
216
|
+
* Gets the Teams origin from session state.
|
|
217
|
+
* Checks multiple known Teams domains to support government clouds.
|
|
218
|
+
*/
|
|
219
|
+
export function getTeamsOrigin(state) {
|
|
220
|
+
if (!state.origins)
|
|
221
|
+
return null;
|
|
222
|
+
// Try known Teams origins in priority order
|
|
223
|
+
for (const knownOrigin of TEAMS_ORIGINS) {
|
|
224
|
+
const origin = state.origins.find(o => o.origin === knownOrigin);
|
|
225
|
+
if (origin)
|
|
226
|
+
return origin;
|
|
227
|
+
}
|
|
228
|
+
// Fallback: find any origin containing 'teams.microsoft'
|
|
229
|
+
return state.origins.find(o => o.origin.includes('teams.microsoft') || o.origin.includes('teams.cloud')) ?? null;
|
|
230
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token extraction from session state.
|
|
3
|
+
*
|
|
4
|
+
* Extracts various authentication tokens from Playwright's saved session state.
|
|
5
|
+
* Teams stores MSAL tokens in localStorage; we parse these to get bearer tokens
|
|
6
|
+
* for various APIs (Substrate search, chatsvc messaging, etc.).
|
|
7
|
+
*/
|
|
8
|
+
import { clearTokenCache, type SessionState } from './session-store.js';
|
|
9
|
+
import { type UserProfile } from '../utils/parsers.js';
|
|
10
|
+
/** Substrate search token (for search/people APIs). */
|
|
11
|
+
export interface SubstrateTokenInfo {
|
|
12
|
+
token: string;
|
|
13
|
+
expiry: Date;
|
|
14
|
+
}
|
|
15
|
+
/** Teams chat API token (for chatsvc). */
|
|
16
|
+
export interface TeamsTokenInfo {
|
|
17
|
+
token: string;
|
|
18
|
+
expiry: Date;
|
|
19
|
+
userMri: string;
|
|
20
|
+
}
|
|
21
|
+
/** Cookie-based auth for messaging APIs. */
|
|
22
|
+
export interface MessageAuthInfo {
|
|
23
|
+
skypeToken: string;
|
|
24
|
+
authToken: string;
|
|
25
|
+
userMri: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extracts the Substrate search token from session state.
|
|
29
|
+
* This token is used for search and people APIs.
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractSubstrateToken(state?: SessionState): SubstrateTokenInfo | null;
|
|
32
|
+
/**
|
|
33
|
+
* Gets a valid Substrate token, either from cache or by extracting from session.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getValidSubstrateToken(): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Checks if we have a valid Substrate token.
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasValidSubstrateToken(): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Gets Substrate token status for diagnostics.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getSubstrateTokenStatus(): {
|
|
44
|
+
hasToken: boolean;
|
|
45
|
+
expiresAt?: string;
|
|
46
|
+
minutesRemaining?: number;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Extracts the Teams chat API token from session state.
|
|
50
|
+
*
|
|
51
|
+
* Teams stores multiple tokens for different services. We prefer:
|
|
52
|
+
* 1. chatsvcagg.teams.microsoft.com (primary chat API)
|
|
53
|
+
* 2. api.spaces.skype.com (fallback)
|
|
54
|
+
*/
|
|
55
|
+
export declare function extractTeamsToken(state?: SessionState): TeamsTokenInfo | null;
|
|
56
|
+
/**
|
|
57
|
+
* Extracts the Skype Spaces API token from session state.
|
|
58
|
+
*
|
|
59
|
+
* This token is required for the calendar/meetings API (mt/part endpoints).
|
|
60
|
+
* It has scope: https://api.spaces.skype.com/Authorization.ReadWrite
|
|
61
|
+
*/
|
|
62
|
+
export declare function extractSkypeSpacesToken(state?: SessionState): string | null;
|
|
63
|
+
/** Region configuration from Teams discovery. */
|
|
64
|
+
export interface RegionConfig {
|
|
65
|
+
/** Base region (e.g., "amer", "emea", "apac") - used by chatsvc, csa APIs. */
|
|
66
|
+
region: string;
|
|
67
|
+
/** Partition number (e.g., "02", "01") - only needed for mt/part APIs. */
|
|
68
|
+
partition: string;
|
|
69
|
+
/** Full region with partition (e.g., "amer-02") - for mt/part APIs. */
|
|
70
|
+
regionPartition: string;
|
|
71
|
+
/** Whether this tenant uses partitioned mt/part URLs. */
|
|
72
|
+
hasPartition: boolean;
|
|
73
|
+
/** Full middleTier URL (e.g., "https://teams.microsoft.com/api/mt/part/amer-02"). */
|
|
74
|
+
middleTierUrl: string;
|
|
75
|
+
/** Chat service URL base (e.g., "https://teams.microsoft.com/api/chatsvc/amer"). */
|
|
76
|
+
chatServiceUrl: string;
|
|
77
|
+
/** CSA service URL base (e.g., "https://teams.microsoft.com/api/csa/amer"). */
|
|
78
|
+
csaServiceUrl: string;
|
|
79
|
+
/** Teams base URL (e.g., "https://teams.microsoft.com" or "https://teams.microsoft.us" for GCC). */
|
|
80
|
+
teamsBaseUrl: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Extracts the user's region and partition from the Teams discovery config.
|
|
84
|
+
*
|
|
85
|
+
* Teams stores a DISCOVER-REGION-GTM config in localStorage that contains
|
|
86
|
+
* region-specific URLs for all APIs. There are two formats:
|
|
87
|
+
*
|
|
88
|
+
* **Partitioned (most Enterprise tenants):**
|
|
89
|
+
* - middleTier: "https://teams.microsoft.com/api/mt/part/amer-02"
|
|
90
|
+
* - chatServiceAfd: "https://teams.microsoft.com/api/chatsvc/amer"
|
|
91
|
+
*
|
|
92
|
+
* **Non-partitioned (some tenants, e.g., UK):**
|
|
93
|
+
* - middleTier: "https://teams.microsoft.com/api/mt/emea"
|
|
94
|
+
* - chatServiceAfd: "https://teams.microsoft.com/api/chatsvc/uk"
|
|
95
|
+
*
|
|
96
|
+
* We use the full URLs directly from config rather than reconstructing them,
|
|
97
|
+
* which ensures compatibility with GCC/GCC-High tenants that may use different
|
|
98
|
+
* base URLs (e.g., teams.microsoft.us).
|
|
99
|
+
*/
|
|
100
|
+
export declare function extractRegionConfig(state?: SessionState): RegionConfig | null;
|
|
101
|
+
/** User details from DISCOVER-USER-DETAILS. */
|
|
102
|
+
export interface UserDetails {
|
|
103
|
+
/** User's MRI (e.g., "8:orgid:abc..."). */
|
|
104
|
+
mri: string;
|
|
105
|
+
/** User's region (e.g., "amer", "emea"). */
|
|
106
|
+
region: string;
|
|
107
|
+
/** User's partition (e.g., "amer01"). */
|
|
108
|
+
userPartition: string;
|
|
109
|
+
/** Tenant's partition (e.g., "amer02"). */
|
|
110
|
+
tenantPartition: string;
|
|
111
|
+
/** License details. */
|
|
112
|
+
licenses: {
|
|
113
|
+
isFreemium: boolean;
|
|
114
|
+
isTrial: boolean;
|
|
115
|
+
isTeamsEnabled: boolean;
|
|
116
|
+
isCopilot: boolean;
|
|
117
|
+
isTranscriptEnabled: boolean;
|
|
118
|
+
isFrontline: boolean;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Extracts user details from DISCOVER-USER-DETAILS in localStorage.
|
|
123
|
+
*
|
|
124
|
+
* This provides user-specific info including:
|
|
125
|
+
* - User's MRI
|
|
126
|
+
* - Region and partition info
|
|
127
|
+
* - License details (Copilot, transcription, etc.)
|
|
128
|
+
*/
|
|
129
|
+
export declare function extractUserDetails(state?: SessionState): UserDetails | null;
|
|
130
|
+
/**
|
|
131
|
+
* Extracts authentication info needed for messaging API.
|
|
132
|
+
* Unlike other APIs, messaging uses cookies rather than localStorage tokens.
|
|
133
|
+
*/
|
|
134
|
+
export declare function extractMessageAuth(state?: SessionState): MessageAuthInfo | null;
|
|
135
|
+
/**
|
|
136
|
+
* Gets messaging token status for diagnostics.
|
|
137
|
+
* The skypetoken_asm cookie is a JWT with an exp claim.
|
|
138
|
+
*/
|
|
139
|
+
export declare function getMessageAuthStatus(): {
|
|
140
|
+
hasToken: boolean;
|
|
141
|
+
expiresAt?: string;
|
|
142
|
+
minutesRemaining?: number;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Extracts the CSA token for the conversationFolders API.
|
|
146
|
+
* This searches all origins, not just teams.microsoft.com.
|
|
147
|
+
*/
|
|
148
|
+
export declare function extractCsaToken(state?: SessionState): string | null;
|
|
149
|
+
/**
|
|
150
|
+
* Gets the current user's profile from cached JWT tokens.
|
|
151
|
+
*/
|
|
152
|
+
export declare function getUserProfile(state?: SessionState): UserProfile | null;
|
|
153
|
+
/**
|
|
154
|
+
* Gets user's display name from session state.
|
|
155
|
+
* Searches localStorage entries first, then falls back to JWT claims.
|
|
156
|
+
*/
|
|
157
|
+
export declare function getUserDisplayName(state?: SessionState): string | null;
|
|
158
|
+
/**
|
|
159
|
+
* Checks if tokens in session state are expired.
|
|
160
|
+
*/
|
|
161
|
+
export declare function areTokensExpired(state?: SessionState): boolean;
|
|
162
|
+
export { clearTokenCache };
|
|
163
|
+
/**
|
|
164
|
+
* Discovered configuration from localStorage.
|
|
165
|
+
* Used for debugging and understanding what config is available.
|
|
166
|
+
*/
|
|
167
|
+
export interface DiscoveredConfig {
|
|
168
|
+
/** All DISCOVER-* keys with their parsed values. */
|
|
169
|
+
discoveryConfigs: Record<string, unknown>;
|
|
170
|
+
/** All keys that look like configuration (not tokens). */
|
|
171
|
+
configKeys: string[];
|
|
172
|
+
/** Content of interesting config keys (settings, flags). */
|
|
173
|
+
configContents: Record<string, unknown>;
|
|
174
|
+
/** Teams base URL extracted from discovery config. */
|
|
175
|
+
teamsBaseUrl: string | null;
|
|
176
|
+
/** Substrate URL if found in any config. */
|
|
177
|
+
substrateUrl: string | null;
|
|
178
|
+
/** All unique hosts found in config URLs. */
|
|
179
|
+
uniqueHosts: string[];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Extracts all configuration data from localStorage for debugging.
|
|
183
|
+
* This helps discover what config is available from different tenants.
|
|
184
|
+
*/
|
|
185
|
+
export declare function discoverConfig(state?: SessionState): DiscoveredConfig | null;
|