@rubytech/create-maxy 1.0.763 → 1.0.765

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 (88) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/package-lock.json +56 -1
  3. package/payload/platform/package.json +1 -0
  4. package/payload/platform/plugins/docs/references/outlook-guide.md +69 -0
  5. package/payload/platform/plugins/docs/references/platform.md +1 -1
  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-IO2WQEY4.js +9562 -0
  79. package/payload/server/chunk-TKYZ7AEB.js +3142 -0
  80. package/payload/server/client-pool-CX2MFW75.js +28 -0
  81. package/payload/server/maxy-edge.js +2 -2
  82. package/payload/server/public/assets/{admin-V6NDkEoR.js → admin-g-Fjko8R.js} +5 -5
  83. package/payload/server/public/assets/{graph-BHcUKzXh.js → graph-DHBGJFDX.js} +1 -1
  84. package/payload/server/public/assets/page-BscgOyqp.js +50 -0
  85. package/payload/server/public/graph.html +2 -2
  86. package/payload/server/public/index.html +2 -2
  87. package/payload/server/server.js +146 -39
  88. package/payload/server/public/assets/page-DxH_Opxt.js +0 -50
@@ -0,0 +1,130 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, rmSync, statSync, writeFileSync, readFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { TokenStore } from "../auth/token-store.js";
7
+ function makeAccountsDir() {
8
+ const dir = mkdtempSync(join(tmpdir(), "outlook-test-"));
9
+ return {
10
+ dir,
11
+ cleanup: () => rmSync(dir, { recursive: true, force: true }),
12
+ };
13
+ }
14
+ test("TokenStore round-trip preserves the blob", () => {
15
+ const { dir, cleanup } = makeAccountsDir();
16
+ try {
17
+ const store = new TokenStore("acct-A", dir);
18
+ store.store("access-1", "refresh-1", 3600, {
19
+ graphUserId: "user-1",
20
+ scopes: ["Mail.Read", "Calendars.Read"],
21
+ });
22
+ const fresh = new TokenStore("acct-A", dir);
23
+ const blob = fresh.read();
24
+ assert.ok(blob, "expected blob to exist after round-trip");
25
+ assert.equal(blob.accessToken, "access-1");
26
+ assert.equal(blob.refreshToken, "refresh-1");
27
+ assert.equal(blob.graphUserId, "user-1");
28
+ assert.deepEqual(blob.scopes, ["Mail.Read", "Calendars.Read"]);
29
+ }
30
+ finally {
31
+ cleanup();
32
+ }
33
+ });
34
+ test("TokenStore corrupt cipher fails loud", () => {
35
+ const { dir, cleanup } = makeAccountsDir();
36
+ try {
37
+ const store = new TokenStore("acct-X", dir);
38
+ store.store("access-X", "refresh-X", 3600);
39
+ // Tamper with the ciphertext on disk.
40
+ const tokensPath = join(dir, "acct-X", "secrets", "outlook", "tokens.enc");
41
+ writeFileSync(tokensPath, "garbage-not-a-cipher");
42
+ const fresh = new TokenStore("acct-X", dir);
43
+ assert.throws(() => fresh.read(), /token-decrypt-failed|bad decrypt|wrong final block length|invalid|wrong tag/i);
44
+ }
45
+ finally {
46
+ cleanup();
47
+ }
48
+ });
49
+ test("TokenStore per-account isolation — two stores never decrypt each other", () => {
50
+ const { dir, cleanup } = makeAccountsDir();
51
+ try {
52
+ const storeA = new TokenStore("acct-A", dir);
53
+ const storeB = new TokenStore("acct-B", dir);
54
+ storeA.store("access-A", "refresh-A", 3600, { graphUserId: "user-A", scopes: [] });
55
+ storeB.store("access-B", "refresh-B", 3600, { graphUserId: "user-B", scopes: [] });
56
+ // Read each independently.
57
+ const blobA = new TokenStore("acct-A", dir).read();
58
+ const blobB = new TokenStore("acct-B", dir).read();
59
+ assert.equal(blobA?.accessToken, "access-A");
60
+ assert.equal(blobB?.accessToken, "access-B");
61
+ assert.notEqual(blobA?.accessToken, blobB?.accessToken);
62
+ // Sentinel: swap the .key files between accounts. The store should now
63
+ // fail to decrypt — proves keys are genuinely per-account, not shared.
64
+ const keyA = readFileSync(join(dir, "acct-A", "secrets", "outlook", ".key"), "utf-8");
65
+ const keyB = readFileSync(join(dir, "acct-B", "secrets", "outlook", ".key"), "utf-8");
66
+ assert.notEqual(keyA, keyB, "keys must differ across accounts");
67
+ writeFileSync(join(dir, "acct-A", "secrets", "outlook", ".key"), keyB);
68
+ const tampered = new TokenStore("acct-A", dir);
69
+ assert.throws(() => tampered.read(), /bad decrypt|wrong final|invalid|token-decrypt-failed/i);
70
+ }
71
+ finally {
72
+ cleanup();
73
+ }
74
+ });
75
+ test("TokenStore directory permissions are 0700, file permissions are 0600", () => {
76
+ const { dir, cleanup } = makeAccountsDir();
77
+ try {
78
+ const store = new TokenStore("acct-mode", dir);
79
+ store.store("a", "r", 3600);
80
+ const secretsDir = join(dir, "acct-mode", "secrets", "outlook");
81
+ const dirMode = statSync(secretsDir).mode & 0o777;
82
+ assert.equal(dirMode, 0o700, "secrets dir must be 0700");
83
+ const keyMode = statSync(join(secretsDir, ".key")).mode & 0o777;
84
+ assert.equal(keyMode, 0o600, "key file must be 0600");
85
+ const tokensMode = statSync(join(secretsDir, "tokens.enc")).mode & 0o777;
86
+ assert.equal(tokensMode, 0o600, "tokens file must be 0600");
87
+ }
88
+ finally {
89
+ cleanup();
90
+ }
91
+ });
92
+ test("TokenStore.needsRefresh returns true within the threshold window", () => {
93
+ const { dir, cleanup } = makeAccountsDir();
94
+ try {
95
+ const store = new TokenStore("acct-r", dir);
96
+ store.store("a", "r", 60); // 60s expiry — well under the 300s threshold
97
+ assert.equal(store.needsRefresh(), true);
98
+ }
99
+ finally {
100
+ cleanup();
101
+ }
102
+ });
103
+ test("TokenStore.needsRefresh returns false outside the threshold window", () => {
104
+ const { dir, cleanup } = makeAccountsDir();
105
+ try {
106
+ const store = new TokenStore("acct-f", dir);
107
+ store.store("a", "r", 3600); // 1h expiry
108
+ assert.equal(store.needsRefresh(), false);
109
+ }
110
+ finally {
111
+ cleanup();
112
+ }
113
+ });
114
+ test("TokenStore.clear removes the token blob but leaves the key", () => {
115
+ const { dir, cleanup } = makeAccountsDir();
116
+ try {
117
+ const store = new TokenStore("acct-c", dir);
118
+ store.store("a", "r", 3600);
119
+ store.clear();
120
+ assert.equal(store.read(), null);
121
+ // Key file still present — re-register reuses it.
122
+ const fresh = new TokenStore("acct-c", dir);
123
+ fresh.store("a2", "r2", 3600);
124
+ assert.equal(fresh.read()?.accessToken, "a2");
125
+ }
126
+ finally {
127
+ cleanup();
128
+ }
129
+ });
130
+ //# sourceMappingURL=token-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.test.js","sourceRoot":"","sources":["../../src/__tests__/token-store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
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
+ export interface PkceFlowConfig {
16
+ clientId: string;
17
+ tenantId: string;
18
+ accountId: string;
19
+ }
20
+ export interface TokenResponse {
21
+ access_token: string;
22
+ refresh_token?: string;
23
+ expires_in: number;
24
+ token_type: string;
25
+ scope?: string;
26
+ }
27
+ export interface PkceFlowResult {
28
+ tokenResponse: TokenResponse;
29
+ scopes: string[];
30
+ }
31
+ export interface PkceFlowStart {
32
+ authUrl: string;
33
+ redirectUri: string;
34
+ result: Promise<PkceFlowResult>;
35
+ }
36
+ /**
37
+ * Start the PKCE flow:
38
+ * 1. Generate code_verifier + code_challenge (S256) + state.
39
+ * 2. Bind a one-shot HTTP server on an ephemeral loopback port.
40
+ * 3. Build the authorize URL with the actual port; return it to the caller.
41
+ * 4. The returned `result` promise resolves when the callback arrives (or
42
+ * rejects on state mismatch / OAuth error / timeout) and tokens are
43
+ * exchanged.
44
+ *
45
+ * The caller (the outlook-account-register tool) prints the auth URL,
46
+ * returns it in the tool response, and awaits `result`.
47
+ */
48
+ export declare function startPkceFlow(config: PkceFlowConfig): Promise<PkceFlowStart>;
49
+ export declare function refreshAccessToken(args: {
50
+ clientId: string;
51
+ tenantId: string;
52
+ refreshToken: string;
53
+ }): Promise<TokenResponse>;
54
+ /**
55
+ * Constant-time equality on equal-length strings. Returns false (not throws)
56
+ * for length mismatch so the caller can branch without leaking length via
57
+ * timing. State comparison MUST go through this helper, not `!==`.
58
+ */
59
+ declare function sameLengthEqual(a: string, b: string): boolean;
60
+ export declare const _internals: {
61
+ SCOPES: string[];
62
+ sameLengthEqual: typeof sameLengthEqual;
63
+ };
64
+ export {};
65
+ //# sourceMappingURL=pkce-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce-flow.d.ts","sourceRoot":"","sources":["../../src/auth/pkce-flow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA8BH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,aAAa,CAAC;IAC7B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAsDlF;AAgJD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,aAAa,CAAC,CAmBzB;AAuBD;;;;GAIG;AACH,iBAAS,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAKtD;AAED,eAAO,MAAM,UAAU;;;CAA8B,CAAC"}
@@ -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"}