@rubytech/create-maxy-code 0.1.459 → 0.1.460
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/lib/storage-broker/dist/__tests__/cf-exec.test.js +116 -6
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js +20 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.js +41 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +1 -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 +116 -48
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.d.ts +1 -0
- package/payload/platform/lib/storage-broker/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.js +1 -0
- package/payload/platform/lib/storage-broker/dist/index.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts +27 -0
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/object-limits.js +27 -7
- package/payload/platform/lib/storage-broker/dist/object-limits.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/resource-limits.d.ts +102 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.js +105 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.js.map +1 -0
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +130 -7
- package/payload/platform/lib/storage-broker/src/__tests__/object-limits.test.ts +30 -0
- package/payload/platform/lib/storage-broker/src/__tests__/resource-limits.test.ts +49 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +129 -56
- package/payload/platform/lib/storage-broker/src/index.ts +1 -0
- package/payload/platform/lib/storage-broker/src/object-limits.ts +26 -6
- package/payload/platform/lib/storage-broker/src/resource-limits.ts +105 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +5 -4
- package/payload/platform/plugins/cloudflare/PLUGIN.md +3 -1
- package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-route.test.ts +206 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +201 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/file-routes.test.ts +178 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/harness.test.ts +32 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/passcode.test.ts +74 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/ratelimit.test.ts +77 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/skill-contract.test.ts +46 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +55 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +255 -0
- package/payload/platform/plugins/cloudflare/mcp/package.json +5 -2
- package/payload/platform/plugins/cloudflare/references/r2-object-storage.md +46 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +123 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/authorize.ts +32 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/log.ts +12 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/passcode.ts +73 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/ratelimit.ts +55 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +85 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +44 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/auth.ts +113 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +89 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +70 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +41 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +151 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/index.html +42 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/portal.css +113 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/portal.js +155 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/schema.sql +64 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +21 -0
- package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +8 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +3 -3
- package/payload/platform/plugins/docs/references/voice-mirror-guide.md +1 -0
- package/payload/platform/plugins/filesystem/PLUGIN.md +0 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts +14 -10
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js +14 -10
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js.map +1 -1
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +3 -5
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.js +75 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.js +187 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js +3 -2
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.d.ts +43 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.js +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.d.ts +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.js +99 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts +6 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts +42 -22
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js +59 -23
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.d.ts +15 -16
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js +8 -41
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js.map +1 -1
- package/payload/platform/plugins/outlook/references/auth.md +1 -1
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +7 -3
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +1 -1
- package/payload/platform/plugins/storage-broker/PLUGIN.md +1 -1
- package/payload/platform/plugins/voice-mirror/PLUGIN.md +19 -7
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js +35 -16
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.d.ts +31 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.d.ts.map +1 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.js +87 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.js.map +1 -0
- 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 +13 -3
- 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-ingest-session-text.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js +2 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js +2 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts +2 -2
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js +12 -3
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +334 -0
- package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +13 -0
- package/payload/platform/scripts/voice-author-key-audit.sh +155 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.d.ts +17 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.js +88 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +0 -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 +34 -23
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/server/{chunk-Q6W4U6HL.js → chunk-JXWFVE5X.js} +83 -39
- package/payload/server/server.js +213 -38
- package/payload/server/{src-3I2RYZFB.js → src-4F37OHLK.js} +11 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts +0 -2
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +0 -215
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +0 -1
package/payload/server/server.js
CHANGED
|
@@ -113,15 +113,19 @@ import {
|
|
|
113
113
|
writeAdminUserAndPerson
|
|
114
114
|
} from "./chunk-POBXIHOF.js";
|
|
115
115
|
import {
|
|
116
|
+
D1_MAX_SQL_STATEMENT_BYTES,
|
|
117
|
+
D1_QUERY_MAX_BODY_BYTES,
|
|
118
|
+
R2_OBJECT_ENVELOPE_MAX_BODY_BYTES,
|
|
116
119
|
R2_OBJECT_MAX_BYTES,
|
|
117
120
|
R2_OBJECT_PUT_MAX_BODY_BYTES,
|
|
121
|
+
STORAGE_RESOURCE_NAME_MAX_BODY_BYTES,
|
|
118
122
|
authorizeAccess,
|
|
119
123
|
listResources,
|
|
120
124
|
makeHouseCfExec,
|
|
121
125
|
registerResource,
|
|
122
126
|
resolveOwner,
|
|
123
127
|
tooLargeMessage
|
|
124
|
-
} from "./chunk-
|
|
128
|
+
} from "./chunk-JXWFVE5X.js";
|
|
125
129
|
import {
|
|
126
130
|
__commonJS,
|
|
127
131
|
__toESM
|
|
@@ -8745,6 +8749,49 @@ function log(action, kind, account, target, owner, result, reason) {
|
|
|
8745
8749
|
function cfExec() {
|
|
8746
8750
|
return makeHouseCfExec(platformRoot());
|
|
8747
8751
|
}
|
|
8752
|
+
async function readJsonBody(c) {
|
|
8753
|
+
let parsed;
|
|
8754
|
+
try {
|
|
8755
|
+
parsed = await c.req.json();
|
|
8756
|
+
} catch (err) {
|
|
8757
|
+
if (err instanceof Error && err.name === "BodyLimitError") throw err;
|
|
8758
|
+
return { ok: false, res: c.json({ error: "invalid-json" }, 400) };
|
|
8759
|
+
}
|
|
8760
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8761
|
+
return { ok: false, res: c.json({ error: "invalid-json" }, 400) };
|
|
8762
|
+
}
|
|
8763
|
+
return { ok: true, body: parsed };
|
|
8764
|
+
}
|
|
8765
|
+
function nameBodyLimit(kind) {
|
|
8766
|
+
return bodyLimit({
|
|
8767
|
+
maxSize: STORAGE_RESOURCE_NAME_MAX_BODY_BYTES,
|
|
8768
|
+
onError: (c) => {
|
|
8769
|
+
log("write", kind, caller(c) ?? "none", "*", null, "deny", "name-body-too-large");
|
|
8770
|
+
return c.json(
|
|
8771
|
+
{
|
|
8772
|
+
error: `request body exceeds the ${(STORAGE_RESOURCE_NAME_MAX_BODY_BYTES / 1024).toFixed(
|
|
8773
|
+
1
|
|
8774
|
+
)} KiB (${STORAGE_RESOURCE_NAME_MAX_BODY_BYTES} bytes) limit for a storage resource create, which carries only a name.`
|
|
8775
|
+
},
|
|
8776
|
+
413
|
|
8777
|
+
);
|
|
8778
|
+
}
|
|
8779
|
+
});
|
|
8780
|
+
}
|
|
8781
|
+
var d1QueryBodyLimit = bodyLimit({
|
|
8782
|
+
maxSize: D1_QUERY_MAX_BODY_BYTES,
|
|
8783
|
+
onError: (c) => {
|
|
8784
|
+
log("read", "d1", caller(c) ?? "none", "*", null, "deny", "sql-body-too-large");
|
|
8785
|
+
return c.json(
|
|
8786
|
+
{
|
|
8787
|
+
error: `request body exceeds the ${(D1_QUERY_MAX_BODY_BYTES / 1024 / 1024).toFixed(
|
|
8788
|
+
1
|
|
8789
|
+
)} MiB (${D1_QUERY_MAX_BODY_BYTES} bytes) limit for a D1 query, which carries a database name and a SQL statement of at most ${D1_MAX_SQL_STATEMENT_BYTES} bytes.`
|
|
8790
|
+
},
|
|
8791
|
+
413
|
|
8792
|
+
);
|
|
8793
|
+
}
|
|
8794
|
+
});
|
|
8748
8795
|
app3.get("/d1/list", async (c) => {
|
|
8749
8796
|
const account = caller(c);
|
|
8750
8797
|
if (!account) return c.json({ error: "no-caller" }, 403);
|
|
@@ -8759,10 +8806,12 @@ app3.get("/d1/list", async (c) => {
|
|
|
8759
8806
|
await session.close();
|
|
8760
8807
|
}
|
|
8761
8808
|
});
|
|
8762
|
-
app3.post("/d1/create", async (c) => {
|
|
8809
|
+
app3.post("/d1/create", nameBodyLimit("d1"), async (c) => {
|
|
8763
8810
|
const account = caller(c);
|
|
8764
8811
|
if (!account) return c.json({ error: "no-caller" }, 403);
|
|
8765
|
-
const
|
|
8812
|
+
const parsed = await readJsonBody(c);
|
|
8813
|
+
if (!parsed.ok) return parsed.res;
|
|
8814
|
+
const { name } = parsed.body;
|
|
8766
8815
|
if (!name || typeof name !== "string") return c.json({ error: "name-required" }, 400);
|
|
8767
8816
|
const session = getSession();
|
|
8768
8817
|
try {
|
|
@@ -8781,10 +8830,12 @@ app3.post("/d1/create", async (c) => {
|
|
|
8781
8830
|
await session.close();
|
|
8782
8831
|
}
|
|
8783
8832
|
});
|
|
8784
|
-
app3.post("/d1/query", async (c) => {
|
|
8833
|
+
app3.post("/d1/query", d1QueryBodyLimit, async (c) => {
|
|
8785
8834
|
const account = caller(c);
|
|
8786
8835
|
if (!account) return c.json({ error: "no-caller" }, 403);
|
|
8787
|
-
const
|
|
8836
|
+
const parsed = await readJsonBody(c);
|
|
8837
|
+
if (!parsed.ok) return parsed.res;
|
|
8838
|
+
const { name, sql } = parsed.body;
|
|
8788
8839
|
if (!name || typeof name !== "string") return c.json({ error: "name-required" }, 400);
|
|
8789
8840
|
if (!sql || typeof sql !== "string") return c.json({ error: "sql-required" }, 400);
|
|
8790
8841
|
const session = getSession();
|
|
@@ -8815,10 +8866,12 @@ app3.get("/r2/list", async (c) => {
|
|
|
8815
8866
|
await session.close();
|
|
8816
8867
|
}
|
|
8817
8868
|
});
|
|
8818
|
-
app3.post("/r2/create", async (c) => {
|
|
8869
|
+
app3.post("/r2/create", nameBodyLimit("r2"), async (c) => {
|
|
8819
8870
|
const account = caller(c);
|
|
8820
8871
|
if (!account) return c.json({ error: "no-caller" }, 403);
|
|
8821
|
-
const
|
|
8872
|
+
const parsed = await readJsonBody(c);
|
|
8873
|
+
if (!parsed.ok) return parsed.res;
|
|
8874
|
+
const { name } = parsed.body;
|
|
8822
8875
|
if (!name || typeof name !== "string") return c.json({ error: "name-required" }, 400);
|
|
8823
8876
|
const session = getSession();
|
|
8824
8877
|
try {
|
|
@@ -8869,7 +8922,39 @@ function readKey(body) {
|
|
|
8869
8922
|
const key = body.key;
|
|
8870
8923
|
return typeof key === "string" && key.length > 0 ? key : null;
|
|
8871
8924
|
}
|
|
8872
|
-
|
|
8925
|
+
function envelopeBodyLimit(action) {
|
|
8926
|
+
return bodyLimit({
|
|
8927
|
+
maxSize: R2_OBJECT_ENVELOPE_MAX_BODY_BYTES,
|
|
8928
|
+
// Names the limit rather than the object: the body is deliberately unparsed
|
|
8929
|
+
// here, which is the whole point of the middleware, so the key is genuinely
|
|
8930
|
+
// unknown — as recorded for put in 1695. Stated in KiB with exact bytes rather
|
|
8931
|
+
// than the MiB the object messages use: 64 KiB renders as "0.1 MiB", which is
|
|
8932
|
+
// less legible, not more. The exact bytes are there for the same reason
|
|
8933
|
+
// tooLargeMessage carries them.
|
|
8934
|
+
//
|
|
8935
|
+
// The {error} body is the routes' convention, which the default's bare text
|
|
8936
|
+
// is not. On the content-length branch no body is read and no handler runs, so
|
|
8937
|
+
// this log line is the only trace the request ever existed.
|
|
8938
|
+
//
|
|
8939
|
+
// reason distinguishes this from put's body-too-large rather than reusing it.
|
|
8940
|
+
// They are different events refusing bodies ~2,100x apart, and put and delete
|
|
8941
|
+
// both log action=write, so a shared slug would make a 140 MB put denial and a
|
|
8942
|
+
// 65 KiB delete denial byte-identical — the same conflation the reason field
|
|
8943
|
+
// exists to prevent for ownership-vs-size.
|
|
8944
|
+
onError: (c) => {
|
|
8945
|
+
log(action, "r2", caller(c) ?? "none", "*", null, "deny", "envelope-too-large");
|
|
8946
|
+
return c.json(
|
|
8947
|
+
{
|
|
8948
|
+
error: `request body exceeds the ${(R2_OBJECT_ENVELOPE_MAX_BODY_BYTES / 1024).toFixed(
|
|
8949
|
+
1
|
|
8950
|
+
)} KiB (${R2_OBJECT_ENVELOPE_MAX_BODY_BYTES} bytes) limit for an R2 object metadata envelope, which carries only a bucket and a key or prefix.`
|
|
8951
|
+
},
|
|
8952
|
+
413
|
|
8953
|
+
);
|
|
8954
|
+
}
|
|
8955
|
+
});
|
|
8956
|
+
}
|
|
8957
|
+
app3.post("/r2/object/list", envelopeBodyLimit("list"), async (c) => {
|
|
8873
8958
|
const gate = await objectGate(c, "list");
|
|
8874
8959
|
if (!gate.ok) return gate.res;
|
|
8875
8960
|
const prefix = gate.body.prefix;
|
|
@@ -8883,15 +8968,15 @@ app3.post("/r2/object/list", async (c) => {
|
|
|
8883
8968
|
return c.json({ error: String(err) }, 500);
|
|
8884
8969
|
}
|
|
8885
8970
|
});
|
|
8886
|
-
app3.post("/r2/object/get", async (c) => {
|
|
8971
|
+
app3.post("/r2/object/get", envelopeBodyLimit("read"), async (c) => {
|
|
8887
8972
|
const gate = await objectGate(c, "read");
|
|
8888
8973
|
if (!gate.ok) return gate.res;
|
|
8889
8974
|
const key = readKey(gate.body);
|
|
8890
8975
|
if (!key) return c.json({ error: "key-required" }, 400);
|
|
8891
8976
|
try {
|
|
8892
8977
|
const cf = await cfExec();
|
|
8893
|
-
const
|
|
8894
|
-
const size =
|
|
8978
|
+
const found = await cf.r2ObjectFind(gate.bucket, key);
|
|
8979
|
+
const size = found?.size;
|
|
8895
8980
|
if (typeof size !== "number" || !Number.isFinite(size) || size < 0) {
|
|
8896
8981
|
log("read", "r2", caller(c) ?? "none", key, caller(c), "deny", "size-unknown");
|
|
8897
8982
|
return c.json(
|
|
@@ -8962,7 +9047,7 @@ app3.post(
|
|
|
8962
9047
|
}
|
|
8963
9048
|
}
|
|
8964
9049
|
);
|
|
8965
|
-
app3.post("/r2/object/delete", async (c) => {
|
|
9050
|
+
app3.post("/r2/object/delete", envelopeBodyLimit("write"), async (c) => {
|
|
8966
9051
|
const gate = await objectGate(c, "write");
|
|
8967
9052
|
if (!gate.ok) return gate.res;
|
|
8968
9053
|
const key = readKey(gate.body);
|
|
@@ -8976,7 +9061,7 @@ app3.post("/r2/object/delete", async (c) => {
|
|
|
8976
9061
|
});
|
|
8977
9062
|
var storage_broker_default = app3;
|
|
8978
9063
|
async function runStorageAudit(root) {
|
|
8979
|
-
const { reconcileStorage } = await import("./src-
|
|
9064
|
+
const { reconcileStorage } = await import("./src-4F37OHLK.js");
|
|
8980
9065
|
const session = getSession();
|
|
8981
9066
|
try {
|
|
8982
9067
|
const cf = await makeHouseCfExec(root);
|
|
@@ -9901,6 +9986,78 @@ function pickActivity(entries, grown, nowMs, concurrencyWindowMs) {
|
|
|
9901
9986
|
return { hex: newest.hex, lastEmitAt: newest.mtimeMs, count };
|
|
9902
9987
|
}
|
|
9903
9988
|
|
|
9989
|
+
// server/routes/stream-lifecycle.ts
|
|
9990
|
+
var GAUGE_INTERVAL_MS = 6e4;
|
|
9991
|
+
function isArmed(timer2) {
|
|
9992
|
+
return timer2._destroyed !== true;
|
|
9993
|
+
}
|
|
9994
|
+
var Registration = class {
|
|
9995
|
+
constructor(connId, channel, startedAt) {
|
|
9996
|
+
this.connId = connId;
|
|
9997
|
+
this.channel = channel;
|
|
9998
|
+
this.startedAt = startedAt;
|
|
9999
|
+
}
|
|
10000
|
+
timers = [];
|
|
10001
|
+
closed = false;
|
|
10002
|
+
arm(timer2) {
|
|
10003
|
+
this.timers.push(timer2);
|
|
10004
|
+
return timer2;
|
|
10005
|
+
}
|
|
10006
|
+
teardown(reason) {
|
|
10007
|
+
if (this.closed) return false;
|
|
10008
|
+
this.closed = true;
|
|
10009
|
+
for (const t of this.timers) clearInterval(t);
|
|
10010
|
+
const stillArmed = this.timers.filter(isArmed).length;
|
|
10011
|
+
if (stillArmed > 0) {
|
|
10012
|
+
console.error(
|
|
10013
|
+
`[wa-stream] op=teardown-defect conn=${this.connId} channel=${this.channel} stillArmed=${stillArmed} reason=${reason}`
|
|
10014
|
+
);
|
|
10015
|
+
return true;
|
|
10016
|
+
}
|
|
10017
|
+
registry.delete(this);
|
|
10018
|
+
if (registry.size === 0) emitGauge();
|
|
10019
|
+
return true;
|
|
10020
|
+
}
|
|
10021
|
+
};
|
|
10022
|
+
var registry = /* @__PURE__ */ new Set();
|
|
10023
|
+
var gaugeTimer = null;
|
|
10024
|
+
function emitGauge() {
|
|
10025
|
+
const now = Date.now();
|
|
10026
|
+
const counts = { reader: 0, store: 0 };
|
|
10027
|
+
let oldestMs = 0;
|
|
10028
|
+
let stale = 0;
|
|
10029
|
+
for (const reg of [...registry]) {
|
|
10030
|
+
if (!reg.timers.some(isArmed)) {
|
|
10031
|
+
stale++;
|
|
10032
|
+
registry.delete(reg);
|
|
10033
|
+
continue;
|
|
10034
|
+
}
|
|
10035
|
+
counts[reg.channel]++;
|
|
10036
|
+
oldestMs = Math.max(oldestMs, now - reg.startedAt);
|
|
10037
|
+
}
|
|
10038
|
+
const armed = counts.reader + counts.store;
|
|
10039
|
+
console.log(
|
|
10040
|
+
`[wa-stream] op=gauge armed=${armed} reader=${counts.reader} store=${counts.store} oldestSec=${Math.floor(oldestMs / 1e3)} stale=${stale}`
|
|
10041
|
+
);
|
|
10042
|
+
if (armed === 0) stopGauge();
|
|
10043
|
+
}
|
|
10044
|
+
function startGauge() {
|
|
10045
|
+
if (gaugeTimer) return;
|
|
10046
|
+
gaugeTimer = setInterval(emitGauge, GAUGE_INTERVAL_MS);
|
|
10047
|
+
gaugeTimer.unref();
|
|
10048
|
+
}
|
|
10049
|
+
function stopGauge() {
|
|
10050
|
+
if (!gaugeTimer) return;
|
|
10051
|
+
clearInterval(gaugeTimer);
|
|
10052
|
+
gaugeTimer = null;
|
|
10053
|
+
}
|
|
10054
|
+
function openStream(connId, channel, startedAt) {
|
|
10055
|
+
const reg = new Registration(connId, channel, startedAt);
|
|
10056
|
+
registry.add(reg);
|
|
10057
|
+
startGauge();
|
|
10058
|
+
return reg;
|
|
10059
|
+
}
|
|
10060
|
+
|
|
9904
10061
|
// app/admin-types.ts
|
|
9905
10062
|
var ACCEPT_MIME = "image/jpeg,image/png,image/gif,image/webp,application/pdf,text/plain,text/markdown,text/csv,text/html,text/calendar,application/zip,application/x-zip-compressed,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.oasis.opendocument.text,text/rtf,application/rtf,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/vnd.oasis.opendocument.spreadsheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.ms-powerpoint,application/vnd.oasis.opendocument.presentation,audio/ogg,audio/opus,audio/mp4,audio/x-m4a,audio/mpeg,audio/webm,audio/wav,video/mp4,.docx,.odt,.rtf,.xlsx,.xls,.ods,.pptx,.ppt,.odp,.opus,.ogg,.m4a,.mp4,.mp3,.wav,.webm";
|
|
9906
10063
|
var SUPPORTED_MIME_SET = new Set(ACCEPT_MIME.split(",").filter((t) => !t.startsWith(".")));
|
|
@@ -10209,6 +10366,8 @@ data: ${JSON.stringify(turn)}
|
|
|
10209
10366
|
const connId = `${sessionId.slice(0, 8)}-${process.hrtime.bigint().toString(36).slice(-6)}`;
|
|
10210
10367
|
const startedAt = Date.now();
|
|
10211
10368
|
const lastEventId = c.req.header("Last-Event-ID");
|
|
10369
|
+
let teardown = () => {
|
|
10370
|
+
};
|
|
10212
10371
|
const readable = new ReadableStream({
|
|
10213
10372
|
start(controller) {
|
|
10214
10373
|
const fileEnd0 = (() => {
|
|
@@ -10293,27 +10452,34 @@ data: ${JSON.stringify(obj)}
|
|
|
10293
10452
|
actx.now = Date.now();
|
|
10294
10453
|
runActivityTick(actx);
|
|
10295
10454
|
};
|
|
10296
|
-
const
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10455
|
+
const lifecycle = openStream(connId, "reader", startedAt);
|
|
10456
|
+
lifecycle.arm(setInterval(tick, 1e3));
|
|
10457
|
+
lifecycle.arm(
|
|
10458
|
+
setInterval(() => {
|
|
10459
|
+
try {
|
|
10460
|
+
controller.enqueue(encoder.encode(": ping\n\n"));
|
|
10461
|
+
console.log(`[wa-stream] op=heartbeat conn=${connId}`);
|
|
10462
|
+
} catch {
|
|
10463
|
+
teardown("heartbeat-dead-controller");
|
|
10464
|
+
}
|
|
10465
|
+
}, 2e4)
|
|
10466
|
+
);
|
|
10467
|
+
teardown = (reason) => {
|
|
10468
|
+
if (!lifecycle.teardown(reason)) return;
|
|
10305
10469
|
watcher?.close();
|
|
10306
|
-
clearInterval(poll);
|
|
10307
|
-
clearInterval(heartbeat);
|
|
10308
10470
|
if (actx.emittedHex !== null) {
|
|
10309
10471
|
console.log(`[webchat-activity] op=clear key=${sessionKey} reason=stream-close`);
|
|
10310
10472
|
}
|
|
10311
|
-
console.log(`[wa-stream] op=close conn=${connId} ranMs=${Date.now() - startedAt} reason
|
|
10473
|
+
console.log(`[wa-stream] op=close conn=${connId} ranMs=${Date.now() - startedAt} reason=${reason}`);
|
|
10312
10474
|
try {
|
|
10313
10475
|
controller.close();
|
|
10314
10476
|
} catch {
|
|
10315
10477
|
}
|
|
10316
|
-
}
|
|
10478
|
+
};
|
|
10479
|
+
c.req.raw.signal.addEventListener("abort", () => teardown("client-abort"));
|
|
10480
|
+
},
|
|
10481
|
+
cancel() {
|
|
10482
|
+
teardown("client-cancel");
|
|
10317
10483
|
}
|
|
10318
10484
|
});
|
|
10319
10485
|
return new Response(readable, {
|
|
@@ -10421,6 +10587,8 @@ app5.get("/store-stream", requireAdminSession, (c) => {
|
|
|
10421
10587
|
}
|
|
10422
10588
|
return turn;
|
|
10423
10589
|
};
|
|
10590
|
+
let teardown = () => {
|
|
10591
|
+
};
|
|
10424
10592
|
const readable = new ReadableStream({
|
|
10425
10593
|
start(controller) {
|
|
10426
10594
|
const send = (turn, id) => {
|
|
@@ -10518,23 +10686,30 @@ data: ${JSON.stringify(payload)}
|
|
|
10518
10686
|
watcher = watch(file, drain);
|
|
10519
10687
|
} catch {
|
|
10520
10688
|
}
|
|
10521
|
-
const
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10689
|
+
const lifecycle = openStream(connId, "store", startedAt);
|
|
10690
|
+
lifecycle.arm(setInterval(drain, 1e3));
|
|
10691
|
+
lifecycle.arm(
|
|
10692
|
+
setInterval(() => {
|
|
10693
|
+
try {
|
|
10694
|
+
controller.enqueue(encoder.encode(": ping\n\n"));
|
|
10695
|
+
} catch {
|
|
10696
|
+
teardown("heartbeat-dead-controller");
|
|
10697
|
+
}
|
|
10698
|
+
}, 2e4)
|
|
10699
|
+
);
|
|
10700
|
+
teardown = (reason) => {
|
|
10701
|
+
if (!lifecycle.teardown(reason)) return;
|
|
10529
10702
|
watcher?.close();
|
|
10530
|
-
|
|
10531
|
-
clearInterval(heartbeat);
|
|
10532
|
-
console.log(`[wa-reader] op=store-close conn=${connId} ranMs=${Date.now() - startedAt}`);
|
|
10703
|
+
console.log(`[wa-reader] op=store-close conn=${connId} ranMs=${Date.now() - startedAt} reason=${reason}`);
|
|
10533
10704
|
try {
|
|
10534
10705
|
controller.close();
|
|
10535
10706
|
} catch {
|
|
10536
10707
|
}
|
|
10537
|
-
}
|
|
10708
|
+
};
|
|
10709
|
+
c.req.raw.signal.addEventListener("abort", () => teardown("client-abort"));
|
|
10710
|
+
},
|
|
10711
|
+
cancel() {
|
|
10712
|
+
teardown("client-cancel");
|
|
10538
10713
|
}
|
|
10539
10714
|
});
|
|
10540
10715
|
return new Response(readable, {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
+
D1_MAX_SQL_STATEMENT_BYTES,
|
|
4
|
+
D1_QUERY_MAX_BODY_BYTES,
|
|
5
|
+
JSON_STRING_WORST_CASE_EXPANSION,
|
|
6
|
+
R2_OBJECT_ENVELOPE_MAX_BODY_BYTES,
|
|
3
7
|
R2_OBJECT_MAX_BYTES,
|
|
4
8
|
R2_OBJECT_PUT_MAX_BODY_BYTES,
|
|
9
|
+
STORAGE_RESOURCE_NAME_MAX_BODY_BYTES,
|
|
5
10
|
authorizeAccess,
|
|
6
11
|
base64Ceiling,
|
|
7
12
|
listResources,
|
|
@@ -15,11 +20,16 @@ import {
|
|
|
15
20
|
stripAccountWideTokens,
|
|
16
21
|
tooLargeMessage,
|
|
17
22
|
validateMapping
|
|
18
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-JXWFVE5X.js";
|
|
19
24
|
import "./chunk-PFF6I7KP.js";
|
|
20
25
|
export {
|
|
26
|
+
D1_MAX_SQL_STATEMENT_BYTES,
|
|
27
|
+
D1_QUERY_MAX_BODY_BYTES,
|
|
28
|
+
JSON_STRING_WORST_CASE_EXPANSION,
|
|
29
|
+
R2_OBJECT_ENVELOPE_MAX_BODY_BYTES,
|
|
21
30
|
R2_OBJECT_MAX_BYTES,
|
|
22
31
|
R2_OBJECT_PUT_MAX_BODY_BYTES,
|
|
32
|
+
STORAGE_RESOURCE_NAME_MAX_BODY_BYTES,
|
|
23
33
|
authorizeAccess,
|
|
24
34
|
base64Ceiling,
|
|
25
35
|
listResources,
|
|
@@ -1,215 +0,0 @@
|
|
|
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.ReadWrite Mail.Send Calendars.ReadWrite 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.ReadWrite",
|
|
75
|
-
"Contacts.Read",
|
|
76
|
-
"Mail.ReadWrite",
|
|
77
|
-
"Mail.Send",
|
|
78
|
-
"User.Read",
|
|
79
|
-
"offline_access",
|
|
80
|
-
].sort());
|
|
81
|
-
// Token endpoint was called with code + verifier + redirect_uri.
|
|
82
|
-
assert.equal(fetchMock.calls.length, 1);
|
|
83
|
-
const body = String(fetchMock.calls[0].init?.body ?? "");
|
|
84
|
-
assert.match(body, /code=fake-code/);
|
|
85
|
-
assert.match(body, /code_verifier=/);
|
|
86
|
-
assert.match(body, /grant_type=authorization_code/);
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
fetchMock.restore();
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
test("PKCE state mismatch → flow rejects, token endpoint NOT called", async () => {
|
|
93
|
-
const fetchMock = mockFetch(() => {
|
|
94
|
-
throw new Error("token endpoint should not be called on state mismatch");
|
|
95
|
-
});
|
|
96
|
-
try {
|
|
97
|
-
const flow = await startPkceFlow({
|
|
98
|
-
clientId: "test-client",
|
|
99
|
-
tenantId: "common",
|
|
100
|
-
accountId: "acct-pkce-2",
|
|
101
|
-
});
|
|
102
|
-
// Subscribe BEFORE driving the callback — otherwise the rejection lands
|
|
103
|
-
// before any handler is attached and Node flags it as unhandled.
|
|
104
|
-
const rejection = assert.rejects(flow.result, /State mismatch/i);
|
|
105
|
-
const port = extractRedirectUriPort(flow.redirectUri);
|
|
106
|
-
const cbResp = await fetch(`http://127.0.0.1:${port}/callback?code=fake-code&state=wrong-state-value`);
|
|
107
|
-
assert.equal(cbResp.status, 400);
|
|
108
|
-
await rejection;
|
|
109
|
-
assert.equal(fetchMock.calls.length, 0, "token endpoint must not be called");
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
fetchMock.restore();
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
test("PKCE OAuth error in callback → flow rejects, no token call", async () => {
|
|
116
|
-
const fetchMock = mockFetch(() => {
|
|
117
|
-
throw new Error("token endpoint should not be called on OAuth error");
|
|
118
|
-
});
|
|
119
|
-
try {
|
|
120
|
-
const flow = await startPkceFlow({
|
|
121
|
-
clientId: "test-client",
|
|
122
|
-
tenantId: "common",
|
|
123
|
-
accountId: "acct-pkce-3",
|
|
124
|
-
});
|
|
125
|
-
const rejection = assert.rejects(flow.result, /OAuth error: access_denied/);
|
|
126
|
-
const state = extractStateFromAuthUrl(flow.authUrl);
|
|
127
|
-
const port = extractRedirectUriPort(flow.redirectUri);
|
|
128
|
-
const cbResp = await fetch(`http://127.0.0.1:${port}/callback?error=access_denied&error_description=user%20cancelled&state=${state}`);
|
|
129
|
-
assert.equal(cbResp.status, 400);
|
|
130
|
-
await rejection;
|
|
131
|
-
assert.equal(fetchMock.calls.length, 0);
|
|
132
|
-
}
|
|
133
|
-
finally {
|
|
134
|
-
fetchMock.restore();
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
test("PKCE callback missing code → flow rejects", async () => {
|
|
138
|
-
const fetchMock = mockFetch(() => {
|
|
139
|
-
throw new Error("token endpoint should not be called");
|
|
140
|
-
});
|
|
141
|
-
try {
|
|
142
|
-
const flow = await startPkceFlow({
|
|
143
|
-
clientId: "test-client",
|
|
144
|
-
tenantId: "common",
|
|
145
|
-
accountId: "acct-pkce-4",
|
|
146
|
-
});
|
|
147
|
-
const rejection = assert.rejects(flow.result, /No authorization code received/);
|
|
148
|
-
const state = extractStateFromAuthUrl(flow.authUrl);
|
|
149
|
-
const port = extractRedirectUriPort(flow.redirectUri);
|
|
150
|
-
const cbResp = await fetch(`http://127.0.0.1:${port}/callback?state=${state}`);
|
|
151
|
-
assert.equal(cbResp.status, 400);
|
|
152
|
-
await rejection;
|
|
153
|
-
assert.equal(fetchMock.calls.length, 0);
|
|
154
|
-
}
|
|
155
|
-
finally {
|
|
156
|
-
fetchMock.restore();
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
test("PKCE auth URL has S256 challenge + correct scopes + prompt=select_account", async () => {
|
|
160
|
-
// No mock needed — we assert URL structure and abort the flow without
|
|
161
|
-
// exchanging tokens. Sending an OAuth error in the callback closes the
|
|
162
|
-
// server and rejects flow.result loudly; we catch it explicitly.
|
|
163
|
-
const flow = await startPkceFlow({
|
|
164
|
-
clientId: "client-X",
|
|
165
|
-
tenantId: "common",
|
|
166
|
-
accountId: "acct-pkce-5",
|
|
167
|
-
});
|
|
168
|
-
const expectedRejection = assert.rejects(flow.result, /OAuth error/);
|
|
169
|
-
try {
|
|
170
|
-
const url = new URL(flow.authUrl);
|
|
171
|
-
assert.equal(url.searchParams.get("client_id"), "client-X");
|
|
172
|
-
assert.equal(url.searchParams.get("response_type"), "code");
|
|
173
|
-
assert.equal(url.searchParams.get("code_challenge_method"), "S256");
|
|
174
|
-
assert.equal(url.searchParams.get("prompt"), "select_account");
|
|
175
|
-
const challenge = url.searchParams.get("code_challenge");
|
|
176
|
-
assert.ok(challenge && challenge.length > 30, "challenge should be base64url-encoded SHA-256");
|
|
177
|
-
const scope = url.searchParams.get("scope") ?? "";
|
|
178
|
-
assert.match(scope, /offline_access/);
|
|
179
|
-
assert.match(scope, /User\.Read/);
|
|
180
|
-
assert.match(scope, /Mail\.ReadWrite/);
|
|
181
|
-
assert.match(scope, /Mail\.Send/);
|
|
182
|
-
assert.match(scope, /Calendars\.ReadWrite/);
|
|
183
|
-
assert.match(scope, /Contacts\.Read/);
|
|
184
|
-
// Abort the flow by sending an OAuth error. Server closes; result rejects.
|
|
185
|
-
const port = extractRedirectUriPort(flow.redirectUri);
|
|
186
|
-
const state = url.searchParams.get("state");
|
|
187
|
-
await fetch(`http://127.0.0.1:${port}/callback?error=access_denied&state=${state}`);
|
|
188
|
-
}
|
|
189
|
-
finally {
|
|
190
|
-
await expectedRejection;
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
test("PKCE redirect URI uses ephemeral loopback port (not fixed 49152)", async () => {
|
|
194
|
-
// No token-exchange mock — abort each flow with an OAuth error so the
|
|
195
|
-
// server closes without invoking exchangeCode.
|
|
196
|
-
const flow1 = await startPkceFlow({ clientId: "c", tenantId: "common", accountId: "a1" });
|
|
197
|
-
const flow2 = await startPkceFlow({ clientId: "c", tenantId: "common", accountId: "a2" });
|
|
198
|
-
const r1 = assert.rejects(flow1.result, /OAuth error/);
|
|
199
|
-
const r2 = assert.rejects(flow2.result, /OAuth error/);
|
|
200
|
-
try {
|
|
201
|
-
const port1 = extractRedirectUriPort(flow1.redirectUri);
|
|
202
|
-
const port2 = extractRedirectUriPort(flow2.redirectUri);
|
|
203
|
-
assert.ok(port1 > 0 && port1 < 65536);
|
|
204
|
-
assert.ok(port2 > 0 && port2 < 65536);
|
|
205
|
-
assert.notEqual(port1, port2, "two parallel flows must allocate different ephemeral ports");
|
|
206
|
-
const s1 = extractStateFromAuthUrl(flow1.authUrl);
|
|
207
|
-
const s2 = extractStateFromAuthUrl(flow2.authUrl);
|
|
208
|
-
await fetch(`http://127.0.0.1:${port1}/callback?error=access_denied&state=${s1}`);
|
|
209
|
-
await fetch(`http://127.0.0.1:${port2}/callback?error=access_denied&state=${s2}`);
|
|
210
|
-
}
|
|
211
|
-
finally {
|
|
212
|
-
await Promise.all([r1, r2]);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
//# sourceMappingURL=pkce-flow.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pkce-flow.test.js","sourceRoot":"","sources":["../../src/__tests__/pkce-flow.test.ts"],"names":[],"mappings":"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"}
|