@rubytech/create-maxy 1.0.762 → 1.0.764

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.
Files changed (84) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/schema.cypher +50 -0
  3. package/payload/platform/package-lock.json +56 -1
  4. package/payload/platform/package.json +1 -0
  5. package/payload/platform/plugins/docs/references/outlook-guide.md +69 -0
  6. package/payload/platform/plugins/outlook/PLUGIN.md +48 -0
  7. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts +2 -0
  8. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts.map +1 -0
  9. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +94 -0
  10. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -0
  11. package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts +2 -0
  12. package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts.map +1 -0
  13. package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js +31 -0
  14. package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js.map +1 -0
  15. package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts +2 -0
  16. package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +213 -0
  18. package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +1 -0
  19. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts +2 -0
  20. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts.map +1 -0
  21. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +130 -0
  22. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -0
  23. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts +65 -0
  24. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts.map +1 -0
  25. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js +261 -0
  26. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js.map +1 -0
  27. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +61 -0
  28. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -0
  29. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +170 -0
  30. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -0
  31. package/payload/platform/plugins/outlook/mcp/dist/index.d.ts +18 -0
  32. package/payload/platform/plugins/outlook/mcp/dist/index.d.ts.map +1 -0
  33. package/payload/platform/plugins/outlook/mcp/dist/index.js +152 -0
  34. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -0
  35. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +60 -0
  36. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -0
  37. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +189 -0
  38. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -0
  39. package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts +23 -0
  40. package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts.map +1 -0
  41. package/payload/platform/plugins/outlook/mcp/dist/lib/log.js +53 -0
  42. package/payload/platform/plugins/outlook/mcp/dist/lib/log.js.map +1 -0
  43. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +26 -0
  44. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -0
  45. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +50 -0
  46. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -0
  47. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts +12 -0
  48. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts.map +1 -0
  49. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js +32 -0
  50. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js.map +1 -0
  51. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts +59 -0
  52. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts.map +1 -0
  53. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js +54 -0
  54. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js.map +1 -0
  55. package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts +14 -0
  56. package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts.map +1 -0
  57. package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js +45 -0
  58. package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js.map +1 -0
  59. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +15 -0
  60. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -0
  61. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +48 -0
  62. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -0
  63. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts +8 -0
  64. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -0
  65. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +49 -0
  66. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -0
  67. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts +19 -0
  68. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts.map +1 -0
  69. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js +58 -0
  70. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js.map +1 -0
  71. package/payload/platform/plugins/outlook/mcp/package.json +20 -0
  72. package/payload/platform/plugins/outlook/mcp/scripts/verify-doc-impl.sh +109 -0
  73. package/payload/platform/plugins/outlook/references/auth.md +118 -0
  74. package/payload/platform/plugins/outlook/references/graph-surfaces.md +114 -0
  75. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +65 -0
  76. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  77. package/payload/server/chunk-EIQT6QDH.js +9562 -0
  78. package/payload/server/chunk-S3M2NZMA.js +3136 -0
  79. package/payload/server/chunk-SGBNY4NP.js +9540 -0
  80. package/payload/server/client-pool-5V5GX3UT.js +28 -0
  81. package/payload/server/maxy-edge.js +2 -2
  82. package/payload/server/public/assets/{admin-7vGwd7wu.js → admin-V6NDkEoR.js} +2 -2
  83. package/payload/server/public/index.html +1 -1
  84. package/payload/server/server.js +104 -6
@@ -0,0 +1,261 @@
1
+ /**
2
+ * OAuth 2.0 Authorization Code + PKCE flow for Microsoft Identity Platform.
3
+ *
4
+ * Adapted from nsakki55/outlook-mcp@e49d8e68ac8d69db653c25803127dc3b98626cda
5
+ * (MIT). Adaptations:
6
+ * - Ephemeral loopback port via `server.listen(0)` (upstream uses fixed
7
+ * 49152). The Entra app is registered with `http://localhost` (no port);
8
+ * Microsoft Identity Platform treats native-app loopback redirects with
9
+ * port-flexible matching.
10
+ * - No `xdg-open` browser auto-launch; the auth URL is logged to stderr
11
+ * and returned to the caller for the operator to open in the VNC browser.
12
+ * - Tool handler awaits the full PKCE flow synchronously (5-min timeout)
13
+ * instead of upstream's background-promise + immediate-error pattern.
14
+ */
15
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
16
+ import { createServer } from "node:http";
17
+ import { URL } from "node:url";
18
+ import { log, challengePrefix } from "../lib/log.js";
19
+ const FLOW_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
20
+ const SCOPES = [
21
+ "offline_access",
22
+ "User.Read",
23
+ "Mail.Read",
24
+ "Calendars.Read",
25
+ "Contacts.Read",
26
+ ];
27
+ function buildUrls(tenantId) {
28
+ const tenant = tenantId || "common";
29
+ return {
30
+ authorizeUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`,
31
+ tokenUrl: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,
32
+ };
33
+ }
34
+ /**
35
+ * Start the PKCE flow:
36
+ * 1. Generate code_verifier + code_challenge (S256) + state.
37
+ * 2. Bind a one-shot HTTP server on an ephemeral loopback port.
38
+ * 3. Build the authorize URL with the actual port; return it to the caller.
39
+ * 4. The returned `result` promise resolves when the callback arrives (or
40
+ * rejects on state mismatch / OAuth error / timeout) and tokens are
41
+ * exchanged.
42
+ *
43
+ * The caller (the outlook-account-register tool) prints the auth URL,
44
+ * returns it in the tool response, and awaits `result`.
45
+ */
46
+ export async function startPkceFlow(config) {
47
+ const codeVerifier = randomBytes(32).toString("base64url");
48
+ const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
49
+ const state = randomBytes(16).toString("hex");
50
+ const bound = await bindCallbackServer(state);
51
+ const redirectUri = `http://localhost:${bound.port}/callback`;
52
+ const { authorizeUrl, tokenUrl } = buildUrls(config.tenantId);
53
+ const authUrl = new URL(authorizeUrl);
54
+ authUrl.searchParams.set("client_id", config.clientId);
55
+ authUrl.searchParams.set("response_type", "code");
56
+ authUrl.searchParams.set("redirect_uri", redirectUri);
57
+ authUrl.searchParams.set("scope", SCOPES.join(" "));
58
+ authUrl.searchParams.set("state", state);
59
+ authUrl.searchParams.set("code_challenge", codeChallenge);
60
+ authUrl.searchParams.set("code_challenge_method", "S256");
61
+ authUrl.searchParams.set("prompt", "select_account");
62
+ log({
63
+ event: "auth-init",
64
+ account: config.accountId,
65
+ codeChallenge: challengePrefix(codeChallenge),
66
+ redirectPath: `/callback (port ${bound.port})`,
67
+ });
68
+ const result = (async () => {
69
+ const startMs = Date.now();
70
+ try {
71
+ const code = await bound.codePromise;
72
+ log({
73
+ event: "auth-callback",
74
+ account: config.accountId,
75
+ elapsedMs: Date.now() - startMs,
76
+ });
77
+ const tokenResponse = await exchangeCode({
78
+ code,
79
+ codeVerifier,
80
+ redirectUri,
81
+ clientId: config.clientId,
82
+ tokenUrl,
83
+ });
84
+ const scopes = (tokenResponse.scope ?? SCOPES.join(" ")).split(/\s+/);
85
+ return { tokenResponse, scopes };
86
+ }
87
+ finally {
88
+ bound.close();
89
+ }
90
+ })();
91
+ return {
92
+ authUrl: authUrl.toString(),
93
+ redirectUri,
94
+ result,
95
+ };
96
+ }
97
+ function bindCallbackServer(expectedState) {
98
+ return new Promise((resolveBind, rejectBind) => {
99
+ let resolved = false;
100
+ let codeResolve = null;
101
+ let codeReject = null;
102
+ const codePromise = new Promise((resolve, reject) => {
103
+ codeResolve = resolve;
104
+ codeReject = reject;
105
+ });
106
+ let timer = null;
107
+ const server = createServer((req, res) => {
108
+ if (!req.url) {
109
+ res.writeHead(400);
110
+ res.end();
111
+ return;
112
+ }
113
+ const parsed = new URL(req.url, "http://localhost");
114
+ if (parsed.pathname !== "/callback") {
115
+ res.writeHead(404, { "Content-Type": "text/plain" });
116
+ res.end("not found");
117
+ return;
118
+ }
119
+ const code = parsed.searchParams.get("code");
120
+ const returnedState = parsed.searchParams.get("state");
121
+ const error = parsed.searchParams.get("error");
122
+ const errorDescription = parsed.searchParams.get("error_description");
123
+ // Reject Host header rebinding (RFC 6749 native-client guidance).
124
+ const host = req.headers.host ?? "";
125
+ if (!/^(localhost|127\.0\.0\.1)(:\d+)?$/.test(host)) {
126
+ respondHtml(res, 400, "Invalid host", "Loopback callback rejected non-localhost host header.");
127
+ finish();
128
+ codeReject?.(new Error(`Loopback callback received unexpected host header: ${host}`));
129
+ return;
130
+ }
131
+ if (error) {
132
+ respondHtml(res, 400, "Authentication error", `${error}: ${errorDescription ?? ""}`);
133
+ finish();
134
+ codeReject?.(new Error(`OAuth error: ${error} - ${errorDescription ?? ""}`));
135
+ return;
136
+ }
137
+ if (!returnedState || !sameLengthEqual(returnedState, expectedState)) {
138
+ respondHtml(res, 400, "State mismatch", "Authorization state did not match. Possible CSRF; retry register.");
139
+ finish();
140
+ codeReject?.(new Error("State mismatch — possible CSRF"));
141
+ return;
142
+ }
143
+ if (!code) {
144
+ respondHtml(res, 400, "No authorization code", "The authorization callback did not include a code.");
145
+ finish();
146
+ codeReject?.(new Error("No authorization code received"));
147
+ return;
148
+ }
149
+ respondHtml(res, 200, "Outlook authorized", "You may close this window. Return to admin chat to continue.");
150
+ finish();
151
+ codeResolve?.(code);
152
+ });
153
+ server.on("error", (err) => {
154
+ finish();
155
+ // If we never resolved bind, fail bind; otherwise fail the code wait.
156
+ if (!resolvedBind)
157
+ rejectBind(err);
158
+ codeReject?.(err);
159
+ });
160
+ let resolvedBind = false;
161
+ server.listen(0, "127.0.0.1", () => {
162
+ const address = server.address();
163
+ if (!address || typeof address !== "object") {
164
+ rejectBind(new Error("Loopback bind failed: address unavailable"));
165
+ return;
166
+ }
167
+ resolvedBind = true;
168
+ timer = setTimeout(() => {
169
+ finish();
170
+ codeReject?.(new Error("Authentication timeout (5 minutes)"));
171
+ }, FLOW_TIMEOUT_MS);
172
+ resolveBind({
173
+ port: address.port,
174
+ codePromise,
175
+ close: finish,
176
+ });
177
+ });
178
+ function finish() {
179
+ if (resolved)
180
+ return;
181
+ resolved = true;
182
+ if (timer)
183
+ clearTimeout(timer);
184
+ timer = null;
185
+ try {
186
+ server.close();
187
+ }
188
+ catch {
189
+ // best-effort
190
+ }
191
+ }
192
+ });
193
+ }
194
+ async function exchangeCode(args) {
195
+ const params = new URLSearchParams({
196
+ client_id: args.clientId,
197
+ scope: SCOPES.join(" "),
198
+ code: args.code,
199
+ redirect_uri: args.redirectUri,
200
+ grant_type: "authorization_code",
201
+ code_verifier: args.codeVerifier,
202
+ });
203
+ const response = await fetch(args.tokenUrl, {
204
+ method: "POST",
205
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
206
+ body: params.toString(),
207
+ });
208
+ if (!response.ok) {
209
+ const text = await response.text();
210
+ throw new Error(`Token exchange failed (${response.status}): ${text}`);
211
+ }
212
+ return (await response.json());
213
+ }
214
+ export async function refreshAccessToken(args) {
215
+ const { tokenUrl } = buildUrls(args.tenantId);
216
+ const params = new URLSearchParams({
217
+ client_id: args.clientId,
218
+ scope: SCOPES.join(" "),
219
+ refresh_token: args.refreshToken,
220
+ grant_type: "refresh_token",
221
+ });
222
+ const response = await fetch(tokenUrl, {
223
+ method: "POST",
224
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
225
+ body: params.toString(),
226
+ });
227
+ if (!response.ok) {
228
+ const text = await response.text();
229
+ throw new Error(`Token refresh failed (${response.status}): ${text}`);
230
+ }
231
+ return (await response.json());
232
+ }
233
+ function respondHtml(res, status, title, message) {
234
+ const color = status === 200 ? "#107c10" : "#d83b01";
235
+ const body = `<!DOCTYPE html>
236
+ <html lang="en"><head><meta charset="UTF-8"><title>${title}</title>
237
+ <style>
238
+ body{font-family:'Segoe UI',sans-serif;text-align:center;padding:60px;background:#f3f2f1;}
239
+ .card{background:white;border-radius:8px;padding:40px;max-width:480px;margin:0 auto;
240
+ box-shadow:0 2px 8px rgba(0,0,0,0.1);}
241
+ h1{color:${color};margin:0 0 16px;}
242
+ p{color:#605e5c;margin:0;line-height:1.5;}
243
+ </style></head>
244
+ <body><div class="card"><h1>${title}</h1><p>${message}</p></div></body></html>`;
245
+ res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
246
+ res.end(body);
247
+ }
248
+ /**
249
+ * Constant-time equality on equal-length strings. Returns false (not throws)
250
+ * for length mismatch so the caller can branch without leaking length via
251
+ * timing. State comparison MUST go through this helper, not `!==`.
252
+ */
253
+ function sameLengthEqual(a, b) {
254
+ if (a.length !== b.length)
255
+ return false;
256
+ const aBuf = Buffer.from(a, "utf8");
257
+ const bBuf = Buffer.from(b, "utf8");
258
+ return timingSafeEqual(aBuf, bBuf);
259
+ }
260
+ export const _internals = { SCOPES, sameLengthEqual };
261
+ //# sourceMappingURL=pkce-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce-flow.js","sourceRoot":"","sources":["../../src/auth/pkce-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Per-account encrypted token store for Outlook OAuth credentials.
3
+ *
4
+ * Adapted from nsakki55/outlook-mcp@e49d8e68ac8d69db653c25803127dc3b98626cda
5
+ * (MIT). Adaptations:
6
+ * - Per-account paths under {ACCOUNTS_DIR}/{accountId}/secrets/outlook/
7
+ * - keytar fallback removed (Pi runs headless, no Secret Service)
8
+ * - node-persist removed; single JSON blob written atomically (temp + rename)
9
+ *
10
+ * tokens.enc AES-256-CBC ciphertext "<iv-hex>:<cipher-hex>" of the JSON blob
11
+ * .key 32 random bytes, base64, mode 0600 (the AES key)
12
+ *
13
+ * The JSON blob shape is {accessToken, refreshToken, accessTokenExpiry,
14
+ * refreshTokenExpiry, lastRefresh, graphUserId, scopes}.
15
+ *
16
+ * Rationale: per CLAUDE.md doctrine, account scope is absolute. Each account
17
+ * holds its own AES key — compromising account A's keyfile cannot decrypt
18
+ * account B's tokens.
19
+ */
20
+ export interface TokenBlob {
21
+ accessToken: string;
22
+ refreshToken: string | null;
23
+ accessTokenExpiry: number;
24
+ refreshTokenExpiry: number;
25
+ lastRefresh: number;
26
+ graphUserId: string | null;
27
+ scopes: string[];
28
+ }
29
+ export declare class TokenStore {
30
+ readonly accountId: string;
31
+ private cachedBlob;
32
+ private encryptionKey;
33
+ private readonly secretsDir;
34
+ private readonly tokensPath;
35
+ private readonly keyPath;
36
+ constructor(accountId: string, accountsDir: string);
37
+ /** Load (or create) the AES key. Idempotent and TOCTOU-safe via wx flag. */
38
+ private loadKey;
39
+ private encrypt;
40
+ private decrypt;
41
+ /** Atomic write — temp + rename. Either old or new tokens, never partial. */
42
+ private writeBlob;
43
+ /** Read and decrypt the blob from disk; cached after first read. */
44
+ read(): TokenBlob | null;
45
+ store(accessToken: string, refreshToken: string | null, expiresInSeconds: number, extras?: {
46
+ graphUserId?: string | null;
47
+ scopes?: string[];
48
+ }): TokenBlob;
49
+ private tryReadSilent;
50
+ /**
51
+ * Whether the access token is within the refresh threshold of expiry. Caller
52
+ * should refresh before next Graph call when this is true.
53
+ */
54
+ needsRefresh(): boolean;
55
+ hasTokens(): boolean;
56
+ refreshTokenExpired(): boolean;
57
+ clear(): void;
58
+ }
59
+ /** Exported for tests + the mailbox-info tool. */
60
+ export declare const refreshThresholdMs: number;
61
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAkBH,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,qBAAa,UAAU;aAQH,SAAS,EAAE,MAAM;IAPnC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAGf,SAAS,EAAE,MAAM,EACjC,WAAW,EAAE,MAAM;IAOrB,4EAA4E;IAC5E,OAAO,CAAC,OAAO;IAuCf,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,OAAO;IAaf,6EAA6E;IAC7E,OAAO,CAAC,SAAS;IASjB,oEAAoE;IACpE,IAAI,IAAI,SAAS,GAAG,IAAI;IAWxB,KAAK,CACH,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,gBAAgB,EAAE,MAAM,EACxB,MAAM,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAO,GAC9D,SAAS;IAkBZ,OAAO,CAAC,aAAa;IAQrB;;;OAGG;IACH,YAAY,IAAI,OAAO;IAMvB,SAAS,IAAI,OAAO;IAIpB,mBAAmB,IAAI,OAAO;IAM9B,KAAK,IAAI,IAAI;CAId;AAED,kDAAkD;AAClD,eAAO,MAAM,kBAAkB,QAAuB,CAAC"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Per-account encrypted token store for Outlook OAuth credentials.
3
+ *
4
+ * Adapted from nsakki55/outlook-mcp@e49d8e68ac8d69db653c25803127dc3b98626cda
5
+ * (MIT). Adaptations:
6
+ * - Per-account paths under {ACCOUNTS_DIR}/{accountId}/secrets/outlook/
7
+ * - keytar fallback removed (Pi runs headless, no Secret Service)
8
+ * - node-persist removed; single JSON blob written atomically (temp + rename)
9
+ *
10
+ * tokens.enc AES-256-CBC ciphertext "<iv-hex>:<cipher-hex>" of the JSON blob
11
+ * .key 32 random bytes, base64, mode 0600 (the AES key)
12
+ *
13
+ * The JSON blob shape is {accessToken, refreshToken, accessTokenExpiry,
14
+ * refreshTokenExpiry, lastRefresh, graphUserId, scopes}.
15
+ *
16
+ * Rationale: per CLAUDE.md doctrine, account scope is absolute. Each account
17
+ * holds its own AES key — compromising account A's keyfile cannot decrypt
18
+ * account B's tokens.
19
+ */
20
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
21
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
22
+ import { join } from "node:path";
23
+ const REFRESH_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
24
+ const REFRESH_TOKEN_LIFETIME_MS = 90 * 24 * 60 * 60 * 1000; // 90 days
25
+ export class TokenStore {
26
+ accountId;
27
+ cachedBlob = null;
28
+ encryptionKey = null;
29
+ secretsDir;
30
+ tokensPath;
31
+ keyPath;
32
+ constructor(accountId, accountsDir) {
33
+ this.accountId = accountId;
34
+ this.secretsDir = join(accountsDir, accountId, "secrets", "outlook");
35
+ this.tokensPath = join(this.secretsDir, "tokens.enc");
36
+ this.keyPath = join(this.secretsDir, ".key");
37
+ }
38
+ /** Load (or create) the AES key. Idempotent and TOCTOU-safe via wx flag. */
39
+ loadKey() {
40
+ if (this.encryptionKey)
41
+ return this.encryptionKey;
42
+ mkdirSync(this.secretsDir, { recursive: true, mode: 0o700 });
43
+ chmodSync(this.secretsDir, 0o700);
44
+ if (existsSync(this.keyPath)) {
45
+ const stat = statSync(this.keyPath);
46
+ if ((stat.mode & 0o777) !== 0o600)
47
+ chmodSync(this.keyPath, 0o600);
48
+ const raw = readFileSync(this.keyPath, "utf-8").trim();
49
+ const key = Buffer.from(raw, "base64");
50
+ if (key.length !== 32) {
51
+ throw new Error(`Outlook key for account=${this.accountId} is corrupt (length=${key.length}); delete ${this.keyPath} and re-run outlook-account-register.`);
52
+ }
53
+ this.encryptionKey = key;
54
+ return key;
55
+ }
56
+ const newKey = randomBytes(32);
57
+ try {
58
+ writeFileSync(this.keyPath, newKey.toString("base64"), {
59
+ flag: "wx",
60
+ mode: 0o600,
61
+ });
62
+ }
63
+ catch (err) {
64
+ // Concurrent flow created the key first — read theirs.
65
+ if (err.code === "EEXIST") {
66
+ const raw = readFileSync(this.keyPath, "utf-8").trim();
67
+ this.encryptionKey = Buffer.from(raw, "base64");
68
+ return this.encryptionKey;
69
+ }
70
+ throw err;
71
+ }
72
+ this.encryptionKey = newKey;
73
+ return newKey;
74
+ }
75
+ encrypt(plaintext) {
76
+ const key = this.loadKey();
77
+ const iv = randomBytes(16);
78
+ const cipher = createCipheriv("aes-256-cbc", key, iv);
79
+ let encrypted = cipher.update(plaintext, "utf8", "hex");
80
+ encrypted += cipher.final("hex");
81
+ return `${iv.toString("hex")}:${encrypted}`;
82
+ }
83
+ decrypt(payload) {
84
+ const key = this.loadKey();
85
+ const [ivHex, ...rest] = payload.split(":");
86
+ if (!ivHex || rest.length === 0) {
87
+ throw new Error("token-decrypt-failed: malformed cipher payload");
88
+ }
89
+ const iv = Buffer.from(ivHex, "hex");
90
+ const decipher = createDecipheriv("aes-256-cbc", key, iv);
91
+ let decrypted = decipher.update(rest.join(":"), "hex", "utf8");
92
+ decrypted += decipher.final("utf8");
93
+ return decrypted;
94
+ }
95
+ /** Atomic write — temp + rename. Either old or new tokens, never partial. */
96
+ writeBlob(blob) {
97
+ const ciphertext = this.encrypt(JSON.stringify(blob));
98
+ const temp = `${this.tokensPath}.tmp`;
99
+ writeFileSync(temp, ciphertext, { mode: 0o600 });
100
+ chmodSync(temp, 0o600);
101
+ renameSync(temp, this.tokensPath);
102
+ this.cachedBlob = blob;
103
+ }
104
+ /** Read and decrypt the blob from disk; cached after first read. */
105
+ read() {
106
+ if (this.cachedBlob)
107
+ return this.cachedBlob;
108
+ if (!existsSync(this.tokensPath))
109
+ return null;
110
+ const raw = readFileSync(this.tokensPath, "utf-8").trim();
111
+ if (!raw)
112
+ return null;
113
+ const json = this.decrypt(raw);
114
+ const blob = JSON.parse(json);
115
+ this.cachedBlob = blob;
116
+ return blob;
117
+ }
118
+ store(accessToken, refreshToken, expiresInSeconds, extras = {}) {
119
+ const now = Date.now();
120
+ const previous = this.cachedBlob ?? this.tryReadSilent();
121
+ const blob = {
122
+ accessToken,
123
+ refreshToken: refreshToken ?? previous?.refreshToken ?? null,
124
+ accessTokenExpiry: now + expiresInSeconds * 1000,
125
+ refreshTokenExpiry: refreshToken
126
+ ? now + REFRESH_TOKEN_LIFETIME_MS
127
+ : (previous?.refreshTokenExpiry ?? now + REFRESH_TOKEN_LIFETIME_MS),
128
+ lastRefresh: now,
129
+ graphUserId: extras.graphUserId ?? previous?.graphUserId ?? null,
130
+ scopes: extras.scopes ?? previous?.scopes ?? [],
131
+ };
132
+ this.writeBlob(blob);
133
+ return blob;
134
+ }
135
+ tryReadSilent() {
136
+ try {
137
+ return this.read();
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ }
143
+ /**
144
+ * Whether the access token is within the refresh threshold of expiry. Caller
145
+ * should refresh before next Graph call when this is true.
146
+ */
147
+ needsRefresh() {
148
+ const blob = this.read();
149
+ if (!blob)
150
+ return false;
151
+ return Date.now() > blob.accessTokenExpiry - REFRESH_THRESHOLD_MS;
152
+ }
153
+ hasTokens() {
154
+ return this.read() !== null;
155
+ }
156
+ refreshTokenExpired() {
157
+ const blob = this.read();
158
+ if (!blob)
159
+ return false;
160
+ return Date.now() > blob.refreshTokenExpiry;
161
+ }
162
+ clear() {
163
+ this.cachedBlob = null;
164
+ if (existsSync(this.tokensPath))
165
+ unlinkSync(this.tokensPath);
166
+ }
167
+ }
168
+ /** Exported for tests + the mailbox-info tool. */
169
+ export const refreshThresholdMs = REFRESH_THRESHOLD_MS;
170
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @maxy/outlook MCP server.
4
+ *
5
+ * Read-only Microsoft Graph access for Outlook.com / Microsoft 365 mailboxes.
6
+ * Admin agent only. Public-agent surface is explicitly excluded.
7
+ *
8
+ * Auth: per-account OAuth Auth Code + PKCE. Tokens persist encrypted to
9
+ * {ACCOUNTS_DIR}/{accountId}/secrets/outlook/{tokens.enc, .key}
10
+ *
11
+ * Required environment:
12
+ * ACCOUNT_ID — provided by getMcpServers when spawned per account.
13
+ * PLATFORM_ROOT — repo root; used to derive ACCOUNTS_DIR.
14
+ * OUTLOOK_CLIENT_ID — Azure AD app (client) ID. Operator-set per install.
15
+ * OUTLOOK_TENANT_ID — Azure tenant ("common" for multi-tenant). Default "common".
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG"}