@powerhousedao/switchboard 6.2.3-dev.23 → 6.2.3-dev.24
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/.tsbuild/test/boot-create-signature-policy.test.d.ts +2 -0
- package/.tsbuild/test/boot-create-signature-policy.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-without-renown.test.d.ts +2 -0
- package/.tsbuild/test/boot-without-renown.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-without-require-signatures.test.d.ts +2 -0
- package/.tsbuild/test/boot-without-require-signatures.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-worker-trust-policy.test.d.ts +2 -0
- package/.tsbuild/test/boot-worker-trust-policy.test.d.ts.map +1 -0
- package/.tsbuild/test/builder-defaults.test.d.ts +2 -0
- package/.tsbuild/test/builder-defaults.test.d.ts.map +1 -0
- package/.tsbuild/test/create-signature-policy.test.d.ts +2 -0
- package/.tsbuild/test/create-signature-policy.test.d.ts.map +1 -0
- package/.tsbuild/test/renown-trust-policy.test.d.ts +2 -0
- package/.tsbuild/test/renown-trust-policy.test.d.ts.map +1 -0
- package/.tsbuild/tsconfig.tsbuildinfo +1 -1
- package/Auth.md +41 -0
- package/CHANGELOG.md +20 -0
- package/README.md +14 -0
- package/dist/index.mjs +2 -2
- package/dist/{server-DVotaGIp.mjs → server-BFbus-at.mjs} +177 -29
- package/dist/server-BFbus-at.mjs.map +1 -0
- package/dist/server.d.mts +11 -5
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +4 -4
- package/dist/{utils-Baw7rThP.mjs → utils-BBHt2kdo.mjs} +15 -9
- package/dist/utils-BBHt2kdo.mjs.map +1 -0
- package/dist/utils.d.mts.map +1 -1
- package/dist/utils.mjs +3 -3
- package/package.json +16 -16
- package/test/attachments/auth.test.ts +85 -0
- package/test/attachments/index.test.ts +34 -3
- package/test/boot-create-signature-policy.test.ts +108 -0
- package/test/boot-without-renown.test.ts +81 -0
- package/test/boot-without-require-signatures.test.ts +74 -0
- package/test/boot-worker-trust-policy.test.ts +90 -0
- package/test/builder-defaults.test.ts +80 -0
- package/test/create-signature-policy.test.ts +69 -0
- package/test/renown-trust-policy.test.ts +315 -0
- package/dist/server-DVotaGIp.mjs.map +0 -1
- package/dist/utils-Baw7rThP.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils-BBHt2kdo.mjs","sources":["../src/utils.mts"],"sourcesContent":["import type { IReactorClient } from \"@powerhousedao/reactor\";\nimport {\n reactorDriveCreateDocument,\n reactorDriveCreateState,\n} from \"@powerhousedao/reactor-drive\";\nimport {\n driveCreateDocument,\n driveCreateState,\n} from \"@powerhousedao/shared/document-drive\";\nimport type { DriveInput } from \"@powerhousedao/shared/document-drive\";\nimport {\n withSignaturePolicy,\n type PHDocument,\n} from \"@powerhousedao/shared/document-model\";\n\n/**\n * A configured id pins the drive, and only a legacy drive can take a chosen\n * id; otherwise the client's creation default applies.\n */\nasync function underCreatePolicy<TDocument extends PHDocument>(\n client: IReactorClient,\n document: TDocument,\n id: string | undefined,\n): Promise<TDocument> {\n return id\n ? withSignaturePolicy(document, \"legacy\", { id })\n : withSignaturePolicy(document, await client.getCreateSignaturePolicy());\n}\n\nexport async function addDefaultDrive(\n client: IReactorClient,\n drive: DriveInput,\n serverPort: number,\n) {\n let driveId = drive.id;\n if (!driveId || driveId.length === 0) {\n driveId = drive.slug;\n }\n\n if (!driveId || driveId.length === 0) {\n throw new Error(\"Invalid Drive Id\");\n }\n\n // check if the drive already exists\n let existingDrive;\n try {\n existingDrive = await client.get(driveId);\n } catch {\n //\n }\n\n // already exists, return the existing drive url\n if (existingDrive) {\n return `http://localhost:${serverPort}/d/${driveId}`;\n }\n\n const { global } = driveCreateState();\n const created = driveCreateDocument({\n global: {\n ...global,\n name: drive.global.name,\n icon: drive.global.icon ?? global.icon,\n },\n local: {\n availableOffline: drive.local?.availableOffline ?? false,\n sharingType: drive.local?.sharingType ?? \"public\",\n listeners: drive.local?.listeners ?? [],\n triggers: drive.local?.triggers ?? [],\n },\n });\n\n const document = await underCreatePolicy(client, created, drive.id);\n if (drive.slug && drive.slug.length > 0) {\n document.header.slug = drive.slug;\n }\n if (drive.global.name) {\n document.header.name = drive.global.name;\n }\n if (drive.preferredEditor) {\n document.header.meta = { preferredEditor: drive.preferredEditor };\n }\n\n try {\n await client.create(document);\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : String(e);\n if (!errorMessage.includes(\"already exists\")) {\n throw e;\n }\n }\n\n return `http://localhost:${serverPort}/d/${driveId}`;\n}\n\nexport async function addDefaultReactorDrive(\n client: IReactorClient,\n drive: DriveInput,\n serverPort: number,\n) {\n let driveId = drive.id;\n if (!driveId || driveId.length === 0) {\n driveId = drive.slug;\n }\n\n if (!driveId || driveId.length === 0) {\n throw new Error(\"Invalid Drive Id\");\n }\n\n let existingDrive;\n try {\n existingDrive = await client.get(driveId);\n } catch {\n //\n }\n\n if (existingDrive) {\n return `http://localhost:${serverPort}/d/${driveId}`;\n }\n\n const { global, local } = reactorDriveCreateState();\n const created = reactorDriveCreateDocument({\n global: {\n ...global,\n name: drive.global.name,\n icon: drive.global.icon ?? global.icon,\n },\n local: {\n ...local,\n availableOffline: drive.local?.availableOffline ?? local.availableOffline,\n sharingType: drive.local?.sharingType ?? local.sharingType,\n },\n });\n\n const document = await underCreatePolicy(client, created, drive.id);\n if (drive.slug && drive.slug.length > 0) {\n document.header.slug = drive.slug;\n }\n if (drive.global.name) {\n document.header.name = drive.global.name;\n }\n if (drive.preferredEditor) {\n document.header.meta = { preferredEditor: drive.preferredEditor };\n }\n\n try {\n await client.create(document);\n } catch (e) {\n const errorMessage = e instanceof Error ? e.message : String(e);\n if (!errorMessage.includes(\"already exists\")) {\n throw e;\n }\n }\n\n return `http://localhost:${serverPort}/d/${driveId}`;\n}\n\nexport function isPostgresUrl(url: string) {\n return url.startsWith(\"postgresql\") || url.startsWith(\"postgres\");\n}\n"],"names":[],"mappings":";;;;;;;;;;AAmBA,eAAe,kBACb,QACA,UACA,IACoB;AACpB,QAAO,KACH,oBAAoB,UAAU,UAAU,EAAE,IAAI,CAAC,GAC/C,oBAAoB,UAAU,MAAM,OAAO,0BAA0B,CAAC;;AAG5E,eAAsB,gBACpB,QACA,OACA,YACA;CACA,IAAI,UAAU,MAAM;AACpB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,WAAU,MAAM;AAGlB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,OAAM,IAAI,MAAM,mBAAmB;CAIrC,IAAI;AACJ,KAAI;AACF,kBAAgB,MAAM,OAAO,IAAI,QAAQ;SACnC;AAKR,KAAI,cACF,QAAO,oBAAoB,WAAW,KAAK;CAG7C,MAAM,EAAE,WAAW,kBAAkB;CAerC,MAAM,WAAW,MAAM,kBAAkB,QAdzB,oBAAoB;EAClC,QAAQ;GACN,GAAG;GACH,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO,QAAQ,OAAO;GACnC;EACD,OAAO;GACL,kBAAkB,MAAM,OAAO,oBAAoB;GACnD,aAAa,MAAM,OAAO,eAAe;GACzC,WAAW,MAAM,OAAO,aAAa,EAAE;GACvC,UAAU,MAAM,OAAO,YAAY,EAAE;GACtC;EACF,CAAC,EAEwD,MAAM,GAAG;AACnE,KAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,EACpC,UAAS,OAAO,OAAO,MAAM;AAE/B,KAAI,MAAM,OAAO,KACf,UAAS,OAAO,OAAO,MAAM,OAAO;AAEtC,KAAI,MAAM,gBACR,UAAS,OAAO,OAAO,EAAE,iBAAiB,MAAM,iBAAiB;AAGnE,KAAI;AACF,QAAM,OAAO,OAAO,SAAS;UACtB,GAAG;AAEV,MAAI,EADiB,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,EAC7C,SAAS,iBAAiB,CAC1C,OAAM;;AAIV,QAAO,oBAAoB,WAAW,KAAK;;AAG7C,eAAsB,uBACpB,QACA,OACA,YACA;CACA,IAAI,UAAU,MAAM;AACpB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,WAAU,MAAM;AAGlB,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,OAAM,IAAI,MAAM,mBAAmB;CAGrC,IAAI;AACJ,KAAI;AACF,kBAAgB,MAAM,OAAO,IAAI,QAAQ;SACnC;AAIR,KAAI,cACF,QAAO,oBAAoB,WAAW,KAAK;CAG7C,MAAM,EAAE,QAAQ,UAAU,yBAAyB;CAcnD,MAAM,WAAW,MAAM,kBAAkB,QAbzB,2BAA2B;EACzC,QAAQ;GACN,GAAG;GACH,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO,QAAQ,OAAO;GACnC;EACD,OAAO;GACL,GAAG;GACH,kBAAkB,MAAM,OAAO,oBAAoB,MAAM;GACzD,aAAa,MAAM,OAAO,eAAe,MAAM;GAChD;EACF,CAAC,EAEwD,MAAM,GAAG;AACnE,KAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,EACpC,UAAS,OAAO,OAAO,MAAM;AAE/B,KAAI,MAAM,OAAO,KACf,UAAS,OAAO,OAAO,MAAM,OAAO;AAEtC,KAAI,MAAM,gBACR,UAAS,OAAO,OAAO,EAAE,iBAAiB,MAAM,iBAAiB;AAGnE,KAAI;AACF,QAAM,OAAO,OAAO,SAAS;UACtB,GAAG;AAEV,MAAI,EADiB,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,EAC7C,SAAS,iBAAiB,CAC1C,OAAM;;AAIV,QAAO,oBAAoB,WAAW,KAAK;;AAG7C,SAAgB,cAAc,KAAa;AACzC,QAAO,IAAI,WAAW,aAAa,IAAI,IAAI,WAAW,WAAW","debug_id":"a69847ec-becf-5c25-988b-cd47759b2c19"}
|
package/dist/utils.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.mts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.mts"],"mappings":";;;;iBA6BsB,eAAA,CACpB,MAAA,EAAQ,cAAA,EACR,KAAA,EAAO,UAAA,EACP,UAAA,WAAkB,OAAA;AAAA,iBA8DE,sBAAA,CACpB,MAAA,EAAQ,cAAA,EACR,KAAA,EAAO,UAAA,EACP,UAAA,WAAkB,OAAA;AAAA,iBA2DJ,aAAA,CAAc,GAAA"}
|
package/dist/utils.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as addDefaultReactorDrive, r as isPostgresUrl, t as addDefaultDrive } from "./utils-
|
|
1
|
+
import { n as addDefaultReactorDrive, r as isPostgresUrl, t as addDefaultDrive } from "./utils-BBHt2kdo.mjs";
|
|
2
2
|
export { addDefaultDrive, addDefaultReactorDrive, isPostgresUrl };
|
|
3
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
4
|
-
//# debugId=
|
|
3
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4dfe0eb9-58d9-5240-969e-d37feaef290c")}catch(e){}}();
|
|
4
|
+
//# debugId=4dfe0eb9-58d9-5240-969e-d37feaef290c
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/switchboard",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "6.2.3-dev.
|
|
4
|
+
"version": "6.2.3-dev.24",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@openfeature/core": "1.9.1",
|
|
42
42
|
"@openfeature/env-var-provider": "0.3.1",
|
|
43
43
|
"@openfeature/server-sdk": "1.19.0",
|
|
44
|
-
"@powerhousedao/config": "6.2.3-dev.
|
|
44
|
+
"@powerhousedao/config": "6.2.3-dev.24",
|
|
45
45
|
"@opentelemetry/api": "^1.9.0",
|
|
46
46
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.218.0",
|
|
47
47
|
"@opentelemetry/exporter-trace-otlp-http": "^0.218.0",
|
|
@@ -55,22 +55,22 @@
|
|
|
55
55
|
"@opentelemetry/sdk-node": "^0.218.0",
|
|
56
56
|
"@opentelemetry/sdk-trace-base": "^2.7.1",
|
|
57
57
|
"@opentelemetry/semantic-conventions": "^1.41.1",
|
|
58
|
-
"@powerhousedao/opentelemetry-instrumentation-reactor": "6.2.3-dev.
|
|
59
|
-
"@powerhousedao/reactor": "6.2.3-dev.
|
|
60
|
-
"@powerhousedao/shared": "6.2.3-dev.
|
|
61
|
-
"@powerhousedao/vetra": "6.2.3-dev.
|
|
62
|
-
"@powerhousedao/reactor-api": "6.2.3-dev.
|
|
63
|
-
"@powerhousedao/reactor-attachments": "6.2.3-dev.
|
|
64
|
-
"@powerhousedao/reactor-drive": "6.2.3-dev.
|
|
65
|
-
"@powerhousedao/reactor-group": "6.2.3-dev.
|
|
66
|
-
"@powerhousedao/reactor-workflow": "6.2.3-dev.
|
|
67
|
-
"@powerhousedao/workflow": "6.2.3-dev.
|
|
68
|
-
"@powerhousedao/pglite-fs": "6.2.3-dev.
|
|
58
|
+
"@powerhousedao/opentelemetry-instrumentation-reactor": "6.2.3-dev.24",
|
|
59
|
+
"@powerhousedao/reactor": "6.2.3-dev.24",
|
|
60
|
+
"@powerhousedao/shared": "6.2.3-dev.24",
|
|
61
|
+
"@powerhousedao/vetra": "6.2.3-dev.24",
|
|
62
|
+
"@powerhousedao/reactor-api": "6.2.3-dev.24",
|
|
63
|
+
"@powerhousedao/reactor-attachments": "6.2.3-dev.24",
|
|
64
|
+
"@powerhousedao/reactor-drive": "6.2.3-dev.24",
|
|
65
|
+
"@powerhousedao/reactor-group": "6.2.3-dev.24",
|
|
66
|
+
"@powerhousedao/reactor-workflow": "6.2.3-dev.24",
|
|
67
|
+
"@powerhousedao/workflow": "6.2.3-dev.24",
|
|
68
|
+
"@powerhousedao/pglite-fs": "6.2.3-dev.24",
|
|
69
69
|
"@pyroscope/nodejs": "^0.4.5",
|
|
70
|
-
"@renown/sdk": "6.2.3-dev.
|
|
70
|
+
"@renown/sdk": "6.2.3-dev.24",
|
|
71
71
|
"@sentry/node": "^10.53.1",
|
|
72
72
|
"@sentry/opentelemetry": "^10.53.1",
|
|
73
|
-
"document-model": "6.2.3-dev.
|
|
73
|
+
"document-model": "6.2.3-dev.24",
|
|
74
74
|
"dotenv": "^16.4.7",
|
|
75
75
|
"express": "^4.21.2",
|
|
76
76
|
"graphql": "^16",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"scripts": {
|
|
97
97
|
"tsc": "tsc",
|
|
98
98
|
"test": "vitest run",
|
|
99
|
-
"lint": "
|
|
99
|
+
"lint": "cd ../.. && oxlint apps/switchboard",
|
|
100
100
|
"build": "tsdown && pnpm run install-packages",
|
|
101
101
|
"start": "node dist/index.mjs",
|
|
102
102
|
"start:profile": "mkdir -p .prof && node --cpu-prof --cpu-prof-dir=.prof dist/index.mjs",
|
|
@@ -342,6 +342,91 @@ describe("requireAuth", () => {
|
|
|
342
342
|
});
|
|
343
343
|
});
|
|
344
344
|
|
|
345
|
+
/**
|
|
346
|
+
* The deployment-wide floor. These routes never pass the GraphQL fetch chain,
|
|
347
|
+
* so `REQUIRE_AUTHENTICATED_CALLER` reaches them through this option instead
|
|
348
|
+
* — including on the routes that opt into `allowAnonymous`, which says the
|
|
349
|
+
* handler can decide without identity, not that the server will serve callers
|
|
350
|
+
* it cannot name.
|
|
351
|
+
*/
|
|
352
|
+
describe("requireAuthenticatedCaller", () => {
|
|
353
|
+
const anonymousUnderOpenPolicy = () =>
|
|
354
|
+
makeAuthService(() =>
|
|
355
|
+
Promise.resolve({ user: undefined, admins: [], auth_enabled: false }),
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
it("refuses a missing bearer even with auth disabled, where today it passes", async () => {
|
|
359
|
+
const { service } = anonymousUnderOpenPolicy();
|
|
360
|
+
const handler = vi.fn<NodeHandler>();
|
|
361
|
+
const wrapped = requireAuth(service, handler, {
|
|
362
|
+
requireAuthenticatedCaller: true,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const res = makeRes();
|
|
366
|
+
await wrapped(makeReq({ headers: {} }), res);
|
|
367
|
+
|
|
368
|
+
expect(res.statusCode).toBe(401);
|
|
369
|
+
expect(JSON.parse(res._body)).toEqual({
|
|
370
|
+
error: "Authentication required",
|
|
371
|
+
});
|
|
372
|
+
expect(handler).not.toHaveBeenCalled();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("overrides allowAnonymous", async () => {
|
|
376
|
+
const { service } = anonymousUnderOpenPolicy();
|
|
377
|
+
const handler = vi.fn<NodeHandler>();
|
|
378
|
+
const wrapped = requireAuth(service, handler, {
|
|
379
|
+
allowAnonymous: true,
|
|
380
|
+
requireAuthenticatedCaller: true,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const res = makeRes();
|
|
384
|
+
await wrapped(makeReq({ headers: {} }), res);
|
|
385
|
+
|
|
386
|
+
expect(res.statusCode).toBe(401);
|
|
387
|
+
expect(handler).not.toHaveBeenCalled();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("still serves a verified caller", async () => {
|
|
391
|
+
const { service } = makeAuthService(() =>
|
|
392
|
+
Promise.resolve({
|
|
393
|
+
user: { address: "0x123", chainId: 1, networkId: "mainnet" },
|
|
394
|
+
admins: [],
|
|
395
|
+
auth_enabled: false,
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
398
|
+
const handler = vi.fn<NodeHandler>();
|
|
399
|
+
const wrapped = requireAuth(service, handler, {
|
|
400
|
+
allowAnonymous: true,
|
|
401
|
+
requireAuthenticatedCaller: true,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const req = makeReq({ headers: { authorization: "Bearer good-token" } });
|
|
405
|
+
const res = makeRes();
|
|
406
|
+
await wrapped(req, res);
|
|
407
|
+
|
|
408
|
+
expect(res.statusCode).toBe(200);
|
|
409
|
+
expect(handler).toHaveBeenCalledWith(req, res, undefined, {
|
|
410
|
+
user: { address: "0x123", chainId: 1, networkId: "mainnet" },
|
|
411
|
+
authEnabled: false,
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("leaves every route as it was when the deployment has not set it", async () => {
|
|
416
|
+
const { service } = anonymousUnderOpenPolicy();
|
|
417
|
+
const handler = vi.fn<NodeHandler>();
|
|
418
|
+
const wrapped = requireAuth(service, handler, {
|
|
419
|
+
requireAuthenticatedCaller: false,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const res = makeRes();
|
|
423
|
+
await wrapped(makeReq({ headers: {} }), res);
|
|
424
|
+
|
|
425
|
+
expect(res.statusCode).toBe(200);
|
|
426
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
345
430
|
it("calls verifyBearer with undefined when no authorization header is present", async () => {
|
|
346
431
|
const { service, spy } = makeAuthService(() =>
|
|
347
432
|
Promise.resolve({
|
|
@@ -13,8 +13,16 @@ type Captured = {
|
|
|
13
13
|
) => void | Promise<void>;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
type FakeApi = Pick<
|
|
17
|
+
API,
|
|
18
|
+
"httpAdapter" | "authService" | "requireAuthenticatedCaller"
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
function makeFakeApi(
|
|
22
|
+
authService: AuthService | undefined,
|
|
23
|
+
requireAuthenticatedCaller = false,
|
|
24
|
+
): {
|
|
25
|
+
api: FakeApi;
|
|
18
26
|
captured: Captured[];
|
|
19
27
|
} {
|
|
20
28
|
const captured: Captured[] = [];
|
|
@@ -29,7 +37,8 @@ function makeFakeApi(authService: AuthService | undefined): {
|
|
|
29
37
|
},
|
|
30
38
|
},
|
|
31
39
|
authService,
|
|
32
|
-
|
|
40
|
+
requireAuthenticatedCaller,
|
|
41
|
+
} as unknown as FakeApi;
|
|
33
42
|
return { api, captured };
|
|
34
43
|
}
|
|
35
44
|
|
|
@@ -138,6 +147,28 @@ describe("mountAuthenticatedNodeRoute", () => {
|
|
|
138
147
|
});
|
|
139
148
|
});
|
|
140
149
|
|
|
150
|
+
it("carries the deployment's authenticated-caller floor onto the route, over allowAnonymous", async () => {
|
|
151
|
+
// auth_enabled false is the shape a deployment running the document
|
|
152
|
+
// policies has: the boundary policy is OPEN, and this floor is what keeps
|
|
153
|
+
// an anonymous caller off the attachment routes.
|
|
154
|
+
const verifyBearer = vi.fn(() =>
|
|
155
|
+
Promise.resolve({ user: undefined, admins: [], auth_enabled: false }),
|
|
156
|
+
);
|
|
157
|
+
const authService = { verifyBearer } as unknown as AuthService;
|
|
158
|
+
const { api, captured } = makeFakeApi(authService, true);
|
|
159
|
+
const inner = vi.fn();
|
|
160
|
+
|
|
161
|
+
mountAuthenticatedNodeRoute(api, "GET", "/x", inner, {
|
|
162
|
+
allowAnonymous: true,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const res = makeRes();
|
|
166
|
+
await captured[0].handler(makeReq(), res);
|
|
167
|
+
|
|
168
|
+
expect(res.statusCode).toBe(401);
|
|
169
|
+
expect(inner).not.toHaveBeenCalled();
|
|
170
|
+
});
|
|
171
|
+
|
|
141
172
|
it("mounts a wrapper that forwards the anonymous actor when authService is undefined", async () => {
|
|
142
173
|
const { api, captured } = makeFakeApi(undefined);
|
|
143
174
|
const inner = vi.fn();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasDerivedDocumentId,
|
|
3
|
+
isDerivedDocumentId,
|
|
4
|
+
signaturePolicyOf,
|
|
5
|
+
} from "@powerhousedao/shared/document-model";
|
|
6
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import type { ILogger } from "document-model";
|
|
10
|
+
import { describe, expect, it, vi } from "vitest";
|
|
11
|
+
import { startSwitchboard } from "../src/server.mjs";
|
|
12
|
+
|
|
13
|
+
function stubLogger(): ILogger {
|
|
14
|
+
const logger = {
|
|
15
|
+
verbose: vi.fn(),
|
|
16
|
+
debug: vi.fn(),
|
|
17
|
+
info: vi.fn(),
|
|
18
|
+
warn: vi.fn(),
|
|
19
|
+
error: vi.fn(),
|
|
20
|
+
child: vi.fn(),
|
|
21
|
+
};
|
|
22
|
+
logger.child.mockReturnValue(logger);
|
|
23
|
+
return logger as unknown as ILogger;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function withSwitchboard(
|
|
27
|
+
env: Record<string, string>,
|
|
28
|
+
run: (
|
|
29
|
+
switchboard: Awaited<ReturnType<typeof startSwitchboard>>,
|
|
30
|
+
) => Promise<void>,
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
const tempRoot = await mkdtemp(join(tmpdir(), "switchboard-create-policy-"));
|
|
33
|
+
const names = ["PH_REACTOR_DATABASE_URL", ...Object.keys(env)];
|
|
34
|
+
const previous = names.map((name) => [name, process.env[name]] as const);
|
|
35
|
+
process.env.PH_REACTOR_DATABASE_URL = join(tempRoot, "reactor-storage");
|
|
36
|
+
Object.assign(process.env, env);
|
|
37
|
+
let switchboard: Awaited<ReturnType<typeof startSwitchboard>> | undefined;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
switchboard = await startSwitchboard({
|
|
41
|
+
workflows: { enabled: false },
|
|
42
|
+
dbPath: join(tempRoot, "read-model"),
|
|
43
|
+
port: 0,
|
|
44
|
+
mcp: false,
|
|
45
|
+
disableLocalPackages: true,
|
|
46
|
+
identity: { keypairPath: join(tempRoot, "identity.json") },
|
|
47
|
+
drive: {
|
|
48
|
+
slug: "default-drive",
|
|
49
|
+
global: { name: "Default Drive" },
|
|
50
|
+
},
|
|
51
|
+
logger: stubLogger(),
|
|
52
|
+
});
|
|
53
|
+
await run(switchboard);
|
|
54
|
+
await switchboard.shutdown();
|
|
55
|
+
switchboard = undefined;
|
|
56
|
+
} finally {
|
|
57
|
+
await switchboard?.shutdown();
|
|
58
|
+
for (const [name, value] of previous) {
|
|
59
|
+
if (value === undefined) {
|
|
60
|
+
delete process.env[name];
|
|
61
|
+
} else {
|
|
62
|
+
process.env[name] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("booting Switchboard", () => {
|
|
70
|
+
it("creates v2-required documents and drives by default", async () => {
|
|
71
|
+
await withSwitchboard({}, async ({ reactor }) => {
|
|
72
|
+
const document = await reactor.createEmpty("powerhouse/document-model");
|
|
73
|
+
expect(signaturePolicyOf(document.header)).toBe("v2-required");
|
|
74
|
+
expect(hasDerivedDocumentId(document.header)).toBe(true);
|
|
75
|
+
|
|
76
|
+
const drive = await reactor.drives.create({ global: { name: "Drive" } });
|
|
77
|
+
expect(signaturePolicyOf(drive.header)).toBe("v2-required");
|
|
78
|
+
|
|
79
|
+
const defaultDrive = await reactor.get("default-drive");
|
|
80
|
+
expect(signaturePolicyOf(defaultDrive.header)).toBe("v2-required");
|
|
81
|
+
});
|
|
82
|
+
}, 60_000);
|
|
83
|
+
|
|
84
|
+
it("creates legacy documents and drives under CREATE_SIGNATURE_POLICY=legacy", async () => {
|
|
85
|
+
await withSwitchboard(
|
|
86
|
+
{ CREATE_SIGNATURE_POLICY: "legacy" },
|
|
87
|
+
async ({ reactor }) => {
|
|
88
|
+
const document = await reactor.createEmpty("powerhouse/document-model");
|
|
89
|
+
expect(signaturePolicyOf(document.header)).toBe("legacy");
|
|
90
|
+
expect(isDerivedDocumentId(document.header.id)).toBe(false);
|
|
91
|
+
|
|
92
|
+
const drive = await reactor.drives.create({
|
|
93
|
+
global: { name: "Drive" },
|
|
94
|
+
});
|
|
95
|
+
expect(signaturePolicyOf(drive.header)).toBe("legacy");
|
|
96
|
+
expect(isDerivedDocumentId(drive.header.id)).toBe(false);
|
|
97
|
+
|
|
98
|
+
const defaultDrive = await reactor.get("default-drive");
|
|
99
|
+
expect(signaturePolicyOf(defaultDrive.header)).toBe("legacy");
|
|
100
|
+
|
|
101
|
+
const asked = await reactor.createEmpty("powerhouse/document-model", {
|
|
102
|
+
signaturePolicy: "v2-required",
|
|
103
|
+
});
|
|
104
|
+
expect(hasDerivedDocumentId(asked.header)).toBe(true);
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
}, 60_000);
|
|
108
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isDerivedDocumentId,
|
|
3
|
+
signaturePolicyOf,
|
|
4
|
+
} from "@powerhousedao/shared/document-model";
|
|
5
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import type { ILogger } from "document-model";
|
|
9
|
+
import { describe, expect, it, vi } from "vitest";
|
|
10
|
+
import type * as RenownModule from "../src/renown.js";
|
|
11
|
+
import { startSwitchboard } from "../src/server.mjs";
|
|
12
|
+
|
|
13
|
+
vi.mock("../src/renown.js", async (importOriginal) => ({
|
|
14
|
+
...(await importOriginal<typeof RenownModule>()),
|
|
15
|
+
initRenown: vi.fn().mockRejectedValue(new Error("Renown is unreachable")),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
function stubLogger(): ILogger & { warn: ReturnType<typeof vi.fn> } {
|
|
19
|
+
const logger = {
|
|
20
|
+
verbose: vi.fn(),
|
|
21
|
+
debug: vi.fn(),
|
|
22
|
+
info: vi.fn(),
|
|
23
|
+
warn: vi.fn(),
|
|
24
|
+
error: vi.fn(),
|
|
25
|
+
child: vi.fn(),
|
|
26
|
+
};
|
|
27
|
+
logger.child.mockReturnValue(logger);
|
|
28
|
+
return logger as unknown as ILogger & { warn: ReturnType<typeof vi.fn> };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("booting Switchboard without Renown", () => {
|
|
32
|
+
it("warns and creates legacy documents, which it can write unsigned", async () => {
|
|
33
|
+
const tempRoot = await mkdtemp(join(tmpdir(), "switchboard-no-renown-"));
|
|
34
|
+
const previous = process.env.PH_REACTOR_DATABASE_URL;
|
|
35
|
+
process.env.PH_REACTOR_DATABASE_URL = join(tempRoot, "reactor-storage");
|
|
36
|
+
const logger = stubLogger();
|
|
37
|
+
let switchboard: Awaited<ReturnType<typeof startSwitchboard>> | undefined;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
switchboard = await startSwitchboard({
|
|
41
|
+
workflows: { enabled: false },
|
|
42
|
+
dbPath: join(tempRoot, "read-model"),
|
|
43
|
+
port: 0,
|
|
44
|
+
mcp: false,
|
|
45
|
+
disableLocalPackages: true,
|
|
46
|
+
logger,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(
|
|
50
|
+
logger.warn.mock.calls.some(
|
|
51
|
+
([message]) =>
|
|
52
|
+
typeof message === "string" &&
|
|
53
|
+
message.includes("new documents are created legacy"),
|
|
54
|
+
),
|
|
55
|
+
).toBe(true);
|
|
56
|
+
|
|
57
|
+
const document = await switchboard.reactor.createEmpty(
|
|
58
|
+
"powerhouse/document-model",
|
|
59
|
+
);
|
|
60
|
+
expect(signaturePolicyOf(document.header)).toBe("legacy");
|
|
61
|
+
expect(isDerivedDocumentId(document.header.id)).toBe(false);
|
|
62
|
+
|
|
63
|
+
const renamed = await switchboard.reactor.rename(
|
|
64
|
+
document.header.id,
|
|
65
|
+
"Renamed",
|
|
66
|
+
);
|
|
67
|
+
expect(renamed.header.name).toBe("Renamed");
|
|
68
|
+
|
|
69
|
+
await switchboard.shutdown();
|
|
70
|
+
switchboard = undefined;
|
|
71
|
+
} finally {
|
|
72
|
+
await switchboard?.shutdown();
|
|
73
|
+
if (previous === undefined) {
|
|
74
|
+
delete process.env.PH_REACTOR_DATABASE_URL;
|
|
75
|
+
} else {
|
|
76
|
+
process.env.PH_REACTOR_DATABASE_URL = previous;
|
|
77
|
+
}
|
|
78
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
79
|
+
}
|
|
80
|
+
}, 60_000);
|
|
81
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { ILogger } from "document-model";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { startSwitchboard } from "../src/server.mjs";
|
|
7
|
+
|
|
8
|
+
function stubLogger(): ILogger & { info: ReturnType<typeof vi.fn> } {
|
|
9
|
+
const logger = {
|
|
10
|
+
verbose: vi.fn(),
|
|
11
|
+
debug: vi.fn(),
|
|
12
|
+
info: vi.fn(),
|
|
13
|
+
warn: vi.fn(),
|
|
14
|
+
error: vi.fn(),
|
|
15
|
+
child: vi.fn(),
|
|
16
|
+
};
|
|
17
|
+
logger.child.mockReturnValue(logger);
|
|
18
|
+
return logger as unknown as ILogger & { info: ReturnType<typeof vi.fn> };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("booting Switchboard", () => {
|
|
22
|
+
it("has no REQUIRE_SIGNATURES flag, and ignores the variable", async () => {
|
|
23
|
+
const tempRoot = await mkdtemp(join(tmpdir(), "switchboard-signatures-"));
|
|
24
|
+
const previous = {
|
|
25
|
+
reactorDb: process.env.PH_REACTOR_DATABASE_URL,
|
|
26
|
+
requireSignatures: process.env.REQUIRE_SIGNATURES,
|
|
27
|
+
};
|
|
28
|
+
process.env.PH_REACTOR_DATABASE_URL = join(tempRoot, "reactor-storage");
|
|
29
|
+
process.env.REQUIRE_SIGNATURES = "true";
|
|
30
|
+
const logger = stubLogger();
|
|
31
|
+
let switchboard: Awaited<ReturnType<typeof startSwitchboard>> | undefined;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
switchboard = await startSwitchboard({
|
|
35
|
+
workflows: { enabled: false },
|
|
36
|
+
dbPath: join(tempRoot, "read-model"),
|
|
37
|
+
port: 0,
|
|
38
|
+
mcp: false,
|
|
39
|
+
disableLocalPackages: true,
|
|
40
|
+
identity: { keypairPath: join(tempRoot, "identity.json") },
|
|
41
|
+
logger,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const flagLog = logger.info.mock.calls.find(
|
|
45
|
+
([message]) => message === "Feature flags: @flags",
|
|
46
|
+
);
|
|
47
|
+
expect(flagLog).toBeDefined();
|
|
48
|
+
expect(JSON.parse(flagLog![1] as string)).not.toHaveProperty(
|
|
49
|
+
"REQUIRE_SIGNATURES",
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const created = await switchboard.reactor.createEmpty(
|
|
53
|
+
"powerhouse/document-model",
|
|
54
|
+
);
|
|
55
|
+
expect(created.header.id).toBeTruthy();
|
|
56
|
+
|
|
57
|
+
await switchboard.shutdown();
|
|
58
|
+
switchboard = undefined;
|
|
59
|
+
} finally {
|
|
60
|
+
await switchboard?.shutdown();
|
|
61
|
+
for (const [name, value] of [
|
|
62
|
+
["PH_REACTOR_DATABASE_URL", previous.reactorDb],
|
|
63
|
+
["REQUIRE_SIGNATURES", previous.requireSignatures],
|
|
64
|
+
] as const) {
|
|
65
|
+
if (value === undefined) {
|
|
66
|
+
delete process.env[name];
|
|
67
|
+
} else {
|
|
68
|
+
process.env[name] = value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
}, 60_000);
|
|
74
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ILogger } from "document-model";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type * as RenownModule from "../src/renown.js";
|
|
7
|
+
import { startSwitchboard } from "../src/server.mjs";
|
|
8
|
+
|
|
9
|
+
vi.mock("../src/renown.js", async (importOriginal) => ({
|
|
10
|
+
...(await importOriginal<typeof RenownModule>()),
|
|
11
|
+
initRenown: vi.fn().mockRejectedValue(new Error("Renown is unreachable")),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const TRUST_FAILURE =
|
|
15
|
+
/REACTOR_WORKERS[\s\S]*REACTOR_AUTH_ENFORCEMENT[\s\S]*RENOWN_SOURCE/;
|
|
16
|
+
const ENV_KEYS = [
|
|
17
|
+
"PH_REACTOR_DATABASE_URL",
|
|
18
|
+
"RENOWN_SOURCE",
|
|
19
|
+
"REACTOR_DOCUMENT_DECISIONS",
|
|
20
|
+
"REACTOR_AUTH_ENFORCEMENT",
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
function stubLogger(): ILogger {
|
|
24
|
+
const logger = {
|
|
25
|
+
verbose: vi.fn(),
|
|
26
|
+
debug: vi.fn(),
|
|
27
|
+
info: vi.fn(),
|
|
28
|
+
warn: vi.fn(),
|
|
29
|
+
error: vi.fn(),
|
|
30
|
+
child: vi.fn(),
|
|
31
|
+
};
|
|
32
|
+
logger.child.mockReturnValue(logger);
|
|
33
|
+
return logger as unknown as ILogger;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("booting with pooled workers under authEnforcement", () => {
|
|
37
|
+
let tempRoot: string;
|
|
38
|
+
let previous: Partial<Record<(typeof ENV_KEYS)[number], string>>;
|
|
39
|
+
|
|
40
|
+
beforeEach(async () => {
|
|
41
|
+
tempRoot = await mkdtemp(join(tmpdir(), "switchboard-worker-trust-"));
|
|
42
|
+
previous = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
|
|
43
|
+
process.env.PH_REACTOR_DATABASE_URL = join(tempRoot, "reactor-storage");
|
|
44
|
+
process.env.REACTOR_DOCUMENT_DECISIONS = "true";
|
|
45
|
+
process.env.REACTOR_AUTH_ENFORCEMENT = "true";
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(async () => {
|
|
49
|
+
for (const key of ENV_KEYS) {
|
|
50
|
+
if (previous[key] === undefined) {
|
|
51
|
+
delete process.env[key];
|
|
52
|
+
} else {
|
|
53
|
+
process.env[key] = previous[key];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function boot(numWorkers: number) {
|
|
60
|
+
return startSwitchboard({
|
|
61
|
+
workflows: { enabled: false },
|
|
62
|
+
dbPath: join(tempRoot, "read-model"),
|
|
63
|
+
port: 0,
|
|
64
|
+
mcp: false,
|
|
65
|
+
disableLocalPackages: true,
|
|
66
|
+
logger: stubLogger(),
|
|
67
|
+
workerPool: { numWorkers },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
it("fails with a self Renown source", async () => {
|
|
72
|
+
process.env.RENOWN_SOURCE = "self";
|
|
73
|
+
|
|
74
|
+
await expect(boot(2)).rejects.toThrow(TRUST_FAILURE);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Postgres is the next requirement a pooled boot has; reaching it passes the check.
|
|
78
|
+
it("passes the check with a remote Renown source", async () => {
|
|
79
|
+
process.env.RENOWN_SOURCE = "remote";
|
|
80
|
+
|
|
81
|
+
await expect(boot(2)).rejects.toThrow(/requires a Postgres/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// No loaded package serves the renown read model here; reaching that check passes this one.
|
|
85
|
+
it("passes the check without workers", async () => {
|
|
86
|
+
process.env.RENOWN_SOURCE = "self";
|
|
87
|
+
|
|
88
|
+
await expect(boot(0)).rejects.toThrow(/no loaded package serves/);
|
|
89
|
+
}, 60_000);
|
|
90
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ISigner } from "@powerhousedao/shared/document-model";
|
|
2
|
+
import {
|
|
3
|
+
ReactorBuilder,
|
|
4
|
+
ReactorClientBuilder,
|
|
5
|
+
type FactorySpec,
|
|
6
|
+
} from "@powerhousedao/reactor";
|
|
7
|
+
import type { IRenown } from "@renown/sdk/node";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
import { describe, expect, it, vi } from "vitest";
|
|
11
|
+
import { applySwitchboardReactorDefaults } from "../src/builder-defaults.mjs";
|
|
12
|
+
import {
|
|
13
|
+
getRenownSignerConfig,
|
|
14
|
+
getRenownWorkerSignerSpec,
|
|
15
|
+
} from "../src/renown.js";
|
|
16
|
+
|
|
17
|
+
const USER = { address: "0xswitchboard", networkId: "eip155", chainId: 1 };
|
|
18
|
+
|
|
19
|
+
function stubRenown(user = USER): IRenown {
|
|
20
|
+
const signer = {
|
|
21
|
+
user,
|
|
22
|
+
app: { name: "switchboard", key: "did:key:zSwitchboard" },
|
|
23
|
+
} as unknown as ISigner;
|
|
24
|
+
return { signer } as unknown as IRenown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("applySwitchboardReactorDefaults", () => {
|
|
28
|
+
it("gives the reactor the signer and its worker spec", () => {
|
|
29
|
+
const reactorBuilder = new ReactorBuilder();
|
|
30
|
+
const clientBuilder = new ReactorClientBuilder();
|
|
31
|
+
const reactorWithSigner = vi.spyOn(reactorBuilder, "withSigner");
|
|
32
|
+
const clientWithSigner = vi.spyOn(clientBuilder, "withSigner");
|
|
33
|
+
const signer = getRenownSignerConfig(stubRenown(), ".ph/key.json");
|
|
34
|
+
|
|
35
|
+
applySwitchboardReactorDefaults(reactorBuilder, clientBuilder, {
|
|
36
|
+
includeBaseModels: false,
|
|
37
|
+
signalHandlers: false,
|
|
38
|
+
signer,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(clientWithSigner).toHaveBeenCalledWith(signer);
|
|
42
|
+
expect(reactorWithSigner).toHaveBeenCalledWith(
|
|
43
|
+
signer.signer,
|
|
44
|
+
signer.workerSigner,
|
|
45
|
+
);
|
|
46
|
+
expect(reactorBuilder.hasSigner()).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("getRenownWorkerSignerSpec", () => {
|
|
51
|
+
it("points workers at createNodeRenownSigner over the stored key", () => {
|
|
52
|
+
const spec: FactorySpec = getRenownWorkerSignerSpec(
|
|
53
|
+
stubRenown(),
|
|
54
|
+
".ph/key.json",
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(spec.module.exportName).toBe("createNodeRenownSigner");
|
|
58
|
+
const filePath = "filePath" in spec.module ? spec.module.filePath : "";
|
|
59
|
+
expect(existsSync(filePath)).toBe(true);
|
|
60
|
+
expect(spec.initArgs).toEqual({
|
|
61
|
+
appName: "switchboard",
|
|
62
|
+
keypairPath: resolve(".ph/key.json"),
|
|
63
|
+
did: "did:key:zSwitchboard",
|
|
64
|
+
user: USER,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("leaves the user out when none is logged in", () => {
|
|
69
|
+
const renown = stubRenown();
|
|
70
|
+
(renown.signer as { user?: unknown }).user = undefined;
|
|
71
|
+
|
|
72
|
+
const spec = getRenownWorkerSignerSpec(renown);
|
|
73
|
+
|
|
74
|
+
expect(spec.initArgs).toEqual({
|
|
75
|
+
appName: "switchboard",
|
|
76
|
+
keypairPath: resolve(".ph/.keypair.json"),
|
|
77
|
+
did: "did:key:zSwitchboard",
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|