m365-cli 0.1.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/README.md +683 -0
- package/bin/m365.js +489 -0
- package/config/default.json +18 -0
- package/package.json +36 -0
- package/src/auth/device-flow.js +154 -0
- package/src/auth/token-manager.js +237 -0
- package/src/commands/calendar.js +279 -0
- package/src/commands/mail.js +353 -0
- package/src/commands/onedrive.js +423 -0
- package/src/commands/sharepoint.js +312 -0
- package/src/graph/client.js +875 -0
- package/src/utils/config.js +60 -0
- package/src/utils/error.js +114 -0
- package/src/utils/output.js +850 -0
- package/src/utils/trusted-senders.js +190 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Trusted senders whitelist manager
|
|
7
|
+
* Protects against phishing by filtering email content from untrusted senders
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Whitelist file paths (check in order)
|
|
11
|
+
const WHITELIST_PATHS = [
|
|
12
|
+
join(homedir(), '.m365-cli/trusted-senders.txt'),
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the active whitelist file path
|
|
17
|
+
*/
|
|
18
|
+
function getWhitelistPath() {
|
|
19
|
+
// Return first existing path
|
|
20
|
+
for (const path of WHITELIST_PATHS) {
|
|
21
|
+
if (existsSync(path)) {
|
|
22
|
+
return path;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Default to second path if none exist
|
|
27
|
+
return WHITELIST_PATHS[1];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load trusted senders from file
|
|
32
|
+
* @returns {Array<string>} List of trusted email addresses and domains
|
|
33
|
+
*/
|
|
34
|
+
export function loadTrustedSenders() {
|
|
35
|
+
const path = getWhitelistPath();
|
|
36
|
+
|
|
37
|
+
if (!existsSync(path)) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const content = readFileSync(path, 'utf-8');
|
|
43
|
+
return content
|
|
44
|
+
.split('\n')
|
|
45
|
+
.map(line => line.trim())
|
|
46
|
+
.filter(line => line && !line.startsWith('#')); // Skip comments and empty lines
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(`Warning: Failed to read whitelist from ${path}:`, error.message);
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if a sender is trusted
|
|
55
|
+
* @param {string} senderEmail - Email address to check
|
|
56
|
+
* @returns {boolean} True if sender is trusted
|
|
57
|
+
*/
|
|
58
|
+
export function isTrustedSender(senderEmail) {
|
|
59
|
+
if (!senderEmail) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Handle Exchange DN format (internal mail)
|
|
64
|
+
// These are formatted like: /O=EXCHANGELABS/OU=.../CN=RECIPIENTS/CN=...
|
|
65
|
+
if (senderEmail.startsWith('/O=EXCHANGELABS') || senderEmail.startsWith('/O=EXCHANGE')) {
|
|
66
|
+
// Internal organization mail - consider trusted
|
|
67
|
+
// In a production environment, you might want to be more selective
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const trustedSenders = loadTrustedSenders();
|
|
72
|
+
const normalizedEmail = senderEmail.toLowerCase().trim();
|
|
73
|
+
|
|
74
|
+
for (const entry of trustedSenders) {
|
|
75
|
+
const normalized = entry.toLowerCase();
|
|
76
|
+
|
|
77
|
+
// Domain match (e.g., @example.com)
|
|
78
|
+
if (normalized.startsWith('@')) {
|
|
79
|
+
const domain = normalized.substring(1);
|
|
80
|
+
if (normalizedEmail.endsWith(`@${domain}`)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Exact email match
|
|
85
|
+
else if (normalized === normalizedEmail) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Add a sender to the whitelist
|
|
95
|
+
* @param {string} email - Email address or domain to trust
|
|
96
|
+
*/
|
|
97
|
+
export function addTrustedSender(email) {
|
|
98
|
+
const path = getWhitelistPath();
|
|
99
|
+
const trustedSenders = loadTrustedSenders();
|
|
100
|
+
|
|
101
|
+
// Normalize input
|
|
102
|
+
const normalized = email.toLowerCase().trim();
|
|
103
|
+
|
|
104
|
+
// Check if already trusted
|
|
105
|
+
if (trustedSenders.some(entry => entry.toLowerCase() === normalized)) {
|
|
106
|
+
throw new Error(`Already trusted: ${email}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Ensure directory exists
|
|
110
|
+
const dir = dirname(path);
|
|
111
|
+
if (!existsSync(dir)) {
|
|
112
|
+
mkdirSync(dir, { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Append to file
|
|
116
|
+
const line = `\n${email}`;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
if (existsSync(path)) {
|
|
120
|
+
writeFileSync(path, readFileSync(path, 'utf-8') + line, 'utf-8');
|
|
121
|
+
} else {
|
|
122
|
+
// Create new file with header
|
|
123
|
+
const header = `# M365 Trusted Senders Whitelist\n
|
|
124
|
+
# One email address or domain per line\n
|
|
125
|
+
# Lines starting with @ match entire domains (e.g. @example.com)\n
|
|
126
|
+
# Senders not in this list will have their email body filtered out\n
|
|
127
|
+
|
|
128
|
+
`;
|
|
129
|
+
writeFileSync(path, header + email + '\n', 'utf-8');
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
throw new Error(`Failed to add trusted sender: ${error.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Remove a sender from the whitelist
|
|
138
|
+
* @param {string} email - Email address or domain to untrust
|
|
139
|
+
*/
|
|
140
|
+
export function removeTrustedSender(email) {
|
|
141
|
+
const path = getWhitelistPath();
|
|
142
|
+
|
|
143
|
+
if (!existsSync(path)) {
|
|
144
|
+
throw new Error('Whitelist file does not exist');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const trustedSenders = loadTrustedSenders();
|
|
148
|
+
const normalized = email.toLowerCase().trim();
|
|
149
|
+
|
|
150
|
+
// Find matching entry (case-insensitive)
|
|
151
|
+
const matchingEntry = trustedSenders.find(
|
|
152
|
+
entry => entry.toLowerCase() === normalized
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (!matchingEntry) {
|
|
156
|
+
throw new Error(`Not in whitelist: ${email}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
// Read full content
|
|
161
|
+
const content = readFileSync(path, 'utf-8');
|
|
162
|
+
|
|
163
|
+
// Remove the matching line
|
|
164
|
+
const lines = content.split('\n');
|
|
165
|
+
const filtered = lines.filter(line => {
|
|
166
|
+
const trimmed = line.trim();
|
|
167
|
+
return trimmed !== matchingEntry;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
writeFileSync(path, filtered.join('\n'), 'utf-8');
|
|
171
|
+
} catch (error) {
|
|
172
|
+
throw new Error(`Failed to remove trusted sender: ${error.message}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* List all trusted senders
|
|
178
|
+
* @returns {Array<string>} List of trusted entries
|
|
179
|
+
*/
|
|
180
|
+
export function listTrustedSenders() {
|
|
181
|
+
return loadTrustedSenders();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get whitelist file path (for display purposes)
|
|
186
|
+
* @returns {string} Path to whitelist file
|
|
187
|
+
*/
|
|
188
|
+
export function getWhitelistFilePath() {
|
|
189
|
+
return getWhitelistPath();
|
|
190
|
+
}
|