@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.
- package/package.json +1 -1
- package/payload/platform/package-lock.json +56 -1
- package/payload/platform/package.json +1 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +69 -0
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +48 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +94 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js +31 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +213 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +130 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js +261 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +61 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +170 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.d.ts +18 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +152 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +60 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +189 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts +23 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.js +53 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +26 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +50 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts +12 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js +32 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts +59 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js +54 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts +14 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js +45 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +15 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +48 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts +8 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +49 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts +19 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js +58 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/package.json +20 -0
- package/payload/platform/plugins/outlook/mcp/scripts/verify-doc-impl.sh +109 -0
- package/payload/platform/plugins/outlook/references/auth.md +118 -0
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +114 -0
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +65 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/chunk-EIQT6QDH.js +9562 -0
- package/payload/server/chunk-IO2WQEY4.js +9562 -0
- package/payload/server/chunk-TKYZ7AEB.js +3142 -0
- package/payload/server/client-pool-CX2MFW75.js +28 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/{admin-V6NDkEoR.js → admin-g-Fjko8R.js} +5 -5
- package/payload/server/public/assets/{graph-BHcUKzXh.js → graph-DHBGJFDX.js} +1 -1
- package/payload/server/public/assets/page-BscgOyqp.js +50 -0
- package/payload/server/public/graph.html +2 -2
- package/payload/server/public/index.html +2 -2
- package/payload/server/server.js +146 -39
- package/payload/server/public/assets/page-DxH_Opxt.js +0 -50
package/package.json
CHANGED
|
@@ -207,6 +207,10 @@
|
|
|
207
207
|
"resolved": "plugins/telegram/mcp",
|
|
208
208
|
"link": true
|
|
209
209
|
},
|
|
210
|
+
"node_modules/@maxy/outlook": {
|
|
211
|
+
"resolved": "plugins/outlook/mcp",
|
|
212
|
+
"link": true
|
|
213
|
+
},
|
|
210
214
|
"node_modules/@maxy/replicate": {
|
|
211
215
|
"resolved": "plugins/replicate/mcp",
|
|
212
216
|
"link": true
|
|
@@ -231,6 +235,33 @@
|
|
|
231
235
|
"resolved": "plugins/workflows/mcp",
|
|
232
236
|
"link": true
|
|
233
237
|
},
|
|
238
|
+
"node_modules/@microsoft/microsoft-graph-client": {
|
|
239
|
+
"version": "3.0.7",
|
|
240
|
+
"resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-client/-/microsoft-graph-client-3.0.7.tgz",
|
|
241
|
+
"integrity": "sha512-/AazAV/F+HK4LIywF9C+NYHcJo038zEnWkteilcxC1FM/uK/4NVGDKGrxx7nNq1ybspAroRKT4I1FHfxQzxkUw==",
|
|
242
|
+
"license": "MIT",
|
|
243
|
+
"dependencies": {
|
|
244
|
+
"@babel/runtime": "^7.12.5",
|
|
245
|
+
"tslib": "^2.2.0"
|
|
246
|
+
},
|
|
247
|
+
"engines": {
|
|
248
|
+
"node": ">=12.0.0"
|
|
249
|
+
},
|
|
250
|
+
"peerDependenciesMeta": {
|
|
251
|
+
"@azure/identity": {
|
|
252
|
+
"optional": true
|
|
253
|
+
},
|
|
254
|
+
"@azure/msal-browser": {
|
|
255
|
+
"optional": true
|
|
256
|
+
},
|
|
257
|
+
"buffer": {
|
|
258
|
+
"optional": true
|
|
259
|
+
},
|
|
260
|
+
"stream-browserify": {
|
|
261
|
+
"optional": true
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
},
|
|
234
265
|
"node_modules/@modelcontextprotocol/sdk": {
|
|
235
266
|
"version": "1.27.1",
|
|
236
267
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz",
|
|
@@ -3485,11 +3516,34 @@
|
|
|
3485
3516
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
3486
3517
|
"neo4j-driver": "^5.28.1"
|
|
3487
3518
|
},
|
|
3519
|
+
"devDependencies": {
|
|
3520
|
+
"@types/node": "^22.0.0",
|
|
3521
|
+
"typescript": "^5.7.0",
|
|
3522
|
+
"vitest": "^4.1.2"
|
|
3523
|
+
}
|
|
3524
|
+
},
|
|
3525
|
+
"plugins/outlook/mcp": {
|
|
3526
|
+
"name": "@maxy/outlook",
|
|
3527
|
+
"version": "0.1.0",
|
|
3528
|
+
"dependencies": {
|
|
3529
|
+
"@microsoft/microsoft-graph-client": "^3.0.7",
|
|
3530
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
3531
|
+
"zod": "^3.24.0"
|
|
3532
|
+
},
|
|
3488
3533
|
"devDependencies": {
|
|
3489
3534
|
"@types/node": "^22.0.0",
|
|
3490
3535
|
"typescript": "^5.7.0"
|
|
3491
3536
|
}
|
|
3492
3537
|
},
|
|
3538
|
+
"plugins/outlook/mcp/node_modules/zod": {
|
|
3539
|
+
"version": "3.25.76",
|
|
3540
|
+
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
|
3541
|
+
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
|
3542
|
+
"license": "MIT",
|
|
3543
|
+
"funding": {
|
|
3544
|
+
"url": "https://github.com/sponsors/colinhacks"
|
|
3545
|
+
}
|
|
3546
|
+
},
|
|
3493
3547
|
"plugins/replicate/mcp": {
|
|
3494
3548
|
"name": "@maxy/replicate",
|
|
3495
3549
|
"version": "0.1.0",
|
|
@@ -3524,7 +3578,8 @@
|
|
|
3524
3578
|
},
|
|
3525
3579
|
"devDependencies": {
|
|
3526
3580
|
"@types/node": "^22.0.0",
|
|
3527
|
-
"typescript": "^5.7.0"
|
|
3581
|
+
"typescript": "^5.7.0",
|
|
3582
|
+
"vitest": "^4.1.2"
|
|
3528
3583
|
}
|
|
3529
3584
|
},
|
|
3530
3585
|
"plugins/scheduling/mcp/node_modules/zod": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"build:cloudflare": "tsc -p plugins/cloudflare/mcp/tsconfig.json",
|
|
16
16
|
"build:tasks": "tsc -p plugins/tasks/mcp/tsconfig.json",
|
|
17
17
|
"build:email": "tsc -p plugins/email/mcp/tsconfig.json",
|
|
18
|
+
"build:outlook": "tsc -p plugins/outlook/mcp/tsconfig.json",
|
|
18
19
|
"build:workflows": "tsc -p plugins/workflows/mcp/tsconfig.json",
|
|
19
20
|
"build:stubs": "tsc -p plugins/scheduling/mcp/tsconfig.json",
|
|
20
21
|
"build:replicate": "tsc -p plugins/replicate/mcp/tsconfig.json"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Outlook Plugin — Operator Guide
|
|
2
|
+
|
|
3
|
+
The `outlook` plugin gives the admin agent read-only access to Microsoft 365 / Outlook.com via Microsoft Graph. Per-account OAuth (Auth Code + PKCE), encrypted token storage, automatic refresh.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
1. **Register an Entra app once per Maxy install** — see `platform/plugins/outlook/references/auth.md` for full steps. Set `OUTLOOK_CLIENT_ID` (and `OUTLOOK_TENANT_ID`, default `common`) in the operator's environment.
|
|
8
|
+
2. **Per account: register the Outlook account** — in admin chat, ask the agent to "register my Outlook account". The agent runs `outlook-account-register`, which prints an authorization URL.
|
|
9
|
+
3. **Open the URL in the VNC browser** — sign in to your Microsoft account, consent to the requested scopes (`offline_access`, `User.Read`, `Mail.Read`, `Calendars.Read`, `Contacts.Read`).
|
|
10
|
+
4. **Done.** Subsequent tool calls (mail, calendar, contacts) use the persisted refresh token transparently.
|
|
11
|
+
|
|
12
|
+
## Tools
|
|
13
|
+
|
|
14
|
+
| Tool | Purpose |
|
|
15
|
+
|------|---------|
|
|
16
|
+
| `outlook-account-register` | Run the PKCE flow for this account. One-time per account; re-run if tokens expire (90 days) or consent is revoked. |
|
|
17
|
+
| `outlook-mail-list` | Recent mail. Default top=25, folder=Inbox. |
|
|
18
|
+
| `outlook-mail-search` | Microsoft Graph `$search` over the mailbox. |
|
|
19
|
+
| `outlook-calendar-list` | Calendar events in next rangeDays days (default 7, max 365). |
|
|
20
|
+
| `outlook-calendar-event` | Full detail of a single event by id. |
|
|
21
|
+
| `outlook-contacts-list` | Top contacts. Default top=50. |
|
|
22
|
+
| `outlook-mailbox-info` | Health probe — auth state, refresh-window, folder count. |
|
|
23
|
+
|
|
24
|
+
## Observability
|
|
25
|
+
|
|
26
|
+
All log lines start with `[outlook-mcp]` and write to `server.log`. They are key=value, account-scoped:
|
|
27
|
+
|
|
28
|
+
| Event | Line shape |
|
|
29
|
+
|-------|------------|
|
|
30
|
+
| Auth init | `auth-init account=<id> codeChallenge=<sha256-prefix-8> redirectPath=<callback-path>` |
|
|
31
|
+
| Auth callback | `auth-callback account=<id> elapsedMs=<N>` |
|
|
32
|
+
| Auth ok | `auth-ok account=<id> graphUserId=<id> scopes=<csv> tokenExpSec=<N>` |
|
|
33
|
+
| Token refreshed | `token-refreshed account=<id> oldExpSec=<N> newExpSec=<N>` |
|
|
34
|
+
| Refresh failed | `token-refresh-failed account=<id> reason=<err>` (terminal) |
|
|
35
|
+
| Mail list | `mail-list account=<id> folder=<id-or-Inbox> count=<N> elapsedMs=<N>` |
|
|
36
|
+
| Mail search | `mail-search account=<id> query=<trunc-32> count=<N> elapsedMs=<N>` |
|
|
37
|
+
| Calendar list | `calendar-list account=<id> rangeDays=<N> count=<N> elapsedMs=<N>` |
|
|
38
|
+
| Calendar event | `calendar-event account=<id> eventId=<trunc-12> elapsedMs=<N>` |
|
|
39
|
+
| Contacts list | `contacts-list account=<id> count=<N> elapsedMs=<N>` |
|
|
40
|
+
| Mailbox info | `mailbox-info account=<id> tokenWithinRefreshWindow=<bool> folderCount=<N>` |
|
|
41
|
+
| Graph error | `graph-error account=<id> status=<N> code=<graphErrorCode> retryAfterMs=<N-or-null>` |
|
|
42
|
+
| On-prem rejected | `on-prem-rejected account=<id> mailServer=<host>` (terminal) |
|
|
43
|
+
|
|
44
|
+
## Diagnostic paths
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# All outlook lines for one account, last 50
|
|
48
|
+
ssh laptop 'grep -E "^\[outlook-mcp\]" ~/.maxy/logs/server.log | grep "account=<id>" | tail -50'
|
|
49
|
+
|
|
50
|
+
# Token-leak audit — must always return zero
|
|
51
|
+
grep -rn -iE "Bearer |access_token=" ~/.maxy/logs/server.log | head
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Latency triage: `mail-list count=0 elapsedMs<200` consistent → permissions issue; `elapsedMs > 5000` → Graph slowness or DNS.
|
|
55
|
+
|
|
56
|
+
## Failure modes
|
|
57
|
+
|
|
58
|
+
| Operator-visible message | Cause | Fix |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `Outlook not connected for account=X; run outlook-account-register` | Tokens never saved | Run register tool |
|
|
61
|
+
| `Outlook refresh token expired for account=X; run outlook-account-register` | >90 days since last refresh, or consent revoked | Run register tool |
|
|
62
|
+
| `Outlook token refresh failed for account=X; re-auth required` | Network down at refresh time, or refresh token invalidated | Verify network; re-register |
|
|
63
|
+
| `Outlook auth expired for account=X; run outlook-account-register` | Refresh-then-retry still got 401 | Re-register |
|
|
64
|
+
| `Outlook rate-limited without Retry-After hint` | Graph 429 with no backoff guidance | Wait + retry; if persistent, file bug |
|
|
65
|
+
| `Microsoft Graph does not support on-premises Exchange. Use Task 769 (IMAP).` | Mailbox is on hybrid Exchange | Use the `email` plugin |
|
|
66
|
+
|
|
67
|
+
## Out of scope
|
|
68
|
+
|
|
69
|
+
Write tools (send, draft, move, flag), OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
|
|
@@ -65,7 +65,7 @@ There is no dashboard, no settings panel, no menus. Everything is done through c
|
|
|
65
65
|
|
|
66
66
|
The chat input auto-grows as you type — it expands to fit your message and shrinks back when you delete text. You can also drag the resize handle above the input to set a custom height.
|
|
67
67
|
|
|
68
|
-
The admin interface is a three-pane layout: a sidebar on the left with your brand mark, navigation (Chat, Projects, Artefacts), and your recent conversations; the chat in the middle; and an artefact pane on the right that opens when you select a document or open Browser, Data, or Graph from the menu — holding the surface side-by-side with the conversation so the chat stays live while you work in it. The sidebar's nav rows swap the list view in place — Chat shows recent conversations, Projects shows your active work projects, and Artefacts lists every
|
|
68
|
+
The admin interface is a three-pane layout: a sidebar on the left with your brand mark, navigation (Chat, Projects, Artefacts), and your recent conversations; the chat in the middle; and an artefact pane on the right that opens when you select a document, click a project, or open Browser, Data, or Graph from the menu — holding the surface side-by-side with the conversation so the chat stays live while you work in it. The sidebar's nav rows swap the list view in place — Chat shows recent conversations, Projects shows your active work projects, and Artefacts lists every KnowledgeDocument plus this account's agent templates (your admin agent's IDENTITY, SOUL, and KNOWLEDGE files plus one entry per enabled specialist), all rendered read-only. Click an artefact row to open the document; click a project row to open the Graph view focused on that project's neighbourhood — clicking a second project swaps the focus rather than stacking on top. The chat / artefact divider is drag-resizable — drag the line between the columns to make either side wider; double-click it to reset to half of the available width (viewport minus sidebar), clamped to the chat / artefact min-width floors. Your chosen width is remembered across reloads. On wider screens (>1280px) all three panes are visible. The sidebar narrows at 1280px, the artefact pane hides at 1080px (Browser, Data, and Graph then open as full-window pages instead), and the sidebar collapses to a 56px icon rail at 820px. On phones (<640px) the sidebar slides in as a drawer from the left when you tap the menu icon in the chat header.
|
|
69
69
|
|
|
70
70
|
Page titles are brand-aware: the browser tab shows your product name (e.g. `Real Agent` instead of `Maxy`) on every shell — chat, graph, and data — so a non-default brand never leaks the default name in tab strips or browser history.
|
|
71
71
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: outlook
|
|
3
|
+
description: Microsoft 365 / Outlook.com via Microsoft Graph (read-only first cut). Per-account OAuth Auth Code + PKCE; no client secret. Tools — outlook-account-register: PKCE register; outlook-mail-list / outlook-mail-search: inbox; outlook-calendar-list / outlook-calendar-event: calendar; outlook-contacts-list: contacts; outlook-mailbox-info: auth state + folder count.
|
|
4
|
+
tools:
|
|
5
|
+
- outlook-account-register
|
|
6
|
+
- outlook-mail-list
|
|
7
|
+
- outlook-mail-search
|
|
8
|
+
- outlook-calendar-list
|
|
9
|
+
- outlook-calendar-event
|
|
10
|
+
- outlook-contacts-list
|
|
11
|
+
- outlook-mailbox-info
|
|
12
|
+
always: false
|
|
13
|
+
embed: ["admin"]
|
|
14
|
+
metadata: {"platform":{"optional":true}}
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Outlook
|
|
18
|
+
|
|
19
|
+
Read-only Microsoft Graph access for Outlook.com / Microsoft 365 mailboxes. Admin agent only — public agent surface is explicitly excluded.
|
|
20
|
+
|
|
21
|
+
## Capabilities
|
|
22
|
+
|
|
23
|
+
- **Register:** `outlook-account-register` — initiates OAuth Auth Code + PKCE flow against the operator's Entra app. Returns an authorization URL the operator opens in the VNC browser to consent. Tokens persist per-account, encrypted on disk (AES-256-CBC).
|
|
24
|
+
- **Mail (read-only):** `outlook-mail-list` returns recent messages; `outlook-mail-search` runs a Graph `$search` query.
|
|
25
|
+
- **Calendar (read-only):** `outlook-calendar-list` returns events in a future range; `outlook-calendar-event` returns a single event with full body + attendees.
|
|
26
|
+
- **Contacts (read-only):** `outlook-contacts-list` returns contacts.
|
|
27
|
+
- **Health:** `outlook-mailbox-info` reports auth state, refresh-window status, and top-level folder count. Use this to answer "did account X auth?" without grepping logs.
|
|
28
|
+
|
|
29
|
+
## Out of scope
|
|
30
|
+
|
|
31
|
+
- Send / draft / move / flag — write surfaces are deferred to a follow-up task.
|
|
32
|
+
- OneDrive, Files, Sites — separate plugin; different endpoints.
|
|
33
|
+
- Push notifications via `/subscriptions` — first cut polls.
|
|
34
|
+
- On-premises Exchange — Microsoft Graph deprecated hybrid REST 2023-07. The plugin detects on-prem mailboxes and routes the operator to the IMAP path (`email` plugin).
|
|
35
|
+
- M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`) — separate plugin.
|
|
36
|
+
|
|
37
|
+
## References
|
|
38
|
+
|
|
39
|
+
| Reference | Topics |
|
|
40
|
+
|-----------|--------|
|
|
41
|
+
| [auth.md](references/auth.md) | Entra app registration, PKCE flow, vendored upstream SHA, re-vendor procedure |
|
|
42
|
+
| [graph-surfaces.md](references/graph-surfaces.md) | Graph endpoint and response shape per tool |
|
|
43
|
+
|
|
44
|
+
## Skills
|
|
45
|
+
|
|
46
|
+
| Skill | Purpose |
|
|
47
|
+
|-------|---------|
|
|
48
|
+
| [outlook](skills/outlook/SKILL.md) | When to activate; how to register a new Outlook account |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/graph-client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { classifyGraphError } from "../lib/graph-client.js";
|
|
4
|
+
test("classifyGraphError 401 → auth", () => {
|
|
5
|
+
const cls = classifyGraphError({
|
|
6
|
+
statusCode: 401,
|
|
7
|
+
code: "InvalidAuthenticationToken",
|
|
8
|
+
body: '{"error":{"code":"InvalidAuthenticationToken"}}',
|
|
9
|
+
});
|
|
10
|
+
assert.equal(cls.kind, "auth");
|
|
11
|
+
assert.equal(cls.statusCode, 401);
|
|
12
|
+
});
|
|
13
|
+
test("classifyGraphError 429 with Retry-After → rate-limit-recoverable", () => {
|
|
14
|
+
const cls = classifyGraphError({
|
|
15
|
+
statusCode: 429,
|
|
16
|
+
headers: { "Retry-After": "30" },
|
|
17
|
+
});
|
|
18
|
+
assert.equal(cls.kind, "rate-limit-recoverable");
|
|
19
|
+
if (cls.kind === "rate-limit-recoverable") {
|
|
20
|
+
assert.equal(cls.retryAfterMs, 30000);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
test("classifyGraphError 429 lowercase Retry-After header still parsed", () => {
|
|
24
|
+
const cls = classifyGraphError({
|
|
25
|
+
statusCode: 429,
|
|
26
|
+
headers: { "retry-after": "5" },
|
|
27
|
+
});
|
|
28
|
+
assert.equal(cls.kind, "rate-limit-recoverable");
|
|
29
|
+
if (cls.kind === "rate-limit-recoverable") {
|
|
30
|
+
assert.equal(cls.retryAfterMs, 5000);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
test("classifyGraphError 429 without Retry-After → rate-limit-terminal", () => {
|
|
34
|
+
const cls = classifyGraphError({
|
|
35
|
+
statusCode: 429,
|
|
36
|
+
headers: {},
|
|
37
|
+
});
|
|
38
|
+
assert.equal(cls.kind, "rate-limit-terminal");
|
|
39
|
+
});
|
|
40
|
+
test("classifyGraphError 503 + MailboxNotEnabledForRESTAPI → on-prem", () => {
|
|
41
|
+
const cls = classifyGraphError({
|
|
42
|
+
statusCode: 503,
|
|
43
|
+
code: "MailboxNotEnabledForRESTAPI",
|
|
44
|
+
body: '{"error":{"code":"MailboxNotEnabledForRESTAPI","message":"REST API is not yet supported for this mailbox."}}',
|
|
45
|
+
headers: { "x-mailbox-server": "ex2019.acme.local" },
|
|
46
|
+
});
|
|
47
|
+
assert.equal(cls.kind, "on-prem");
|
|
48
|
+
if (cls.kind === "on-prem") {
|
|
49
|
+
assert.equal(cls.statusCode, 503);
|
|
50
|
+
assert.equal(cls.mailServer, "ex2019.acme.local");
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
test("classifyGraphError 503 without MailboxNotEnabledForRESTAPI → 5xx (NOT on-prem)", () => {
|
|
54
|
+
// Critical: on-prem detection keys off the structured code field, not body
|
|
55
|
+
// prose. A generic 503 must NOT be misclassified as on-prem.
|
|
56
|
+
const cls = classifyGraphError({
|
|
57
|
+
statusCode: 503,
|
|
58
|
+
code: "ServiceUnavailable",
|
|
59
|
+
body: '{"error":{"code":"ServiceUnavailable","message":"REST API on-premises mailboxes are temporarily unavailable for everyone, somehow"}}',
|
|
60
|
+
});
|
|
61
|
+
assert.equal(cls.kind, "5xx", "must not infer on-prem from prose body");
|
|
62
|
+
});
|
|
63
|
+
test("classifyGraphError parses code from body when not on top-level", () => {
|
|
64
|
+
const cls = classifyGraphError({
|
|
65
|
+
statusCode: 503,
|
|
66
|
+
body: '{"error":{"code":"MailboxNotEnabledForRESTAPI","message":"foo"}}',
|
|
67
|
+
});
|
|
68
|
+
assert.equal(cls.kind, "on-prem");
|
|
69
|
+
});
|
|
70
|
+
test("classifyGraphError 500 → 5xx", () => {
|
|
71
|
+
const cls = classifyGraphError({ statusCode: 500, code: "InternalServerError" });
|
|
72
|
+
assert.equal(cls.kind, "5xx");
|
|
73
|
+
});
|
|
74
|
+
test("classifyGraphError 404 → other", () => {
|
|
75
|
+
const cls = classifyGraphError({ statusCode: 404, code: "ItemNotFound" });
|
|
76
|
+
assert.equal(cls.kind, "other");
|
|
77
|
+
if (cls.kind === "other") {
|
|
78
|
+
assert.equal(cls.statusCode, 404);
|
|
79
|
+
assert.equal(cls.code, "ItemNotFound");
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
test("classifyGraphError reads Retry-After via fetch-style response.headers.get", () => {
|
|
83
|
+
const cls = classifyGraphError({
|
|
84
|
+
statusCode: 429,
|
|
85
|
+
response: {
|
|
86
|
+
headers: { get: (h) => (h.toLowerCase() === "retry-after" ? "10" : null) },
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
assert.equal(cls.kind, "rate-limit-recoverable");
|
|
90
|
+
if (cls.kind === "rate-limit-recoverable") {
|
|
91
|
+
assert.equal(cls.retryAfterMs, 10000);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=graph-client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-client.test.js","sourceRoot":"","sources":["../../src/__tests__/graph-client.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/log.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { redact, trunc } from "../lib/log.js";
|
|
4
|
+
test("redact masks Bearer tokens", () => {
|
|
5
|
+
const input = "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.payload.signature";
|
|
6
|
+
assert.match(redact(input), /Bearer <REDACTED>/);
|
|
7
|
+
assert.doesNotMatch(redact(input), /eyJhbGciOiJIUzI1NiJ9/);
|
|
8
|
+
});
|
|
9
|
+
test("redact masks access_token query strings", () => {
|
|
10
|
+
const input = "https://login.microsoftonline.com/cb?access_token=abc123_secret&state=xyz";
|
|
11
|
+
const out = redact(input);
|
|
12
|
+
assert.match(out, /access_token=<REDACTED>/);
|
|
13
|
+
assert.doesNotMatch(out, /abc123_secret/);
|
|
14
|
+
// Other query params survive.
|
|
15
|
+
assert.match(out, /state=xyz/);
|
|
16
|
+
});
|
|
17
|
+
test("redact is a no-op on safe text", () => {
|
|
18
|
+
assert.equal(redact("user logged in account=abc count=5"), "user logged in account=abc count=5");
|
|
19
|
+
});
|
|
20
|
+
test("redact handles multiple Bearer tokens in one string", () => {
|
|
21
|
+
const input = "first Bearer aaaaa second Bearer bbbbb";
|
|
22
|
+
const out = redact(input);
|
|
23
|
+
assert.match(out, /first Bearer <REDACTED> second Bearer <REDACTED>/);
|
|
24
|
+
});
|
|
25
|
+
test("trunc preserves short values verbatim", () => {
|
|
26
|
+
assert.equal(trunc("short", 10), "short");
|
|
27
|
+
});
|
|
28
|
+
test("trunc shortens long values with ellipsis", () => {
|
|
29
|
+
assert.equal(trunc("abcdefghijklmnop", 10), "abcdefghi…");
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=log.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.test.js","sourceRoot":"","sources":["../../src/__tests__/log.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE9C,IAAI,CAAC,4BAA4B,EAAE,GAAG,EAAE;IACtC,MAAM,KAAK,GAAG,8DAA8D,CAAC;IAC7E,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC,CAAC;IACjD,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,sBAAsB,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACnD,MAAM,KAAK,GAAG,2EAA2E,CAAC;IAC1F,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC;IAC7C,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAC1C,8BAA8B;IAC9B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC1C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,oCAAoC,CAAC,EAAE,oCAAoC,CAAC,CAAC;AACnG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAC/D,MAAM,KAAK,GAAG,wCAAwC,CAAC;IACvD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,kDAAkD,CAAC,CAAC;AACxE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;IACjD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACpD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,kBAAkB,EAAE,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce-flow.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/pkce-flow.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { _internals, startPkceFlow } from "../auth/pkce-flow.js";
|
|
4
|
+
const { sameLengthEqual } = _internals;
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// sameLengthEqual: timing-safe state comparison
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
test("sameLengthEqual matches identical strings", () => {
|
|
9
|
+
assert.equal(sameLengthEqual("abcdef0123456789", "abcdef0123456789"), true);
|
|
10
|
+
});
|
|
11
|
+
test("sameLengthEqual rejects different equal-length strings", () => {
|
|
12
|
+
assert.equal(sameLengthEqual("abcdef0123456789", "abcdef0123456780"), false);
|
|
13
|
+
});
|
|
14
|
+
test("sameLengthEqual rejects length mismatch without throwing", () => {
|
|
15
|
+
assert.equal(sameLengthEqual("short", "longerstring"), false);
|
|
16
|
+
});
|
|
17
|
+
function mockFetch(handler) {
|
|
18
|
+
const calls = [];
|
|
19
|
+
const original = globalThis.fetch;
|
|
20
|
+
globalThis.fetch = (async (url, init) => {
|
|
21
|
+
const urlStr = typeof url === "string" ? url : url instanceof URL ? url.toString() : url.url;
|
|
22
|
+
// Pass loopback callbacks through to the real fetch — only the upstream
|
|
23
|
+
// token endpoint is intercepted. Otherwise the test's own callback
|
|
24
|
+
// request would loop back into the mock and never reach the bound server.
|
|
25
|
+
if (urlStr.startsWith("http://127.0.0.1:") || urlStr.startsWith("http://localhost:")) {
|
|
26
|
+
return original(url, init);
|
|
27
|
+
}
|
|
28
|
+
calls.push({ url: urlStr, init });
|
|
29
|
+
return handler(urlStr, init);
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
calls,
|
|
33
|
+
restore: () => {
|
|
34
|
+
globalThis.fetch = original;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function tokenSuccess() {
|
|
39
|
+
return new Response(JSON.stringify({
|
|
40
|
+
access_token: "test-access",
|
|
41
|
+
refresh_token: "test-refresh",
|
|
42
|
+
expires_in: 3600,
|
|
43
|
+
token_type: "Bearer",
|
|
44
|
+
scope: "offline_access User.Read Mail.Read Calendars.Read Contacts.Read",
|
|
45
|
+
}), { status: 200, headers: { "Content-Type": "application/json" } });
|
|
46
|
+
}
|
|
47
|
+
function extractStateFromAuthUrl(authUrl) {
|
|
48
|
+
const url = new URL(authUrl);
|
|
49
|
+
const state = url.searchParams.get("state");
|
|
50
|
+
assert.ok(state, "auth URL must include state param");
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
function extractRedirectUriPort(redirectUri) {
|
|
54
|
+
const url = new URL(redirectUri);
|
|
55
|
+
return Number(url.port);
|
|
56
|
+
}
|
|
57
|
+
test("PKCE happy path: state matches → tokens exchanged", async () => {
|
|
58
|
+
const fetchMock = mockFetch(() => tokenSuccess());
|
|
59
|
+
try {
|
|
60
|
+
const flow = await startPkceFlow({
|
|
61
|
+
clientId: "test-client",
|
|
62
|
+
tenantId: "common",
|
|
63
|
+
accountId: "acct-pkce-1",
|
|
64
|
+
});
|
|
65
|
+
const state = extractStateFromAuthUrl(flow.authUrl);
|
|
66
|
+
const port = extractRedirectUriPort(flow.redirectUri);
|
|
67
|
+
// Drive the callback synthetically, as Microsoft would after consent.
|
|
68
|
+
const callbackResp = await fetch(`http://127.0.0.1:${port}/callback?code=fake-code&state=${state}`);
|
|
69
|
+
assert.equal(callbackResp.status, 200);
|
|
70
|
+
const result = await flow.result;
|
|
71
|
+
assert.equal(result.tokenResponse.access_token, "test-access");
|
|
72
|
+
assert.equal(result.tokenResponse.refresh_token, "test-refresh");
|
|
73
|
+
assert.deepEqual(result.scopes.sort(), [
|
|
74
|
+
"Calendars.Read",
|
|
75
|
+
"Contacts.Read",
|
|
76
|
+
"Mail.Read",
|
|
77
|
+
"User.Read",
|
|
78
|
+
"offline_access",
|
|
79
|
+
].sort());
|
|
80
|
+
// Token endpoint was called with code + verifier + redirect_uri.
|
|
81
|
+
assert.equal(fetchMock.calls.length, 1);
|
|
82
|
+
const body = String(fetchMock.calls[0].init?.body ?? "");
|
|
83
|
+
assert.match(body, /code=fake-code/);
|
|
84
|
+
assert.match(body, /code_verifier=/);
|
|
85
|
+
assert.match(body, /grant_type=authorization_code/);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
fetchMock.restore();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
test("PKCE state mismatch → flow rejects, token endpoint NOT called", async () => {
|
|
92
|
+
const fetchMock = mockFetch(() => {
|
|
93
|
+
throw new Error("token endpoint should not be called on state mismatch");
|
|
94
|
+
});
|
|
95
|
+
try {
|
|
96
|
+
const flow = await startPkceFlow({
|
|
97
|
+
clientId: "test-client",
|
|
98
|
+
tenantId: "common",
|
|
99
|
+
accountId: "acct-pkce-2",
|
|
100
|
+
});
|
|
101
|
+
// Subscribe BEFORE driving the callback — otherwise the rejection lands
|
|
102
|
+
// before any handler is attached and Node flags it as unhandled.
|
|
103
|
+
const rejection = assert.rejects(flow.result, /State mismatch/i);
|
|
104
|
+
const port = extractRedirectUriPort(flow.redirectUri);
|
|
105
|
+
const cbResp = await fetch(`http://127.0.0.1:${port}/callback?code=fake-code&state=wrong-state-value`);
|
|
106
|
+
assert.equal(cbResp.status, 400);
|
|
107
|
+
await rejection;
|
|
108
|
+
assert.equal(fetchMock.calls.length, 0, "token endpoint must not be called");
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
fetchMock.restore();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
test("PKCE OAuth error in callback → flow rejects, no token call", async () => {
|
|
115
|
+
const fetchMock = mockFetch(() => {
|
|
116
|
+
throw new Error("token endpoint should not be called on OAuth error");
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
const flow = await startPkceFlow({
|
|
120
|
+
clientId: "test-client",
|
|
121
|
+
tenantId: "common",
|
|
122
|
+
accountId: "acct-pkce-3",
|
|
123
|
+
});
|
|
124
|
+
const rejection = assert.rejects(flow.result, /OAuth error: access_denied/);
|
|
125
|
+
const state = extractStateFromAuthUrl(flow.authUrl);
|
|
126
|
+
const port = extractRedirectUriPort(flow.redirectUri);
|
|
127
|
+
const cbResp = await fetch(`http://127.0.0.1:${port}/callback?error=access_denied&error_description=user%20cancelled&state=${state}`);
|
|
128
|
+
assert.equal(cbResp.status, 400);
|
|
129
|
+
await rejection;
|
|
130
|
+
assert.equal(fetchMock.calls.length, 0);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
fetchMock.restore();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
test("PKCE callback missing code → flow rejects", async () => {
|
|
137
|
+
const fetchMock = mockFetch(() => {
|
|
138
|
+
throw new Error("token endpoint should not be called");
|
|
139
|
+
});
|
|
140
|
+
try {
|
|
141
|
+
const flow = await startPkceFlow({
|
|
142
|
+
clientId: "test-client",
|
|
143
|
+
tenantId: "common",
|
|
144
|
+
accountId: "acct-pkce-4",
|
|
145
|
+
});
|
|
146
|
+
const rejection = assert.rejects(flow.result, /No authorization code received/);
|
|
147
|
+
const state = extractStateFromAuthUrl(flow.authUrl);
|
|
148
|
+
const port = extractRedirectUriPort(flow.redirectUri);
|
|
149
|
+
const cbResp = await fetch(`http://127.0.0.1:${port}/callback?state=${state}`);
|
|
150
|
+
assert.equal(cbResp.status, 400);
|
|
151
|
+
await rejection;
|
|
152
|
+
assert.equal(fetchMock.calls.length, 0);
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
fetchMock.restore();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
test("PKCE auth URL has S256 challenge + correct scopes + prompt=select_account", async () => {
|
|
159
|
+
// No mock needed — we assert URL structure and abort the flow without
|
|
160
|
+
// exchanging tokens. Sending an OAuth error in the callback closes the
|
|
161
|
+
// server and rejects flow.result loudly; we catch it explicitly.
|
|
162
|
+
const flow = await startPkceFlow({
|
|
163
|
+
clientId: "client-X",
|
|
164
|
+
tenantId: "common",
|
|
165
|
+
accountId: "acct-pkce-5",
|
|
166
|
+
});
|
|
167
|
+
const expectedRejection = assert.rejects(flow.result, /OAuth error/);
|
|
168
|
+
try {
|
|
169
|
+
const url = new URL(flow.authUrl);
|
|
170
|
+
assert.equal(url.searchParams.get("client_id"), "client-X");
|
|
171
|
+
assert.equal(url.searchParams.get("response_type"), "code");
|
|
172
|
+
assert.equal(url.searchParams.get("code_challenge_method"), "S256");
|
|
173
|
+
assert.equal(url.searchParams.get("prompt"), "select_account");
|
|
174
|
+
const challenge = url.searchParams.get("code_challenge");
|
|
175
|
+
assert.ok(challenge && challenge.length > 30, "challenge should be base64url-encoded SHA-256");
|
|
176
|
+
const scope = url.searchParams.get("scope") ?? "";
|
|
177
|
+
assert.match(scope, /offline_access/);
|
|
178
|
+
assert.match(scope, /User\.Read/);
|
|
179
|
+
assert.match(scope, /Mail\.Read/);
|
|
180
|
+
assert.match(scope, /Calendars\.Read/);
|
|
181
|
+
assert.match(scope, /Contacts\.Read/);
|
|
182
|
+
// Abort the flow by sending an OAuth error. Server closes; result rejects.
|
|
183
|
+
const port = extractRedirectUriPort(flow.redirectUri);
|
|
184
|
+
const state = url.searchParams.get("state");
|
|
185
|
+
await fetch(`http://127.0.0.1:${port}/callback?error=access_denied&state=${state}`);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
await expectedRejection;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
test("PKCE redirect URI uses ephemeral loopback port (not fixed 49152)", async () => {
|
|
192
|
+
// No token-exchange mock — abort each flow with an OAuth error so the
|
|
193
|
+
// server closes without invoking exchangeCode.
|
|
194
|
+
const flow1 = await startPkceFlow({ clientId: "c", tenantId: "common", accountId: "a1" });
|
|
195
|
+
const flow2 = await startPkceFlow({ clientId: "c", tenantId: "common", accountId: "a2" });
|
|
196
|
+
const r1 = assert.rejects(flow1.result, /OAuth error/);
|
|
197
|
+
const r2 = assert.rejects(flow2.result, /OAuth error/);
|
|
198
|
+
try {
|
|
199
|
+
const port1 = extractRedirectUriPort(flow1.redirectUri);
|
|
200
|
+
const port2 = extractRedirectUriPort(flow2.redirectUri);
|
|
201
|
+
assert.ok(port1 > 0 && port1 < 65536);
|
|
202
|
+
assert.ok(port2 > 0 && port2 < 65536);
|
|
203
|
+
assert.notEqual(port1, port2, "two parallel flows must allocate different ephemeral ports");
|
|
204
|
+
const s1 = extractStateFromAuthUrl(flow1.authUrl);
|
|
205
|
+
const s2 = extractStateFromAuthUrl(flow2.authUrl);
|
|
206
|
+
await fetch(`http://127.0.0.1:${port1}/callback?error=access_denied&state=${s1}`);
|
|
207
|
+
await fetch(`http://127.0.0.1:${port2}/callback?error=access_denied&state=${s2}`);
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
await Promise.all([r1, r2]);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
//# sourceMappingURL=pkce-flow.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce-flow.test.js","sourceRoot":"","sources":["../../src/__tests__/pkce-flow.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/token-store.test.ts"],"names":[],"mappings":""}
|