@rubytech/create-maxy-code 0.1.460 → 0.1.462
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/dist/__tests__/cron-heartbeat-registration.test.js +57 -28
- package/dist/__tests__/launchd-plist.test.js +26 -0
- package/dist/cron-registration.js +30 -8
- package/dist/index.js +50 -9
- package/dist/launchd-plist.js +1 -1
- package/dist/uninstall.js +9 -3
- package/package.json +1 -1
- package/payload/platform/lib/graph-mcp/dist/index.d.ts +2 -1
- package/payload/platform/lib/graph-mcp/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-mcp/dist/index.js +14 -22
- package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-mcp/src/index.ts +15 -22
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts +8 -7
- package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js +19 -19
- package/payload/platform/lib/mcp-spawn-tee/dist/index.js.map +1 -1
- package/payload/platform/lib/mcp-spawn-tee/src/__tests__/spawn-tee.test.ts +34 -5
- package/payload/platform/lib/mcp-spawn-tee/src/index.ts +22 -19
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts +35 -17
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js +41 -48
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js.map +1 -1
- package/payload/platform/lib/mcp-stderr-tee/src/index.ts +42 -51
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js +27 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +49 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/audit.d.ts +11 -0
- package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/audit.js +16 -0
- package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +18 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +77 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/house-scoped-token.js +3 -0
- package/payload/platform/lib/storage-broker/dist/house-scoped-token.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/registry.d.ts +1 -1
- package/payload/platform/lib/storage-broker/dist/registry.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/registry.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/__tests__/audit.test.ts +32 -1
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +61 -0
- package/payload/platform/lib/storage-broker/src/audit.ts +45 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +91 -0
- package/payload/platform/lib/storage-broker/src/house-scoped-token.ts +3 -0
- package/payload/platform/lib/storage-broker/src/registry.ts +5 -1
- package/payload/platform/plugins/admin/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +22 -11
- package/payload/platform/plugins/aeo/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/browser/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -1
- package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +96 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-route.test.ts +54 -40
- package/payload/platform/plugins/cloudflare/mcp/__tests__/passcode.test.ts +169 -39
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +207 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +5 -5
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/package.json +1 -1
- package/payload/platform/plugins/cloudflare/references/hosting-sites.md +2 -0
- package/payload/platform/plugins/cloudflare/skills/calendar-site/SKILL.md +1 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +21 -19
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/passcode.mjs +193 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/auth.ts +18 -12
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/schema.sql +14 -5
- package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +2 -0
- package/payload/platform/plugins/connector/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/contacts/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/docs/references/admin-session.md +3 -3
- package/payload/platform/plugins/docs/references/cloudflare.md +2 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +11 -4
- package/payload/platform/plugins/docs/references/plugins-guide.md +8 -6
- package/payload/platform/plugins/email/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/filesystem/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/graph-viewer/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/memory/PLUGIN.md +1 -1
- package/payload/platform/plugins/memory/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -1
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +2 -2
- package/payload/platform/plugins/outlook/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-threaded.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-threaded.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-threaded.test.js +238 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-threaded.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +9 -5
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts +58 -9
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +113 -19
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js.map +1 -1
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +5 -2
- package/payload/platform/plugins/quickbooks/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/replicate/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/scheduling/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/storage-broker/PLUGIN.md +9 -1
- package/payload/platform/plugins/storage-broker/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.d.ts.map +1 -1
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.js +16 -5
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.js.map +1 -1
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js +49 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/telegram/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/url-get/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/voice-mirror/PLUGIN.md +1 -1
- package/payload/platform/plugins/voice-mirror/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +98 -9
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js +54 -21
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +110 -0
- package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +6 -3
- package/payload/platform/plugins/web-designer/PLUGIN.md +1 -1
- package/payload/platform/plugins/web-designer/commands/website.md +6 -0
- package/payload/platform/plugins/web-designer/skills/web-design/references/deploy.md +12 -0
- package/payload/platform/plugins/whatsapp/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/work/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/plugins/workflows/lib/mcp-spawn-tee/index.js +19 -19
- package/payload/platform/scripts/__tests__/logs-rotate.test.sh +260 -0
- package/payload/platform/scripts/__tests__/resume-tunnel.test.sh +79 -0
- package/payload/platform/scripts/logs-read.sh +31 -0
- package/payload/platform/scripts/logs-rotate.sh +207 -0
- package/payload/platform/scripts/resume-tunnel.sh +27 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +41 -5
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.d.ts +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +22 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.d.ts +2 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.js +12 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts +122 -9
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.js +214 -29
- package/payload/platform/services/claude-session-manager/dist/pty-census.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +39 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +5 -3
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +13 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.js +4 -2
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts +150 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +333 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.js +55 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.js.map +1 -0
- package/payload/server/{chunk-JXWFVE5X.js → chunk-BBSPFN45.js} +65 -0
- package/payload/server/public/assets/{AdminLoginScreens-BFbpkEUS.js → AdminLoginScreens-Bed8SDxY.js} +1 -1
- package/payload/server/public/assets/{AdminShell-CdOGObaZ.js → AdminShell-BLh9O6dn.js} +1 -1
- package/payload/server/public/assets/{Checkbox-RFD_F0c0.js → Checkbox-BC_KSbpS.js} +1 -1
- package/payload/server/public/assets/{admin-CYGdL-1O.js → admin-DfWgJVI3.js} +1 -1
- package/payload/server/public/assets/{browser-DVfa_plj.js → browser-C0vkv-hH.js} +1 -1
- package/payload/server/public/assets/{calendar-JC_Dxa4n.js → calendar-_pfHw0Hp.js} +1 -1
- package/payload/server/public/assets/chat-CufidiBQ.js +1 -0
- package/payload/server/public/assets/chevron-left-BnpVBu51.js +1 -0
- package/payload/server/public/assets/data-CR-fbPWr.js +1 -0
- package/payload/server/public/assets/{graph-hBUrRSkr.js → graph-BZiudUGr.js} +1 -1
- package/payload/server/public/assets/{graph-labels-CHh6zs8t.js → graph-labels-BzmmJwCR.js} +1 -1
- package/payload/server/public/assets/{maximize-2-DjxE4rm9.js → maximize-2-Bhdsv0U-.js} +1 -1
- package/payload/server/public/assets/{operator-BqU9SQuu.js → operator-Cj0B_usT.js} +1 -1
- package/payload/server/public/assets/page-84Fdwiu_.js +32 -0
- package/payload/server/public/assets/{page-DiQhU6lD.js → page-8AB3jUa_.js} +1 -1
- package/payload/server/public/assets/{public-Cu_eALgY.js → public-BkNW9e9T.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-F9DsSTSJ.js → rotate-ccw-D_5cETGt.js} +1 -1
- package/payload/server/public/assets/{tasks-CE-D0f50.js → tasks-EyKLXINi.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-D2rsWhov.js → time-entry-format-DAwmmR3i.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-QxOmoobV.js → useCopyFeedback-rOWd_lzl.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-RIzNqFjV.js → useSelectionMode-DDmLmPph.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-BW_fK1Nt.css → useSubAccountSwitcher-pu0jjLCz.css} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-WrWurSYI.js → useVoiceRecorder-azG8zSUI.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +10 -10
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +13 -13
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +581 -209
- package/payload/server/{src-4F37OHLK.js → src-YLGAZY36.js} +5 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/passcode.ts +0 -73
- package/payload/server/public/assets/chat-Yf8Nkop8.js +0 -1
- package/payload/server/public/assets/chevron-left-CAiAUBxO.js +0 -1
- package/payload/server/public/assets/data-hqdYXaEn.js +0 -1
- package/payload/server/public/assets/page-B4c4_4HP.js +0 -32
- /package/payload/server/public/assets/{useSubAccountSwitcher-wo_qGW-I.js → useSubAccountSwitcher-CU-lOxTd.js} +0 -0
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Claude Code CLI → shim (this file) = node running <real-entry> in-process
|
|
15
15
|
* stdin/stdout : untouched (JSON-RPC channel)
|
|
16
|
-
* stderr : process.stderr.write teed →
|
|
16
|
+
* stderr : process.stderr.write teed → raw sink + passthrough
|
|
17
17
|
*
|
|
18
|
-
* Destinations
|
|
19
|
-
* - `${LOG_DIR}/mcp-<name
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* Destinations:
|
|
19
|
+
* - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — raw stderr + lifecycle lines.
|
|
20
|
+
* The sole raw sink. Task 1721 retired the per-date twin: its date was
|
|
21
|
+
* pinned at spawn, no reader ever parsed it, and mcp-stderr-tee wrote the
|
|
22
|
+
* same file, so every line landed twice.
|
|
23
|
+
* - `${LOG_DIR}/mcp-<name>-nosession.log` — the same, for a spawn with
|
|
24
|
+
* no SESSION_ID (enumeration). One fixed file per server.
|
|
24
25
|
* - `server.log` via the loopback log-ingest route — best-effort mirror of
|
|
25
26
|
* the [mcp-helper] lifecycle lines.
|
|
26
27
|
*
|
|
@@ -49,13 +50,13 @@ const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
|
49
50
|
const ENTRY = process.argv[2];
|
|
50
51
|
const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
|
|
51
52
|
const spawnStamp = Date.now();
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}
|
|
53
|
+
// The one raw sink (Task 1721). A sessionless spawn (rc-daemon stamps
|
|
54
|
+
// SESSION_ID='' — rc-daemon.ts:513 — while still stamping LOG_DIR) shares one
|
|
55
|
+
// fixed file per server rather than a date-named one: the date was never read
|
|
56
|
+
// by any reader and named a process lifetime, not a day. Interleaved spawns in
|
|
57
|
+
// the nosession file are separated by the [mcp-helper] op=spawn pid= lines.
|
|
58
|
+
const rawSinkPath = LOG_DIR
|
|
59
|
+
? (0, node_path_1.resolve)(LOG_DIR, SESSION_ID ? `mcp-${SERVER_NAME}-${SESSION_ID}.log` : `mcp-${SERVER_NAME}-nosession.log`)
|
|
59
60
|
: undefined;
|
|
60
61
|
// Rolling tail of entry stderr for op=exit. Capped so a chatty server cannot
|
|
61
62
|
// grow the buffer without bound.
|
|
@@ -66,7 +67,7 @@ let exitEmitted = false;
|
|
|
66
67
|
let exitSignal;
|
|
67
68
|
// The real stderr writer, captured before the tee replaces it. Lifecycle lines
|
|
68
69
|
// and stderr passthrough both go through this so they are never re-teed into
|
|
69
|
-
// the
|
|
70
|
+
// the raw sink nor recursed back into the patched writer.
|
|
70
71
|
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
71
72
|
// LOG_DIR is created once, lazily, on the first successful append — not per
|
|
72
73
|
// chunk. teeWrite runs on the server's own stderr hot path now, so a per-call
|
|
@@ -115,7 +116,7 @@ function postToServerLog(suffix, level) {
|
|
|
115
116
|
// log-ingest route rejects newlines).
|
|
116
117
|
function emitLifecycle(suffix, level) {
|
|
117
118
|
const line = `[mcp-helper] ${suffix}\n`;
|
|
118
|
-
appendSafe(
|
|
119
|
+
appendSafe(rawSinkPath, line);
|
|
119
120
|
try {
|
|
120
121
|
rawStderrWrite(line);
|
|
121
122
|
}
|
|
@@ -127,12 +128,11 @@ if (!ENTRY) {
|
|
|
127
128
|
process.exit(2);
|
|
128
129
|
}
|
|
129
130
|
// Replace process.stderr.write with a tee: mirror every server stderr byte to
|
|
130
|
-
// the
|
|
131
|
+
// the raw sink, keep a rolling tail for op=exit, emit
|
|
131
132
|
// op=boot on the first bytes, then pass the write through to the real stderr.
|
|
132
133
|
const teeWrite = ((...args) => {
|
|
133
134
|
const chunk = args[0];
|
|
134
|
-
appendSafe(
|
|
135
|
-
appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
|
|
135
|
+
appendSafe(rawSinkPath, chunk); // raw sink (Task 1721)
|
|
136
136
|
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
137
137
|
stderrTail = (stderrTail + text).slice(-TAIL_CAP);
|
|
138
138
|
if (!bootEmitted) {
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Claude Code CLI → shim (this file) = node running <real-entry> in-process
|
|
15
15
|
* stdin/stdout : untouched (JSON-RPC channel)
|
|
16
|
-
* stderr : process.stderr.write teed →
|
|
16
|
+
* stderr : process.stderr.write teed → raw sink + passthrough
|
|
17
17
|
*
|
|
18
|
-
* Destinations
|
|
19
|
-
* - `${LOG_DIR}/mcp-<name
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* Destinations:
|
|
19
|
+
* - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — raw stderr + lifecycle lines.
|
|
20
|
+
* The sole raw sink. Task 1721 retired the per-date twin: its date was
|
|
21
|
+
* pinned at spawn, no reader ever parsed it, and mcp-stderr-tee wrote the
|
|
22
|
+
* same file, so every line landed twice.
|
|
23
|
+
* - `${LOG_DIR}/mcp-<name>-nosession.log` — the same, for a spawn with
|
|
24
|
+
* no SESSION_ID (enumeration). One fixed file per server.
|
|
24
25
|
* - `server.log` via the loopback log-ingest route — best-effort mirror of
|
|
25
26
|
* the [mcp-helper] lifecycle lines.
|
|
26
27
|
*
|
|
@@ -49,13 +50,13 @@ const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
|
49
50
|
const ENTRY = process.argv[2];
|
|
50
51
|
const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
|
|
51
52
|
const spawnStamp = Date.now();
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}
|
|
53
|
+
// The one raw sink (Task 1721). A sessionless spawn (rc-daemon stamps
|
|
54
|
+
// SESSION_ID='' — rc-daemon.ts:513 — while still stamping LOG_DIR) shares one
|
|
55
|
+
// fixed file per server rather than a date-named one: the date was never read
|
|
56
|
+
// by any reader and named a process lifetime, not a day. Interleaved spawns in
|
|
57
|
+
// the nosession file are separated by the [mcp-helper] op=spawn pid= lines.
|
|
58
|
+
const rawSinkPath = LOG_DIR
|
|
59
|
+
? (0, node_path_1.resolve)(LOG_DIR, SESSION_ID ? `mcp-${SERVER_NAME}-${SESSION_ID}.log` : `mcp-${SERVER_NAME}-nosession.log`)
|
|
59
60
|
: undefined;
|
|
60
61
|
// Rolling tail of entry stderr for op=exit. Capped so a chatty server cannot
|
|
61
62
|
// grow the buffer without bound.
|
|
@@ -66,7 +67,7 @@ let exitEmitted = false;
|
|
|
66
67
|
let exitSignal;
|
|
67
68
|
// The real stderr writer, captured before the tee replaces it. Lifecycle lines
|
|
68
69
|
// and stderr passthrough both go through this so they are never re-teed into
|
|
69
|
-
// the
|
|
70
|
+
// the raw sink nor recursed back into the patched writer.
|
|
70
71
|
const rawStderrWrite = process.stderr.write.bind(process.stderr);
|
|
71
72
|
// LOG_DIR is created once, lazily, on the first successful append — not per
|
|
72
73
|
// chunk. teeWrite runs on the server's own stderr hot path now, so a per-call
|
|
@@ -115,7 +116,7 @@ function postToServerLog(suffix, level) {
|
|
|
115
116
|
// log-ingest route rejects newlines).
|
|
116
117
|
function emitLifecycle(suffix, level) {
|
|
117
118
|
const line = `[mcp-helper] ${suffix}\n`;
|
|
118
|
-
appendSafe(
|
|
119
|
+
appendSafe(rawSinkPath, line);
|
|
119
120
|
try {
|
|
120
121
|
rawStderrWrite(line);
|
|
121
122
|
}
|
|
@@ -127,12 +128,11 @@ if (!ENTRY) {
|
|
|
127
128
|
process.exit(2);
|
|
128
129
|
}
|
|
129
130
|
// Replace process.stderr.write with a tee: mirror every server stderr byte to
|
|
130
|
-
// the
|
|
131
|
+
// the raw sink, keep a rolling tail for op=exit, emit
|
|
131
132
|
// op=boot on the first bytes, then pass the write through to the real stderr.
|
|
132
133
|
const teeWrite = ((...args) => {
|
|
133
134
|
const chunk = args[0];
|
|
134
|
-
appendSafe(
|
|
135
|
-
appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
|
|
135
|
+
appendSafe(rawSinkPath, chunk); // raw sink (Task 1721)
|
|
136
136
|
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
137
137
|
stderrTail = (stderrTail + text).slice(-TAIL_CAP);
|
|
138
138
|
if (!bootEmitted) {
|
|
@@ -44,7 +44,7 @@ The plugin registers no agent-facing MCP tools. Every Cloudflare operation is th
|
|
|
44
44
|
| [api.md](references/api.md) | Cloudflare API library — canonical docs URL + curated endpoint map (DNS, zones, tunnels, token list/create/verify/revoke), the advanced master-token creation walkthrough, the account-scoped secrets-file storage convention, the agent's reuse-a-stable-per-scope-token discipline, and the standing reconcile pass that revokes strays. |
|
|
45
45
|
| [d1-data-capture.md](references/d1-data-capture.md) | Form → Pages Function → D1 store → read/sweep. The Pages-Edit **and** D1-Edit token-scope requirement, `wrangler d1 create`/`execute --remote`, the `swept` column, plus the establishment discipline: `<accountId>-<purpose>` naming, one store per purpose, and the canonical `acceptances` schema. |
|
|
46
46
|
| [r2-object-storage.md](references/r2-object-storage.md) | R2 buckets under the same establishment discipline as D1: `<accountId>-<purpose>` naming via `cf-store-name.sh`, one bucket per purpose, `wrangler r2 bucket create`, and the store-provision breadcrumb. |
|
|
47
|
-
| [hosting-sites.md](references/hosting-sites.md) | Deploy a static or Next.js site to Cloudflare Pages via `wrangler pages deploy`. |
|
|
47
|
+
| [hosting-sites.md](references/hosting-sites.md) | Deploy a static or Next.js site to Cloudflare Pages via `wrangler pages deploy`. House/single-tenant path: on a multi-tenant install a client account is denied the `pages` scope (`reason=not-house`) and publishes with `storage-broker:storage-pages-deploy` instead. |
|
|
48
48
|
| [web-analytics.md](references/web-analytics.md) | Why a registered site reports 0 visitors — the RUM API is blocked (no permission group), registered ≠ collecting, edge auto-injection is impossible for a Pages-only custom domain, and the beacon must be injected into the site HTML, rebuilt, and redeployed. Outcome: beacon present in live HTML. |
|
|
49
49
|
| [dashboard-guide.md](references/dashboard-guide.md) | Click-paths for operations the operator prefers to do by hand — sign in, switch accounts, add a site, edit an apex CNAME, verify nameservers, delete a tunnel, manage CNAME records, author an Access policy. |
|
|
50
50
|
| [reset-guide.md](references/reset-guide.md) | Decision tree for reset vs. patch, the exact `pkill` incantation for token-mode connectors, and the dashboard cleanup paths for stray records and rogue entries. |
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Enrol one person into a data portal: mint a passcode, hash it, print the
|
|
3
|
+
// passcode once, and emit the INSERT.
|
|
4
|
+
//
|
|
5
|
+
// This exists because "generate a high-entropy passcode" in a skill's prose is
|
|
6
|
+
// not a constraint — an agent satisfies that sentence with a six-digit number.
|
|
7
|
+
// The strength now lives in code (template/functions/api/_lib/passcode.mjs), and
|
|
8
|
+
// this is the entry point that applies it. Hashing a passcode any other way
|
|
9
|
+
// bypasses the pinned length, so enrolment runs through here.
|
|
10
|
+
//
|
|
11
|
+
// It deliberately stops at emitting SQL. Running `wrangler d1 execute` itself
|
|
12
|
+
// would put the script's only path behind a Cloudflare credential and a network,
|
|
13
|
+
// which makes it untestable; the skill pipes stdout instead.
|
|
14
|
+
//
|
|
15
|
+
// The stream split is load-bearing, not cosmetic. stdout is SQL and NOTHING else,
|
|
16
|
+
// so it pipes straight into wrangler. The passcode goes to stderr: the operator
|
|
17
|
+
// still reads it off their terminal, but no pipe can carry it into wrangler,
|
|
18
|
+
// which would echo it back inside a syntax error — a plaintext passcode in a log
|
|
19
|
+
// is the one thing enrolment must never produce.
|
|
20
|
+
//
|
|
21
|
+
// Usage:
|
|
22
|
+
// node platform/plugins/cloudflare/bin/portal-enrol.mjs --owner <ownerId> --name <name> \
|
|
23
|
+
// | npx wrangler d1 execute <db> --remote --file=-
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
PASSCODE_LENGTH,
|
|
27
|
+
generatePasscode,
|
|
28
|
+
hashPasscode,
|
|
29
|
+
randomSaltHex,
|
|
30
|
+
} from '../skills/data-portal/template/functions/api/_lib/passcode.mjs'
|
|
31
|
+
|
|
32
|
+
/** The isolation rule is that an R2 key starts with `<ownerId>/`, so an ownerId
|
|
33
|
+
* containing a separator would let one person read another's files. The gate
|
|
34
|
+
* enforces this too; enrolment refuses rather than minting a row the gate will
|
|
35
|
+
* later reject. */
|
|
36
|
+
const OWNER_ID = /^[a-z0-9-]+$/
|
|
37
|
+
|
|
38
|
+
/** @param {string} message */
|
|
39
|
+
function die(message) {
|
|
40
|
+
console.error(`portal-enrol: ${message}`)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @param {string} flag @returns {string | undefined} */
|
|
45
|
+
function arg(flag) {
|
|
46
|
+
const i = process.argv.indexOf(flag)
|
|
47
|
+
return i === -1 ? undefined : process.argv[i + 1]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ownerId = arg('--owner')
|
|
51
|
+
const name = arg('--name')
|
|
52
|
+
|
|
53
|
+
if (!ownerId) die('--owner <id> is required')
|
|
54
|
+
if (!name) die('--name <name> is required')
|
|
55
|
+
if (!OWNER_ID.test(/** @type {string} */ (ownerId))) {
|
|
56
|
+
die(`--owner must match ${OWNER_ID.source} — the isolation gate rejects anything else`)
|
|
57
|
+
}
|
|
58
|
+
// A newline in the name would split the one statement stdout promises to be.
|
|
59
|
+
// It is not an injection — SQLite has no escape but '' and a newline sits inside
|
|
60
|
+
// the literal harmlessly — but it breaks the pipeable contract, so refuse it.
|
|
61
|
+
if (/[\r\n]/.test(/** @type {string} */ (name))) die('--name cannot contain a line break')
|
|
62
|
+
|
|
63
|
+
const saltBytes = new Uint8Array(16)
|
|
64
|
+
crypto.getRandomValues(saltBytes)
|
|
65
|
+
const saltHex = randomSaltHex(saltBytes)
|
|
66
|
+
|
|
67
|
+
const passcode = generatePasscode()
|
|
68
|
+
const hash = await hashPasscode(passcode, saltHex)
|
|
69
|
+
|
|
70
|
+
// Doubled single quotes: the only SQL literal here not already constrained to a
|
|
71
|
+
// charset is the name, which comes from an operator.
|
|
72
|
+
const sqlName = /** @type {string} */ (name).replace(/'/g, "''")
|
|
73
|
+
const createdAt = new Date().toISOString()
|
|
74
|
+
|
|
75
|
+
// stdout: the statement, alone, pipeable.
|
|
76
|
+
//
|
|
77
|
+
// An upsert, not a bare INSERT, because `ownerId` is TEXT PRIMARY KEY and
|
|
78
|
+
// re-running this script for an existing person IS the rotation path — the only
|
|
79
|
+
// one there is. A bare INSERT dies on the UNIQUE violation inside wrangler, a
|
|
80
|
+
// separate process, AFTER this script has already printed a new passcode and
|
|
81
|
+
// exited 0: the operator hands out a passcode that never worked, believing the
|
|
82
|
+
// leaked one is revoked, while it and its sessions keep resolving. Rotation is
|
|
83
|
+
// precisely the case where a remedy that silently no-ops is worse than none.
|
|
84
|
+
//
|
|
85
|
+
// `createdAt` is deliberately absent from the DO UPDATE: it records when the
|
|
86
|
+
// person was enrolled, and rotating a passcode is not creating a person.
|
|
87
|
+
console.log(
|
|
88
|
+
`INSERT INTO people (ownerId, name, salt, hash, createdAt) VALUES ('${ownerId}', '${sqlName}', '${saltHex}', '${hash}', '${createdAt}') ON CONFLICT(ownerId) DO UPDATE SET name=excluded.name, salt=excluded.salt, hash=excluded.hash;`,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// stderr: everything for the human. Never stdout — see the note at the top.
|
|
92
|
+
console.error(`passcode: ${passcode}`)
|
|
93
|
+
console.error('')
|
|
94
|
+
console.error(
|
|
95
|
+
`Give the passcode to ${name} out of band, and do not store it anywhere. It is ${PASSCODE_LENGTH} characters and it is not recoverable — re-running this rotates it, which also revokes their live sessions.`,
|
|
96
|
+
)
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
2
2
|
import { processAuth } from '../../skills/data-portal/template/functions/api/auth'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
generatePasscode,
|
|
5
|
+
hashPasscode,
|
|
6
|
+
} from '../../skills/data-portal/template/functions/api/_lib/passcode.mjs'
|
|
4
7
|
import type { PortalEnv } from '../../skills/data-portal/template/functions/api/_lib/types'
|
|
5
8
|
|
|
6
9
|
const SALT = '000102030405060708090a0b0c0d0e0f'
|
|
7
|
-
|
|
10
|
+
// Real passcodes, because hashPasscode now refuses anything below the pinned
|
|
11
|
+
// strength — 'right' and 'wrong' would throw before they ever reached a route.
|
|
12
|
+
const RIGHT = generatePasscode()
|
|
13
|
+
const WRONG = generatePasscode()
|
|
8
14
|
const IP = '203.0.113.7'
|
|
9
15
|
|
|
10
16
|
async function envWith(people: Array<{ ownerId: string; passcode: string }>, attemptCount = 0) {
|
|
@@ -12,8 +18,7 @@ async function envWith(people: Array<{ ownerId: string; passcode: string }>, att
|
|
|
12
18
|
people.map(async (p) => ({
|
|
13
19
|
ownerId: p.ownerId,
|
|
14
20
|
salt: SALT,
|
|
15
|
-
hash: await hashPasscode(p.passcode, SALT
|
|
16
|
-
iterations: ITER,
|
|
21
|
+
hash: await hashPasscode(p.passcode, SALT),
|
|
17
22
|
})),
|
|
18
23
|
)
|
|
19
24
|
const sessions: unknown[][] = []
|
|
@@ -57,10 +62,10 @@ afterEach(() => vi.restoreAllMocks())
|
|
|
57
62
|
|
|
58
63
|
describe('processAuth', () => {
|
|
59
64
|
it('accepts the right passcode and mints a session', async () => {
|
|
60
|
-
const { env, sessions } = await envWith([{ ownerId: 'alice', passcode:
|
|
65
|
+
const { env, sessions } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
61
66
|
const lines: string[] = []
|
|
62
67
|
const res = await processAuth(
|
|
63
|
-
{ ownerId: 'alice', passcode:
|
|
68
|
+
{ ownerId: 'alice', passcode: RIGHT },
|
|
64
69
|
env,
|
|
65
70
|
(l) => lines.push(l),
|
|
66
71
|
() => 'sess-1',
|
|
@@ -74,10 +79,10 @@ describe('processAuth', () => {
|
|
|
74
79
|
})
|
|
75
80
|
|
|
76
81
|
it('denies the wrong passcode and mints no session', async () => {
|
|
77
|
-
const { env, sessions } = await envWith([{ ownerId: 'alice', passcode:
|
|
82
|
+
const { env, sessions } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
78
83
|
const lines: string[] = []
|
|
79
84
|
const res = await processAuth(
|
|
80
|
-
{ ownerId: 'alice', passcode:
|
|
85
|
+
{ ownerId: 'alice', passcode: WRONG },
|
|
81
86
|
env,
|
|
82
87
|
(l) => lines.push(l),
|
|
83
88
|
() => 'sess-1',
|
|
@@ -90,9 +95,9 @@ describe('processAuth', () => {
|
|
|
90
95
|
})
|
|
91
96
|
|
|
92
97
|
it('denies an unknown owner without revealing that it is unknown', async () => {
|
|
93
|
-
const { env } = await envWith([{ ownerId: 'alice', passcode:
|
|
98
|
+
const { env } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
94
99
|
const res = await processAuth(
|
|
95
|
-
{ ownerId: 'nobody', passcode:
|
|
100
|
+
{ ownerId: 'nobody', passcode: RIGHT },
|
|
96
101
|
env,
|
|
97
102
|
() => {},
|
|
98
103
|
() => 'sess-1',
|
|
@@ -106,10 +111,10 @@ describe('processAuth', () => {
|
|
|
106
111
|
})
|
|
107
112
|
|
|
108
113
|
it('rate-limits before comparing the passcode', async () => {
|
|
109
|
-
const { env, sessions } = await envWith([{ ownerId: 'alice', passcode:
|
|
114
|
+
const { env, sessions } = await envWith([{ ownerId: 'alice', passcode: RIGHT }], 5)
|
|
110
115
|
const lines: string[] = []
|
|
111
116
|
const res = await processAuth(
|
|
112
|
-
{ ownerId: 'alice', passcode:
|
|
117
|
+
{ ownerId: 'alice', passcode: RIGHT },
|
|
113
118
|
env,
|
|
114
119
|
(l) => lines.push(l),
|
|
115
120
|
() => 'sess-1',
|
|
@@ -122,63 +127,72 @@ describe('processAuth', () => {
|
|
|
122
127
|
})
|
|
123
128
|
|
|
124
129
|
it('never logs the passcode', async () => {
|
|
125
|
-
const { env } = await envWith([{ ownerId: 'alice', passcode:
|
|
130
|
+
const { env } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
126
131
|
const lines: string[] = []
|
|
127
132
|
await processAuth(
|
|
128
|
-
{ ownerId: 'alice', passcode:
|
|
133
|
+
{ ownerId: 'alice', passcode: RIGHT },
|
|
129
134
|
env,
|
|
130
135
|
(l) => lines.push(l),
|
|
131
136
|
() => 'sess-1',
|
|
132
137
|
1_000_000,
|
|
133
138
|
IP,
|
|
134
139
|
)
|
|
135
|
-
expect(lines.join('\n')).not.toContain(
|
|
140
|
+
expect(lines.join('\n')).not.toContain(RIGHT)
|
|
136
141
|
})
|
|
137
142
|
|
|
138
143
|
it('rejects a malformed body', async () => {
|
|
139
|
-
const { env } = await envWith([{ ownerId: 'alice', passcode:
|
|
144
|
+
const { env } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
140
145
|
const res = await processAuth({} as never, env, () => {}, () => 'sess-1', 1_000_000, IP)
|
|
141
146
|
expect(res.status).toBe(400)
|
|
142
147
|
})
|
|
143
148
|
|
|
144
|
-
it('
|
|
149
|
+
it('hashes for an unknown owner too, closing the timing oracle', async () => {
|
|
145
150
|
// The identical DENIED payload is cosmetic on its own: an unknown owner
|
|
146
|
-
// returning after one D1 read
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
// returning after one D1 read while a known one also hashes enumerates the
|
|
152
|
+
// enrolled people outright. Both paths must pay.
|
|
153
|
+
//
|
|
154
|
+
// 1689 closed a ~5ms-vs-250ms gap here. 1708 replaced PBKDF2 with one
|
|
155
|
+
// SHA-256, shrinking the gap to a measured 43.8us — still inside the band
|
|
156
|
+
// remote timing attacks have been shown to resolve, so the dummy stays. A
|
|
157
|
+
// call count is the right instrument: it is deterministic where a clock is
|
|
158
|
+
// not, and it fails if someone drops the dummy without restoring the cost.
|
|
159
|
+
// Both envs are built BEFORE the spy: envWith hashes its own fixtures, and
|
|
160
|
+
// counting that setup work would let the two totals match without the route
|
|
161
|
+
// hashing at all.
|
|
162
|
+
const known = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
163
|
+
const unknown = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
164
|
+
const spy = vi.spyOn(crypto.subtle, 'digest')
|
|
165
|
+
|
|
166
|
+
await processAuth({ ownerId: 'alice', passcode: WRONG }, known.env, () => {}, () => 's', 1_000_000, IP)
|
|
167
|
+
const hashesForKnown = spy.mock.calls.length
|
|
153
168
|
spy.mockClear()
|
|
154
169
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const derivesForUnknown = spy.mock.calls.length
|
|
170
|
+
await processAuth({ ownerId: 'nobody', passcode: WRONG }, unknown.env, () => {}, () => 's', 1_000_000, IP)
|
|
171
|
+
const hashesForUnknown = spy.mock.calls.length
|
|
158
172
|
|
|
159
|
-
expect(
|
|
160
|
-
expect(
|
|
173
|
+
expect(hashesForKnown).toBeGreaterThan(0)
|
|
174
|
+
expect(hashesForUnknown).toBe(hashesForKnown)
|
|
161
175
|
})
|
|
162
176
|
|
|
163
177
|
it('scopes the rate limiter on owner AND ip, so one caller cannot lock out another', async () => {
|
|
164
178
|
// Keyed on the owner alone the counter is a weapon: six unauthenticated
|
|
165
179
|
// requests naming a person lock that person out of their own portal.
|
|
166
|
-
const { env, scopes } = await envWith([{ ownerId: 'alice', passcode:
|
|
167
|
-
await processAuth({ ownerId: 'alice', passcode:
|
|
180
|
+
const { env, scopes } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
181
|
+
await processAuth({ ownerId: 'alice', passcode: RIGHT }, env, () => {}, () => 's', 1_000_000, IP)
|
|
168
182
|
expect(scopes[0]).toBe(`alice|${IP}`)
|
|
169
183
|
expect(String(scopes[0])).toContain(IP)
|
|
170
184
|
})
|
|
171
185
|
|
|
172
186
|
it('clears the counter on a successful sign-in, so a person cannot self-lock', async () => {
|
|
173
|
-
const { env, cleared } = await envWith([{ ownerId: 'alice', passcode:
|
|
174
|
-
await processAuth({ ownerId: 'alice', passcode:
|
|
187
|
+
const { env, cleared } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
188
|
+
await processAuth({ ownerId: 'alice', passcode: RIGHT }, env, () => {}, () => 's', 1_000_000, IP)
|
|
175
189
|
expect(cleared.length).toBe(1)
|
|
176
190
|
expect(cleared[0][0]).toBe(`alice|${IP}`)
|
|
177
191
|
})
|
|
178
192
|
|
|
179
193
|
it('does not clear the counter on a failed sign-in', async () => {
|
|
180
|
-
const { env, cleared } = await envWith([{ ownerId: 'alice', passcode:
|
|
181
|
-
await processAuth({ ownerId: 'alice', passcode:
|
|
194
|
+
const { env, cleared } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
195
|
+
await processAuth({ ownerId: 'alice', passcode: WRONG }, env, () => {}, () => 's', 1_000_000, IP)
|
|
182
196
|
expect(cleared.length).toBe(0)
|
|
183
197
|
})
|
|
184
198
|
|
|
@@ -186,18 +200,18 @@ describe('processAuth', () => {
|
|
|
186
200
|
// sessions carries pcCheck; resolveSession joins people and requires it to
|
|
187
201
|
// still match. Rotation changes the hash, so old sessions die by
|
|
188
202
|
// construction rather than by an operator remembering a cleanup step.
|
|
189
|
-
const { env, sessions } = await envWith([{ ownerId: 'alice', passcode:
|
|
190
|
-
await processAuth({ ownerId: 'alice', passcode:
|
|
191
|
-
const expected = (await hashPasscode(
|
|
203
|
+
const { env, sessions } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
204
|
+
await processAuth({ ownerId: 'alice', passcode: RIGHT }, env, () => {}, () => 's', 1_000_000, IP)
|
|
205
|
+
const expected = (await hashPasscode(RIGHT, SALT)).slice(0, 16)
|
|
192
206
|
// bind order: sessionId, ownerId, pcCheck, expiresAt, createdAt
|
|
193
207
|
expect(sessions[0][2]).toBe(expected)
|
|
194
208
|
})
|
|
195
209
|
|
|
196
210
|
it('quotes the owner in log lines, so a crafted ownerId cannot forge one', async () => {
|
|
197
|
-
const { env } = await envWith([{ ownerId: 'alice', passcode:
|
|
211
|
+
const { env } = await envWith([{ ownerId: 'alice', passcode: RIGHT }])
|
|
198
212
|
const lines: string[] = []
|
|
199
213
|
const forged = 'x result=ok\n[data-portal] op=auth owner=alice result=ok'
|
|
200
|
-
await processAuth({ ownerId: forged, passcode:
|
|
214
|
+
await processAuth({ ownerId: forged, passcode: WRONG }, env, (l) => lines.push(l), () => 's', 1_000_000, IP)
|
|
201
215
|
// Exactly one line, and the injected newline must not have started another.
|
|
202
216
|
expect(lines.length).toBe(1)
|
|
203
217
|
expect(lines[0].split('\n').length).toBe(1)
|