diff --git a/benches/suites/keys/key_generation.ts b/benches/suites/keys/key_generation.ts index f6463b936..382902b5f 100644 --- a/benches/suites/keys/key_generation.ts +++ b/benches/suites/keys/key_generation.ts @@ -10,8 +10,8 @@ async function main() { b.add('generate root asymmetric keypair', () => { generate.generateKeyPair(); }), - b.add('generate deterministic root keypair', () => { - generate.generateDeterministicKeyPair(code); + b.add('generate deterministic root keypair', async () => { + await generate.generateDeterministicKeyPair(code); }), b.add('generate 256 bit symmetric key', () => { generate.generateKey(); diff --git a/benches/suites/keys/keyring_lifecycle.ts b/benches/suites/keys/keyring_lifecycle.ts index 7fd30fa54..9185e5c63 100644 --- a/benches/suites/keys/keyring_lifecycle.ts +++ b/benches/suites/keys/keyring_lifecycle.ts @@ -21,7 +21,7 @@ async function main() { keysPath: `${dataDir}/keys`, password: 'password', logger, - fresh: true + fresh: true, }); await keyRing.stop(); }; @@ -36,13 +36,13 @@ async function main() { const keyRing = await KeyRing.createKeyRing({ keysPath: `${dataDir}/keys`, password: 'password', - logger + logger, }); await keyRing.stop(); return async () => { // Due to password hashing this is intended to be slow await keyRing.start({ - password: 'password' + password: 'password', }); await keyRing.stop(); }; diff --git a/benches/suites/keys/password_hashing.ts b/benches/suites/keys/password_hashing.ts index 063cfc030..3f6fa6c9c 100644 --- a/benches/suites/keys/password_hashing.ts +++ b/benches/suites/keys/password_hashing.ts @@ -10,7 +10,7 @@ async function main() { 'password', undefined, password.passwordOpsLimits.min, - password.passwordMemLimits.min + password.passwordMemLimits.min, ); }), b.add('password hashing - interactive', () => { @@ -18,7 +18,7 @@ async function main() { 'password', undefined, password.passwordOpsLimits.interactive, - password.passwordMemLimits.interactive + password.passwordMemLimits.interactive, ); }), b.add('password hashing - moderate', () => { @@ -26,7 +26,7 @@ async function main() { 'password', undefined, password.passwordOpsLimits.moderate, - password.passwordMemLimits.moderate + password.passwordMemLimits.moderate, ); }), b.add('password hashing - sensitive', () => { @@ -34,7 +34,7 @@ async function main() { 'password', undefined, password.passwordOpsLimits.sensitive, - password.passwordMemLimits.sensitive + password.passwordMemLimits.sensitive, ); }), ...suiteCommon, diff --git a/benches/suites/keys/x509.ts b/benches/suites/keys/x509.ts index 0ecfada3b..9589edb33 100644 --- a/benches/suites/keys/x509.ts +++ b/benches/suites/keys/x509.ts @@ -41,18 +41,21 @@ async function main() { x509.certFromASN1(certASN1); }; }), - b.add('certificate serialization & deserialization to ASN1 buffer', async () => { - const cert = await x509.generateCertificate({ - certId: certIdGenerator(), - subjectKeyPair, - issuerPrivateKey: issuerKeyPair.privateKey, - duration: 1000, - }); - return () => { - const certASN1 = x509.certToASN1(cert); - x509.certFromASN1(certASN1); - }; - }), + b.add( + 'certificate serialization & deserialization to ASN1 buffer', + async () => { + const cert = await x509.generateCertificate({ + certId: certIdGenerator(), + subjectKeyPair, + issuerPrivateKey: issuerKeyPair.privateKey, + duration: 1000, + }); + return () => { + const certASN1 = x509.certToASN1(cert); + x509.certFromASN1(certASN1); + }; + }, + ), ...suiteCommon, ); return summary; diff --git a/benches/suites/workers/worker_keys.ts b/benches/suites/workers/worker_keys.ts index 65e90d7ec..72711d730 100644 --- a/benches/suites/workers/worker_keys.ts +++ b/benches/suites/workers/worker_keys.ts @@ -11,7 +11,10 @@ async function main() { const logger = new Logger(`worker_overhead bench`, LogLevel.WARN, [ new StreamHandler(), ]); - const workerManager = await workersUtils.createWorkerManager({ cores, logger }); + const workerManager = await workersUtils.createWorkerManager({ + cores, + logger, + }); let summary: Summary; try { summary = await b.suite( @@ -56,7 +59,9 @@ async function main() { issuerPrivateKey: subjectKeyPair.privateKey.buffer, duration: 1000, }); - return keysUtils.certFromASN1(Buffer.from(result) as CertificateASN1)!; + return keysUtils.certFromASN1( + Buffer.from(result) as CertificateASN1, + )!; }); }; }), diff --git a/benches/suites/workers/worker_overhead.ts b/benches/suites/workers/worker_overhead.ts index 8b2229fd6..a55722eb8 100644 --- a/benches/suites/workers/worker_overhead.ts +++ b/benches/suites/workers/worker_overhead.ts @@ -1,5 +1,4 @@ import b from 'benny'; -import crypto from 'crypto'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import * as workersUtils from '@/workers/utils'; import { summaryName, suiteCommon } from '../../utils'; @@ -9,10 +8,12 @@ async function main() { const logger = new Logger(`worker_overhead bench`, LogLevel.WARN, [ new StreamHandler(), ]); - const workerManager = await workersUtils.createWorkerManager({ cores, logger }); + const workerManager = await workersUtils.createWorkerManager({ + cores, + logger, + }); // 1 MiB worth of data is the ballpark range of data to be worth parallelising // 1 KiB of data is still too small - const bytes = crypto.randomBytes(1024 * 1024); const summary = await b.suite( summaryName(__filename), b.add('call overhead', async () => { diff --git a/package-lock.json b/package-lock.json index 47d5cefe3..9ef9ca0cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,10 +13,10 @@ "@matrixai/async-cancellable": "^1.0.2", "@matrixai/async-init": "^1.8.2", "@matrixai/async-locks": "^3.2.0", - "@matrixai/db": "^5.0.3", - "@matrixai/errors": "^1.1.5", + "@matrixai/db": "^5.1.0", + "@matrixai/errors": "^1.1.6", "@matrixai/id": "^3.3.3", - "@matrixai/logger": "^3.0.0", + "@matrixai/logger": "^3.1.0", "@matrixai/resources": "^1.1.4", "@matrixai/timer": "^1.0.0", "@matrixai/workers": "^1.3.6", @@ -2671,9 +2671,9 @@ } }, "node_modules/@matrixai/db": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.0.3.tgz", - "integrity": "sha512-/BNbg+vzFw8fv5e7KXZTXb5CvZvFUjwH5cI4l7kZ/kUHTWKgVSvdxz77h7njYDuhHStY6sSHnVAlWrgczFbQ8w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.1.0.tgz", + "integrity": "sha512-sdJOUNjXV7sdztFr3Ut99yzOHa4TYmfRCaq/mrc/MPgkQ8UgdQEx1XDstdXmGn6K9kKN+YhLl47yb8uOgYDRvA==", "hasInstallScript": true, "dependencies": { "@matrixai/async-init": "^1.8.1", @@ -2691,11 +2691,11 @@ } }, "node_modules/@matrixai/errors": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@matrixai/errors/-/errors-1.1.5.tgz", - "integrity": "sha512-75ERxIvp+WyjBaZTrdb492MnC/K8vZeBUD9+eYEzSB5uPZ9mIl60A8AXqKS8W+xFL2VsDiHb2BYSZiVGZcNAUw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@matrixai/errors/-/errors-1.1.6.tgz", + "integrity": "sha512-Wn8ppT8NUOf5WUaQ2hKO/XzodyvK3EF8o7ULLedGq2wdKy4aK0WxDtRMwDmgwUeCcKLKglT1foPHJ3vMf9Y+Zw==", "dependencies": { - "ts-custom-error": "^3.2.2" + "ts-custom-error": "3.2.2" } }, "node_modules/@matrixai/id": { @@ -2708,9 +2708,9 @@ } }, "node_modules/@matrixai/logger": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-3.0.0.tgz", - "integrity": "sha512-J2KMMw4FCHHmIacRfbU3mBPMvGxxwRc4Y8eFEtzkOcL8WhqBfWKiZ96xNduJGxUo+nfTlj+Q2Ep9RwRw3FCxMw==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-3.1.0.tgz", + "integrity": "sha512-C4JWpgbNik3V99bfGfDell5cH3JULD67eEq9CeXl4rYgsvanF8hhuY84ZYvndPhimt9qjA9/Z8uExKGoiv1zVw==" }, "node_modules/@matrixai/resources": { "version": "1.1.4", @@ -14007,9 +14007,9 @@ } }, "@matrixai/db": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.0.3.tgz", - "integrity": "sha512-/BNbg+vzFw8fv5e7KXZTXb5CvZvFUjwH5cI4l7kZ/kUHTWKgVSvdxz77h7njYDuhHStY6sSHnVAlWrgczFbQ8w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@matrixai/db/-/db-5.1.0.tgz", + "integrity": "sha512-sdJOUNjXV7sdztFr3Ut99yzOHa4TYmfRCaq/mrc/MPgkQ8UgdQEx1XDstdXmGn6K9kKN+YhLl47yb8uOgYDRvA==", "requires": { "@matrixai/async-init": "^1.8.1", "@matrixai/async-locks": "^3.1.1", @@ -14022,11 +14022,11 @@ } }, "@matrixai/errors": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@matrixai/errors/-/errors-1.1.5.tgz", - "integrity": "sha512-75ERxIvp+WyjBaZTrdb492MnC/K8vZeBUD9+eYEzSB5uPZ9mIl60A8AXqKS8W+xFL2VsDiHb2BYSZiVGZcNAUw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@matrixai/errors/-/errors-1.1.6.tgz", + "integrity": "sha512-Wn8ppT8NUOf5WUaQ2hKO/XzodyvK3EF8o7ULLedGq2wdKy4aK0WxDtRMwDmgwUeCcKLKglT1foPHJ3vMf9Y+Zw==", "requires": { - "ts-custom-error": "^3.2.2" + "ts-custom-error": "3.2.2" } }, "@matrixai/id": { @@ -14039,9 +14039,9 @@ } }, "@matrixai/logger": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-3.0.0.tgz", - "integrity": "sha512-J2KMMw4FCHHmIacRfbU3mBPMvGxxwRc4Y8eFEtzkOcL8WhqBfWKiZ96xNduJGxUo+nfTlj+Q2Ep9RwRw3FCxMw==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@matrixai/logger/-/logger-3.1.0.tgz", + "integrity": "sha512-C4JWpgbNik3V99bfGfDell5cH3JULD67eEq9CeXl4rYgsvanF8hhuY84ZYvndPhimt9qjA9/Z8uExKGoiv1zVw==" }, "@matrixai/resources": { "version": "1.1.4", diff --git a/package.json b/package.json index 5b65c0d83..a57843b8c 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "ts-node": "ts-node", "ts-node-inspect": "node --require ts-node/register --inspect", "test": "jest", - "lint": "eslint '{src,tests,scripts,benches}/**/*.{js,ts,json}'", - "lintfix": "eslint '{src,tests,scripts,benches}/**/*.{js,ts,json}' --fix", + "lint": "eslint '{src,tests,scripts}/**/*.{js,ts,json}' 'benches/**/*.ts'", + "lintfix": "eslint '{src,tests,scripts}/**/*.{js,ts,json}' 'benches/**/*.ts' --fix", "lint-shell": "find ./src ./tests ./scripts -type f -regextype posix-extended -regex '.*\\.(sh)' -exec shellcheck {} +", "docs": "shx rm -rf ./docs && typedoc --gitRevision master --tsconfig ./tsconfig.build.json --out ./docs src", "bench": "shx rm -rf ./benches/results && ts-node ./benches", @@ -82,10 +82,10 @@ "@matrixai/async-cancellable": "^1.0.2", "@matrixai/async-init": "^1.8.2", "@matrixai/async-locks": "^3.2.0", - "@matrixai/db": "^5.0.3", - "@matrixai/errors": "^1.1.5", + "@matrixai/db": "^5.1.0", + "@matrixai/errors": "^1.1.6", "@matrixai/id": "^3.3.3", - "@matrixai/logger": "^3.0.0", + "@matrixai/logger": "^3.1.0", "@matrixai/resources": "^1.1.4", "@matrixai/timer": "^1.0.0", "@matrixai/workers": "^1.3.6", diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index 3528fd26e..a5ef855fa 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -2,8 +2,9 @@ import type { FileSystem } from './types'; import type { PolykeyWorkerManagerInterface } from './workers/types'; import type { ConnectionData, Host, Port, TLSConfig } from './network/types'; import type { SeedNodes } from './nodes/types'; -import type { CertificatePEMChain, CertManagerChangeData, Key } from './keys/types'; +import type { CertManagerChangeData, Key } from './keys/types'; import type { RecoveryCode, PrivateKey } from './keys/types'; +import type { PasswordMemLimit, PasswordOpsLimit } from './keys/types'; import path from 'path'; import process from 'process'; import Logger from '@matrixai/logger'; @@ -37,7 +38,6 @@ import * as utils from './utils'; import * as keysUtils from './keys/utils'; import * as nodesUtils from './nodes/utils'; import TaskManager from './tasks/TaskManager'; -import { PasswordMemLimit, PasswordOpsLimit } from './keys/types'; type NetworkConfig = { forwardHost?: Host; @@ -112,12 +112,12 @@ class PolykeyAgent { recoveryCode?: RecoveryCode; privateKey?: PrivateKey; privateKeyPath?: string; - passwordOpsLimit?: PasswordOpsLimit, - passwordMemLimit?: PasswordMemLimit, + passwordOpsLimit?: PasswordOpsLimit; + passwordMemLimit?: PasswordMemLimit; strictMemoryLock?: boolean; }; certManagerConfig?: { - certDuration?: number, + certDuration?: number; }; proxyConfig?: { authToken?: string; @@ -169,7 +169,7 @@ class PolykeyAgent { const certManagerConfig_ = { ...config.defaults.certManagerConfig, ...utils.filterEmptyObject(certManagerConfig), - } + }; const proxyConfig_ = { authToken: keysUtils.getRandomBytes(10).toString(), ...config.defaults.proxyConfig, @@ -259,15 +259,18 @@ class PolykeyAgent { lazy: true, logger, })); - certManager = certManager ?? (await CertManager.createCertManager({ - keyRing, - db, - taskManager, - changeCallback: async (data) => events.emitAsync(PolykeyAgent.eventSymbols.CertManager, data), - logger: logger.getChild(CertManager.name), - fresh, - ...certManagerConfig_, - })) + certManager = + certManager ?? + (await CertManager.createCertManager({ + keyRing, + db, + taskManager, + changeCallback: async (data) => + events.emitAsync(PolykeyAgent.eventSymbols.CertManager, data), + logger: logger.getChild(CertManager.name), + fresh, + ...certManagerConfig_, + })); sigchain = sigchain ?? (await Sigchain.createSigchain({ @@ -597,9 +600,7 @@ class PolykeyAgent { // Update the sigchain await this.sigchain.onKeyRingChange(); const tlsConfig: TLSConfig = { - keyPrivatePem: keysUtils.privateKeyToPEM( - data.keyPair.privateKey, - ), + keyPrivatePem: keysUtils.privateKeyToPEM(data.keyPair.privateKey), certChainPem: await this.certManager.getCertPEMsChainPEM(), }; this.grpcServerClient.setTLSConfig(tlsConfig); @@ -676,18 +677,41 @@ class PolykeyAgent { password, fresh, }); - await this.db.start({ fresh }); + await this.db.start({ + crypto: { + key: this.keyRing.dbKey, + ops: { + encrypt: async (key, plainText) => { + return keysUtils.encryptWithKey( + utils.bufferWrap(key) as Key, + utils.bufferWrap(plainText), + ); + }, + decrypt: async (key, cipherText) => { + return keysUtils.decryptWithKey( + utils.bufferWrap(key) as Key, + utils.bufferWrap(cipherText), + ); + }, + }, + }, + fresh, + }); await this.taskManager.start({ fresh, lazy: true }); await this.certManager.start({ - fresh + fresh, }); await this.sigchain.start({ fresh }); await this.acl.start({ fresh }); await this.gestaltGraph.start({ fresh }); + // Adding self to the gestaltGraph + await this.gestaltGraph.setNode({ nodeId: this.keyRing.getNodeId() }); await this.identitiesManager.start({ fresh }); // GRPC Server const tlsConfig: TLSConfig = { - keyPrivatePem: keysUtils.privateKeyToPEM(this.keyRing.keyPair.privateKey), + keyPrivatePem: keysUtils.privateKeyToPEM( + this.keyRing.keyPair.privateKey, + ), certChainPem: await this.certManager.getCertPEMsChainPEM(), }; // Client server @@ -796,10 +820,30 @@ class PolykeyAgent { this.logger.info(`Stopped ${this.constructor.name}`); } - public async destroy() { + public async destroy(password: string) { this.logger.info(`Destroying ${this.constructor.name}`); + // KeyRing needs to be started for the DB + await this.keyRing.start({ password }); // DB needs to be running for dependent domains to properly clear state. - await this.db.start(); + await this.db.start({ + crypto: { + key: this.keyRing.dbKey, + ops: { + encrypt: async (key, plainText) => { + return keysUtils.encryptWithKey( + utils.bufferWrap(key) as Key, + utils.bufferWrap(plainText), + ); + }, + decrypt: async (key, cipherText) => { + return keysUtils.decryptWithKey( + utils.bufferWrap(key) as Key, + utils.bufferWrap(cipherText), + ); + }, + }, + }, + }); // TaskManager needs to be running for dependent domains to clear state. await this.taskManager.start({ lazy: true }); await this.sessionManager.destroy(); @@ -818,6 +862,7 @@ class PolykeyAgent { await this.db.stop(); // Non-DB dependencies await this.db.destroy(); + await this.keyRing.stop(); await this.keyRing.destroy(); await this.schema.destroy(); this.logger.info(`Destroyed ${this.constructor.name}`); diff --git a/src/acl/types.ts b/src/acl/types.ts index 64e0fabaf..a75573b73 100644 --- a/src/acl/types.ts +++ b/src/acl/types.ts @@ -1,5 +1,5 @@ import type { PermissionId, PermissionIdString } from '../ids/types'; -import type { GestaltAction } from '../gestalts/types'; +import type { GestaltActions } from '../gestalts/types'; import type { VaultActions, VaultIdString } from '../vaults/types'; type Permission = { @@ -7,8 +7,6 @@ type Permission = { vaults: Record; }; -type GestaltActions = Partial>; - export type { PermissionId, PermissionIdString, diff --git a/src/agent/service/nodesChainDataGet.ts b/src/agent/service/nodesChainDataGet.ts index b95b16524..e20074d40 100644 --- a/src/agent/service/nodesChainDataGet.ts +++ b/src/agent/service/nodesChainDataGet.ts @@ -1,16 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { DB } from '@matrixai/db'; import type Sigchain from '../../sigchain/Sigchain'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import * as agentUtils from '../utils'; import * as claimsUtils from '../../claims/utils'; -import { encodeClaimId } from '../../ids' -import { KeyRing } from 'keys/index'; -import * as keysPB from 'proto/js/polykey/v1/keys/keys_pb'; -import { decodeClaimId } from '../../claims/utils'; +import { encodeClaimId } from '../../ids'; /** * Retrieves the ChainDataEncoded of this node. @@ -18,34 +14,32 @@ import { decodeClaimId } from '../../claims/utils'; function nodesChainDataGet({ sigchain, db, - keyRing, logger, }: { sigchain: Sigchain; db: DB; - keyRing: KeyRing; logger: Logger; }) { return async ( - call: grpc.ServerWritableStream + call: grpc.ServerWritableStream, ): Promise => { - const genClaims = grpcUtils.generatorWritable( - call, - false, - ); + const genClaims = grpcUtils.generatorWritable(call, false); try { - const SeekClaimId = decodeClaimId(call.request.getClaimId()); + // Const seekClaimId = decodeClaimId(call.request.getClaimId()); await db.withTransactionF(async (tran) => { - for await (const [claimId, signedClaim] of sigchain.getSignedClaims({ seek: SeekClaimId, order: 'asc' }, tran)){ - const encodedClaim = claimsUtils.generateSignedClaim(signedClaim) + for await (const [claimId, signedClaim] of sigchain.getSignedClaims( + { /* seek: seekClaimId,*/ order: 'asc' }, + tran, + )) { + const encodedClaim = claimsUtils.generateSignedClaim(signedClaim); const response = new nodesPB.AgentClaim(); response.setClaimId(encodeClaimId(claimId)); response.setPayload(encodedClaim.payload); - const signatureMessages = encodedClaim.signatures.map(item => { + const signatureMessages = encodedClaim.signatures.map((item) => { return new nodesPB.Signature() .setSignature(item.signature) - .setProtected(item.protected) - }) + .setProtected(item.protected); + }); response.setSignaturesList(signatureMessages); await genClaims.next(response); } diff --git a/src/agent/service/nodesCrossSignClaim.ts b/src/agent/service/nodesCrossSignClaim.ts index c2240144a..48341005f 100644 --- a/src/agent/service/nodesCrossSignClaim.ts +++ b/src/agent/service/nodesCrossSignClaim.ts @@ -3,12 +3,12 @@ import type NodeManager from '../../nodes/NodeManager'; import type KeyRing from '../../keys/KeyRing'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import type Logger from '@matrixai/logger'; +import type { ConnectionInfoGet } from '../types'; +import type ACL from '../../acl/ACL'; import * as grpcUtils from '../../grpc/utils'; import * as claimsErrors from '../../claims/errors'; import * as agentUtils from '../utils'; -import { ConnectionInfoGet } from '../types'; -import ACL from '../../acl/ACL'; -import * as nodesErrors from '../../nodes/errors'; +import * as nodesErrors from '../../nodes/errors'; function nodesCrossSignClaim({ keyRing, @@ -26,7 +26,7 @@ function nodesCrossSignClaim({ return async ( call: grpc.ServerDuplexStream, ) => { - const requestingNodeId = connectionInfoGet(call)!.remoteNodeId + const requestingNodeId = connectionInfoGet(call)!.remoteNodeId; const nodeId = keyRing.getNodeId(); const genClaims = grpcUtils.generatorDuplex( call, @@ -35,12 +35,13 @@ function nodesCrossSignClaim({ ); try { // Check the ACL for permissions - const permissions = await acl.getNodePerm(requestingNodeId) - if (permissions?.gestalt.claim !== null) throw new nodesErrors.ErrorNodePermissionDenied(); + const permissions = await acl.getNodePerm(requestingNodeId); + if (permissions?.gestalt.claim !== null) { + throw new nodesErrors.ErrorNodePermissionDenied(); + } // Handle claiming the node await nodeManager.handleClaimNode(requestingNodeId, genClaims); } catch (e) { - console.error(e); await genClaims.throw(e); !agentUtils.isAgentClientError(e, [ claimsErrors.ErrorEmptyStream, diff --git a/src/agent/service/notificationsSend.ts b/src/agent/service/notificationsSend.ts index 4a223424e..ea5cca0e4 100644 --- a/src/agent/service/notificationsSend.ts +++ b/src/agent/service/notificationsSend.ts @@ -3,13 +3,13 @@ import type NotificationsManager from '../../notifications/NotificationsManager' import type * as notificationsPB from '../../proto/js/polykey/v1/notifications/notifications_pb'; import type Logger from '@matrixai/logger'; import type { DB } from '@matrixai/db'; +import type { SignedNotification } from '../../notifications/types'; +import type KeyRing from '../../keys/KeyRing'; import * as grpcUtils from '../../grpc/utils'; import * as notificationsUtils from '../../notifications/utils'; import * as notificationsErrors from '../../notifications/errors'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import * as agentUtils from '../utils'; -import { SignedNotification } from '../../notifications/types'; -import KeyRing from '../../keys/KeyRing'; function notificationsSend({ notificationsManager, @@ -30,8 +30,12 @@ function notificationsSend({ callback: grpc.sendUnaryData, ): Promise => { try { - const signedNotification = call.request.getContent() as SignedNotification; - const notification = await notificationsUtils.verifyAndDecodeNotif(signedNotification, keyRing.getNodeId()); + const signedNotification = + call.request.getContent() as SignedNotification; + const notification = await notificationsUtils.verifyAndDecodeNotif( + signedNotification, + keyRing.getNodeId(), + ); await db.withTransactionF((tran) => notificationsManager.receiveNotification(notification, tran), ); diff --git a/src/bin/agent/CommandStart.ts b/src/bin/agent/CommandStart.ts index aa06d3e27..648623cc6 100644 --- a/src/bin/agent/CommandStart.ts +++ b/src/bin/agent/CommandStart.ts @@ -96,8 +96,12 @@ class CommandStart extends CommandPolykey { keyRingConfig: { recoveryCode: recoveryCodeIn, privateKeyPath: options.privateKeyFile, - passwordOpsLimit: fastPasswordHash ? keysUtils.passwordOpsLimits.min : undefined, - passwordMemLimit: fastPasswordHash ? keysUtils.passwordMemLimits.min : undefined, + passwordOpsLimit: fastPasswordHash + ? keysUtils.passwordOpsLimits.min + : undefined, + passwordMemLimit: fastPasswordHash + ? keysUtils.passwordMemLimits.min + : undefined, }, proxyConfig: { connConnectTime: options.connectionTimeout, diff --git a/src/bin/identities/CommandAllow.ts b/src/bin/identities/CommandAllow.ts index 7f84c901f..c61417ea0 100644 --- a/src/bin/identities/CommandAllow.ts +++ b/src/bin/identities/CommandAllow.ts @@ -62,38 +62,40 @@ class CommandAllow extends CommandPolykey { const setActionMessage = new permissionsPB.ActionSet(); setActionMessage.setAction(permissions); const [type, id] = gestaltId; - switch(type) { - case 'node': { - // Setting by Node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - setActionMessage.setNode(nodeMessage); - // Trusting - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsActionsSetByNode( - setActionMessage, - auth, - ), - meta, - ); - } - break; - case 'identity': { - // Setting By Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - setActionMessage.setIdentity(providerMessage); - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsActionsSetByIdentity( - setActionMessage, - auth, - ), - meta, - ); - } + switch (type) { + case 'node': + { + // Setting by Node + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); + setActionMessage.setNode(nodeMessage); + // Trusting + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsActionsSetByNode( + setActionMessage, + auth, + ), + meta, + ); + } + break; + case 'identity': + { + // Setting By Identity + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(id[0]); + providerMessage.setIdentityId(id[1]); + setActionMessage.setIdentity(providerMessage); + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsActionsSetByIdentity( + setActionMessage, + auth, + ), + meta, + ); + } break; default: utils.never(); diff --git a/src/bin/identities/CommandDisallow.ts b/src/bin/identities/CommandDisallow.ts index a653f9c35..1b2a30bcd 100644 --- a/src/bin/identities/CommandDisallow.ts +++ b/src/bin/identities/CommandDisallow.ts @@ -63,38 +63,40 @@ class CommandDisallow extends CommandPolykey { setActionMessage.setAction(permissions); const [type, id] = gestaltId; switch (type) { - case 'node': { - // Setting by Node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - setActionMessage.setNode(nodeMessage); - // Trusting - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsActionsUnsetByNode( - setActionMessage, - auth, - ), - meta, - ); - } + case 'node': + { + // Setting by Node + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); + setActionMessage.setNode(nodeMessage); + // Trusting + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsActionsUnsetByNode( + setActionMessage, + auth, + ), + meta, + ); + } break; - case 'identity': { - // Setting by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - setActionMessage.setIdentity(providerMessage); - // Trusting. - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsActionsUnsetByIdentity( - setActionMessage, - auth, - ), - meta, - ); - } + case 'identity': + { + // Setting by Identity + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(id[0]); + providerMessage.setIdentityId(id[1]); + setActionMessage.setIdentity(providerMessage); + // Trusting. + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsActionsUnsetByIdentity( + setActionMessage, + auth, + ), + meta, + ); + } break; default: utils.never(); diff --git a/src/bin/identities/CommandDiscover.ts b/src/bin/identities/CommandDiscover.ts index 0cb237a1a..e115eca0c 100644 --- a/src/bin/identities/CommandDiscover.ts +++ b/src/bin/identities/CommandDiscover.ts @@ -53,32 +53,37 @@ class CommandDiscover extends CommandPolykey { }); const [type, id] = gestaltId; switch (type) { - case 'node': { - // Discovery by Node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsDiscoveryByNode(nodeMessage, auth), - meta, - ); - } - break; - case 'identity': { - // Discovery by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsDiscoveryByIdentity( - providerMessage, - auth, - ), - meta, - ); - } - break; + case 'node': + { + // Discovery by Node + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsDiscoveryByNode( + nodeMessage, + auth, + ), + meta, + ); + } + break; + case 'identity': + { + // Discovery by Identity + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(id[0]); + providerMessage.setIdentityId(id[1]); + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsDiscoveryByIdentity( + providerMessage, + auth, + ), + meta, + ); + } + break; default: utils.never(); } diff --git a/src/bin/identities/CommandGet.ts b/src/bin/identities/CommandGet.ts index 5eb6fb7b5..2b464f594 100644 --- a/src/bin/identities/CommandGet.ts +++ b/src/bin/identities/CommandGet.ts @@ -57,32 +57,37 @@ class CommandGet extends CommandPolykey { let res: gestaltsPB.Graph | null = null; const [type, id] = gestaltId; switch (type) { - case 'node': { - // Getting from node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - res = await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsGestaltGetByNode(nodeMessage, auth), - meta, - ); - } - break; - case 'identity': { - // Getting from identity. - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - res = await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsGestaltGetByIdentity( - providerMessage, - auth, - ), - meta, - ); - } - break; + case 'node': + { + // Getting from node + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); + res = await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsGestaltGetByNode( + nodeMessage, + auth, + ), + meta, + ); + } + break; + case 'identity': + { + // Getting from identity. + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(id[0]); + providerMessage.setIdentityId(id[1]); + res = await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsGestaltGetByIdentity( + providerMessage, + auth, + ), + meta, + ); + } + break; default: utils.never(); } @@ -94,7 +99,7 @@ class CommandGet extends CommandPolykey { // Listing nodes. for (const nodeKey of Object.keys(gestalt.nodes)) { const node = gestalt.nodes[nodeKey]; - output.push(`${node.id}`); + output.push(`${node.nodeId}`); } // Listing identities for (const identityKey of Object.keys(gestalt.identities)) { diff --git a/src/bin/identities/CommandInvite.ts b/src/bin/identities/CommandInvite.ts index a1b29f437..205166b08 100644 --- a/src/bin/identities/CommandInvite.ts +++ b/src/bin/identities/CommandInvite.ts @@ -50,7 +50,8 @@ class CommandClaim extends CommandPolykey { const nodeClaimMessage = new nodesPB.Claim(); nodeClaimMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.identitiesInvite(nodeClaimMessage, auth), + (auth) => + pkClient.grpcClient.identitiesInvite(nodeClaimMessage, auth), meta, ); process.stdout.write( diff --git a/src/bin/identities/CommandList.ts b/src/bin/identities/CommandList.ts index 9b52641c1..ca578ca84 100644 --- a/src/bin/identities/CommandList.ts +++ b/src/bin/identities/CommandList.ts @@ -57,7 +57,7 @@ class CommandList extends CommandPolykey { }; for (const node of Object.keys(gestalt.nodes)) { const nodeInfo = gestalt.nodes[node]; - newGestalt.nodes.push({ id: nodeInfo.id }); + newGestalt.nodes.push({ nodeId: nodeInfo.nodeId }); } for (const identity of Object.keys(gestalt.identities)) { const identityInfo = gestalt.identities[identity]; @@ -68,7 +68,7 @@ class CommandList extends CommandPolykey { } // Getting the permissions for the gestalt. const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(newGestalt.nodes[0].id); + nodeMessage.setNodeId(newGestalt.nodes[0].nodeId); const actionsMessage = await binUtils.retryAuthentication( (auth) => pkClient.grpcClient.gestaltsActionsGetByNode(nodeMessage, auth), @@ -83,7 +83,7 @@ class CommandList extends CommandPolykey { }, meta); output = gestalts; if (options.format !== 'json') { - // Convert to a human readable list. + // Convert to a human-readable list. output = []; let count = 1; for (const gestalt of gestalts) { diff --git a/src/bin/identities/CommandPermissions.ts b/src/bin/identities/CommandPermissions.ts index 24e992567..5d3edf03a 100644 --- a/src/bin/identities/CommandPermissions.ts +++ b/src/bin/identities/CommandPermissions.ts @@ -54,34 +54,39 @@ class CommandPermissions extends CommandPolykey { const [type, id] = gestaltId; let actions: string[] = []; switch (type) { - case 'node': { - // Getting by Node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - const res = await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsActionsGetByNode(nodeMessage, auth), - meta, - ); - actions = res.getActionList(); - } + case 'node': + { + // Getting by Node + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); + const res = await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsActionsGetByNode( + nodeMessage, + auth, + ), + meta, + ); + actions = res.getActionList(); + } + break; + case 'identity': + { + // Getting by Identity + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(id[0]); + providerMessage.setIdentityId(id[1]); + const res = await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsActionsGetByIdentity( + providerMessage, + auth, + ), + meta, + ); + actions = res.getActionList(); + } break; - case 'identity': { - // Getting by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - const res = await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsActionsGetByIdentity( - providerMessage, - auth, - ), - meta, - ); - actions = res.getActionList(); - } - break; default: utils.never(); } diff --git a/src/bin/identities/CommandTrust.ts b/src/bin/identities/CommandTrust.ts index 4aa2dd227..852c1bed7 100644 --- a/src/bin/identities/CommandTrust.ts +++ b/src/bin/identities/CommandTrust.ts @@ -53,31 +53,36 @@ class CommandTrust extends CommandPolykey { }); const [type, id] = gestaltId; switch (type) { - case 'node': { - // Setting by Node. - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsGestaltTrustByNode(nodeMessage, auth), - meta, - ); - } + case 'node': + { + // Setting by Node. + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsGestaltTrustByNode( + nodeMessage, + auth, + ), + meta, + ); + } break; - case 'identity': { - // Setting by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsGestaltTrustByIdentity( - providerMessage, - auth, - ), - meta, - ); - } + case 'identity': + { + // Setting by Identity + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(id[0]); + providerMessage.setIdentityId(id[1]); + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsGestaltTrustByIdentity( + providerMessage, + auth, + ), + meta, + ); + } break; default: utils.never(); diff --git a/src/bin/identities/CommandUntrust.ts b/src/bin/identities/CommandUntrust.ts index d4e58ed5f..1047b60a7 100644 --- a/src/bin/identities/CommandUntrust.ts +++ b/src/bin/identities/CommandUntrust.ts @@ -59,36 +59,38 @@ class CommandUntrust extends CommandPolykey { setActionMessage.setAction(action); const [type, id] = gestaltId; switch (type) { - case 'node': { - // Setting by Node. - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - setActionMessage.setNode(nodeMessage); - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsActionsUnsetByNode( - setActionMessage, - auth, - ), - meta, - ); - } + case 'node': + { + // Setting by Node. + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); + setActionMessage.setNode(nodeMessage); + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsActionsUnsetByNode( + setActionMessage, + auth, + ), + meta, + ); + } break; - case 'identity': { - // Setting by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - setActionMessage.setIdentity(providerMessage); - await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.gestaltsActionsUnsetByIdentity( - setActionMessage, - auth, - ), - meta, - ); - } + case 'identity': + { + // Setting by Identity + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(id[0]); + providerMessage.setIdentityId(id[1]); + setActionMessage.setIdentity(providerMessage); + await binUtils.retryAuthentication( + (auth) => + pkClient.grpcClient.gestaltsActionsUnsetByIdentity( + setActionMessage, + auth, + ), + meta, + ); + } break; default: utils.never(); diff --git a/src/bin/keys/CommandEncrypt.ts b/src/bin/keys/CommandEncrypt.ts index 623a5460e..77317c455 100644 --- a/src/bin/keys/CommandEncrypt.ts +++ b/src/bin/keys/CommandEncrypt.ts @@ -1,10 +1,10 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { JWK } from '../../keys/types'; import * as binErrors from '../errors'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; -import { JWK } from '../../keys/types'; class CommandEncypt extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -15,10 +15,7 @@ class CommandEncypt extends CommandPolykey { '', 'Path to the file to encrypt, file must use binary encoding', ); - this.argument( - '', - 'NodeId or public JWK for target node' - ) + this.argument('', 'NodeId or public JWK for target node'); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); @@ -71,18 +68,23 @@ class CommandEncypt extends CommandPolykey { let publicJWK: JWK; const nodeId = nodesUtils.decodeNodeId(nodeIdOrJwkFile); if (nodeId != null) { - publicJWK = keysUtils.publicKeyToJWK(keysUtils.publicKeyFromNodeId(nodeId)); + publicJWK = keysUtils.publicKeyToJWK( + keysUtils.publicKeyFromNodeId(nodeId), + ); } else { // If it's not a NodeId then it's a file path to the JWK try { const rawJWK = await this.fs.promises.readFile(nodeIdOrJwkFile, { encoding: 'utf-8', - }) + }); publicJWK = JSON.parse(rawJWK) as JWK; // Checking if the JWK is valid keysUtils.publicKeyFromJWK(publicJWK); } catch (e) { - throw new binErrors.ErrorCLIPublicJWKFileRead('Failed to parse JWK file', {cause: e}); + throw new binErrors.ErrorCLIPublicJWKFileRead( + 'Failed to parse JWK file', + { cause: e }, + ); } } cryptoMessage.setData(plainText); diff --git a/src/bin/keys/CommandReset.ts b/src/bin/keys/CommandReset.ts index 3e47974a1..99b67c99b 100644 --- a/src/bin/keys/CommandReset.ts +++ b/src/bin/keys/CommandReset.ts @@ -33,6 +33,8 @@ class CommandReset extends CommandPolykey { this.fs, true, ); + this.logger.error('ASDASDASD'); + this.logger.error(passwordNew); let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); diff --git a/src/bin/keys/CommandVerify.ts b/src/bin/keys/CommandVerify.ts index bc6f1641a..b3e58445a 100644 --- a/src/bin/keys/CommandVerify.ts +++ b/src/bin/keys/CommandVerify.ts @@ -1,10 +1,10 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { JWK } from '../../keys/types'; import * as binErrors from '../errors'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; -import { JWK } from '../../keys/types'; class CommandVerify extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -19,10 +19,7 @@ class CommandVerify extends CommandPolykey { '', 'Path to the signature to be verified, file must be binary encoded', ); - this.argument( - '', - 'NodeId or public JWK for target node' - ); + this.argument('', 'NodeId or public JWK for target node'); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); @@ -79,18 +76,23 @@ class CommandVerify extends CommandPolykey { let publicJWK: JWK; const nodeId = nodesUtils.decodeNodeId(nodeIdOrJwkFile); if (nodeId != null) { - publicJWK = keysUtils.publicKeyToJWK(keysUtils.publicKeyFromNodeId(nodeId)); + publicJWK = keysUtils.publicKeyToJWK( + keysUtils.publicKeyFromNodeId(nodeId), + ); } else { // If it's not a NodeId then it's a file path to the JWK try { const rawJWK = await this.fs.promises.readFile(nodeIdOrJwkFile, { encoding: 'utf-8', - }) + }); publicJWK = JSON.parse(rawJWK) as JWK; // Checking if the JWK is valid keysUtils.publicKeyFromJWK(publicJWK); } catch (e) { - throw new binErrors.ErrorCLIPublicJWKFileRead('Failed to parse JWK file', {cause: e}); + throw new binErrors.ErrorCLIPublicJWKFileRead( + 'Failed to parse JWK file', + { cause: e }, + ); } } cryptoMessage.setData(data); diff --git a/src/bin/notifications/CommandRead.ts b/src/bin/notifications/CommandRead.ts index 039c358d0..8ec6479ff 100644 --- a/src/bin/notifications/CommandRead.ts +++ b/src/bin/notifications/CommandRead.ts @@ -76,7 +76,9 @@ class CommandRead extends CommandPolykey { const notificationMessages = response.getNotificationList(); const notifications: Array = []; for (const message of notificationMessages) { - const notification = notificationsUtils.parseNotification(JSON.parse(message.getContent())); + const notification = notificationsUtils.parseNotification( + JSON.parse(message.getContent()), + ); notifications.push(notification); } for (const notification of notifications) { diff --git a/src/bin/types.ts b/src/bin/types.ts index 04889a942..cb48a2ed9 100644 --- a/src/bin/types.ts +++ b/src/bin/types.ts @@ -5,7 +5,7 @@ import type { Host, Port } from '../network/types'; import type { StatusLive } from '../status/types'; import type { NodeIdEncoded } from '../ids/types'; import type { PrivateKey } from '../keys/types'; -import { PasswordOpsLimit, PasswordMemLimit } from '../keys/types'; +import type { PasswordOpsLimit, PasswordMemLimit } from '../keys/types'; type AgentStatusLiveData = Omit & { nodeId: NodeIdEncoded; @@ -28,12 +28,12 @@ type AgentChildProcessInput = { recoveryCode?: RecoveryCode; privateKey?: PrivateKey; privateKeyPath?: string; - passwordOpsLimit?: PasswordOpsLimit, - passwordMemLimit?: PasswordMemLimit, + passwordOpsLimit?: PasswordOpsLimit; + passwordMemLimit?: PasswordMemLimit; strictMemoryLock?: boolean; }; certManagerConfig?: { - certDuration?: number, + certDuration?: number; }; forwardProxyConfig?: { authToken?: string; diff --git a/src/bin/utils/processors.ts b/src/bin/utils/processors.ts index f23c0ea55..8b38d26e8 100644 --- a/src/bin/utils/processors.ts +++ b/src/bin/utils/processors.ts @@ -1,5 +1,5 @@ import type { FileSystem } from '../../types'; -import type { RecoveryCode, PrivateKeyPEM } from '../../keys/types'; +import type { RecoveryCode } from '../../keys/types'; import type { NodeId } from '../../ids/types'; import type { Host, Port } from '../../network/types'; import type { @@ -145,7 +145,7 @@ async function processNewPassword( } } else if (!existing && typeof process.env['PK_PASSWORD'] === 'string') { passwordNew = process.env['PK_PASSWORD']; - } else if(typeof process.env['PK_PASSWORD_NEW'] === 'string'){ + } else if (typeof process.env['PK_PASSWORD_NEW'] === 'string') { passwordNew = process.env['PK_PASSWORD_NEW']; } else { passwordNew = await promptNewPassword(); diff --git a/src/claims/payloads/claimLinkIdentity.ts b/src/claims/payloads/claimLinkIdentity.ts index fbf6d8fe2..b0394db45 100644 --- a/src/claims/payloads/claimLinkIdentity.ts +++ b/src/claims/payloads/claimLinkIdentity.ts @@ -10,16 +10,20 @@ import * as utils from '../../utils'; * Linking node and digital identity together */ interface ClaimLinkIdentity extends Claim { + typ: 'ClaimLinkIdentity'; iss: NodeIdEncoded; sub: ProviderIdentityIdEncoded; } function assertClaimLinkIdentity( - claimLinkIdentity: unknown + claimLinkIdentity: unknown, ): asserts claimLinkIdentity is ClaimLinkIdentity { if (!utils.isObject(claimLinkIdentity)) { + throw new validationErrors.ErrorParse('must be POJO'); + } + if (claimLinkIdentity['typ'] !== 'ClaimLinkIdentity') { throw new validationErrors.ErrorParse( - 'must be POJO', + '`typ` property must be `ClaimLinkIdentity`', ); } if ( @@ -31,27 +35,23 @@ function assertClaimLinkIdentity( ); } if (typeof claimLinkIdentity['sub'] !== 'string') { - throw new validationErrors.ErrorParse( - '`sub` property must be a string' - ); + throw new validationErrors.ErrorParse('`sub` property must be a string'); } } function parseClaimLinkIdentity( - claimLinkIdentityEncoded: unknown + claimLinkIdentityEncoded: unknown, ): ClaimLinkIdentity { - const claimLinkIdentity = claimsUtils.parseClaim( - claimLinkIdentityEncoded - ); + const claimLinkIdentity = claimsUtils.parseClaim(claimLinkIdentityEncoded); assertClaimLinkIdentity(claimLinkIdentity); return claimLinkIdentity; } function parseSignedClaimLinkIdentity( - signedClaimLinkIdentityEncoded: unknown + signedClaimLinkIdentityEncoded: unknown, ): SignedClaim { const signedClaim = tokensUtils.parseSignedToken( - signedClaimLinkIdentityEncoded + signedClaimLinkIdentityEncoded, ); assertClaimLinkIdentity(signedClaim.payload); return signedClaim as SignedClaim; @@ -63,6 +63,4 @@ export { parseSignedClaimLinkIdentity, }; -export type { - ClaimLinkIdentity -}; +export type { ClaimLinkIdentity }; diff --git a/src/claims/payloads/claimLinkNode.ts b/src/claims/payloads/claimLinkNode.ts index 48da9100a..8d56de9d1 100644 --- a/src/claims/payloads/claimLinkNode.ts +++ b/src/claims/payloads/claimLinkNode.ts @@ -10,16 +10,20 @@ import * as utils from '../../utils'; * Linking 2 nodes together */ interface ClaimLinkNode extends Claim { + typ: 'ClaimLinkNode'; iss: NodeIdEncoded; sub: NodeIdEncoded; } function assertClaimLinkNode( - claimLinkNode: unknown + claimLinkNode: unknown, ): asserts claimLinkNode is ClaimLinkNode { if (!utils.isObject(claimLinkNode)) { + throw new validationErrors.ErrorParse('must be POJO'); + } + if (claimLinkNode['typ'] !== 'ClaimLinkNode') { throw new validationErrors.ErrorParse( - 'must be POJO', + '`typ` property must be `ClaimLinkNode`', ); } if ( @@ -40,32 +44,20 @@ function assertClaimLinkNode( } } -function parseClaimLinkNode( - claimLinkNodeEncoded: unknown -): ClaimLinkNode { - const claimLinkNode = claimsUtils.parseClaim( - claimLinkNodeEncoded - ); +function parseClaimLinkNode(claimLinkNodeEncoded: unknown): ClaimLinkNode { + const claimLinkNode = claimsUtils.parseClaim(claimLinkNodeEncoded); assertClaimLinkNode(claimLinkNode); return claimLinkNode; } function parseSignedClaimLinkNode( - signedClaimLinkNodeEncoded: unknown + signedClaimLinkNodeEncoded: unknown, ): SignedClaim { - const signedClaim = tokensUtils.parseSignedToken( - signedClaimLinkNodeEncoded - ); + const signedClaim = tokensUtils.parseSignedToken(signedClaimLinkNodeEncoded); assertClaimLinkNode(signedClaim.payload); return signedClaim as SignedClaim; } -export { - assertClaimLinkNode, - parseClaimLinkNode, - parseSignedClaimLinkNode, -}; +export { assertClaimLinkNode, parseClaimLinkNode, parseSignedClaimLinkNode }; -export type { - ClaimLinkNode -}; +export type { ClaimLinkNode }; diff --git a/src/claims/types.ts b/src/claims/types.ts index 5f07be85f..ba7a1b28e 100644 --- a/src/claims/types.ts +++ b/src/claims/types.ts @@ -61,8 +61,4 @@ export type { SignedClaimDigestEncoded, }; -export type { - ClaimId, - ClaimIdString, - ClaimIdEncoded, -} from '../ids/types'; +export type { ClaimId, ClaimIdString, ClaimIdEncoded } from '../ids/types'; diff --git a/src/claims/utils.ts b/src/claims/utils.ts index 374364247..86a1daa9c 100644 --- a/src/claims/utils.ts +++ b/src/claims/utils.ts @@ -5,10 +5,7 @@ import type { SignedClaimEncoded, SignedClaimDigestEncoded, } from './types'; -import type { - Digest, - DigestFormats, -} from '../keys/types'; +import type { Digest, DigestFormats } from '../keys/types'; import canonicalize from 'canonicalize'; import * as ids from '../ids'; import * as tokensUtils from '../tokens/utils'; @@ -27,32 +24,21 @@ function generateSignedClaim(signedClaim: SignedClaim): SignedClaimEncoded { function assertClaim(claim: unknown): asserts claim is Claim { if (!utils.isObject(claim)) { - throw new validationErrors.ErrorParse( - 'must be POJO', - ); + throw new validationErrors.ErrorParse('must be POJO'); } - if ( - claim['jti'] == null || - ids.decodeClaimId(claim['jti']) == null - ) { + if (claim['jti'] == null || ids.decodeClaimId(claim['jti']) == null) { throw new validationErrors.ErrorParse( '`jti` property must be an encoded claim ID', ); } if (claim['iat'] == null) { - throw new validationErrors.ErrorParse( - '`iat` property must be integer', - ); + throw new validationErrors.ErrorParse('`iat` property must be integer'); } if (claim['nbf'] == null) { - throw new validationErrors.ErrorParse( - '`nbf` property must be integer', - ); + throw new validationErrors.ErrorParse('`nbf` property must be integer'); } if (claim['seq'] == null) { - throw new validationErrors.ErrorParse( - '`seq` property must be integer', - ); + throw new validationErrors.ErrorParse('`seq` property must be integer'); } if ( claim['prevClaimId'] !== null && @@ -62,30 +48,23 @@ function assertClaim(claim: unknown): asserts claim is Claim { '`prevClaimId` property must be an encoded claim ID', ); } - if ( - claim['prevDigest'] !== null && - typeof claim['prevDigest'] !== 'string' - ) { + if (claim['prevDigest'] !== null && typeof claim['prevDigest'] !== 'string') { throw new validationErrors.ErrorParse( '`prevDigest` property must be string or null', ); } } -function parseClaim( - claimEncoded: unknown -): C { +function parseClaim(claimEncoded: unknown): C { const claim = tokensUtils.parseTokenPayload(claimEncoded); assertClaim(claim); return claim as C; } function parseSignedClaim( - signedClaimEncoded: unknown + signedClaimEncoded: unknown, ): SignedClaim { - const signedClaim = tokensUtils.parseSignedToken( - signedClaimEncoded - ); + const signedClaim = tokensUtils.parseSignedToken(signedClaimEncoded); assertClaim(signedClaim.payload); return signedClaim; } @@ -95,7 +74,7 @@ function parseSignedClaim( */ function hashSignedClaim( claim: SignedClaim, - format: F + format: F, ): Digest { const claimJSON = canonicalize(claim)!; const claimData = Buffer.from(claimJSON, 'utf-8'); @@ -108,10 +87,13 @@ function hashSignedClaim( */ function encodeSignedClaimDigest( claimDigest: Digest, - format: F + format: F, ): SignedClaimDigestEncoded { const claimMultiDigest = keysUtils.digestToMultidigest(claimDigest, format); - const claimDigestEncoded = utils.toMultibase(claimMultiDigest.bytes, 'base58btc'); + const claimDigestEncoded = utils.toMultibase( + claimMultiDigest.bytes, + 'base58btc', + ); return claimDigestEncoded as SignedClaimDigestEncoded; } @@ -119,7 +101,7 @@ function encodeSignedClaimDigest( * Decodes multibase multihash string to claim digest */ function decodeSignedClaimDigest( - claimDigestEncoded: any + claimDigestEncoded: any, ): [Digest, F] | undefined { if (typeof claimDigestEncoded !== 'string') { return; @@ -128,17 +110,13 @@ function decodeSignedClaimDigest( if (claimMultiDigestData == null) { return; } - const claimMultiDigest = keysUtils.digestFromMultidigest(claimMultiDigestData); + const claimMultiDigest = + keysUtils.digestFromMultidigest(claimMultiDigestData); if (claimMultiDigest == null) { return; } - const format = keysTypes.multihashCodesI[ - claimMultiDigest.code - ]; - return [ - utils.bufferWrap(claimMultiDigest.digest) as Digest, - format as F, - ]; + const format = keysTypes.multihashCodesI[claimMultiDigest.code]; + return [utils.bufferWrap(claimMultiDigest.digest) as Digest, format as F]; } export { @@ -152,8 +130,4 @@ export { decodeSignedClaimDigest, }; -export { - createClaimIdGenerator, - encodeClaimId, - decodeClaimId, -} from '../ids'; +export { createClaimIdGenerator, encodeClaimId, decodeClaimId } from '../ids'; diff --git a/src/client/service/agentStatus.ts b/src/client/service/agentStatus.ts index 2eee56cec..b8056ca7e 100644 --- a/src/client/service/agentStatus.ts +++ b/src/client/service/agentStatus.ts @@ -48,7 +48,9 @@ function agentStatus({ response.setForwardPort(proxy.getForwardPort()); response.setProxyHost(proxy.getProxyHost()); response.setProxyPort(proxy.getProxyPort()); - response.setPublicKeyJwk(JSON.stringify(keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey))); + response.setPublicKeyJwk( + JSON.stringify(keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey)), + ); response.setCertChainPem(await certManager.getCertPEMsChainPEM()); callback(null, response); return; diff --git a/src/client/service/gestaltsActionsGetByIdentity.ts b/src/client/service/gestaltsActionsGetByIdentity.ts index 1b469de47..dc9da528b 100644 --- a/src/client/service/gestaltsActionsGetByIdentity.ts +++ b/src/client/service/gestaltsActionsGetByIdentity.ts @@ -49,16 +49,12 @@ function gestaltsActionsGetByIdentity({ ); const result = await db.withTransactionF((tran) => - gestaltGraph.getGestaltActions(['identity', [providerId, identityId]], tran), + gestaltGraph.getGestaltActions( + ['identity', [providerId, identityId]], + tran, + ), ); - if (result == null) { - // Node doesn't exist, so no permissions - response.setActionList([]); - } else { - // Contains permission - const actions = Object.keys(result); - response.setActionList(actions); - } + response.setActionList(Object.keys(result)); callback(null, response); return; } catch (e) { diff --git a/src/client/service/gestaltsActionsGetByNode.ts b/src/client/service/gestaltsActionsGetByNode.ts index a420db4e9..8089fee47 100644 --- a/src/client/service/gestaltsActionsGetByNode.ts +++ b/src/client/service/gestaltsActionsGetByNode.ts @@ -45,14 +45,7 @@ function gestaltsActionsGetByNode({ const result = await db.withTransactionF((tran) => gestaltGraph.getGestaltActions(['node', nodeId], tran), ); - if (result == null) { - // Node doesn't exist, so no permissions - response.setActionList([]); - } else { - // Contains permission - const actions = Object.keys(result); - response.setActionList(actions); - } + response.setActionList(Object.keys(result)); callback(null, response); return; } catch (e) { diff --git a/src/client/service/gestaltsActionsSetByIdentity.ts b/src/client/service/gestaltsActionsSetByIdentity.ts index 1af6c6048..dc0d445e5 100644 --- a/src/client/service/gestaltsActionsSetByIdentity.ts +++ b/src/client/service/gestaltsActionsSetByIdentity.ts @@ -57,7 +57,7 @@ function gestaltsActionsSetByIdentity({ }, ); await db.withTransactionF((tran) => - gestaltGraph.setGestaltActions( + gestaltGraph.setGestaltAction( ['identity', [providerId, identityId]], action, tran, diff --git a/src/client/service/gestaltsActionsSetByNode.ts b/src/client/service/gestaltsActionsSetByNode.ts index bb3cad805..c97f889d0 100644 --- a/src/client/service/gestaltsActionsSetByNode.ts +++ b/src/client/service/gestaltsActionsSetByNode.ts @@ -48,7 +48,7 @@ function gestaltsActionsSetByNode({ }, ); await db.withTransactionF((tran) => - gestaltGraph.setGestaltActions(['node', nodeId], action, tran), + gestaltGraph.setGestaltAction(['node', nodeId], action, tran), ); callback(null, response); return; diff --git a/src/client/service/gestaltsActionsUnsetByIdentity.ts b/src/client/service/gestaltsActionsUnsetByIdentity.ts index 1076a5e35..8ce32db19 100644 --- a/src/client/service/gestaltsActionsUnsetByIdentity.ts +++ b/src/client/service/gestaltsActionsUnsetByIdentity.ts @@ -57,7 +57,7 @@ function gestaltsActionsUnsetByIdentity({ }, ); await db.withTransactionF((tran) => - gestaltGraph.unsetGestaltActions( + gestaltGraph.unsetGestaltAction( ['identity', [providerId, identityId]], action, tran, diff --git a/src/client/service/gestaltsActionsUnsetByNode.ts b/src/client/service/gestaltsActionsUnsetByNode.ts index 56ed86aa9..7a072184a 100644 --- a/src/client/service/gestaltsActionsUnsetByNode.ts +++ b/src/client/service/gestaltsActionsUnsetByNode.ts @@ -48,7 +48,7 @@ function gestaltsActionsUnsetByNode({ }, ); await db.withTransactionF((tran) => - gestaltGraph.unsetGestaltActions(['node', nodeId], action, tran), + gestaltGraph.unsetGestaltAction(['node', nodeId], action, tran), ); callback(null, response); return; diff --git a/src/client/service/gestaltsGestaltGetByIdentity.ts b/src/client/service/gestaltsGestaltGetByIdentity.ts index c15d580c0..0ab2dfea5 100644 --- a/src/client/service/gestaltsGestaltGetByIdentity.ts +++ b/src/client/service/gestaltsGestaltGetByIdentity.ts @@ -11,6 +11,7 @@ import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; import * as clientUtils from '../utils'; +import * as nodesUtils from '../../nodes/utils'; function gestaltsGestaltGetByIdentity({ authenticate, @@ -54,7 +55,27 @@ function gestaltsGestaltGetByIdentity({ gestaltGraph.getGestaltByIdentity([providerId, identityId], tran), ); if (gestalt != null) { - response.setGestaltGraph(JSON.stringify(gestalt)); + const newGestalt = { + matrix: {}, + nodes: {}, + identities: gestalt.identities, + }; + for (const [key, value] of Object.entries(gestalt.nodes)) { + newGestalt.nodes[key] = { + nodeId: nodesUtils.encodeNodeId(value.nodeId), + }; + } + for (const keyA of Object.keys(gestalt.matrix)) { + for (const keyB of Object.keys(gestalt.matrix[keyA])) { + let record = newGestalt.matrix[keyA]; + if (record == null) { + record = {}; + newGestalt.matrix[keyA] = record; + } + record[keyB] = null; + } + } + response.setGestaltGraph(JSON.stringify(newGestalt)); } callback(null, response); return; diff --git a/src/client/service/gestaltsGestaltGetByNode.ts b/src/client/service/gestaltsGestaltGetByNode.ts index b23f85ca7..becd0b002 100644 --- a/src/client/service/gestaltsGestaltGetByNode.ts +++ b/src/client/service/gestaltsGestaltGetByNode.ts @@ -11,6 +11,7 @@ import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; import * as clientUtils from '../utils'; +import * as nodesUtils from '../../nodes/utils'; function gestaltsGestaltGetByNode({ authenticate, @@ -50,7 +51,27 @@ function gestaltsGestaltGetByNode({ gestaltGraph.getGestaltByNode(nodeId, tran), ); if (gestalt != null) { - response.setGestaltGraph(JSON.stringify(gestalt)); + const newGestalt = { + matrix: {}, + nodes: {}, + identities: gestalt.identities, + }; + for (const [key, value] of Object.entries(gestalt.nodes)) { + newGestalt.nodes[key] = { + nodeId: nodesUtils.encodeNodeId(value.nodeId), + }; + } + for (const keyA of Object.keys(gestalt.matrix)) { + for (const keyB of Object.keys(gestalt.matrix[keyA])) { + let record = newGestalt.matrix[keyA]; + if (record == null) { + record = {}; + newGestalt.matrix[keyA] = record; + } + record[keyB] = null; + } + } + response.setGestaltGraph(JSON.stringify(newGestalt)); } callback(null, response); return; diff --git a/src/client/service/gestaltsGestaltList.ts b/src/client/service/gestaltsGestaltList.ts index faa34ee34..f15e64af9 100644 --- a/src/client/service/gestaltsGestaltList.ts +++ b/src/client/service/gestaltsGestaltList.ts @@ -2,12 +2,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type { Gestalt } from '../../gestalts/types'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; import * as clientUtils from '../utils'; +import * as nodesUtils from '../../nodes/utils'; function gestaltsGestaltList({ authenticate, @@ -24,22 +24,36 @@ function gestaltsGestaltList({ call: grpc.ServerWritableStream, ): Promise => { const genWritable = grpcUtils.generatorWritable(call, false); - let gestaltMessage: gestaltsPB.Gestalt; try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const certs: Array = [] // FIXME: this should be streaming the data await db.withTransactionF(async (tran) => { - for await (const gestalt of gestaltGraph.getGestalts(tran)) { - certs.push(gestalt); + for await (const gestalt of gestaltGraph.getGestalts(tran)) { + const newGestalt = { + matrix: {}, + nodes: {}, + identities: gestalt.identities, + }; + for (const [key, value] of Object.entries(gestalt.nodes)) { + newGestalt.nodes[key] = { + nodeId: nodesUtils.encodeNodeId(value.nodeId), + }; } + for (const keyA of Object.keys(gestalt.matrix)) { + let record = newGestalt.matrix[keyA]; + if (record == null) { + record = {}; + newGestalt.matrix[keyA] = record; + } + for (const keyB of Object.keys(gestalt.matrix[keyA])) { + record[keyB] = null; + } + } + const gestaltMessage = new gestaltsPB.Gestalt(); + gestaltMessage.setName(JSON.stringify(newGestalt)); + await genWritable.next(gestaltMessage); } - ); - for (const cert of certs) { - gestaltMessage = new gestaltsPB.Gestalt(); - gestaltMessage.setName(JSON.stringify(cert)); - await genWritable.next(gestaltMessage); - } + }); await genWritable.next(null); return; } catch (e) { diff --git a/src/client/service/gestaltsGestaltTrustByIdentity.ts b/src/client/service/gestaltsGestaltTrustByIdentity.ts index 7bf2e5b0d..390e8d1a2 100644 --- a/src/client/service/gestaltsGestaltTrustByIdentity.ts +++ b/src/client/service/gestaltsGestaltTrustByIdentity.ts @@ -72,7 +72,7 @@ function gestaltsGestaltTrustByIdentity({ // will throw an error. Since discovery can take time, you may need to // reattempt this command if it fails on the first attempt and you expect // there to be a linked node for the identity. - await gestaltGraph.setGestaltActions( + await gestaltGraph.setGestaltAction( ['identity', [providerId, identityId]], 'notify', tran, diff --git a/src/client/service/gestaltsGestaltTrustByNode.ts b/src/client/service/gestaltsGestaltTrustByNode.ts index 26dad4828..a4153fb05 100644 --- a/src/client/service/gestaltsGestaltTrustByNode.ts +++ b/src/client/service/gestaltsGestaltTrustByNode.ts @@ -12,7 +12,6 @@ import * as grpcUtils from '../../grpc/utils'; import * as gestaltsErrors from '../../gestalts/errors'; import * as validationUtils from '../../validation/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as nodesUtils from '../../nodes/utils'; import * as clientUtils from '../utils'; function gestaltsGestaltTrustByNode({ @@ -64,7 +63,7 @@ function gestaltsGestaltTrustByNode({ await discovery.queueDiscoveryByNode(nodeId); } // Set notify permission - await gestaltGraph.setGestaltActions(['node', nodeId], 'notify', tran); + await gestaltGraph.setGestaltAction(['node', nodeId], 'notify', tran); }); callback(null, response); return; diff --git a/src/client/service/identitiesClaim.ts b/src/client/service/identitiesClaim.ts index 7c6ac9999..e5944ee8f 100644 --- a/src/client/service/identitiesClaim.ts +++ b/src/client/service/identitiesClaim.ts @@ -1,21 +1,15 @@ import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type Sigchain from '../../sigchain/Sigchain'; import type IdentitiesManager from '../../identities/IdentitiesManager'; import type { IdentityId, ProviderId } from '../../identities/types'; import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; -import * as nodesUtils from '../../nodes/utils'; import * as identitiesErrors from '../../identities/errors'; import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; import { matchSync } from '../../utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; import * as clientUtils from '../utils'; -import { SignedClaim } from 'claims/types'; -import { ClaimLinkIdentity } from 'claims/payloads/index'; /** * Augments the keynode with a new identity. @@ -56,7 +50,10 @@ function identitiesClaim({ identityId: call.request.getIdentityId(), }, ); - const claimData = await identitiesManager.handleClaimIdentity(providerId, identityId); + const claimData = await identitiesManager.handleClaimIdentity( + providerId, + identityId, + ); response.setClaimId(claimData.id); if (claimData.url) { response.setUrl(claimData.url); diff --git a/src/client/service/identitiesTokenPut.ts b/src/client/service/identitiesTokenPut.ts index 9cb2f7909..c75804b57 100644 --- a/src/client/service/identitiesTokenPut.ts +++ b/src/client/service/identitiesTokenPut.ts @@ -2,7 +2,11 @@ import type * as grpc from '@grpc/grpc-js'; import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type { IdentityId, ProviderId, ProviderToken } from '../../identities/types'; +import type { + IdentityId, + ProviderId, + ProviderToken, +} from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; diff --git a/src/client/service/keysEncrypt.ts b/src/client/service/keysEncrypt.ts index 0e3769a65..88d96d2d8 100644 --- a/src/client/service/keysEncrypt.ts +++ b/src/client/service/keysEncrypt.ts @@ -2,10 +2,10 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type KeyRing from '../../keys/KeyRing'; import type Logger from '@matrixai/logger'; +import type { PublicKey, JWK } from '../../keys/types'; import * as grpcUtils from '../../grpc/utils'; import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; import * as clientUtils from '../utils'; -import { PublicKey, JWK } from '../../keys/types'; import * as keysUtils from '../../keys/utils/index'; import { never } from '../../utils/index'; import * as keysErrors from '../../keys/errors'; @@ -33,7 +33,7 @@ function keysEncrypt({ publicKey = keysUtils.publicKeyFromJWK(jwk); if (publicKey == null) never(); } catch (e) { - throw new keysErrors.ErrorPublicKeyParse(undefined, {cause: e}); + throw new keysErrors.ErrorPublicKeyParse(undefined, { cause: e }); } const data = keyRing.encrypt( publicKey, diff --git a/src/client/service/keysVerify.ts b/src/client/service/keysVerify.ts index 67402d37c..1aa78ea6d 100644 --- a/src/client/service/keysVerify.ts +++ b/src/client/service/keysVerify.ts @@ -3,13 +3,13 @@ import type { Authenticate } from '../types'; import type KeyRing from '../../keys/KeyRing'; import type * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; import type Logger from '@matrixai/logger'; +import type { Signature, JWK, PublicKey } from '../../keys/types'; import * as grpcUtils from '../../grpc/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '../utils'; -import * as keysUtils from '../../keys/utils' -import * as keysErrors from '../../keys/errors' +import * as keysUtils from '../../keys/utils'; +import * as keysErrors from '../../keys/errors'; import { never } from '../../utils/index'; -import { Signature, JWK, PublicKey } from '../../keys/types'; function keysVerify({ authenticate, @@ -34,7 +34,7 @@ function keysVerify({ publicKey = keysUtils.publicKeyFromJWK(jwk); if (publicKey == null) never(); } catch (e) { - throw new keysErrors.ErrorPublicKeyParse(undefined, {cause: e}); + throw new keysErrors.ErrorPublicKeyParse(undefined, { cause: e }); } const status = keyRing.verify( publicKey, diff --git a/src/client/service/nodesClaim.ts b/src/client/service/nodesClaim.ts index 405b06eb5..e69c90f7b 100644 --- a/src/client/service/nodesClaim.ts +++ b/src/client/service/nodesClaim.ts @@ -3,7 +3,6 @@ import type { DB } from '@matrixai/db'; import type { Authenticate } from '../types'; import type NodeManager from '../../nodes/NodeManager'; import type { NodeId } from '../../ids/types'; -import type NotificationsManager from '../../notifications/NotificationsManager'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import type Logger from '@matrixai/logger'; import * as grpcUtils from '../../grpc/utils'; @@ -22,13 +21,11 @@ import * as clientUtils from '../utils'; function nodesClaim({ authenticate, nodeManager, - notificationsManager, db, logger, }: { authenticate: Authenticate; nodeManager: NodeManager; - notificationsManager: NotificationsManager; db: DB; logger: Logger; }) { diff --git a/src/client/service/notificationsSend.ts b/src/client/service/notificationsSend.ts index a581785ab..524cac6b1 100644 --- a/src/client/service/notificationsSend.ts +++ b/src/client/service/notificationsSend.ts @@ -4,6 +4,7 @@ import type NotificationsManager from '../../notifications/NotificationsManager' import type { NodeId } from '../../ids/types'; import type * as notificationsPB from '../../proto/js/polykey/v1/notifications/notifications_pb'; import type Logger from '@matrixai/logger'; +import type { General } from '../../notifications/types'; import * as grpcUtils from '../../grpc/utils'; import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; @@ -11,7 +12,6 @@ import * as nodesErrors from '../../nodes/errors'; import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '../utils'; -import { General } from '../../notifications/types'; function notificationsSend({ authenticate, diff --git a/src/client/service/vaultsPermissionSet.ts b/src/client/service/vaultsPermissionSet.ts index 08f6ef041..e5d7eab2c 100644 --- a/src/client/service/vaultsPermissionSet.ts +++ b/src/client/service/vaultsPermissionSet.ts @@ -80,7 +80,7 @@ function vaultsPermissionSet({ if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); // Setting permissions const actionsSet: VaultActions = {}; - await gestaltGraph.setGestaltActions(['node', nodeId], 'scan', tran); + await gestaltGraph.setGestaltAction(['node', nodeId], 'scan', tran); for (const action of actions) { await acl.setVaultAction(vaultId, nodeId, action, tran); actionsSet[action] = null; diff --git a/src/client/service/vaultsPermissionUnset.ts b/src/client/service/vaultsPermissionUnset.ts index 4648ceedd..ff55c206d 100644 --- a/src/client/service/vaultsPermissionUnset.ts +++ b/src/client/service/vaultsPermissionUnset.ts @@ -75,7 +75,7 @@ function vaultsPermissionUnset({ const vaultMeta = await vaultManager.getVaultMeta(vaultId, tran); if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); // Unsetting permissions - await gestaltGraph.setGestaltActions(['node', nodeId], 'scan', tran); + await gestaltGraph.setGestaltAction(['node', nodeId], 'scan', tran); for (const action of actions) { await acl.unsetVaultAction(vaultId, nodeId, action, tran); } @@ -89,7 +89,11 @@ function vaultsPermissionUnset({ .reduce((prev, current) => current + prev); // If no permissions are left then we remove the scan permission if (totalPermissions === 0) { - await gestaltGraph.unsetGestaltActions(['node', nodeId], 'scan', tran); + await gestaltGraph.unsetGestaltAction( + ['node', nodeId], + 'scan', + tran, + ); } } }); diff --git a/src/config.ts b/src/config.ts index 6c701ff2b..6c96a548b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -87,7 +87,7 @@ const config = { efsBase: 'efs', tokenBase: 'token', certManagerConfig: { - certDuration: 31536000 + certDuration: 31536000, }, networkConfig: { // ForwardProxy diff --git a/src/discovery/Discovery.ts b/src/discovery/Discovery.ts index b2923b884..e092596d9 100644 --- a/src/discovery/Discovery.ts +++ b/src/discovery/Discovery.ts @@ -3,7 +3,11 @@ import type { PromiseCancellable } from '@matrixai/async-cancellable'; import type { NodeId } from '../nodes/types'; import type NodeManager from '../nodes/NodeManager'; import type GestaltGraph from '../gestalts/GestaltGraph'; -import type { GestaltId, GestaltNodeInfo, GestaltIdEncoded } from '../gestalts/types'; +import type { + GestaltId, + GestaltNodeInfo, + GestaltIdEncoded, +} from '../gestalts/types'; import type IdentitiesManager from '../identities/IdentitiesManager'; import type { IdentityData, @@ -17,8 +21,15 @@ import type { ClaimId, ClaimIdEncoded, SignedClaim } from '../claims/types'; import type TaskManager from '../tasks/TaskManager'; import type { ContextTimed } from '../contexts/types'; import type { TaskHandler, TaskHandlerId } from '../tasks/types'; +import type { + ClaimLinkIdentity, + ClaimLinkNode, +} from '../claims/payloads/index'; import Logger from '@matrixai/logger'; -import { CreateDestroyStartStop, ready } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import { + CreateDestroyStartStop, + ready, +} from '@matrixai/async-init/dist/CreateDestroyStartStop'; import { Timer } from '@matrixai/timer'; import * as discoveryErrors from './errors'; import * as tasksErrors from '../tasks/errors'; @@ -28,10 +39,8 @@ import * as keysUtils from '../keys/utils'; import { never } from '../utils'; import { context } from '../contexts/index'; import TimedCancellable from '../contexts/decorators/timedCancellable'; -import { ClaimLinkIdentity, ClaimLinkNode } from '../claims/payloads/index'; import Token from '../tokens/Token'; import { decodeClaimId } from '../ids/index'; -import { utils as idUtils } from '@matrixai/id'; /** * This is the reason used to cancel duplicate tasks for vertices @@ -110,7 +119,9 @@ class Discovery { e === discoveryStoppingTaskReason ) { // We need to recreate the task for the vertex - await this.scheduleDiscoveryForVertex(gestaltsUtils.decodeGestaltId(vertex)!); + const vertexId = gestaltsUtils.decodeGestaltId(vertex); + if (vertexId == null) never(); + await this.scheduleDiscoveryForVertex(vertexId); return; } // Aborting a duplicate task is not an error @@ -218,7 +229,10 @@ class Discovery { providerId: ProviderId, identityId: IdentityId, ): Promise { - await this.scheduleDiscoveryForVertex(['identity', [providerId, identityId]]); + await this.scheduleDiscoveryForVertex([ + 'identity', + [providerId, identityId], + ]); } // Fixme, when processing a vertex, we need to check existing links in the @@ -235,7 +249,9 @@ class Discovery { @context ctx: ContextTimed, ): Promise { this.logger.debug(`Processing vertex: ${vertex}`); - const [type, id] = gestaltsUtils.decodeGestaltId(vertex)!; + const vertexId = gestaltsUtils.decodeGestaltId(vertex); + if (vertexId == null) never(); + const [type, id] = vertexId; switch (type) { case 'node': return await this.processNode(id, connectionTimeout, ctx); @@ -247,11 +263,16 @@ class Discovery { this.visitedVertices.add(vertex); } - protected async processNode(id: NodeId, connectionTimeout: number | undefined, ctx: ContextTimed) { - + protected async processNode( + nodeId: NodeId, + connectionTimeout: number | undefined, + ctx: ContextTimed, + ) { // If the vertex we've found is our own node, we simply get our own chain - const nodeId = nodesUtils.decodeNodeId(id)!; - const encodedGestaltNodeId = gestaltsUtils.encodeGestaltNodeId(['node', nodeId]) + const encodedGestaltNodeId = gestaltsUtils.encodeGestaltNodeId([ + 'node', + nodeId, + ]); if (nodeId.equals(this.keyRing.getNodeId())) { // Skip our own nodeId, we actively add this information when it changes, // so there is no need to scan it. @@ -259,18 +280,21 @@ class Discovery { return; } // Get the oldest known claim for this node - const gestaltLinks = await this.gestaltGraph.getLinks(['node', nodeId]); // get the oldest one let newestClaimId: ClaimId | undefined = undefined; - for (let [,gestaltLink] of gestaltLinks) { + for await (const [, gestaltLink] of this.gestaltGraph.getLinks([ + 'node', + nodeId, + ])) { const claimIdEncoded = gestaltLink[1].claim.payload.jti; - const claimId = decodeClaimId(claimIdEncoded)!; - if (newestClaimId == null) newestClaimId = claimId - else if (Buffer.compare(newestClaimId, claimId) == -1) { + const claimId = decodeClaimId(claimIdEncoded); + if (claimId == null) never(); + if (newestClaimId == null) { + newestClaimId = claimId; + } else if (Buffer.compare(newestClaimId, claimId) === -1) { newestClaimId = claimId; } } - // The sigChain data of the vertex (containing all cryptolinks) let vertexChainData: Record = {}; try { @@ -283,7 +307,9 @@ class Discovery { } catch (e) { this.visitedVertices.add(encodedGestaltNodeId); this.logger.error( - `Failed to discover ${id} - ${e.toString()}`, + `Failed to discover ${nodesUtils.encodeNodeId( + nodeId, + )} - ${e.toString()}`, ); return; } @@ -303,105 +329,138 @@ class Discovery { for (const signedClaim of Object.values(vertexChainData)) { if (ctx.signal.aborted) throw ctx.signal.reason; switch (signedClaim.payload.typ) { - case 'node': { - // Get the chain data of the linked node - // Could be node1 or node2 in the claim so get the one that's - // not equal to nodeId from above - const node1Id = nodesUtils.decodeNodeId( - signedClaim.payload.iss, - )!; - const node2Id = nodesUtils.decodeNodeId( - signedClaim.payload.sub, - )!; - // Verify the claim - const node1PublicKey = keysUtils.publicKeyFromNodeId(node1Id); - const node2PublicKey = keysUtils.publicKeyFromNodeId(node2Id); - const token = Token.fromSigned(signedClaim); - if ( - !token.verifyWithPublicKey(node1PublicKey) || - !token.verifyWithPublicKey(node2PublicKey) - ) { - this.logger.warn(`Failed to verify node claim between ${signedClaim.payload.iss} and ${signedClaim.payload.sub}`); - continue; - } - const linkedVertexNodeId = node1Id.equals(nodeId) - ? node2Id - : node1Id; - const linkedVertexNodeInfo: GestaltNodeInfo = { - nodeId: linkedVertexNodeId, - }; - await this.gestaltGraph.linkNodeAndNode( - vertexNodeInfo, - linkedVertexNodeInfo, - { - claim: signedClaim as SignedClaim, - meta: {}, + case 'ClaimLinkNode': + { + // Get the chain data of the linked node + // Could be node1 or node2 in the claim so get the one that's + // not equal to nodeId from above + const node1Id = nodesUtils.decodeNodeId(signedClaim.payload.iss); + if (node1Id == null) never(); + const node2Id = nodesUtils.decodeNodeId(signedClaim.payload.sub); + if (node2Id == null) never(); + // Verify the claim + const node1PublicKey = keysUtils.publicKeyFromNodeId(node1Id); + const node2PublicKey = keysUtils.publicKeyFromNodeId(node2Id); + const token = Token.fromSigned(signedClaim); + if ( + !token.verifyWithPublicKey(node1PublicKey) || + !token.verifyWithPublicKey(node2PublicKey) + ) { + this.logger.warn( + `Failed to verify node claim between ${signedClaim.payload.iss} and ${signedClaim.payload.sub}`, + ); + continue; } - ); - // Add this vertex to the queue if it hasn't already been visited - if (!this.visitedVertices.has(gestaltsUtils.encodeGestaltNodeId(['node', linkedVertexNodeId]))) { - await this.scheduleDiscoveryForVertex(['node', linkedVertexNodeId]); - } - } - break; - case 'identity': { - // Checking the claim is valid - const publicKey = keysUtils.publicKeyFromNodeId(nodeId); - const token = Token.fromSigned(signedClaim); - if (!token.verifyWithPublicKey(publicKey)) { - this.logger.warn(`Failed to verify identity claim between ${nodesUtils.encodeNodeId(nodeId)} and ${signedClaim.payload.sub}`); - continue; - } - // Attempt to get the identity info on the identity provider - const timer = - connectionTimeout != null - ? new Timer({ delay: connectionTimeout }) - : undefined; - const [providerId, identityId] = JSON.parse(signedClaim.payload.sub!); - const identityInfo = await this.getIdentityInfo( - providerId, - identityId, - { signal: ctx.signal, timer }, - ); - // If we can't get identity info, simply skip this claim - if (identityInfo == null) { - this.logger.warn(`Failed to get identity info for ${providerId}:${identityId}`); - continue; - } - // Need to get the corresponding claim for this - let providerIdentityClaimId: ProviderIdentityClaimId | null = null; - const identityClaims = await this.verifyIdentityClaims(providerId, identityId) - for (const [id, claim] of Object.entries(identityClaims)) { - const issuerNodeId = nodesUtils.decodeNodeId(claim.payload.iss); - if (issuerNodeId == null) continue; - if (nodeId.equals(issuerNodeId)){ - providerIdentityClaimId = id as ProviderIdentityClaimId; - break; + const linkedVertexNodeId = node1Id.equals(nodeId) + ? node2Id + : node1Id; + const linkedVertexNodeInfo: GestaltNodeInfo = { + nodeId: linkedVertexNodeId, + }; + await this.gestaltGraph.linkNodeAndNode( + vertexNodeInfo, + linkedVertexNodeInfo, + { + claim: signedClaim as SignedClaim, + meta: {}, + }, + ); + // Add this vertex to the queue if it hasn't already been visited + if ( + !this.visitedVertices.has( + gestaltsUtils.encodeGestaltNodeId(['node', linkedVertexNodeId]), + ) + ) { + await this.scheduleDiscoveryForVertex([ + 'node', + linkedVertexNodeId, + ]); } } - if (providerIdentityClaimId == null) { - this.logger.warn(`Failed to get corresponding identity claim for ${providerId}:${identityId}`); - continue; - } - // Link the node to the found identity info - await this.gestaltGraph.linkNodeAndIdentity( - vertexNodeInfo, - identityInfo, - { - claim : signedClaim as SignedClaim, - meta: { - providerIdentityClaimId: providerIdentityClaimId, - url: identityInfo.url + break; + case 'ClaimLinkIdentity': + { + // Checking the claim is valid + const publicKey = keysUtils.publicKeyFromNodeId(nodeId); + const token = Token.fromSigned(signedClaim); + if (!token.verifyWithPublicKey(publicKey)) { + this.logger.warn( + `Failed to verify identity claim between ${nodesUtils.encodeNodeId( + nodeId, + )} and ${signedClaim.payload.sub}`, + ); + continue; + } + // Attempt to get the identity info on the identity provider + const timer = + connectionTimeout != null + ? new Timer({ delay: connectionTimeout }) + : undefined; + if (signedClaim.payload.sub == null) never(); + const [providerId, identityId] = JSON.parse( + signedClaim.payload.sub, + ); + const identityInfo = await this.getIdentityInfo( + providerId, + identityId, + { signal: ctx.signal, timer }, + ); + // If we can't get identity info, simply skip this claim + if (identityInfo == null) { + this.logger.warn( + `Failed to get identity info for ${providerId}:${identityId}`, + ); + continue; + } + // Need to get the corresponding claim for this + let providerIdentityClaimId: ProviderIdentityClaimId | null = null; + const identityClaims = await this.verifyIdentityClaims( + providerId, + identityId, + ); + for (const [id, claim] of Object.entries(identityClaims)) { + const issuerNodeId = nodesUtils.decodeNodeId(claim.payload.iss); + if (issuerNodeId == null) continue; + if (nodeId.equals(issuerNodeId)) { + providerIdentityClaimId = id as ProviderIdentityClaimId; + break; + } + } + if (providerIdentityClaimId == null) { + this.logger.warn( + `Failed to get corresponding identity claim for ${providerId}:${identityId}`, + ); + continue; + } + // Link the node to the found identity info + await this.gestaltGraph.linkNodeAndIdentity( + vertexNodeInfo, + identityInfo, + { + claim: signedClaim as SignedClaim, + meta: { + providerIdentityClaimId: providerIdentityClaimId, + url: identityInfo.url, + }, }, + ); + // Add this identity vertex to the queue if it is not present + const providerIdentityId = JSON.parse(signedClaim.payload.sub!); + if ( + !this.visitedVertices.has( + gestaltsUtils.encodeGestaltIdentityId([ + 'identity', + providerIdentityId, + ]), + ) + ) { + await this.scheduleDiscoveryForVertex([ + 'identity', + providerIdentityId, + ]); } - ); - // Add this identity vertex to the queue if it is not present - const providerIdentityId = JSON.parse(signedClaim.payload.sub!); - if (!this.visitedVertices.has(gestaltsUtils.encodeGestaltIdentityId(['identity', providerIdentityId]))) { - await this.scheduleDiscoveryForVertex(['identity', providerIdentityId]); } - } - break; + break; default: never(); } @@ -409,7 +468,11 @@ class Discovery { this.visitedVertices.add(encodedGestaltNodeId); } - protected async processIdentity(id: ProviderIdentityId, connectionTimeout: number | undefined, ctx: ContextTimed) { + protected async processIdentity( + id: ProviderIdentityId, + connectionTimeout: number | undefined, + ctx: ContextTimed, + ) { // If the next vertex is an identity, perform a social discovery // Firstly get the identity info of this identity const providerIdentityId = id; @@ -435,7 +498,8 @@ class Discovery { if (ctx.signal.aborted) throw ctx.signal.reason; // Claims on an identity provider will always be node -> identity // So just cast payload data as such - const linkedVertexNodeId = nodesUtils.decodeNodeId(claim.payload.node)!; + const linkedVertexNodeId = nodesUtils.decodeNodeId(claim.payload.iss); + if (linkedVertexNodeId == null) never(); // With this verified chain, we can link const linkedVertexNodeInfo = { nodeId: linkedVertexNodeId, @@ -448,15 +512,27 @@ class Discovery { meta: { providerIdentityClaimId: claimId as ProviderIdentityClaimId, url: vertexIdentityInfo.url, - } - } + }, + }, ); // Add this vertex to the queue if it is not present - if (!this.visitedVertices.has(gestaltsUtils.encodeGestaltIdentityId(['identity', providerIdentityId]))) { - await this.scheduleDiscoveryForVertex(['identity', providerIdentityId]); + if ( + !this.visitedVertices.has( + gestaltsUtils.encodeGestaltNodeId([ + 'node', + linkedVertexNodeInfo.nodeId, + ]), + ) + ) { + await this.scheduleDiscoveryForVertex([ + 'node', + linkedVertexNodeInfo.nodeId, + ]); } } - this.visitedVertices.add(gestaltsUtils.encodeGestaltIdentityId(['identity', providerIdentityId])); + this.visitedVertices.add( + gestaltsUtils.encodeGestaltIdentityId(['identity', providerIdentityId]), + ); } /** @@ -506,7 +582,11 @@ class Discovery { const gestaltIdEncoded = gestaltsUtils.encodeGestaltId(vertex); // Locking on vertex to avoid duplicates await tran.lock( - [this.constructor.name, this.discoverVertexHandlerId, gestaltIdEncoded].join(''), + [ + this.constructor.name, + this.discoverVertexHandlerId, + gestaltIdEncoded, + ].join(''), ); // Check if task exists let taskExists = false; @@ -528,7 +608,11 @@ class Discovery { { handlerId: this.discoverVertexHandlerId, parameters: [gestaltIdEncoded], - path: [this.constructor.name, this.discoverVertexHandlerId, gestaltIdEncoded], + path: [ + this.constructor.name, + this.discoverVertexHandlerId, + gestaltIdEncoded, + ], lazy: true, }, tran, @@ -566,11 +650,9 @@ class Discovery { } const authIdentityId = authIdentityIds[0]; // Return the identity data - return await provider.getIdentityData( - authIdentityId, - identityId, - { signal: ctx.signal }, - ); + return await provider.getIdentityData(authIdentityId, identityId, { + signal: ctx.signal, + }); } /** @@ -594,14 +676,22 @@ class Discovery { return {}; } const authIdentityId = authIdentityIds[0]; - const identityClaims: Record> = {}; - for await (const identitySignedClaim of provider.getClaims(authIdentityId, identityId)) { - identitySignedClaim.claim + const identityClaims: Record< + ProviderIdentityClaimId, + SignedClaim + > = {}; + for await (const identitySignedClaim of provider.getClaims( + authIdentityId, + identityId, + )) { + identitySignedClaim.claim; // Claims on an identity provider will always be node -> identity const claim = identitySignedClaim.claim; const data = claim.payload; // Verify the claim with the public key of the node - const publicKey = keysUtils.publicKeyFromNodeId(nodesUtils.decodeNodeId(data.node)!); + const nodeId = nodesUtils.decodeNodeId(data.iss); + if (nodeId == null) never(); + const publicKey = keysUtils.publicKeyFromNodeId(nodeId); const token = Token.fromSigned(claim); // If verified, add to the record if (token.verifyWithPublicKey(publicKey)) { diff --git a/src/gestalts/GestaltGraph.ts b/src/gestalts/GestaltGraph.ts index 6370c483c..7425a5e48 100644 --- a/src/gestalts/GestaltGraph.ts +++ b/src/gestalts/GestaltGraph.ts @@ -1,4 +1,4 @@ -import type { DB, DBIterator, DBTransaction, KeyPath, LevelPath } from '@matrixai/db'; +import type { DB, DBIterator, DBTransaction, LevelPath } from '@matrixai/db'; import type { Gestalt, GestaltAction, @@ -14,19 +14,20 @@ import type { GestaltLinkIdentity, GestaltId, } from './types'; -import { GestaltLinkJSON } from './types'; import type { NodeId, ProviderIdentityId } from '../ids/types'; import type ACL from '../acl/ACL'; +import type { GestaltLinkJSON } from './types'; import Logger from '@matrixai/logger'; -import { CreateDestroyStartStop, ready } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import { + CreateDestroyStartStop, + ready, +} from '@matrixai/async-init/dist/CreateDestroyStartStop'; import { IdInternal } from '@matrixai/id'; import * as gestaltsUtils from './utils'; import * as gestaltsErrors from './errors'; import * as aclUtils from '../acl/utils'; import { never } from '../utils'; -const invalidCombinationError = () => Error('TMP Invalid combination error'); - interface GestaltGraph extends CreateDestroyStartStop {} @CreateDestroyStartStop( new gestaltsErrors.ErrorGestaltsGraphRunning(), @@ -64,25 +65,37 @@ class GestaltGraph { * `GestaltGraph/matrix/{GestaltKey} -> null` * `GestaltGraph/matrix/{GestaltKey}/{GestaltKey} -> {raw(GestaltLinkId)}` */ - public readonly dbMatrixPath: Readonly = [this.constructor.name, 'matrix']; + public readonly dbMatrixPath: Readonly = [ + this.constructor.name, + 'matrix', + ]; /** * Gestalt links. * `GestaltGraph/links/{GestaltLinkId} -> {json(GestaltLink)}` */ - public readonly dbLinksPath: Readonly = [this.constructor.name, 'links']; + public readonly dbLinksPath: Readonly = [ + this.constructor.name, + 'links', + ]; /** * Node information * `GestaltGraph/nodes/{GestaltKey} -> {json(GestaltNodeInfo)}` */ - public readonly dbNodesPath: Readonly = [this.constructor.name, 'nodes']; + public readonly dbNodesPath: Readonly = [ + this.constructor.name, + 'nodes', + ]; /** * Identity information * `GestaltGraph/identities/{GestaltKey} -> {json(GestaltIdentityInfo)}` */ - public readonly dbIdentitiesPath: LevelPath = [this.constructor.name, 'identities']; + public readonly dbIdentitiesPath: LevelPath = [ + this.constructor.name, + 'identities', + ]; protected generateGestaltLinkId: () => GestaltLinkId; @@ -95,7 +108,7 @@ class GestaltGraph { public async start({ fresh = false }: { fresh?: boolean } = {}) { this.logger.info(`Starting ${this.constructor.name}`); if (fresh) { - await this.db.clear(this.dbMatrixPath); + await this.db.clear(this.dbPath); } this.generateGestaltLinkId = gestaltsUtils.createGestaltLinkIdGenerator(); this.logger.info(`Started ${this.constructor.name}`); @@ -129,12 +142,10 @@ class GestaltGraph { return this.db.withTransactionF((tran) => this.setNode(nodeInfo, tran)); } const gestaltNodeId = ['node', nodeInfo.nodeId] as ['node', NodeId]; - const gestaltNodeKey = gestaltsUtils.toGestaltNodeKey( - gestaltNodeId - ); + const gestaltNodeKey = gestaltsUtils.toGestaltNodeKey(gestaltNodeId); const nodeInfoJSON = await tran.get([ ...this.dbNodesPath, - gestaltNodeKey + gestaltNodeKey, ]); if (nodeInfoJSON == null) { // Set the singleton node @@ -166,61 +177,55 @@ class GestaltGraph { } const gestaltIdentityId = [ 'identity', - [identityInfo.providerId, identityInfo.identityId] + [identityInfo.providerId, identityInfo.identityId], ] as ['identity', ProviderIdentityId]; - const gestaltIdentityKey = gestaltsUtils.toGestaltIdentityKey(gestaltIdentityId); + const gestaltIdentityKey = + gestaltsUtils.toGestaltIdentityKey(gestaltIdentityId); const identityInfo_ = await tran.get([ ...this.dbIdentitiesPath, - gestaltIdentityKey + gestaltIdentityKey, ]); if (identityInfo_ == null) { // Set the singleton identity await tran.put([...this.dbMatrixPath, gestaltIdentityKey], null); } // Updates the identity information - await tran.put([...this.dbIdentitiesPath, gestaltIdentityKey], identityInfo); + await tran.put( + [...this.dbIdentitiesPath, gestaltIdentityKey], + identityInfo, + ); return gestaltIdentityId; } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async unsetNode( - nodeId: NodeId, - tran?: DBTransaction, - ): Promise { + public async unsetNode(nodeId: NodeId, tran?: DBTransaction): Promise { if (tran == null) { - return this.db.withTransactionF((tran) => - this.unsetNode(nodeId, tran), - ); + return this.db.withTransactionF((tran) => this.unsetNode(nodeId, tran)); } - // When a vertex is unset, their permissions in the ACL must be deleted, - // and all their links must also be broken. This means you have to iterate - // over all its neighbours and remove those entries in matrix. But you must - // also remove themselves from the matrix if they are a singleton gestalt. - const gestaltNodeKey = gestaltsUtils.toGestaltNodeKey(['node', nodeId]); - // 1. Iterate over all links and delete them - for await (const [keyPath, gestaltLinkIdBuffer] of tran.iterator([...this.dbMatrixPath, gestaltNodeKey], {valueAsBuffer: true})){ - // We want to delete each link but also the reverse link - if (gestaltLinkIdBuffer == null) continue; - const linkedGestaltIdKey = keyPath[keyPath.length - 1] as GestaltKey; - const [type, id] = gestaltsUtils.fromGestaltKey(linkedGestaltIdKey); - switch(type) { - case 'node': - // id goes first since that side of the split gestalt gets its - // permissions updated - await this.unlinkNodeAndNode(id, nodeId, tran); - break; - case 'identity': - await this.unlinkNodeAndIdentity(nodeId, id, tran); - break; - default: - never(); + const gestaltKey1 = gestaltsUtils.toGestaltNodeKey(['node', nodeId]); + // Remove the singleton gestalt if it exists + await tran.del([...this.dbMatrixPath, gestaltKey1]); + // Unlink all neighbours, this will iterate over singletons because it is already removed + for await (const [keyPath, value] of tran.iterator( + [...this.dbMatrixPath, gestaltKey1], + { values: false }, + )) { + if (value == null) continue; + const [, gestaltKey2] = keyPath as [GestaltKey, GestaltKey]; + const gestaltId2 = gestaltsUtils.fromGestaltKey(gestaltKey2); + if (gestaltId2[0] === 'node') { + // The first gestalt preserves the same permission ID + // thes second gestalt gets a new permission ID + await this.unlinkNodeAndNode(gestaltId2[1], nodeId, tran); + } else if (gestaltId2[0] === 'identity') { + await this.unlinkNodeAndIdentity(nodeId, gestaltId2[1], tran); } } - // 2. remove the node information. - await tran.del([...this.dbNodesPath, gestaltNodeKey]); - // 1. unset the permissions for the node in the ACL + // Remove the node information + await tran.del([...this.dbNodesPath, gestaltKey1]); + // Remove the permissions await this.acl.unsetNodePerm(nodeId, tran); - }; + } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async unsetIdentity( @@ -236,14 +241,20 @@ class GestaltGraph { // and all their links must also be broken. This means you have to iterate // over all its neighbours and remove those entries in matrix. But you must // also remove themselves from the matrix if they are a singleton gestalt. - const gestaltIdentityKey = gestaltsUtils.toGestaltIdentityKey(['identity', providerIdentityId]); + const gestaltIdentityKey = gestaltsUtils.toGestaltIdentityKey([ + 'identity', + providerIdentityId, + ]); // 1. Iterate over all links and delete them - for await (const [keyPath, gestaltLinkIdBuffer] of tran.iterator([...this.dbMatrixPath, gestaltIdentityKey], {valueAsBuffer: true})){ + for await (const [keyPath, gestaltLinkIdBuffer] of tran.iterator( + [...this.dbMatrixPath, gestaltIdentityKey], + { valueAsBuffer: true }, + )) { // We want to delete each link but also the reverse link if (gestaltLinkIdBuffer == null) continue; const linkedGestaltIdKey = keyPath[keyPath.length - 1] as GestaltKey; const [type, id] = gestaltsUtils.fromGestaltKey(linkedGestaltIdKey); - switch(type) { + switch (type) { case 'node': await this.unlinkNodeAndIdentity(id, providerIdentityId, tran); break; @@ -254,7 +265,7 @@ class GestaltGraph { } // 2. remove the node information. await tran.del([...this.dbIdentitiesPath, gestaltIdentityKey]); - }; + } // Calls one of `setNode` or `setIdentity` @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) @@ -263,7 +274,7 @@ class GestaltGraph { tran?: DBTransaction, ): Promise { const [type, info] = gestaltInfo; - switch(type) { + switch (type) { case 'node': return this.setNode(info, tran); case 'identity': @@ -271,14 +282,14 @@ class GestaltGraph { default: never(); } - }; + } // Calls one of `unsetNode` or `unsetIdentity` @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public unsetVertex( gestaltId: GestaltId, tran?: DBTransaction, - ): Promise{ + ): Promise { const [type, id] = gestaltId; switch (type) { case 'node': @@ -288,7 +299,7 @@ class GestaltGraph { default: never(); } - }; + } // LINKING AND UNLINKING VERTICES @@ -309,30 +320,25 @@ class GestaltGraph { this.linkNodeAndNode(nodeInfo1, nodeInfo2, linkNode, tran), ); } - if (!gestaltsUtils.checkLinkNodeMatches( - nodeInfo1.nodeId, - nodeInfo2.nodeId, - linkNode.claim.payload - )) { + if ( + !gestaltsUtils.checkLinkNodeMatches( + nodeInfo1.nodeId, + nodeInfo2.nodeId, + linkNode.claim.payload, + ) + ) { throw new gestaltsErrors.ErrorGestaltsGraphLinkNodeMatch(); } - const nodeKey1 = gestaltsUtils.toGestaltNodeKey( - ['node', nodeInfo1.nodeId], - ); - const nodeKey2 = gestaltsUtils.toGestaltNodeKey( - ['node', nodeInfo2.nodeId], - ); + const nodeKey1 = gestaltsUtils.toGestaltNodeKey(['node', nodeInfo1.nodeId]); + const nodeKey2 = gestaltsUtils.toGestaltNodeKey(['node', nodeInfo2.nodeId]); // If they are already connected, only update the link node const gestaltLinkIdBuffer = await tran.get( - [ - ...this.dbMatrixPath, - nodeKey1, - nodeKey2 - ], - true + [...this.dbMatrixPath, nodeKey1, nodeKey2], + true, ); if (gestaltLinkIdBuffer != null) { - const gestaltLinkId = IdInternal.fromBuffer(gestaltLinkIdBuffer); + const gestaltLinkId = + IdInternal.fromBuffer(gestaltLinkIdBuffer); await tran.put( [...this.dbLinksPath, gestaltLinkIdBuffer], [ @@ -340,25 +346,23 @@ class GestaltGraph { { ...linkNode, id: gestaltLinkId, - } - ] + }, + ], ); return gestaltLinkId; } // Check if the node infos are new let nodeNew1 = false; if ( - await tran.get( - [...this.dbNodesPath, nodeKey1] - ) == null + (await tran.get([...this.dbNodesPath, nodeKey1])) == + null ) { nodeNew1 = true; } let nodeNew2 = false; if ( - await tran.get( - [...this.dbNodesPath, nodeKey2] - ) == null + (await tran.get([...this.dbNodesPath, nodeKey2])) == + null ) { nodeNew2 = true; } @@ -384,29 +388,20 @@ class GestaltGraph { } else if (!nodeNew1 && !nodeNew2) { // Get the gestalt for node 2 const gestalt2 = (await this.getGestaltByKey(nodeKey1, undefined, tran))!; - const nodeIds2 = Object.keys(gestalt2.nodes).map((gestaltNodeIdEncoded) => { - return gestaltsUtils.decodeGestaltNodeId(gestaltNodeIdEncoded)![1]; - }); + const nodeIds2 = Object.keys(gestalt2.nodes).map( + (gestaltNodeIdEncoded) => { + return gestaltsUtils.decodeGestaltNodeId(gestaltNodeIdEncoded)![1]; + }, + ); // If the nodes exist in the gestalt, they must exist in the ACL - const nodePerm1 = (await this.acl.getNodePerm( - nodeInfo1.nodeId, - tran, - ))!; - const nodePerm2 = (await this.acl.getNodePerm( - nodeInfo2.nodeId, - tran, - ))!; + const nodePerm1 = (await this.acl.getNodePerm(nodeInfo1.nodeId, tran))!; + const nodePerm2 = (await this.acl.getNodePerm(nodeInfo2.nodeId, tran))!; // Union the perms together const permNew = aclUtils.permUnion(nodePerm1, nodePerm2); // Join node 2's gestalt permission with node 1 // Node 1's gestalt permission is updated with the // union of both gestalt's permissions - await this.acl.joinNodePerm( - nodeInfo1.nodeId, - nodeIds2, - permNew, - tran - ); + await this.acl.joinNodePerm(nodeInfo1.nodeId, nodeIds2, permNew, tran); } else if (nodeNew1 && !nodeNew2) { await this.acl.joinNodePerm( nodeInfo2.nodeId, @@ -432,19 +427,19 @@ class GestaltGraph { { ...linkNode, id: gestaltLinkIdNew, - } - ] + }, + ], ); // Link the nodes together await tran.put( [...this.dbMatrixPath, nodeKey1, nodeKey2], gestaltLinkIdBufferNew, - true + true, ); await tran.put( [...this.dbMatrixPath, nodeKey2, nodeKey1], gestaltLinkIdBufferNew, - true + true, ); // Remove any singleton entries await tran.del([...this.dbMatrixPath, nodeKey1]); @@ -467,26 +462,28 @@ class GestaltGraph { this.linkNodeAndIdentity(nodeInfo, identityInfo, linkIdentity, tran), ); } - if (!gestaltsUtils.checkLinkIdentityMatches( - nodeInfo.nodeId, - [identityInfo.providerId, identityInfo.identityId], - linkIdentity.claim.payload - )) { + if ( + !gestaltsUtils.checkLinkIdentityMatches( + nodeInfo.nodeId, + [identityInfo.providerId, identityInfo.identityId], + linkIdentity.claim.payload, + ) + ) { throw new gestaltsErrors.ErrorGestaltsGraphLinkIdentityMatch(); } - const nodeKey = gestaltsUtils.toGestaltNodeKey(['node', nodeInfo.nodeId]) - const identityKey = gestaltsUtils.toGestaltIdentityKey(['identity', [identityInfo.providerId, identityInfo.identityId]]) + const nodeKey = gestaltsUtils.toGestaltNodeKey(['node', nodeInfo.nodeId]); + const identityKey = gestaltsUtils.toGestaltIdentityKey([ + 'identity', + [identityInfo.providerId, identityInfo.identityId], + ]); // If they are already connected, only update the link identity const gestaltLinkIdBuffer = await tran.get( - [ - ...this.dbMatrixPath, - nodeKey, - identityKey - ], - true + [...this.dbMatrixPath, nodeKey, identityKey], + true, ); if (gestaltLinkIdBuffer != null) { - const gestaltLinkId = IdInternal.fromBuffer(gestaltLinkIdBuffer); + const gestaltLinkId = + IdInternal.fromBuffer(gestaltLinkIdBuffer); await tran.put( [...this.dbLinksPath, gestaltLinkIdBuffer], [ @@ -494,21 +491,23 @@ class GestaltGraph { { ...linkIdentity, id: gestaltLinkId, - } - ] + }, + ], ); return gestaltLinkId; } // Check if the infos are new let nodeNew = false; if ( - await tran.get( - [...this.dbNodesPath, nodeKey] - ) == null + (await tran.get([...this.dbNodesPath, nodeKey])) == + null ) { nodeNew = true; } - let identityLinkedNodeId = await this.getIdentityLinkedNodeId([identityInfo.providerId, identityInfo.identityId], tran) + const identityLinkedNodeId = await this.getIdentityLinkedNodeId( + [identityInfo.providerId, identityInfo.identityId], + tran, + ); // ACL changes depend on the situation: // If the node and identity is new // then the node needs a new permission @@ -531,14 +530,13 @@ class GestaltGraph { } else if (!nodeNew && identityLinkedNodeId != null) { // Get the gestalt for node 2 const gestalt2 = (await this.getGestaltByKey(nodeKey, undefined, tran))!; - const nodeIds2 = Object.keys(gestalt2.nodes).map((gestaltNodeIdEncoded) => { - return gestaltsUtils.decodeGestaltNodeId(gestaltNodeIdEncoded)![1]; - }); + const nodeIds2 = Object.keys(gestalt2.nodes).map( + (gestaltNodeIdEncoded) => { + return gestaltsUtils.decodeGestaltNodeId(gestaltNodeIdEncoded)![1]; + }, + ); // If the nodes exist in the gestalt, they must exist in the ACL - const nodePerm1 = (await this.acl.getNodePerm( - nodeInfo.nodeId, - tran, - ))!; + const nodePerm1 = (await this.acl.getNodePerm(nodeInfo.nodeId, tran))!; const nodePerm2 = (await this.acl.getNodePerm( identityLinkedNodeId, tran, @@ -548,12 +546,7 @@ class GestaltGraph { // Join node 2's gestalt permission with node 1 // Node 1's gestalt permission is updated with the // union of both gestalt's permissions - await this.acl.joinNodePerm( - nodeInfo.nodeId, - nodeIds2, - permNew, - tran - ); + await this.acl.joinNodePerm(nodeInfo.nodeId, nodeIds2, permNew, tran); } else if (nodeNew && identityLinkedNodeId != null) { await this.acl.joinNodePerm( identityLinkedNodeId, @@ -575,30 +568,47 @@ class GestaltGraph { { ...linkIdentity, id: gestaltLinkIdNew, - } - ] + }, + ], ); // Link the node and identity together await tran.put( [...this.dbMatrixPath, nodeKey, identityKey], gestaltLinkIdBufferNew, - true + true, ); await tran.put( [...this.dbMatrixPath, identityKey, nodeKey], gestaltLinkIdBufferNew, - true + true, ); // Remove any singleton entries await tran.del([...this.dbMatrixPath, nodeKey]); await tran.del([...this.dbMatrixPath, identityKey]); // Upsert the node and identity info await tran.put([...this.dbNodesPath, nodeKey], nodeInfo); - await tran.put([...this.dbNodesPath, identityKey], identityInfo); + await tran.put([...this.dbIdentitiesPath, identityKey], identityInfo); return gestaltLinkIdNew; - }; + } - // Overloaded version of linkNodeAndNode and linkNodeAndIdentity + public linkVertexAndVertex( + gestaltInfo1: ['node', GestaltNodeInfo], + gestaltInfo2: ['node', GestaltNodeInfo], + link: ['node', Omit], + tran?: DBTransaction, + ): Promise; + public linkVertexAndVertex( + gestaltInfo1: ['node', GestaltNodeInfo], + gestaltInfo2: ['identity', GestaltIdentityInfo], + link: ['identity', Omit], + tran?: DBTransaction, + ): Promise; + public linkVertexAndVertex( + gestaltInfo1: ['identity', GestaltIdentityInfo], + gestaltInfo2: ['node', GestaltNodeInfo], + link: ['identity', Omit], + tran?: DBTransaction, + ): Promise; @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public linkVertexAndVertex( gestaltInfo1: GestaltInfo, @@ -606,30 +616,31 @@ class GestaltGraph { link: [GestaltLink[0], Omit], tran?: DBTransaction, ): Promise { - const [type1, info1] = gestaltInfo1; - const [type2, info2] = gestaltInfo2; - const [type3, linkInfo] = link; - - // Keeping the switch flat with implicit typing here doesn't work, - // so we need to use enforce the types here - switch (`${type1}-${type2}-${type3}`) { - case 'node-node-node': - return this.linkNodeAndNode(info1 as GestaltNodeInfo, info2 as GestaltNodeInfo, linkInfo as Omit, tran); - case 'node-identity-identity': - return this.linkNodeAndIdentity(info1 as GestaltNodeInfo, info2 as GestaltIdentityInfo, linkInfo as Omit, tran); - case 'identity-node-identity': - return this.linkNodeAndIdentity(info2 as GestaltNodeInfo, info1 as GestaltIdentityInfo, linkInfo as Omit, tran); - // These are not valid - case 'identity-identity-identity': - case 'identity-identity-node': - case 'node-node-identity': - case 'node-identity-node': - case 'identity-node-node': - throw invalidCombinationError(); - default: - never(); + if (gestaltInfo1[0] === 'node' && gestaltInfo2[0] === 'node') { + return this.linkNodeAndNode( + gestaltInfo1[1], + gestaltInfo2[1], + link[1] as Omit, + tran, + ); + } else if (gestaltInfo1[0] === 'node' && gestaltInfo2[0] === 'identity') { + return this.linkNodeAndIdentity( + gestaltInfo1[1], + gestaltInfo2[1], + link[1] as Omit, + tran, + ); + } else if (gestaltInfo1[0] === 'identity' && gestaltInfo2[0] === 'node') { + return this.linkNodeAndIdentity( + gestaltInfo2[1], + gestaltInfo1[1], + link[1] as Omit, + tran, + ); + } else { + never(); } - }; + } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async unlinkNodeAndNode( @@ -652,39 +663,50 @@ class GestaltGraph { const nodeKey1 = gestaltsUtils.toGestaltNodeKey(['node', nodeId1]); const nodeKey2 = gestaltsUtils.toGestaltNodeKey(['node', nodeId2]); // Checking if the vertices exist - if (await tran.get([...this.dbNodesPath, nodeKey1], true) == null) return; - if (await tran.get([...this.dbNodesPath, nodeKey2], true) == null) return; + if ((await tran.get([...this.dbNodesPath, nodeKey1], true)) == null) return; + if ((await tran.get([...this.dbNodesPath, nodeKey2], true)) == null) return; // Checking if the link exists - const linkId = await tran.get([...this.dbMatrixPath, nodeKey1, nodeKey2], true); + const linkId = await tran.get( + [...this.dbMatrixPath, nodeKey1, nodeKey2], + true, + ); if (linkId == null) return; // Remove the link await tran.del([...this.dbLinksPath, linkId]); await tran.del([...this.dbMatrixPath, nodeKey1, nodeKey2]); await tran.del([...this.dbMatrixPath, nodeKey2, nodeKey1]); - // we check this by iterating over the links in the matrix. + // We check this by iterating over the links in the matrix. let nodeNeighbors1 = false; - for await (const _ of tran.iterator([...this.dbMatrixPath, nodeKey1], {limit: 1})){ + for await (const _ of tran.iterator([...this.dbMatrixPath, nodeKey1], { + limit: 1, + })) { nodeNeighbors1 = true; } // Set as a singleton if (!nodeNeighbors1) await tran.put([...this.dbMatrixPath, nodeKey1], null); let nodeNeighbors2 = false; - for await (const _ of tran.iterator([...this.dbMatrixPath, nodeKey2], {limit: 1})){ + for await (const _ of tran.iterator([...this.dbMatrixPath, nodeKey2], { + limit: 1, + })) { nodeNeighbors2 = true; } // Set as a singleton if (!nodeNeighbors2) await tran.put([...this.dbMatrixPath, nodeKey2], null); // Check if the gestalt was split in two const gestalt = (await this.getGestaltByKey(nodeKey1, undefined, tran))!; - const nodeKeyEncoded2 = gestaltsUtils.encodeGestaltNodeId(['node', nodeId2]); + const nodeKeyEncoded2 = gestaltsUtils.encodeGestaltNodeId([ + 'node', + nodeId2, + ]); // If the nodes are part of the same gestalt then do nothing to the permissions if (gestalt.nodes[nodeKeyEncoded2] != null) return; // Need to copy the ACL permissions between the two gestalts - const nodeIds = Object.keys(gestalt.nodes) - .map(nodeIdEncoded => gestaltsUtils.decodeGestaltNodeId(nodeIdEncoded)![1]) - const perm = (await this.acl.getNodePerm(nodeId1))! + const nodeIds = Object.keys(gestalt.nodes).map( + (nodeIdEncoded) => gestaltsUtils.decodeGestaltNodeId(nodeIdEncoded)![1], + ); + const perm = (await this.acl.getNodePerm(nodeId1))!; await this.acl.setNodesPerm(nodeIds, perm, tran); - }; + } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async unlinkNodeAndIdentity( @@ -705,12 +727,22 @@ class GestaltGraph { // 4. check if the gestalt splits into two separate gestalts and copy the // permissions between them. const nodeKey = gestaltsUtils.toGestaltNodeKey(['node', nodeId]); - const identityKey = gestaltsUtils.toGestaltIdentityKey(['identity', providerIdentityId]); - // Checking if the verticies exist - if (await tran.get([...this.dbNodesPath, nodeKey], true) == null) return; - if (await tran.get([...this.dbIdentitiesPath, identityKey], true) == null) return; + const identityKey = gestaltsUtils.toGestaltIdentityKey([ + 'identity', + providerIdentityId, + ]); + // Checking if the vertices exist + if ((await tran.get([...this.dbNodesPath, nodeKey], true)) == null) return; + if ( + (await tran.get([...this.dbIdentitiesPath, identityKey], true)) == null + ) { + return; + } // Checking if the link exists - const linkId = await tran.get([...this.dbMatrixPath, nodeKey, identityKey], true); + const linkId = await tran.get( + [...this.dbMatrixPath, nodeKey, identityKey], + true, + ); if (linkId == null) return; // Remove the link await tran.del([...this.dbLinksPath, linkId]); @@ -718,18 +750,26 @@ class GestaltGraph { await tran.del([...this.dbMatrixPath, identityKey, nodeKey]); // Check if the gestalt was split in two const gestalt = (await this.getGestaltByKey(nodeKey, undefined, tran))!; - const identityKeyId = gestaltsUtils.encodeGestaltIdentityId(['identity', providerIdentityId]); + const identityKeyId = gestaltsUtils.encodeGestaltIdentityId([ + 'identity', + providerIdentityId, + ]); // If the nodes are part of the same gestalt then do nothing to the permissions if (gestalt.identities[identityKeyId] != null) return; // Check if the vertices should be singletons now. // we check this by iterating over the links in the matrix. let nodeNeighbors = false; - for await (const _ of tran.iterator([...this.dbMatrixPath, nodeKey], {limit: 1})){ + for await (const _ of tran.iterator([...this.dbMatrixPath, nodeKey], { + limit: 1, + })) { nodeNeighbors = true; } // Set as a singleton if (!nodeNeighbors) await tran.put([...this.dbMatrixPath, nodeKey], null); - const identityLinkedNode = await this.getIdentityLinkedNodeId(providerIdentityId, tran); + const identityLinkedNode = await this.getIdentityLinkedNodeId( + providerIdentityId, + tran, + ); // If the identity is a singleton now // Then there is no need to update permissions if (identityLinkedNode == null) { @@ -737,13 +777,29 @@ class GestaltGraph { return; } // Need to copy the ACL permissions between the two gestalts - const nodeIds = Object.keys(gestalt.nodes) - .map(nodeIdEncoded => gestaltsUtils.decodeGestaltNodeId(nodeIdEncoded)![1]) - const perm = (await this.acl.getNodePerm(identityLinkedNode))! + const nodeIds = Object.keys(gestalt.nodes).map( + (nodeIdEncoded) => gestaltsUtils.decodeGestaltNodeId(nodeIdEncoded)![1], + ); + const perm = (await this.acl.getNodePerm(identityLinkedNode))!; await this.acl.setNodesPerm(nodeIds, perm, tran); - }; + } - // Overlaoded version of unlinkNodeAndNode and unlinkNodeAndIdentity + // Overloaded version of unlinkNodeAndNode and unlinkNodeAndIdentity + public unlinkVertexAndVertex( + gestaltId1: ['node', NodeId], + gestaltId2: ['node', NodeId], + tran?: DBTransaction, + ): Promise; + public unlinkVertexAndVertex( + gestaltId1: ['node', NodeId], + gestaltId2: ['identity', ProviderIdentityId], + tran?: DBTransaction, + ): Promise; + public unlinkVertexAndVertex( + gestaltId1: ['identity', ProviderIdentityId], + gestaltId2: ['node', NodeId], + tran?: DBTransaction, + ): Promise; @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public unlinkVertexAndVertex( gestaltId1: GestaltId, @@ -752,46 +808,57 @@ class GestaltGraph { ): Promise { const [type1, info1] = gestaltId1; const [type2, info2] = gestaltId2; - switch(`${type1}-${type2}`) { - case 'node-node': - return this.unlinkNodeAndNode(info1 as NodeId, info2 as NodeId, tran); - case 'node-identity': - return this.unlinkNodeAndIdentity(info1 as NodeId, info2 as ProviderIdentityId, tran); - case 'identity-node': - return this.unlinkNodeAndIdentity(info2 as NodeId, info1 as ProviderIdentityId, tran); - case 'identity-identity': - throw invalidCombinationError(); - default: - never(); + if (type1 === 'node' && type2 === 'node') { + return this.unlinkNodeAndNode(info1 as NodeId, info2 as NodeId, tran); + } else if (type1 === 'node' && type2 === 'identity') { + return this.unlinkNodeAndIdentity( + info1 as NodeId, + info2 as ProviderIdentityId, + tran, + ); + } else if (type1 === 'identity' && type2 === 'node') { + return this.unlinkNodeAndIdentity( + info2 as NodeId, + info1 as ProviderIdentityId, + tran, + ); + } else { + never(); } - }; + } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async getGestaltActions( gestaltId: GestaltId, - tran?: DBTransaction - ): Promise{ + tran?: DBTransaction, + ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - this.getGestaltActions(gestaltId, tran) - ) + this.getGestaltActions(gestaltId, tran), + ); } const [type, id] = gestaltId; const gestaltKey = gestaltsUtils.toGestaltKey(gestaltId); switch (type) { - case 'node':{ - if (await tran.get([...this.dbNodesPath, gestaltKey], true) == null) return; + case 'node': { + if ((await tran.get([...this.dbNodesPath, gestaltKey], true)) == null) { + return {}; + } const perm = await this.acl.getNodePerm(id, tran); - if (perm == null) return; + if (perm == null) return {}; return perm.gestalt; } - case 'identity':{ - if (await tran.get([...this.dbIdentitiesPath, gestaltKey], true) == null) return; + case 'identity': { + if ( + (await tran.get([...this.dbIdentitiesPath, gestaltKey], true)) == null + ) { + return {}; + } const linkedNodeId = await this.getIdentityLinkedNodeId(id, tran); - if (linkedNodeId == null) return; + if (linkedNodeId == null) return {}; const perm = await this.acl.getNodePerm(linkedNodeId, tran); - if (perm == null) return; + if (perm == null) return {}; return perm.gestalt; } default: @@ -800,29 +867,37 @@ class GestaltGraph { } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async setGestaltActions( + public async setGestaltAction( gestaltId: GestaltId, action: GestaltAction, - tran?: DBTransaction - ): Promise{ + tran?: DBTransaction, + ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - this.setGestaltActions(gestaltId, action, tran) - ) + this.setGestaltAction(gestaltId, action, tran), + ); } const [type, id] = gestaltId; const gestaltKey = gestaltsUtils.toGestaltKey(gestaltId); switch (type) { - case 'node':{ - if (await tran.get([...this.dbNodesPath, gestaltKey], true) == null) throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + case 'node': { + if ((await tran.get([...this.dbNodesPath, gestaltKey], true)) == null) { + throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + } await this.acl.setNodeAction(id, action, tran); return; } - case 'identity':{ - if (await tran.get([...this.dbIdentitiesPath, gestaltKey], true) == null) throw new gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing(); + case 'identity': { + if ( + (await tran.get([...this.dbIdentitiesPath, gestaltKey], true)) == null + ) { + throw new gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing(); + } const linkedNodeId = await this.getIdentityLinkedNodeId(id, tran); - if (linkedNodeId == null) throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + if (linkedNodeId == null) { + throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + } await this.acl.setNodeAction(linkedNodeId, action, tran); return; } @@ -832,29 +907,37 @@ class GestaltGraph { } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) - public async unsetGestaltActions( + public async unsetGestaltAction( gestaltId: GestaltId, action: GestaltAction, - tran?: DBTransaction - ): Promise{ + tran?: DBTransaction, + ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - this.unsetGestaltActions(gestaltId, action, tran) - ) + this.unsetGestaltAction(gestaltId, action, tran), + ); } const [type, id] = gestaltId; const gestaltKey = gestaltsUtils.toGestaltKey(gestaltId); switch (type) { - case 'node':{ - if (await tran.get([...this.dbNodesPath, gestaltKey], true) == null) throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + case 'node': { + if ((await tran.get([...this.dbNodesPath, gestaltKey], true)) == null) { + throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + } await this.acl.unsetNodeAction(id, action, tran); return; } - case 'identity':{ - if (await tran.get([...this.dbIdentitiesPath, gestaltKey], true) == null) throw new gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing(); + case 'identity': { + if ( + (await tran.get([...this.dbIdentitiesPath, gestaltKey], true)) == null + ) { + throw new gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing(); + } const linkedNodeId = await this.getIdentityLinkedNodeId(id, tran); - if (linkedNodeId == null) throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + if (linkedNodeId == null) { + throw new gestaltsErrors.ErrorGestaltsGraphNodeIdMissing(); + } await this.acl.unsetNodeAction(linkedNodeId, action, tran); return; } @@ -868,16 +951,13 @@ class GestaltGraph { @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async *getGestalts(tran?: DBTransaction): AsyncGenerator { if (tran == null) { - return yield* this.db.withTransactionG((tran) => - this.getGestalts(tran), - ); + return yield* this.db.withTransactionG((tran) => this.getGestalts(tran)); } const visited: Set = new Set(); let lastGestaltKey: GestaltKey | null = null; - for await (const [[gestaltKey]] of tran.iterator( - this.dbMatrixPath, - { values: false } - ) as DBIterator<[GestaltKey], undefined>) { + for await (const [[gestaltKey]] of tran.iterator(this.dbMatrixPath, { + values: false, + }) as DBIterator<[GestaltKey], undefined>) { if (lastGestaltKey == null) { lastGestaltKey = gestaltKey; } @@ -894,21 +974,17 @@ class GestaltGraph { visited.delete(lastGestaltKey.toString('binary')); lastGestaltKey = gestaltKey; } - const gestalt = (await this.getGestaltByKey( - gestaltKey, - visited, - tran - ))!; + const gestalt = (await this.getGestaltByKey(gestaltKey, visited, tran))!; yield gestalt; } } public async getGestalt( gestaltId: GestaltId, - tran?: DBTransaction + tran?: DBTransaction, ): Promise { const [type, id] = gestaltId; - switch(type) { + switch (type) { case 'node': return await this.getGestaltByNode(id, tran); case 'identity': @@ -916,7 +992,7 @@ class GestaltGraph { default: never(); } - }; + } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async getGestaltByNode( @@ -942,7 +1018,10 @@ class GestaltGraph { this.getGestaltByIdentity(providerIdentityId, tran), ); } - const identityKey = gestaltsUtils.toGestaltKey(['identity', providerIdentityId]); + const identityKey = gestaltsUtils.toGestaltKey([ + 'identity', + providerIdentityId, + ]); return this.getGestaltByKey(identityKey, undefined, tran); } @@ -955,10 +1034,13 @@ class GestaltGraph { return this.db.withTransactionF((tran) => this.getNode(nodeId, tran)); } const gestaltNodeKey = gestaltsUtils.toGestaltNodeKey(['node', nodeId]); - const gestaltNodeInfoJSON = await tran.get([...this.dbNodesPath, gestaltNodeKey]); + const gestaltNodeInfoJSON = await tran.get([ + ...this.dbNodesPath, + gestaltNodeKey, + ]); if (gestaltNodeInfoJSON == null) return; return gestaltsUtils.fromGestaltNodeInfoJSON(gestaltNodeInfoJSON); - }; + } @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async getIdentity( @@ -966,13 +1048,21 @@ class GestaltGraph { tran?: DBTransaction, ): Promise { if (tran == null) { - return this.db.withTransactionF((tran) => this.getIdentity(providerIdentityId, tran)); + return this.db.withTransactionF((tran) => + this.getIdentity(providerIdentityId, tran), + ); } - const gestaltIdentityKey = gestaltsUtils.toGestaltIdentityKey(['identity', providerIdentityId]); - return await tran.get([...this.dbNodesPath, gestaltIdentityKey]); - }; + const gestaltIdentityKey = gestaltsUtils.toGestaltIdentityKey([ + 'identity', + providerIdentityId, + ]); + return await tran.get([ + ...this.dbIdentitiesPath, + gestaltIdentityKey, + ]); + } -// Overloaded getVertex + // Overloaded getVertex public async getVertex( gestaltId: ['node', NodeId], @@ -982,19 +1072,23 @@ class GestaltGraph { gestaltId: ['identity', ProviderIdentityId], tran?: DBTransaction, ): Promise<['identity', GestaltIdentityInfo] | undefined>; + public async getVertex( + gestaltId: GestaltId, + tran?: DBTransaction, + ): Promise; @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async getVertex( gestaltId: GestaltId, tran?: DBTransaction, - ): Promise{ + ): Promise { const [type, id] = gestaltId; - switch(type) { - case 'node':{ + switch (type) { + case 'node': { const gestaltNodeInfo = await this.getNode(id, tran); if (gestaltNodeInfo == null) return; return ['node', gestaltNodeInfo]; } - case 'identity':{ + case 'identity': { const gestaltIdentityInfo = await this.getIdentity(id, tran); if (gestaltIdentityInfo == null) return; return ['identity', gestaltIdentityInfo]; @@ -1002,9 +1096,22 @@ class GestaltGraph { default: never(); } - }; + } -// Overloaded getLink + public async getLinkById( + linkId: GestaltLinkId, + tran?: DBTransaction, + ): Promise { + if (tran == null) { + return this.db.withTransactionF((tran) => this.getLinkById(linkId, tran)); + } + const gestaltLinkJSON = await tran.get([ + ...this.dbLinksPath, + linkId.toBuffer(), + ]); + if (gestaltLinkJSON == null) return; + return gestaltsUtils.fromGestaltLinkJSON(gestaltLinkJSON); + } public async getLink( gestaltId1: ['node', NodeId], @@ -1029,52 +1136,48 @@ class GestaltGraph { ): Promise { if (tran == null) { return this.db.withTransactionF((tran) => - // @ts-ignore: Recursive type funzies - this.getLink(gestaltId1, gestaltId2, tran) - ) - } - - const [type1, id1] = gestaltId1; - const [type2, id2] = gestaltId2; - if (type1 === 'identity' && type2 === 'identity') throw invalidCombinationError(); - // checking for invalid types - switch (`${type1}-${type2}`) { - case 'node-node': - case 'node-identity': - case 'identity-node': - break; - default: - never(); + this.getLink(gestaltId1 as any, gestaltId2 as any, tran), + ); } const gestaltKey1 = gestaltsUtils.toGestaltKey(gestaltId1); const gestaltKey2 = gestaltsUtils.toGestaltKey(gestaltId2); - // Getting link Id. - const linkIdBuffer = await tran.get([...this.dbMatrixPath, gestaltKey1, gestaltKey2], true) + const linkIdBuffer = await tran.get( + [...this.dbMatrixPath, gestaltKey1, gestaltKey2], + true, + ); if (linkIdBuffer == null) return; - const gestaltLinkJSON = await tran.get([...this.dbLinksPath, linkIdBuffer]); - if (gestaltLinkJSON == null) return; + const gestaltLinkJSON = (await tran.get([ + ...this.dbLinksPath, + linkIdBuffer, + ]))!; return gestaltsUtils.fromGestaltLinkJSON(gestaltLinkJSON); - }; + } - public async getLinks( + public async *getLinks( gestaltId: GestaltId, tran?: DBTransaction, - ): Promise> { + ): AsyncGenerator<[GestaltId, GestaltLink]> { if (tran == null) { - return this.db.withTransactionF((tran) => + return yield* this.db.withTransactionG((tran) => this.getLinks(gestaltId, tran), - ) + ); } const gestaltKey = gestaltsUtils.toGestaltKey(gestaltId); - const results: Array<[GestaltId, GestaltLink]> = []; - for await (const [keyPath ,gestaltLinkJson] of tran.iterator([...this.dbMatrixPath, gestaltKey], {valueAsBuffer: false})) { + for await (const [keyPath, gestaltLinkId] of tran.iterator( + [...this.dbMatrixPath, gestaltKey], + { valueAsBuffer: true }, + )) { + if (gestaltLinkId == null) continue; + const gestaltLinkJson = await tran.get([ + ...this.dbLinksPath, + gestaltLinkId, + ]); if (gestaltLinkJson == null) continue; const gestaltLink = gestaltsUtils.fromGestaltLinkJSON(gestaltLinkJson); const linkedGestaltKey = keyPath[keyPath.length - 1] as GestaltKey; const linkedGestaltId = gestaltsUtils.fromGestaltKey(linkedGestaltKey); - results.push([linkedGestaltId, gestaltLink]); + yield [linkedGestaltId, gestaltLink]; } - return results; } /** @@ -1090,17 +1193,23 @@ class GestaltGraph { protected async getGestaltByKey( gestaltKey: GestaltKey, visited: Set = new Set(), - tran: DBTransaction + tran: DBTransaction, ): Promise { - const nodeInfoJSON = await tran.get([...this.dbNodesPath, gestaltKey]); - const identityInfo = await tran.get([...this.dbIdentitiesPath, gestaltKey]); + const nodeInfoJSON = await tran.get([ + ...this.dbNodesPath, + gestaltKey, + ]); + const identityInfo = await tran.get([ + ...this.dbIdentitiesPath, + gestaltKey, + ]); if (nodeInfoJSON == null && identityInfo == null) { return; } const gestalt = { matrix: {}, nodes: {}, - identities: {} + identities: {}, }; const queue = [gestaltKey]; visited.add(gestaltKey.toString('binary')); @@ -1113,29 +1222,36 @@ class GestaltGraph { const gestaltIdEncoded = gestaltsUtils.encodeGestaltId(gestaltId); // Process the vertex's node info or identity info if (gestaltId[0] === 'node') { - const gestaltNodeInfoJSON = (await tran.get( - [...this.dbNodesPath, gestaltKey], - ))!; - gestalt.nodes[gestaltIdEncoded] = gestaltsUtils.fromGestaltNodeInfoJSON(gestaltNodeInfoJSON); + const gestaltNodeInfoJSON = await tran.get([ + ...this.dbNodesPath, + gestaltKey, + ]); + // Skipping if it doesn't exist + if (gestaltNodeInfoJSON == null) continue; + gestalt.nodes[gestaltIdEncoded] = + gestaltsUtils.fromGestaltNodeInfoJSON(gestaltNodeInfoJSON); } else if (gestaltId[0] === 'identity') { - gestalt.identities[gestaltIdEncoded] = (await tran.get( - [...this.dbIdentitiesPath, gestaltKey], - ))!; + const gestaltIdentityInfo = await tran.get([ + ...this.dbIdentitiesPath, + gestaltKey, + ]); + // Skipping if it doesn't exist + if (gestaltIdentityInfo == null) continue; + gestalt.identities[gestaltIdEncoded] = gestaltIdentityInfo; } // Singleton gestalts will just have an empty record gestalt.matrix[gestaltIdEncoded] ??= {}; for await (const [ [gestaltKeyNeighbour], - gestaltLinkIdBuffer - ] of tran.iterator( - [...this.dbMatrixPath, gestaltKey] - ) as DBIterator, Buffer>) { - const gestaltIdNeighbour = gestaltsUtils.fromGestaltKey( - gestaltKeyNeighbour - ); - const gestaltIdEncodedNeighbour = gestaltsUtils.encodeGestaltId( - gestaltIdNeighbour - ); + gestaltLinkIdBuffer, + ] of tran.iterator([...this.dbMatrixPath, gestaltKey]) as DBIterator< + Array, + Buffer + >) { + const gestaltIdNeighbour = + gestaltsUtils.fromGestaltKey(gestaltKeyNeighbour); + const gestaltIdEncodedNeighbour = + gestaltsUtils.encodeGestaltId(gestaltIdNeighbour); // Skip processing neighbours that have already been processed if ( gestalt.matrix[gestaltIdEncoded][gestaltIdEncodedNeighbour] != null @@ -1145,10 +1261,12 @@ class GestaltGraph { gestalt.matrix[gestaltIdEncodedNeighbour] ??= {}; const gestaltLink = (await tran.get([ ...this.dbLinksPath, - gestaltLinkIdBuffer + gestaltLinkIdBuffer, ]))!; - gestalt.matrix[gestaltIdEncoded][gestaltIdEncodedNeighbour] = gestaltLink; - gestalt.matrix[gestaltIdEncodedNeighbour][gestaltIdEncoded] = gestaltLink; + gestalt.matrix[gestaltIdEncoded][gestaltIdEncodedNeighbour] = + gestaltLink; + gestalt.matrix[gestaltIdEncodedNeighbour][gestaltIdEncoded] = + gestaltLink; // Only queue the vertexes that aren't already queued if (!visited.has(gestaltKeyNeighbour.toString('binary'))) { queue.push(gestaltKeyNeighbour); @@ -1159,29 +1277,19 @@ class GestaltGraph { return gestalt; } - private async getIdentityLinkedNodeId( + protected async getIdentityLinkedNodeId( providerIdentityId: ProviderIdentityId, - tran: DBTransaction - ): Promise { - const identityKey = gestaltsUtils.toGestaltIdentityKey(['identity', providerIdentityId]) - if (await tran.get( - [...this.dbIdentitiesPath, identityKey] + tran: DBTransaction, + ): Promise { + let nodeId: NodeId | undefined; + for await (const [gestaltId] of this.getLinks( + ['identity', providerIdentityId], + tran, )) { - // We need fo find a node linked to it - let linkId: Buffer | null = null; - let linkPath: KeyPath | null = null; - for await (const [keyPath, linkId_] of tran.iterator([...this.dbMatrixPath, identityKey], {limit: 1, valueAsBuffer: true})){ - linkId = linkId_; - linkPath = keyPath - } - if (linkPath != null ) { - const gestaltkey = linkPath[linkPath.length - 1] as GestaltKey; - const [type, id] = gestaltsUtils.fromGestaltKey(gestaltkey); - if (type === 'node'){ - return id; - } - } + // Return the first NodeId + if (gestaltId[0] === 'node') nodeId = gestaltId[1]; } + return nodeId; } } diff --git a/src/gestalts/errors.ts b/src/gestalts/errors.ts index 96be39e22..b05a63cfe 100644 --- a/src/gestalts/errors.ts +++ b/src/gestalts/errors.ts @@ -28,12 +28,14 @@ class ErrorGestaltsGraphIdentityIdMissing extends ErrorGestalts { } class ErrorGestaltsGraphLinkNodeMatch extends ErrorGestalts { - static description = 'Link node signed claim does not have matching `iss` and `sub` node IDs'; + static description = + 'Link node signed claim does not have matching `iss` and `sub` node IDs'; exitCode = sysexits.USAGE; } class ErrorGestaltsGraphLinkIdentityMatch extends ErrorGestalts { - static description = 'Link identity signed claim does not have matching `iss` and `sub` node and identity IDs'; + static description = + 'Link identity signed claim does not have matching `iss` and `sub` node and identity IDs'; exitCode = sysexits.USAGE; } diff --git a/src/gestalts/types.ts b/src/gestalts/types.ts index 29ba82f0b..ffa6e5992 100644 --- a/src/gestalts/types.ts +++ b/src/gestalts/types.ts @@ -5,23 +5,18 @@ import type { GestaltIdEncoded, ProviderIdentityClaimId, NodeId, - GestaltLinkId + GestaltLinkId, } from '../ids/types'; -import type { - SignedClaim, - SignedClaimJSON, -} from '../claims/types'; -import type { - ClaimLinkIdentity, - ClaimLinkNode -} from '../claims/payloads'; +import type { SignedClaim, SignedClaimJSON } from '../claims/types'; +import type { ClaimLinkIdentity, ClaimLinkNode } from '../claims/payloads'; const gestaltActions = ['notify', 'scan', 'claim'] as const; type GestaltKey = Opaque<'GestaltKey', Buffer>; -type GestaltInfo = ['node', GestaltNodeInfo] - | ['identity', GestaltIdentityInfo]; +type GestaltInfo = + | ['node', GestaltNodeInfo] + | ['identity', GestaltIdentityInfo]; type GestaltNodeInfo = { nodeId: NodeId; @@ -35,8 +30,8 @@ type GestaltNodeInfo = { */ interface GestaltNodeInfoJSON extends Omit { nodeId: { - type: 'IdInternal', - data: Array + type: 'IdInternal'; + data: Array; }; } @@ -54,9 +49,13 @@ type GestaltIdentityInfo = { * Links are edges between node and identity vertexes. * The data within these links would be acquired by discovery. */ -type GestaltLink = ['node', GestaltLinkNode] | ['identity', GestaltLinkIdentity]; +type GestaltLink = + | ['node', GestaltLinkNode] + | ['identity', GestaltLinkIdentity]; -type GestaltLinkJSON = ['node', GestaltLinkNodeJSON] | ['identity', GestaltLinkIdentityJSON]; +type GestaltLinkJSON = + | ['node', GestaltLinkNodeJSON] + | ['identity', GestaltLinkIdentityJSON]; /** * Linking node to node. @@ -73,9 +72,9 @@ type GestaltLinkNode = { type GestaltLinkNodeJSON = Omit & { id: { - type: 'IdInternal', - data: Array - }, + type: 'IdInternal'; + data: Array; + }; claim: SignedClaimJSON; }; @@ -92,14 +91,14 @@ type GestaltLinkIdentity = { url?: string; // The `undefined` is a hack to include the optional reserved properties [key: string]: JSONValue | undefined; - } + }; }; type GestaltLinkIdentityJSON = Omit & { id: { - type: 'IdInternal', - data: Array - }, + type: 'IdInternal'; + data: Array; + }; claim: SignedClaimJSON; }; @@ -107,15 +106,9 @@ type GestaltLinks = Record; type GestaltMatrix = Record; -type GestaltNodes = Record< - GestaltIdEncoded, - GestaltNodeInfo ->; +type GestaltNodes = Record; -type GestaltIdentities = Record< - GestaltIdEncoded, - GestaltIdentityInfo ->; +type GestaltIdentities = Record; type Gestalt = { matrix: GestaltMatrix; diff --git a/src/gestalts/utils.ts b/src/gestalts/utils.ts index e6c1b522a..2cdcb60ac 100644 --- a/src/gestalts/utils.ts +++ b/src/gestalts/utils.ts @@ -1,11 +1,5 @@ -import type { - GestaltLinkId, - NodeId, - ProviderIdentityId -} from '../ids/types'; -import type { - TokenSignature -} from '../tokens/types'; +import type { GestaltLinkId, NodeId, ProviderIdentityId } from '../ids/types'; +import type { TokenSignature } from '../tokens/types'; import type { GestaltId, GestaltKey, @@ -14,18 +8,14 @@ import type { GestaltNodeInfoJSON, GestaltLink, GestaltLinkJSON, - GestaltLinkNode, - GestaltLinkNodeJSON, - GestaltLinkIdentity, - GestaltLinkIdentityJSON, } from './types'; +import type { ClaimLinkNode, ClaimLinkIdentity } from '../claims/payloads'; import { IdInternal } from '@matrixai/id'; import { gestaltActions } from './types'; import * as ids from '../ids'; -import type { ClaimLinkNode, ClaimLinkIdentity } from '../claims/payloads'; function toGestaltKey(gestaltId: GestaltId): GestaltKey { - switch(gestaltId[0]) { + switch (gestaltId[0]) { case 'node': return toGestaltNodeKey(gestaltId); case 'identity': @@ -40,7 +30,9 @@ function fromGestaltKey(gestaltKey: GestaltKey): GestaltId { } else if (type.equals(Buffer.from('identity'))) { return fromGestaltIdentityKey(gestaltKey); } else { - throw new TypeError('Buffer is neither a GestaltNodeKey nor GestaltIdentityKey'); + throw new TypeError( + 'Buffer is neither a GestaltNodeKey nor GestaltIdentityKey', + ); } } @@ -62,40 +54,36 @@ function fromGestaltNodeKey(gestaltNodeKey: GestaltKey): ['node', NodeId] { if (nodeId.length !== 32) { throw new TypeError('Buffer is not a GestaltNodeKey'); } - return [ - 'node', - nodeId, - ]; + return ['node', nodeId]; } function toGestaltIdentityKey( - gestaltIdentityId: ['identity', ProviderIdentityId] + gestaltIdentityId: ['identity', ProviderIdentityId], ): GestaltKey { return Buffer.concat([ Buffer.from(gestaltIdentityId[0], 'utf-8'), Buffer.from('-'), - Buffer.from(ids.encodeProviderIdentityId(gestaltIdentityId[1]), 'utf-8') + Buffer.from(ids.encodeProviderIdentityId(gestaltIdentityId[1]), 'utf-8'), ]) as GestaltKey; } function fromGestaltIdentityKey( - gestaltIdentityKey: GestaltKey + gestaltIdentityKey: GestaltKey, ): ['identity', ProviderIdentityId] { const type = gestaltIdentityKey.slice(0, gestaltIdentityKey.indexOf('-')); if (!type.equals(Buffer.from('identity'))) { throw new TypeError('Buffer is not a GestaltIdentityKey'); } - const providerIdentityIdEncoded = gestaltIdentityKey.slice(gestaltIdentityKey.indexOf('-') + 1); + const providerIdentityIdEncoded = gestaltIdentityKey.slice( + gestaltIdentityKey.indexOf('-') + 1, + ); const providerIdentityId = ids.decodeProviderIdentityId( - providerIdentityIdEncoded.toString('utf-8') + providerIdentityIdEncoded.toString('utf-8'), ); if (providerIdentityId == null) { throw new TypeError('Buffer is not a GestaltIdentityKey'); } - return [ - 'identity', - providerIdentityId, - ]; + return ['identity', providerIdentityId]; } function isGestaltAction(action: any): action is GestaltAction { @@ -104,13 +92,11 @@ function isGestaltAction(action: any): action is GestaltAction { } function fromGestaltNodeInfoJSON( - gestaltNodeInfoJSON: GestaltNodeInfoJSON + gestaltNodeInfoJSON: GestaltNodeInfoJSON, ): GestaltNodeInfo { return { ...gestaltNodeInfoJSON, - nodeId: IdInternal.fromJSON( - gestaltNodeInfoJSON.nodeId - )! + nodeId: IdInternal.fromJSON(gestaltNodeInfoJSON.nodeId)!, }; } @@ -124,13 +110,15 @@ function fromGestaltLinkJSON(gestaltLinkJSON: GestaltLinkJSON): GestaltLink { claim: { ...gestaltLinkJSONData.claim, signatures: gestaltLinkJSONData.claim.signatures.map( - headerSignatureJSON => ({ + (headerSignatureJSON) => ({ ...headerSignatureJSON, - signature: Buffer.from(headerSignatureJSON.signature.data) as TokenSignature, - }) + signature: Buffer.from( + headerSignatureJSON.signature.data, + ) as TokenSignature, + }), ), }, - } + }, ] as GestaltLink; } @@ -140,7 +128,7 @@ function fromGestaltLinkJSON(gestaltLinkJSON: GestaltLinkJSON): GestaltLink { function checkLinkNodeMatches( nodeId1: NodeId, nodeId2: NodeId, - claimPayload: ClaimLinkNode + claimPayload: ClaimLinkNode, ): boolean { const issNodeId = ids.decodeNodeId(claimPayload.iss)!; const subNodeId = ids.decodeNodeId(claimPayload.sub)!; @@ -163,11 +151,15 @@ function checkLinkIdentityMatches( ) { const [providerId, identityId] = providerIdentityId; const issNodeId = ids.decodeNodeId(claimPayload.iss)!; - const [subProviderId, subIdentityId] = ids.decodeProviderIdentityId((claimPayload.sub))!; + const [subProviderId, subIdentityId] = ids.decodeProviderIdentityId( + claimPayload.sub, + )!; - return issNodeId.equals(nodeId) && + return ( + issNodeId.equals(nodeId) && subProviderId === providerId && - subIdentityId === identityId; + subIdentityId === identityId + ); } export { diff --git a/src/grpc/GRPCClient.ts b/src/grpc/GRPCClient.ts index 9f0e86b0e..1dba77707 100644 --- a/src/grpc/GRPCClient.ts +++ b/src/grpc/GRPCClient.ts @@ -15,7 +15,6 @@ import Logger from '@matrixai/logger'; import * as grpc from '@grpc/grpc-js'; import * as grpcUtils from './utils'; import * as grpcErrors from './errors'; -import * as keysUtils from '../keys/utils'; import * as networkUtils from '../network/utils'; import * as networkErrors from '../network/errors'; import * as nodeUtils from '../nodes/utils'; @@ -152,7 +151,10 @@ abstract class GRPCClient { const socket = session.socket as TLSSocket; serverCertChain = networkUtils.getCertificateChain(socket); try { - networkUtils.verifyServerCertificateChain([nodeId], serverCertChain); + await networkUtils.verifyServerCertificateChain( + [nodeId], + serverCertChain, + ); } catch (e) { const e_ = e; if (e instanceof networkErrors.ErrorCertChain) { diff --git a/src/grpc/GRPCServer.ts b/src/grpc/GRPCServer.ts index f0d887ab3..e4b17fd95 100644 --- a/src/grpc/GRPCServer.ts +++ b/src/grpc/GRPCServer.ts @@ -76,7 +76,7 @@ class GRPCServer { const http2Servers = server.http2ServerList; for (const http2ServerObjects of http2Servers) { const http2Server = http2ServerObjects.server as Http2SecureServer; - http2Server.on('session', (session: Http2Session) => { + http2Server.on('session', async (session: Http2Session) => { const socket = session.socket as TLSSocket; const address = networkUtils.buildAddress( socket.remoteAddress as Host, @@ -91,7 +91,7 @@ class GRPCServer { ); } else { try { - networkUtils.verifyClientCertificateChain(clientCertChain); + await networkUtils.verifyClientCertificateChain(clientCertChain); this.logger.debug(`Verified certificate from ${address}`); this.clientCertChains.set(session, clientCertChain); } catch (e) { diff --git a/src/identities/IdentitiesManager.ts b/src/identities/IdentitiesManager.ts index 863e50793..a3053e185 100644 --- a/src/identities/IdentitiesManager.ts +++ b/src/identities/IdentitiesManager.ts @@ -2,22 +2,23 @@ import type { ProviderId, IdentityId, ProviderTokens, - ProviderToken, IdentitySignedClaim, + ProviderToken, + IdentitySignedClaim, } from './types'; import type { DB, DBTransaction, KeyPath, LevelPath } from '@matrixai/db'; import type Provider from './Provider'; -import Logger from '@matrixai/logger'; +import type { SignedClaim } from '../claims/types'; +import type { ClaimLinkIdentity } from '../claims/payloads'; +import type KeyRing from '../keys/KeyRing'; +import type Sigchain from '../sigchain/Sigchain'; +import type GestaltGraph from '../gestalts/GestaltGraph'; import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import Logger from '@matrixai/logger'; import * as identitiesErrors from './errors'; import * as nodesUtils from '../nodes/utils'; -import { SignedClaim } from '../claims/types'; -import { ClaimLinkIdentity } from '../claims/payloads'; -import KeyRing from '../keys/KeyRing'; -import Sigchain from '../sigchain/Sigchain'; -import GestaltGraph from '../gestalts/GestaltGraph'; import { promise } from '../utils/index'; import { encodeProviderIdentityId } from '../ids'; @@ -43,7 +44,13 @@ class IdentitiesManager { fresh?: boolean; }): Promise { logger.info(`Creating ${this.name}`); - const identitiesManager = new this({ db, sigchain, keyRing, gestaltGraph, logger }); + const identitiesManager = new this({ + db, + sigchain, + keyRing, + gestaltGraph, + logger, + }); await identitiesManager.start({ fresh }); logger.info(`Created ${this.name}`); return identitiesManager; @@ -64,11 +71,23 @@ class IdentitiesManager { ]; protected providers: Map = new Map(); - constructor({ keyRing, db, sigchain, gestaltGraph, logger }: { keyRing: KeyRing; db: DB; sigchain: Sigchain; gestaltGraph: GestaltGraph; logger: Logger }) { + constructor({ + keyRing, + db, + sigchain, + gestaltGraph, + logger, + }: { + keyRing: KeyRing; + db: DB; + sigchain: Sigchain; + gestaltGraph: GestaltGraph; + logger: Logger; + }) { this.keyRing = keyRing; this.db = db; this.sigchain = sigchain; - this.gestaltGraph = gestaltGraph + this.gestaltGraph = gestaltGraph; this.logger = logger; } @@ -215,7 +234,7 @@ class IdentitiesManager { public async handleClaimIdentity( providerId: ProviderId, - identityId: IdentityId + identityId: IdentityId, ) { // Check provider is authenticated const provider = this.getProvider(providerId); @@ -227,19 +246,24 @@ class IdentitiesManager { throw new identitiesErrors.ErrorProviderUnauthenticated(); } // Create identity claim on our node - const publishedClaimProm = promise() + const publishedClaimProm = promise(); await this.db.withTransactionF((tran) => this.sigchain.addClaim( { - typ: 'identity', + typ: 'ClaimLinkIdentity', iss: nodesUtils.encodeNodeId(this.keyRing.getNodeId()), sub: encodeProviderIdentityId([providerId, identityId]), }, undefined, async (token) => { // Publishing in the callback to avoid adding bad claims - const claim = token.toSigned() - publishedClaimProm.resolveP(await provider.publishClaim(identityId, claim as SignedClaim)) + const claim = token.toSigned(); + const asd = await provider.publishClaim( + identityId, + claim as SignedClaim, + ); + publishedClaimProm.resolveP(asd); + return token; }, tran, ), @@ -248,20 +272,16 @@ class IdentitiesManager { // Publish claim on identity const issNodeInfo = { nodeId: this.keyRing.getNodeId(), - } + }; const subIdentityInfo = { providerId: providerId, identityId: identityId, url: publishedClaim.url, - } - await this.gestaltGraph.linkNodeAndIdentity( - issNodeInfo, - subIdentityInfo, - { - meta: { providerIdentityClaimId: publishedClaim.id }, - claim: publishedClaim.claim - } - ) + }; + await this.gestaltGraph.linkNodeAndIdentity(issNodeInfo, subIdentityInfo, { + meta: { providerIdentityClaimId: publishedClaim.id }, + claim: publishedClaim.claim, + }); return publishedClaim; } } diff --git a/src/identities/Provider.ts b/src/identities/Provider.ts index a267be726..854e16620 100644 --- a/src/identities/Provider.ts +++ b/src/identities/Provider.ts @@ -91,7 +91,7 @@ abstract class Provider { * This does not verify whether the signature is correct */ public parseClaim( - signedClaimEncodedJSON: string + signedClaimEncodedJSON: string, ): SignedClaim | undefined { let signedClaimEncoded; try { @@ -104,9 +104,8 @@ abstract class Provider { } let signedClaim: SignedClaim; try { - signedClaim = claimLinkIdentity.parseSignedClaimLinkIdentity( - signedClaimEncoded - ); + signedClaim = + claimLinkIdentity.parseSignedClaimLinkIdentity(signedClaimEncoded); } catch { return; } @@ -141,7 +140,9 @@ abstract class Provider { /** * Gets the corresponding identity ID to a token key */ - public abstract getIdentityId(ProviderToken: ProviderToken): Promise; + public abstract getIdentityId( + providerToken: ProviderToken, + ): Promise; /** * Gets the identity data for a given identity diff --git a/src/identities/providers/github/GitHubProvider.ts b/src/identities/providers/github/GitHubProvider.ts index b160ba698..9c2098c71 100644 --- a/src/identities/providers/github/GitHubProvider.ts +++ b/src/identities/providers/github/GitHubProvider.ts @@ -177,7 +177,9 @@ class GitHubProvider extends Provider { * GitHub has user ids, but it is an implementation detail. * Usernames on GitHub are changeable. */ - public async getIdentityId(providerToken: ProviderToken): Promise { + public async getIdentityId( + providerToken: ProviderToken, + ): Promise { providerToken = await this.checkToken(providerToken); const request = this.createRequest( `${this.apiUrl}/user`, diff --git a/src/identities/utils.ts b/src/identities/utils.ts index 0cd2f432e..27cbb06a7 100644 --- a/src/identities/utils.ts +++ b/src/identities/utils.ts @@ -87,7 +87,4 @@ function matchIdentityData( export { browser, matchIdentityData }; -export { - encodeProviderIdentityId, - decodeProviderIdentityId -} from '../ids'; +export { encodeProviderIdentityId, decodeProviderIdentityId } from '../ids'; diff --git a/src/ids/index.ts b/src/ids/index.ts index 05a6bd8ed..82ad88472 100644 --- a/src/ids/index.ts +++ b/src/ids/index.ts @@ -183,12 +183,14 @@ function decodeClaimId(claimIdEncoded: unknown): ClaimId | undefined { } function encodeProviderIdentityId( - providerIdentityId: ProviderIdentityId + providerIdentityId: ProviderIdentityId, ): ProviderIdentityIdEncoded { return JSON.stringify(providerIdentityId) as ProviderIdentityIdEncoded; } -function decodeProviderIdentityId(providerIdentityIdEncoded: unknown): ProviderIdentityId | undefined { +function decodeProviderIdentityId( + providerIdentityIdEncoded: unknown, +): ProviderIdentityId | undefined { if (typeof providerIdentityIdEncoded !== 'string') { return; } @@ -209,11 +211,11 @@ function decodeProviderIdentityId(providerIdentityIdEncoded: unknown): ProviderI return providerIdentityId as ProviderIdentityId; } -// function encodeGestaltId(gestaltId: GestaltNodeId): GestaltNodeIdEncoded; +// Function encodeGestaltId(gestaltId: GestaltNodeId): GestaltNodeIdEncoded; // function encodeGestaltId(gestaltId: GestaltIdentityId): GestaltIdentityIdEncoded; // function encodeGestaltId(gestaltId: GestaltId): GestaltIdEncoded; function encodeGestaltId(gestaltId: GestaltId): GestaltIdEncoded { - switch(gestaltId[0]) { + switch (gestaltId[0]) { case 'node': return encodeGestaltNodeId(gestaltId); case 'identity': @@ -222,18 +224,22 @@ function encodeGestaltId(gestaltId: GestaltId): GestaltIdEncoded { } function encodeGestaltNodeId( - gestaltNodeId: ['node', NodeId] + gestaltNodeId: ['node', NodeId], ): GestaltIdEncoded { - return gestaltNodeId[0] + '-' + encodeNodeId(gestaltNodeId[1]) as GestaltIdEncoded; + return (gestaltNodeId[0] + + '-' + + encodeNodeId(gestaltNodeId[1])) as GestaltIdEncoded; } function encodeGestaltIdentityId( - gestaltIdentityId: ['identity', ProviderIdentityId] + gestaltIdentityId: ['identity', ProviderIdentityId], ): GestaltIdEncoded { - return gestaltIdentityId[0] + '-' + encodeProviderIdentityId(gestaltIdentityId[1]) as GestaltIdEncoded; + return (gestaltIdentityId[0] + + '-' + + encodeProviderIdentityId(gestaltIdentityId[1])) as GestaltIdEncoded; } -// function decodeGestaltId(gestaltIdEncoded: GestaltNodeIdEncoded): GestaltNodeId; +// Function decodeGestaltId(gestaltIdEncoded: GestaltNodeIdEncoded): GestaltNodeId; // function decodeGestaltId(gestaltIdEncoded: GestaltIdentityIdEncoded): GestaltIdentityId; // function decodeGestaltId(gestaltIdEncoded: GestaltIdEncoded): GestaltId; // function decodeGestaltId(gestaltIdEncoded: unknown): GestaltId | undefined; @@ -249,7 +255,9 @@ function decodeGestaltId(gestaltIdEncoded: unknown): GestaltId | undefined { } } -function decodeGestaltNodeId(gestaltNodeIdEncoded: unknown): ['node', NodeId] | undefined { +function decodeGestaltNodeId( + gestaltNodeIdEncoded: unknown, +): ['node', NodeId] | undefined { if (typeof gestaltNodeIdEncoded !== 'string') { return; } @@ -264,7 +272,9 @@ function decodeGestaltNodeId(gestaltNodeIdEncoded: unknown): ['node', NodeId] | return ['node', nodeId]; } -function decodeGestaltIdentityId(gestaltIdentityId: unknown): ['identity', ProviderIdentityId] | undefined { +function decodeGestaltIdentityId( + gestaltIdentityId: unknown, +): ['identity', ProviderIdentityId] | undefined { if (typeof gestaltIdentityId !== 'string') { return; } @@ -272,7 +282,9 @@ function decodeGestaltIdentityId(gestaltIdentityId: unknown): ['identity', Provi return; } const providerIdentityIdEncoded = gestaltIdentityId.slice(9); - const providerIdentityId = decodeProviderIdentityId(providerIdentityIdEncoded); + const providerIdentityId = decodeProviderIdentityId( + providerIdentityIdEncoded, + ); if (providerIdentityId == null) { return; } @@ -296,13 +308,17 @@ function createNotificationIdGenerator( return () => generator.get(); } -function encodeNotificationId(notificationId: NotificationId): NotificationIdEncoded { +function encodeNotificationId( + notificationId: NotificationId, +): NotificationIdEncoded { return notificationId.toMultibase('base32hex') as NotificationIdEncoded; } -function decodeNotificationId(notificationIdEncoded: string): NotificationId | undefined { +function decodeNotificationId( + notificationIdEncoded: string, +): NotificationId | undefined { const notificationId = IdInternal.fromMultibase( - notificationIdEncoded + notificationIdEncoded, ); if (notificationId == null) { return; diff --git a/src/ids/types.ts b/src/ids/types.ts index e399dadeb..193369f39 100644 --- a/src/ids/types.ts +++ b/src/ids/types.ts @@ -83,14 +83,14 @@ type ProviderIdentityClaimId = Opaque<'ProviderIdentityClaimId', string>; */ type GestaltId = ['node', NodeId] | ['identity', ProviderIdentityId]; -// type GestaltNodeId = ['node', NodeId]; +// Type GestaltNodeId = ['node', NodeId]; // type GestaltIdentityId = ['identity', ProviderIdentityId]; /** * GestaltId encoded. */ type GestaltIdEncoded = Opaque<'GestaltIdEncoded', string>; -// type GestaltIdEncoded = GestaltNodeIdEncoded | GestaltIdentityIdEncoded; +// Type GestaltIdEncoded = GestaltNodeIdEncoded | GestaltIdentityIdEncoded; // /** // * Concatenation of `'node'` and `NodeIdEncoded` diff --git a/src/keys/CertManager.ts b/src/keys/CertManager.ts index a28d815f1..bafb4df86 100644 --- a/src/keys/CertManager.ts +++ b/src/keys/CertManager.ts @@ -29,7 +29,9 @@ import * as ids from '../ids'; /** * This signal reason indicates we want to stop the renewal */ -const abortRenewCertTaskReason = Symbol('abort automatic certificate task renewal'); +const abortRenewCertTaskReason = Symbol( + 'abort automatic certificate task renewal', +); interface CertManager extends CreateDestroyStartStop {} @CreateDestroyStartStop( @@ -61,25 +63,24 @@ class CertManager { workerManager, logger = new Logger(this.name), subjectAttrsExtra, - now = new Date, + now = new Date(), lazy = false, fresh = false, }: { - db: DB; - keyRing: KeyRing; - taskManager: TaskManager; - certDuration?: number; - certRenewLeadTime?: number; - changeCallback?: (data: CertManagerChangeData) => any; - workerManager?: PolykeyWorkerManagerInterface; - logger?: Logger; - subjectAttrsExtra?: Array<{ [key: string]: Array }>, - issuerAttrsExtra?: Array<{ [key: string]: Array }>, - now?: Date; - lazy?: boolean; - fresh?: boolean; - } - ): Promise { + db: DB; + keyRing: KeyRing; + taskManager: TaskManager; + certDuration?: number; + certRenewLeadTime?: number; + changeCallback?: (data: CertManagerChangeData) => any; + workerManager?: PolykeyWorkerManagerInterface; + logger?: Logger; + subjectAttrsExtra?: Array<{ [key: string]: Array }>; + issuerAttrsExtra?: Array<{ [key: string]: Array }>; + now?: Date; + lazy?: boolean; + fresh?: boolean; + }): Promise { logger.info(`Creating ${this.name}`); const certManager = new this({ db, @@ -95,7 +96,7 @@ class CertManager { subjectAttrsExtra, now, lazy, - fresh + fresh, }); logger.info(`Created ${this.name}`); return certManager; @@ -179,11 +180,11 @@ class CertManager { public async start({ subjectAttrsExtra, - now = new Date, + now = new Date(), lazy = false, fresh = false, }: { - subjectAttrsExtra?: Array<{ [key: string]: Array }>, + subjectAttrsExtra?: Array<{ [key: string]: Array }>; now?: Date; lazy?: boolean; fresh?: boolean; @@ -227,7 +228,7 @@ class CertManager { * This is idempotent. */ @ready(new keysErrors.ErrorCertManagerNotRunning(), false, ['starting']) - public async startTasks(now: Date = new Date): Promise { + public async startTasks(now: Date = new Date()): Promise { this.tasksRunning = true; await this.setupRenewCurrentCertTask(now); } @@ -241,11 +242,9 @@ class CertManager { // it will be registered again upon startup if (this.renewCurrentCertTaskId != null) { this.logger.info( - `Cancelling task ${ - this.renewCurrentCertHandlerId - }:${ - ids.encodeTaskId(this.renewCurrentCertTaskId) - }` + `Cancelling task ${this.renewCurrentCertHandlerId}:${ids.encodeTaskId( + this.renewCurrentCertTaskId, + )}`, ); const task = await this.taskManager.getTask(this.renewCurrentCertTaskId); if (task != null) { @@ -280,7 +279,10 @@ class CertManager { * Get a certificate according to the `CertID` */ @ready(new keysErrors.ErrorCertManagerNotRunning(), false, ['starting']) - public async getCert(certId: CertId, tran?: DBTransaction): Promise { + public async getCert( + certId: CertId, + tran?: DBTransaction, + ): Promise { const certData = await (tran ?? this.db).get( [...this.dbCertsPath, certId.toBuffer()], true, @@ -311,8 +313,10 @@ class CertManager { * Gets an array of `Certificate` in order of leaf to root */ @ready(new keysErrors.ErrorCertManagerNotRunning()) - public async getCertsChain(tran?: DBTransaction): Promise> { - let certs: Array = []; + public async getCertsChain( + tran?: DBTransaction, + ): Promise> { + const certs: Array = []; for await (const cert of this.getCerts(tran)) { certs.push(cert); } @@ -323,7 +327,9 @@ class CertManager { * Get `CertificatePEM` from leaf to root */ @ready(new keysErrors.ErrorCertManagerNotRunning()) - public async *getCertPEMs(tran?: DBTransaction): AsyncGenerator { + public async *getCertPEMs( + tran?: DBTransaction, + ): AsyncGenerator { for await (const cert of this.getCerts(tran)) { yield keysUtils.certToPEM(cert); } @@ -333,7 +339,9 @@ class CertManager { * Gets an array of `CertificatePEM` in order of leaf to root */ @ready(new keysErrors.ErrorCertManagerNotRunning()) - public async getCertPEMsChain(tran?: DBTransaction): Promise> { + public async getCertPEMsChain( + tran?: DBTransaction, + ): Promise> { const pems: Array = []; for await (const certPem of this.getCertPEMs(tran)) { pems.push(certPem); @@ -345,7 +353,9 @@ class CertManager { * Gets a concatenated `CertificatePEM` ordered from leaf to root */ @ready(new keysErrors.ErrorCertManagerNotRunning()) - public async getCertPEMsChainPEM(tran?: DBTransaction): Promise { + public async getCertPEMsChainPEM( + tran?: DBTransaction, + ): Promise { let pem = ''; for await (const certPem of this.getCertPEMs(tran)) { pem += certPem; @@ -370,7 +380,9 @@ class CertManager { * Get the current (leaf) certificate in PEM */ @ready(new keysErrors.ErrorCertManagerNotRunning()) - public async getCurrentCertPEM(tran?: DBTransaction): Promise { + public async getCurrentCertPEM( + tran?: DBTransaction, + ): Promise { const cert = await this.getCurrentCert(tran); return keysUtils.certToPEM(cert); } @@ -394,7 +406,7 @@ class CertManager { public async renewCertWithNewKeyPair( password: string, duration: number = this.certDuration, - now: Date = new Date, + now: Date = new Date(), ): Promise { let certNew: Certificate; await this.renewResetLock.withF(async () => { @@ -404,7 +416,11 @@ class CertManager { const currentCert = await this.getCurrentCert(); await this.keyRing.rotateKeyPair( password, - async (keyPairNew: KeyPair, keyPairOld: KeyPair, recoveryCodeNew_: RecoveryCode) => { + async ( + keyPairNew: KeyPair, + keyPairOld: KeyPair, + recoveryCodeNew_: RecoveryCode, + ) => { recoveryCodeNew = recoveryCodeNew_; certNew = await this.generateCertificate({ subjectKeyPair: keyPairNew, @@ -419,7 +435,7 @@ class CertManager { // This is because we can rollback the new certificate // but we cannot rollback a key pair rotation. await this.putCert(certNew); - } + }, ); } catch (e) { // Use the same now to ensure that the new certificate is not expired @@ -427,7 +443,7 @@ class CertManager { await this.gcCerts(false, now); throw new keysErrors.ErrorCertsRenew( 'Failed renewing with new key pair', - { cause: e } + { cause: e }, ); } // Use the same now to ensure that the new certificate is not expired @@ -469,7 +485,7 @@ class CertManager { @ready(new keysErrors.ErrorCertManagerNotRunning(), false, ['starting']) public async renewCertWithCurrentKeyPair( duration: number = this.certDuration, - now: Date = new Date, + now: Date = new Date(), ): Promise { let certNew: Certificate; await this.renewResetLock.withF(async () => { @@ -491,7 +507,7 @@ class CertManager { await this.gcCerts(false, now); throw new keysErrors.ErrorCertsRenew( 'Failed renewing with current key pair', - { cause: e } + { cause: e }, ); } // Use the same now to ensure that the new certificate is not expired @@ -529,14 +545,13 @@ class CertManager { public async resetCertWithNewKeyPair( password: string, duration: number = this.certDuration, - now: Date = new Date, + now: Date = new Date(), ): Promise { let certNew: Certificate; await this.renewResetLock.withF(async () => { this.logger.info('Resetting certificate chain with new key pair'); let recoveryCodeNew: RecoveryCode; try { - const currentCert = await this.getCurrentCert(); await this.keyRing.rotateKeyPair( password, async (keyPairNew: KeyPair, _, recoveryCodeNew_) => { @@ -554,7 +569,7 @@ class CertManager { // This is because we can rollback the new certificate // but we cannot rollback a key pair rotation. await this.putCert(certNew); - } + }, ); } catch (e) { // Use the same now to ensure that the new certificate is not expired @@ -562,7 +577,7 @@ class CertManager { await this.gcCerts(false, now); throw new keysErrors.ErrorCertsReset( 'Failed resetting with new key pair', - { cause: e } + { cause: e }, ); } // Use the same now to ensure that the new certificate is not expired @@ -600,7 +615,7 @@ class CertManager { @ready(new keysErrors.ErrorCertManagerNotRunning()) public async resetCertWithCurrentKeyPair( duration: number = this.certDuration, - now: Date = new Date, + now: Date = new Date(), ): Promise { let certNew: Certificate; await this.renewResetLock.withF(async () => { @@ -621,7 +636,7 @@ class CertManager { await this.gcCerts(false, now); throw new keysErrors.ErrorCertsReset( 'Failed resetting with current key pair', - { cause: e } + { cause: e }, ); } // Use the same now to ensure that the new certificate is not expired @@ -643,30 +658,25 @@ class CertManager { return certNew!; } - protected async putCert(cert: Certificate, tran?: DBTransaction): Promise { + protected async putCert( + cert: Certificate, + tran?: DBTransaction, + ): Promise { if (tran == null) { - return this.db.withTransactionF((tran) => - this.putCert(cert, tran) - ); + return this.db.withTransactionF((tran) => this.putCert(cert, tran)); } const certId = keysUtils.certCertId(cert)!; const certIdBuffer = certId.toBuffer(); const certASN1 = keysUtils.certToASN1(cert); - await tran.put( - [...this.dbCertsPath, certIdBuffer], - certASN1, - true - ); + await tran.put([...this.dbCertsPath, certIdBuffer], certASN1, true); await tran.put(this.dbLastCertIdPath, certIdBuffer, true); } - protected async delCert(certId: CertId, tran?: DBTransaction) : Promise { + protected async delCert(certId: CertId, tran?: DBTransaction): Promise { await (tran ?? this.db).del([...this.dbCertsPath, certId.toBuffer()]); } - protected async setupCurrentCert( - now: Date = new Date, - ): Promise { + protected async setupCurrentCert(now: Date = new Date()): Promise { this.logger.info('Begin current certificate setup'); let cert: Certificate | undefined; for await (const [, certASN1] of this.db.iterator(this.dbCertsPath, { @@ -696,11 +706,10 @@ class CertManager { !certPublicKey.equals(this.keyRing.keyPair.publicKey) || !keysUtils.certNotExpiredBy(cert, now) ) { - this.logger.info('Existing current certificate is invalid or expired, starting certificate renewal'); - await this.renewCertWithCurrentKeyPair( - this.certDuration, - now, + this.logger.info( + 'Existing current certificate is invalid or expired, starting certificate renewal', ); + await this.renewCertWithCurrentKeyPair(this.certDuration, now); } } this.logger.info('Finish current certificate setup'); @@ -713,19 +722,19 @@ class CertManager { * This task is a singleton. It must be updated when the current certificate * is renewed. */ - protected async setupRenewCurrentCertTask(now: Date = new Date): Promise { + protected async setupRenewCurrentCertTask( + now: Date = new Date(), + ): Promise { await this.db.withTransactionF(async (tran) => { const cert = await this.getCurrentCert(tran); const delay = Math.max( keysUtils.certRemainingDuration(cert, now) - this.certRenewLeadTime, - 0 + 0, ); let task: Task | undefined; - for await (const task_ of this.taskManager.getTasks( - 'asc', - true, - [this.renewCurrentCertHandlerId] - )) { + for await (const task_ of this.taskManager.getTasks('asc', true, [ + this.renewCurrentCertHandlerId, + ])) { // If the task is scheduled, we can update the delay // Otherwise we will let it complete, it will recall this method if (task_.status === 'scheduled') { @@ -740,9 +749,9 @@ class CertManager { handlerId: this.renewCurrentCertHandlerId, delay, lazy: true, - path: [this.renewCurrentCertHandlerId] + path: [this.renewCurrentCertHandlerId], }, - tran + tran, ); this.renewCurrentCertTaskId = task.id; } @@ -755,7 +764,7 @@ class CertManager { duration, subjectAttrsExtra, issuerAttrsExtra, - now = new Date, + now = new Date(), }: { subjectKeyPair: { publicKey: PublicKey; @@ -818,7 +827,7 @@ class CertManager { */ protected async gcCerts( force: boolean = false, - now: Date = new Date, + now: Date = new Date(), ): Promise { this.logger.info('Garbage collecting certificates'); await this.db.withTransactionF(async (tran) => { @@ -838,7 +847,11 @@ class CertManager { if (certPublicKey.equals(this.keyRing.keyPair.publicKey)) { currentCertFound = true; } else { - this.logger.warn(`Garbage collecting invalid certificate ${ids.encodeCertId(certId)} caused by failed key rotation`); + this.logger.warn( + `Garbage collecting invalid certificate ${ids.encodeCertId( + certId, + )} caused by failed key rotation`, + ); // Delete this invalid certificate. // This can only happen if the key pair rotation failed // after the certificate was put in to the DB. @@ -866,7 +879,7 @@ class CertManager { // This should never occur because there should always be a "valid" // current certificate after renewal or resetting throw new keysErrors.ErrorCertsGC( - 'Current certificate is not found during garbage collection' + 'Current certificate is not found during garbage collection', ); } }); diff --git a/src/keys/KeyRing.ts b/src/keys/KeyRing.ts index 49850fe36..1b61d7af3 100644 --- a/src/keys/KeyRing.ts +++ b/src/keys/KeyRing.ts @@ -43,25 +43,27 @@ class KeyRing { logger = new Logger(this.name), ...startOptions }: { - keysPath: string; - password: string; - workerManager?: PolykeyWorkerManagerInterface; - passwordOpsLimit?: PasswordOpsLimit; - passwordMemLimit?: PasswordMemLimit; - strictMemoryLock?: boolean; - fs?: FileSystem; - logger?: Logger; - fresh?: boolean; - } & ( - { } | { - recoveryCode: RecoveryCode - } | { + keysPath: string; + password: string; + workerManager?: PolykeyWorkerManagerInterface; + passwordOpsLimit?: PasswordOpsLimit; + passwordMemLimit?: PasswordMemLimit; + strictMemoryLock?: boolean; + fs?: FileSystem; + logger?: Logger; + fresh?: boolean; + } & ( // eslint-disable-next-line @typescript-eslint/ban-types + | {} + | { + recoveryCode: RecoveryCode; + } + | { privateKey: PrivateKey; - } | { + } + | { privateKeyPath: string; } - ) - ): Promise { + )): Promise { logger.info(`Creating ${this.name}`); logger.info(`Setting keys path to ${keysPath}`); const keyRing = new this({ @@ -90,8 +92,8 @@ class KeyRing { protected _keyPair?: KeyPairLocked; protected _dbKey?: BufferLocked; protected passwordHash?: Readonly<{ - hash: BufferLocked, - salt: BufferLocked + hash: BufferLocked; + salt: BufferLocked; }>; protected passwordOpsLimit?: PasswordOpsLimit; protected passwordMemLimit?: PasswordMemLimit; @@ -135,18 +137,20 @@ class KeyRing { delete this.workerManager; } - public async start(options: { - password: string; - fresh?: boolean; - } & ( - { } | - { recoveryCode: RecoveryCode; } | - { privateKey: PrivateKey; } | - { privateKeyPath: string; } - )): Promise { + public async start( + options: { + password: string; + fresh?: boolean; + } & ( // eslint-disable-next-line @typescript-eslint/ban-types + | {} + | { recoveryCode: RecoveryCode } + | { privateKey: PrivateKey } + | { privateKeyPath: string } + ), + ): Promise { const { fresh = false, ...setupKeyPairOptions } = options; this.logger.info(`Starting ${this.constructor.name}`); - if (options.fresh) { + if (fresh) { await this.fs.promises.rm(this.keysPath, { force: true, recursive: true, @@ -157,7 +161,9 @@ class KeyRing { setupKeyPairOptions, ); const dbKey = await this.setupDbKey(keyPair); - const [passwordHash, passwordSalt] = await this.setupPasswordHash(options.password); + const [passwordHash, passwordSalt] = await this.setupPasswordHash( + options.password, + ); bufferLock(keyPair.publicKey, this.strictMemoryLock); bufferLock(keyPair.privateKey, this.strictMemoryLock); bufferLock(keyPair.secretKey, this.strictMemoryLock); @@ -168,7 +174,7 @@ class KeyRing { this._dbKey = dbKey; this.passwordHash = { hash: passwordHash, - salt: passwordSalt + salt: passwordSalt, }; if (recoveryCode != null) { const recoveryCodeData = Buffer.from(recoveryCode, 'utf-8'); @@ -274,14 +280,16 @@ class KeyRing { await this.rotateLock.withF(async () => { this.logger.info('Changing root key pair password'); await this.writeKeyPair(this._keyPair!, password); - const [passwordHash, passwordSalt] = await this.setupPasswordHash(password); + const [passwordHash, passwordSalt] = await this.setupPasswordHash( + password, + ); bufferUnlock(this.passwordHash!.hash); bufferUnlock(this.passwordHash!.salt); bufferLock(passwordHash, this.strictMemoryLock); bufferLock(passwordSalt, this.strictMemoryLock); this.passwordHash = { hash: passwordHash, - salt: passwordSalt + salt: passwordSalt, }; this.logger.info('Changed root key pair password'); }); @@ -310,33 +318,21 @@ class KeyRing { await Promise.all([ this.fs.promises.copyFile( this.publicKeyPath, - `${this.publicKeyPath}.bak` + `${this.publicKeyPath}.bak`, ), this.fs.promises.copyFile( this.privateKeyPath, - `${this.privateKeyPath}.bak` + `${this.privateKeyPath}.bak`, ), - this.fs.promises.copyFile( - this.dbKeyPath, - `${this.dbKeyPath}.bak` - ) + this.fs.promises.copyFile(this.dbKeyPath, `${this.dbKeyPath}.bak`), ]); } catch (e) { this.logger.error('Failed backing up root key pair and DB key'); try { await Promise.all([ - this.fs.promises.rm( - `${this.publicKeyPath}.bak`, - { force: true, } - ), - this.fs.promises.rm( - `${this.privateKeyPath}.bak`, - { force: true } - ), - this.fs.promises.rm( - `${this.dbKeyPath}.bak`, - { force: true } - ) + this.fs.promises.rm(`${this.publicKeyPath}.bak`, { force: true }), + this.fs.promises.rm(`${this.privateKeyPath}.bak`, { force: true }), + this.fs.promises.rm(`${this.dbKeyPath}.bak`, { force: true }), ]); } catch (e) { // Any error here should not terminate the program @@ -344,7 +340,7 @@ class KeyRing { } throw new keysErrors.ErrorKeyPairRotate( 'Failed backing up root key pair and DB key', - { cause: e } + { cause: e }, ); } try { @@ -372,11 +368,26 @@ class KeyRing { this._keyPair = keyPair as KeyPairLocked; const recoveryCodeData = Buffer.from(recoveryCode, 'utf-8'); bufferLock(recoveryCodeData, this.strictMemoryLock); - if (this._recoveryCodeData != null) bufferUnlock(this._recoveryCodeData); + if (this._recoveryCodeData != null) { + bufferUnlock(this._recoveryCodeData); + } this._recoveryCodeData = recoveryCodeData as RecoveryCodeLocked; + const [passwordHash, passwordSalt] = await this.setupPasswordHash( + password, + ); + bufferUnlock(this.passwordHash!.hash); + bufferUnlock(this.passwordHash!.salt); + bufferLock(passwordHash, this.strictMemoryLock); + bufferLock(passwordSalt, this.strictMemoryLock); + this.passwordHash = { + hash: passwordHash, + salt: passwordSalt, + }; this.logger.info('Rotated root key pair'); } catch (e) { - this.logger.error('Failed rotating root key pair, recovering from backups'); + this.logger.error( + 'Failed rotating root key pair, recovering from backups', + ); try { await Promise.all([ this.fs.promises.rename( @@ -387,10 +398,7 @@ class KeyRing { `${this.privateKeyPath}.bak`, this.privateKeyPath, ), - this.fs.promises.rename( - `${this.dbKeyPath}.bak`, - this.dbKeyPath, - ) + this.fs.promises.rename(`${this.dbKeyPath}.bak`, this.dbKeyPath), ]); } catch (e) { // Any error here should not terminate the program @@ -399,7 +407,7 @@ class KeyRing { } throw new keysErrors.ErrorKeyPairRotate( 'Failed rotating root key pair', - { cause: e } + { cause: e }, ); } }); @@ -418,12 +426,12 @@ class KeyRing { public encrypt( receiverPublicKey: PublicKey, plainText: Buffer, - authenticated: boolean = false + authenticated: boolean = false, ): Buffer { return keysUtils.encryptWithPublicKey( receiverPublicKey, plainText, - (authenticated) ? this._keyPair : undefined + authenticated ? this._keyPair : undefined, ); } @@ -433,10 +441,7 @@ class KeyRing { */ @ready(new keysErrors.ErrorKeyRingNotRunning()) public decrypt(cipherText: Buffer): Buffer | undefined { - return keysUtils.decryptWithPrivateKey( - this._keyPair!, - cipherText, - ); + return keysUtils.decryptWithPrivateKey(this._keyPair!, cipherText); } @ready(new keysErrors.ErrorKeyRingNotRunning()) @@ -478,25 +483,33 @@ class KeyRing { * The key pair is encrypted with the password. * The key pair is returned without the recovery code. */ - protected async setupKeyPair(options: { - password: string; - } | { - password: string; - recoveryCode: RecoveryCode; - } | { - password: string; - privateKey: PrivateKey; - } | { - password: string; - privateKeyPath: string; - }): Promise<[KeyPair, RecoveryCode | undefined]> { + protected async setupKeyPair( + options: + | { + password: string; + } + | { + password: string; + recoveryCode: RecoveryCode; + } + | { + password: string; + privateKey: PrivateKey; + } + | { + password: string; + privateKeyPath: string; + }, + ): Promise<[KeyPair, RecoveryCode | undefined]> { let rootKeyPair: KeyPair; let recoveryCodeNew: RecoveryCode | undefined; if (await this.existsKeyPair()) { if ('recoveryCode' in options && options.recoveryCode != null) { // Recover the key pair this.logger.info('Recovering root key pair'); - const recoveredKeyPair = await this.recoverKeyPair(options.recoveryCode); + const recoveredKeyPair = await this.recoverKeyPair( + options.recoveryCode, + ); if (recoveredKeyPair == null) { throw new keysErrors.ErrorKeysRecoveryCodeIncorrect(); } @@ -526,11 +539,14 @@ class KeyRing { rootKeyPair = keyPair as KeyPairLocked; await this.writeKeyPair(rootKeyPair, options.password); return [rootKeyPair, undefined]; - } else if ('privateKeyPath' in options && options.privateKeyPath != null) { + } else if ( + 'privateKeyPath' in options && + options.privateKeyPath != null + ) { this.logger.info('Making root key pair from provided private key path'); const privateKey = await this.readPrivateKey( options.password, - options.privateKeyPath + options.privateKeyPath, ); const publicKey = keysUtils.publicKeyFromPrivateKeyEd25519(privateKey); const keyPair = keysUtils.makeKeyPair(publicKey, privateKey); @@ -567,7 +583,7 @@ class KeyRing { } throw new keysErrors.ErrorKeyPairRead( `Failed to check for existence of ${this.privateKeyPath}`, - { cause: e } + { cause: e }, ); } return true; @@ -614,9 +630,7 @@ class KeyRing { */ protected async readKeyPair(password: string): Promise { const privateKey = await this.readPrivateKey(password); - const publicKey = keysUtils.publicKeyFromPrivateKeyEd25519( - privateKey, - ); + const publicKey = keysUtils.publicKeyFromPrivateKeyEd25519(privateKey); const keyPair = keysUtils.makeKeyPair(publicKey, privateKey); return keyPair; } @@ -626,18 +640,15 @@ class KeyRing { * The public key is expected to be stored in a flattened JWE format. */ protected async readPublicKey( - publicKeyPath: string = this.publicKeyPath + publicKeyPath: string = this.publicKeyPath, ): Promise { let publicJWKJSON: string; try { - publicJWKJSON = await this.fs.promises.readFile( - publicKeyPath, - 'utf-8', - ); + publicJWKJSON = await this.fs.promises.readFile(publicKeyPath, 'utf-8'); } catch (e) { throw new keysErrors.ErrorKeyPairRead( `Public key path ${publicKeyPath} cannot be read`, - { cause: e } + { cause: e }, ); } let publicJWK: any; @@ -646,13 +657,13 @@ class KeyRing { } catch (e) { throw new keysErrors.ErrorKeyPairParse( `Public key path ${publicKeyPath} is not a valid JSON file`, - { cause: e } + { cause: e }, ); } const publicKey = keysUtils.publicKeyFromJWK(publicJWK); if (publicKey == null) { throw new keysErrors.ErrorKeyPairParse( - `Public key path ${publicKeyPath} is not a valid public key` + `Public key path ${publicKeyPath} is not a valid public key`, ); } return publicKey; @@ -668,14 +679,11 @@ class KeyRing { ): Promise { let privateJSON: string; try { - privateJSON = await this.fs.promises.readFile( - privateKeyPath, - 'utf-8', - ); + privateJSON = await this.fs.promises.readFile(privateKeyPath, 'utf-8'); } catch (e) { throw new keysErrors.ErrorKeyPairRead( `Private key path ${privateKeyPath} cannot be read`, - { cause: e } + { cause: e }, ); } let privateObject: any; @@ -684,39 +692,42 @@ class KeyRing { } catch (e) { throw new keysErrors.ErrorKeyPairParse( `Private key path ${privateKeyPath} is not a valid JSON file`, - { cause: e } + { cause: e }, ); } if ('kty' in privateObject && privateObject.kty != null) { const privateKey = keysUtils.privateKeyFromJWK(privateObject); if (privateKey == null) { throw new keysErrors.ErrorKeyPairParse( - `Private key path ${privateKeyPath} is not a valid JWK` + `Private key path ${privateKeyPath} is not a valid JWK`, ); } return privateKey; - } else if ('ciphertext' in privateObject && privateObject.ciphertext != null) { + } else if ( + 'ciphertext' in privateObject && + privateObject.ciphertext != null + ) { const privateJWK = keysUtils.unwrapWithPassword( password, privateObject, this.passwordOpsLimit, - this.passwordMemLimit + this.passwordMemLimit, ); if (privateJWK == null) { throw new keysErrors.ErrorKeyPairParse( - `Private key path ${privateKeyPath} is not a valid encrypted JWK` + `Private key path ${privateKeyPath} is not a valid encrypted JWK`, ); } const privateKey = keysUtils.privateKeyFromJWK(privateJWK); if (privateKey == null) { throw new keysErrors.ErrorKeyPairParse( - `Private key path ${privateKeyPath} is not a valid private key` + `Private key path ${privateKeyPath} is not a valid private key`, ); } return privateKey; } else { throw new keysErrors.ErrorKeyPairParse( - `Private key path ${privateKeyPath} has to be a JWK or an encrypted JWK` + `Private key path ${privateKeyPath} has to be a JWK or an encrypted JWK`, ); } } @@ -746,11 +757,15 @@ class KeyRing { try { // Write to temporary files first, then atomically rename await Promise.all([ - this.fs.promises.writeFile(`${this.publicKeyPath}.tmp`, publicJWKJSON, 'utf-8'), + this.fs.promises.writeFile( + `${this.publicKeyPath}.tmp`, + publicJWKJSON, + 'utf-8', + ), this.fs.promises.writeFile( `${this.privateKeyPath}.tmp`, privateJWEJSON, - 'utf-8' + 'utf-8', ), ]); await Promise.all([ @@ -766,7 +781,7 @@ class KeyRing { } catch (e) { throw new keysErrors.ErrorKeyPairWrite( `Key pair paths ${this.publicKeyPath} and ${this.privateKeyPath} cannot be written to`, - { cause: e } + { cause: e }, ); } } @@ -873,7 +888,7 @@ class KeyRing { */ protected async readDbKey( keyPair: KeyPair, - dbKeyPath: string = this.dbKeyPath + dbKeyPath: string = this.dbKeyPath, ): Promise { let dbJWEJSON: string; try { @@ -881,7 +896,7 @@ class KeyRing { } catch (e) { throw new keysErrors.ErrorDBKeyRead( `DB key path ${dbKeyPath} cannot be read`, - { cause: e } + { cause: e }, ); } let dbJWE: any; @@ -890,22 +905,19 @@ class KeyRing { } catch (e) { throw new keysErrors.ErrorDBKeyParse( `DB key path ${dbKeyPath} is not a valid JSON file`, - { cause: e } + { cause: e }, ); } - const dbJWK = keysUtils.decapsulateWithPrivateKey( - keyPair, - dbJWE - ); + const dbJWK = keysUtils.decapsulateWithPrivateKey(keyPair, dbJWE); if (dbJWK == null) { throw new keysErrors.ErrorDBKeyParse( - `DB key path ${dbKeyPath} is not a valid encrypted JWK` + `DB key path ${dbKeyPath} is not a valid encrypted JWK`, ); } const dbKey = keysUtils.keyFromJWK(dbJWK); if (dbKey == null) { throw new keysErrors.ErrorDBKeyParse( - `DB key path ${dbKeyPath} is not a valid key` + `DB key path ${dbKeyPath} is not a valid key`, ); } return dbKey; @@ -916,21 +928,22 @@ class KeyRing { * The DB key will be stored in flattened JWE format. * The DB key will be encrypted with our ECIES. */ - protected async writeDbKey( - dbKey: Key, - publicKey: PublicKey, - ): Promise { + protected async writeDbKey(dbKey: Key, publicKey: PublicKey): Promise { const dbJWK = keysUtils.keyToJWK(dbKey); const dbJWE = keysUtils.encapsulateWithPublicKey(publicKey, dbJWK); const dbJWEJSON = JSON.stringify(dbJWE); try { // Write to temporary file first, then atomically rename - await this.fs.promises.writeFile(`${this.dbKeyPath}.tmp`, dbJWEJSON, 'utf-8'), - await this.fs.promises.rename(`${this.dbKeyPath}.tmp`, this.dbKeyPath); + await this.fs.promises.writeFile( + `${this.dbKeyPath}.tmp`, + dbJWEJSON, + 'utf-8', + ), + await this.fs.promises.rename(`${this.dbKeyPath}.tmp`, this.dbKeyPath); } catch (e) { throw new keysErrors.ErrorDBKeyWrite( `DB key path ${this.dbKeyPath} cannot be written to`, - { cause: e } + { cause: e }, ); } } @@ -951,10 +964,7 @@ class KeyRing { */ protected async setupPasswordHash( password: string, - ): Promise<[ - PasswordHash, - PasswordSalt - ]> { + ): Promise<[PasswordHash, PasswordSalt]> { let hash: PasswordHash, salt: PasswordSalt; if (this.workerManager == null) { [hash, salt] = keysUtils.hashPassword( diff --git a/src/keys/types.ts b/src/keys/types.ts index da14c03a2..0bf2c17c5 100644 --- a/src/keys/types.ts +++ b/src/keys/types.ts @@ -302,7 +302,4 @@ export type { export type { CertId, CertIdString, CertIdEncoded } from '../ids/types'; -export { - multihashCodes, - multihashCodesI, -}; +export { multihashCodes, multihashCodesI }; diff --git a/src/keys/utils/asymmetric.ts b/src/keys/utils/asymmetric.ts index e777e1aac..e40368646 100644 --- a/src/keys/utils/asymmetric.ts +++ b/src/keys/utils/asymmetric.ts @@ -23,7 +23,9 @@ import * as utils from '../../utils'; */ function makeKeyPair(publicKey: PublicKey, privateKey: PrivateKey): KeyPair { // This ensures `secretKey.buffer` is not using the shared internal pool - const secretKey = Buffer.allocUnsafeSlow(privateKey.byteLength + publicKey.byteLength); + const secretKey = Buffer.allocUnsafeSlow( + privateKey.byteLength + publicKey.byteLength, + ); privateKey.copy(secretKey); publicKey.copy(secretKey, privateKey.byteLength); return { @@ -107,7 +109,7 @@ function publicKeyFromPrivateKeyX25519(privateKey: PrivateKeyX): PublicKeyX { */ function publicKeyEd25519ToX25519(publicKey: PublicKey): PublicKeyX { const publicKeyX25519 = Buffer.allocUnsafeSlow( - sodium.crypto_box_PUBLICKEYBYTES + sodium.crypto_box_PUBLICKEYBYTES, ); sodium.crypto_sign_ed25519_pk_to_curve25519(publicKeyX25519, publicKey); return publicKeyX25519 as PublicKeyX; @@ -122,7 +124,7 @@ function privateKeyEd25519ToX25519(privateKey: PrivateKey): PrivateKeyX { const publicKey = publicKeyFromPrivateKeyEd25519(privateKey); const secretKeyEd25519 = Buffer.concat([privateKey, publicKey]); const privateKeyX25519 = Buffer.allocUnsafeSlow( - sodium.crypto_box_SECRETKEYBYTES + sodium.crypto_box_SECRETKEYBYTES, ); sodium.crypto_sign_ed25519_sk_to_curve25519( privateKeyX25519, @@ -139,7 +141,7 @@ function privateKeyEd25519ToX25519(privateKey: PrivateKey): PrivateKeyX { function keyPairEd25519ToX25519(keyPair: KeyPair): KeyPairX { const publicKeyX25519 = publicKeyEd25519ToX25519(keyPair.publicKey); const privateKeyX25519 = Buffer.allocUnsafeSlow( - sodium.crypto_box_SECRETKEYBYTES + sodium.crypto_box_SECRETKEYBYTES, ); sodium.crypto_sign_ed25519_sk_to_curve25519( privateKeyX25519, @@ -209,7 +211,9 @@ function encryptWithPublicKey( recieverPublicKeyX25519, senderKeyPairX25519.privateKey, ); - const result = Buffer.allocUnsafeSlow(nonce.byteLength + macAndCipherText.byteLength); + const result = Buffer.allocUnsafeSlow( + nonce.byteLength + macAndCipherText.byteLength, + ); nonce.copy(result); macAndCipherText.copy(result, nonce.byteLength); // Note that no public key is concatenated here @@ -338,7 +342,10 @@ function verifyWithPublicKey( * Checks if data is a signature */ function isSignature(signature: unknown): signature is Signature { - return Buffer.isBuffer(signature) && signature.byteLength === sodium.crypto_sign_BYTES; + return ( + Buffer.isBuffer(signature) && + signature.byteLength === sodium.crypto_sign_BYTES + ); } /** diff --git a/src/keys/utils/hash.ts b/src/keys/utils/hash.ts index 94ae1a4c1..a9dc21b83 100644 --- a/src/keys/utils/hash.ts +++ b/src/keys/utils/hash.ts @@ -1,11 +1,5 @@ -import type { - MultihashDigest -} from 'multiformats/hashes/interface'; -import type { - Digest, - DigestCode, - DigestFormats, -} from '../types'; +import type { MultihashDigest } from 'multiformats/hashes/interface'; +import type { Digest, DigestCode, DigestFormats } from '../types'; import sodium from 'sodium-native'; import * as multiformats from 'multiformats'; import * as keysTypes from '../types'; @@ -13,9 +7,7 @@ import * as utils from '../../utils'; import * as errors from '../../errors'; function sha2256(data: BufferSource): Digest<'sha2-256'> { - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_hash_sha256_BYTES - ); + const digest = Buffer.allocUnsafeSlow(sodium.crypto_hash_sha256_BYTES); sodium.crypto_hash_sha256(digest, utils.bufferWrap(data)); return digest as Digest<'sha2-256'>; } @@ -25,13 +17,9 @@ function sha2256(data: BufferSource): Digest<'sha2-256'> { * Use `next()` to prime the generator. * Use `next(null)` to finish the consumer. */ -function *sha2256G(): Generator, BufferSource | null>{ - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_hash_sha256_BYTES - ); - const state = Buffer.allocUnsafe( - sodium.crypto_hash_sha256_STATEBYTES - ); +function* sha2256G(): Generator, BufferSource | null> { + const digest = Buffer.allocUnsafeSlow(sodium.crypto_hash_sha256_BYTES); + const state = Buffer.allocUnsafe(sodium.crypto_hash_sha256_STATEBYTES); sodium.crypto_hash_sha256_init(state); while (true) { const data = yield; @@ -47,12 +35,8 @@ function *sha2256G(): Generator, BufferSource | null>{ * Stream compute a SHA256 hash with iterable */ function sha2256I(data: Iterable): Digest<'sha2-256'> { - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_hash_sha256_BYTES - ); - const state = Buffer.allocUnsafe( - sodium.crypto_hash_sha256_STATEBYTES - ); + const digest = Buffer.allocUnsafeSlow(sodium.crypto_hash_sha256_BYTES); + const state = Buffer.allocUnsafe(sodium.crypto_hash_sha256_STATEBYTES); sodium.crypto_hash_sha256_init(state); for (const d of data) { sodium.crypto_hash_sha256_update(state, utils.bufferWrap(d)); @@ -62,9 +46,7 @@ function sha2256I(data: Iterable): Digest<'sha2-256'> { } function sha2512(data: BufferSource): Digest<'sha2-512'> { - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_hash_sha512_BYTES - ); + const digest = Buffer.allocUnsafeSlow(sodium.crypto_hash_sha512_BYTES); sodium.crypto_hash_sha512(digest, utils.bufferWrap(data)); return digest as Digest<'sha2-512'>; } @@ -74,13 +56,9 @@ function sha2512(data: BufferSource): Digest<'sha2-512'> { * Use `next()` to prime the generator. * Use `next(null)` to finish the consumer. */ -function *sha2512G(): Generator, BufferSource | null>{ - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_hash_sha512_BYTES - ); - const state = Buffer.allocUnsafe( - sodium.crypto_hash_sha512_STATEBYTES - ); +function* sha2512G(): Generator, BufferSource | null> { + const digest = Buffer.allocUnsafeSlow(sodium.crypto_hash_sha512_BYTES); + const state = Buffer.allocUnsafe(sodium.crypto_hash_sha512_STATEBYTES); sodium.crypto_hash_sha512_init(state); while (true) { const data = yield; @@ -96,12 +74,8 @@ function *sha2512G(): Generator, BufferSource | null>{ * Stream compute a SHA512 hash with iterable */ function sha2512I(data: Iterable): Digest<'sha2-512'> { - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_hash_sha512_BYTES - ); - const state = Buffer.allocUnsafe( - sodium.crypto_hash_sha512_STATEBYTES - ); + const digest = Buffer.allocUnsafeSlow(sodium.crypto_hash_sha512_BYTES); + const state = Buffer.allocUnsafe(sodium.crypto_hash_sha512_STATEBYTES); sodium.crypto_hash_sha512_init(state); for (const d of data) { sodium.crypto_hash_sha512_update(state, utils.bufferWrap(d)); @@ -113,16 +87,20 @@ function sha2512I(data: Iterable): Digest<'sha2-512'> { function sha2512256(data: BufferSource): Digest<'sha2-512-256'> { const digest = sha2512(data); const digestTruncated = Buffer.allocUnsafeSlow( - sodium.crypto_hash_sha256_BYTES + sodium.crypto_hash_sha256_BYTES, ); digest.copy(digestTruncated, 0, 0, sodium.crypto_hash_sha256_BYTES); return digestTruncated as Digest<'sha2-512-256'>; } -function *sha2512256G(): Generator, BufferSource | null> { +function* sha2512256G(): Generator< + void, + Digest<'sha2-512-256'>, + BufferSource | null +> { const digest = yield* sha2512G(); const digestTruncated = Buffer.allocUnsafeSlow( - sodium.crypto_hash_sha256_BYTES + sodium.crypto_hash_sha256_BYTES, ); digest.copy(digestTruncated, 0, 0, sodium.crypto_hash_sha256_BYTES); return digestTruncated as Digest<'sha2-512-256'>; @@ -131,16 +109,14 @@ function *sha2512256G(): Generator, BufferSource | function sha2512256I(data: Iterable): Digest<'sha2-512-256'> { const digest = sha2512I(data); const digestTruncated = Buffer.allocUnsafeSlow( - sodium.crypto_hash_sha256_BYTES + sodium.crypto_hash_sha256_BYTES, ); digest.copy(digestTruncated, 0, 0, sodium.crypto_hash_sha256_BYTES); return digestTruncated as Digest<'sha2-512-256'>; } function blake2b256(data: BufferSource): Digest<'blake2b-256'> { - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_generichash_BYTES - ); + const digest = Buffer.allocUnsafeSlow(sodium.crypto_generichash_BYTES); sodium.crypto_generichash(digest, utils.bufferWrap(data)); return digest as Digest<'blake2b-256'>; } @@ -150,14 +126,18 @@ function blake2b256(data: BufferSource): Digest<'blake2b-256'> { * This is a pre-primed generator. * Use `next(null)` to finish the consumer. */ -function *blake2b256G(): Generator, BufferSource | null>{ - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_generichash_BYTES +function* blake2b256G(): Generator< + void, + Digest<'blake2b-256'>, + BufferSource | null +> { + const digest = Buffer.allocUnsafeSlow(sodium.crypto_generichash_BYTES); + const state = Buffer.allocUnsafe(sodium.crypto_generichash_STATEBYTES); + sodium.crypto_generichash_init( + state, + undefined, + sodium.crypto_generichash_BYTES, ); - const state = Buffer.allocUnsafe( - sodium.crypto_generichash_STATEBYTES - ); - sodium.crypto_generichash_init(state, undefined, sodium.crypto_generichash_BYTES); while (true) { const data = yield; if (data === null) { @@ -172,13 +152,13 @@ function *blake2b256G(): Generator, BufferSource | n * Stream compute a BLAKE2b hash with iterable */ function blake2b256I(data: Iterable): Digest<'blake2b-256'> { - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_generichash_BYTES - ); - const state = Buffer.allocUnsafe( - sodium.crypto_generichash_STATEBYTES + const digest = Buffer.allocUnsafeSlow(sodium.crypto_generichash_BYTES); + const state = Buffer.allocUnsafe(sodium.crypto_generichash_STATEBYTES); + sodium.crypto_generichash_init( + state, + undefined, + sodium.crypto_generichash_BYTES, ); - sodium.crypto_generichash_init(state, undefined, sodium.crypto_generichash_BYTES); for (const d of data) { sodium.crypto_generichash_update(state, utils.bufferWrap(d)); } @@ -186,7 +166,10 @@ function blake2b256I(data: Iterable): Digest<'blake2b-256'> { return digest as Digest<'blake2b-256'>; } -function hash(data: BufferSource, format: F): Digest { +function hash( + data: BufferSource, + format: F, +): Digest { switch (format) { case 'sha2-256': return sha2256(data) as Digest; @@ -202,7 +185,7 @@ function hash(data: BufferSource, format: F): Digest } function hashG( - format: F + format: F, ): Generator, BufferSource | null> { switch (format) { case 'sha2-256': @@ -220,7 +203,7 @@ function hashG( function hashI( data: Iterable, - format: F + format: F, ): Digest { switch (format) { case 'sha2-256': @@ -238,23 +221,21 @@ function hashI( function digestToMultidigest( digest: Digest, - format: F + format: F, ): MultihashDigest> { const code = keysTypes.multihashCodes[format]; return multiformats.digest.create(code, digest); } function digestFromMultidigest( - multiDigest: unknown + multiDigest: unknown, ): MultihashDigest> | undefined { if (!utils.isBufferSource(multiDigest)) { return; } let digest: MultihashDigest; try { - digest = multiformats.digest.decode( - utils.bufferWrap(multiDigest) - ); + digest = multiformats.digest.decode(utils.bufferWrap(multiDigest)); } catch { // Fails if the length is incorrect return; diff --git a/src/keys/utils/jwk.ts b/src/keys/utils/jwk.ts index 91e44ae82..76eb13878 100644 --- a/src/keys/utils/jwk.ts +++ b/src/keys/utils/jwk.ts @@ -118,7 +118,9 @@ function privateKeyFromJWK(privateKeyJWK: JWK): PrivateKey | undefined { return; } // If the public key doesn't match, then the JWK is invalid - const publicKeyData_ = publicKeyFromPrivateKeyEd25519(privateKeyData as PrivateKey); + const publicKeyData_ = publicKeyFromPrivateKeyEd25519( + privateKeyData as PrivateKey, + ); if (!publicKeyData_.equals(publicKeyData)) { return; } @@ -149,7 +151,9 @@ function keyPairFromJWK(keyPair: KeyPairJWK): KeyPair | undefined { if (publicKey == null || privateKey == null) { return; } - const secretKey = Buffer.allocUnsafeSlow(privateKey.byteLength + publicKey.byteLength); + const secretKey = Buffer.allocUnsafeSlow( + privateKey.byteLength + publicKey.byteLength, + ); privateKey.copy(secretKey); publicKey.copy(secretKey, privateKey.byteLength); return { diff --git a/src/keys/utils/memory.ts b/src/keys/utils/memory.ts index d8fb2daf9..0517e1984 100644 --- a/src/keys/utils/memory.ts +++ b/src/keys/utils/memory.ts @@ -9,11 +9,11 @@ import * as keysErrors from '../errors'; */ function bufferLock( data: T, - strict: boolean = true + strict: boolean = true, ): asserts data is BufferLocked { try { // There's a limit to how much data can be locked - sodium.sodium_mlock(data) + sodium.sodium_mlock(data); } catch { // If strict, we will throw an exception for being unable to lock if (strict) { diff --git a/src/keys/utils/password.ts b/src/keys/utils/password.ts index 02b33baa5..6720ae947 100644 --- a/src/keys/utils/password.ts +++ b/src/keys/utils/password.ts @@ -78,7 +78,7 @@ function hashPassword( salt ??= getRandomBytes( sodium.crypto_pwhash_SALTBYTES, undefined, - false + false, ) as PasswordSalt; sodium.crypto_pwhash( hash, diff --git a/src/keys/utils/pem.ts b/src/keys/utils/pem.ts index 030d23a05..2a6efa780 100644 --- a/src/keys/utils/pem.ts +++ b/src/keys/utils/pem.ts @@ -25,7 +25,11 @@ function publicKeyToPEM(publicKey: PublicKey): PublicKeyPEM { subjectPublicKey: publicKey, }); const data = Buffer.from(asn1.AsnSerializer.serialize(spki)); - const contents = data.toString('base64').replace(/(.{64})/g, '$1\n').trimEnd() + '\n'; + const contents = + data + .toString('base64') + .replace(/(.{64})/g, '$1\n') + .trimEnd() + '\n'; return `-----BEGIN PUBLIC KEY-----\n${contents}-----END PUBLIC KEY-----\n` as PublicKeyPEM; } @@ -59,7 +63,11 @@ function privateKeyToPEM(privateKey: PrivateKey): PrivateKeyPEM { ), }); const data = Buffer.from(asn1.AsnSerializer.serialize(pkcs8)); - const contents = data.toString('base64').replace(/(.{64})/g, '$1\n').trimEnd() + '\n'; + const contents = + data + .toString('base64') + .replace(/(.{64})/g, '$1\n') + .trimEnd() + '\n'; return `-----BEGIN PRIVATE KEY-----\n${contents}-----END PRIVATE KEY-----\n` as PrivateKeyPEM; } @@ -109,7 +117,9 @@ function keyPairFromPEM(keyPair: KeyPairPEM): KeyPair | undefined { if (publicKey == null || privateKey == null) { return undefined; } - const secretKey = Buffer.allocUnsafeSlow(privateKey.byteLength + publicKey.byteLength); + const secretKey = Buffer.allocUnsafeSlow( + privateKey.byteLength + publicKey.byteLength, + ); privateKey.copy(secretKey); publicKey.copy(secretKey, privateKey.byteLength); return { diff --git a/src/keys/utils/random.ts b/src/keys/utils/random.ts index 5f0c0bf0d..eccf53379 100644 --- a/src/keys/utils/random.ts +++ b/src/keys/utils/random.ts @@ -7,7 +7,11 @@ import sodium from 'sodium-native'; * Set `pool` to false to acquire an unpooled buffer. * This means the underlying `ArrayBuffer` is safely transferrable. */ -function getRandomBytes(size: number, seedNumber?: number, pool = true): Buffer { +function getRandomBytes( + size: number, + seedNumber?: number, + pool = true, +): Buffer { let randomBytes: Buffer; if (pool) { randomBytes = Buffer.allocUnsafe(size); diff --git a/src/keys/utils/symmetric.ts b/src/keys/utils/symmetric.ts index 33f4a3b0a..19555548f 100644 --- a/src/keys/utils/symmetric.ts +++ b/src/keys/utils/symmetric.ts @@ -50,7 +50,9 @@ function encryptWithKey( key, ); // This ensures `result.buffer` is not using the shared internal pool - const result = Buffer.allocUnsafeSlow(nonceSize + macSize + plainText.byteLength); + const result = Buffer.allocUnsafeSlow( + nonceSize + macSize + plainText.byteLength, + ); nonce.copy(result); macAndCipherText.copy(result, nonceSize); return result; @@ -78,7 +80,9 @@ function decryptWithKey( const nonce = cipherText.subarray(0, nonceSize); const macAndCipherText = cipherText.subarray(nonceSize); // This ensures `plainText.buffer` is not using the shared internal pool - const plainText = Buffer.allocUnsafeSlow(macAndCipherText.byteLength - macSize); + const plainText = Buffer.allocUnsafeSlow( + macAndCipherText.byteLength - macSize, + ); // This returns the number of bytes that has been decrypted const decrypted = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt( plainText, @@ -95,20 +99,14 @@ function decryptWithKey( } function macWithKey(key: Key, data: Buffer): MAC { - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_generichash_BYTES - ); + const digest = Buffer.allocUnsafeSlow(sodium.crypto_generichash_BYTES); sodium.crypto_generichash(digest, data, key); return digest as Digest<'blake2b-256'>; } -function *macWithKeyG(key: Key): Generator{ - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_generichash_BYTES - ); - const state = Buffer.allocUnsafe( - sodium.crypto_generichash_STATEBYTES - ); +function* macWithKeyG(key: Key): Generator { + const digest = Buffer.allocUnsafeSlow(sodium.crypto_generichash_BYTES); + const state = Buffer.allocUnsafe(sodium.crypto_generichash_STATEBYTES); sodium.crypto_generichash_init(state, key, sodium.crypto_generichash_BYTES); while (true) { const data = yield; @@ -121,12 +119,8 @@ function *macWithKeyG(key: Key): Generator{ } function macWithKeyI(key: Key, data: Iterable): MAC { - const digest = Buffer.allocUnsafeSlow( - sodium.crypto_generichash_BYTES - ); - const state = Buffer.allocUnsafe( - sodium.crypto_generichash_STATEBYTES - ); + const digest = Buffer.allocUnsafeSlow(sodium.crypto_generichash_BYTES); + const state = Buffer.allocUnsafe(sodium.crypto_generichash_STATEBYTES); sodium.crypto_generichash_init(state, key, sodium.crypto_generichash_BYTES); for (const d of data) { sodium.crypto_generichash_update(state, utils.bufferWrap(d)); @@ -141,13 +135,20 @@ function authWithKey(key: Key, data: Buffer, digest: Buffer): boolean { return sodium.sodium_memcmp(digest_, digest); } -function *authWithKeyG(key: Key, digest: Buffer): Generator { - const digest_ = yield * macWithKeyG(key); +function* authWithKeyG( + key: Key, + digest: Buffer, +): Generator { + const digest_ = yield* macWithKeyG(key); if (digest_.byteLength !== digest.byteLength) return false; return sodium.sodium_memcmp(digest_, digest); } -function authWithKeyI(key: Key, data: Iterable, digest: Buffer): boolean { +function authWithKeyI( + key: Key, + data: Iterable, + digest: Buffer, +): boolean { const digest_ = macWithKeyI(key, data); if (digest_.byteLength !== digest.byteLength) return false; return sodium.sodium_memcmp(digest_, digest); @@ -157,7 +158,9 @@ function authWithKeyI(key: Key, data: Iterable, digest: Buffer): b * Checks if data is a MAC */ function isMAC(mac: unknown): mac is MAC { - return Buffer.isBuffer(mac) && mac.byteLength === sodium.crypto_generichash_BYTES; + return ( + Buffer.isBuffer(mac) && mac.byteLength === sodium.crypto_generichash_BYTES + ); } /** diff --git a/src/keys/utils/webcrypto.ts b/src/keys/utils/webcrypto.ts index e1b20b07d..a89631a7a 100644 --- a/src/keys/utils/webcrypto.ts +++ b/src/keys/utils/webcrypto.ts @@ -78,7 +78,9 @@ async function importKeyPair({ * This means the underlying `ArrayBuffer` is safely transferrable. */ async function exportPublicKey(publicCryptoKey: CryptoKey): Promise { - return Buffer.from(await webcrypto.subtle.exportKey('raw', publicCryptoKey)) as PublicKey; + return Buffer.from( + await webcrypto.subtle.exportKey('raw', publicCryptoKey), + ) as PublicKey; } /** @@ -87,7 +89,9 @@ async function exportPublicKey(publicCryptoKey: CryptoKey): Promise { * The returned buffers is guaranteed to unpooled. * This means the underlying `ArrayBuffer` is safely transferrable. */ -async function exportPrivateKey(privateCryptoKey: CryptoKey): Promise { +async function exportPrivateKey( + privateCryptoKey: CryptoKey, +): Promise { const privateJWK = await webcrypto.subtle.exportKey('jwk', privateCryptoKey); if (privateJWK.d == null) { throw new TypeError('Private key is not an Ed25519 private key'); @@ -110,7 +114,9 @@ async function exportKeyPair(keyPair: { }): Promise { const publicKey = await exportPublicKey(keyPair.publicKey); const privateKey = await exportPrivateKey(keyPair.privateKey); - const secretKey = Buffer.allocUnsafeSlow(privateKey.byteLength + publicKey.byteLength); + const secretKey = Buffer.allocUnsafeSlow( + privateKey.byteLength + publicKey.byteLength, + ); privateKey.copy(secretKey); publicKey.copy(secretKey, privateKey.byteLength); return { diff --git a/src/keys/utils/x509.ts b/src/keys/utils/x509.ts index 160e4b15d..4a7494670 100644 --- a/src/keys/utils/x509.ts +++ b/src/keys/utils/x509.ts @@ -140,7 +140,7 @@ async function generateCertificate({ duration: number; subjectAttrsExtra?: Array<{ [key: string]: Array }>; issuerAttrsExtra?: Array<{ [key: string]: Array }>; - now?: Date, + now?: Date; }): Promise { const subjectPublicKey = subjectKeyPair.publicKey; const subjectPublicCryptoKey = await importPublicKey( @@ -256,7 +256,10 @@ function certCertId(cert: Certificate): CertId | undefined { * This means the underlying `ArrayBuffer` is safely transferrable. */ function certPublicKey(cert: Certificate): PublicKey | undefined { - const spki = asn1.AsnConvert.parse(cert.publicKey.rawData, asn1X509.SubjectPublicKeyInfo); + const spki = asn1.AsnConvert.parse( + cert.publicKey.rawData, + asn1X509.SubjectPublicKeyInfo, + ); const publicKey = Buffer.from(spki.subjectPublicKey); if (!validatePublicKey(publicKey)) { return; @@ -353,7 +356,10 @@ function certNotExpiredBy(cert: Certificate, now: Date = new Date()): boolean { return cert.notBefore.getTime() <= time && time <= cert.notAfter.getTime(); } -function certRemainingDuration(cert: Certificate, now: Date = new Date()): number { +function certRemainingDuration( + cert: Certificate, + now: Date = new Date(), +): number { const time = now.getTime() - (now.getTime() % 1000); const duration = Math.max(cert.notAfter.getTime() - time, 0); return duration / 1000; @@ -421,7 +427,7 @@ function certFromASN1(certASN1: CertificateASN1): Certificate | undefined { } function certToPEM(cert: Certificate): CertificatePEM { - return cert.toString('pem') + '\n' as CertificatePEM; + return (cert.toString('pem') + '\n') as CertificatePEM; } function certFromPEM(certPEM: CertificatePEM): Certificate | undefined { diff --git a/src/network/ConnectionForward.ts b/src/network/ConnectionForward.ts index 0b01e2418..946d2961b 100644 --- a/src/network/ConnectionForward.ts +++ b/src/network/ConnectionForward.ts @@ -215,7 +215,7 @@ class ConnectionForward extends Connection { } const serverCertChain = networkUtils.getCertificateChain(this.tlsSocket); try { - this.nodeId_ = networkUtils.verifyServerCertificateChain( + this.nodeId_ = await networkUtils.verifyServerCertificateChain( this.nodeIds, serverCertChain, ); diff --git a/src/network/ConnectionReverse.ts b/src/network/ConnectionReverse.ts index 370f73339..c0f3c2997 100644 --- a/src/network/ConnectionReverse.ts +++ b/src/network/ConnectionReverse.ts @@ -293,7 +293,7 @@ class ConnectionReverse extends Connection { } const clientCertChain = networkUtils.getCertificateChain(tlsSocket); try { - networkUtils.verifyClientCertificateChain(clientCertChain); + await networkUtils.verifyClientCertificateChain(clientCertChain); } catch (e) { // Clean up partial compose if (!tlsSocket.destroyed) { diff --git a/src/network/utils.ts b/src/network/utils.ts index 872789a9a..774cbe7d9 100644 --- a/src/network/utils.ts +++ b/src/network/utils.ts @@ -2,10 +2,11 @@ import type { Socket } from 'net'; import type { TLSSocket } from 'tls'; import type { PromiseCancellable } from '@matrixai/async-cancellable'; import type { Host, Hostname, Port, Address, NetworkMessage } from './types'; -import type { Certificate, PublicKey } from '../keys/types'; +import type { Certificate } from '../keys/types'; import type { NodeId } from '../ids/types'; import type { ContextTimed } from '../contexts/types'; import type { NodeAddress } from 'nodes/types'; +import type { CertificateASN1 } from '../keys/types'; import { Buffer } from 'buffer'; import dns from 'dns'; import { IPv4, IPv6, Validator } from 'ip-num'; @@ -13,7 +14,6 @@ import * as networkErrors from './errors'; import timedCancellable from '../contexts/functions/timedCancellable'; import * as keysUtils from '../keys/utils'; import * as utils from '../utils'; -import { CertificateASN1 } from '../keys/types'; import { never } from '../utils'; const pingBuffer = serializeNetworkMessage({ @@ -286,10 +286,10 @@ function isTLSSocket(socket: Socket | TLSSocket): socket is TLSSocket { * It is possible that the server has a new NodeId. In that case we will * verify that the new NodeId is the true descendant of the target NodeId. */ -function verifyServerCertificateChain( +async function verifyServerCertificateChain( nodeIds: Array, certChain: Array, -): NodeId { +): Promise { if (!certChain.length) { throw new networkErrors.ErrorCertChainEmpty( 'No certificates available to verify', @@ -344,7 +344,7 @@ function verifyServerCertificateChain( }, ); } - if (!keysUtils.certNodeSigned(cert)) { + if (!(await keysUtils.certNodeSigned(cert))) { throw new networkErrors.ErrorCertChainSignatureInvalid( 'Chain certificate does not have a valid node-signature', { @@ -384,7 +384,10 @@ function verifyServerCertificateChain( certChild = certChain[certIndex - 1]; if ( !keysUtils.certIssuedBy(certParent, certChild) || - !keysUtils.certSignedBy(certParent, keysUtils.certPublicKey(certChild)!) + !(await keysUtils.certSignedBy( + certParent, + keysUtils.certPublicKey(certChild)!, + )) ) { throw new networkErrors.ErrorCertChainBroken( 'Chain certificate is not signed by parent certificate', @@ -406,7 +409,9 @@ function verifyServerCertificateChain( * Verify the client certificate chain when it connects to the server. * The server does have a target NodeId. This means we verify the entire chain. */ -function verifyClientCertificateChain(certChain: Array): void { +async function verifyClientCertificateChain( + certChain: Array, +): Promise { if (!certChain.length) { throw new networkErrors.ErrorCertChainEmpty( 'No certificates available to verify', @@ -454,7 +459,7 @@ function verifyClientCertificateChain(certChain: Array): void { }, ); } - if (!keysUtils.certNodeSigned(cert)) { + if (!(await keysUtils.certNodeSigned(cert))) { throw new networkErrors.ErrorCertChainSignatureInvalid( 'Chain certificate does not have a valid node-signature', { @@ -470,7 +475,10 @@ function verifyClientCertificateChain(certChain: Array): void { if (certNext != null) { if ( !keysUtils.certIssuedBy(certNext, cert) || - !keysUtils.certSignedBy(certNext, keysUtils.certPublicKey(cert)!) + !(await keysUtils.certSignedBy( + certNext, + keysUtils.certPublicKey(cert)!, + )) ) { throw new networkErrors.ErrorCertChainSignatureInvalid( 'Chain certificate is not signed by parent certificate', diff --git a/src/nodes/NodeManager.ts b/src/nodes/NodeManager.ts index 441f4de20..a2d783863 100644 --- a/src/nodes/NodeManager.ts +++ b/src/nodes/NodeManager.ts @@ -10,14 +10,25 @@ import type { NodeBucketIndex, NodeData, } from './types'; -import type { ClaimId, SignedClaim, SignedClaimEncoded } from '../claims/types'; +import type { + Claim, + ClaimId, + SignedClaim, + SignedClaimEncoded, +} from '../claims/types'; import type TaskManager from '../tasks/TaskManager'; -import type GestaltGraph from '../gestalts/GestaltGraph'; +import type GestaltGraph from '../gestalts/GestaltGraph'; import type { TaskHandler, TaskHandlerId, Task } from '../tasks/types'; import type { ContextTimed } from 'contexts/types'; import type { PromiseCancellable } from '@matrixai/async-cancellable'; import type { Host, Port } from '../network/types'; -import type { TokenHeaderSignatureEncoded, TokenPayloadEncoded } from '../tokens/types'; +import type { + TokenHeaderSignatureEncoded, + TokenPayloadEncoded, +} from '../tokens/types'; +import type { ClaimLinkNode } from '../claims/payloads/index'; +import type { ServerDuplexStream } from '@grpc/grpc-js'; +import type { AsyncGeneratorDuplexStream } from '../grpc/types'; import Logger from '@matrixai/logger'; import { StartStop, ready } from '@matrixai/async-init/dist/StartStop'; import { Semaphore, Lock } from '@matrixai/async-locks'; @@ -31,11 +42,12 @@ import * as nodesPB from '../proto/js/polykey/v1/nodes/nodes_pb'; import * as claimsErrors from '../claims/errors'; import * as keysUtils from '../keys/utils'; import { never } from '../utils/utils'; -import { decodeClaimId, encodeClaimId, parseSignedClaim } from '../claims/utils'; +import { + decodeClaimId, + encodeClaimId, + parseSignedClaim, +} from '../claims/utils'; import Token from '../tokens/Token'; -import { AsyncGeneratorDuplexStream } from '../grpc/types'; -import { ServerDuplexStream } from '@grpc/grpc-js'; -import { ClaimLinkNode } from '../claims/payloads/index'; const abortEphemeralTaskReason = Symbol('abort ephemeral task reason'); const abortSingletonTaskReason = Symbol('abort singleton task reason'); @@ -353,12 +365,14 @@ class NodeManager { const claimIdMessage = new nodesPB.ClaimId(); if (claimId != null) claimIdMessage.setClaimId(encodeClaimId(claimId)); const client = connection.getClient(); - for await (const agentClaim of client.nodesChainDataGet(claimIdMessage)) { + for await (const agentClaim of client.nodesChainDataGet( + claimIdMessage, + )) { if (ctx.signal.aborted) throw ctx.signal.reason; // Need to re-construct each claim const claimId: ClaimId = decodeClaimId(agentClaim.getClaimId())!; const payload = agentClaim.getPayload() as TokenPayloadEncoded; - const signatures = agentClaim.getSignaturesList().map(item => { + const signatures = agentClaim.getSignaturesList().map((item) => { return { protected: item.getProtected(), signature: item.getSignature(), @@ -367,21 +381,26 @@ class NodeManager { const signedClaimEncoded: SignedClaimEncoded = { payload, signatures, - } + }; const signedClaim = parseSignedClaim(signedClaimEncoded); // Verifying the claim - const issPublicKey = keysUtils.publicKeyFromNodeId(nodesUtils.decodeNodeId(signedClaim.payload.iss)!); - const subPublicKey = signedClaim.payload.typ === 'node' ? - keysUtils.publicKeyFromNodeId(nodesUtils.decodeNodeId(signedClaim.payload.iss)!) : - null; + const issPublicKey = keysUtils.publicKeyFromNodeId( + nodesUtils.decodeNodeId(signedClaim.payload.iss)!, + ); + const subPublicKey = + signedClaim.payload.typ === 'node' + ? keysUtils.publicKeyFromNodeId( + nodesUtils.decodeNodeId(signedClaim.payload.iss)!, + ) + : null; const token = Token.fromSigned(signedClaim); - if (token.verifyWithPublicKey(issPublicKey)) { + if (!token.verifyWithPublicKey(issPublicKey)) { this.logger.warn('Failed to verify issuing node'); continue; } if ( subPublicKey != null && - token.verifyWithPublicKey(subPublicKey) + !token.verifyWithPublicKey(subPublicKey) ) { this.logger.warn('Failed to verify subject node'); continue; @@ -408,23 +427,26 @@ class NodeManager { return this.claimNode(targetNodeId, tran); }); } - const [, claim] = await this.sigchain.addClaim({ - typ: 'node', - iss: nodesUtils.encodeNodeId(this.keyRing.getNodeId()), - sub: nodesUtils.encodeNodeId(targetNodeId), - }, + const [, claim] = await this.sigchain.addClaim( + { + typ: 'ClaimLinkNode', + iss: nodesUtils.encodeNodeId(this.keyRing.getNodeId()), + sub: nodesUtils.encodeNodeId(targetNodeId), + }, undefined, async (token) => { - await this.nodeConnectionManager.withConnF( + return this.nodeConnectionManager.withConnF( targetNodeId, async (conn) => { // 2. create the agentClaim message to send const halfSignedClaim = token.toSigned(); - const agentClaimMessage = nodesUtils.signedClaimToAgentClaimMessage(halfSignedClaim); + const agentClaimMessage = + nodesUtils.signedClaimToAgentClaimMessage(halfSignedClaim); const client = conn.getClient(); const genClaims = client.nodesCrossSignClaim(); + let fullySignedToken: Token; try { - await genClaims.write(agentClaimMessage) + await genClaims.write(agentClaimMessage); // 3. We expect to receive the doubly signed claim const readStatus = await genClaims.read(); if (readStatus.done) { @@ -432,17 +454,20 @@ class NodeManager { } const receivedClaim = readStatus.value; // We need to re-construct the token from the message - const [,signedClaim] = nodesUtils.agentClaimMessageToSignedClaim(receivedClaim); - const fullySignedToken = Token.fromSigned(signedClaim); + const [, signedClaim] = + nodesUtils.agentClaimMessageToSignedClaim(receivedClaim); + fullySignedToken = Token.fromSigned(signedClaim); // Check that the signatures are correct - const targetNodePublicKey = keysUtils.publicKeyFromNodeId(targetNodeId); + const targetNodePublicKey = + keysUtils.publicKeyFromNodeId(targetNodeId); if ( - !fullySignedToken.verifyWithPublicKey(this.keyRing.keyPair.publicKey) || + !fullySignedToken.verifyWithPublicKey( + this.keyRing.keyPair.publicKey, + ) || !fullySignedToken.verifyWithPublicKey(targetNodePublicKey) - ) throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); - - // With the claim token verified we can mutate the original token - token = fullySignedToken + ) { + throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); + } // Next stage is to process the claim for the other node const readStatus2 = await genClaims.read(); @@ -451,7 +476,8 @@ class NodeManager { } const receivedClaimRemote = readStatus2.value; // We need to re-construct the token from the message - const [,signedClaimRemote] = nodesUtils.agentClaimMessageToSignedClaim(receivedClaimRemote); + const [, signedClaimRemote] = + nodesUtils.agentClaimMessageToSignedClaim(receivedClaimRemote); // This is a singly signed claim, // we want to verify it before signing and sending back const signedTokenRemote = Token.fromSigned(signedClaimRemote); @@ -460,7 +486,10 @@ class NodeManager { } signedTokenRemote.signWithPrivateKey(this.keyRing.keyPair); // 4. X <- responds with double signing the X signed claim <- Y - const agentClaimMessageRemote = nodesUtils.signedClaimToAgentClaimMessage(signedTokenRemote.toSigned()); + const agentClaimMessageRemote = + nodesUtils.signedClaimToAgentClaimMessage( + signedTokenRemote.toSigned(), + ); await genClaims.write(agentClaimMessageRemote); // Check the stream is closed (should be closed by other side) @@ -472,38 +501,39 @@ class NodeManager { await genClaims.throw(e); throw e; } + return fullySignedToken; }, ctx, - ) + ); }, tran, ); // With the claim created we want to add it to the gestalt graph const issNodeInfo = { - nodeId: this.keyRing.getNodeId() - } + nodeId: this.keyRing.getNodeId(), + }; const subNodeInfo = { nodeId: targetNodeId, - } - await this.gestaltGraph.linkNodeAndNode( - issNodeInfo, - subNodeInfo, - { - claim: claim as SignedClaim, - meta: {}, - }, - ) + }; + await this.gestaltGraph.linkNodeAndNode(issNodeInfo, subNodeInfo, { + claim: claim as SignedClaim, + meta: {}, + }); } public async handleClaimNode( requestingNodeId: NodeId, - genClaims: AsyncGeneratorDuplexStream>, + genClaims: AsyncGeneratorDuplexStream< + nodesPB.AgentClaim, + nodesPB.AgentClaim, + ServerDuplexStream + >, tran?: DBTransaction, - ){ - if ( tran == null ) { + ) { + if (tran == null) { return this.db.withTransactionF((tran) => this.handleClaimNode(requestingNodeId, genClaims, tran), - ) + ); } const readStatus = await genClaims.read(); // If nothing to read, end and destroy @@ -511,69 +541,76 @@ class NodeManager { throw new claimsErrors.ErrorEmptyStream(); } const receivedMessage = readStatus.value; - const [,signedClaim] = nodesUtils.agentClaimMessageToSignedClaim(receivedMessage); + const [, signedClaim] = + nodesUtils.agentClaimMessageToSignedClaim(receivedMessage); const token = Token.fromSigned(signedClaim); // Verify if the token is signed - if (!token.verifyWithPublicKey(keysUtils.publicKeyFromNodeId(requestingNodeId))){ + if ( + !token.verifyWithPublicKey( + keysUtils.publicKeyFromNodeId(requestingNodeId), + ) + ) { throw new claimsErrors.ErrorSinglySignedClaimVerificationFailed(); } // If verified, add your own signature to the received claim token.signWithPrivateKey(this.keyRing.keyPair); - // return the signed claim + // Return the signed claim const doublySignedClaim = token.toSigned(); - const agentClaimMessage = nodesUtils.signedClaimToAgentClaimMessage(doublySignedClaim); - await genClaims.write(agentClaimMessage) + const agentClaimMessage = + nodesUtils.signedClaimToAgentClaimMessage(doublySignedClaim); + await genClaims.write(agentClaimMessage); // Now we want to send our own claim signed - const [, claim] = await this.sigchain.addClaim({ - typ: 'node', - iss: nodesUtils.encodeNodeId(this.keyRing.getNodeId()), - sub: nodesUtils.encodeNodeId(requestingNodeId), - }, + const [, claim] = await this.sigchain.addClaim( + { + typ: 'ClaimLinkNode', + iss: nodesUtils.encodeNodeId(requestingNodeId), + sub: nodesUtils.encodeNodeId(this.keyRing.getNodeId()), + }, undefined, async (token) => { const halfSignedClaim = token.toSigned(); - const agentClaimMessage = nodesUtils.signedClaimToAgentClaimMessage(halfSignedClaim); - await genClaims.write(agentClaimMessage) + const agentClaimMessage = + nodesUtils.signedClaimToAgentClaimMessage(halfSignedClaim); + await genClaims.write(agentClaimMessage); const readStatus = await genClaims.read(); if (readStatus.done) { throw new claimsErrors.ErrorEmptyStream(); } const receivedClaim = readStatus.value; // We need to re-construct the token from the message - const [,signedClaim] = nodesUtils.agentClaimMessageToSignedClaim(receivedClaim); + const [, signedClaim] = + nodesUtils.agentClaimMessageToSignedClaim(receivedClaim); const fullySignedToken = Token.fromSigned(signedClaim); // Check that the signatures are correct - const requestingNodePublicKey = keysUtils.publicKeyFromNodeId(requestingNodeId); + const requestingNodePublicKey = + keysUtils.publicKeyFromNodeId(requestingNodeId); if ( - !fullySignedToken.verifyWithPublicKey(this.keyRing.keyPair.publicKey) || + !fullySignedToken.verifyWithPublicKey( + this.keyRing.keyPair.publicKey, + ) || !fullySignedToken.verifyWithPublicKey(requestingNodePublicKey) - ) throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); - // With the claim token verified we can mutate the original token - token = fullySignedToken + ) { + throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed(); + } // Ending the stream await genClaims.next(null); + return fullySignedToken; }, - ) + ); // With the claim created we want to add it to the gestalt graph const issNodeInfo = { nodeId: requestingNodeId, - } + }; const subNodeInfo = { nodeId: this.keyRing.getNodeId(), - } - await this.gestaltGraph.linkNodeAndNode( - issNodeInfo, - subNodeInfo, - { - claim: claim as SignedClaim, - meta: {}, - }, - ) + }; + await this.gestaltGraph.linkNodeAndNode(issNodeInfo, subNodeInfo, { + claim: claim as SignedClaim, + meta: {}, + }); } - - /** * Retrieves the node Address from the NodeGraph * @param nodeId node ID of the target node diff --git a/src/nodes/errors.ts b/src/nodes/errors.ts index 2f70e4840..b57f6a6a4 100644 --- a/src/nodes/errors.ts +++ b/src/nodes/errors.ts @@ -100,8 +100,7 @@ class ErrorNodePingFailed extends ErrorNodes { } class ErrorNodePermissionDenied extends ErrorNodes { - static description = - 'Permission not given to do this action'; + static description = 'Permission not given to do this action'; exitCode = sysexits.NOHOST; } diff --git a/src/nodes/utils.ts b/src/nodes/utils.ts index 4bd96887d..4577a8634 100644 --- a/src/nodes/utils.ts +++ b/src/nodes/utils.ts @@ -1,5 +1,12 @@ import type { NodeBucket, NodeBucketIndex, NodeId } from './types'; import type { KeyPath } from '@matrixai/db'; +import type { ClaimId } from '../ids'; +import type { SignedClaim, SignedClaimEncoded, Claim } from '../claims/types'; +import type { + TokenPayloadEncoded, + TokenHeaderSignatureEncoded, + SignedToken, +} from '../tokens/types'; import { utils as dbUtils } from '@matrixai/db'; import { IdInternal } from '@matrixai/id'; import lexi from 'lexicographic-integer'; @@ -7,11 +14,9 @@ import * as nodesErrors from './errors'; import * as keysUtils from '../keys/utils'; import * as grpcErrors from '../grpc/errors'; import * as agentErrors from '../agent/errors'; -import { encodeNodeId, decodeNodeId, ClaimId, decodeClaimId } from '../ids'; +import { encodeNodeId, decodeNodeId, decodeClaimId } from '../ids'; import { bytes2BigInt } from '../utils'; import * as nodesPB from '../proto/js/polykey/v1/nodes/nodes_pb'; -import { SignedClaim, SignedClaimEncoded, Claim } from '../claims/types'; -import { TokenPayloadEncoded, TokenHeaderSignatureEncoded, SignedToken } from '../tokens/types'; import { parseSignedClaim } from '../claims/utils'; import * as claimsUtils from '../claims/utils'; @@ -322,10 +327,14 @@ function refreshBucketsDelayJitter( return (Math.random() - 0.5) * delay * jitterMultiplier; } -function agentClaimMessageToSignedClaim(receivedClaim: nodesPB.AgentClaim): [ClaimId | undefined, SignedClaim] { - const claimId: ClaimId | undefined = decodeClaimId(receivedClaim.getClaimId()); +function agentClaimMessageToSignedClaim( + receivedClaim: nodesPB.AgentClaim, +): [ClaimId | undefined, SignedClaim] { + const claimId: ClaimId | undefined = decodeClaimId( + receivedClaim.getClaimId(), + ); const payload = receivedClaim.getPayload() as TokenPayloadEncoded; - const signatures = receivedClaim.getSignaturesList().map(item => { + const signatures = receivedClaim.getSignaturesList().map((item) => { return { protected: item.getProtected(), signature: item.getSignature(), @@ -340,10 +349,11 @@ function agentClaimMessageToSignedClaim(receivedClaim: nodesPB.AgentClaim): [Cla } function signedClaimToAgentClaimMessage(halfSignedClaim: SignedToken) { - const halfSignedClaimEncoded = claimsUtils.generateSignedClaim(halfSignedClaim); + const halfSignedClaimEncoded = + claimsUtils.generateSignedClaim(halfSignedClaim); const agentClaimMessage = new nodesPB.AgentClaim(); agentClaimMessage.setPayload(halfSignedClaimEncoded.payload); - const signatureMessages = halfSignedClaimEncoded.signatures.map(item => { + const signatureMessages = halfSignedClaimEncoded.signatures.map((item) => { return new nodesPB.Signature() .setSignature(item.signature) .setProtected(item.protected); diff --git a/src/notifications/types.ts b/src/notifications/types.ts index 0a8a304bd..df95bdaa6 100644 --- a/src/notifications/types.ts +++ b/src/notifications/types.ts @@ -1,5 +1,5 @@ import type { Opaque } from '../types'; -import type { NotificationId, NodeIdEncoded } from '../ids/types'; +import type { NotificationId } from '../ids/types'; import type { VaultName, VaultActions, VaultIdEncoded } from '../vaults/types'; type GestaltInvite = { diff --git a/src/notifications/utils.ts b/src/notifications/utils.ts index 76532d649..31fb740b5 100644 --- a/src/notifications/utils.ts +++ b/src/notifications/utils.ts @@ -16,6 +16,7 @@ import * as validationErrors from '../validation/errors'; import * as utils from '../utils'; import * as ids from '../ids/index'; import { vaultActions } from '../vaults/types'; +import { never } from '../utils'; function constructGestaltInviteMessage(nodeId: NodeId): string { return `Keynode with ID ${nodeId} has invited this Keynode to join their Gestalt. Accept this invitation by typing the command: xxx`; @@ -40,7 +41,7 @@ async function generateNotification( ...notification, iat: Date.now() / 1000, }); - token.signWithPrivateKey(keyPair.privateKey) + token.signWithPrivateKey(keyPair.privateKey); return JSON.stringify(token.toJSON()) as SignedNotification; } @@ -48,29 +49,37 @@ async function generateNotification( * Verify, decode, validate, and return a notification. Assumes it was signed * using signNotification as a SignJWT. */ -async function verifyAndDecodeNotif(signedNotification: SignedNotification, nodeId: NodeId): Promise { +async function verifyAndDecodeNotif( + signedNotification: SignedNotification, + nodeId: NodeId, +): Promise { const token = Token.fromEncoded(JSON.parse(signedNotification)); - const issuerNodeId = nodesUtils.decodeNodeId(token.payload.iss)! + assertNotification(token.payload); + const issuerNodeId = nodesUtils.decodeNodeId(token.payload.iss); + if (issuerNodeId == null) never(); const issuerPublicKey = keysUtils.publicKeyFromNodeId(issuerNodeId); - if (!token.verifyWithPublicKey(issuerPublicKey)) + if (!token.verifyWithPublicKey(issuerPublicKey)) { throw new notificationsErrors.ErrorNotificationsVerificationFailed(); - if (token.payload.sub !== nodesUtils.encodeNodeId(nodeId)) + } + if (token.payload.sub !== nodesUtils.encodeNodeId(nodeId)) { throw new notificationsErrors.ErrorNotificationsInvalidDestination(); - const payload = token.payload; + } + const payload = token.payload; return parseNotification(payload); } /** * JSON schema validator for a notification type */ -function assertNotification(notification: unknown): asserts notification is Notification { +function assertNotification( + notification: unknown, +): asserts notification is Notification { if (!utils.isObject(notification)) { - throw new validationErrors.ErrorParse( - 'must be POJO', - ); + throw new validationErrors.ErrorParse('must be POJO'); } - if (notification['typ'] !== 'notification') + if (notification['typ'] !== 'notification') { throw new validationErrors.ErrorParse('Payload typ was not a notification'); + } if ( notification['iss'] == null || ids.decodeNodeId(notification['iss']) == null @@ -89,23 +98,16 @@ function assertNotification(notification: unknown): asserts notification is Noti } if (typeof notification['isRead'] !== 'boolean') { throw new validationErrors.ErrorParse( - '`isRead` property must be a boolean' + '`isRead` property must be a boolean', ); } // Checking the data const notificationData = notification['data']; - if ( - notificationData !== null && - !utils.isObject(notificationData) - ) { - throw new validationErrors.ErrorParse( - '`data` property must be a POJO' - ); + if (notificationData !== null && !utils.isObject(notificationData)) { + throw new validationErrors.ErrorParse('`data` property must be a POJO'); } if (typeof notificationData['type'] !== 'string') { - throw new validationErrors.ErrorParse( - '`type` property must be a string' - ); + throw new validationErrors.ErrorParse('`type` property must be a string'); } switch (notificationData['type']) { case 'GestaltInvite': @@ -119,14 +121,12 @@ function assertNotification(notification: unknown): asserts notification is Noti break; default: throw new validationErrors.ErrorParse( - '`type` property must be a valid type' + '`type` property must be a valid type', ); } } -function parseNotification( - signedNotification: unknown, -): Notification { +function parseNotification(signedNotification: unknown): Notification { assertNotification(signedNotification); return signedNotification; } @@ -136,15 +136,14 @@ function parseNotification( */ function assertGeneral(general: unknown): asserts general is General { if (!utils.isObject(general)) { - throw new validationErrors.ErrorParse( - 'must be POJO', - ); + throw new validationErrors.ErrorParse('must be POJO'); } - if (general['type'] !== 'General') + if (general['type'] !== 'General') { throw new validationErrors.ErrorParse('`type` property must be `General`'); + } if (typeof general['message'] !== 'string') { throw new validationErrors.ErrorParse( - '`message` property must be a string' + '`message` property must be a string', ); } } @@ -152,27 +151,33 @@ function assertGeneral(general: unknown): asserts general is General { /** * JSON schema validator for a GestaltInvite notification's data field */ -function assertGestaltInvite(gestaltInvite: unknown): asserts gestaltInvite is GestaltInvite { +function assertGestaltInvite( + gestaltInvite: unknown, +): asserts gestaltInvite is GestaltInvite { if (!utils.isObject(gestaltInvite)) { + throw new validationErrors.ErrorParse('must be POJO'); + } + if (gestaltInvite['type'] !== 'GestaltInvite') { throw new validationErrors.ErrorParse( - 'must be POJO', + '`type` property must be `GestaltInvite`', ); } - if (gestaltInvite['type'] !== 'GestaltInvite') - throw new validationErrors.ErrorParse('`type` property must be `GestaltInvite`'); } /** * JSON schema validator for a VaultShare notification's data field */ -function assertVaultShare(vaultShare: unknown): asserts vaultShare is VaultShare { +function assertVaultShare( + vaultShare: unknown, +): asserts vaultShare is VaultShare { if (!utils.isObject(vaultShare)) { + throw new validationErrors.ErrorParse('must be POJO'); + } + if (vaultShare['type'] !== 'VaultShare') { throw new validationErrors.ErrorParse( - 'must be POJO', + '`type` property must be `VaultShare`', ); } - if (vaultShare['type'] !== 'VaultShare') - throw new validationErrors.ErrorParse('`type` property must be `VaultShare`'); if ( vaultShare['vaultId'] == null || ids.decodeVaultId(vaultShare['vaultId']) == null @@ -183,22 +188,21 @@ function assertVaultShare(vaultShare: unknown): asserts vaultShare is VaultShare } if (typeof vaultShare['vaultName'] !== 'string') { throw new validationErrors.ErrorParse( - '`vaultName` property must be a string' + '`vaultName` property must be a string', ); } if ( vaultShare['actions'] !== null && !utils.isObject(vaultShare['actions']) ) { - throw new validationErrors.ErrorParse( - '`actions` property must be a POJO' - ); + throw new validationErrors.ErrorParse('`actions` property must be a POJO'); } - for (const action of vaultShare['actions']) { - if (!(action in vaultActions)) + for (const action of Object.keys(vaultShare['actions'])) { + if (vaultActions.find((i) => action === i) == null) { throw new validationErrors.ErrorParse( - '`actions` property must contain valid actions' + '`actions` property must contain valid actions', ); + } } } diff --git a/src/sessions/SessionManager.ts b/src/sessions/SessionManager.ts index 436287d04..f1de6c611 100644 --- a/src/sessions/SessionManager.ts +++ b/src/sessions/SessionManager.ts @@ -1,6 +1,7 @@ import type { DB, DBTransaction, LevelPath } from '@matrixai/db'; import type { SessionToken } from './types'; import type KeyRing from '../keys/KeyRing'; +import type { Key } from '../keys/types'; import Logger from '@matrixai/logger'; import { CreateDestroyStartStop, @@ -11,7 +12,6 @@ import * as sessionsUtils from './utils'; import * as sessionsErrors from './errors'; import * as keysUtils from '../keys/utils'; import * as nodesUtils from '../nodes/utils'; -import { Key } from '../keys/types'; interface SessionManager extends CreateDestroyStartStop {} @CreateDestroyStartStop( @@ -115,7 +115,10 @@ class SessionManager { iss: nodesUtils.encodeNodeId(this.keyRing.getNodeId()), sub: nodesUtils.encodeNodeId(this.keyRing.getNodeId()), }; - const key = await tranOrDb.get([...this.sessionsDbPath, 'key'], true) as Key; + const key = (await tranOrDb.get( + [...this.sessionsDbPath, 'key'], + true, + )) as Key; return await sessionsUtils.createSessionToken(payload, key!, expiry); } @@ -125,7 +128,10 @@ class SessionManager { tran?: DBTransaction, ): Promise { const tranOrDb = tran ?? this.db; - const key = await tranOrDb.get([...this.sessionsDbPath, 'key'], true) as Key; + const key = (await tranOrDb.get( + [...this.sessionsDbPath, 'key'], + true, + )) as Key; const result = await sessionsUtils.verifySessionToken(token, key!); return result !== undefined; } diff --git a/src/sessions/utils.ts b/src/sessions/utils.ts index b5f287498..6c93f6e42 100644 --- a/src/sessions/utils.ts +++ b/src/sessions/utils.ts @@ -1,7 +1,7 @@ import type { SessionToken } from './types'; +import type { TokenPayload } from '../tokens/types'; +import type { Key } from '../keys/types'; import Token from '../tokens/Token'; -import { TokenPayload } from '../tokens/types'; -import { Key } from '../keys/types'; /** * Create session token @@ -18,12 +18,13 @@ async function createSessionToken( key: Key, expiry?: number, ): Promise { - const expiry_ = expiry != null ? Math.round(Date.now() / 1000) + expiry : undefined + const expiry_ = + expiry != null ? Math.round(Date.now() / 1000) + expiry : undefined; const token = Token.fromPayload({ ...payload, exp: expiry_, iat: Date.now() / 1000, - }) + }); token.signWithKey(key); return JSON.stringify(token.toJSON()) as SessionToken; } @@ -42,7 +43,7 @@ async function verifySessionToken( const parsedToken = Token.fromEncoded(signedTokenEncoded); if (!parsedToken.verifyWithKey(key)) return; const expiry = parsedToken.payload.exp; - if (expiry != null && expiry < Math.round(Date.now() / 1000) ) return; + if (expiry != null && expiry < Math.round(Date.now() / 1000)) return; return parsedToken.payload; } catch (e) { return; diff --git a/src/sigchain/Sigchain.ts b/src/sigchain/Sigchain.ts index 7d01b0119..7b920d530 100644 --- a/src/sigchain/Sigchain.ts +++ b/src/sigchain/Sigchain.ts @@ -1,10 +1,7 @@ import type { DB, DBTransaction, LevelPath, KeyPath } from '@matrixai/db'; import type { ClaimInput } from './types'; import type KeyRing from '../keys/KeyRing'; -import type { - TokenSignature, - TokenHeaderSignatureJSON -} from '../tokens/types'; +import type { TokenSignature, TokenHeaderSignatureJSON } from '../tokens/types'; import type { ClaimId, Claim, @@ -17,8 +14,8 @@ import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; -import Token from '../tokens/Token'; import * as sigchainErrors from './errors'; +import Token from '../tokens/Token'; import * as claimsUtils from '../claims/utils'; import * as utils from '../utils'; @@ -72,7 +69,10 @@ class Sigchain { * The sequence number provides cardinal and ordinal information regarding a claim. * `Sigchain/lastSequenceNumber -> {SequenceNumber}}` */ - protected dbLastSequenceNumberPath: KeyPath = [...this.dbPath, 'lastSequenceNumber']; + protected dbLastSequenceNumberPath: KeyPath = [ + ...this.dbPath, + 'lastSequenceNumber', + ]; constructor({ db, @@ -144,7 +144,7 @@ class Sigchain { tran?: DBTransaction, ): Promise { const lastSequenceNumber = await (tran ?? this.db).get( - this.dbLastSequenceNumberPath + this.dbLastSequenceNumberPath, ); return lastSequenceNumber; } @@ -163,19 +163,29 @@ class Sigchain { } @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async getLastClaim(tran?: DBTransaction): Promise<[ClaimId, Claim] | undefined> { - for await (const claimEntry of this.getClaims({ order: 'desc', limit: 1}, tran)) { + public async getLastClaim( + tran?: DBTransaction, + ): Promise<[ClaimId, Claim] | undefined> { + for await (const claimEntry of this.getClaims( + { order: 'desc', limit: 1 }, + tran, + )) { return claimEntry; } return; } @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async getLastSignedClaim(tran?: DBTransaction): Promise<[ClaimId, SignedClaim] | undefined> { - for await (const signedClaimEntry of this.getSignedClaims({ - order: 'desc', - limit: 1 - }, tran)) { + public async getLastSignedClaim( + tran?: DBTransaction, + ): Promise<[ClaimId, SignedClaim] | undefined> { + for await (const signedClaimEntry of this.getSignedClaims( + { + order: 'desc', + limit: 1, + }, + tran, + )) { return signedClaimEntry; } return; @@ -192,10 +202,7 @@ class Sigchain { if (tran == null) { return this.db.withTransactionF((tran) => this.getClaim(claimId, tran)); } - return tran.get([ - ... this.dbClaimsPath, - claimId.toBuffer(), - ]); + return tran.get([...this.dbClaimsPath, claimId.toBuffer()]); } /** @@ -207,10 +214,12 @@ class Sigchain { tran?: DBTransaction, ): Promise { if (tran == null) { - return this.db.withTransactionF((tran) => this.getSignedClaim(claimId, tran)); + return this.db.withTransactionF((tran) => + this.getSignedClaim(claimId, tran), + ); } const claim = await tran.get([ - ... this.dbClaimsPath, + ...this.dbClaimsPath, claimId.toBuffer(), ]); if (claim == null) { @@ -219,7 +228,7 @@ class Sigchain { const claimSignatures = await this.getSignatures(claimId, tran); return { payload: claim, - signatures: claimSignatures + signatures: claimSignatures, }; } @@ -232,19 +241,26 @@ class Sigchain { tran?: DBTransaction, ): Promise> { if (tran == null) { - return this.db.withTransactionF((tran) => this.getSignatures(claimId, tran)); + return this.db.withTransactionF((tran) => + this.getSignatures(claimId, tran), + ); } const headerSignatures: Array = []; - for await (const [, headerSignatureJSON] of tran.iterator( + for await (const [ + , + headerSignatureJSON, + ] of tran.iterator( [...this.dbSignaturesPath, claimId.toBuffer()], { keys: false, - valueAsBuffer: false - } + valueAsBuffer: false, + }, )) { headerSignatures.push({ protected: headerSignatureJSON.protected, - signature: Buffer.from(headerSignatureJSON.signature.data) as TokenSignature + signature: Buffer.from( + headerSignatureJSON.signature.data, + ) as TokenSignature, }); } return headerSignatures; @@ -258,25 +274,34 @@ class Sigchain { { order = 'asc', seek, - limit + limit, }: { order?: 'asc' | 'desc'; seek?: ClaimId; limit?: number; } = {}, - tran?: DBTransaction + tran?: DBTransaction, ): AsyncGenerator<[ClaimId, Claim]> { if (tran == null) { - return yield* this.db.withTransactionG((tran) => this.getClaims({ order, seek }, tran)); + return yield* this.db.withTransactionG((tran) => + this.getClaims({ order, seek, limit }, tran), + ); } - const orderOptions = (order === 'asc') ? { reverse: false } : { reverse: true }; - let seekOptions: { gte: [ClaimId] } | { lte: [ClaimId] } | {} = {}; + const orderOptions = + order === 'asc' ? { reverse: false } : { reverse: true }; + let seekOptions: + | { gte: [Buffer] } + | { lte: [Buffer] } + | Record = {}; if (seek != null) { - seekOptions = (order === 'asc') ? { - gte: [seek.toBuffer()], - } : { - lte: [seek.toBuffer()], - }; + seekOptions = + order === 'asc' + ? { + gte: [seek.toBuffer()], + } + : { + lte: [seek.toBuffer()], + }; } for await (const [kP, claim] of tran.iterator(this.dbClaimsPath, { valueAsBuffer: false, @@ -297,25 +322,34 @@ class Sigchain { { order = 'asc', seek, - limit + limit, }: { order?: 'asc' | 'desc'; seek?: ClaimId; limit?: number; } = {}, - tran?: DBTransaction + tran?: DBTransaction, ): AsyncGenerator<[ClaimId, SignedClaim]> { if (tran == null) { - return yield* this.db.withTransactionG((tran) => this.getSignedClaims({ order, seek }, tran)); + return yield* this.db.withTransactionG((tran) => + this.getSignedClaims({ order, seek }, tran), + ); } - const orderOptions = (order === 'asc') ? { reverse: false } : { reverse: true }; - let seekOptions: { gte: [ClaimId] } | { lte: [ClaimId] } | {} = {}; + const orderOptions = + order === 'asc' ? { reverse: false } : { reverse: true }; + let seekOptions: + | { gte: [Buffer] } + | { lte: [Buffer] } + | Record = {}; if (seek != null) { - seekOptions = (order === 'asc') ? { - gte: [seek.toBuffer()], - } : { - lte: [seek.toBuffer()], - }; + seekOptions = + order === 'asc' + ? { + lte: [seek.toBuffer()], + } + : { + gte: [seek.toBuffer()], + }; } for await (const [kP, claim] of tran.iterator(this.dbClaimsPath, { valueAsBuffer: false, @@ -329,8 +363,8 @@ class Sigchain { claimId, { payload: claim, - signatures: claimSignatures - } + signatures: claimSignatures, + }, ]; } } @@ -345,16 +379,13 @@ class Sigchain { public async addClaim( data: ClaimInput, date: Date = new Date(), - signingHook?: (token: Token) => Promise, + signingHook?: (token: Token) => Promise>, tran?: DBTransaction, ): Promise<[ClaimId, SignedClaim]> { if (tran == null) { - return this.db.withTransactionF((tran) => this.addClaim( - data, - date, - signingHook, - tran - )); + return this.db.withTransactionF((tran) => + this.addClaim(data, date, signingHook, tran), + ); } // Appending is a serialised operation await this.lockLastClaimId(tran); @@ -376,11 +407,11 @@ class Sigchain { const prevClaimId = prevSignedClaim[0]; const prevDigest = claimsUtils.hashSignedClaim( prevSignedClaim[1], - 'blake2b-256' + 'blake2b-256', ); const prevDigestEncoded = claimsUtils.encodeSignedClaimDigest( prevDigest, - 'blake2b-256' + 'blake2b-256', ); claim = { ...data, @@ -402,24 +433,18 @@ class Sigchain { prevDigest: null, }; } - const claimToken = Token.fromPayload(claim); + let claimToken = Token.fromPayload(claim); // Sign all claims with this node's keypair - claimToken.signWithPrivateKey( - this.keyRing.keyPair - ); + claimToken.signWithPrivateKey(this.keyRing.keyPair); if (signingHook != null) { - await signingHook(claimToken); + claimToken = await signingHook(claimToken); } const signedClaim = claimToken.toSigned(); await tran.put([...this.dbClaimsPath, claimIdBuffer], signedClaim.payload); for (const [index, headerSignature] of signedClaim.signatures.entries()) { await tran.put( - [ - ...this.dbSignaturesPath, - claimIdBuffer, - utils.lexiPackBuffer(index) - ], - headerSignature + [...this.dbSignaturesPath, claimIdBuffer, utils.lexiPackBuffer(index)], + headerSignature, ); } await tran.put(this.dbLastClaimIdPath, claimIdBuffer, true); diff --git a/src/sigchain/types.ts b/src/sigchain/types.ts index 63e03b9f7..e062cac0f 100644 --- a/src/sigchain/types.ts +++ b/src/sigchain/types.ts @@ -7,8 +7,6 @@ import type { ClaimDefault } from '../claims/types'; */ type ClaimInput = TokenPayload & { [Property in keyof ClaimDefault]?: undefined; -} - -export type { - ClaimInput, }; + +export type { ClaimInput }; diff --git a/src/tokens/Token.ts b/src/tokens/Token.ts index e23e51543..80cb43841 100644 --- a/src/tokens/Token.ts +++ b/src/tokens/Token.ts @@ -7,13 +7,8 @@ import type { SignedToken, SignedTokenEncoded, } from './types'; -import type { - Key, - PublicKey, - PrivateKey, - KeyPair -} from '../keys/types'; -import type { POJO, DeepReadonly } from '../types'; +import type { Key, PublicKey, PrivateKey, KeyPair } from '../keys/types'; +import type { POJO } from '../types'; import * as tokensUtils from './utils'; import * as tokensErrors from './errors'; import * as ids from '../ids'; @@ -41,21 +36,21 @@ class Token

{ protected signatureSet: Set = new Set(); public static fromPayload

( - payload: P + payload: P, ): Token

{ const payloadEncoded = tokensUtils.generateTokenPayload(payload); return new this(payload, payloadEncoded); } public static fromSigned

( - tokenSigned: SignedToken

+ tokenSigned: SignedToken

, ): Token

{ const tokenSignedEncoded = tokensUtils.generateSignedToken(tokenSigned); return new this( tokenSigned.payload, tokenSignedEncoded.payload, tokenSigned.signatures, - tokenSignedEncoded.signatures + tokenSignedEncoded.signatures, ); } @@ -64,7 +59,7 @@ class Token

{ * It is up the caller to decide what the payload type should be. */ public static fromEncoded

( - signedTokenEncoded: SignedTokenEncoded + signedTokenEncoded: SignedTokenEncoded, ): Token

{ let signedToken: SignedToken

; try { @@ -80,7 +75,7 @@ class Token

{ signedToken.payload, signedTokenEncoded.payload, signedToken.signatures, - signedTokenEncoded.signatures + signedTokenEncoded.signatures, ); } @@ -88,7 +83,7 @@ class Token

{ payload: P, payloadEncoded: TokenPayloadEncoded, signatures: Array = [], - signaturesEncoded: Array = [] + signaturesEncoded: Array = [], ) { this.payload = payload; this.payloadEncoded = payloadEncoded; @@ -103,41 +98,39 @@ class Token

{ return this._signatures; } - public get signaturesEncoded(): Readonly>> { + public get signaturesEncoded(): Readonly< + Array> + > { return this._signaturesEncoded; } public signWithKey( key: Key, additionalProtectedHeader?: POJO, - force: boolean = false + force: boolean = false, ): void { const protectedHeader = { ...additionalProtectedHeader, - alg: 'BLAKE2b' as const + alg: 'BLAKE2b' as const, }; - const protectedHeaderEncoded = tokensUtils.generateTokenProtectedHeader( - protectedHeader - ); + const protectedHeaderEncoded = + tokensUtils.generateTokenProtectedHeader(protectedHeader); const data = Buffer.from( this.payloadEncoded + '.' + protectedHeaderEncoded, - 'ascii' + 'ascii', ); const signature = keysUtils.macWithKey(key, data); const signatureEncoded = tokensUtils.generateTokenSignature(signature); - if ( - !force && - this.signatureSet.has(signatureEncoded) - ) { + if (!force && this.signatureSet.has(signatureEncoded)) { throw new tokensErrors.ErrorTokensDuplicateSignature(); } this._signatures.push({ protected: protectedHeader, - signature: signature + signature: signature, }); this._signaturesEncoded.push({ protected: protectedHeaderEncoded, - signature: signatureEncoded + signature: signatureEncoded, }); this.signatureSet.add(signatureEncoded); } @@ -145,31 +138,29 @@ class Token

{ public signWithPrivateKey( privateKeyOrKeyPair: PrivateKey | KeyPair, additionalProtectedHeader?: POJO, - force: boolean = false + force: boolean = false, ): void { let keyPair: KeyPair; if (Buffer.isBuffer(privateKeyOrKeyPair)) { - const publicKey = keysUtils.publicKeyFromPrivateKeyEd25519( - privateKeyOrKeyPair - ); + const publicKey = + keysUtils.publicKeyFromPrivateKeyEd25519(privateKeyOrKeyPair); keyPair = keysUtils.makeKeyPair(publicKey, privateKeyOrKeyPair); } else { keyPair = privateKeyOrKeyPair; } const kid = ids.encodeNodeId( - keysUtils.publicKeyToNodeId(keyPair.publicKey) + keysUtils.publicKeyToNodeId(keyPair.publicKey), ); const protectedHeader = { ...additionalProtectedHeader, alg: 'EdDSA' as const, - kid + kid, }; - const protectedHeaderEncoded = tokensUtils.generateTokenProtectedHeader( - protectedHeader - ); + const protectedHeaderEncoded = + tokensUtils.generateTokenProtectedHeader(protectedHeader); const data = Buffer.from( this.payloadEncoded + '.' + protectedHeaderEncoded, - 'ascii' + 'ascii', ); const signature = keysUtils.signWithPrivateKey(keyPair, data); const signatureEncoded = tokensUtils.generateTokenSignature(signature); @@ -178,11 +169,11 @@ class Token

{ } const headerSignature = { protected: protectedHeader, - signature: signature + signature: signature, }; const headerSignatureEncoded = { protected: protectedHeaderEncoded, - signature: signatureEncoded + signature: signatureEncoded, }; this._signatures.push(headerSignature); this._signaturesEncoded.push(headerSignatureEncoded); @@ -201,13 +192,9 @@ class Token

{ } const data = Buffer.from( this.payloadEncoded + '.' + headerSignatureEncoded.protected, - 'ascii' - ); - const auth = keysUtils.authWithKey( - key, - data, - headerSignature.signature + 'ascii', ); + const auth = keysUtils.authWithKey(key, data, headerSignature.signature); if (!auth) continue; return true; } @@ -226,7 +213,7 @@ class Token

{ } const data = Buffer.from( this.payloadEncoded + '.' + headerSignatureEncoded.protected, - 'ascii' + 'ascii', ); const auth = keysUtils.verifyWithPublicKey( publicKey, diff --git a/src/tokens/errors.ts b/src/tokens/errors.ts index 361aac48e..b4d105ff8 100644 --- a/src/tokens/errors.ts +++ b/src/tokens/errors.ts @@ -12,8 +12,4 @@ class ErrorTokensSignedParse extends ErrorTokens { exitCode = sysexits.USAGE; } -export { - ErrorTokens, - ErrorTokensDuplicateSignature, - ErrorTokensSignedParse, -}; +export { ErrorTokens, ErrorTokensDuplicateSignature, ErrorTokensSignedParse }; diff --git a/src/tokens/schemas/index.ts b/src/tokens/schemas/index.ts index bbf446459..1b13c2b38 100644 --- a/src/tokens/schemas/index.ts +++ b/src/tokens/schemas/index.ts @@ -5,13 +5,7 @@ import SignedTokenEncodedSchema from './SignedTokenEncodedSchema.json'; const ajv = new Ajv(); -const validateSignedTokenEncoded: ValidateFunction< - SignedTokenEncoded -> = ajv.compile( - SignedTokenEncodedSchema -); +const validateSignedTokenEncoded: ValidateFunction = + ajv.compile(SignedTokenEncodedSchema); -export { - SignedTokenEncodedSchema, - validateSignedTokenEncoded -}; +export { SignedTokenEncodedSchema, validateSignedTokenEncoded }; diff --git a/src/tokens/types.ts b/src/tokens/types.ts index 9989ec934..a0b6ef8bf 100644 --- a/src/tokens/types.ts +++ b/src/tokens/types.ts @@ -1,6 +1,6 @@ import type { Opaque, JSONValue } from '../types'; import type { Signature, MAC } from '../keys/types'; -import type { NodeIdEncoded, } from '../ids/types'; +import type { NodeIdEncoded } from '../ids/types'; /** * Token based on JWT specification. @@ -30,20 +30,25 @@ type TokenPayloadEncoded = Opaque<'TokenPayloadEncoded', string>; /** * Token header properties based on JWT specification */ -type TokenProtectedHeader = { - alg: 'EdDSA'; - kid: NodeIdEncoded; - [key: string]: JSONValue; -} | { - alg: 'BLAKE2b'; - [key: string]: JSONValue; -}; +type TokenProtectedHeader = + | { + alg: 'EdDSA'; + kid: NodeIdEncoded; + [key: string]: JSONValue; + } + | { + alg: 'BLAKE2b'; + [key: string]: JSONValue; + }; /** * Encoded token header * `base64url(json(TokenHeader))` */ -type TokenProtectedHeaderEncoded = Opaque<'TokenProtectedHeaderEncoded', string>; +type TokenProtectedHeaderEncoded = Opaque< + 'TokenProtectedHeaderEncoded', + string +>; /** * Signature can either be Ed25519 signature or BLAKE2b MAC code @@ -98,7 +103,10 @@ type SignedToken

= { /** * Token that is signed in JSON */ -type SignedTokenJSON

= Omit, 'signatures'> & { +type SignedTokenJSON

= Omit< + SignedToken

, + 'signatures' +> & { signatures: Array; }; diff --git a/src/tokens/utils.ts b/src/tokens/utils.ts index 3ccdda1d8..c19d0dec4 100644 --- a/src/tokens/utils.ts +++ b/src/tokens/utils.ts @@ -24,36 +24,36 @@ function generateTokenPayload(payload: TokenPayload): TokenPayloadEncoded { } function generateTokenProtectedHeader( - header: TokenProtectedHeader + header: TokenProtectedHeader, ): TokenProtectedHeaderEncoded { - const headerJSON = canonicalize(header)! + const headerJSON = canonicalize(header)!; const headerData = Buffer.from(headerJSON, 'utf-8'); return headerData.toString('base64url') as TokenProtectedHeaderEncoded; } function generateTokenSignature( - signature: TokenSignature + signature: TokenSignature, ): TokenSignatureEncoded { return signature.toString('base64url') as TokenSignatureEncoded; } function generateTokenHeaderSignature( - tokenHeaderSignature: TokenHeaderSignature + tokenHeaderSignature: TokenHeaderSignature, ): TokenHeaderSignatureEncoded { return { protected: generateTokenProtectedHeader(tokenHeaderSignature.protected), - signature: generateTokenSignature(tokenHeaderSignature.signature) + signature: generateTokenSignature(tokenHeaderSignature.signature), }; } function generateSignedToken(signed: SignedToken): SignedTokenEncoded { const payload = generateTokenPayload(signed.payload); const signatures = signed.signatures.map((tokenHeaderSignature) => - generateTokenHeaderSignature(tokenHeaderSignature) + generateTokenHeaderSignature(tokenHeaderSignature), ); return { payload, - signatures + signatures, }; } @@ -61,16 +61,12 @@ function generateSignedToken(signed: SignedToken): SignedTokenEncoded { * Parses `TokenPayloadEncoded` to `TokenPayload` */ function parseTokenPayload

( - tokenPayloadEncoded: unknown + tokenPayloadEncoded: unknown, ): P { if (typeof tokenPayloadEncoded !== 'string') { - throw new validationErrors.ErrorParse( - 'must be a string', - ); + throw new validationErrors.ErrorParse('must be a string'); } - const tokenPayloadData = Buffer.from( - tokenPayloadEncoded, 'base64url' - ); + const tokenPayloadData = Buffer.from(tokenPayloadEncoded, 'base64url'); const tokenPayloadJSON = tokenPayloadData.toString('utf-8'); let tokenPayload; try { @@ -86,19 +82,12 @@ function parseTokenPayload

( ); } if ('iss' in tokenPayload && typeof tokenPayload['iss'] !== 'string') { - throw new validationErrors.ErrorParse( - '`iss` property must be a string', - ); + throw new validationErrors.ErrorParse('`iss` property must be a string'); } if ('sub' in tokenPayload && typeof tokenPayload['sub'] !== 'string') { - throw new validationErrors.ErrorParse( - '`sub` property must be a string', - ); + throw new validationErrors.ErrorParse('`sub` property must be a string'); } - if ( - 'aud' in tokenPayload && - typeof tokenPayload['aud'] !== 'string' - ) { + if ('aud' in tokenPayload && typeof tokenPayload['aud'] !== 'string') { if (!Array.isArray(tokenPayload['aud'])) { throw new validationErrors.ErrorParse( '`aud` property must be a string or array of strings', @@ -113,24 +102,16 @@ function parseTokenPayload

( } } if ('exp' in tokenPayload && typeof tokenPayload['exp'] !== 'number') { - throw new validationErrors.ErrorParse( - '`exp` property must be a number', - ); + throw new validationErrors.ErrorParse('`exp` property must be a number'); } if ('nbf' in tokenPayload && typeof tokenPayload['nbf'] !== 'number') { - throw new validationErrors.ErrorParse( - '`nbf` property must be a number', - ); + throw new validationErrors.ErrorParse('`nbf` property must be a number'); } if ('iat' in tokenPayload && typeof tokenPayload['iat'] !== 'number') { - throw new validationErrors.ErrorParse( - '`iat` property must be a number', - ); + throw new validationErrors.ErrorParse('`iat` property must be a number'); } if ('jti' in tokenPayload && typeof tokenPayload['jti'] !== 'string') { - throw new validationErrors.ErrorParse( - '`jti` property must be a string', - ); + throw new validationErrors.ErrorParse('`jti` property must be a string'); } return tokenPayload as P; } @@ -139,15 +120,14 @@ function parseTokenPayload

( * Parses `TokenProtectedHeaderEncoded` to `TokenProtectedHeader` */ function parseTokenProtectedHeader( - tokenProtectedHeaderEncoded: unknown + tokenProtectedHeaderEncoded: unknown, ): TokenProtectedHeader { if (typeof tokenProtectedHeaderEncoded !== 'string') { - throw new validationErrors.ErrorParse( - 'must be a string', - ); + throw new validationErrors.ErrorParse('must be a string'); } const tokenProtectedHeaderData = Buffer.from( - tokenProtectedHeaderEncoded, 'base64url' + tokenProtectedHeaderEncoded, + 'base64url', ); const tokenProtectedHeaderJSON = tokenProtectedHeaderData.toString('utf-8'); let tokenProtectedHeader: any; @@ -164,9 +144,7 @@ function parseTokenProtectedHeader( ); } if (typeof tokenProtectedHeader['alg'] !== 'string') { - throw new validationErrors.ErrorParse( - '`alg` property must be a string', - ); + throw new validationErrors.ErrorParse('`alg` property must be a string'); } if ( tokenProtectedHeader['alg'] !== 'EdDSA' && @@ -187,15 +165,12 @@ function parseTokenProtectedHeader( return tokenProtectedHeader as TokenProtectedHeader; } - /** * Parses `TokenSignatureEncoded` to `TokenSignature` */ function parseTokenSignature(tokenSignatureEncoded: unknown): TokenSignature { if (typeof tokenSignatureEncoded !== 'string') { - throw new validationErrors.ErrorParse( - 'must be a string', - ); + throw new validationErrors.ErrorParse('must be a string'); } const signature = Buffer.from(tokenSignatureEncoded, 'base64url'); if (!keysUtils.isSignature(signature) && !keysUtils.isMAC(signature)) { @@ -210,12 +185,10 @@ function parseTokenSignature(tokenSignatureEncoded: unknown): TokenSignature { * Parses `TokenHeaderSignatureEncoded` to `TokenHeaderSignature` */ function parseTokenHeaderSignature( - tokenHeaderSignatureEncoded: unknown + tokenHeaderSignatureEncoded: unknown, ): TokenHeaderSignature { if (!utils.isObject(tokenHeaderSignatureEncoded)) { - throw new validationErrors.ErrorParse( - 'must be a JSON POJO', - ); + throw new validationErrors.ErrorParse('must be a JSON POJO'); } if (!('protected' in tokenHeaderSignatureEncoded)) { throw new validationErrors.ErrorParse( @@ -228,10 +201,10 @@ function parseTokenHeaderSignature( ); } const protectedHeader = parseTokenProtectedHeader( - tokenHeaderSignatureEncoded['protected'] + tokenHeaderSignatureEncoded['protected'], ); const signature = parseTokenSignature( - tokenHeaderSignatureEncoded['signature'] + tokenHeaderSignatureEncoded['signature'], ); return { protected: protectedHeader, @@ -239,22 +212,17 @@ function parseTokenHeaderSignature( }; } - /** * Parses `SignedTokenEncoded` to `SignedToken` */ function parseSignedToken

( - signedTokenEncoded: unknown + signedTokenEncoded: unknown, ): SignedToken

{ if (!utils.isObject(signedTokenEncoded)) { - throw new validationErrors.ErrorParse( - 'must be a JSON POJO', - ); + throw new validationErrors.ErrorParse('must be a JSON POJO'); } if (!('payload' in signedTokenEncoded)) { - throw new validationErrors.ErrorParse( - '`payload` property must be defined', - ); + throw new validationErrors.ErrorParse('`payload` property must be defined'); } if (!('signatures' in signedTokenEncoded)) { throw new validationErrors.ErrorParse( @@ -269,12 +237,14 @@ function parseSignedToken

( } const signatures: Array = []; for (const headerSignatureEncoded of signedTokenEncoded['signatures']) { - const tokenHeaderSignature = parseTokenHeaderSignature(headerSignatureEncoded); + const tokenHeaderSignature = parseTokenHeaderSignature( + headerSignatureEncoded, + ); signatures.push(tokenHeaderSignature); } return { payload, - signatures + signatures, }; } diff --git a/src/types.ts b/src/types.ts index 2f5ac8121..9a5289884 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,12 +14,12 @@ type POJO = { [key: string]: any }; * `JSON.stringify` automatically converts `undefined` to `null. */ type JSONValue = - { [key: string]: JSONValue } | - Array | - string | - number | - boolean | - null; + | { [key: string]: JSONValue } + | Array + | string + | number + | boolean + | null; /** * Opaque types are wrappers of existing types @@ -134,19 +134,20 @@ type NonFunctionPropertyNames = { */ type NonFunctionProperties = Pick>; - /** * Finds the key type corresponding to a value type for a record type */ type RecordKeyFromValue = { - [K in keyof T]: V extends T[K] ? K : never + [K in keyof T]: V extends T[K] ? K : never; }[keyof T]; /** * Inverses a record type, "flipping a record" */ -type InverseRecord> = { - [K in M[keyof M]]: RecordKeyFromValue +type InverseRecord< + M extends Record, +> = { + [K in M[keyof M]]: RecordKeyFromValue; }; export type { @@ -168,5 +169,5 @@ export type { FunctionProperties, NonFunctionProperties, RecordKeyFromValue, - InverseRecord + InverseRecord, }; diff --git a/src/utils/base.ts b/src/utils/base.ts index 1f7201c71..3598032d8 100644 --- a/src/utils/base.ts +++ b/src/utils/base.ts @@ -30,11 +30,6 @@ function fromMultibase(s: string): Buffer | undefined { return bufferWrap(data); } -export { - toMultibase, - fromMultibase, -}; +export { toMultibase, fromMultibase }; -export type { - MultibaseFormats -}; +export type { MultibaseFormats }; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4944d70f0..ef12a05ed 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -434,11 +434,12 @@ function lexiUnpackBuffer(b: Buffer): number { * Structured clone does deep copy * Remove the reliance on v8 in Node 17 */ -const structuredClone = ('structuredClone' in globalThis) - ? globalThis.structuredClone - : (value: any) => { - return v8.deserialize(v8.serialize(value)); - }; +const structuredClone = + 'structuredClone' in globalThis + ? globalThis.structuredClone + : (value: any) => { + return v8.deserialize(v8.serialize(value)); + }; export { AsyncFunction, diff --git a/src/validation/utils.ts b/src/validation/utils.ts index f528d4028..270fa44ca 100644 --- a/src/validation/utils.ts +++ b/src/validation/utils.ts @@ -65,10 +65,7 @@ function parseGestaltId(data: any): GestaltId { } const providerId = parseProviderId(match[1]); const identityId = parseIdentityId(match[2]); - return [ - 'identity', - [providerId, identityId] - ] + return ['identity', [providerId, identityId]]; } function parseClaimId(data: any): ClaimId { @@ -305,7 +302,6 @@ function parseSeedNodes(data: any): [SeedNodes, boolean] { return [seedNodes, defaults]; } - export { parseInteger, parseNumber, diff --git a/src/vaults/VaultManager.ts b/src/vaults/VaultManager.ts index 1c0262276..902e9d8d8 100644 --- a/src/vaults/VaultManager.ts +++ b/src/vaults/VaultManager.ts @@ -18,8 +18,9 @@ import type ACL from '../acl/ACL'; import type { RemoteInfo } from './VaultInternal'; import type { VaultAction } from './types'; import type { MultiLockRequest } from '@matrixai/async-locks'; -import { DB } from '@matrixai/db'; +import type { Key } from 'keys/types'; import path from 'path'; +import { DB } from '@matrixai/db'; import { PassThrough } from 'readable-stream'; import { EncryptedFS, errors as encryptedFsErrors } from 'encryptedfs'; import Logger from '@matrixai/logger'; @@ -31,6 +32,7 @@ import { IdInternal } from '@matrixai/id'; import { withF, withG } from '@matrixai/resources'; import { LockBox, RWLockWriter } from '@matrixai/async-locks'; import VaultInternal from './VaultInternal'; +import * as utils from '../utils'; import * as vaultsUtils from '../vaults/utils'; import * as vaultsErrors from '../vaults/errors'; import * as gitUtils from '../git/utils'; @@ -40,8 +42,6 @@ import * as keysUtils from '../keys/utils'; import config from '../config'; import { mkdirExists } from '../utils/utils'; import * as utilsPB from '../proto/js/polykey/v1/utils/utils_pb'; -import * as utils from '@/utils'; -import { Key } from 'keys/types'; /** * Object map pattern for each vault @@ -197,7 +197,7 @@ class VaultManager { }, dbPath: this.efsPath, logger: this.logger.getChild('EFS Database'), - }) + }); efs = await EncryptedFS.createEncryptedFS({ fresh, db: efsDb, @@ -598,7 +598,7 @@ class VaultManager { if (vaultMeta == null) throw new vaultsErrors.ErrorVaultsVaultUndefined(); // NodeId permissions translated to other nodes in // a gestalt by other domains - await this.gestaltGraph.setGestaltActions(['node', nodeId], 'scan', tran); + await this.gestaltGraph.setGestaltAction(['node', nodeId], 'scan', tran); await this.acl.setVaultAction(vaultId, nodeId, 'pull', tran); await this.acl.setVaultAction(vaultId, nodeId, 'clone', tran); await this.notificationsManager.sendNotification(nodeId, { @@ -630,7 +630,7 @@ class VaultManager { const vaultMeta = await this.getVaultMeta(vaultId, tran); if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); - await this.gestaltGraph.unsetGestaltActions(['node', nodeId], 'scan', tran); + await this.gestaltGraph.unsetGestaltAction(['node', nodeId], 'scan', tran); await this.acl.unsetVaultAction(vaultId, nodeId, 'pull', tran); await this.acl.unsetVaultAction(vaultId, nodeId, 'clone', tran); } diff --git a/src/workers/polykeyWorkerModule.ts b/src/workers/polykeyWorkerModule.ts index 34f14c485..75a3c14b9 100644 --- a/src/workers/polykeyWorkerModule.ts +++ b/src/workers/polykeyWorkerModule.ts @@ -1,5 +1,5 @@ import type { TransferDescriptor } from 'threads'; -import { +import type { Key, KeyPair, PrivateKey, @@ -12,8 +12,8 @@ import { } from '../keys/types'; import { isWorkerRuntime } from 'threads'; import { Transfer } from 'threads/worker'; -import * as keysUtils from '../keys/utils'; import { IdInternal } from '@matrixai/id'; +import * as keysUtils from '../keys/utils'; /** * Worker object that contains all functions that will be executed in parallel. @@ -42,7 +42,6 @@ import { IdInternal } from '@matrixai/id'; * Note that `Buffer.from(ArrayBuffer)` is a zero-copy wrapper. */ const polykeyWorker = { - // Diagnostic functions /** @@ -68,7 +67,7 @@ const polykeyWorker = { password: string, salt?: ArrayBuffer, opsLimit?: PasswordOpsLimit, - memLimit?: PasswordMemLimit + memLimit?: PasswordMemLimit, ): TransferDescriptor<[ArrayBuffer, ArrayBuffer]> { if (salt != null) salt = Buffer.from(salt); // It is guaranteed that `keysUtils.hashPassword` returns non-pooled buffers @@ -76,12 +75,12 @@ const polykeyWorker = { password, salt as PasswordSalt | undefined, opsLimit, - memLimit + memLimit, ); // Result is a tuple of [hash, salt] using transferable `ArrayBuffer` const result: [ArrayBuffer, ArrayBuffer] = [ hashAndSalt[0].buffer, - hashAndSalt[1].buffer + hashAndSalt[1].buffer, ]; return Transfer(result, [result[0], result[1]]); }, @@ -90,7 +89,7 @@ const polykeyWorker = { hash: ArrayBuffer, salt: ArrayBuffer, opsLimit?: PasswordOpsLimit, - memLimit?: PasswordMemLimit + memLimit?: PasswordMemLimit, ): boolean { hash = Buffer.from(hash); salt = Buffer.from(salt); @@ -99,24 +98,28 @@ const polykeyWorker = { hash as PasswordHash, salt as PasswordSalt, opsLimit, - memLimit + memLimit, ); }, - async generateDeterministicKeyPair( - recoveryCode: RecoveryCode - ): Promise> { + async generateDeterministicKeyPair(recoveryCode: RecoveryCode): Promise< + TransferDescriptor<{ + publicKey: ArrayBuffer; + privateKey: ArrayBuffer; + secretKey: ArrayBuffer; + }> + > { const keyPair = await keysUtils.generateDeterministicKeyPair(recoveryCode); // Result is a record of {publicKey, privateKey, secretKey} using transferable `ArrayBuffer` const result = { publicKey: keyPair.publicKey.buffer, privateKey: keyPair.privateKey.buffer, - secretKey: keyPair.secretKey.buffer + secretKey: keyPair.secretKey.buffer, }; - return Transfer(result, [result.publicKey, result.privateKey, result.secretKey]); + return Transfer(result, [ + result.publicKey, + result.privateKey, + result.secretKey, + ]); }, async generateCertificate({ certId, @@ -125,14 +128,14 @@ const polykeyWorker = { duration, subjectAttrsExtra, issuerAttrsExtra, - now = new Date, + now = new Date(), }: { certId: ArrayBuffer; subjectKeyPair: { publicKey: ArrayBuffer; privateKey: ArrayBuffer; - }, - issuerPrivateKey: ArrayBuffer, + }; + issuerPrivateKey: ArrayBuffer; duration: number; subjectAttrsExtra?: Array<{ [key: string]: Array }>; issuerAttrsExtra?: Array<{ [key: string]: Array }>; @@ -149,7 +152,7 @@ const polykeyWorker = { duration, subjectAttrsExtra, issuerAttrsExtra, - now + now, }); return Transfer(cert.rawData); }, @@ -162,7 +165,7 @@ const polykeyWorker = { ): TransferDescriptor { const cipherText = keysUtils.encryptWithKey( Buffer.from(key) as Key, - Buffer.from(plainText) + Buffer.from(plainText), ); return Transfer(cipherText.buffer); }, @@ -172,7 +175,7 @@ const polykeyWorker = { ): TransferDescriptor | undefined { const plainText = keysUtils.decryptWithKey( Buffer.from(key) as Key, - Buffer.from(cipherText) + Buffer.from(cipherText), ); if (plainText != null) { return Transfer(plainText.buffer); diff --git a/test-ajv.ts b/test-ajv.ts deleted file mode 100644 index bec582f79..000000000 --- a/test-ajv.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { signedClaimValidate } from './src/claims/schema'; -import { ClaimIdEncoded, SignedClaim } from './src/claims/types'; -import { NodeIdEncoded } from './src/ids/types'; - -async function main () { - - const y: SignedClaim = { - payload: { - jti: 'abc' as ClaimIdEncoded, - nbf: 123, - iat: 456, - seq: 123, - prevClaimId: 'abc' as ClaimIdEncoded, - prevDigest: null, - iss: 'abc' as NodeIdEncoded, - sub: 'abc', - }, - signatures: [{ - protected: { - alg: "BLAKE2b" - }, - header: { - - }, - signature: "abc", - }] - }; - - const x = signedClaimValidate( - y - ); - - console.log(signedClaimValidate.errors); - -} - -main(); diff --git a/test-g.ts b/test-g.ts deleted file mode 100644 index 30300ecca..000000000 --- a/test-g.ts +++ /dev/null @@ -1,22 +0,0 @@ -function *concatStrings(): Generator { - let result = ''; - while (true) { - const data = yield; - if (data === null) { - return result; - } - result += data; - } -} - -function *combine() { - return (yield* concatStrings()) + 'FINISH'; -} - -const g = combine(); -g.next(); -g.next("a"); -g.next("b"); -g.next("c"); -const r = g.next(null); -console.log(r.value); diff --git a/test-gg.ts b/test-gg.ts deleted file mode 100644 index 90f3e7d88..000000000 --- a/test-gg.ts +++ /dev/null @@ -1,211 +0,0 @@ -import fc from 'fast-check'; -import type { ClaimIdEncoded, IdentityId, NodeId, ProviderId } from './src/ids'; -import { DB } from '@matrixai/db'; -import ACL from './src/acl/ACL'; -import GestaltGraph from './src/gestalts/GestaltGraph'; -import { IdInternal } from '@matrixai/id'; -import Logger, { LogLevel, StreamHandler, formatting } from '@matrixai/logger'; -import * as ids from './src/ids'; - -const nodeIdArb = fc.uint8Array({ minLength: 32, maxLength: 32 }).map( - IdInternal.create -) as fc.Arbitrary; - -// const nodeId = IdInternal.fromBuffer(Buffer.allocUnsafe(32)); - -async function main() { - - // Top level - // but we cannot raise the bottom level - // we can only hide levels - // or filter - // You could also set a filter - - const logger = new Logger( - 'TEST', - LogLevel.DEBUG, - [ - new StreamHandler( - formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}` - ), - ] - ); - - const dbLogger = logger.getChild('DB'); - dbLogger.setLevel(LogLevel.INFO); - - const db = await DB.createDB({ - dbPath: 'tmp/db', - logger: dbLogger, - fresh: true, - }); - - const aclLogger = logger.getChild('ACL'); - aclLogger.setLevel(LogLevel.INFO); - - const acl = await ACL.createACL({ - db, - logger: aclLogger, - }); - - - const ggLogger = logger.getChild('GestaltGraph'); - ggLogger.setLevel(LogLevel.DEBUG); - - const gg = await GestaltGraph.createGestaltGraph({ - db, - acl, - logger: ggLogger, - }); - - const nodeId1 = fc.sample(nodeIdArb, 1)[0]; - - - await gg.setNode({ - nodeId: nodeId1 - }); - - const nodeId2 = fc.sample(nodeIdArb, 1)[0]; - - await gg.setNode({ - nodeId: nodeId2, - }); - - const nodeId3 = fc.sample(nodeIdArb, 1)[0]; - - await gg.setNode({ - nodeId: nodeId3, - }); - - const nodeId4 = fc.sample(nodeIdArb, 1)[0]; - - await gg.setNode({ - nodeId: nodeId4, - }); - - const nodeId5 = fc.sample(nodeIdArb, 1)[0]; - - await gg.setNode({ - nodeId: nodeId5, - }); - - await gg.setIdentity({ - providerId: '123' as ProviderId, - identityId: 'abc' as IdentityId - }); - - await gg.linkNodeAndNode( - { - nodeId: nodeId1 - }, - { - nodeId: nodeId2 - }, - { - meta: {}, - claim: { - payload: { - iss: ids.encodeNodeId(nodeId1), - sub: ids.encodeNodeId(nodeId2), - jti: 'asfoiuadf' as ClaimIdEncoded, - iat: 123, - nbf: 123, - seq: 123, - prevClaimId: null, - prevDigest: null - }, - signatures: [] - } - } - ); - - await gg.linkNodeAndNode( - { - nodeId: nodeId1 - }, - { - nodeId: nodeId3 - }, - { - meta: {}, - claim: { - payload: { - iss: ids.encodeNodeId(nodeId1), - sub: ids.encodeNodeId(nodeId3), - jti: 'asfoiuadf' as ClaimIdEncoded, - iat: 123, - nbf: 123, - seq: 123, - prevClaimId: null, - prevDigest: null - }, - signatures: [] - } - } - ); - - await gg.linkNodeAndNode( - { - nodeId: nodeId2 - }, - { - nodeId: nodeId3 - }, - { - meta: {}, - claim: { - payload: { - iss: ids.encodeNodeId(nodeId2), - sub: ids.encodeNodeId(nodeId3), - jti: 'asfoiuadf' as ClaimIdEncoded, - iat: 123, - nbf: 123, - seq: 123, - prevClaimId: null, - prevDigest: null - }, - signatures: [] - } - } - ); - - // await gg.linkNodeAndNode( - // { - // nodeId: nodeId1 - // }, - // { - // nodeId: nodeId2 - // }, - // { - // type: 'node', - // meta: {}, - // claim: { - // payload: { - // jti: 's8d9sf98s7fd98sfd7' as ClaimIdEncoded, - // iss: ids.encodeNodeId(nodeId1), - // sub: ids.encodeNodeId(nodeId2), - // iat: 123, - // nbf: 123, - // seq: 123, - // prevClaimId: null, - // prevDigest: null - // }, - // signatures: [] - // } - // } - // ); - - console.log(await db.dump(gg.dbMatrixPath, true)); - // console.log(await db.dump(gg.dbNodesPath, true)); - // console.log(await db.dump(gg.dbLinksPath, true)); - - for await (const gestalt of gg.getGestalts()) { - console.group('Gestalt'); - console.dir(gestalt, { depth: null }); - // console.log('nodes', gestalt.nodes); - console.groupEnd(); - } - -} - -main(); diff --git a/test-hashing.ts b/test-hashing.ts deleted file mode 100644 index cc8e4eed7..000000000 --- a/test-hashing.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as hash from './src/keys/utils/hash'; -import * as hashing from './src/tokens/utils'; - -async function main () { - - // thisis what it takes to do it - - const digest = hash.sha256(Buffer.from('hello world')); - console.log(hashing.sha256MultiHash(digest)); - - - - // const encodeR = await hashing.sha256M.encode(Buffer.from('abc')); - // const digestR = await hashing.sha256M.digest(Buffer.from('abc')); - - // console.log(encodeR.byteLength); - // console.log(encodeR); - - // console.log(digestR); - - // // so remember - // // that upon hashing, you have a multihash digest - - // // this is the actual byte reprentation - // // the remaining stuff still needs to be "multibase" encoded - // console.log(digestR.bytes); - - - // // so therefore - // // BASEENCODING + MULTIHASH is exactly what you want - - - - -} - -main(); diff --git a/tests/PolykeyAgent.test.ts b/tests/PolykeyAgent.test.ts index c97be8259..ea204e17e 100644 --- a/tests/PolykeyAgent.test.ts +++ b/tests/PolykeyAgent.test.ts @@ -41,13 +41,13 @@ describe('PolykeyAgent', () => { strictMemoryLock: false, }, }); - await expect(pkAgent.destroy()).rejects.toThrow( + await expect(pkAgent.destroy(password)).rejects.toThrow( errors.ErrorPolykeyAgentRunning, ); // Should be a noop await pkAgent.start({ password }); await pkAgent.stop(); - await pkAgent.destroy(); + await pkAgent.destroy(password); await expect(pkAgent.start({ password })).rejects.toThrow( errors.ErrorPolykeyAgentDestroyed, ); @@ -83,7 +83,7 @@ describe('PolykeyAgent', () => { expect(stateContents).toContain(config.defaults.keysBase); expect(stateContents).toContain(config.defaults.dbBase); expect(stateContents).toContain(config.defaults.vaultsBase); - await pkAgent.destroy(); + await pkAgent.destroy(password); nodePathContents = await fs.promises.readdir(nodePath); // The status will be the only file left over expect(nodePathContents).toHaveLength(1); @@ -119,7 +119,7 @@ describe('PolykeyAgent', () => { pkAgent.start({ password: 'wrong password' }), ).rejects.toThrowError(errors.ErrorKeyPairParse); expect(await status.readStatus()).toMatchObject({ status: 'DEAD' }); - await pkAgent.destroy(); + await pkAgent.destroy(password); expect(await status.readStatus()).toMatchObject({ status: 'DEAD' }); }); test('schema state version is maintained after start and stop', async () => { @@ -214,7 +214,7 @@ describe('PolykeyAgent', () => { await expect(prom.p).resolves.toBeDefined(); } finally { await pkAgent?.stop(); - await pkAgent?.destroy(); + await pkAgent?.destroy(password); } }); test('resetRootKeyPair change event propagates', async () => { @@ -243,7 +243,7 @@ describe('PolykeyAgent', () => { await expect(prom.p).resolves.toBeDefined(); } finally { await pkAgent?.stop(); - await pkAgent?.destroy(); + await pkAgent?.destroy(password); } }); test('resetRootCert change event propagates', async () => { @@ -272,7 +272,7 @@ describe('PolykeyAgent', () => { await expect(prom.p).resolves.toBeDefined(); } finally { await pkAgent?.stop(); - await pkAgent?.destroy(); + await pkAgent?.destroy(password); } }); }); diff --git a/tests/acl/ACL.test.ts b/tests/acl/ACL.test.ts index 2ffc89fcc..68c079be9 100644 --- a/tests/acl/ACL.test.ts +++ b/tests/acl/ACL.test.ts @@ -42,7 +42,7 @@ describe(ACL.name, () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); - const dbKey = await keysUtils.generateKey(); + const dbKey = keysUtils.generateKey(); const dbPath = `${dataDir}/db`; db = await DB.createDB({ dbPath, diff --git a/tests/acl/utils.ts b/tests/acl/utils.ts new file mode 100644 index 000000000..c435d9cfb --- /dev/null +++ b/tests/acl/utils.ts @@ -0,0 +1,57 @@ +import type { DB } from '@matrixai/db'; +import type { Permission } from '@/acl/types'; +import type { NodeId, VaultId } from '@/ids/types'; +import fc from 'fast-check'; +import Logger, { LogLevel } from '@matrixai/logger'; +import { IdInternal } from '@matrixai/id'; +import ACL from '@/acl/ACL'; +import * as testsGestaltsUtils from '../gestalts/utils'; +import * as testsVaultsUtils from '../vaults/utils'; +import * as testsIdsUtils from '../ids/utils'; + +const permissionArb = (vaultIds: Array = []) => + fc.record({ + gestalt: testsGestaltsUtils.gestaltActionsArb(), + vaults: + vaultIds.length < 1 + ? fc.constant({}) + : fc.dictionary( + fc.constantFrom(...vaultIds.map((id) => id.toString())), + testsVaultsUtils.vaultActionsArb, + { + minKeys: vaultIds.length, + maxKeys: vaultIds.length, + }, + ), + }) as fc.Arbitrary; + +const aclFactoryArb = (vaultIds: Array = []) => { + return fc + .record({ + nodes: fc.dictionary( + testsIdsUtils.nodeIdStringArb, + permissionArb(vaultIds), + ), + }) + .map(({ nodes }) => { + const logger = new Logger(undefined, LogLevel.SILENT); + return async (db: DB) => { + const acl = await ACL.createACL({ db, logger, fresh: true }); + for (const nodeIdString in nodes) { + const nodeId = IdInternal.fromString(nodeIdString); + const permission = nodes[nodeIdString]; + await acl.setNodePerm(nodeId, permission); + for (const vaultIdString in permission.vaults) { + const vaultId = IdInternal.fromString(vaultIdString); + const vaultActions = permission.vaults[vaultIdString].keys(); + for (const vaultAction of vaultActions) { + await acl.setVaultAction(vaultId, nodeId, vaultAction); + } + } + } + return acl; + }; + }); +}; + +export { permissionArb, aclFactoryArb }; diff --git a/tests/agent/GRPCClientAgent.test.ts b/tests/agent/GRPCClientAgent.test.ts index 96fcea19e..0299239ec 100644 --- a/tests/agent/GRPCClientAgent.test.ts +++ b/tests/agent/GRPCClientAgent.test.ts @@ -133,6 +133,7 @@ describe(GRPCClientAgent.name, () => { keyRing: keyRing, nodeGraph: nodeGraph, nodeConnectionManager: nodeConnectionManager, + gestaltGraph, taskManager, logger: logger, }); diff --git a/tests/agent/service/nodesChainDataGet.test.ts b/tests/agent/service/nodesChainDataGet.test.ts index e8e2c77be..bbacc5dca 100644 --- a/tests/agent/service/nodesChainDataGet.test.ts +++ b/tests/agent/service/nodesChainDataGet.test.ts @@ -1,5 +1,5 @@ import type { Host, Port } from '@/network/types'; -import type { NodeIdEncoded } from '@/ids/types'; +import type { IdentityId, ProviderId } from '@/identities/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -8,14 +8,15 @@ import PolykeyAgent from '@/PolykeyAgent'; import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientAgent from '@/agent/GRPCClientAgent'; import { AgentServiceService } from '@/proto/js/polykey/v1/agent_service_grpc_pb'; -import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as nodesUtils from '@/nodes/utils'; -import nodesClosestLocalNodesGet from '@/agent/service/nodesClosestLocalNodesGet'; +import nodesChainDataGet from '@/agent/service/nodesChainDataGet'; +import { encodeProviderIdentityId } from '@/identities/utils'; +import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as testNodesUtils from '../../nodes/utils'; import * as keysUtils from '../../../src/keys/utils/index'; -describe('nodesClosestLocalNode', () => { - const logger = new Logger('nodesClosestLocalNode test', LogLevel.WARN, [ +describe('nodesChainDataGet', () => { + const logger = new Logger('nodesChainDataGet test', LogLevel.WARN, [ new StreamHandler(), ]); const password = 'helloworld'; @@ -43,10 +44,9 @@ describe('nodesClosestLocalNode', () => { }, logger, }); - // Setting up a remote keynode const agentService = { - nodesClosestLocalNodesGet: nodesClosestLocalNodesGet({ - nodeGraph: pkAgent.nodeGraph, + nodesChainDataGet: nodesChainDataGet({ + sigchain: pkAgent.sigchain, db: pkAgent.db, logger, }), @@ -68,33 +68,44 @@ describe('nodesClosestLocalNode', () => { await grpcClient.destroy(); await grpcServer.stop(); await pkAgent.stop(); - await pkAgent.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, }); }); - test('should get closest local nodes', async () => { - // Adding 10 nodes - const nodes: Array = []; - for (let i = 0; i < 10; i++) { - const nodeId = testNodesUtils.generateRandomNodeId(); - await pkAgent.nodeGraph.setNode(nodeId, { - host: 'localhost' as Host, - port: 55555 as Port, - }); - nodes.push(nodesUtils.encodeNodeId(nodeId)); - } - const nodeIdEncoded = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), + test('Should get chain data', async () => { + const srcNodeIdEncoded = nodesUtils.encodeNodeId( + pkAgent.keyRing.getNodeId(), ); - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodeIdEncoded); - const result = await grpcClient.nodesClosestLocalNodesGet(nodeMessage); - const resultNodes: Array = []; - for (const [resultNode] of result.toObject().nodeTableMap) { - resultNodes.push(resultNode as NodeIdEncoded); + // Add 10 claims + for (let i = 1; i <= 5; i++) { + const node2 = nodesUtils.encodeNodeId( + testNodesUtils.generateRandomNodeId(), + ); + const nodeLink = { + type: 'ClaimLinkNode', + iss: srcNodeIdEncoded, + sub: node2, + }; + await pkAgent.sigchain.addClaim(nodeLink); + } + for (let i = 6; i <= 10; i++) { + const identityLink = { + type: 'ClaimLinkIdentity', + iss: srcNodeIdEncoded, + sub: encodeProviderIdentityId([ + ('ProviderId' + i.toString()) as ProviderId, + ('IdentityId' + i.toString()) as IdentityId, + ]), + }; + await pkAgent.sigchain.addClaim(identityLink); + } + + const response = grpcClient.nodesChainDataGet(new nodesPB.ClaimId()); + const chainIds: Array = []; + for await (const claim of response) { + chainIds.push(claim.getClaimId()); } - expect(nodes.sort()).toEqual(resultNodes.sort()); + expect(chainIds).toHaveLength(10); }); }); diff --git a/tests/agent/service/nodesClosestLocalNode.test.ts b/tests/agent/service/nodesClosestLocalNode.test.ts index af409ee3a..1f263a1ba 100644 --- a/tests/agent/service/nodesClosestLocalNode.test.ts +++ b/tests/agent/service/nodesClosestLocalNode.test.ts @@ -1,6 +1,5 @@ import type { Host, Port } from '@/network/types'; -import type { ClaimData } from '@/claims/types'; -import type { IdentityId, ProviderId } from '@/identities/types'; +import type { NodeIdEncoded } from '@/ids/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -9,14 +8,14 @@ import PolykeyAgent from '@/PolykeyAgent'; import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientAgent from '@/agent/GRPCClientAgent'; import { AgentServiceService } from '@/proto/js/polykey/v1/agent_service_grpc_pb'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as nodesUtils from '@/nodes/utils'; -import nodesChainDataGet from '@/agent/service/nodesChainDataGet'; +import nodesClosestLocalNodesGet from '@/agent/service/nodesClosestLocalNodesGet'; import * as testNodesUtils from '../../nodes/utils'; import * as keysUtils from '../../../src/keys/utils/index'; -describe('nodesChainDataGet', () => { - const logger = new Logger('nodesChainDataGet test', LogLevel.WARN, [ +describe('nodesClosestLocalNode', () => { + const logger = new Logger('nodesClosestLocalNode test', LogLevel.WARN, [ new StreamHandler(), ]); const password = 'helloworld'; @@ -44,9 +43,10 @@ describe('nodesChainDataGet', () => { }, logger, }); + // Setting up a remote keynode const agentService = { - nodesChainDataGet: nodesChainDataGet({ - sigchain: pkAgent.sigchain, + nodesClosestLocalNodesGet: nodesClosestLocalNodesGet({ + nodeGraph: pkAgent.nodeGraph, db: pkAgent.db, logger, }), @@ -68,43 +68,32 @@ describe('nodesChainDataGet', () => { await grpcClient.destroy(); await grpcServer.stop(); await pkAgent.stop(); - await pkAgent.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, }); }); - test('should get closest nodes', async () => { - const srcNodeIdEncoded = nodesUtils.encodeNodeId( - pkAgent.keyRing.getNodeId(), - ); - // Add 10 claims - for (let i = 1; i <= 5; i++) { - const node2 = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - const nodeLink: ClaimData = { - type: 'node', - node1: srcNodeIdEncoded, - node2: node2, - }; - await pkAgent.sigchain.addClaim(nodeLink); - } - for (let i = 6; i <= 10; i++) { - const identityLink: ClaimData = { - type: 'identity', - node: srcNodeIdEncoded, - provider: ('ProviderId' + i.toString()) as ProviderId, - identity: ('IdentityId' + i.toString()) as IdentityId, - }; - await pkAgent.sigchain.addClaim(identityLink); + test('should get closest local nodes', async () => { + // Adding 10 nodes + const nodes: Array = []; + for (let i = 0; i < 10; i++) { + const nodeId = testNodesUtils.generateRandomNodeId(); + await pkAgent.nodeGraph.setNode(nodeId, { + host: 'localhost' as Host, + port: 55555 as Port, + }); + nodes.push(nodesUtils.encodeNodeId(nodeId)); } - - const response = await grpcClient.nodesChainDataGet( - new utilsPB.EmptyMessage(), + const nodeIdEncoded = nodesUtils.encodeNodeId( + testNodesUtils.generateRandomNodeId(), ); - const chainIds: Array = []; - for (const [id] of response.toObject().chainDataMap) chainIds.push(id); - expect(chainIds).toHaveLength(10); + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(nodeIdEncoded); + const result = await grpcClient.nodesClosestLocalNodesGet(nodeMessage); + const resultNodes: Array = []; + for (const [resultNode] of result.toObject().nodeTableMap) { + resultNodes.push(resultNode as NodeIdEncoded); + } + expect(nodes.sort()).toEqual(resultNodes.sort()); }); }); diff --git a/tests/agent/service/nodesCrossSignClaim.test.ts b/tests/agent/service/nodesCrossSignClaim.test.ts index 81c8cb0ac..676156992 100644 --- a/tests/agent/service/nodesCrossSignClaim.test.ts +++ b/tests/agent/service/nodesCrossSignClaim.test.ts @@ -1,6 +1,6 @@ -import type { ClaimIdString, ClaimIntermediary } from '@/claims/types'; -import type { Host, Port } from '@/network/types'; +import type { ConnectionInfo, Host, Port } from '@/network/types'; import type { NodeId } from '@/ids/types'; +import type { ClaimLinkNode } from '@/claims/payloads/index'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -14,6 +14,7 @@ import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as nodesUtils from '@/nodes/utils'; import * as claimsUtils from '@/claims/utils'; import * as grpcErrors from '@/grpc/errors'; +import Token from '@/tokens/Token'; import * as testNodesUtils from '../../nodes/utils'; import * as keysUtils from '../../../src/keys/utils/index'; @@ -66,13 +67,16 @@ describe('nodesCrossSignClaim', () => { logger, }); remoteId = remoteNode.keyRing.getNodeId(); + await pkAgent.acl.setNodeAction(remoteId, 'claim'); await testNodesUtils.nodesConnect(pkAgent, remoteNode); const agentService = { nodesCrossSignClaim: nodesCrossSignClaim({ keyRing: pkAgent.keyRing, nodeManager: pkAgent.nodeManager, - sigchain: pkAgent.sigchain, - db: pkAgent.db, + acl: pkAgent.acl, + connectionInfoGet: () => { + return { remoteNodeId: remoteId } as ConnectionInfo; + }, logger, }), }; @@ -93,9 +97,7 @@ describe('nodesCrossSignClaim', () => { await grpcClient.destroy(); await grpcServer.stop(); await pkAgent.stop(); - await pkAgent.destroy(); await remoteNode.stop(); - await remoteNode.destroy(); await remoteNode.stop(); await fs.promises.rm(dataDir, { force: true, @@ -105,126 +107,80 @@ describe('nodesCrossSignClaim', () => { test('successfully cross signs a claim', async () => { const genClaims = grpcClient.nodesCrossSignClaim(); expect(genClaims.stream.destroyed).toBe(false); - // Create a dummy intermediary claim to "receive" - const claim = await claimsUtils.createClaim({ - privateKey: remoteNode.keyRing.keyPair.privateKey, - hPrev: null, - seq: 1, - data: { - type: 'node', - node1: nodesUtils.encodeNodeId(remoteId), - node2: nodesUtils.encodeNodeId(localId), - }, - kid: nodesUtils.encodeNodeId(remoteId), - }); - const intermediary: ClaimIntermediary = { - payload: claim.payload, - signature: claim.signatures[0], + const claimId = claimsUtils.createClaimIdGenerator(localId)(); + const claimPayload: ClaimLinkNode = { + typ: 'ClaimLinkNode', + iss: nodesUtils.encodeNodeId(remoteId), + sub: nodesUtils.encodeNodeId(localId), + jti: claimsUtils.encodeClaimId(claimId), + iat: 0, + nbf: 0, + seq: 0, + prevDigest: null, + prevClaimId: null, }; - const crossSignMessage = claimsUtils.createCrossSignMessage({ - singlySignedClaim: intermediary, - }); - await genClaims.write(crossSignMessage); + const token = Token.fromPayload(claimPayload); + token.signWithPrivateKey(remoteNode.keyRing.keyPair.privateKey); + const claimMessage = nodesUtils.signedClaimToAgentClaimMessage( + token.toSigned(), + ); + await genClaims.write(claimMessage); // X reads this intermediary signed claim, and is expected to send back: // 1. Doubly signed claim // 2. Singly signed intermediary claim const response = await genClaims.read(); // Check X's sigchain is locked at start expect(response.done).toBe(false); - expect(response.value).toBeInstanceOf(nodesPB.CrossSign); - const receivedMessage = response.value as nodesPB.CrossSign; - expect(receivedMessage.getSinglySignedClaim()).toBeDefined(); - expect(receivedMessage.getDoublySignedClaim()).toBeDefined(); - const constructedIntermediary = claimsUtils.reconstructClaimIntermediary( - receivedMessage.getSinglySignedClaim()!, - ); - const constructedDoubly = claimsUtils.reconstructClaimEncoded( - receivedMessage.getDoublySignedClaim()!, - ); - // Verify the intermediary claim with X's public key - const verifiedSingly = await claimsUtils.verifyIntermediaryClaimSignature( - constructedIntermediary, - pkAgent.keyRing.keyPair.publicKey, - ); - expect(verifiedSingly).toBe(true); + expect(response.value).toBeInstanceOf(nodesPB.AgentClaim); + const receivedMessage = response.value as nodesPB.AgentClaim; + const [, constructedDoubly] = + nodesUtils.agentClaimMessageToSignedClaim(receivedMessage); + const tokenDoubleClaim = Token.fromSigned(constructedDoubly); // Verify the doubly signed claim with both public keys - const verifiedDoubly = - (await claimsUtils.verifyClaimSignature( - constructedDoubly, + expect( + tokenDoubleClaim.verifyWithPublicKey( remoteNode.keyRing.keyPair.publicKey, - )) && - (await claimsUtils.verifyClaimSignature( - constructedDoubly, - pkAgent.keyRing.keyPair.publicKey, - )); - expect(verifiedDoubly).toBe(true); + ), + ).toBeTrue(); + expect( + tokenDoubleClaim.verifyWithPublicKey(pkAgent.keyRing.keyPair.publicKey), + ).toBeTrue(); // 4. X <- sends doubly signed claim (X's intermediary) <- Y - const doublyResponse = await claimsUtils.signIntermediaryClaim({ - claim: constructedIntermediary, - privateKey: remoteNode.keyRing.keyPair.privateKey, - signeeNodeId: nodesUtils.encodeNodeId(remoteId), - }); - const doublyMessage = claimsUtils.createCrossSignMessage({ - doublySignedClaim: doublyResponse, - }); + const response2 = await genClaims.read(); + expect(response2.done).toBeFalse(); + expect(response2.value).toBeInstanceOf(nodesPB.AgentClaim); + const receivedMessage2 = response2.value as nodesPB.AgentClaim; + const [, constructedSingly] = + nodesUtils.agentClaimMessageToSignedClaim(receivedMessage2); + const tokenSingleClaim = Token.fromSigned(constructedSingly); + tokenSingleClaim.signWithPrivateKey(remoteNode.keyRing.keyPair.privateKey); + const claimSingleMessage = nodesUtils.signedClaimToAgentClaimMessage( + tokenSingleClaim.toSigned(), + ); // Just before we complete the last step, check X's sigchain is still locked - await genClaims.write(doublyMessage); + await genClaims.write(claimSingleMessage); // Expect the stream to be closed. const finalResponse = await genClaims.read(); + await genClaims.write(null); expect(finalResponse.done).toBe(true); expect(genClaims.stream.destroyed).toBe(true); // Check X's sigchain is released at end. // Check claim is in both node's sigchains // Rather, check it's in X's sigchain - const chain = await pkAgent.sigchain.getChainData(); - expect(Object.keys(chain).length).toBe(1); // Iterate just to be safe, but expected to only have this single claim - for (const c of Object.keys(chain)) { - const claimId = c as ClaimIdString; - expect(chain[claimId]).toStrictEqual(doublyResponse); + for await (const [, claim] of pkAgent.sigchain.getClaims()) { + expect(claim).toStrictEqual(tokenSingleClaim.payload); } - // Revert side effects - await pkAgent.sigchain.stop(); - await pkAgent.sigchain.destroy(); - await remoteNode.sigchain.stop(); - await remoteNode.sigchain.destroy(); }); test('fails after receiving undefined singly signed claim', async () => { const genClaims = grpcClient.nodesCrossSignClaim(); expect(genClaims.stream.destroyed).toBe(false); // 2. X <- sends its intermediary signed claim <- Y - const crossSignMessageUndefinedSingly = new nodesPB.CrossSign(); + const crossSignMessageUndefinedSingly = new nodesPB.AgentClaim(); await genClaims.write(crossSignMessageUndefinedSingly); await expect(() => genClaims.read()).rejects.toThrow( grpcErrors.ErrorPolykeyRemote, ); expect(genClaims.stream.destroyed).toBe(true); - // Check sigchain's lock is released - // Revert side effects - await pkAgent.sigchain.stop(); - await pkAgent.sigchain.destroy(); - await remoteNode.sigchain.stop(); - await remoteNode.sigchain.destroy(); - }); - test('fails after receiving singly signed claim with no signature', async () => { - const genClaims = grpcClient.nodesCrossSignClaim(); - expect(genClaims.stream.destroyed).toBe(false); - // 2. X <- sends its intermediary signed claim <- Y - const crossSignMessageUndefinedSinglySignature = new nodesPB.CrossSign(); - const intermediaryNoSignature = new nodesPB.ClaimIntermediary(); - crossSignMessageUndefinedSinglySignature.setSinglySignedClaim( - intermediaryNoSignature, - ); - await genClaims.write(crossSignMessageUndefinedSinglySignature); - await expect(() => genClaims.read()).rejects.toThrow( - grpcErrors.ErrorPolykeyRemote, - ); - expect(genClaims.stream.destroyed).toBe(true); - // Check sigchain's lock is released - // Revert side effects - await pkAgent.sigchain.stop(); - await pkAgent.sigchain.destroy(); - await remoteNode.sigchain.stop(); - await remoteNode.sigchain.destroy(); }); }); diff --git a/tests/agent/service/nodesHolePunchMessage.test.ts b/tests/agent/service/nodesHolePunchMessage.test.ts index 1112ca5be..5cf1c8d18 100644 --- a/tests/agent/service/nodesHolePunchMessage.test.ts +++ b/tests/agent/service/nodesHolePunchMessage.test.ts @@ -53,7 +53,7 @@ describe('nodesHolePunchMessage', () => { ({ remoteHost: '127.0.0.1' as Host, remotePort: 55555 as Port, - remoteNodeId: pkAgent.keyManager.getNodeId(), + remoteNodeId: pkAgent.keyRing.getNodeId(), } as ConnectionInfo), logger, }), @@ -75,7 +75,6 @@ describe('nodesHolePunchMessage', () => { await grpcClient.destroy(); await grpcServer.stop(); await pkAgent.stop(); - await pkAgent.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, diff --git a/tests/agent/service/notificationsSend.test.ts b/tests/agent/service/notificationsSend.test.ts index af4dea6d2..daf76a9e8 100644 --- a/tests/agent/service/notificationsSend.test.ts +++ b/tests/agent/service/notificationsSend.test.ts @@ -1,11 +1,10 @@ import type { Host, Port } from '@/network/types'; import type { Notification } from '@/notifications/types'; import type { NodeId } from '@/ids/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import { createPrivateKey, createPublicKey } from 'crypto'; -import { exportJWK, SignJWT } from 'jose'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import TaskManager from '@/tasks/TaskManager'; @@ -26,10 +25,11 @@ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as notificationsPB from '@/proto/js/polykey/v1/notifications/notifications_pb'; import * as nodesUtils from '@/nodes/utils'; import * as notificationsUtils from '@/notifications/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; -import { CertificatePEMChain } from '@/keys/types'; +import Token from '@/tokens/Token'; +import * as validationErrors from '@/validation/errors'; import * as testsUtils from '../../utils/index'; +import * as testUtils from '../../utils'; describe('notificationsSend', () => { const logger = new Logger('notificationsSend test', LogLevel.WARN, [ @@ -37,7 +37,7 @@ describe('notificationsSend', () => { ]); const password = 'helloworld'; const authToken = 'abc123'; - let senderId: NodeId; + let senderNodeId: NodeId; let senderKeyRing: KeyRing; let dataDir: string; let nodeGraph: NodeGraph; @@ -75,7 +75,7 @@ describe('notificationsSend', () => { passwordMemLimit: keysUtils.passwordMemLimits.min, strictMemoryLock: false, }); - senderId = senderKeyRing.getNodeId(); + senderNodeId = senderKeyRing.getNodeId(); const dbPath = path.join(dataDir, 'db'); db = await DB.createDB({ dbPath, @@ -125,6 +125,7 @@ describe('notificationsSend', () => { nodeConnectionManager, sigchain, taskManager, + gestaltGraph: {} as GestaltGraph, logger, }); await nodeManager.start(); @@ -142,6 +143,7 @@ describe('notificationsSend', () => { const agentService = { notificationsSend: notificationsSend({ notificationsManager, + keyRing, logger, db, }), @@ -182,20 +184,22 @@ describe('notificationsSend', () => { }); test('successfully sends a notification', async () => { // Set notify permission for sender on receiver - await acl.setNodePerm(senderId, { + await acl.setNodePerm(senderNodeId, { gestalt: { notify: null }, vaults: {}, }); // Construct and send notification const notification: Notification = { + typ: 'notification', data: { type: 'General', message: 'test', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderNodeId), + sub: nodesUtils.encodeNodeId(keyRing.getNodeId()), isRead: false, }; - const signedNotification = await notificationsUtils.signNotification( + const signedNotification = await notificationsUtils.generateNotification( notification, senderKeyRing.keyPair, ); @@ -208,31 +212,34 @@ describe('notificationsSend', () => { await notificationsManager.readNotifications(); expect(receivedNotifications).toHaveLength(1); expect(receivedNotifications[0].data).toEqual(notification.data); - expect(receivedNotifications[0].senderId).toEqual(notification.senderId); + expect(receivedNotifications[0].iss).toEqual(notification.iss); // Reverse side effects await notificationsManager.clearNotifications(); - await acl.unsetNodePerm(senderId); + await acl.unsetNodePerm(senderNodeId); }); test('cannot send invalidly formatted notification', async () => { // Set notify permission for sender on receiver - await acl.setNodePerm(senderId, { + await acl.setNodePerm(senderNodeId, { gestalt: { notify: null }, vaults: {}, }); // Unsigned notification const notification1: Notification = { + typ: 'notification', data: { type: 'General', message: 'test', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderNodeId), + sub: nodesUtils.encodeNodeId(keyRing.getNodeId()), isRead: false, }; + const token = Token.fromPayload(notification1); const request1 = new notificationsPB.AgentNotification(); - request1.setContent(notification1.toString()); + request1.setContent(JSON.stringify(token.toJSON())); await testUtils.expectRemoteError( grpcClient.notificationsSend(request1), - notificationsErrors.ErrorNotificationsParse, + notificationsErrors.ErrorNotificationsVerificationFailed, ); // Check notification was not received let receivedNotifications = await notificationsManager.readNotifications(); @@ -242,43 +249,39 @@ describe('notificationsSend', () => { data: { type: 'invalid', }, - senderId, + senderId: senderNodeId, isRead: false, }; - const publicKey = createPublicKey( - senderKeyRing.keyPair.publicKey, - ); - const privateKey = createPrivateKey( - senderKeyRing.keyPair.privateKey, + const signedNotification = await notificationsUtils.generateNotification( + // @ts-ignore: invalidly constructed notification + notification2, + senderKeyRing.keyPair, ); - const jwkPublicKey = await exportJWK(publicKey); - const signedNotification = await new SignJWT(notification2) - .setProtectedHeader({ alg: 'RS256', jwk: jwkPublicKey }) - .setIssuedAt() - .sign(privateKey); const request2 = new notificationsPB.AgentNotification(); request2.setContent(signedNotification); await testUtils.expectRemoteError( grpcClient.notificationsSend(request2), - notificationsErrors.ErrorNotificationsValidationFailed, + validationErrors.ErrorParse, ); // Check notification was not received receivedNotifications = await notificationsManager.readNotifications(); expect(receivedNotifications).toHaveLength(0); // Reverse side effects - await acl.unsetNodePerm(senderId); + await acl.unsetNodePerm(senderNodeId); }); test('cannot send notification without permission', async () => { // Construct and send notification const notification: Notification = { + typ: 'notification', data: { type: 'General', message: 'test', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderNodeId), + sub: nodesUtils.encodeNodeId(keyRing.getNodeId()), isRead: false, }; - const signedNotification = await notificationsUtils.signNotification( + const signedNotification = await notificationsUtils.generateNotification( notification, senderKeyRing.keyPair, ); diff --git a/tests/bin/agent/start.test.ts b/tests/bin/agent/start.test.ts index ddbe4c073..3d94b0037 100644 --- a/tests/bin/agent/start.test.ts +++ b/tests/bin/agent/start.test.ts @@ -336,13 +336,7 @@ describe('start', () => { logger.getChild('agentProcess'), ), testUtils.pkSpawn( - [ - 'bootstrap', - '--fresh', - '--verbose', - '--format', - 'json', - ], + ['bootstrap', '--fresh', '--verbose', '--format', 'json'], { env: { PK_NODE_PATH: path.join(dataDir, 'polykey'), @@ -805,11 +799,20 @@ describe('start', () => { const keyPair = keysUtils.generateKeyPair(); const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); const privateKeyJWK = keysUtils.privateKeyToJWK(keyPair.privateKey); - const privateKeyJWE = keysUtils.wrapWithPassword(password, privateKeyJWK, keysUtils.passwordOpsLimits.min, keysUtils.passwordMemLimits.min) + const privateKeyJWE = keysUtils.wrapWithPassword( + password, + privateKeyJWK, + keysUtils.passwordOpsLimits.min, + keysUtils.passwordMemLimits.min, + ); const privateKeyPath = path.join(dataDir, 'private.jwe'); - await fs.promises.writeFile(privateKeyPath, JSON.stringify(privateKeyJWE), { - encoding: 'utf-8', - }); + await fs.promises.writeFile( + privateKeyPath, + JSON.stringify(privateKeyJWE), + { + encoding: 'utf-8', + }, + ); const agentProcess = await testUtils.pkSpawn( [ 'agent', @@ -839,173 +842,170 @@ describe('start', () => { }, globalThis.defaultTimeout * 2, ); - // testUtils.describeIf(testUtils.isTestPlatformEmpty) - describe( - 'start with global agent', - () => { - let agentDataDir; - let agent1Status: StatusLive; - let agent1Close: () => Promise; - let agent2Status: StatusLive; - let agent2Close: () => Promise; - let seedNodeId1: NodeId; - let seedNodeHost1: Host; - let seedNodePort1: Port; - let seedNodeId2: NodeId; - let seedNodeHost2: Host; - let seedNodePort2: Port; - beforeEach(async () => { - // Additional seed node - agentDataDir = await fs.promises.mkdtemp( - path.join(globalThis.tmpDir, 'polykey-test-'), - ); - ({ agentStatus: agent1Status, agentClose: agent1Close } = - await testUtils.setupTestAgent(logger)); - ({ agentStatus: agent2Status, agentClose: agent2Close } = - await testUtils.setupTestAgent(logger)); - seedNodeId1 = agent1Status.data.nodeId; - seedNodeHost1 = agent1Status.data.proxyHost; - seedNodePort1 = agent1Status.data.proxyPort; - seedNodeId2 = agent2Status.data.nodeId; - seedNodeHost2 = agent2Status.data.proxyHost; - seedNodePort2 = agent2Status.data.proxyPort; + // TestUtils.describeIf(testUtils.isTestPlatformEmpty) + describe('start with global agent', () => { + let agentDataDir; + let agent1Status: StatusLive; + let agent1Close: () => Promise; + let agent2Status: StatusLive; + let agent2Close: () => Promise; + let seedNodeId1: NodeId; + let seedNodeHost1: Host; + let seedNodePort1: Port; + let seedNodeId2: NodeId; + let seedNodeHost2: Host; + let seedNodePort2: Port; + beforeEach(async () => { + // Additional seed node + agentDataDir = await fs.promises.mkdtemp( + path.join(globalThis.tmpDir, 'polykey-test-'), + ); + ({ agentStatus: agent1Status, agentClose: agent1Close } = + await testUtils.setupTestAgent(logger)); + ({ agentStatus: agent2Status, agentClose: agent2Close } = + await testUtils.setupTestAgent(logger)); + seedNodeId1 = agent1Status.data.nodeId; + seedNodeHost1 = agent1Status.data.proxyHost; + seedNodePort1 = agent1Status.data.proxyPort; + seedNodeId2 = agent2Status.data.nodeId; + seedNodeHost2 = agent2Status.data.proxyHost; + seedNodePort2 = agent2Status.data.proxyPort; + }); + afterEach(async () => { + await agent1Close(); + await agent2Close(); + await fs.promises.rm(agentDataDir, { + force: true, + recursive: true, }); - afterEach(async () => { - await agent1Close(); - await agent2Close(); - await fs.promises.rm(agentDataDir, { - force: true, - recursive: true, + }); + test( + 'start with seed nodes option', + async () => { + const password = 'abc123'; + const nodePath = path.join(dataDir, 'polykey'); + const statusPath = path.join(nodePath, config.defaults.statusBase); + const statusLockPath = path.join( + nodePath, + config.defaults.statusLockBase, + ); + const status = new Status({ + statusPath, + statusLockPath, + fs, + logger, }); - }); - test( - 'start with seed nodes option', - async () => { - const password = 'abc123'; - const nodePath = path.join(dataDir, 'polykey'); - const statusPath = path.join(nodePath, config.defaults.statusBase); - const statusLockPath = path.join( - nodePath, - config.defaults.statusLockBase, - ); - const status = new Status({ - statusPath, - statusLockPath, - fs, - logger, - }); - const mockedConfigDefaultsNetwork = jestMockProps - .spyOnProp(config.defaults, 'network') - .mockValue({ - mainnet: { - [seedNodeId2]: { - host: seedNodeHost2, - port: seedNodePort2, - }, + const mockedConfigDefaultsNetwork = jestMockProps + .spyOnProp(config.defaults, 'network') + .mockValue({ + mainnet: { + [seedNodeId2]: { + host: seedNodeHost2, + port: seedNodePort2, }, - testnet: {}, - }); - await testUtils.pkStdio( - [ - 'agent', - 'start', - '--client-host', - '127.0.0.1', - '--proxy-host', - '127.0.0.1', - '--workers', - '0', - '--seed-nodes', - `${seedNodeId1}@${seedNodeHost1}:${seedNodePort1};`, - '--network', - 'mainnet', - '--verbose', - ], - { - env: { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - PK_FAST_PASSWORD_HASH: 'true', - }, - cwd: dataDir, }, - ); - await testUtils.pkStdio(['agent', 'stop'], { + testnet: {}, + }); + await testUtils.pkStdio( + [ + 'agent', + 'start', + '--client-host', + '127.0.0.1', + '--proxy-host', + '127.0.0.1', + '--workers', + '0', + '--seed-nodes', + `${seedNodeId1}@${seedNodeHost1}:${seedNodePort1};`, + '--network', + 'mainnet', + '--verbose', + ], + { env: { PK_NODE_PATH: nodePath, PK_PASSWORD: password, PK_FAST_PASSWORD_HASH: 'true', }, cwd: dataDir, - }); - mockedConfigDefaultsNetwork.mockRestore(); - await status.waitFor('DEAD'); - }, - globalThis.defaultTimeout * 2, - ); - test( - 'start with seed nodes environment variable', - async () => { - const password = 'abc123'; - const nodePath = path.join(dataDir, 'polykey'); - const statusPath = path.join(nodePath, config.defaults.statusBase); - const statusLockPath = path.join( - nodePath, - config.defaults.statusLockBase, - ); - const status = new Status({ - statusPath, - statusLockPath, - fs, - logger, - }); - const mockedConfigDefaultsNetwork = jestMockProps - .spyOnProp(config.defaults, 'network') - .mockValue({ - mainnet: {}, - testnet: { - [seedNodeId2]: { - host: seedNodeHost2, - port: seedNodePort2, - }, - }, - }); - await testUtils.pkStdio( - [ - 'agent', - 'start', - '--client-host', - '127.0.0.1', - '--proxy-host', - '127.0.0.1', - '--workers', - '0', - '--verbose', - ], - { - env: { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - PK_FAST_PASSWORD_HASH: 'true', - PK_SEED_NODES: `;${seedNodeId1}@${seedNodeHost1}:${seedNodePort1}`, - PK_NETWORK: 'testnet', + }, + ); + await testUtils.pkStdio(['agent', 'stop'], { + env: { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + PK_FAST_PASSWORD_HASH: 'true', + }, + cwd: dataDir, + }); + mockedConfigDefaultsNetwork.mockRestore(); + await status.waitFor('DEAD'); + }, + globalThis.defaultTimeout * 2, + ); + test( + 'start with seed nodes environment variable', + async () => { + const password = 'abc123'; + const nodePath = path.join(dataDir, 'polykey'); + const statusPath = path.join(nodePath, config.defaults.statusBase); + const statusLockPath = path.join( + nodePath, + config.defaults.statusLockBase, + ); + const status = new Status({ + statusPath, + statusLockPath, + fs, + logger, + }); + const mockedConfigDefaultsNetwork = jestMockProps + .spyOnProp(config.defaults, 'network') + .mockValue({ + mainnet: {}, + testnet: { + [seedNodeId2]: { + host: seedNodeHost2, + port: seedNodePort2, }, - cwd: dataDir, }, - ); - await testUtils.pkStdio(['agent', 'stop'], { + }); + await testUtils.pkStdio( + [ + 'agent', + 'start', + '--client-host', + '127.0.0.1', + '--proxy-host', + '127.0.0.1', + '--workers', + '0', + '--verbose', + ], + { env: { PK_NODE_PATH: nodePath, PK_PASSWORD: password, PK_FAST_PASSWORD_HASH: 'true', + PK_SEED_NODES: `;${seedNodeId1}@${seedNodeHost1}:${seedNodePort1}`, + PK_NETWORK: 'testnet', }, cwd: dataDir, - }); - mockedConfigDefaultsNetwork.mockRestore(); - await status.waitFor('DEAD'); - }, - globalThis.defaultTimeout * 2, - ); - }, - ); + }, + ); + await testUtils.pkStdio(['agent', 'stop'], { + env: { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + PK_FAST_PASSWORD_HASH: 'true', + }, + cwd: dataDir, + }); + mockedConfigDefaultsNetwork.mockRestore(); + await status.waitFor('DEAD'); + }, + globalThis.defaultTimeout * 2, + ); + }); }); diff --git a/tests/bin/bootstrap.test.ts b/tests/bin/bootstrap.test.ts index a2c69c2a8..eac71e1a2 100644 --- a/tests/bin/bootstrap.test.ts +++ b/tests/bin/bootstrap.test.ts @@ -32,12 +32,7 @@ describe('bootstrap', () => { const passwordPath = path.join(dataDir, 'password'); await fs.promises.writeFile(passwordPath, password); const { exitCode, stdout } = await testUtils.pkExec( - [ - 'bootstrap', - '--password-file', - passwordPath, - '--verbose', - ], + ['bootstrap', '--password-file', passwordPath, '--verbose'], { env: { PK_NODE_PATH: path.join(dataDir, 'polykey'), @@ -64,13 +59,22 @@ describe('bootstrap', () => { const password = 'password'; const passwordPath = path.join(dataDir, 'password'); await fs.promises.writeFile(passwordPath, password); - const keyPair = await keysUtils.generateKeyPair(); + const keyPair = keysUtils.generateKeyPair(); const privateKeyjwK = keysUtils.privateKeyToJWK(keyPair.privateKey); - const privateKeyJWE = keysUtils.wrapWithPassword(password, privateKeyjwK, keysUtils.passwordOpsLimits.min, keysUtils.passwordMemLimits.min) + const privateKeyJWE = keysUtils.wrapWithPassword( + password, + privateKeyjwK, + keysUtils.passwordOpsLimits.min, + keysUtils.passwordMemLimits.min, + ); const privateKeyPath = path.join(dataDir, 'private.jwe'); - await fs.promises.writeFile(privateKeyPath, JSON.stringify(privateKeyJWE), { - encoding: 'utf-8', - }); + await fs.promises.writeFile( + privateKeyPath, + JSON.stringify(privateKeyJWE), + { + encoding: 'utf-8', + }, + ); const { exitCode: exitCode1 } = await testUtils.pkExec( [ 'bootstrap', @@ -159,12 +163,7 @@ describe('bootstrap', () => { const password = 'password'; const [bootstrapProcess1, bootstrapProcess2] = await Promise.all([ testUtils.pkSpawn( - [ - 'bootstrap', - '--verbose', - '--format', - 'json', - ], + ['bootstrap', '--verbose', '--format', 'json'], { env: { PK_NODE_PATH: path.join(dataDir, 'polykey'), @@ -177,12 +176,7 @@ describe('bootstrap', () => { logger.getChild('bootstrapProcess1'), ), testUtils.pkSpawn( - [ - 'bootstrap', - '--verbose', - '--format', - 'json', - ], + ['bootstrap', '--verbose', '--format', 'json'], { env: { PK_NODE_PATH: path.join(dataDir, 'polykey'), @@ -265,7 +259,10 @@ describe('bootstrap', () => { // This line is brittle // It may change if the log format changes // Make sure to keep it updated at the exact point when the root key pair is generated - if (l === 'INFO:polykey.KeyRing:Generating root key pair and recovery code') { + if ( + l === + 'INFO:polykey.KeyRing:Generating root key pair and recovery code' + ) { bootstrapProcess1.kill('SIGINT'); resolve(); } @@ -276,12 +273,7 @@ describe('bootstrap', () => { }); // Attempting to bootstrap should fail with existing state const bootstrapProcess2 = await testUtils.pkExec( - [ - 'bootstrap', - '--verbose', - '--format', - 'json', - ], + ['bootstrap', '--verbose', '--format', 'json'], { env: { PK_NODE_PATH: path.join(dataDir, 'polykey'), diff --git a/tests/bin/identities/allowDisallowPermissions.test.ts b/tests/bin/identities/allowDisallowPermissions.test.ts index 0441deee6..9c06dffd3 100644 --- a/tests/bin/identities/allowDisallowPermissions.test.ts +++ b/tests/bin/identities/allowDisallowPermissions.test.ts @@ -1,19 +1,19 @@ import type { Host, Port } from '@/network/types'; import type { IdentityId, ProviderId } from '@/identities/types'; -import type { ClaimLinkIdentity } from '@/claims/types'; -import type { Gestalt } from '@/gestalts/types'; import type { NodeId } from '@/ids/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { SignedClaim } from '@/claims/types'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; -import { poll, sysexits } from '@/utils'; +import { sysexits } from '@/utils'; import * as nodesUtils from '@/nodes/utils'; -import * as claimsUtils from '@/claims/utils'; import * as identitiesUtils from '@/identities/utils'; -import * as testUtils from '../../utils'; -import TestProvider from '../../identities/TestProvider'; import * as keysUtils from '@/keys/utils/index'; +import { encodeProviderIdentityId } from '@/identities/utils'; +import TestProvider from '../../identities/TestProvider'; +import * as testUtils from '../../utils'; describe('allow/disallow/permissions', () => { const logger = new Logger('allow/disallow/permissions test', LogLevel.WARN, [ @@ -82,15 +82,16 @@ describe('allow/disallow/permissions', () => { accessToken: 'def456', }); provider.users[identity] = {}; - const identityClaim: ClaimLinkIdentity = { - type: 'identity', - node: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), - provider: provider.id, - identity: identity, + const identityClaim = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), + sub: encodeProviderIdentityId([provider.id, identity]), }; - const [, claimEncoded] = await node.sigchain.addClaim(identityClaim); - const claim = claimsUtils.decodeClaim(claimEncoded); - await provider.publishClaim(identity, claim); + const [, claim] = await node.sigchain.addClaim(identityClaim); + await provider.publishClaim( + identity, + claim as SignedClaim, + ); }); afterEach(async () => { await node.stop(); @@ -269,27 +270,9 @@ describe('allow/disallow/permissions', () => { }, cwd: dataDir, }); - await poll( - async () => { - const gestalts = await poll>( - async () => { - return await pkAgent.gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 2) return true; - return false; - }, - 100, - ); + while ((await pkAgent.discovery.waitForDiscoveryTasks()) > 0) { + // Waiting for discovery to complete + } ({ exitCode } = await testUtils.pkStdio( ['identities', 'trust', providerString], { diff --git a/tests/bin/identities/authenticateAuthenticated.test.ts b/tests/bin/identities/authenticateAuthenticated.test.ts index 1311403b5..f8c595c13 100644 --- a/tests/bin/identities/authenticateAuthenticated.test.ts +++ b/tests/bin/identities/authenticateAuthenticated.test.ts @@ -6,9 +6,9 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import { sysexits } from '@/utils'; import * as identitiesUtils from '@/identities/utils'; +import * as keysUtils from '@/keys/utils/index'; import TestProvider from '../../identities/TestProvider'; import * as testUtils from '../../utils'; -import * as keysUtils from '@/keys/utils/index'; describe('authenticate/authenticated', () => { const logger = new Logger('authenticate/authenticated test', LogLevel.WARN, [ diff --git a/tests/bin/identities/claim.test.ts b/tests/bin/identities/claim.test.ts index 9bc8333c2..d8525d8e2 100644 --- a/tests/bin/identities/claim.test.ts +++ b/tests/bin/identities/claim.test.ts @@ -1,7 +1,7 @@ import type { - IdentityClaimId, IdentityId, ProviderId, + ProviderIdentityClaimId, } from '@/identities/types'; import type { Host } from '@/network/types'; import path from 'path'; @@ -10,9 +10,9 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import { sysexits } from '@/utils'; import * as identitiesUtils from '@/identities/utils'; +import * as keysUtils from '@/keys/utils/index'; import TestProvider from '../../identities/TestProvider'; import * as testUtils from '../../utils'; -import * as keysUtils from '@/keys/utils/index'; describe('claim', () => { const logger = new Logger('claim test', LogLevel.WARN, [new StreamHandler()]); @@ -102,11 +102,11 @@ describe('claim', () => { // Check for claim on the provider const claim = await testProvider.getClaim( testToken.identityId, - '0' as IdentityClaimId, + '0' as ProviderIdentityClaimId, ); expect(claim).toBeDefined(); expect(claim!.id).toBe('0'); - expect(claim!.payload.data.type).toBe('identity'); + // Expect(claim!.payload.data.type).toBe('identity'); mockedBrowser.mockRestore(); }, ); diff --git a/tests/bin/identities/discoverGet.test.ts b/tests/bin/identities/discoverGet.test.ts index ba58b05cc..46ab3cb32 100644 --- a/tests/bin/identities/discoverGet.test.ts +++ b/tests/bin/identities/discoverGet.test.ts @@ -1,20 +1,20 @@ import type { IdentityId, ProviderId } from '@/identities/types'; -import type { ClaimLinkIdentity } from '@/claims/types'; -import type { Gestalt } from '@/gestalts/types'; import type { Host, Port } from '@/network/types'; import type { NodeId } from '@/ids/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { SignedClaim } from '@/claims/types'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; -import { poll, sysexits } from '@/utils'; +import { sysexits } from '@/utils'; import * as identitiesUtils from '@/identities/utils'; -import * as claimsUtils from '@/claims/utils'; import * as nodesUtils from '@/nodes/utils'; -import * as testNodesUtils from '../../nodes/utils'; -import TestProvider from '../../identities/TestProvider'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import { encodeProviderIdentityId } from '@/identities/utils'; +import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; +import * as testNodesUtils from '../../nodes/utils'; describe('discover/get', () => { const logger = new Logger('discover/get test', LogLevel.WARN, [ @@ -100,6 +100,7 @@ describe('discover/get', () => { }); pkAgent.identitiesManager.registerProvider(testProvider); // Add node claim to gestalt + await nodeB.acl.setNodeAction(nodeAId, 'claim'); await nodeA.nodeManager.claimNode(nodeBId); // Add identity claim to gestalt testProvider.users[identityId] = {}; @@ -107,15 +108,16 @@ describe('discover/get', () => { await nodeA.identitiesManager.putToken(testProvider.id, identityId, { accessToken: 'abc123', }); - const identityClaim: ClaimLinkIdentity = { - type: 'identity', - node: nodesUtils.encodeNodeId(nodeAId), - provider: testProvider.id, - identity: identityId, + const identityClaim = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(nodeAId), + sub: encodeProviderIdentityId([testProvider.id, identityId]), }; - const [, claimEncoded] = await nodeA.sigchain.addClaim(identityClaim); - const claim = claimsUtils.decodeClaim(claimEncoded); - await testProvider.publishClaim(identityId, claim); + const [, claim] = await nodeA.sigchain.addClaim(identityClaim); + await testProvider.publishClaim( + identityId, + claim as SignedClaim, + ); }); afterEach(async () => { await pkAgent.stop(); @@ -126,8 +128,7 @@ describe('discover/get', () => { recursive: true, }); }); - // TestUtils.testIf(testUtils.isTestPlatformEmpty) - test( + testUtils.testIf(testUtils.isTestPlatformEmpty)( 'discovers and gets gestalt by node', async () => { // Need an authenticated identity @@ -180,28 +181,9 @@ describe('discover/get', () => { ); expect(discoverResponse.exitCode).toBe(0); // Since discovery is a background process we need to wait for the - // gestalt to be discovered - await poll( - async () => { - const gestalts = await poll>( - async () => { - return await pkAgent.gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 3) return true; - return false; - }, - 100, - ); + while ((await pkAgent.discovery.waitForDiscoveryTasks()) > 0) { + // Gestalt to be discovered + } // Now we can get the gestalt const getResponse = await testUtils.pkStdio( ['identities', 'get', nodesUtils.encodeNodeId(nodeAId)], @@ -220,7 +202,7 @@ describe('discover/get', () => { // Revert side effects await pkAgent.gestaltGraph.unsetNode(nodeAId); await pkAgent.gestaltGraph.unsetNode(nodeBId); - await pkAgent.gestaltGraph.unsetIdentity(testProvider.id, identityId); + await pkAgent.gestaltGraph.unsetIdentity([testProvider.id, identityId]); await pkAgent.nodeGraph.unsetNode(nodeAId); await pkAgent.identitiesManager.delToken( testToken.providerId, @@ -285,28 +267,9 @@ describe('discover/get', () => { ); expect(discoverResponse.exitCode).toBe(0); // Since discovery is a background process we need to wait for the - // gestalt to be discovered - await poll( - async () => { - const gestalts = await poll>( - async () => { - return await pkAgent.gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 3) return true; - return false; - }, - 100, - ); + while ((await pkAgent.discovery.waitForDiscoveryTasks()) > 0) { + // Gestalt to be discovered + } // Now we can get the gestalt const getResponse = await testUtils.pkStdio( ['identities', 'get', providerString], @@ -325,7 +288,7 @@ describe('discover/get', () => { // Revert side effects await pkAgent.gestaltGraph.unsetNode(nodeAId); await pkAgent.gestaltGraph.unsetNode(nodeBId); - await pkAgent.gestaltGraph.unsetIdentity(testProvider.id, identityId); + await pkAgent.gestaltGraph.unsetIdentity([testProvider.id, identityId]); await pkAgent.nodeGraph.unsetNode(nodeAId); await pkAgent.identitiesManager.delToken( testToken.providerId, diff --git a/tests/bin/identities/search.test.ts b/tests/bin/identities/search.test.ts index 54e73feaf..9f753d6e9 100644 --- a/tests/bin/identities/search.test.ts +++ b/tests/bin/identities/search.test.ts @@ -6,9 +6,9 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import { sysexits } from '@/utils'; import * as identitiesUtils from '@/identities/utils'; +import * as keysUtils from '@/keys/utils/index'; import TestProvider from '../../identities/TestProvider'; import * as testUtils from '../../utils'; -import * as keysUtils from '@/keys/utils/index'; describe('search', () => { const logger = new Logger('search test', LogLevel.WARN, [ diff --git a/tests/bin/identities/trustUntrustList.test.ts b/tests/bin/identities/trustUntrustList.test.ts index 1b6c2f446..0b0935a92 100644 --- a/tests/bin/identities/trustUntrustList.test.ts +++ b/tests/bin/identities/trustUntrustList.test.ts @@ -1,31 +1,32 @@ import type { Host, Port } from '@/network/types'; import type { IdentityId, ProviderId } from '@/identities/types'; -import type { ClaimLinkIdentity } from '@/claims/types'; import type { NodeId } from '@/ids/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { SignedClaim } from '@/claims/types'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import { sysexits } from '@/utils'; import * as nodesUtils from '@/nodes/utils'; -import * as claimsUtils from '@/claims/utils'; import * as identitiesUtils from '@/identities/utils'; -import TestProvider from '../../identities/TestProvider'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import { encodeProviderIdentityId } from '@/identities/utils'; +import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; describe('trust/untrust/list', () => { const logger = new Logger('trust/untrust/list test', LogLevel.WARN, [ new StreamHandler(), ]); const password = 'password'; - const provider = new TestProvider(); const identity = 'abc' as IdentityId; - const providerString = `${provider.id}:${identity}`; const testToken = { providerId: 'test-provider' as ProviderId, identityId: 'test_user' as IdentityId, }; + let provider: TestProvider; + let providerString: string; let dataDir: string; let nodePath: string; let pkAgent: PolykeyAgent; @@ -34,6 +35,8 @@ describe('trust/untrust/list', () => { let nodeHost: Host; let nodePort: Port; beforeEach(async () => { + provider = new TestProvider(); + providerString = `${provider.id}:${identity}`; dataDir = await fs.promises.mkdtemp( path.join(globalThis.tmpDir, 'polykey-test-'), ); @@ -81,15 +84,16 @@ describe('trust/untrust/list', () => { accessToken: 'def456', }); provider.users[identity] = {}; - const identityClaim: ClaimLinkIdentity = { - type: 'identity', - node: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), - provider: provider.id, - identity: identity, + const identityClaim = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), + sub: encodeProviderIdentityId([provider.id, identity]), }; - const [, claimEncoded] = await node.sigchain.addClaim(identityClaim); - const claim = claimsUtils.decodeClaim(claimEncoded); - await provider.publishClaim(identity, claim); + const [, claim] = await node.sigchain.addClaim(identityClaim); + await provider.publishClaim( + identity, + claim as SignedClaim, + ); }); afterEach(async () => { await node.stop(); @@ -172,10 +176,10 @@ describe('trust/untrust/list', () => { }, )); expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)).toHaveLength(2); expect(JSON.parse(stdout)[0]).toEqual({ permissions: ['notify'], - nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + nodes: [{ nodeId: nodesUtils.encodeNodeId(nodeId) }], identities: [ { providerId: provider.id, @@ -209,10 +213,10 @@ describe('trust/untrust/list', () => { }, )); expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)).toHaveLength(2); expect(JSON.parse(stdout)[0]).toEqual({ permissions: null, - nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + nodes: [{ nodeId: nodesUtils.encodeNodeId(nodeId) }], identities: [ { providerId: provider.id, @@ -222,7 +226,7 @@ describe('trust/untrust/list', () => { }); // Revert side-effects await pkAgent.gestaltGraph.unsetNode(nodeId); - await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); + await pkAgent.gestaltGraph.unsetIdentity([provider.id, identity]); await pkAgent.nodeGraph.unsetNode(nodeId); await pkAgent.identitiesManager.delToken( testToken.providerId, @@ -320,10 +324,10 @@ describe('trust/untrust/list', () => { }, )); expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)).toHaveLength(2); expect(JSON.parse(stdout)[0]).toEqual({ permissions: ['notify'], - nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + nodes: [{ nodeId: nodesUtils.encodeNodeId(nodeId) }], identities: [ { providerId: provider.id, @@ -357,10 +361,10 @@ describe('trust/untrust/list', () => { }, )); expect(exitCode).toBe(0); - expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)).toHaveLength(2); expect(JSON.parse(stdout)[0]).toEqual({ permissions: null, - nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + nodes: [{ nodeId: nodesUtils.encodeNodeId(nodeId) }], identities: [ { providerId: provider.id, @@ -370,7 +374,7 @@ describe('trust/untrust/list', () => { }); // Revert side-effects await pkAgent.gestaltGraph.unsetNode(nodeId); - await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); + await pkAgent.gestaltGraph.unsetIdentity([provider.id, identity]); await pkAgent.nodeGraph.unsetNode(nodeId); await pkAgent.identitiesManager.delToken( testToken.providerId, diff --git a/tests/bin/keys/cert.test.ts b/tests/bin/keys/cert.test.ts index 2da9f9494..8ac18d0f9 100644 --- a/tests/bin/keys/cert.test.ts +++ b/tests/bin/keys/cert.test.ts @@ -45,7 +45,7 @@ describe('cert', () => { }, )); expect(exitCode).toBe(0); - const certStatus = JSON.parse(stdout).rootCertPem; + const certStatus = JSON.parse(stdout).certChainPEM; expect(certCommand).toBe(certStatus); }); }); diff --git a/tests/bin/keys/encryptDecrypt.test.ts b/tests/bin/keys/encryptDecrypt.test.ts index f9241ae40..aa752deff 100644 --- a/tests/bin/keys/encryptDecrypt.test.ts +++ b/tests/bin/keys/encryptDecrypt.test.ts @@ -1,11 +1,11 @@ +import type { StatusLive } from '@/status/types'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; import sysexits from '@/utils/sysexits'; -import { StatusLive } from '@/status/types'; +import * as testUtils from '../../utils'; describe('encrypt-decrypt', () => { const logger = new Logger('encrypt-decrypt test', LogLevel.WARN, [ @@ -16,9 +16,8 @@ describe('encrypt-decrypt', () => { let agentClose; let agentStatus: StatusLive; beforeEach(async () => { - ({ agentDir, agentPassword, agentClose, agentStatus } = await testUtils.setupTestAgent( - logger, - )); + ({ agentDir, agentPassword, agentClose, agentStatus } = + await testUtils.setupTestAgent(logger)); }); afterEach(async () => { await agentClose(); @@ -28,7 +27,10 @@ describe('encrypt-decrypt', () => { )('decrypts data', async () => { const dataPath = path.join(agentDir, 'data'); const publicKey = keysUtils.publicKeyFromNodeId(agentStatus.data.nodeId); - const encrypted = keysUtils.encryptWithPublicKey(publicKey, Buffer.from('abc')); + const encrypted = keysUtils.encryptWithPublicKey( + publicKey, + Buffer.from('abc'), + ); await fs.promises.writeFile(dataPath, encrypted, { encoding: 'binary', }); @@ -59,7 +61,14 @@ describe('encrypt-decrypt', () => { encoding: 'binary', }); const { exitCode, stdout } = await testUtils.pkExec( - ['keys', 'encrypt', dataPath, nodesUtils.encodeNodeId(targetNodeId), '--format', 'json'], + [ + 'keys', + 'encrypt', + dataPath, + nodesUtils.encodeNodeId(targetNodeId), + '--format', + 'json', + ], { env: { PK_NODE_PATH: agentDir, @@ -74,9 +83,11 @@ describe('encrypt-decrypt', () => { encryptedData: expect.any(String), }); const encrypted = JSON.parse(stdout).encryptedData; - const decrypted = keysUtils.decryptWithPrivateKey(targetkeyPair, Buffer.from(encrypted, 'binary')) + const decrypted = keysUtils.decryptWithPrivateKey( + targetkeyPair, + Buffer.from(encrypted, 'binary'), + ); expect(decrypted?.toString()).toBe('abc'); - }); testUtils.testIf( testUtils.isTestPlatformEmpty || testUtils.isTestPlatformDocker, @@ -106,13 +117,15 @@ describe('encrypt-decrypt', () => { encryptedData: expect.any(String), }); const encrypted = JSON.parse(stdout).encryptedData; - const decrypted = keysUtils.decryptWithPrivateKey(targetkeyPair, Buffer.from(encrypted, 'binary')) + const decrypted = keysUtils.decryptWithPrivateKey( + targetkeyPair, + Buffer.from(encrypted, 'binary'), + ); expect(decrypted?.toString()).toBe('abc'); }); testUtils.testIf( testUtils.isTestPlatformEmpty || testUtils.isTestPlatformDocker, )('encrypts data fails with invalid JWK file', async () => { - const dataPath = path.join(agentDir, 'data'); const jwkPath = path.join(agentDir, 'jwk'); await fs.promises.writeFile(dataPath, 'abc', { diff --git a/tests/bin/keys/keypair.test.ts b/tests/bin/keys/keypair.test.ts index 20941f436..405690f7b 100644 --- a/tests/bin/keys/keypair.test.ts +++ b/tests/bin/keys/keypair.test.ts @@ -2,7 +2,9 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import * as testUtils from '../../utils'; describe('keypair', () => { - const logger = new Logger('keypair test', LogLevel.WARN, [new StreamHandler()]); + const logger = new Logger('keypair test', LogLevel.WARN, [ + new StreamHandler(), + ]); let agentDir; let agentPassword; let agentClose; @@ -33,18 +35,18 @@ describe('keypair', () => { expect(JSON.parse(stdout)).toEqual({ publicKey: { alg: expect.any(String), - crv: expect.any(String), + crv: expect.any(String), ext: expect.any(Boolean), - key_ops: expect.any(Array), + key_ops: expect.any(Array), kty: expect.any(String), x: expect.any(String), }, - privateKey: { + privateKey: { ciphertext: expect.any(String), iv: expect.any(String), protected: expect.any(String), tag: expect.any(String), - } + }, }); }); }); diff --git a/tests/bin/keys/private.test.ts b/tests/bin/keys/private.test.ts index 6911f215e..2e5da204e 100644 --- a/tests/bin/keys/private.test.ts +++ b/tests/bin/keys/private.test.ts @@ -2,7 +2,9 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import * as testUtils from '../../utils'; describe('private', () => { - const logger = new Logger('private test', LogLevel.WARN, [new StreamHandler()]); + const logger = new Logger('private test', LogLevel.WARN, [ + new StreamHandler(), + ]); let agentDir; let agentPassword; let agentClose; @@ -23,7 +25,7 @@ describe('private', () => { env: { PK_NODE_PATH: agentDir, PK_PASSWORD: agentPassword, - PK_PASSWORD_NEW: 'newPassword' + PK_PASSWORD_NEW: 'newPassword', }, cwd: agentDir, command: globalThis.testCmd, diff --git a/tests/bin/keys/public.test.ts b/tests/bin/keys/public.test.ts index f85426a22..e2eac4a87 100644 --- a/tests/bin/keys/public.test.ts +++ b/tests/bin/keys/public.test.ts @@ -2,7 +2,9 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import * as testUtils from '../../utils'; describe('public', () => { - const logger = new Logger('public test', LogLevel.WARN, [new StreamHandler()]); + const logger = new Logger('public test', LogLevel.WARN, [ + new StreamHandler(), + ]); let agentDir; let agentPassword; let agentClose; @@ -17,7 +19,7 @@ describe('public', () => { testUtils.testIf( testUtils.isTestPlatformEmpty || testUtils.isTestPlatformDocker, )('public gets public key', async () => { - const { exitCode, stdout, stderr } = await testUtils.pkExec( + const { exitCode, stdout } = await testUtils.pkExec( ['keys', 'public', 'password', '--format', 'json'], { env: { @@ -33,7 +35,7 @@ describe('public', () => { alg: expect.any(String), crv: expect.any(String), ext: expect.any(Boolean), - key_ops: expect.any(Array), + key_ops: expect.any(Array), kty: expect.any(String), x: expect.any(String), }); diff --git a/tests/bin/keys/reset.test.ts b/tests/bin/keys/reset.test.ts index 1e03b84e7..fd09434db 100644 --- a/tests/bin/keys/reset.test.ts +++ b/tests/bin/keys/reset.test.ts @@ -7,7 +7,7 @@ import * as keysUtils from '@/keys/utils'; import * as testUtils from '../../utils'; describe('reset', () => { - const logger = new Logger('reset test', LogLevel.WARN, [new StreamHandler()]); + const logger = new Logger('reset test', LogLevel.INFO, [new StreamHandler()]); const password = 'helloworld'; let dataDir: string; let nodePath: string; @@ -87,6 +87,7 @@ describe('reset', () => { )); expect(exitCode).toBe(0); // Get new keypair and nodeId and compare against old + // FIXME, this is still on the old password for some reason ({ exitCode, stdout } = await testUtils.pkStdio( ['keys', 'keypair', '--format', 'json'], { diff --git a/tests/bin/keys/signVerify.test.ts b/tests/bin/keys/signVerify.test.ts index f3f56c0d5..3872db965 100644 --- a/tests/bin/keys/signVerify.test.ts +++ b/tests/bin/keys/signVerify.test.ts @@ -1,12 +1,12 @@ +import type { StatusLive } from '@/status/types'; +import type { Signature } from '@/keys/types'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import * as testUtils from '../../utils'; -import { StatusLive } from '@/status/types'; import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; import sysexits from '@/utils/sysexits'; -import { Signature } from '@/keys/types'; +import * as testUtils from '../../utils'; describe('sign-verify', () => { const logger = new Logger('sign-verify test', LogLevel.WARN, [ @@ -17,9 +17,8 @@ describe('sign-verify', () => { let agentClose; let agentStatus: StatusLive; beforeEach(async () => { - ({ agentDir, agentPassword, agentClose, agentStatus } = await testUtils.setupTestAgent( - logger, - )); + ({ agentDir, agentPassword, agentClose, agentStatus } = + await testUtils.setupTestAgent(logger)); }); afterEach(async () => { await agentClose(); @@ -49,11 +48,13 @@ describe('sign-verify', () => { }); const signed = JSON.parse(stdout).signature; - expect(keysUtils.verifyWithPublicKey( - publicKey, - Buffer.from('sign-me'), - Buffer.from(signed, 'binary') as Signature, - )).toBeTrue(); + expect( + keysUtils.verifyWithPublicKey( + publicKey, + Buffer.from('sign-me'), + Buffer.from(signed, 'binary') as Signature, + ), + ).toBeTrue(); }); testUtils.testIf( testUtils.isTestPlatformEmpty || testUtils.isTestPlatformDocker, @@ -64,13 +65,24 @@ describe('sign-verify', () => { await fs.promises.writeFile(dataPath, 'sign-me', { encoding: 'binary', }); - const signed = keysUtils.signWithPrivateKey(sourceKeyPair, Buffer.from('sign-me', 'binary')); + const signed = keysUtils.signWithPrivateKey( + sourceKeyPair, + Buffer.from('sign-me', 'binary'), + ); const signaturePath = path.join(agentDir, 'signature'); await fs.promises.writeFile(signaturePath, signed, { encoding: 'binary', }); const { exitCode, stdout } = await testUtils.pkExec( - ['keys', 'verify', dataPath, signaturePath, nodesUtils.encodeNodeId(nodeId), '--format', 'json'], + [ + 'keys', + 'verify', + dataPath, + signaturePath, + nodesUtils.encodeNodeId(nodeId), + '--format', + 'json', + ], { env: { PK_NODE_PATH: agentDir, @@ -94,7 +106,10 @@ describe('sign-verify', () => { await fs.promises.writeFile(dataPath, 'sign-me', { encoding: 'binary', }); - const signed = keysUtils.signWithPrivateKey(sourceKeyPair, Buffer.from('sign-me', 'binary')); + const signed = keysUtils.signWithPrivateKey( + sourceKeyPair, + Buffer.from('sign-me', 'binary'), + ); const signaturePath = path.join(agentDir, 'signature'); await fs.promises.writeFile(signaturePath, signed, { encoding: 'binary', diff --git a/tests/bin/nodes/add.test.ts b/tests/bin/nodes/add.test.ts index 9794212c0..72945830f 100644 --- a/tests/bin/nodes/add.test.ts +++ b/tests/bin/nodes/add.test.ts @@ -8,9 +8,9 @@ import { sysexits } from '@/utils'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; import NodeManager from '@/nodes/NodeManager'; +import * as keysUtils from '@/keys/utils/index'; import * as testNodesUtils from '../../nodes/utils'; import * as testUtils from '../../utils'; -import * as keysUtils from '@/keys/utils/index'; describe('add', () => { const logger = new Logger('add test', LogLevel.WARN, [new StreamHandler()]); @@ -53,7 +53,6 @@ describe('add', () => { }); afterEach(async () => { await pkAgent.stop(); - await pkAgent.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, diff --git a/tests/bin/nodes/claim.test.ts b/tests/bin/nodes/claim.test.ts index e2c57078c..1a2514a1b 100644 --- a/tests/bin/nodes/claim.test.ts +++ b/tests/bin/nodes/claim.test.ts @@ -5,9 +5,9 @@ import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; +import * as keysUtils from '@/keys/utils/index'; import * as testNodesUtils from '../../nodes/utils'; import * as testUtils from '../../utils'; -import * as keysUtils from '@/keys/utils/index'; describe('claim', () => { const logger = new Logger('claim test', LogLevel.WARN, [new StreamHandler()]); @@ -66,21 +66,21 @@ describe('claim', () => { await pkAgent.acl.setNodePerm(remoteId, { gestalt: { notify: null, + claim: null, }, vaults: {}, }); await remoteNode.acl.setNodePerm(localId, { gestalt: { notify: null, + claim: null, }, vaults: {}, }); }); afterEach(async () => { await pkAgent.stop(); - await pkAgent.destroy(); await remoteNode.stop(); - await remoteNode.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, @@ -100,7 +100,7 @@ describe('claim', () => { }, ); expect(exitCode).toBe(0); - expect(stdout).toContain('Gestalt Invite'); + expect(stdout).toContain('Successfully generated a cryptolink claim'); expect(stdout).toContain(remoteIdEncoded); }, ); @@ -121,7 +121,7 @@ describe('claim', () => { }, ); expect(exitCode).toBe(0); - expect(stdout).toContain('Gestalt Invite'); + expect(stdout).toContain('Successfully generated a cryptolink'); expect(stdout).toContain(nodesUtils.encodeNodeId(remoteId)); }, ); diff --git a/tests/bin/nodes/find.test.ts b/tests/bin/nodes/find.test.ts index 7e70cb2e2..63c348c03 100644 --- a/tests/bin/nodes/find.test.ts +++ b/tests/bin/nodes/find.test.ts @@ -6,9 +6,9 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; import { sysexits } from '@/errors'; +import * as keysUtils from '@/keys/utils/index'; import * as testNodesUtils from '../../nodes/utils'; import * as testUtils from '../../utils'; -import * as keysUtils from '@/keys/utils/index'; describe('find', () => { const logger = new Logger('find test', LogLevel.WARN, [new StreamHandler()]); @@ -96,11 +96,8 @@ describe('find', () => { }); afterEach(async () => { await polykeyAgent.stop(); - await polykeyAgent.destroy(); await remoteOnline.stop(); - await remoteOnline.destroy(); await remoteOffline.stop(); - await remoteOffline.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, diff --git a/tests/bin/nodes/ping.test.ts b/tests/bin/nodes/ping.test.ts index fd40d3717..5c8839782 100644 --- a/tests/bin/nodes/ping.test.ts +++ b/tests/bin/nodes/ping.test.ts @@ -6,9 +6,9 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; import { sysexits } from '@/errors'; +import * as keysUtils from '@/keys/utils/index'; import * as testNodesUtils from '../../nodes/utils'; import * as testUtils from '../../utils'; -import * as keysUtils from '@/keys/utils/index'; describe('ping', () => { const logger = new Logger('ping test', LogLevel.WARN, [new StreamHandler()]); @@ -91,11 +91,8 @@ describe('ping', () => { }); afterEach(async () => { await polykeyAgent.stop(); - await polykeyAgent.destroy(); await remoteOnline.stop(); - await remoteOnline.destroy(); await remoteOffline.stop(); - await remoteOffline.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, diff --git a/tests/bin/notifications/sendReadClear.test.ts b/tests/bin/notifications/sendReadClear.test.ts index 661615b22..ab80a6f85 100644 --- a/tests/bin/notifications/sendReadClear.test.ts +++ b/tests/bin/notifications/sendReadClear.test.ts @@ -193,7 +193,8 @@ describe('send/read/claim', () => { type: 'General', message: 'test message 3', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderId), + sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); expect(readNotifications[1]).toMatchObject({ @@ -201,7 +202,8 @@ describe('send/read/claim', () => { type: 'General', message: 'test message 2', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderId), + sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); expect(readNotifications[2]).toMatchObject({ @@ -209,7 +211,8 @@ describe('send/read/claim', () => { type: 'General', message: 'test message 1', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderId), + sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); // Read only unread (none) @@ -253,7 +256,8 @@ describe('send/read/claim', () => { type: 'General', message: 'test message 1', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderId), + sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); expect(readNotifications[1]).toMatchObject({ @@ -261,7 +265,8 @@ describe('send/read/claim', () => { type: 'General', message: 'test message 2', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderId), + sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); expect(readNotifications[2]).toMatchObject({ @@ -269,7 +274,8 @@ describe('send/read/claim', () => { type: 'General', message: 'test message 3', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderId), + sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); // Read only one notification @@ -295,7 +301,8 @@ describe('send/read/claim', () => { type: 'General', message: 'test message 3', }, - senderId: nodesUtils.encodeNodeId(senderId), + iss: nodesUtils.encodeNodeId(senderId), + sub: nodesUtils.encodeNodeId(receiverId), isRead: true, }); // Clear notifications diff --git a/tests/bin/secrets/secrets.test.ts b/tests/bin/secrets/secrets.test.ts index 58bd1cccf..73e72befc 100644 --- a/tests/bin/secrets/secrets.test.ts +++ b/tests/bin/secrets/secrets.test.ts @@ -42,7 +42,6 @@ describe('CLI secrets', () => { }); afterEach(async () => { await polykeyAgent.stop(); - await polykeyAgent.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, diff --git a/tests/bin/utils.test.ts b/tests/bin/utils.test.ts index cedac5d09..8fab82c78 100644 --- a/tests/bin/utils.test.ts +++ b/tests/bin/utils.test.ts @@ -62,19 +62,19 @@ describe('bin/utils', () => { type: 'dict', data: { key1: 'value1', key2: 'value2' }, }), - ).toBe('key1\tvalue1\nkey2\tvalue2\n'); + ).toBe('key1\t"value1"\nkey2\t"value2"\n'); expect( binUtils.outputFormatter({ type: 'dict', data: { key1: 'first\nsecond', key2: 'first\nsecond\n' }, }), - ).toBe('key1\tfirst\n\tsecond\nkey2\tfirst\n\tsecond\n'); + ).toBe('key1\t"first\\nsecond"\nkey2\t"first\\nsecond\\n"\n'); expect( binUtils.outputFormatter({ type: 'dict', data: { key1: null, key2: undefined }, }), - ).toBe('key1\t\nkey2\t\n'); + ).toBe('key1\t""\nkey2\t""\n'); // JSON expect( binUtils.outputFormatter({ @@ -93,7 +93,7 @@ describe('bin/utils', () => { const port = 55555 as Port; const nodeId = testUtils.generateRandomNodeId(); const standardError = new TypeError('some error'); - const pkError = new ErrorPolykey('some pk error', { + const pkError = new ErrorPolykey('some pk error', { timestamp, data, }); diff --git a/tests/bin/vaults/vaults.test.ts b/tests/bin/vaults/vaults.test.ts index 1676ab8a1..b58d39ebd 100644 --- a/tests/bin/vaults/vaults.test.ts +++ b/tests/bin/vaults/vaults.test.ts @@ -1,6 +1,7 @@ -import type { NodeIdEncoded, NodeAddress, NodeInfo } from '@/nodes/types'; +import type { NodeAddress } from '@/nodes/types'; import type { VaultId, VaultName } from '@/vaults/types'; import type { Host } from '@/network/types'; +import type { GestaltNodeInfo } from '@/gestalts/types'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; @@ -9,9 +10,9 @@ import * as nodesUtils from '@/nodes/utils'; import * as vaultsUtils from '@/vaults/utils'; import sysexits from '@/utils/sysexits'; import NotificationsManager from '@/notifications/NotificationsManager'; +import * as keysUtils from '@/keys/utils/index'; import * as testNodesUtils from '../../nodes/utils'; import * as testUtils from '../../utils'; -import * as keysUtils from '@/keys/utils/index'; describe('CLI vaults', () => { const password = 'password'; @@ -23,24 +24,18 @@ describe('CLI vaults', () => { let vaultNumber: number; let vaultName: VaultName; - const nodeId1Encoded = - 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded; - const nodeId2Encoded = - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg' as NodeIdEncoded; - const nodeId3Encoded = - 'v359vgrgmqf1r5g4fvisiddjknjko6bmm4qv7646jr7fi9enbfuug' as NodeIdEncoded; + const nodeId1 = testNodesUtils.generateRandomNodeId(); + const nodeId2 = testNodesUtils.generateRandomNodeId(); + const nodeId3 = testNodesUtils.generateRandomNodeId(); - const node1: NodeInfo = { - id: nodeId1Encoded, - chain: {}, + const node1: GestaltNodeInfo = { + nodeId: nodeId1, }; - const node2: NodeInfo = { - id: nodeId2Encoded, - chain: {}, + const node2: GestaltNodeInfo = { + nodeId: nodeId2, }; - const node3: NodeInfo = { - id: nodeId3Encoded, - chain: {}, + const node3: GestaltNodeInfo = { + nodeId: nodeId3, }; // Helper functions @@ -84,7 +79,6 @@ describe('CLI vaults', () => { }); afterEach(async () => { await polykeyAgent.stop(); - await polykeyAgent.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, @@ -256,8 +250,7 @@ describe('CLI vaults', () => { ); await targetPolykeyAgent.gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(polykeyAgent.keyRing.getNodeId()), - chain: {}, + nodeId: polykeyAgent.keyRing.getNodeId(), }); const targetNodeId = targetPolykeyAgent.keyRing.getNodeId(); const targetNodeIdEncoded = nodesUtils.encodeNodeId(targetNodeId); @@ -280,8 +273,8 @@ describe('CLI vaults', () => { }); const nodeId = polykeyAgent.keyRing.getNodeId(); - await targetPolykeyAgent.gestaltGraph.setGestaltActionByNode( - nodeId, + await targetPolykeyAgent.gestaltGraph.setGestaltAction( + ['node', nodeId], 'scan', ); await targetPolykeyAgent.acl.setVaultAction(vaultId, nodeId, 'clone'); @@ -392,7 +385,6 @@ describe('CLI vaults', () => { expect(result.exitCode).toBe(sysexits.USAGE); await targetPolykeyAgent.stop(); - await targetPolykeyAgent.destroy(); await fs.promises.rm(dataDir2, { force: true, recursive: true, @@ -418,8 +410,7 @@ describe('CLI vaults', () => { const targetNodeId = testNodesUtils.generateRandomNodeId(); const targetNodeIdEncoded = nodesUtils.encodeNodeId(targetNodeId); await polykeyAgent.gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(targetNodeId), - chain: {}, + nodeId: targetNodeId, }); expect( (await polykeyAgent.acl.getNodePerm(targetNodeId))?.vaults[vaultId], @@ -465,13 +456,12 @@ describe('CLI vaults', () => { const targetNodeId = testNodesUtils.generateRandomNodeId(); const targetNodeIdEncoded = nodesUtils.encodeNodeId(targetNodeId); await polykeyAgent.gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(targetNodeId), - chain: {}, + nodeId: targetNodeId, }); // Creating permissions - await polykeyAgent.gestaltGraph.setGestaltActionByNode( - targetNodeId, + await polykeyAgent.gestaltGraph.setGestaltAction( + ['node', targetNodeId], 'scan', ); await polykeyAgent.acl.setVaultAction(vaultId1, targetNodeId, 'clone'); @@ -545,13 +535,12 @@ describe('CLI vaults', () => { const targetNodeId = testNodesUtils.generateRandomNodeId(); const targetNodeIdEncoded = nodesUtils.encodeNodeId(targetNodeId); await polykeyAgent.gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(targetNodeId), - chain: {}, + nodeId: targetNodeId, }); // Creating permissions - await polykeyAgent.gestaltGraph.setGestaltActionByNode( - targetNodeId, + await polykeyAgent.gestaltGraph.setGestaltAction( + ['node', targetNodeId], 'scan', ); await polykeyAgent.acl.setVaultAction(vaultId1, targetNodeId, 'clone'); @@ -849,8 +838,7 @@ describe('CLI vaults', () => { } as NodeAddress); await remoteOnline.gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(polykeyAgent.keyRing.getNodeId()), - chain: {}, + nodeId: polykeyAgent.keyRing.getNodeId(), }); const commands1 = [ @@ -869,8 +857,8 @@ describe('CLI vaults', () => { 'ErrorVaultsPermissionDenied: Permission was denied - Scanning is not allowed for', ); - await remoteOnline.gestaltGraph.setGestaltActionByNode( - polykeyAgent.keyRing.getNodeId(), + await remoteOnline.gestaltGraph.setGestaltAction( + ['node', polykeyAgent.keyRing.getNodeId()], 'notify', ); @@ -890,8 +878,8 @@ describe('CLI vaults', () => { 'ErrorVaultsPermissionDenied: Permission was denied - Scanning is not allowed for', ); - await remoteOnline.gestaltGraph.setGestaltActionByNode( - polykeyAgent.keyRing.getNodeId(), + await remoteOnline.gestaltGraph.setGestaltAction( + ['node', polykeyAgent.keyRing.getNodeId()], 'scan', ); @@ -931,7 +919,6 @@ describe('CLI vaults', () => { ); } finally { await remoteOnline?.stop(); - await remoteOnline?.destroy(); } }, globalThis.defaultTimeout * 2, diff --git a/tests/claims/payloads/claimLinkIdentity.test.ts b/tests/claims/payloads/claimLinkIdentity.test.ts index 442ce3d35..8bda82f45 100644 --- a/tests/claims/payloads/claimLinkIdentity.test.ts +++ b/tests/claims/payloads/claimLinkIdentity.test.ts @@ -5,48 +5,45 @@ import * as testsClaimsPayloadsUtils from './utils'; describe('claims/payloads/claimLinkIdentity', () => { testProp( 'parse claim link identity', - [ - testsClaimsPayloadsUtils.claimLinkIdentityEncodedArb, - fc.string() - ], + [testsClaimsPayloadsUtils.claimLinkIdentityEncodedArb, fc.string()], (claimLinkIdentityEncodedCorrect, claimLinkIdentityEncodedIncorrect) => { expect(() => { claimsPayloadsClaimLinkIdentity.parseClaimLinkIdentity( - claimLinkIdentityEncodedCorrect + claimLinkIdentityEncodedCorrect, ); }).not.toThrow(); expect(() => { claimsPayloadsClaimLinkIdentity.parseClaimLinkIdentity( - claimLinkIdentityEncodedIncorrect + claimLinkIdentityEncodedIncorrect, ); }).toThrow(); - } + }, ); testProp( 'parse signed claim link identity', [ testsClaimsPayloadsUtils.signedClaimEncodedArb( - testsClaimsPayloadsUtils.claimLinkIdentityArb + testsClaimsPayloadsUtils.claimLinkIdentityArb, ), fc.record({ payload: fc.string(), - signatures: fc.array(fc.string()) - }) + signatures: fc.array(fc.string()), + }), ], ( signedClaimLinkIdentityEncodedCorrect, - signedClaimLinkIdentityEncodedIncorrect + signedClaimLinkIdentityEncodedIncorrect, ) => { expect(() => { claimsPayloadsClaimLinkIdentity.parseSignedClaimLinkIdentity( - signedClaimLinkIdentityEncodedCorrect + signedClaimLinkIdentityEncodedCorrect, ); }).not.toThrow(); expect(() => { claimsPayloadsClaimLinkIdentity.parseSignedClaimLinkIdentity( - signedClaimLinkIdentityEncodedIncorrect + signedClaimLinkIdentityEncodedIncorrect, ); }).toThrow(); - } + }, ); }); diff --git a/tests/claims/payloads/claimLinkNode.test.ts b/tests/claims/payloads/claimLinkNode.test.ts index 33a86fd0f..61041990d 100644 --- a/tests/claims/payloads/claimLinkNode.test.ts +++ b/tests/claims/payloads/claimLinkNode.test.ts @@ -5,48 +5,45 @@ import * as testsClaimsPayloadsUtils from './utils'; describe('claims/payloads/claimLinkNode', () => { testProp( 'parse claim link node', - [ - testsClaimsPayloadsUtils.claimLinkNodeEncodedArb, - fc.string() - ], + [testsClaimsPayloadsUtils.claimLinkNodeEncodedArb, fc.string()], (claimLinkNodeEncodedCorrect, claimLinkNodeEncodedIncorrect) => { expect(() => { claimsPayloadsClaimLinkNode.parseClaimLinkNode( - claimLinkNodeEncodedCorrect + claimLinkNodeEncodedCorrect, ); }).not.toThrow(); expect(() => { claimsPayloadsClaimLinkNode.parseClaimLinkNode( - claimLinkNodeEncodedIncorrect + claimLinkNodeEncodedIncorrect, ); }).toThrow(); - } + }, ); testProp( 'parse signed claim link node', [ testsClaimsPayloadsUtils.signedClaimEncodedArb( - testsClaimsPayloadsUtils.claimLinkNodeArb + testsClaimsPayloadsUtils.claimLinkNodeArb, ), fc.record({ payload: fc.string(), - signatures: fc.array(fc.string()) - }) + signatures: fc.array(fc.string()), + }), ], ( signedClaimLinkNodeEncodedCorrect, - signedClaimLinkNodeEncodedIncorrect + signedClaimLinkNodeEncodedIncorrect, ) => { expect(() => { claimsPayloadsClaimLinkNode.parseSignedClaimLinkNode( - signedClaimLinkNodeEncodedCorrect + signedClaimLinkNodeEncodedCorrect, ); }).not.toThrow(); expect(() => { claimsPayloadsClaimLinkNode.parseSignedClaimLinkNode( - signedClaimLinkNodeEncodedIncorrect + signedClaimLinkNodeEncodedIncorrect, ); }).toThrow(); - } + }, ); }); diff --git a/tests/claims/payloads/utils.ts b/tests/claims/payloads/utils.ts index b0bc9ebbb..6810a24a5 100644 --- a/tests/claims/payloads/utils.ts +++ b/tests/claims/payloads/utils.ts @@ -1,61 +1,58 @@ -import type { - Claim, - SignedClaim -} from '@/claims/types'; -import type { - ClaimLinkNode, - ClaimLinkIdentity -} from '@/claims/payloads'; +import type { Claim, SignedClaim } from '@/claims/types'; +import type { ClaimLinkNode, ClaimLinkIdentity } from '@/claims/payloads'; import fc from 'fast-check'; import * as claimsUtils from '@/claims/utils'; import * as testsClaimsUtils from '../utils'; import * as testsTokensUtils from '../../tokens/utils'; import * as testsIdsUtils from '../../ids/utils'; -const claimLinkIdentityArb = testsClaimsUtils.claimArb.chain( - (claim) => { - return fc.record({ +const claimLinkIdentityArb = testsClaimsUtils.claimArb.chain((claim) => { + return fc + .record({ iss: testsIdsUtils.nodeIdEncodedArb, - sub: testsIdsUtils.providerIdentityIdEncodedArb - }).chain(value => { + sub: testsIdsUtils.providerIdentityIdEncodedArb, + }) + .chain((value) => { return fc.constant({ + typ: 'ClaimLinkIdentity', ...claim, - ...value + ...value, }); }); - } -) as fc.Arbitrary; +}) as fc.Arbitrary; -const claimLinkIdentityEncodedArb = claimLinkIdentityArb.map(claimsUtils.generateClaim); +const claimLinkIdentityEncodedArb = claimLinkIdentityArb.map( + claimsUtils.generateClaim, +); -const claimLinkNodeArb = testsClaimsUtils.claimArb.chain( - (claim) => { - return fc.record({ +const claimLinkNodeArb = testsClaimsUtils.claimArb.chain((claim) => { + return fc + .record({ iss: testsIdsUtils.nodeIdEncodedArb, sub: testsIdsUtils.nodeIdEncodedArb, - }).chain(value => { + }) + .chain((value) => { return fc.constant({ + typ: 'ClaimLinkNode', ...claim, - ...value + ...value, }); }); - } -) as fc.Arbitrary; +}) as fc.Arbitrary; const claimLinkNodeEncodedArb = claimLinkNodeArb.map(claimsUtils.generateClaim); const signedClaimArb =

( - payloadArb: fc.Arbitrary

+ payloadArb: fc.Arbitrary

, ): fc.Arbitrary> => { return fc.record({ payload: payloadArb, - signatures: fc.array(testsTokensUtils.tokenHeaderSignatureArb) + signatures: fc.array(testsTokensUtils.tokenHeaderSignatureArb), }); }; -const signedClaimEncodedArb = (payloadArb: fc.Arbitrary) => signedClaimArb(payloadArb).map( - claimsUtils.generateSignedClaim -); +const signedClaimEncodedArb = (payloadArb: fc.Arbitrary) => + signedClaimArb(payloadArb).map(claimsUtils.generateSignedClaim); export { claimLinkIdentityArb, diff --git a/tests/claims/utils.test.ts b/tests/claims/utils.test.ts index 032145463..a41ee1ba4 100644 --- a/tests/claims/utils.test.ts +++ b/tests/claims/utils.test.ts @@ -6,22 +6,15 @@ import * as testsClaimsUtils from './utils'; describe('claims/utils', () => { testProp( 'parse claim', - [ - testsClaimsUtils.claimEncodedArb, - fc.string() - ], + [testsClaimsUtils.claimEncodedArb, fc.string()], (claimEncodedCorrect, claimEncodedIncorrect) => { expect(() => { - claimsUtils.parseClaim( - claimEncodedCorrect - ); + claimsUtils.parseClaim(claimEncodedCorrect); }).not.toThrow(); expect(() => { - claimsUtils.parseClaim( - claimEncodedIncorrect - ); + claimsUtils.parseClaim(claimEncodedIncorrect); }).toThrow(validationErrors.ErrorParse); - } + }, ); testProp( 'parse signed claim', @@ -29,62 +22,54 @@ describe('claims/utils', () => { testsClaimsUtils.signedClaimEncodedArb, fc.record({ payload: fc.string(), - signatures: fc.array(fc.string()) - }) + signatures: fc.array(fc.string()), + }), ], (signedClaimEncodedCorrect, signedClaimEncodedIncorrect) => { expect(() => { - claimsUtils.parseSignedClaim( - signedClaimEncodedCorrect - ); + claimsUtils.parseSignedClaim(signedClaimEncodedCorrect); }).not.toThrow(); expect(() => { - claimsUtils.parseSignedClaim( - signedClaimEncodedIncorrect - ); + claimsUtils.parseSignedClaim(signedClaimEncodedIncorrect); }).toThrow(validationErrors.ErrorParse); }, ); testProp( 'hashing signed claims', - [ - testsClaimsUtils.signedClaimArb - ], + [testsClaimsUtils.signedClaimArb], (signedClaim) => { const signedClaimDigest = claimsUtils.hashSignedClaim( signedClaim, - 'blake2b-256' + 'blake2b-256', ); const signedClaimEncoded = claimsUtils.generateSignedClaim(signedClaim); const signedClaim_ = claimsUtils.parseSignedClaim(signedClaimEncoded); const signedClaimDigest_ = claimsUtils.hashSignedClaim( signedClaim_, - 'blake2b-256' + 'blake2b-256', ); expect(signedClaimDigest_).toEqual(signedClaimDigest); - } + }, ); testProp( 'encode and decode signed claims digests', - [ - testsClaimsUtils.signedClaimArb - ], + [testsClaimsUtils.signedClaimArb], (signedClaim) => { const signedClaimDigest = claimsUtils.hashSignedClaim( signedClaim, - 'blake2b-256' + 'blake2b-256', ); const signedClaimDigestEncoded = claimsUtils.encodeSignedClaimDigest( signedClaimDigest, - 'blake2b-256' + 'blake2b-256', ); const result = claimsUtils.decodeSignedClaimDigest( - signedClaimDigestEncoded + signedClaimDigestEncoded, ); expect(result).toBeDefined(); const [signedClaimDigest_, format] = result!; expect(signedClaimDigest_).toStrictEqual(signedClaimDigest); expect(format).toBe('blake2b-256'); - } + }, ); }); diff --git a/tests/claims/utils.ts b/tests/claims/utils.ts index 6a73e7f2a..8561bccd6 100644 --- a/tests/claims/utils.ts +++ b/tests/claims/utils.ts @@ -15,25 +15,20 @@ const claimInitialArb = fc.record({ const signedClaimInitialArb = fc.record({ payload: claimInitialArb, - signatures: fc.array(testsTokensUtils.tokenHeaderSignatureArb) + signatures: fc.array(testsTokensUtils.tokenHeaderSignatureArb), }) as fc.Arbitrary; -const signedClaimDigestArb = signedClaimInitialArb.map( - (signedClaimInitial) => { - return claimsUtils.hashSignedClaim( - signedClaimInitial, - 'blake2b-256' - ); - } -); +const signedClaimDigestArb = signedClaimInitialArb.map((signedClaimInitial) => { + return claimsUtils.hashSignedClaim(signedClaimInitial, 'blake2b-256'); +}); const signedClaimDigestEncodedArb = signedClaimDigestArb.map( (signedClaimDigest) => { return claimsUtils.encodeSignedClaimDigest( signedClaimDigest, - 'blake2b-256' + 'blake2b-256', ); - } + }, ); const claimArb = fc.oneof( @@ -44,19 +39,19 @@ const claimArb = fc.oneof( nbf: fc.nat(), seq: fc.nat(), prevClaimId: testsIdsUtils.claimIdEncodedArb, - prevDigest: signedClaimDigestEncodedArb - }) + prevDigest: signedClaimDigestEncodedArb, + }), ); const claimEncodedArb = claimArb.map(claimsUtils.generateClaim); const signedClaimArb = fc.record({ payload: claimArb, - signatures: fc.array(testsTokensUtils.tokenHeaderSignatureArb) + signatures: fc.array(testsTokensUtils.tokenHeaderSignatureArb), }) as fc.Arbitrary; const signedClaimEncodedArb = signedClaimArb.map( - claimsUtils.generateSignedClaim + claimsUtils.generateSignedClaim, ); export { diff --git a/tests/client/GRPCClientClient.test.ts b/tests/client/GRPCClientClient.test.ts index bafd70061..027771691 100644 --- a/tests/client/GRPCClientClient.test.ts +++ b/tests/client/GRPCClientClient.test.ts @@ -11,8 +11,8 @@ import Session from '@/sessions/Session'; import * as clientErrors from '@/client/errors'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import { timerStart } from '@/utils'; -import * as testClientUtils from './utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testClientUtils from './utils'; describe(GRPCClientClient.name, () => { const password = 'password'; @@ -57,7 +57,6 @@ describe(GRPCClientClient.name, () => { await client.destroy(); await testClientUtils.closeTestClientServer(server); await pkAgent.stop(); - await pkAgent.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, diff --git a/tests/client/service/agentStatus.test.ts b/tests/client/service/agentStatus.test.ts index 86b3429db..4f271cedc 100644 --- a/tests/client/service/agentStatus.test.ts +++ b/tests/client/service/agentStatus.test.ts @@ -4,9 +4,10 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; +import { DB } from '@matrixai/db'; import KeyRing from '@/keys/KeyRing'; import TaskManager from '@/tasks/TaskManager'; -import CertManager from '@/keys/CertManager'; +import CertManager from '@/keys/CertManager'; import Proxy from '@/network/Proxy'; import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientClient from '@/client/GRPCClientClient'; @@ -15,9 +16,8 @@ import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_ import * as agentPB from '@/proto/js/polykey/v1/agent/agent_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; -import { DB } from '@matrixai/db'; -import * as testsUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testsUtils from '../../utils'; describe('agentStatus', () => { const logger = new Logger('agentStatus test', LogLevel.WARN, [ @@ -46,7 +46,7 @@ describe('agentStatus', () => { db = await DB.createDB({ dbPath, logger, - }) + }); keyRing = await KeyRing.createKeyRing({ password, keysPath, @@ -61,7 +61,7 @@ describe('agentStatus', () => { keyRing, taskManager, logger, - }) + }); grpcServerClient = new GRPCServer({ logger }); await grpcServerClient.start({ services: [], diff --git a/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts b/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts index 381ec9b60..91411b6fb 100644 --- a/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts +++ b/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts @@ -1,6 +1,10 @@ -import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; -import type { NodeId, NodeInfo } from '@/nodes/types'; +import type { IdentityId, ProviderId } from '@/identities/types'; +import type { NodeId } from '@/nodes/types'; import type { Host, Port } from '@/network/types'; +import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { ClaimIdEncoded } from '@/ids/index'; +import type { ProviderIdentityClaimId } from '@/identities/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -21,6 +25,9 @@ import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; import * as permissionsPB from '@/proto/js/polykey/v1/permissions/permissions_pb'; import * as nodesUtils from '@/nodes/utils'; import * as clientUtils from '@/client/utils/utils'; +import { encodeProviderIdentityId } from '@/ids/index'; +import Token from '@/tokens/Token'; +import * as keysUtils from '@/keys/utils'; describe('gestaltsActionsByIdentity', () => { const logger = new Logger('gestaltsActionsByIdentity test', LogLevel.WARN, [ @@ -29,18 +36,14 @@ describe('gestaltsActionsByIdentity', () => { const password = 'helloworld'; const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; - const nodeId = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 5, - ]); - const node: NodeInfo = { - id: nodesUtils.encodeNodeId(nodeId), - chain: {}, + const keyPair = keysUtils.generateKeyPair(); + const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); + const node: GestaltNodeInfo = { + nodeId: nodeId, }; - const identity: IdentityInfo = { + const identity: GestaltIdentityInfo = { identityId: 'identityId' as IdentityId, providerId: 'providerId' as ProviderId, - claims: {}, }; let dataDir: string; let gestaltGraph: GestaltGraph; @@ -67,7 +70,27 @@ describe('gestaltsActionsByIdentity', () => { logger, }); // Need identity set in GG with linked node to set permissions - await gestaltGraph.linkNodeAndIdentity(node, identity); + // Constructing the claim + const dummyClaim: ClaimLinkIdentity = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(nodeId), + sub: encodeProviderIdentityId([identity.providerId, identity.identityId]), + jti: '' as ClaimIdEncoded, + iat: 0, + nbf: 0, + exp: 0, + aud: '', + seq: 0, + prevClaimId: null, + prevDigest: null, + }; + const token = Token.fromPayload(dummyClaim); + token.signWithPrivateKey(keyPair); + const signedClaim = token.toSigned(); + await gestaltGraph.linkNodeAndIdentity(node, identity, { + claim: signedClaim, + meta: { providerIdentityClaimId: '' as ProviderIdentityClaimId }, + }); const clientService = { gestaltsActionsSetByIdentity: gestaltsActionsSetByIdentity({ authenticate, diff --git a/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts b/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts index 439f9b754..4c67b74e8 100644 --- a/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts +++ b/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts @@ -1,5 +1,6 @@ -import type { NodeId, NodeInfo } from '@/nodes/types'; +import type { NodeId } from '@/nodes/types'; import type { Host, Port } from '@/network/types'; +import type { GestaltNodeInfo } from '@/gestalts/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -32,9 +33,8 @@ describe('gestaltsActionsByNode', () => { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, ]); - const node: NodeInfo = { - id: nodesUtils.encodeNodeId(nodeId), - chain: {}, + const node: GestaltNodeInfo = { + nodeId: nodeId, }; let dataDir: string; let gestaltGraph: GestaltGraph; @@ -112,7 +112,7 @@ describe('gestaltsActionsByNode', () => { test('sets/unsets/gets actions by node', async () => { // Set permission const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(node.id); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(node.nodeId)); const request = new permissionsPB.ActionSet(); request.setNode(nodeMessage); request.setAction('notify'); diff --git a/tests/client/service/gestaltsDiscoveryByIdentity.test.ts b/tests/client/service/gestaltsDiscoveryByIdentity.test.ts index 709c12664..f27a24cda 100644 --- a/tests/client/service/gestaltsDiscoveryByIdentity.test.ts +++ b/tests/client/service/gestaltsDiscoveryByIdentity.test.ts @@ -1,6 +1,7 @@ -import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; +import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; import type { Key } from '@/keys/types'; +import type { GestaltIdentityInfo } from '@/gestalts/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -27,7 +28,6 @@ import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; import * as utils from '@/utils'; import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; -import { CertificatePEMChain } from '@/keys/types'; import * as testsUtils from '../../utils/index'; describe('gestaltsDiscoveryByIdentity', () => { @@ -37,10 +37,9 @@ describe('gestaltsDiscoveryByIdentity', () => { const password = 'helloworld'; const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; - const identity: IdentityInfo = { + const identity: GestaltIdentityInfo = { identityId: 'identityId' as IdentityId, providerId: 'providerId' as ProviderId, - claims: {}, }; const authToken = 'abc123'; let dataDir: string; @@ -106,6 +105,7 @@ describe('gestaltsDiscoveryByIdentity', () => { keyRing, sigchain, db, + gestaltGraph, logger, }); proxy = new Proxy({ @@ -148,6 +148,7 @@ describe('gestaltsDiscoveryByIdentity', () => { nodeGraph, sigchain, taskManager, + gestaltGraph, logger, }); await nodeManager.start(); @@ -158,7 +159,6 @@ describe('gestaltsDiscoveryByIdentity', () => { gestaltGraph, identitiesManager, nodeManager, - sigchain, taskManager, logger, }); diff --git a/tests/client/service/gestaltsDiscoveryByNode.test.ts b/tests/client/service/gestaltsDiscoveryByNode.test.ts index d60366a7f..d85591b9f 100644 --- a/tests/client/service/gestaltsDiscoveryByNode.test.ts +++ b/tests/client/service/gestaltsDiscoveryByNode.test.ts @@ -1,6 +1,6 @@ -import type { NodeInfo } from '@/nodes/types'; import type { Host, Port } from '@/network/types'; import type { Key } from '@/keys/types'; +import type { GestaltNodeInfo } from '@/gestalts/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -29,7 +29,6 @@ import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; import * as testNodesUtils from '../../nodes/utils'; -import { CertificatePEMChain } from '@/keys/types'; import * as testsUtils from '../../utils/index'; describe('gestaltsDiscoveryByNode', () => { @@ -39,9 +38,8 @@ describe('gestaltsDiscoveryByNode', () => { const password = 'helloworld'; const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; - const node: NodeInfo = { - id: nodesUtils.encodeNodeId(testNodesUtils.generateRandomNodeId()), - chain: {}, + const node: GestaltNodeInfo = { + nodeId: testNodesUtils.generateRandomNodeId(), }; const authToken = 'abc123'; let dataDir: string; @@ -107,6 +105,7 @@ describe('gestaltsDiscoveryByNode', () => { keyRing, sigchain, db, + gestaltGraph, logger, }); proxy = new Proxy({ @@ -149,6 +148,7 @@ describe('gestaltsDiscoveryByNode', () => { nodeGraph, sigchain, taskManager, + gestaltGraph, logger, }); await nodeManager.start(); @@ -159,7 +159,6 @@ describe('gestaltsDiscoveryByNode', () => { gestaltGraph, identitiesManager, nodeManager, - sigchain, taskManager, logger, }); @@ -211,7 +210,7 @@ describe('gestaltsDiscoveryByNode', () => { .spyOn(Discovery.prototype, 'queueDiscoveryByNode') .mockResolvedValue(); const request = new nodesPB.Node(); - request.setNodeId(node.id); + request.setNodeId(nodesUtils.encodeNodeId(node.nodeId)); const response = await grpcClient.gestaltsDiscoveryByNode( request, clientUtils.encodeAuthFromPassword(password), diff --git a/tests/client/service/gestaltsGestaltGetByIdentity.test.ts b/tests/client/service/gestaltsGestaltGetByIdentity.test.ts index e3101ecf6..8d2cae4d2 100644 --- a/tests/client/service/gestaltsGestaltGetByIdentity.test.ts +++ b/tests/client/service/gestaltsGestaltGetByIdentity.test.ts @@ -1,8 +1,9 @@ -import type { Gestalt } from '@/gestalts/types'; +import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; import type { NodeId } from '@/ids/types'; -import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; -import type { NodeInfo } from '@/nodes/types'; +import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { ClaimIdEncoded, ProviderIdentityClaimId } from '@/ids/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -21,6 +22,9 @@ import * as gestaltsPB from '@/proto/js/polykey/v1/gestalts/gestalts_pb'; import * as gestaltUtils from '@/gestalts/utils'; import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; +import { encodeProviderIdentityId } from '@/ids/index'; +import Token from '@/tokens/Token'; +import * as keysUtils from '@/keys/utils'; describe('gestaltsGestaltGetByIdentity', () => { const logger = new Logger( @@ -31,25 +35,21 @@ describe('gestaltsGestaltGetByIdentity', () => { const password = 'helloworld'; const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; - const nodeId = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 5, - ]); - const node: NodeInfo = { - id: nodesUtils.encodeNodeId(nodeId), - chain: {}, + const keyPair = keysUtils.generateKeyPair(); + const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); + const node: GestaltNodeInfo = { + nodeId: nodeId, }; - const identity: IdentityInfo = { + const identity: GestaltIdentityInfo = { identityId: 'identityId' as IdentityId, providerId: 'providerId' as ProviderId, - claims: {}, }; - const nodeKey = gestaltUtils.keyFromNode(nodeId); - const identityKey = gestaltUtils.keyFromIdentity( - identity.providerId, - identity.identityId, - ); - const expectedGestalt: Gestalt = { + const nodeKey = gestaltUtils.encodeGestaltId(['node', nodeId]); + const identityKey = gestaltUtils.encodeGestaltId([ + 'identity', + [identity.providerId, identity.identityId], + ]); + const expectedGestalt = { matrix: {}, nodes: {}, identities: {}, @@ -58,8 +58,8 @@ describe('gestaltsGestaltGetByIdentity', () => { expectedGestalt.matrix[nodeKey] = {}; expectedGestalt.matrix[identityKey][nodeKey] = null; expectedGestalt.matrix[nodeKey][identityKey] = null; - expectedGestalt.nodes[nodeKey] = node; - expectedGestalt.identities[identityKey] = identity; + expectedGestalt.nodes[nodeKey] = expect.anything(); + expectedGestalt.identities[identityKey] = expect.anything(); let dataDir: string; let gestaltGraph: GestaltGraph; let acl: ACL; @@ -84,7 +84,27 @@ describe('gestaltsGestaltGetByIdentity', () => { acl, logger, }); - await gestaltGraph.linkNodeAndIdentity(node, identity); + // Constructing the claim + const dummyClaim: ClaimLinkIdentity = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(nodeId), + sub: encodeProviderIdentityId([identity.providerId, identity.identityId]), + jti: '' as ClaimIdEncoded, + iat: 0, + nbf: 0, + exp: 0, + aud: '', + seq: 0, + prevClaimId: null, + prevDigest: null, + }; + const token = Token.fromPayload(dummyClaim); + token.signWithPrivateKey(keyPair); + const signedClaim = token.toSigned(); + await gestaltGraph.linkNodeAndIdentity(node, identity, { + claim: signedClaim, + meta: { providerIdentityClaimId: '' as ProviderIdentityClaimId }, + }); const clientService = { gestaltsGestaltGetByIdentity: gestaltsGestaltGetByIdentity({ authenticate, diff --git a/tests/client/service/gestaltsGestaltGetByNode.test.ts b/tests/client/service/gestaltsGestaltGetByNode.test.ts index 1d7a3ceb6..3cb31a380 100644 --- a/tests/client/service/gestaltsGestaltGetByNode.test.ts +++ b/tests/client/service/gestaltsGestaltGetByNode.test.ts @@ -1,7 +1,13 @@ import type { Host, Port } from '@/network/types'; -import type { Gestalt } from '@/gestalts/types'; -import type { NodeId, NodeInfo } from '@/nodes/types'; -import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; +import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; +import type { NodeId } from '@/nodes/types'; +import type { + IdentityId, + ProviderId, + ProviderIdentityClaimId, +} from '@/identities/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { ClaimIdEncoded } from '@/ids/index'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -20,6 +26,9 @@ import * as gestaltsPB from '@/proto/js/polykey/v1/gestalts/gestalts_pb'; import * as gestaltUtils from '@/gestalts/utils'; import * as nodesUtils from '@/nodes/utils'; import * as clientUtils from '@/client/utils'; +import { encodeProviderIdentityId } from '@/ids/index'; +import Token from '@/tokens/Token'; +import * as keysUtils from '@/keys/utils'; describe('gestaltsGestaltGetByNode', () => { const logger = new Logger('gestaltsGestaltGetByNode test', LogLevel.WARN, [ @@ -28,25 +37,21 @@ describe('gestaltsGestaltGetByNode', () => { const password = 'helloworld'; const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; - const nodeId = IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 5, - ]); - const node: NodeInfo = { - id: nodesUtils.encodeNodeId(nodeId), - chain: {}, + const keyPair = keysUtils.generateKeyPair(); + const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); + const node: GestaltNodeInfo = { + nodeId: nodeId, }; - const identity: IdentityInfo = { + const identity: GestaltIdentityInfo = { identityId: 'identityId' as IdentityId, providerId: 'providerId' as ProviderId, - claims: {}, }; - const nodeKey = gestaltUtils.keyFromNode(nodeId); - const identityKey = gestaltUtils.keyFromIdentity( - identity.providerId, - identity.identityId, - ); - const expectedGestalt: Gestalt = { + const nodeKey = gestaltUtils.encodeGestaltId(['node', nodeId]); + const identityKey = gestaltUtils.encodeGestaltId([ + 'identity', + [identity.providerId, identity.identityId], + ]); + const expectedGestalt = { matrix: {}, nodes: {}, identities: {}, @@ -55,8 +60,8 @@ describe('gestaltsGestaltGetByNode', () => { expectedGestalt.matrix[nodeKey] = {}; expectedGestalt.matrix[identityKey][nodeKey] = null; expectedGestalt.matrix[nodeKey][identityKey] = null; - expectedGestalt.nodes[nodeKey] = node; - expectedGestalt.identities[identityKey] = identity; + expectedGestalt.nodes[nodeKey] = expect.anything(); + expectedGestalt.identities[identityKey] = expect.anything(); let dataDir: string; let gestaltGraph: GestaltGraph; let acl: ACL; @@ -81,7 +86,27 @@ describe('gestaltsGestaltGetByNode', () => { acl, logger, }); - await gestaltGraph.linkNodeAndIdentity(node, identity); + // Constructing the claim + const dummyClaim: ClaimLinkIdentity = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(nodeId), + sub: encodeProviderIdentityId([identity.providerId, identity.identityId]), + jti: '' as ClaimIdEncoded, + iat: 0, + nbf: 0, + exp: 0, + aud: '', + seq: 0, + prevClaimId: null, + prevDigest: null, + }; + const token = Token.fromPayload(dummyClaim); + token.signWithPrivateKey(keyPair); + const signedClaim = token.toSigned(); + await gestaltGraph.linkNodeAndIdentity(node, identity, { + claim: signedClaim, + meta: { providerIdentityClaimId: '' as ProviderIdentityClaimId }, + }); const clientService = { gestaltsGestaltGetByNode: gestaltsGestaltGetByNode({ authenticate, @@ -119,12 +144,14 @@ describe('gestaltsGestaltGetByNode', () => { }); test('gets gestalt by node', async () => { const request = new nodesPB.Node(); - request.setNodeId(node.id); + request.setNodeId(nodesUtils.encodeNodeId(node.nodeId)); const response = await grpcClient.gestaltsGestaltGetByNode( request, clientUtils.encodeAuthFromPassword(password), ); expect(response).toBeInstanceOf(gestaltsPB.Graph); - expect(JSON.parse(response.getGestaltGraph())).toEqual(expectedGestalt); + expect(JSON.parse(response.getGestaltGraph())).toMatchObject( + expectedGestalt, + ); }); }); diff --git a/tests/client/service/gestaltsGestaltList.test.ts b/tests/client/service/gestaltsGestaltList.test.ts index fe457a768..240ba713a 100644 --- a/tests/client/service/gestaltsGestaltList.test.ts +++ b/tests/client/service/gestaltsGestaltList.test.ts @@ -1,7 +1,10 @@ -import type { Gestalt } from '@/gestalts/types'; +import type { + Gestalt, + GestaltIdentityInfo, + GestaltNodeInfo, +} from '@/gestalts/types'; import type { NodeId } from '@/ids/types'; -import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; -import type { NodeInfo } from '@/nodes/types'; +import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; import fs from 'fs'; import path from 'path'; @@ -20,7 +23,6 @@ import * as gestaltsPB from '@/proto/js/polykey/v1/gestalts/gestalts_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as gestaltUtils from '@/gestalts/utils'; import * as clientUtils from '@/client/utils/utils'; -import * as nodesUtils from '@/nodes/utils'; describe('gestaltsGestaltList', () => { const logger = new Logger('gestaltsGestaltList test', LogLevel.WARN, [ @@ -34,34 +36,32 @@ describe('gestaltsGestaltList', () => { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, ]); - const node: NodeInfo = { - id: nodesUtils.encodeNodeId(nodeId), - chain: {}, + const node: GestaltNodeInfo = { + nodeId: nodeId, }; - const identity: IdentityInfo = { + const identity: GestaltIdentityInfo = { identityId: 'identityId' as IdentityId, providerId: 'providerId' as ProviderId, - claims: {}, }; - const nodeKey = gestaltUtils.keyFromNode(nodeId); - const identityKey = gestaltUtils.keyFromIdentity( - identity.providerId, - identity.identityId, - ); + const nodeKey = gestaltUtils.encodeGestaltId(['node', nodeId]); + const identityKey = gestaltUtils.encodeGestaltId([ + 'identity', + [identity.providerId, identity.identityId], + ]); const gestalt1: Gestalt = { matrix: {}, nodes: {}, identities: {}, }; gestalt1.matrix[nodeKey] = {}; - gestalt1.nodes[nodeKey] = node; + gestalt1.nodes[nodeKey] = expect.any(Object); const gestalt2: Gestalt = { matrix: {}, nodes: {}, identities: {}, }; gestalt2.matrix[identityKey] = {}; - gestalt2.identities[identityKey] = identity; + gestalt2.identities[identityKey] = expect.any(Object); let dataDir: string; let gestaltGraph: GestaltGraph; let acl: ACL; @@ -135,7 +135,6 @@ describe('gestaltsGestaltList', () => { gestalts.push(JSON.parse(gestalt.getName())); } expect(gestalts).toHaveLength(2); - expect(gestalts).toContainEqual(gestalt1); - expect(gestalts).toContainEqual(gestalt2); + expect(gestalts).toMatchObject([gestalt2, gestalt1]); }); }); diff --git a/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts b/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts index de660c0e7..eff70ffbd 100644 --- a/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts +++ b/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts @@ -1,9 +1,10 @@ -import type { NodeIdEncoded } from '@/ids/types'; -import type { ClaimLinkIdentity } from '@/claims/types'; -import type { ChainData } from '@/sigchain/types'; +import type { NodeId, ProviderIdentityClaimId } from '@/ids/types'; import type { IdentityId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; import type { Key } from '@/keys/types'; +import type { ClaimId } from '@/ids/types'; +import type { SignedClaim } from '@/claims/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -28,16 +29,16 @@ import gestaltsGestaltTrustByIdentity from '@/client/service/gestaltsGestaltTrus import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; -import * as claimsUtils from '@/claims/utils'; import * as gestaltsErrors from '@/gestalts/errors'; import * as keysUtils from '@/keys/utils'; import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; import * as utils from '@/utils/index'; -import * as testUtils from '../../utils'; -import TestProvider from '../../identities/TestProvider'; -import { CertificatePEMChain } from '@/keys/types'; +import { encodeProviderIdentityId } from '@/ids/index'; +import { sleep } from '@/utils/index'; import * as testsUtils from '../../utils/index'; +import TestProvider from '../../identities/TestProvider'; +import * as testUtils from '../../utils'; describe('gestaltsGestaltTrustByIdentity', () => { const logger = new Logger( @@ -48,13 +49,14 @@ describe('gestaltsGestaltTrustByIdentity', () => { const password = 'helloworld'; const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; - const testProvider = new TestProvider(); + let testProvider: TestProvider; // Create node to trust const connectedIdentity = 'trusted-node' as IdentityId; let nodeDataDir: string; let node: PolykeyAgent; - let nodeId: NodeIdEncoded; - const nodeChainData: ChainData = {}; + let nodeId: NodeId; + let claimId: ClaimId; + const nodeChainData: Record = {}; let mockedRequestChainData: jest.SpyInstance; const authToken = 'abc123'; let dataDir: string; @@ -73,6 +75,7 @@ describe('gestaltsGestaltTrustByIdentity', () => { let grpcServer: GRPCServer; let grpcClient: GRPCClientClient; beforeEach(async () => { + testProvider = new TestProvider(); mockedRequestChainData = jest .spyOn(NodeManager.prototype, 'requestChainData') .mockResolvedValue(nodeChainData); @@ -96,22 +99,24 @@ describe('gestaltsGestaltTrustByIdentity', () => { strictMemoryLock: false, }, }); - nodeId = nodesUtils.encodeNodeId(node.keyRing.getNodeId()); + nodeId = node.keyRing.getNodeId(); node.identitiesManager.registerProvider(testProvider); await node.identitiesManager.putToken(testProvider.id, connectedIdentity, { accessToken: 'abc123', }); testProvider.users['trusted-node'] = {}; - const identityClaim: ClaimLinkIdentity = { - type: 'identity', - node: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), - provider: testProvider.id, - identity: connectedIdentity, + const identityClaim = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), + sub: encodeProviderIdentityId([testProvider.id, connectedIdentity]), }; - const [claimId, claimEncoded] = await node.sigchain.addClaim(identityClaim); - const claim = claimsUtils.decodeClaim(claimEncoded); - nodeChainData[claimId] = claim; - await testProvider.publishClaim(connectedIdentity, claim); + const [claimId_, claim] = await node.sigchain.addClaim(identityClaim); + claimId = claimId_; + nodeChainData[claimId_] = claim; + await testProvider.publishClaim( + connectedIdentity, + claim as SignedClaim, + ); dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), @@ -160,6 +165,7 @@ describe('gestaltsGestaltTrustByIdentity', () => { keyRing, sigchain, db, + gestaltGraph, logger, }); identitiesManager.registerProvider(testProvider); @@ -210,11 +216,12 @@ describe('gestaltsGestaltTrustByIdentity', () => { nodeGraph, sigchain, taskManager, + gestaltGraph, logger, }); await nodeManager.start(); await nodeConnectionManager.start({ nodeManager }); - await nodeManager.setNode(nodesUtils.decodeNodeId(nodeId)!, { + await nodeManager.setNode(nodeId, { host: node.proxy.getProxyHost(), port: node.proxy.getProxyPort(), }); @@ -224,7 +231,6 @@ describe('gestaltsGestaltTrustByIdentity', () => { gestaltGraph, identitiesManager, nodeManager, - sigchain, taskManager, logger, }); @@ -283,14 +289,16 @@ describe('gestaltsGestaltTrustByIdentity', () => { test('trusts an identity (already set in gestalt graph)', async () => { testProvider.users['disconnected-user'] = {}; await gestaltGraph.linkNodeAndIdentity( - { - id: nodeId, - chain: {}, - }, + { nodeId: nodeId }, { providerId: testProvider.id, identityId: connectedIdentity, - claims: {}, + }, + { + claim: nodeChainData[claimId] as SignedClaim, + meta: { + providerIdentityClaimId: '' as ProviderIdentityClaimId, + }, }, ); const request = new identitiesPB.Provider(); @@ -302,16 +310,13 @@ describe('gestaltsGestaltTrustByIdentity', () => { ); expect(response).toBeInstanceOf(utilsPB.EmptyMessage); expect( - await gestaltGraph.getGestaltActionsByIdentity( - testProvider.id, - connectedIdentity, - ), + await gestaltGraph.getGestaltActions([ + 'identity', + [testProvider.id, connectedIdentity], + ]), ).toEqual({ notify: null, }); - // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); - await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); }); test('trusts an identity (new identity)', async () => { const request = new identitiesPB.Provider(); @@ -337,16 +342,13 @@ describe('gestaltsGestaltTrustByIdentity', () => { ); expect(response).toBeInstanceOf(utilsPB.EmptyMessage); expect( - await gestaltGraph.getGestaltActionsByIdentity( - testProvider.id, - connectedIdentity, - ), + await gestaltGraph.getGestaltActions([ + 'identity', + [testProvider.id, connectedIdentity], + ]), ).toEqual({ notify: null, }); - // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); - await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); }); test('cannot trust a disconnected identity', async () => { testProvider.users['disconnected-user'] = {}; @@ -373,14 +375,16 @@ describe('gestaltsGestaltTrustByIdentity', () => { }); test('trust extends to entire gestalt', async () => { await gestaltGraph.linkNodeAndIdentity( - { - id: nodeId, - chain: {}, - }, + { nodeId: nodeId }, { providerId: testProvider.id, identityId: connectedIdentity, - claims: {}, + }, + { + claim: nodeChainData[claimId] as SignedClaim, + meta: { + providerIdentityClaimId: '' as ProviderIdentityClaimId, + }, }, ); const request = new identitiesPB.Provider(); @@ -392,28 +396,20 @@ describe('gestaltsGestaltTrustByIdentity', () => { ); expect(response).toBeInstanceOf(utilsPB.EmptyMessage); expect( - await gestaltGraph.getGestaltActionsByIdentity( - testProvider.id, - connectedIdentity, - ), + await gestaltGraph.getGestaltActions([ + 'identity', + [testProvider.id, connectedIdentity], + ]), ).toEqual({ notify: null, }); - expect( - await gestaltGraph.getGestaltActionsByNode( - nodesUtils.decodeNodeId(nodeId)!, - ), - ).toEqual({ + expect(await gestaltGraph.getGestaltActions(['node', nodeId])).toEqual({ notify: null, }); - // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); - await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); }); test('links trusted identity to an existing node', async () => { await gestaltGraph.setNode({ - id: nodeId, - chain: {}, + nodeId: nodeId, }); const request = new identitiesPB.Provider(); request.setProviderId(testProvider.id); @@ -428,6 +424,11 @@ describe('gestaltsGestaltTrustByIdentity', () => { gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, ); // Wait and try again - should succeed second time + await sleep(2000); + await grpcClient.gestaltsGestaltTrustByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ); // Wait for both identity and node to be set in GG let existingTasks: number = 0; do { @@ -439,22 +440,15 @@ describe('gestaltsGestaltTrustByIdentity', () => { ); expect(response).toBeInstanceOf(utilsPB.EmptyMessage); expect( - await gestaltGraph.getGestaltActionsByIdentity( - testProvider.id, - connectedIdentity, - ), + await gestaltGraph.getGestaltActions([ + 'identity', + [testProvider.id, connectedIdentity], + ]), ).toEqual({ notify: null, }); - expect( - await gestaltGraph.getGestaltActionsByNode( - nodesUtils.decodeNodeId(nodeId)!, - ), - ).toEqual({ + expect(await gestaltGraph.getGestaltActions(['node', nodeId])).toEqual({ notify: null, }); - // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); - await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); }); }); diff --git a/tests/client/service/gestaltsGestaltTrustByNode.test.ts b/tests/client/service/gestaltsGestaltTrustByNode.test.ts index 37b2cd735..76670cca0 100644 --- a/tests/client/service/gestaltsGestaltTrustByNode.test.ts +++ b/tests/client/service/gestaltsGestaltTrustByNode.test.ts @@ -1,10 +1,10 @@ -import type { NodeIdEncoded } from '@/ids/types'; -import type { ClaimLinkIdentity } from '@/claims/types'; -import type { ChainData } from '@/sigchain/types'; -import type { Gestalt } from '@/gestalts/types'; +import type { NodeId, NodeIdEncoded } from '@/ids/types'; import type { IdentityId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; import type { Key } from '@/keys/types'; +import type { ClaimId } from '@/ids/types'; +import type { SignedClaim } from '@/claims/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -27,17 +27,15 @@ import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientClient from '@/client/GRPCClientClient'; import gestaltsGestaltTrustByNode from '@/client/service/gestaltsGestaltTrustByNode'; import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import { poll } from '@/utils'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; -import * as claimsUtils from '@/claims/utils'; import * as keysUtils from '@/keys/utils'; import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; import * as utils from '@/utils/index'; -import TestProvider from '../../identities/TestProvider'; -import { CertificatePEMChain } from '@/keys/types'; +import { encodeProviderIdentityId } from '@/identities/utils'; import * as testsUtils from '../../utils/index'; +import TestProvider from '../../identities/TestProvider'; describe('gestaltsGestaltTrustByNode', () => { const logger = new Logger('gestaltsGestaltTrustByNode test', LogLevel.WARN, [ @@ -51,8 +49,9 @@ describe('gestaltsGestaltTrustByNode', () => { const connectedIdentity = 'trusted-node' as IdentityId; let nodeDataDir: string; let node: PolykeyAgent; - let nodeId: NodeIdEncoded; - const nodeChainData: ChainData = {}; + let nodeId: NodeId; + let nodeIdEncoded: NodeIdEncoded; + const nodeChainData: Record = {}; let mockedRequestChainData: jest.SpyInstance; beforeAll(async () => { mockedRequestChainData = jest @@ -78,22 +77,24 @@ describe('gestaltsGestaltTrustByNode', () => { strictMemoryLock: false, }, }); - nodeId = nodesUtils.encodeNodeId(node.keyRing.getNodeId()); + nodeId = node.keyRing.getNodeId(); + nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); node.identitiesManager.registerProvider(testProvider); await node.identitiesManager.putToken(testProvider.id, connectedIdentity, { accessToken: 'abc123', }); testProvider.users['trusted-node'] = {}; - const identityClaim: ClaimLinkIdentity = { - type: 'identity', - node: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), - provider: testProvider.id, - identity: connectedIdentity, + const identityClaim = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), + sub: encodeProviderIdentityId([testProvider.id, connectedIdentity]), }; - const [claimId, claimEncoded] = await node.sigchain.addClaim(identityClaim); - const claim = claimsUtils.decodeClaim(claimEncoded); + const [claimId, claim] = await node.sigchain.addClaim(identityClaim); nodeChainData[claimId] = claim; - await testProvider.publishClaim(connectedIdentity, claim); + await testProvider.publishClaim( + connectedIdentity, + claim as SignedClaim, + ); }, globalThis.maxTimeout); afterAll(async () => { await node.stop(); @@ -168,6 +169,7 @@ describe('gestaltsGestaltTrustByNode', () => { keyRing, sigchain, db, + gestaltGraph, logger, }); identitiesManager.registerProvider(testProvider); @@ -218,11 +220,12 @@ describe('gestaltsGestaltTrustByNode', () => { nodeGraph, sigchain, taskManager, + gestaltGraph, logger, }); await nodeManager.start(); await nodeConnectionManager.start({ nodeManager }); - await nodeManager.setNode(nodesUtils.decodeNodeId(nodeId)!, { + await nodeManager.setNode(nodesUtils.decodeNodeId(nodeIdEncoded)!, { host: node.proxy.getProxyHost(), port: node.proxy.getProxyPort(), }); @@ -232,7 +235,6 @@ describe('gestaltsGestaltTrustByNode', () => { gestaltGraph, identitiesManager, nodeManager, - sigchain, taskManager, logger, }); @@ -282,95 +284,77 @@ describe('gestaltsGestaltTrustByNode', () => { }); }); test('trusts a node (already set in gestalt graph)', async () => { - await gestaltGraph.setNode({ - id: nodeId, - chain: {}, - }); + await gestaltGraph.setNode({ nodeId: nodeId }); const request = new nodesPB.Node(); - request.setNodeId(nodeId); + request.setNodeId(nodeIdEncoded); const response = await grpcClient.gestaltsGestaltTrustByNode( request, clientUtils.encodeAuthFromPassword(password), ); expect(response).toBeInstanceOf(utilsPB.EmptyMessage); expect( - await gestaltGraph.getGestaltActionsByNode( - nodesUtils.decodeNodeId(nodeId)!, - ), + await gestaltGraph.getGestaltActions([ + 'node', + nodesUtils.decodeNodeId(nodeIdEncoded)!, + ]), ).toEqual({ notify: null, }); // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); - await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeIdEncoded)!); + await gestaltGraph.unsetIdentity([testProvider.id, connectedIdentity]); }); test('trusts a node (new node)', async () => { const request = new nodesPB.Node(); - request.setNodeId(nodeId); + request.setNodeId(nodeIdEncoded); const response = await grpcClient.gestaltsGestaltTrustByNode( request, clientUtils.encodeAuthFromPassword(password), ); expect(response).toBeInstanceOf(utilsPB.EmptyMessage); expect( - await gestaltGraph.getGestaltActionsByNode( - nodesUtils.decodeNodeId(nodeId)!, - ), + await gestaltGraph.getGestaltActions([ + 'node', + nodesUtils.decodeNodeId(nodeIdEncoded)!, + ]), ).toEqual({ notify: null, }); // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); - await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeIdEncoded)!); + await gestaltGraph.unsetIdentity([testProvider.id, connectedIdentity]); }); test('trust extends to entire gestalt', async () => { const request = new nodesPB.Node(); - request.setNodeId(nodeId); + request.setNodeId(nodeIdEncoded); const response = await grpcClient.gestaltsGestaltTrustByNode( request, clientUtils.encodeAuthFromPassword(password), ); expect(response).toBeInstanceOf(utilsPB.EmptyMessage); expect( - await gestaltGraph.getGestaltActionsByNode( - nodesUtils.decodeNodeId(nodeId)!, - ), + await gestaltGraph.getGestaltActions([ + 'node', + nodesUtils.decodeNodeId(nodeIdEncoded)!, + ]), ).toEqual({ notify: null, }); // Give discovery process time to complete before checking identity actions // Wait for both identity and node to be set in GG - await poll( - async () => { - const gestalts = await poll>( - async () => { - return await gestaltGraph.getGestalts(); - }, - (_, result) => { - if (result.length === 1) return true; - return false; - }, - 100, - ); - return gestalts[0]; - }, - (_, result) => { - if (result === undefined) return false; - if (Object.keys(result.matrix).length === 2) return true; - return false; - }, - 100, - ); + while ((await discovery.waitForDiscoveryTasks()) > 0) { + // Waiting for tasks + } expect( - await gestaltGraph.getGestaltActionsByIdentity( - testProvider.id, - connectedIdentity, - ), + await gestaltGraph.getGestaltActions([ + 'identity', + [testProvider.id, connectedIdentity], + ]), ).toEqual({ notify: null, }); // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); - await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeIdEncoded)!); + await gestaltGraph.unsetIdentity([testProvider.id, connectedIdentity]); }); }); diff --git a/tests/client/service/identitiesAuthenticate.test.ts b/tests/client/service/identitiesAuthenticate.test.ts index 5f518527f..f126e16c1 100644 --- a/tests/client/service/identitiesAuthenticate.test.ts +++ b/tests/client/service/identitiesAuthenticate.test.ts @@ -1,5 +1,8 @@ import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; +import type KeyRing from 'keys/KeyRing'; +import type Sigchain from 'sigchain/Sigchain'; +import type GestaltGraph from 'gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -49,6 +52,9 @@ describe('identitiesAuthenticate', () => { }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, logger, }); testProvider = new TestProvider(); diff --git a/tests/client/service/identitiesAuthenticatedGet.test.ts b/tests/client/service/identitiesAuthenticatedGet.test.ts index e8eb433dd..68cbc9094 100644 --- a/tests/client/service/identitiesAuthenticatedGet.test.ts +++ b/tests/client/service/identitiesAuthenticatedGet.test.ts @@ -1,5 +1,8 @@ import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type KeyRing from '@/keys/KeyRing'; +import type Sigchain from '@/sigchain/Sigchain'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -42,6 +45,9 @@ describe('identitiesAuthenticatedGet', () => { }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, logger, }); const clientService = { diff --git a/tests/client/service/identitiesClaim.test.ts b/tests/client/service/identitiesClaim.test.ts index 5a5b70ed3..03695e71b 100644 --- a/tests/client/service/identitiesClaim.test.ts +++ b/tests/client/service/identitiesClaim.test.ts @@ -1,8 +1,9 @@ -import type { ClaimLinkIdentity } from '@/claims/types'; -import type { NodeIdEncoded } from '@/ids/types'; import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; import type NodeManager from '@/nodes/NodeManager'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { Claim } from '@/claims/types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -25,10 +26,11 @@ import * as clientUtils from '@/client/utils/utils'; import * as claimsUtils from '@/claims/utils'; import * as nodesUtils from '@/nodes/utils'; import * as validationErrors from '@/validation/errors'; +import * as keysUtils from '@/keys/utils/index'; +import Token from '@/tokens/Token'; +import { encodeProviderIdentityId } from '@/identities/utils'; import TestProvider from '../../identities/TestProvider'; import * as testUtils from '../../utils'; -import * as keysUtils from '@/keys/utils/index'; -import { CertificatePEMChain } from '@/keys/types'; import * as testsUtils from '../../utils/index'; describe('identitiesClaim', () => { @@ -45,29 +47,39 @@ describe('identitiesClaim', () => { accessToken: 'abc123', }, }; - const claimData: ClaimLinkIdentity = { - type: 'identity', - node: 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg' as NodeIdEncoded, - provider: testToken.providerId, - identity: testToken.identityId, - }; - const claimId = claimsUtils.createClaimIdGenerator( - nodesUtils.decodeNodeId(claimData.node)!, - )(); + const issNodeKeypair = keysUtils.generateKeyPair(); + const issNodeId = keysUtils.publicKeyToNodeId(issNodeKeypair.publicKey); + const claimId = claimsUtils.createClaimIdGenerator(issNodeId)(); let mockedAddClaim: jest.SpyInstance; const dummyNodeManager = { setNode: jest.fn() } as unknown as NodeManager; beforeAll(async () => { - const privateKey = keysUtils.generateKeyPair().privateKey; - const claim = await claimsUtils.createClaim({ - privateKey: privateKey, - hPrev: null, + const dummyClaim: ClaimLinkIdentity = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(issNodeId), + sub: encodeProviderIdentityId([ + testToken.providerId, + testToken.identityId, + ]), + jti: claimsUtils.encodeClaimId(claimId), + iat: 0, + nbf: 0, + exp: 0, + aud: '', seq: 0, - data: claimData, - kid: claimData.node, - }); + prevClaimId: null, + prevDigest: null, + }; + const token = Token.fromPayload(dummyClaim); + token.signWithPrivateKey(issNodeKeypair); + const signedClaim = token.toSigned(); mockedAddClaim = jest .spyOn(Sigchain.prototype, 'addClaim') - .mockResolvedValue([claimId, claim]); + .mockImplementation(async (payload, _, func) => { + const token = Token.fromPayload(payload); + // We need to call the function to resolve a promise in the code + func != null && (await func(token as unknown as Token)); + return [claimId, signedClaim]; + }); }); afterAll(async () => { mockedAddClaim.mockRestore(); @@ -104,8 +116,18 @@ describe('identitiesClaim', () => { dbPath, logger, }); + sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger, + }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ db, + gestaltGraph: { + linkNodeAndIdentity: jest.fn(), + } as unknown as GestaltGraph, + keyRing: keyRing, + sigchain: sigchain, logger, }); testProvider = new TestProvider(); @@ -119,11 +141,6 @@ describe('identitiesClaim', () => { serverPort: 0 as Port, tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), }); - sigchain = await Sigchain.createSigchain({ - db, - keyRing, - logger, - }); nodeGraph = await NodeGraph.createNodeGraph({ db, keyRing, @@ -148,10 +165,7 @@ describe('identitiesClaim', () => { identitiesClaim: identitiesClaim({ authenticate, identitiesManager, - sigchain, - keyRing, logger, - db, }), }; grpcServer = new GRPCServer({ logger }); diff --git a/tests/client/service/identitiesInfoConnectedGet.test.ts b/tests/client/service/identitiesInfoConnectedGet.test.ts index 3a760ad7a..221422420 100644 --- a/tests/client/service/identitiesInfoConnectedGet.test.ts +++ b/tests/client/service/identitiesInfoConnectedGet.test.ts @@ -1,5 +1,8 @@ import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; +import type KeyRing from 'keys/KeyRing'; +import type Sigchain from 'sigchain/Sigchain'; +import type GestaltGraph from 'gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -47,6 +50,9 @@ describe('identitiesInfoConnectedGet', () => { }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, logger, }); const clientService = { diff --git a/tests/client/service/identitiesInfoGet.test.ts b/tests/client/service/identitiesInfoGet.test.ts index ad0bb6374..330f6c8dd 100644 --- a/tests/client/service/identitiesInfoGet.test.ts +++ b/tests/client/service/identitiesInfoGet.test.ts @@ -1,5 +1,8 @@ import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; +import type KeyRing from 'keys/KeyRing'; +import type Sigchain from 'sigchain/Sigchain'; +import type GestaltGraph from 'gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -45,6 +48,9 @@ describe('identitiesInfoGet', () => { }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, logger, }); const clientService = { diff --git a/tests/client/service/identitiesProvidersList.test.ts b/tests/client/service/identitiesProvidersList.test.ts index e75ffd477..426ee7b25 100644 --- a/tests/client/service/identitiesProvidersList.test.ts +++ b/tests/client/service/identitiesProvidersList.test.ts @@ -1,5 +1,8 @@ import type { ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; +import type KeyRing from 'keys/KeyRing'; +import type Sigchain from 'sigchain/Sigchain'; +import type GestaltGraph from 'gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -54,6 +57,9 @@ describe('identitiesProvidersList', () => { }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, logger, }); const clientService = { diff --git a/tests/client/service/identitiesTokenPutDeleteGet.test.ts b/tests/client/service/identitiesTokenPutDeleteGet.test.ts index a325d9cdc..8211c0aad 100644 --- a/tests/client/service/identitiesTokenPutDeleteGet.test.ts +++ b/tests/client/service/identitiesTokenPutDeleteGet.test.ts @@ -1,5 +1,8 @@ import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type KeyRing from '@/keys/KeyRing'; +import type Sigchain from '@/sigchain/Sigchain'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -49,6 +52,9 @@ describe('identitiesTokenPutDeleteGet', () => { }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, logger, }); identitiesManager.registerProvider(new TestProvider()); @@ -116,7 +122,9 @@ describe('identitiesTokenPutDeleteGet', () => { clientUtils.encodeAuthFromPassword(password), ); expect(getPutResponse).toBeInstanceOf(identitiesPB.Token); - expect(JSON.parse(getPutResponse.getToken())).toEqual(testToken.providerToken); + expect(JSON.parse(getPutResponse.getToken())).toEqual( + testToken.providerToken, + ); // Delete token const deleteResponse = await grpcClient.identitiesTokenDelete( providerMessage, diff --git a/tests/client/service/keysCertsChainGet.test.ts b/tests/client/service/keysCertsChainGet.test.ts index 11b1c7b13..10c4fbfc0 100644 --- a/tests/client/service/keysCertsChainGet.test.ts +++ b/tests/client/service/keysCertsChainGet.test.ts @@ -1,4 +1,5 @@ import type { Host, Port } from '@/network/types'; +import type { CertificatePEM } from '../../../src/keys/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -15,7 +16,6 @@ import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; import * as clientUtils from '@/client/utils/utils'; -import { CertificatePEM } from '../../../src/keys/types'; import * as keysUtils from '@/keys/utils/index'; describe('keysCertsChainGet', () => { @@ -59,14 +59,14 @@ describe('keysCertsChainGet', () => { db = await DB.createDB({ dbPath, logger, - }) + }); taskManager = await TaskManager.createTaskManager({ db, logger }); certManager = await CertManager.createCertManager({ db, keyRing, taskManager, logger, - }) + }); const clientService = { keysCertsChainGet: keysCertsChainGet({ authenticate, diff --git a/tests/client/service/keysCertsGet.test.ts b/tests/client/service/keysCertsGet.test.ts index 3368d9ce1..c6f71a74f 100644 --- a/tests/client/service/keysCertsGet.test.ts +++ b/tests/client/service/keysCertsGet.test.ts @@ -1,4 +1,5 @@ import type { Host, Port } from '@/network/types'; +import type { CertificatePEM } from '@/keys/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -15,7 +16,6 @@ import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; import * as clientUtils from '@/client/utils/utils'; -import { CertificatePEM } from '@/keys/types'; import * as keysUtils from '@/keys/utils/index'; describe('keysCertsGet', () => { @@ -65,7 +65,7 @@ describe('keysCertsGet', () => { keyRing, taskManager, logger, - }) + }); const clientService = { keysCertsGet: keysCertsGet({ authenticate, diff --git a/tests/client/service/keysEncryptDecrypt.test.ts b/tests/client/service/keysEncryptDecrypt.test.ts index 1cb01b67b..9b91456b3 100644 --- a/tests/client/service/keysEncryptDecrypt.test.ts +++ b/tests/client/service/keysEncryptDecrypt.test.ts @@ -76,6 +76,8 @@ describe('keysEncryptDecrypt', () => { const plainText = Buffer.from('abc'); const request = new keysPB.Crypto(); request.setData(plainText.toString('binary')); + const publicKeyJWK = keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey); + request.setPublicKeyJwk(JSON.stringify(publicKeyJWK)); const encrypted = await grpcClient.keysEncrypt( request, clientUtils.encodeAuthFromPassword(password), diff --git a/tests/client/service/keysKeyPair.test.ts b/tests/client/service/keysKeyPair.test.ts index f26f237d5..fcad3f87f 100644 --- a/tests/client/service/keysKeyPair.test.ts +++ b/tests/client/service/keysKeyPair.test.ts @@ -7,12 +7,12 @@ import { Metadata } from '@grpc/grpc-js'; import KeyRing from '@/keys/KeyRing'; import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientClient from '@/client/GRPCClientClient'; -import keysKeyPair from '../../../src/client/service/keysKeyPair'; import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; import * as sessionsPB from '@/proto/js/polykey/v1/sessions/sessions_pb'; import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; +import keysKeyPair from '../../../src/client/service/keysKeyPair'; describe('keysKeyPair', () => { const logger = new Logger('keysKeyPair test', LogLevel.WARN, [ @@ -85,7 +85,7 @@ describe('keysKeyPair', () => { alg: expect.any(String), crv: expect.any(String), ext: expect.any(Boolean), - key_ops: expect.any(Array), + key_ops: expect.any(Array), kty: expect.any(String), x: expect.any(String), }); diff --git a/tests/client/service/keysKeyPairRenew.test.ts b/tests/client/service/keysKeyPairRenew.test.ts index 34cd7fdd2..3b7cfebe2 100644 --- a/tests/client/service/keysKeyPairRenew.test.ts +++ b/tests/client/service/keysKeyPairRenew.test.ts @@ -18,7 +18,6 @@ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; import { NodeManager } from '@/nodes'; -import { CertificatePEMChain } from '../../../src/keys/types'; describe('keysKeyPairRenew', () => { const logger = new Logger('keysKeyPairRenew test', LogLevel.WARN, [ diff --git a/tests/client/service/keysKeyPairReset.test.ts b/tests/client/service/keysKeyPairReset.test.ts index 7896e0b27..c86f967b0 100644 --- a/tests/client/service/keysKeyPairReset.test.ts +++ b/tests/client/service/keysKeyPairReset.test.ts @@ -18,7 +18,6 @@ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; import { NodeManager } from '@/nodes'; -import { CertificatePEMChain } from '../../../src/keys/types'; describe('keysKeyPairReset', () => { const logger = new Logger('keysKeyPairReset test', LogLevel.WARN, [ diff --git a/tests/client/service/keysPublicKey.test.ts b/tests/client/service/keysPublicKey.test.ts index f8633bb3e..266b0affd 100644 --- a/tests/client/service/keysPublicKey.test.ts +++ b/tests/client/service/keysPublicKey.test.ts @@ -7,12 +7,12 @@ import { Metadata } from '@grpc/grpc-js'; import KeyRing from '@/keys/KeyRing'; import GRPCServer from '@/grpc/GRPCServer'; import GRPCClientClient from '@/client/GRPCClientClient'; -import keysPublicKey from '../../../src/client/service/keysPublicKey'; import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils'; +import keysPublicKey from '../../../src/client/service/keysPublicKey'; describe('keysPublicKey', () => { const logger = new Logger('keysPublicKey test', LogLevel.WARN, [ @@ -78,7 +78,7 @@ describe('keysPublicKey', () => { alg: expect.any(String), crv: expect.any(String), ext: expect.any(Boolean), - key_ops: expect.any(Array), + key_ops: expect.any(Array), kty: expect.any(String), x: expect.any(String), }); diff --git a/tests/client/service/keysSignVerify.test.ts b/tests/client/service/keysSignVerify.test.ts index 25ee7d33e..1da726a99 100644 --- a/tests/client/service/keysSignVerify.test.ts +++ b/tests/client/service/keysSignVerify.test.ts @@ -14,6 +14,7 @@ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils/index'; +import { publicKeyToJWK } from '@/keys/utils/index'; describe('keysSignVerify', () => { const logger = new Logger('keysSignVerify test', LogLevel.WARN, [ @@ -82,6 +83,8 @@ describe('keysSignVerify', () => { clientUtils.encodeAuthFromPassword(password), ); expect(signed).toBeInstanceOf(keysPB.Crypto); + const publicKeyJWK = publicKeyToJWK(keyRing.keyPair.publicKey); + signed.setPublicKeyJwk(JSON.stringify(publicKeyJWK)); const response = await grpcClient.keysVerify( signed, clientUtils.encodeAuthFromPassword(password), diff --git a/tests/client/service/nodesAdd.test.ts b/tests/client/service/nodesAdd.test.ts index 094086cfd..996636c82 100644 --- a/tests/client/service/nodesAdd.test.ts +++ b/tests/client/service/nodesAdd.test.ts @@ -1,4 +1,5 @@ import type { Host, Port } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -21,8 +22,8 @@ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as nodesUtils from '@/nodes/utils'; import * as clientUtils from '@/client/utils/utils'; import * as validationErrors from '@/validation/errors'; -import * as testsUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testsUtils from '../../utils'; describe('nodesAdd', () => { const logger = new Logger('nodesAdd test', LogLevel.WARN, [ @@ -102,6 +103,7 @@ describe('nodesAdd', () => { nodeGraph, sigchain, taskManager, + gestaltGraph: {} as GestaltGraph, logger, }); await nodeManager.start(); diff --git a/tests/client/service/nodesClaim.test.ts b/tests/client/service/nodesClaim.test.ts index f6a8c77eb..ec08afbc0 100644 --- a/tests/client/service/nodesClaim.test.ts +++ b/tests/client/service/nodesClaim.test.ts @@ -1,6 +1,7 @@ import type { Notification } from '@/notifications/types'; import type { NodeIdEncoded } from '@/ids/types'; import type { Host, Port } from '@/network/types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -24,9 +25,8 @@ import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; import * as validationErrors from '@/validation/errors'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; -import { CertificatePEMChain } from '@/keys/types'; +import * as testUtils from '../../utils'; import * as testsUtils from '../../utils/index'; describe('nodesClaim', () => { @@ -37,11 +37,12 @@ describe('nodesClaim', () => { const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; const dummyNotification: Notification = { + typ: 'notification', data: { type: 'GestaltInvite', }, - senderId: - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg' as NodeIdEncoded, + iss: 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg' as NodeIdEncoded, + sub: 'test' as NodeIdEncoded, isRead: false, }; let mockedFindGestaltInvite: jest.SpyInstance; @@ -140,6 +141,7 @@ describe('nodesClaim', () => { nodeGraph, sigchain, taskManager, + gestaltGraph: {} as GestaltGraph, logger, }); await nodeManager.start(); @@ -158,7 +160,6 @@ describe('nodesClaim', () => { nodesClaim: nodesClaim({ authenticate, nodeManager, - notificationsManager, logger, db, }), @@ -196,30 +197,6 @@ describe('nodesClaim', () => { recursive: true, }); }); - test('sends a gestalt invite (none existing)', async () => { - const request = new nodesPB.Claim(); - request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); - request.setForceInvite(false); - const response = await grpcClient.nodesClaim( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.StatusMessage); - // Does not claim (sends gestalt invite) - expect(response.getSuccess()).toBeFalsy(); - }); - test('sends a gestalt invite (force invite)', async () => { - const request = new nodesPB.Claim(); - request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); - request.setForceInvite(true); - const response = await grpcClient.nodesClaim( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.StatusMessage); - // Does not claim (sends gestalt invite) - expect(response.getSuccess()).toBeFalsy(); - }); test('claims a node', async () => { const request = new nodesPB.Claim(); request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); diff --git a/tests/client/service/nodesFind.test.ts b/tests/client/service/nodesFind.test.ts index 86ddd718c..bd141d7e3 100644 --- a/tests/client/service/nodesFind.test.ts +++ b/tests/client/service/nodesFind.test.ts @@ -19,9 +19,8 @@ import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_ import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as clientUtils from '@/client/utils/utils'; import * as validationErrors from '@/validation/errors'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; -import { CertificatePEMChain } from '@/keys/types'; +import * as testUtils from '../../utils'; import * as testsUtils from '../../utils/index'; describe('nodesFind', () => { diff --git a/tests/client/service/nodesPing.test.ts b/tests/client/service/nodesPing.test.ts index 493ba314a..9accd6e77 100644 --- a/tests/client/service/nodesPing.test.ts +++ b/tests/client/service/nodesPing.test.ts @@ -1,4 +1,5 @@ import type { Host, Port } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -20,9 +21,8 @@ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as clientUtils from '@/client/utils/utils'; import * as validationErrors from '@/validation/errors'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; -import { CertificatePEMChain } from '@/keys/types'; +import * as testUtils from '../../utils'; import * as testsUtils from '../../utils/index'; describe('nodesPing', () => { @@ -113,6 +113,7 @@ describe('nodesPing', () => { nodeGraph, sigchain, taskManager, + gestaltGraph: {} as GestaltGraph, logger, }); await nodeConnectionManager.start({ nodeManager }); diff --git a/tests/client/service/notificationsClear.test.ts b/tests/client/service/notificationsClear.test.ts index 63611750e..34e443891 100644 --- a/tests/client/service/notificationsClear.test.ts +++ b/tests/client/service/notificationsClear.test.ts @@ -1,4 +1,5 @@ import type { Host, Port } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -21,7 +22,6 @@ import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; import * as keysUtils from '@/keys/utils/index'; -import { CertificatePEMChain } from '@/keys/types'; import * as testsUtils from '../../utils/index'; describe('notificationsClear', () => { @@ -117,6 +117,7 @@ describe('notificationsClear', () => { nodeGraph, sigchain, taskManager, + gestaltGraph: {} as GestaltGraph, logger, }); await nodeManager.start(); diff --git a/tests/client/service/notificationsRead.test.ts b/tests/client/service/notificationsRead.test.ts index ea7b6846c..18e60f5e9 100644 --- a/tests/client/service/notificationsRead.test.ts +++ b/tests/client/service/notificationsRead.test.ts @@ -1,5 +1,6 @@ import type { Host, Port } from '@/network/types'; import type { VaultIdEncoded, VaultName } from '@/vaults/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -22,9 +23,8 @@ import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_ import * as notificationsPB from '@/proto/js/polykey/v1/notifications/notifications_pb'; import * as nodesUtils from '@/nodes/utils'; import * as clientUtils from '@/client/utils'; -import * as testNodesUtils from '../../nodes/utils'; import * as keysUtils from '@/keys/utils/index'; -import { CertificatePEMChain } from '@/keys/types'; +import * as testNodesUtils from '../../nodes/utils'; import * as testsUtils from '../../utils/index'; describe('notificationsRead', () => { @@ -33,6 +33,7 @@ describe('notificationsRead', () => { ]); const nodeIdSender = testNodesUtils.generateRandomNodeId(); const nodeIdSenderEncoded = nodesUtils.encodeNodeId(nodeIdSender); + const nodeIdReceiverEncoded = 'test'; const password = 'helloworld'; const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; @@ -42,61 +43,74 @@ describe('notificationsRead', () => { .spyOn(NotificationsManager.prototype, 'readNotifications') .mockResolvedValueOnce([ { + typ: 'notification', data: { type: 'General', message: 'test', }, - senderId: nodeIdSenderEncoded, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, isRead: true, }, ]) .mockResolvedValueOnce([ { + typ: 'notification', data: { type: 'General', message: 'test1', }, - senderId: nodeIdSenderEncoded, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, isRead: true, }, { + typ: 'notification', data: { type: 'General', message: 'test2', }, - senderId: nodeIdSenderEncoded, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, isRead: true, }, ]) .mockResolvedValueOnce([ { + typ: 'notification', data: { type: 'General', message: 'test2', }, - senderId: nodeIdSenderEncoded, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, isRead: true, }, { + typ: 'notification', data: { type: 'General', message: 'test1', }, - senderId: nodeIdSenderEncoded, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, isRead: true, }, ]) .mockResolvedValueOnce([ { + typ: 'notification', data: { type: 'GestaltInvite', }, - senderId: nodeIdSenderEncoded, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, isRead: true, }, ]) .mockResolvedValueOnce([ { + typ: 'notification', data: { type: 'VaultShare', vaultId: 'vault' as VaultIdEncoded, @@ -106,7 +120,8 @@ describe('notificationsRead', () => { pull: null, }, }, - senderId: nodeIdSenderEncoded, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, isRead: true, }, ]) @@ -190,6 +205,7 @@ describe('notificationsRead', () => { keyRing, nodeConnectionManager, nodeGraph, + gestaltGraph: {} as GestaltGraph, sigchain, taskManager, logger, @@ -259,10 +275,12 @@ describe('notificationsRead', () => { expect(response).toBeInstanceOf(notificationsPB.List); const output = response.getNotificationList(); expect(output).toHaveLength(1); - expect(output[0].hasGeneral()).toBeTruthy(); - expect(output[0].getGeneral()!.getMessage()).toBe('test'); - expect(output[0].getSenderId()).toBe(nodeIdSenderEncoded); - expect(output[0].getIsRead()).toBeTruthy(); + const notification = JSON.parse(output[0].getContent()); + expect(notification.data.type).toBe('General'); + expect(notification.data.message).toBe('test'); + expect(notification.iss).toBe(nodeIdSenderEncoded); + expect(notification.sub).toBe(nodeIdReceiverEncoded); + expect(notification.isRead).toBeTruthy(); // Check request was parsed correctly expect(mockedReadNotifications.mock.calls[0][0].unread).toBeFalsy(); expect(mockedReadNotifications.mock.calls[0][0].number).toBe(1); @@ -280,14 +298,18 @@ describe('notificationsRead', () => { expect(response).toBeInstanceOf(notificationsPB.List); const output = response.getNotificationList(); expect(output).toHaveLength(2); - expect(output[0].hasGeneral()).toBeTruthy(); - expect(output[0].getGeneral()!.getMessage()).toBe('test1'); - expect(output[0].getSenderId()).toBe(nodeIdSenderEncoded); - expect(output[0].getIsRead()).toBeTruthy(); - expect(output[1].hasGeneral()).toBeTruthy(); - expect(output[1].getGeneral()!.getMessage()).toBe('test2'); - expect(output[1].getSenderId()).toBe(nodeIdSenderEncoded); - expect(output[1].getIsRead()).toBeTruthy(); + const notification1 = JSON.parse(output[0].getContent()); + const notification2 = JSON.parse(output[1].getContent()); + expect(notification1.data.type).toBe('General'); + expect(notification1.data.message).toBe('test1'); + expect(notification1.iss).toBe(nodeIdSenderEncoded); + expect(notification1.sub).toBe(nodeIdReceiverEncoded); + expect(notification1.isRead).toBeTruthy(); + expect(notification2.data.type).toBe('General'); + expect(notification2.data.message).toBe('test2'); + expect(notification2.iss).toBe(nodeIdSenderEncoded); + expect(notification2.sub).toBe(nodeIdReceiverEncoded); + expect(notification2.isRead).toBeTruthy(); // Check request was parsed correctly expect(mockedReadNotifications.mock.calls[1][0].unread).toBeTruthy(); expect(mockedReadNotifications.mock.calls[1][0].number).toBe('all'); @@ -305,14 +327,18 @@ describe('notificationsRead', () => { expect(response).toBeInstanceOf(notificationsPB.List); const output = response.getNotificationList(); expect(output).toHaveLength(2); - expect(output[0].hasGeneral()).toBeTruthy(); - expect(output[0].getGeneral()!.getMessage()).toBe('test2'); - expect(output[0].getSenderId()).toBe(nodeIdSenderEncoded); - expect(output[0].getIsRead()).toBeTruthy(); - expect(output[1].hasGeneral()).toBeTruthy(); - expect(output[1].getGeneral()!.getMessage()).toBe('test1'); - expect(output[1].getSenderId()).toBe(nodeIdSenderEncoded); - expect(output[1].getIsRead()).toBeTruthy(); + const notification1 = JSON.parse(output[0].getContent()); + const notification2 = JSON.parse(output[1].getContent()); + expect(notification1.data.type).toBe('General'); + expect(notification1.data.message).toBe('test2'); + expect(notification1.iss).toBe(nodeIdSenderEncoded); + expect(notification1.sub).toBe(nodeIdReceiverEncoded); + expect(notification1.isRead).toBeTruthy(); + expect(notification2.data.type).toBe('General'); + expect(notification2.data.message).toBe('test1'); + expect(notification2.iss).toBe(nodeIdSenderEncoded); + expect(notification2.sub).toBe(nodeIdReceiverEncoded); + expect(notification2.isRead).toBeTruthy(); // Check request was parsed correctly expect(mockedReadNotifications.mock.calls[2][0].unread).toBeFalsy(); expect(mockedReadNotifications.mock.calls[2][0].number).toBe('all'); @@ -330,10 +356,11 @@ describe('notificationsRead', () => { expect(response).toBeInstanceOf(notificationsPB.List); const output = response.getNotificationList(); expect(output).toHaveLength(1); - expect(output[0].hasGestaltInvite()).toBeTruthy(); - expect(output[0].getGestaltInvite()).toBe('GestaltInvite'); - expect(output[0].getSenderId()).toBe(nodeIdSenderEncoded); - expect(output[0].getIsRead()).toBeTruthy(); + const notification = JSON.parse(output[0].getContent()); + expect(notification.data.type).toBe('GestaltInvite'); + expect(notification.iss).toBe(nodeIdSenderEncoded); + expect(notification.sub).toBe(nodeIdReceiverEncoded); + expect(notification.isRead).toBeTruthy(); // Check request was parsed correctly expect(mockedReadNotifications.mock.calls[3][0].unread).toBeFalsy(); expect(mockedReadNotifications.mock.calls[3][0].number).toBe('all'); @@ -351,13 +378,17 @@ describe('notificationsRead', () => { expect(response).toBeInstanceOf(notificationsPB.List); const output = response.getNotificationList(); expect(output).toHaveLength(1); - expect(output[0].hasVaultShare()).toBeTruthy(); - expect(output[0].getVaultShare()!.getVaultId()).toBe('vault'); - expect(output[0].getVaultShare()!.getVaultName()).toBe('vault'); - expect(output[0].getVaultShare()!.getActionsList()).toContain('clone'); - expect(output[0].getVaultShare()!.getActionsList()).toContain('pull'); - expect(output[0].getSenderId()).toBe(nodeIdSenderEncoded); - expect(output[0].getIsRead()).toBeTruthy(); + const notification = JSON.parse(output[0].getContent()); + expect(notification.data.type).toBe('VaultShare'); + expect(notification.data.vaultId).toBe('vault'); + expect(notification.data.vaultName).toBe('vault'); + expect(notification.data.actions).toStrictEqual({ + clone: null, + pull: null, + }); + expect(notification.iss).toBe(nodeIdSenderEncoded); + expect(notification.sub).toBe(nodeIdReceiverEncoded); + expect(notification.isRead).toBeTruthy(); // Check request was parsed correctly expect(mockedReadNotifications.mock.calls[4][0].unread).toBeFalsy(); expect(mockedReadNotifications.mock.calls[4][0].number).toBe('all'); diff --git a/tests/client/service/notificationsSend.test.ts b/tests/client/service/notificationsSend.test.ts index 0f3971ca8..e66d9ff6e 100644 --- a/tests/client/service/notificationsSend.test.ts +++ b/tests/client/service/notificationsSend.test.ts @@ -1,5 +1,6 @@ import type { Host, Port } from '@/network/types'; import type { SignedNotification } from '@/notifications/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -25,7 +26,6 @@ import * as nodesUtils from '@/nodes/utils'; import * as notificationsUtils from '@/notifications/utils'; import * as clientUtils from '@/client/utils'; import * as keysUtils from '@/keys/utils/index'; -import { CertificatePEMChain } from '@/keys/types'; import * as testsUtils from '../../utils/index'; describe('notificationsSend', () => { @@ -39,7 +39,7 @@ describe('notificationsSend', () => { let mockedSendNotification: jest.SpyInstance; beforeAll(async () => { mockedSignNotification = jest - .spyOn(notificationsUtils, 'signNotification') + .spyOn(notificationsUtils, 'generateNotification') .mockImplementation(async () => { return 'signedNotification' as SignedNotification; }); @@ -125,6 +125,7 @@ describe('notificationsSend', () => { keyRing, nodeConnectionManager, nodeGraph, + gestaltGraph: {} as GestaltGraph, sigchain, taskManager, logger, @@ -202,11 +203,13 @@ describe('notificationsSend', () => { ).toEqual(receiverNodeIdEncoded); // Check notification content expect(mockedSignNotification.mock.calls[0][0]).toEqual({ + typ: 'notification', data: { type: 'General', message: 'test', }, - senderId: nodesUtils.encodeNodeId(keyRing.getNodeId()), + iss: nodesUtils.encodeNodeId(keyRing.getNodeId()), + sub: receiverNodeIdEncoded, isRead: false, }); }); diff --git a/tests/client/service/vaultsCreateDeleteList.test.ts b/tests/client/service/vaultsCreateDeleteList.test.ts index ff8113448..4b456f163 100644 --- a/tests/client/service/vaultsCreateDeleteList.test.ts +++ b/tests/client/service/vaultsCreateDeleteList.test.ts @@ -20,8 +20,8 @@ import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; import * as clientUtils from '@/client/utils/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsCreateDeleteList', () => { const logger = new Logger('vaultsCreateDeleteList test', LogLevel.WARN, [ diff --git a/tests/client/service/vaultsLog.test.ts b/tests/client/service/vaultsLog.test.ts index 87936a4b6..bd5254179 100644 --- a/tests/client/service/vaultsLog.test.ts +++ b/tests/client/service/vaultsLog.test.ts @@ -18,8 +18,8 @@ import vaultsLog from '@/client/service/vaultsLog'; import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; import * as clientUtils from '@/client/utils/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsLog', () => { const logger = new Logger('vaultsLog test', LogLevel.WARN, [ diff --git a/tests/client/service/vaultsPermissionSetUnsetGet.test.ts b/tests/client/service/vaultsPermissionSetUnsetGet.test.ts index 1d8b17063..606795bfe 100644 --- a/tests/client/service/vaultsPermissionSetUnsetGet.test.ts +++ b/tests/client/service/vaultsPermissionSetUnsetGet.test.ts @@ -23,8 +23,8 @@ import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsPermissionSetUnsetGet', () => { const logger = new Logger('vaultsPermissionSetUnsetGet test', LogLevel.WARN, [ @@ -80,8 +80,7 @@ describe('vaultsPermissionSetUnsetGet', () => { logger, }); await gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(nodeId), - chain: {}, + nodeId: nodeId, }); notificationsManager = await NotificationsManager.createNotificationsManager({ diff --git a/tests/client/service/vaultsRename.test.ts b/tests/client/service/vaultsRename.test.ts index aa307b7e6..469946d8f 100644 --- a/tests/client/service/vaultsRename.test.ts +++ b/tests/client/service/vaultsRename.test.ts @@ -18,8 +18,8 @@ import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_ import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; import * as clientUtils from '@/client/utils/utils'; import * as vaultsUtils from '@/vaults/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsRename', () => { const logger = new Logger('vaultsRename test', LogLevel.WARN, [ diff --git a/tests/client/service/vaultsSecretsEdit.test.ts b/tests/client/service/vaultsSecretsEdit.test.ts index 54f060784..54aa6f801 100644 --- a/tests/client/service/vaultsSecretsEdit.test.ts +++ b/tests/client/service/vaultsSecretsEdit.test.ts @@ -20,8 +20,8 @@ import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; import * as clientUtils from '@/client/utils/utils'; import * as vaultsUtils from '@/vaults/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsSecretsEdit', () => { const logger = new Logger('vaultsSecretsEdit test', LogLevel.WARN, [ diff --git a/tests/client/service/vaultsSecretsMkdir.test.ts b/tests/client/service/vaultsSecretsMkdir.test.ts index 21df014ec..19bad9e50 100644 --- a/tests/client/service/vaultsSecretsMkdir.test.ts +++ b/tests/client/service/vaultsSecretsMkdir.test.ts @@ -19,8 +19,8 @@ import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; import * as vaultsUtils from '@/vaults/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsSecretsMkdir', () => { const logger = new Logger('vaultsSecretsMkdir test', LogLevel.WARN, [ diff --git a/tests/client/service/vaultsSecretsNewDeleteGet.test.ts b/tests/client/service/vaultsSecretsNewDeleteGet.test.ts index c797362fa..a35c35173 100644 --- a/tests/client/service/vaultsSecretsNewDeleteGet.test.ts +++ b/tests/client/service/vaultsSecretsNewDeleteGet.test.ts @@ -23,8 +23,8 @@ import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; import * as clientUtils from '@/client/utils/utils'; import * as vaultsUtils from '@/vaults/utils'; import * as vaultsErrors from '@/vaults/errors'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsSecretsNewDeleteGet', () => { const logger = new Logger('vaultsSecretsNewDeleteGet test', LogLevel.WARN, [ diff --git a/tests/client/service/vaultsSecretsNewDirList.test.ts b/tests/client/service/vaultsSecretsNewDirList.test.ts index 7de71eb65..35714929b 100644 --- a/tests/client/service/vaultsSecretsNewDirList.test.ts +++ b/tests/client/service/vaultsSecretsNewDirList.test.ts @@ -21,8 +21,8 @@ import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; import * as clientUtils from '@/client/utils/utils'; import * as vaultsUtils from '@/vaults/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsSecretsNewDirList', () => { const logger = new Logger('vaultsSecretsNewDirList test', LogLevel.WARN, [ diff --git a/tests/client/service/vaultsSecretsRename.test.ts b/tests/client/service/vaultsSecretsRename.test.ts index e63dfad18..dfe268a79 100644 --- a/tests/client/service/vaultsSecretsRename.test.ts +++ b/tests/client/service/vaultsSecretsRename.test.ts @@ -20,8 +20,8 @@ import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as clientUtils from '@/client/utils/utils'; import * as vaultsUtils from '@/vaults/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsSecretsRename', () => { const logger = new Logger('vaultsSecretsRename test', LogLevel.WARN, [ diff --git a/tests/client/service/vaultsSecretsStat.test.ts b/tests/client/service/vaultsSecretsStat.test.ts index 406903efe..9208ed36d 100644 --- a/tests/client/service/vaultsSecretsStat.test.ts +++ b/tests/client/service/vaultsSecretsStat.test.ts @@ -20,8 +20,8 @@ import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; import * as clientUtils from '@/client/utils/utils'; import * as vaultsUtils from '@/vaults/utils'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsSecretsStat', () => { const logger = new Logger('vaultsSecretsStat test', LogLevel.WARN, [ diff --git a/tests/client/service/vaultsVersion.test.ts b/tests/client/service/vaultsVersion.test.ts index d7fcd5aa5..93e49e55c 100644 --- a/tests/client/service/vaultsVersion.test.ts +++ b/tests/client/service/vaultsVersion.test.ts @@ -20,8 +20,8 @@ import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; import * as clientUtils from '@/client/utils/utils'; import * as vaultsUtils from '@/vaults/utils'; import * as vaultsErrors from '@/vaults/errors'; -import * as testUtils from '../../utils'; import * as keysUtils from '@/keys/utils/index'; +import * as testUtils from '../../utils'; describe('vaultsVersion', () => { const logger = new Logger('vaultsVersion test', LogLevel.WARN, [ diff --git a/tests/client/utils.ts b/tests/client/utils.ts index 79155ae48..d55dc4664 100644 --- a/tests/client/utils.ts +++ b/tests/client/utils.ts @@ -43,7 +43,7 @@ async function openTestClientServer({ grpcServerAgent: pkAgent.grpcServerAgent, fs: pkAgent.fs, db: pkAgent.db, - logger: pkAgent.logger + logger: pkAgent.logger, }); const callCredentials = _secure diff --git a/tests/discovery/Discovery.test.ts b/tests/discovery/Discovery.test.ts index 6edff4f16..e4a4d76ff 100644 --- a/tests/discovery/Discovery.test.ts +++ b/tests/discovery/Discovery.test.ts @@ -1,13 +1,16 @@ -import type { ClaimLinkIdentity } from '@/claims/types'; import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; import type { Key } from '@/keys/types'; +import type { SignedClaim } from '../../src/claims/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads'; +import type { NodeId } from '../../src/ids/index'; import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { PromiseCancellable } from '@matrixai/async-cancellable'; +import { AsyncIterableX as AsyncIterable } from 'ix/asynciterable'; import TaskManager from '@/tasks/TaskManager'; import PolykeyAgent from '@/PolykeyAgent'; import Discovery from '@/discovery/Discovery'; @@ -22,20 +25,21 @@ import Sigchain from '@/sigchain/Sigchain'; import Proxy from '@/network/Proxy'; import * as utils from '@/utils'; import * as nodesUtils from '@/nodes/utils'; -import * as claimsUtils from '@/claims/utils'; import * as discoveryErrors from '@/discovery/errors'; import * as keysUtils from '@/keys/utils'; import * as grpcUtils from '@/grpc/utils/index'; +import * as gestaltsUtils from '@/gestalts/utils'; import * as testNodesUtils from '../nodes/utils'; import TestProvider from '../identities/TestProvider'; import * as testsUtils from '../utils'; +import { encodeProviderIdentityId } from '../../src/ids/index'; +import 'ix/add/asynciterable-operators/toarray'; describe('Discovery', () => { const password = 'password'; const logger = new Logger(`${Discovery.name} Test`, LogLevel.WARN, [ new StreamHandler(), ]); - const testProvider = new TestProvider(); const testToken = { providerId: 'test-provider' as ProviderId, identityId: 'test_user' as IdentityId, @@ -62,6 +66,9 @@ describe('Discovery', () => { let nodeA: PolykeyAgent; let nodeB: PolykeyAgent; let identityId: IdentityId; + let nodeIdA: NodeId; + let nodeIdB: NodeId; + let testProvider: TestProvider; const mockedRefreshBucket = jest.spyOn( NodeManager.prototype, @@ -69,6 +76,7 @@ describe('Discovery', () => { ); beforeEach(async () => { + testProvider = new TestProvider(); mockedRefreshBucket.mockImplementation( () => new PromiseCancellable((resolve) => resolve()), ); @@ -107,6 +115,7 @@ describe('Discovery', () => { }, }, }, + fresh: true, }); acl = await ACL.createACL({ db, @@ -116,10 +125,12 @@ describe('Discovery', () => { db, acl, logger: logger.getChild('gestaltGraph'), + fresh: true, }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ keyRing, db, + gestaltGraph, sigchain, logger: logger.getChild('identities'), }); @@ -169,6 +180,7 @@ describe('Discovery', () => { keyRing, nodeConnectionManager, nodeGraph, + gestaltGraph, sigchain, taskManager, logger, @@ -208,11 +220,14 @@ describe('Discovery', () => { strictMemoryLock: false, }, }); + nodeIdA = nodeA.keyRing.getNodeId(); + nodeIdB = nodeB.keyRing.getNodeId(); await testNodesUtils.nodesConnect(nodeA, nodeB); await nodeGraph.setNode(nodeA.keyRing.getNodeId(), { host: nodeA.proxy.getProxyHost(), port: nodeA.proxy.getProxyPort(), }); + await nodeB.acl.setNodeAction(nodeA.keyRing.getNodeId(), 'claim'); await nodeA.nodeManager.claimNode(nodeB.keyRing.getNodeId()); nodeA.identitiesManager.registerProvider(testProvider); identityId = 'other-gestalt' as IdentityId; @@ -220,15 +235,10 @@ describe('Discovery', () => { accessToken: 'def456', }); testProvider.users[identityId] = {}; - const identityClaim: ClaimLinkIdentity = { - type: 'identity', - node: nodesUtils.encodeNodeId(nodeB.keyRing.getNodeId()), - provider: testProvider.id, - identity: identityId, - }; - const [, claimEncoded] = await nodeB.sigchain.addClaim(identityClaim); - const claim = claimsUtils.decodeClaim(claimEncoded); - await testProvider.publishClaim(identityId, claim); + await nodeA.identitiesManager.handleClaimIdentity( + testProvider.id, + identityId, + ); }); afterEach(async () => { await taskManager.stopProcessing(); @@ -259,7 +269,6 @@ describe('Discovery', () => { gestaltGraph, identitiesManager, nodeManager, - sigchain, taskManager, logger, }); @@ -283,9 +292,9 @@ describe('Discovery', () => { gestaltGraph, identitiesManager, nodeManager, - sigchain, taskManager, logger, + fresh: true, }); await taskManager.startProcessing(); await discovery.queueDiscoveryByNode(nodeA.keyRing.getNodeId()); @@ -293,19 +302,22 @@ describe('Discovery', () => { do { existingTasks = await discovery.waitForDiscoveryTasks(); } while (existingTasks > 0); - const gestalt = await gestaltGraph.getGestalts(); - const gestaltMatrix = gestalt[0].matrix; - const gestaltNodes = gestalt[0].nodes; - const gestaltIdentities = gestalt[0].identities; + const gestalts = await AsyncIterable.as( + gestaltGraph.getGestalts(), + ).toArray(); + const gestalt = gestalts[0]; + const gestaltMatrix = gestalt.matrix; + const gestaltNodes = gestalt.nodes; + const gestaltIdentities = gestalt.identities; expect(Object.keys(gestaltMatrix)).toHaveLength(3); expect(Object.keys(gestaltNodes)).toHaveLength(2); expect(Object.keys(gestaltIdentities)).toHaveLength(1); - const gestaltString = JSON.stringify(gestalt[0]); + const gestaltString = JSON.stringify(gestalt); expect(gestaltString).toContain( - nodesUtils.encodeNodeId(nodeA.keyRing.getNodeId()), + gestaltsUtils.encodeGestaltId(['node', nodeIdA]), ); expect(gestaltString).toContain( - nodesUtils.encodeNodeId(nodeB.keyRing.getNodeId()), + gestaltsUtils.encodeGestaltId(['node', nodeIdB]), ); expect(gestaltString).toContain(identityId); await taskManager.stopProcessing(); @@ -319,7 +331,6 @@ describe('Discovery', () => { gestaltGraph, identitiesManager, nodeManager, - sigchain, taskManager, logger, }); @@ -329,7 +340,10 @@ describe('Discovery', () => { do { existingTasks = await discovery.waitForDiscoveryTasks(); } while (existingTasks > 0); - const gestalt = (await gestaltGraph.getGestalts())[0]; + const gestalts = await AsyncIterable.as( + gestaltGraph.getGestalts(), + ).toArray(); + const gestalt = gestalts[0]; const gestaltMatrix = gestalt.matrix; const gestaltNodes = gestalt.nodes; const gestaltIdentities = gestalt.identities; @@ -355,7 +369,6 @@ describe('Discovery', () => { gestaltGraph, identitiesManager, nodeManager, - sigchain, taskManager, logger, }); @@ -365,7 +378,10 @@ describe('Discovery', () => { do { existingTasks = await discovery.waitForDiscoveryTasks(); } while (existingTasks > 0); - const gestalt1 = (await gestaltGraph.getGestalts())[0]; + const gestalts1 = await AsyncIterable.as( + gestaltGraph.getGestalts(), + ).toArray(); + const gestalt1 = gestalts1[0]; const gestaltMatrix1 = gestalt1.matrix; const gestaltNodes1 = gestalt1.nodes; const gestaltIdentities1 = gestalt1.identities; @@ -386,22 +402,26 @@ describe('Discovery', () => { accessToken: 'ghi789', }); testProvider.users[identityId2] = {}; - const identityClaim: ClaimLinkIdentity = { - type: 'identity', - node: nodesUtils.encodeNodeId(nodeA.keyRing.getNodeId()), - provider: testProvider.id, - identity: identityId2, + const identityClaim = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(nodeA.keyRing.getNodeId()), + sub: encodeProviderIdentityId([testProvider.id, identityId2]), }; - const [, claimEncoded] = await nodeA.sigchain.addClaim(identityClaim); - const claim = claimsUtils.decodeClaim(claimEncoded); - await testProvider.publishClaim(identityId2, claim); + const [, signedClaim] = await nodeA.sigchain.addClaim(identityClaim); + await testProvider.publishClaim( + identityId2, + signedClaim as SignedClaim, + ); // Note that eventually we would like to add in a system of revisiting // already discovered vertices, however for now we must do this manually. await discovery.queueDiscoveryByNode(nodeA.keyRing.getNodeId()); do { existingTasks = await discovery.waitForDiscoveryTasks(); } while (existingTasks > 0); - const gestalt2 = (await gestaltGraph.getGestalts())[0]; + const gestalts2 = await AsyncIterable.as( + gestaltGraph.getGestalts(), + ).toArray(); + const gestalt2 = gestalts2[0]; const gestaltMatrix2 = gestalt2.matrix; const gestaltNodes2 = gestalt2.nodes; const gestaltIdentities2 = gestalt2.identities; @@ -431,7 +451,6 @@ describe('Discovery', () => { gestaltGraph, identitiesManager, nodeManager, - sigchain, taskManager, logger, }); @@ -443,7 +462,10 @@ describe('Discovery', () => { do { existingTasks = await discovery.waitForDiscoveryTasks(); } while (existingTasks > 0); - const gestalt = (await gestaltGraph.getGestalts())[0]; + const gestalts = await AsyncIterable.as( + gestaltGraph.getGestalts(), + ).toArray(); + const gestalt = gestalts[0]; const gestaltMatrix = gestalt.matrix; const gestaltNodes = gestalt.nodes; const gestaltIdentities = gestalt.identities; diff --git a/tests/gestalts/GestaltGraph.test.ts b/tests/gestalts/GestaltGraph.test.ts index a0fe75538..8d664a8c9 100644 --- a/tests/gestalts/GestaltGraph.test.ts +++ b/tests/gestalts/GestaltGraph.test.ts @@ -1,73 +1,139 @@ import type { NodeId } from '@/nodes/types'; -import type { - IdentityId, - ProviderId, ProviderIdentityId, -} from '@/identities/types'; -import type { Claim } from '@/claims/types'; +import type { ProviderIdentityId } from '@/identities/types'; +import type { SignedClaim } from '@/claims/types'; import type { Key } from '@/keys/types'; +import type { + ClaimLinkNode, + ClaimLinkIdentity, +} from '../../src/claims/payloads/index'; +import type { + GestaltIdentityInfo, + GestaltInfo, + GestaltNodeInfo, + GestaltId, + GestaltLink, + GestaltLinkId, + GestaltLinkNode, + GestaltLinkIdentity, +} from '../../src/gestalts/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; +import { fc, testProp } from '@fast-check/jest'; +import { AsyncIterableX as AsyncIterable } from 'ix/asynciterable'; import GestaltGraph from '@/gestalts/GestaltGraph'; import ACL from '@/acl/ACL'; import * as gestaltsErrors from '@/gestalts/errors'; import * as gestaltsUtils from '@/gestalts/utils'; import * as utils from '@/utils'; import * as keysUtils from '@/keys/utils'; -import * as nodesUtils from '@/nodes/utils'; -import * as testNodesUtils from '../nodes/utils'; -import { GestaltIdentityInfo, GestaltNodeInfo } from '../../src/gestalts/types'; import Token from '@/tokens/Token'; -import { parseSignedClaimLinkNode, assertClaimLinkNode } from '../../src/claims/payloads/index'; -import { ClaimIdEncoded } from '@/claims/types'; +import { encodeGestaltNodeId, encodeGestaltIdentityId } from '@/gestalts/utils'; +import * as testsGestaltsUtils from './utils'; +import * as testsIdentitiesUtils from '../identities/utils'; +import * as testsKeysUtils from '../keys/utils'; +import * as ids from '../../src/ids/index'; +import * as testsIdsUtils from '../ids/utils'; +import 'ix/add/asynciterable-operators/toarray'; describe('GestaltGraph', () => { const logger = new Logger('GestaltGraph Test', LogLevel.WARN, [ new StreamHandler(), ]); - const key = keysUtils.generateKey(); - const nodeIdABC = testNodesUtils.generateRandomNodeId(); - const nodeInfoABC: GestaltNodeInfo = { - nodeId: nodeIdABC, - }; - const nodeIdABCEncoded = nodesUtils.encodeNodeId(nodeIdABC); - const encodedGestaltNodeIdABC = gestaltsUtils.encodeGestaltNodeId(['node', nodeIdABC]); - const nodeIdDEE = testNodesUtils.generateRandomNodeId(); - const nodeInfoDEE: GestaltNodeInfo = { - nodeId: nodeIdDEE, - }; - const nodeIdDEEEncoded = nodesUtils.encodeNodeId(nodeIdDEE); - const encodedGestaltNodeIdDEE = gestaltsUtils.encodeGestaltNodeId(['node', nodeIdDEE]); - const nodeIdDEF = testNodesUtils.generateRandomNodeId(); - const nodeIdDEFEncoded = nodesUtils.encodeNodeId(nodeIdDEF); - const nodeIdZZZ = testNodesUtils.generateRandomNodeId(); - const nodeIdZZZEncoded = nodesUtils.encodeNodeId(nodeIdZZZ); - const identityInfo: GestaltIdentityInfo = { - providerId: 'github.com' as ProviderId, - identityId: 'abc' as IdentityId, - }; - const providerIdentityId: ProviderIdentityId = [ - identityInfo.providerId, - identityInfo.identityId, - ]; const encodeGestaltIdentityId = gestaltsUtils.encodeGestaltIdentityId([ - 'identity', - providerIdentityId, - ]); - let dataDir: string; let db: DB; let acl: ACL; - // Abc <--> dee claims: - // const abcDeeSignatures: Record = {}; - let nodeClaimAbcToDee: Claim; - let nodeClaimDeeToAbc: Claim; - // Abc <--> GitHub claims: - // const abcSignature: Record = {}; - let identityClaimAbcToGH: Claim; - // let identityClaimGHToAbc: IdentityClaim; + // Composed arbs + const gestaltNodeInfoComposedArb = testsIdsUtils.nodeIdArb.chain( + testsGestaltsUtils.gestaltNodeInfoArb, + ); + const linkNodeComposedArb = fc + .tuple(testsKeysUtils.keyPairArb, testsKeysUtils.keyPairArb) + .chain(([keyPair1, keyPair2]) => { + const nodeId1 = keysUtils.publicKeyToNodeId(keyPair1.publicKey); + const nodeId2 = keysUtils.publicKeyToNodeId(keyPair2.publicKey); + return fc.record({ + gestaltNodeInfo1: testsGestaltsUtils.gestaltNodeInfoArb(nodeId1), + gestaltNodeInfo2: testsGestaltsUtils.gestaltNodeInfoArb(nodeId2), + linkNode: testsGestaltsUtils.linkNodeArb(keyPair1, keyPair2), + }); + }) + .noShrink(); + const gestaltIdentityInfoComposedArb = fc + .tuple( + testsIdentitiesUtils.providerIdArb, + testsIdentitiesUtils.identitiyIdArb, + ) + .chain((item) => testsGestaltsUtils.gestaltIdentityInfoArb(...item)) + .noShrink(); + const linkIdentityComposedArb = fc + .tuple( + testsKeysUtils.keyPairArb, + testsIdentitiesUtils.providerIdArb, + testsIdentitiesUtils.identitiyIdArb, + ) + .chain(([keyPair, providerId, identityId]) => { + const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); + return fc.record({ + gestaltNodeInfo: testsGestaltsUtils.gestaltNodeInfoArb(nodeId), + gestaltIdentityInfo: testsGestaltsUtils.gestaltIdentityInfoArb( + providerId, + identityId, + ), + linkIdentity: testsGestaltsUtils.linkIdentityArb( + keyPair, + providerId, + identityId, + ), + }); + }) + .noShrink(); + const gestaltInfoComposedArb = fc.oneof( + fc.tuple(fc.constant('node'), gestaltNodeInfoComposedArb), + fc.tuple(fc.constant('identity'), gestaltIdentityInfoComposedArb), + ) as fc.Arbitrary< + ['node', GestaltNodeInfo] | ['identity', GestaltIdentityInfo] + >; + const linkVertexComposedArb = fc + .oneof( + fc.tuple(fc.constant('node'), linkNodeComposedArb), + fc.tuple(fc.constant('identity'), linkIdentityComposedArb), + ) + .map((item) => { + const [type, linkData] = item as any; + switch (type) { + case 'node': + return { + gestaltVertexInfo1: ['node', linkData.gestaltNodeInfo1] as [ + 'node', + GestaltNodeInfo, + ], + gestaltVertexInfo2: [ + 'node', + linkData.gestaltNodeInfo2, + ] as GestaltInfo, + gestaltLink: ['node', linkData.linkNode] as GestaltLink, + }; + case 'identity': + return { + gestaltVertexInfo1: ['node', linkData.gestaltNodeInfo] as [ + 'node', + GestaltNodeInfo, + ], + gestaltVertexInfo2: [ + 'identity', + linkData.gestaltIdentityInfo, + ] as GestaltInfo, + gestaltLink: ['identity', linkData.linkIdentity] as GestaltLink, + }; + default: + } + throw Error(); + }) + .noShrink(); beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -78,7 +144,7 @@ describe('GestaltGraph', () => { dbPath, logger, crypto: { - key: await keysUtils.generateKey(), + key: keysUtils.generateKey(), ops: { encrypt: async (key, plainText) => { return keysUtils.encryptWithKey( @@ -190,1070 +256,1102 @@ describe('GestaltGraph', () => { gestaltsErrors.ErrorGestaltsGraphDestroyed, ); const getGestalts = async () => { - for await (const item of gestaltGraph.getGestalts()){ - // do nothing, should throw + for await (const _ of gestaltGraph.getGestalts()) { + // Do nothing, should throw } }; await expect(getGestalts()).rejects.toThrow( gestaltsErrors.ErrorGestaltsGraphNotRunning, ); }); - test('get, set and unset node', async () => { - const gestaltGraph = await GestaltGraph.createGestaltGraph({ - db, - acl, - logger, - }); - try { - await gestaltGraph.setNode(nodeInfoABC); - const gestalt = await gestaltGraph.getGestaltByNode(nodeIdABC); - expect(gestalt).toStrictEqual({ - matrix: { [encodedGestaltNodeIdABC]: {} }, - nodes: { - [encodedGestaltNodeIdABC]: { - nodeId: nodeIdABC, - }, - }, - identities: {}, + testProp( + 'getNode, setNode and unsetNode', + [gestaltNodeInfoComposedArb], + async (gestaltNodeInfo) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, }); - await gestaltGraph.unsetNode(nodeIdABC); - await gestaltGraph.unsetNode(nodeIdABC); - await expect( - gestaltGraph.getGestaltByNode(nodeIdABC), - ).resolves.toBeUndefined(); - } finally { + expect(await gestaltGraph.setNode(gestaltNodeInfo)).toEqual([ + 'node', + gestaltNodeInfo.nodeId, + ]); + expect(await gestaltGraph.getNode(gestaltNodeInfo.nodeId)).toEqual( + gestaltNodeInfo, + ); + await gestaltGraph.unsetNode(gestaltNodeInfo.nodeId); + expect( + await gestaltGraph.getNode(gestaltNodeInfo.nodeId), + ).toBeUndefined(); await gestaltGraph.stop(); - await gestaltGraph.destroy(); - } - }); - test('get, set and unset identity', async () => { - const gestaltGraph = await GestaltGraph.createGestaltGraph({ - db, - acl, - logger, - }); - try { - await gestaltGraph.setIdentity(identityInfo); - const gestalt = await gestaltGraph.getGestaltByIdentity(providerIdentityId); - expect(gestalt).toStrictEqual({ - matrix: { [encodeGestaltIdentityId]: {} }, - nodes: {}, - identities: { [encodeGestaltIdentityId]: identityInfo }, + }, + ); + testProp( + 'setNode updates node information', + [gestaltNodeInfoComposedArb], + async (gestaltNodeInfo) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, }); - await gestaltGraph.unsetIdentity( - providerIdentityId + expect(await gestaltGraph.setNode(gestaltNodeInfo)).toEqual([ + 'node', + gestaltNodeInfo.nodeId, + ]); + const gestaltNodeInfo_ = { + ...gestaltNodeInfo, + foo: 'bar', + }; + expect(await gestaltGraph.setNode(gestaltNodeInfo_)).toEqual([ + 'node', + gestaltNodeInfo.nodeId, + ]); + expect(await gestaltGraph.getNode(gestaltNodeInfo.nodeId)).toEqual( + gestaltNodeInfo_, ); - await gestaltGraph.unsetIdentity( - providerIdentityId + await gestaltGraph.stop(); + }, + ); + testProp( + 'linkNodeAndNode and unlinkNodeAndNode', + [linkNodeComposedArb], + async ({ gestaltNodeInfo1, gestaltNodeInfo2, linkNode }) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + const gestaltLinkId = await gestaltGraph.linkNodeAndNode( + gestaltNodeInfo1, + gestaltNodeInfo2, + linkNode, ); - await expect( - gestaltGraph.getGestaltByIdentity( - providerIdentityId + const gestaltLink = await gestaltGraph.getLinkById(gestaltLinkId); + expect(gestaltLink).toBeDefined(); + expect(gestaltLink).toMatchObject([ + 'node', + { + id: gestaltLinkId, + claim: { + payload: { + typ: 'ClaimLinkNode', + iss: ids.encodeNodeId(gestaltNodeInfo1.nodeId), + sub: ids.encodeNodeId(gestaltNodeInfo2.nodeId), + }, + signatures: expect.toSatisfy((signatures) => { + return signatures.length === 2; + }), + }, + meta: expect.any(Object), + }, + ]); + const token = Token.fromSigned( + gestaltLink![1].claim as SignedClaim, + ); + expect( + token.verifyWithPublicKey( + keysUtils.publicKeyFromNodeId(gestaltNodeInfo1.nodeId), + ), + ).toBe(true); + expect( + token.verifyWithPublicKey( + keysUtils.publicKeyFromNodeId(gestaltNodeInfo2.nodeId), ), - ).resolves.toBeUndefined(); - } finally { + ).toBe(true); + await gestaltGraph.unlinkNodeAndNode( + gestaltNodeInfo1.nodeId, + gestaltNodeInfo2.nodeId, + ); + expect(await gestaltGraph.getLinkById(gestaltLinkId)).toBeUndefined(); await gestaltGraph.stop(); - await gestaltGraph.destroy(); - } - }); - test('setting independent node and identity gestalts', async () => { - const gestaltGraph = await GestaltGraph.createGestaltGraph({ - db, - acl, - logger, - }); - try { - await gestaltGraph.setNode(nodeInfoABC); - await gestaltGraph.setIdentity(identityInfo); - const gestaltNode = await gestaltGraph.getGestaltByNode(nodeIdABC); - const gestaltIdentity = await gestaltGraph.getGestaltByIdentity(providerIdentityId); - expect(gestaltNode).toStrictEqual({ - matrix: { [encodedGestaltNodeIdABC]: {} }, - nodes: { - [encodedGestaltNodeIdABC]: { - nodeId: nodeIdABC, + }, + ); + testProp( + 'get, set and unset identity', + [gestaltIdentityInfoComposedArb], + async (gestaltIdentityInfo) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + try { + // Setting + const [type, providerIdentityId] = await gestaltGraph.setIdentity( + gestaltIdentityInfo, + ); + expect(type).toBe('identity'); + expect(providerIdentityId[0]).toBe(gestaltIdentityInfo.providerId); + expect(providerIdentityId[1]).toBe(gestaltIdentityInfo.identityId); + // Getting should return the same data + expect( + await gestaltGraph.getIdentity(providerIdentityId), + ).toMatchObject(gestaltIdentityInfo); + // Unsetting should remove the identity + await gestaltGraph.unsetIdentity(providerIdentityId); + expect( + await gestaltGraph.getIdentity(providerIdentityId), + ).toBeUndefined(); + } finally { + await gestaltGraph.stop(); + } + }, + ); + testProp( + 'setIdentity updates identity info', + [gestaltIdentityInfoComposedArb], + async (gestaltIdentityInfo) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + try { + // Setting + const [type, providerIdentityId] = await gestaltGraph.setIdentity( + gestaltIdentityInfo, + ); + expect(type).toBe('identity'); + expect(providerIdentityId[0]).toBe(gestaltIdentityInfo.providerId); + expect(providerIdentityId[1]).toBe(gestaltIdentityInfo.identityId); + // Getting should return the same data + expect( + await gestaltGraph.getIdentity(providerIdentityId), + ).toMatchObject(gestaltIdentityInfo); + // Updating + const newGestaltIdentityInfo = { + ...gestaltIdentityInfo, + foo: 'bar', + }; + const [type_, providerIdentityId_] = await gestaltGraph.setIdentity( + newGestaltIdentityInfo, + ); + expect(type_).toBe('identity'); + expect(providerIdentityId_[0]).toBe(gestaltIdentityInfo.providerId); + expect(providerIdentityId_[1]).toBe(gestaltIdentityInfo.identityId); + // Getting should return the new data + expect( + await gestaltGraph.getIdentity(providerIdentityId), + ).toMatchObject(newGestaltIdentityInfo); + } finally { + await gestaltGraph.stop(); + } + }, + ); + testProp( + 'linkNodeAndIdentity and unlinkNodeAndIdentity', + [linkIdentityComposedArb], + async ({ gestaltNodeInfo, gestaltIdentityInfo, linkIdentity }) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + try { + const gestaltLinkId = await gestaltGraph.linkNodeAndIdentity( + gestaltNodeInfo, + gestaltIdentityInfo, + linkIdentity, + ); + const gestaltLink = await gestaltGraph.getLinkById(gestaltLinkId); + expect(gestaltLink).toBeDefined(); + expect(gestaltLink).toMatchObject([ + 'identity', + { + id: gestaltLinkId, + claim: { + payload: { + typ: 'ClaimLinkIdentity', + iss: ids.encodeNodeId(gestaltNodeInfo.nodeId), + sub: ids.encodeProviderIdentityId([ + gestaltIdentityInfo.providerId, + gestaltIdentityInfo.identityId, + ]), + }, + signatures: expect.toSatisfy((signatures) => { + return signatures.length === 1; + }), + }, + meta: expect.any(Object), }, - }, - identities: {}, + ]); + const token = Token.fromSigned( + gestaltLink![1].claim as SignedClaim, + ); + expect( + token.verifyWithPublicKey( + keysUtils.publicKeyFromNodeId(gestaltNodeInfo.nodeId), + ), + ).toBe(true); + await gestaltGraph.unlinkNodeAndIdentity(gestaltNodeInfo.nodeId, [ + gestaltIdentityInfo.providerId, + gestaltIdentityInfo.identityId, + ]); + expect(await gestaltGraph.getLinkById(gestaltLinkId)).toBeUndefined(); + } finally { + await gestaltGraph.stop(); + } + }, + ); + testProp( + 'getVertex, setVertex and unsetVertex', + [gestaltInfoComposedArb], + async (gestaltInfo) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, }); - expect(gestaltIdentity).toStrictEqual({ - matrix: { [encodeGestaltIdentityId]: {} }, - nodes: {}, - identities: { [encodeGestaltIdentityId]: identityInfo }, + const [type, vertexInfo] = gestaltInfo; + const gestaltId: GestaltId = + type === 'node' + ? [type, vertexInfo.nodeId] + : [type, [vertexInfo.providerId, vertexInfo.identityId]]; + const vertexId = await gestaltGraph.setVertex(gestaltInfo); + expect(vertexId).toEqual(gestaltId); + expect(await gestaltGraph.getVertex(vertexId)).toEqual(gestaltInfo); + await gestaltGraph.unsetVertex(vertexId); + expect(await gestaltGraph.getVertex(vertexId)).toBeUndefined(); + await gestaltGraph.stop(); + }, + ); + testProp( + 'setVertex updates vertex information', + [gestaltInfoComposedArb], + async (gestaltInfo) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, }); - } finally { + const [type, vertexInfo] = gestaltInfo; + const gestaltId: GestaltId = + type === 'node' + ? [type, vertexInfo.nodeId] + : [type, [vertexInfo.providerId, vertexInfo.identityId]]; + const vertexId = await gestaltGraph.setVertex(gestaltInfo); + expect(vertexId).toEqual(gestaltId); + + const gestaltInfo_ = [ + type, + { + ...gestaltInfo[1], + foo: 'bar', + }, + ] as ['node', GestaltNodeInfo] | ['identity', GestaltIdentityInfo]; + expect(await gestaltGraph.setVertex(gestaltInfo_)).toEqual(gestaltId); + expect(await gestaltGraph.getVertex(vertexId)).toEqual(gestaltInfo_); await gestaltGraph.stop(); - await gestaltGraph.destroy(); - } - }); - test('start and stop preserves state', async () => { - let gestaltGraph = await GestaltGraph.createGestaltGraph({ - db, - acl, - logger, - }); - try { - await gestaltGraph.setNode(nodeInfoABC); - await gestaltGraph.setIdentity(identityInfo); + }, + ); + testProp( + 'linkVertexAndVertex and unlinkVertexAndVertex', + [linkVertexComposedArb], + async ({ gestaltVertexInfo1, gestaltVertexInfo2, gestaltLink }) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + const [type] = gestaltVertexInfo2; + // There is no generic form available for this method. + // We need to cast to the proper types. + let gestaltLinkId: GestaltLinkId; + switch (type) { + case 'node': + gestaltLinkId = await gestaltGraph.linkVertexAndVertex( + gestaltVertexInfo1 as ['node', GestaltNodeInfo], + gestaltVertexInfo2 as ['node', GestaltNodeInfo], + gestaltLink as ['node', GestaltLinkNode], + ); + break; + case 'identity': + gestaltLinkId = await gestaltGraph.linkVertexAndVertex( + gestaltVertexInfo1 as ['node', GestaltNodeInfo], + gestaltVertexInfo2 as ['identity', GestaltIdentityInfo], + gestaltLink as ['identity', GestaltLinkIdentity], + ); + break; + default: + fail('invalid logic'); + } + const gestaltLinkNew = await gestaltGraph.getLinkById(gestaltLinkId); + expect(gestaltLinkNew).toBeDefined(); + expect(gestaltLinkNew).toMatchObject([ + type, + { + id: gestaltLinkId, + claim: { + payload: gestaltLink[1].claim.payload, + signatures: expect.toSatisfy((signatures) => { + return signatures.length >= 1; + }), + }, + meta: expect.any(Object), + }, + ]); + const token = Token.fromSigned( + gestaltLinkNew![1].claim as SignedClaim, + ); + const nodeId1 = gestaltVertexInfo1[1].nodeId as NodeId; + expect( + token.verifyWithPublicKey(keysUtils.publicKeyFromNodeId(nodeId1)), + ).toBe(true); + let nodeId2: NodeId | null = null; + if (type === 'node') { + nodeId2 = gestaltVertexInfo2[1].nodeId as NodeId; + expect( + token.verifyWithPublicKey(keysUtils.publicKeyFromNodeId(nodeId2)), + ).toBe(true); + } + // There is no generic form for this method so we need to be type explicit + if (nodeId2 != null) { + await gestaltGraph.unlinkVertexAndVertex( + ['node', nodeId1], + ['node', nodeId2], + ); + } else { + await gestaltGraph.unlinkVertexAndVertex(['node', nodeId1], [ + 'identity', + [gestaltVertexInfo2[1].providerId, gestaltVertexInfo2[1].identityId], + ] as ['identity', ProviderIdentityId]); + } + expect(await gestaltGraph.getLinkById(gestaltLinkId)).toBeUndefined(); await gestaltGraph.stop(); - - gestaltGraph = await GestaltGraph.createGestaltGraph({ + }, + ); + testProp( + 'getGestaltByNode', + [gestaltNodeInfoComposedArb], + async (gestaltNodeInfo) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ db, acl, logger, + fresh: true, }); - const gestaltNode = await gestaltGraph.getGestaltByNode(nodeIdABC); - const gestaltIdentity = await gestaltGraph.getGestaltByIdentity(providerIdentityId); - expect(gestaltNode).toStrictEqual({ - matrix: { [encodedGestaltNodeIdABC]: {} }, + expect(await gestaltGraph.setNode(gestaltNodeInfo)).toEqual([ + 'node', + gestaltNodeInfo.nodeId, + ]); + const gestalt = await gestaltGraph.getGestaltByNode( + gestaltNodeInfo.nodeId, + ); + const gestaltNodeId = encodeGestaltNodeId([ + 'node', + gestaltNodeInfo.nodeId, + ]); + expect(gestalt).toMatchObject({ + matrix: { + [gestaltNodeId]: {}, + }, nodes: { - [encodedGestaltNodeIdABC]: { - nodeId: nodeIdABC, - }, + [gestaltNodeId]: gestaltNodeInfo, }, identities: {}, }); - expect(gestaltIdentity).toStrictEqual({ - matrix: { [encodeGestaltIdentityId]: {} }, + }, + ); + testProp( + 'getGestaltByIdentity', + [gestaltIdentityInfoComposedArb], + async (gestaltIdentityInfo) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + const providerIdentitiyId: ProviderIdentityId = [ + gestaltIdentityInfo.providerId, + gestaltIdentityInfo.identityId, + ]; + expect(await gestaltGraph.setIdentity(gestaltIdentityInfo)).toEqual([ + 'identity', + providerIdentitiyId, + ]); + const gestalt = await gestaltGraph.getGestaltByIdentity( + providerIdentitiyId, + ); + const gestaltIdentityId = encodeGestaltIdentityId([ + 'identity', + providerIdentitiyId, + ]); + expect(gestalt).toMatchObject({ + matrix: { + [gestaltIdentityId]: {}, + }, nodes: {}, - identities: { [encodeGestaltIdentityId]: identityInfo }, + identities: { + [gestaltIdentityId]: gestaltIdentityInfo, + }, }); - } finally { - await gestaltGraph.stop(); - await gestaltGraph.destroy(); - } - }); - test('link node to node', async () => { + }, + ); + testProp('getGestalt', [gestaltInfoComposedArb], async (gestaltInfo) => { const gestaltGraph = await GestaltGraph.createGestaltGraph({ db, acl, logger, + fresh: true, }); - try{ - // abc -> dee - // dee -> abc - await gestaltGraph.setNode(nodeInfoABC); - await gestaltGraph.setNode(nodeInfoDEE); - const claim = Token.fromPayload({ - iss: nodeIdABCEncoded, - sub: nodeIdDEEEncoded, - jti: '' as ClaimIdEncoded, - iat: 0, - nbf: 0, - seq: 0, - prevClaimId: null, - prevDigest: null, + const gestaltId = await gestaltGraph.setVertex(gestaltInfo); + const gestalt = await gestaltGraph.getGestalt(gestaltId); + const [type] = gestaltInfo; + switch (type) { + case 'node': + { + const gestaltNodeId = encodeGestaltNodeId([ + 'node', + gestaltInfo[1].nodeId, + ]); + expect(gestalt).toMatchObject({ + matrix: { + [gestaltNodeId]: {}, + }, + nodes: { + [gestaltNodeId]: gestaltInfo[1], + }, + identities: {}, + }); + } + break; + case 'identity': + { + const providerIdentitiyId: ProviderIdentityId = [ + gestaltInfo[1].providerId, + gestaltInfo[1].identityId, + ]; + const gestaltIdentityId = encodeGestaltIdentityId([ + 'identity', + providerIdentitiyId, + ]); + expect(gestalt).toMatchObject({ + matrix: { + [gestaltIdentityId]: {}, + }, + nodes: {}, + identities: { + [gestaltIdentityId]: gestaltInfo[1], + }, + }); + } + break; + default: + fail('invalid type'); + } + }); + testProp( + 'getGestalts with nodes', + [fc.array(gestaltNodeInfoComposedArb, { minLength: 2 })], + async (gestaltNodeInfos) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, }); - claim.signWithKey(key) - await gestaltGraph.linkNodeAndNode(nodeInfoABC, nodeInfoDEE, { - claim: claim.toSigned(), - meta: {} + for (const gestaltNodeInfo of gestaltNodeInfos) { + await gestaltGraph.setNode(gestaltNodeInfo); + } + const gestalts = await AsyncIterable.as( + gestaltGraph.getGestalts(), + ).toArray(); + expect(gestalts).toHaveLength(gestaltNodeInfos.length); + for (const gestalt of gestalts) { + const gestaltId = Object.keys(gestalt.nodes)[0]; + const [, nodeId] = gestaltsUtils.decodeGestaltNodeId(gestaltId)!; + expect(gestalt).toMatchObject({ + matrix: { + [gestaltId]: {}, + }, + nodes: { + [gestaltId]: { nodeId }, + }, + identities: {}, + }); + } + }, + ); + testProp( + 'getGestalts with identities', + [fc.array(gestaltIdentityInfoComposedArb, { minLength: 2 }).noShrink()], + async (gestaltIdentityInfos) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, }); - const gestaltNode1 = await gestaltGraph.getGestaltByNode(nodeIdABC); - const gestaltNode2 = await gestaltGraph.getGestaltByNode(nodeIdDEE); - expect(gestaltNode1).not.toBeUndefined(); - expect(gestaltNode2).not.toBeUndefined(); - expect(gestaltNode1).toStrictEqual(gestaltNode2); - expect(gestaltNode1).toStrictEqual({ - matrix: { - [encodedGestaltNodeIdABC]: { - [encodedGestaltNodeIdDEE]: expect.any(Array), + for (const gestaltIdentityInfo of gestaltIdentityInfos) { + await gestaltGraph.setIdentity(gestaltIdentityInfo); + } + const gestalts = await AsyncIterable.as( + gestaltGraph.getGestalts(), + ).toArray(); + expect(gestalts).toHaveLength(gestaltIdentityInfos.length); + for (const gestalt of gestalts) { + const gestaltId = Object.keys(gestalt.identities)[0]; + const [, providerIdentityId] = + gestaltsUtils.decodeGestaltIdentityId(gestaltId)!; + expect(gestalt).toMatchObject({ + matrix: { + [gestaltId]: {}, }, - [encodedGestaltNodeIdDEE]: { - [encodedGestaltNodeIdABC]: expect.any(Array), + nodes: {}, + identities: { + [gestaltId]: { + providerId: providerIdentityId[0], + identityId: providerIdentityId[1], + }, }, + }); + } + }, + ); + testProp( + 'getGestalts with nodes and identities', + [fc.array(gestaltInfoComposedArb, { minLength: 2 })], + async (gestaltInfos) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + for (const gestaltinfo of gestaltInfos) { + await gestaltGraph.setVertex(gestaltinfo); + } + const gestalts = await AsyncIterable.as( + gestaltGraph.getGestalts(), + ).toArray(); + expect(gestalts).toHaveLength(gestaltInfos.length); + for (const gestalt of gestalts) { + const gestaltId = Object.keys(gestalt.matrix)[0]; + const [type, id] = gestaltsUtils.decodeGestaltId(gestaltId)!; + switch (type) { + case 'node': + { + expect(gestalt).toMatchObject({ + matrix: { + [gestaltId]: {}, + }, + nodes: { + [gestaltId]: { nodeId: id }, + }, + identities: {}, + }); + } + break; + case 'identity': + { + expect(gestalt).toMatchObject({ + matrix: { + [gestaltId]: {}, + }, + nodes: {}, + identities: { + [gestaltId]: { + providerId: id[0], + identityId: id[1], + }, + }, + }); + } + break; + default: + fail('invalid type'); + } + } + }, + ); + testProp( + 'getGestalt with node links', + [linkNodeComposedArb], + async ({ gestaltNodeInfo1, gestaltNodeInfo2, linkNode }) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + await gestaltGraph.linkNodeAndNode( + gestaltNodeInfo1, + gestaltNodeInfo2, + linkNode, + ); + + const gestalt = (await gestaltGraph.getGestaltByNode( + gestaltNodeInfo1.nodeId, + ))!; + const gestaltId1 = gestaltsUtils.encodeGestaltNodeId([ + 'node', + gestaltNodeInfo1.nodeId, + ]); + const gestaltId2 = gestaltsUtils.encodeGestaltNodeId([ + 'node', + gestaltNodeInfo2.nodeId, + ]); + // We expect that the links exist, don't care about details for this test + expect(gestalt).toMatchObject({ + matrix: { + [gestaltId1]: { [gestaltId2]: expect.any(Array) }, + [gestaltId2]: { [gestaltId1]: expect.any(Array) }, }, nodes: { - [encodedGestaltNodeIdABC]: { - nodeId: nodeIdABC, - }, - [encodedGestaltNodeIdDEE]: { - nodeId: nodeIdDEE, - }, + [gestaltId1]: expect.any(Object), + [gestaltId2]: expect.any(Object), }, identities: {}, }); - } finally { + // Unlinking should split the gestalts + await gestaltGraph.unlinkNodeAndNode( + gestaltNodeInfo1.nodeId, + gestaltNodeInfo2.nodeId, + ); + expect( + await gestaltGraph.getGestaltByNode(gestaltNodeInfo1.nodeId), + ).toMatchObject({ + matrix: expect.toSatisfy((item) => { + const keys = Object.keys(item); + if (keys.length !== 1) return false; + return keys[0] === gestaltId1; + }), + nodes: expect.toSatisfy((item) => { + const keys = Object.keys(item); + if (keys.length !== 1) return false; + return keys[0] === gestaltId1; + }), + identities: {}, + }); await gestaltGraph.stop(); - await gestaltGraph.destroy(); - } - }); - // test('link node to identity', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // try { - // // abc -> GitHub - // // GitHub -> abc - // await gestaltGraph.linkNodeAndIdentity(nodeInfoABC, identityInfo, { - // claim: undefined, - // meta: { - // providerIdentityClaimId, - // } - // }); - // const gestaltNode = await gestaltGraph.getGestaltByNode(nodeIdABC); - // const gestaltIdentity = await gestaltGraph.getGestaltByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(gestaltNode).not.toBeUndefined(); - // expect(gestaltNode).toStrictEqual(gestaltIdentity); - // const gkNode = gestaltsUtils.keyFromNode(nodeIdABC); - // const gkIdentity = gestaltsUtils.keyFromIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(gestaltNode).toStrictEqual({ - // matrix: { - // [gkNode]: { - // [gkIdentity]: null, - // }, - // [gkIdentity]: { - // [gkNode]: null, - // }, - // }, - // nodes: { - // [gkNode]: { - // id: nodesUtils.encodeNodeId(nodeIdABC), - // chain: nodeInfo.chain, - // }, - // }, - // identities: { - // [gkIdentity]: identityInfo, - // }, - // }); - // } finally { - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // } - // }); - - // test('link node to node and identity', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> dee - // // abc -> GitHub - // const nodeInfo1Chain: Record = {}; - // nodeInfo1Chain['A'] = nodeClaimAbcToDee; - // identityClaimAbcToGH.payload.seq = 2; - // nodeInfo1Chain['B'] = identityClaimAbcToGH; - // const nodeInfo1: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // NodeInfo on node 'dee'. Contains claims: - // // dee -> abc - // const nodeInfo2Chain: ChainData = {}; - // nodeInfo2Chain['A'] = nodeClaimDeeToAbc; - // const nodeInfo2: NodeInfo = { - // id: nodeIdDEEEncoded, - // chain: nodeInfo2Chain, - // }; - // // IdentityInfo on identity from GitHub. Contains claims: - // // GitHub -> abc - // const identityInfoClaims: IdentityClaims = {}; - // identityInfoClaims['abcGistId'] = identityClaimGHToAbc; - // const identityInfo: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: identityInfoClaims, - // }; - // await gestaltGraph.linkNodeAndIdentity(nodeInfo1, identityInfo); - // await gestaltGraph.linkNodeAndNode(nodeInfo1, nodeInfo2); - // const gestaltNode1 = await gestaltGraph.getGestaltByNode(nodeIdABC); - // const gestaltNode2 = await gestaltGraph.getGestaltByNode(nodeIdDEE); - // const gestaltIdentity = await gestaltGraph.getGestaltByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(gestaltNode1).not.toBeUndefined(); - // expect(gestaltNode2).not.toBeUndefined(); - // expect(gestaltIdentity).not.toBeUndefined(); - // expect(gestaltNode1).toStrictEqual(gestaltNode2); - // expect(gestaltNode2).toStrictEqual(gestaltIdentity); - // const gkNode1 = gestaltsUtils.keyFromNode(nodeIdABC); - // const gkNode2 = gestaltsUtils.keyFromNode(nodeIdDEE); - // const gkIdentity = gestaltsUtils.keyFromIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(gestaltIdentity).toStrictEqual({ - // matrix: { - // [gkNode1]: { - // [gkNode2]: null, - // [gkIdentity]: null, - // }, - // [gkNode2]: { - // [gkNode1]: null, - // }, - // [gkIdentity]: { - // [gkNode1]: null, - // }, - // }, - // nodes: { - // [gkNode1]: { - // id: nodesUtils.encodeNodeId(nodeIdABC), - // chain: nodeInfo1.chain, - // }, - // [gkNode2]: { - // id: nodesUtils.encodeNodeId(nodeIdDEE), - // chain: nodeInfo2.chain, - // }, - // }, - // identities: { - // [gkIdentity]: identityInfo, - // }, - // }); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); - - // test('getting all gestalts', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // const nodeInfo1: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: {}, - // }; - // const nodeInfo2: NodeInfo = { - // id: nodeIdDEFEncoded, - // chain: {}, - // }; - // const identityInfo: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: {}, - // }; - // await gestaltGraph.setNode(nodeInfo1); - // await gestaltGraph.setNode(nodeInfo2); - // await gestaltGraph.setIdentity(identityInfo); - // await gestaltGraph.linkNodeAndIdentity(nodeInfo1, identityInfo); - // const gestalts = await gestaltGraph.getGestalts(); - // const identityGestalt = await gestaltGraph.getGestaltByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // const nodeGestalt = await gestaltGraph.getGestaltByNode(nodeIdABC); - // expect(gestalts).toContainEqual(identityGestalt); - // expect(gestalts).toContainEqual(nodeGestalt); - // expect(gestalts).toHaveLength(2); - // - // // Check if the two combine after linking. - // await gestaltGraph.linkNodeAndNode(nodeInfo1, nodeInfo2); - // const gestalts2 = await gestaltGraph.getGestalts(); - // expect(gestalts2).toHaveLength(1); - // const gestalts2String = JSON.stringify(gestalts2[0]); - // expect(gestalts2String).toContain(nodeInfo1.id); - // expect(gestalts2String).toContain(nodeInfo2.id); - // expect(gestalts2String).toContain(identityInfo.providerId); - // expect(gestalts2String).toContain(identityInfo.identityId); - // - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); - - // test('new node gestalts creates a new acl record', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // const nodeInfo: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: {}, - // }; - // expect(await acl.getNodePerm(nodeIdABC)).toBeUndefined(); - // await gestaltGraph.setNode(nodeInfo); - // const perm = await acl.getNodePerm(nodeIdABC); - // expect(perm).toBeDefined(); - // expect(perm).toMatchObject({ - // gestalt: {}, - // vaults: {}, - // }); - // const actions = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // expect(actions).toBeDefined(); - // expect(actions).toMatchObject({}); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + }, + ); + testProp( + 'getGestalt with identity links', + [linkIdentityComposedArb], + async ({ gestaltNodeInfo, gestaltIdentityInfo, linkIdentity }) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + await gestaltGraph.linkNodeAndIdentity( + gestaltNodeInfo, + gestaltIdentityInfo, + linkIdentity, + ); + const gestalt = (await gestaltGraph.getGestaltByIdentity([ + gestaltIdentityInfo.providerId, + gestaltIdentityInfo.identityId, + ]))!; + const gestaltId1 = gestaltsUtils.encodeGestaltNodeId([ + 'node', + gestaltNodeInfo.nodeId, + ]); + const gestaltId2 = gestaltsUtils.encodeGestaltIdentityId([ + 'identity', + [gestaltIdentityInfo.providerId, gestaltIdentityInfo.identityId], + ]); + // We expect that the links exist, don't care about details for this test + expect(gestalt).toMatchObject({ + matrix: { + [gestaltId1]: { [gestaltId2]: expect.any(Array) }, + [gestaltId2]: { [gestaltId1]: expect.any(Array) }, + }, + nodes: { + [gestaltId1]: expect.any(Object), + }, + identities: { + [gestaltId2]: expect.any(Object), + }, + }); + // Unlinking should split the gestalts + await gestaltGraph.unlinkNodeAndIdentity(gestaltNodeInfo.nodeId, [ + gestaltIdentityInfo.providerId, + gestaltIdentityInfo.identityId, + ]); + expect( + await gestaltGraph.getGestaltByNode(gestaltNodeInfo.nodeId), + ).toMatchObject({ + matrix: expect.toSatisfy((item) => { + const keys = Object.keys(item); + if (keys.length !== 1) return false; + return keys[0] === gestaltId1; + }), + nodes: expect.toSatisfy((item) => { + const keys = Object.keys(item); + if (keys.length !== 1) return false; + return keys[0] === gestaltId1; + }), + identities: {}, + }); + await gestaltGraph.stop(); + }, + ); + testProp( + 'getGestalt with node and identity links', + [linkVertexComposedArb], + async ({ gestaltVertexInfo1, gestaltVertexInfo2, gestaltLink }) => { + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + const [type, info] = gestaltVertexInfo2; + switch (type) { + case 'node': + await gestaltGraph.linkVertexAndVertex( + gestaltVertexInfo1 as ['node', GestaltNodeInfo], + gestaltVertexInfo2 as ['node', GestaltNodeInfo], + gestaltLink as ['node', GestaltLinkNode], + ); + break; + case 'identity': + await gestaltGraph.linkVertexAndVertex( + gestaltVertexInfo1 as ['node', GestaltNodeInfo], + gestaltVertexInfo2 as ['identity', GestaltIdentityInfo], + gestaltLink as ['identity', GestaltLinkIdentity], + ); + break; + default: + fail('invalid type'); + } - // test('new identity gestalts does not create a new acl record', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // const identityInfo: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: {}, - // }; - // await gestaltGraph.setIdentity(identityInfo); - // const actions = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(actions).toBeUndefined(); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + const gestalt = (await gestaltGraph.getGestalt([ + 'node', + gestaltVertexInfo1[1].nodeId, + ]))!; + const gestaltId1 = gestaltsUtils.encodeGestaltNodeId([ + 'node', + gestaltVertexInfo1[1].nodeId, + ]); + switch (type) { + case 'node': + { + const gestaltId2 = gestaltsUtils.encodeGestaltNodeId([ + 'node', + info.nodeId, + ]); + // We expect that the links exist, don't care about details for this test + expect(gestalt).toMatchObject({ + matrix: { + [gestaltId1]: { [gestaltId2]: expect.any(Array) }, + [gestaltId2]: { [gestaltId1]: expect.any(Array) }, + }, + nodes: { + [gestaltId1]: expect.any(Object), + [gestaltId2]: expect.any(Object), + }, + identities: {}, + }); + // Unlinking should split the gestalts + await gestaltGraph.unlinkVertexAndVertex( + ['node', gestaltVertexInfo1[1].nodeId], + ['node', info.nodeId], + ); + expect( + await gestaltGraph.getGestalt([ + 'node', + gestaltVertexInfo1[1].nodeId, + ]), + ).toMatchObject({ + matrix: expect.toSatisfy((item) => { + const keys = Object.keys(item); + if (keys.length !== 1) return false; + return keys[0] === gestaltId1; + }), + nodes: expect.toSatisfy((item) => { + const keys = Object.keys(item); + if (keys.length !== 1) return false; + return keys[0] === gestaltId1; + }), + identities: {}, + }); + } + break; + case 'identity': + { + const gestaltId2 = gestaltsUtils.encodeGestaltIdentityId([ + 'identity', + [info.providerId, info.identityId], + ]); + // We expect that the links exist, don't care about details for this test + expect(gestalt).toMatchObject({ + matrix: { + [gestaltId1]: { [gestaltId2]: expect.any(Array) }, + [gestaltId2]: { [gestaltId1]: expect.any(Array) }, + }, + nodes: { + [gestaltId1]: expect.any(Object), + }, + identities: { + [gestaltId2]: expect.any(Object), + }, + }); + // Unlinking should split the gestalts + await gestaltGraph.unlinkVertexAndVertex( + ['node', gestaltVertexInfo1[1].nodeId], + ['identity', [info.providerId, info.identityId]], + ); + expect( + await gestaltGraph.getGestalt([ + 'node', + gestaltVertexInfo1[1].nodeId, + ]), + ).toMatchObject({ + matrix: expect.toSatisfy((item) => { + const keys = Object.keys(item); + if (keys.length !== 1) return false; + return keys[0] === gestaltId1; + }), + nodes: expect.toSatisfy((item) => { + const keys = Object.keys(item); + if (keys.length !== 1) return false; + return keys[0] === gestaltId1; + }), + identities: {}, + }); + } + break; + default: + fail('invalid type'); + } + await gestaltGraph.stop(); + }, + ); - // test('set and unset gestalt actions', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // const nodeInfo: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: {}, - // }; - // await gestaltGraph.setNode(nodeInfo); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'notify'); - // let actions; - // actions = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // expect(actions).toHaveProperty('notify'); - // const perm = await acl.getNodePerm(nodeIdABC); - // expect(perm).toBeDefined(); - // expect(perm).toMatchObject({ - // gestalt: { - // notify: null, - // }, - // vaults: {}, - // }); - // await gestaltGraph.unsetGestaltActionByNode(nodeIdABC, 'notify'); - // actions = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // expect(actions).not.toHaveProperty('notify'); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + describe('Model based testing', () => { + const altCommandsArb = + // Use a record to generate a constrained set of vertices + fc + .record({ + keyPairs: fc.array(testsKeysUtils.keyPairArb, { minLength: 2 }), + identityInfos: fc.array(gestaltIdentityInfoComposedArb, { + minLength: 1, + }), + }) + .chain((verticies) => { + const { keyPairs, identityInfos } = verticies; + const nodeInfos = keyPairs.map((keyPair) => { + const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); + const nodeInfo: GestaltNodeInfo = { nodeId }; + return nodeInfo; + }); + const vertexInfos = [ + ...nodeInfos.map((nodeInfo) => ['node', nodeInfo]), + ...identityInfos.map((identityInfo) => ['identity', identityInfo]), + ] as Array; - // test('linking 2 new nodes results in a merged permission', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // // 2 new nodes should have the same permission - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> dee - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = nodeClaimAbcToDee; - // const nodeInfo1: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // NodeInfo on node 'dee'. Contains claims: - // // dee -> abc - // const nodeInfo2Chain: ChainData = {}; - // nodeInfo2Chain['A'] = nodeClaimDeeToAbc; - // const nodeInfo2: NodeInfo = { - // id: nodeIdDEEEncoded, - // chain: nodeInfo2Chain, - // }; - // await gestaltGraph.linkNodeAndNode(nodeInfo1, nodeInfo2); - // let actions1, actions2; - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByNode(nodeIdDEE); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions1).toEqual(actions2); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'notify'); - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByNode(nodeIdDEE); - // expect(actions1).toEqual({ notify: null }); - // expect(actions1).toEqual(actions2); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + // Random selection arbs + const randomNodeInfoArb = fc.constantFrom(...nodeInfos); + const randomNodeIdArb = randomNodeInfoArb.map( + (nodeInfo) => nodeInfo.nodeId, + ); + const randomIdentityInfoArb = fc.constantFrom(...identityInfos); + const randomProviderIdentityIdArb = randomIdentityInfoArb.map( + (identityInfo) => + [ + identityInfo.providerId, + identityInfo.identityId, + ] as ProviderIdentityId, + ); + const randomVertexInfo = fc.constantFrom(...vertexInfos); + const randomVertexId = fc.oneof( + fc.tuple(fc.constant('node'), randomNodeIdArb), + fc.tuple(fc.constant('identity'), randomProviderIdentityIdArb), + ) as fc.Arbitrary; + const randomKeyPair = fc.constantFrom(...keyPairs); - // test('linking 2 existing nodes results in a merged permission', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // // 2 existing nodes will have a joined permission - // const nodeInfo1: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: {}, - // }; - // const nodeInfo2: NodeInfo = { - // id: nodeIdDEEEncoded, - // chain: {}, - // }; - // await gestaltGraph.setNode(nodeInfo1); - // await gestaltGraph.setNode(nodeInfo2); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'notify'); - // await gestaltGraph.setGestaltActionByNode(nodeIdDEE, 'scan'); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> dee - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = nodeClaimAbcToDee; - // const nodeInfo1Linked: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // NodeInfo on node 'dee'. Contains claims: - // // dee -> abc - // const nodeInfo2Chain: ChainData = {}; - // nodeInfo2Chain['A'] = nodeClaimDeeToAbc; - // const nodeInfo2Linked: NodeInfo = { - // id: nodeIdDEEEncoded, - // chain: nodeInfo2Chain, - // }; - // await gestaltGraph.linkNodeAndNode(nodeInfo1Linked, nodeInfo2Linked); - // const actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // const actions2 = await gestaltGraph.getGestaltActionsByNode(nodeIdDEE); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions1).toEqual({ notify: null, scan: null }); - // expect(actions1).toEqual(actions2); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + const setVertexCommandArb = fc + .tuple(randomVertexInfo, testsGestaltsUtils.gestaltActionsArb(1)) + .map((args) => new testsGestaltsUtils.SetVertexCommand(...args)); + const unsetVertexCommandArb = randomVertexId.map( + (args) => new testsGestaltsUtils.UnsetVertexCommand(args), + ); + const linkNodesParamsArb = fc + .tuple(randomKeyPair, randomKeyPair) + .filter(([a, b]) => !a.privateKey.equals(b.privateKey)) + .chain(([keyPair1, keyPair2]) => { + const nodeInfo1 = { + nodeId: keysUtils.publicKeyToNodeId(keyPair1.publicKey), + }; + const nodeInfo2 = { + nodeId: keysUtils.publicKeyToNodeId(keyPair2.publicKey), + }; + return fc.tuple( + fc.constant(nodeInfo1), + fc.constant(nodeInfo2), + testsGestaltsUtils.gestaltLinkNodeArb(keyPair1, keyPair2), + ); + }); + const linkNodesCommandArb = linkNodesParamsArb.map( + ([nodeInfo1, nodeInfo2, linkNode]) => + new testsGestaltsUtils.LinkNodeAndNodeCommand( + nodeInfo1, + nodeInfo2, + linkNode, + ), + ); - // test('link existing node to new node', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // // Node 1 exists, but node 2 is new - // const nodeInfo1: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: {}, - // }; - // await gestaltGraph.setNode(nodeInfo1); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'notify'); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> dee - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = nodeClaimAbcToDee; - // const nodeInfo1Linked: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // NodeInfo on node 'dee'. Contains claims: - // // dee -> abc - // const nodeInfo2Chain: ChainData = {}; - // nodeInfo2Chain['A'] = nodeClaimDeeToAbc; - // const nodeInfo2Linked: NodeInfo = { - // id: nodeIdDEEEncoded, - // chain: nodeInfo2Chain, - // }; - // await gestaltGraph.linkNodeAndNode(nodeInfo1Linked, nodeInfo2Linked); - // let actions1, actions2; - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByNode(nodeIdDEE); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions1).toEqual({ notify: null }); - // expect(actions1).toEqual(actions2); - // // Node 3 is new and linking to node 2 which is now exists - // const zzzDeeSignatures: Record = {}; - // zzzDeeSignatures['zzz'] = 'zzzSignature'; - // zzzDeeSignatures['dee'] = 'deeSignature'; - // // Node claim on node abc: abc -> dee - // const nodeClaimZzzToDee: Claim = { - // payload: { - // hPrev: null, - // seq: 1, - // data: { - // type: 'node', - // node1: nodeIdZZZEncoded, - // node2: nodeIdDEEEncoded, - // }, - // iat: 1618203162, - // }, - // signatures: zzzDeeSignatures, - // }; - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> dee - // const nodeInfo3Chain: ChainData = {}; - // nodeInfo3Chain['A'] = nodeClaimZzzToDee; - // const nodeInfo3Linked: NodeInfo = { - // id: nodeIdZZZEncoded, - // chain: nodeInfo3Chain, - // }; - // await gestaltGraph.linkNodeAndNode(nodeInfo3Linked, nodeInfo2Linked); - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByNode(nodeIdDEE); - // const actions3 = await gestaltGraph.getGestaltActionsByNode(nodeIdZZZ); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions3).not.toBeUndefined(); - // expect(actions3).toEqual({ notify: null }); - // expect(actions3).toEqual(actions2); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + const linkIdentitiesParamsArb = fc + .tuple(randomKeyPair, randomIdentityInfoArb) + .chain(([keyPair, identityInfo]) => { + const nodeInfo = { + nodeId: keysUtils.publicKeyToNodeId(keyPair.publicKey), + }; + return fc.tuple( + fc.constant(nodeInfo), + fc.constant(identityInfo), + testsGestaltsUtils.gestaltLinkIdentityArb( + keyPair, + identityInfo.providerId, + identityInfo.identityId, + ), + ); + }); + const linkIdentitiiesCommandArb = linkIdentitiesParamsArb.map( + ([nodeInfo, identitiyInfo, linkIdentity]) => + new testsGestaltsUtils.LinkNodeAndIdentityCommand( + nodeInfo, + identitiyInfo, + linkIdentity, + ), + ); - // test('linking new node and new identity results in a merged permission', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> GitHub - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = identityClaimAbcToGH; - // const nodeInfo: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // IdentityInfo on identity from GitHub. Contains claims: - // // GitHub -> abc - // const identityInfoClaims: IdentityClaims = {}; - // identityInfoClaims['abcGistId'] = identityClaimGHToAbc; - // const identityInfo: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: identityInfoClaims, - // }; - // await gestaltGraph.linkNodeAndIdentity(nodeInfo, identityInfo); - // let actions1, actions2; - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions1).toEqual({}); - // expect(actions1).toEqual(actions2); - // await gestaltGraph.setGestaltActionByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // 'notify', - // ); - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(actions1).toEqual({ notify: null }); - // expect(actions1).toEqual(actions2); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + const linkVertexCommandArb = fc + .oneof( + linkNodesParamsArb.map( + ([info1, info2, link]) => + [ + ['node', info1], + ['node', info2], + ['node', link], + ] as [GestaltInfo, GestaltInfo, GestaltLink], + ), + linkIdentitiesParamsArb.map( + ([info1, info2, link]) => + [ + ['node', info1], + ['identity', info2], + ['identity', link], + ] as [GestaltInfo, GestaltInfo, GestaltLink], + ), + ) + .map( + ([gestaltInfo1, gestaltInfo2, gestaltLink]) => + new testsGestaltsUtils.LinkVertexAndVertexCommand( + gestaltInfo1 as ['node', GestaltNodeInfo], + gestaltInfo2, + gestaltLink, + ), + ); - // test('linking existing node and existing identity results in merged permission', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // const nodeInfo: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: {}, - // }; - // const identityInfo: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: {}, - // }; - // await gestaltGraph.setNode(nodeInfo); - // await gestaltGraph.setIdentity(identityInfo); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'notify'); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> GitHub - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = identityClaimAbcToGH; - // const nodeInfoLinked: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // IdentityInfo on identity from GitHub. Contains claims: - // // GitHub -> abc - // const identityInfoClaims: IdentityClaims = {}; - // identityInfoClaims['abcGistId'] = identityClaimGHToAbc; - // const identityInfoLinked: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: identityInfoClaims, - // }; - // await gestaltGraph.linkNodeAndIdentity(nodeInfoLinked, identityInfoLinked); - // let actions1, actions2; - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions1).toEqual({ notify: null }); - // expect(actions1).toEqual(actions2); - // const nodeInfo2: NodeInfo = { - // id: nodeIdDEFEncoded, - // chain: {}, - // }; - // await gestaltGraph.setNode(nodeInfo2); - // await gestaltGraph.unsetGestaltActionByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // 'notify', - // ); - // await gestaltGraph.setGestaltActionByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // 'scan', - // ); - // await gestaltGraph.setGestaltActionByNode(nodeIdDEF, 'notify'); - // - // const defSignature: Record = {}; - // defSignature['def'] = 'defSignature'; - // // Identity claim on node abc: def -> GitHub - // const identityClaimDefToGH: Claim = { - // payload: { - // hPrev: null, - // seq: 1, - // data: { - // type: 'identity', - // node: nodeIdDEFEncoded, - // provider: 'github.com' as ProviderId, - // identity: 'abc' as IdentityId, - // }, - // iat: 1618203162, - // }, - // signatures: defSignature, - // }; - // // NodeInfo on node 'def'. Contains claims: - // // def -> GitHub (abc) - // const nodeInfo2Chain: ChainData = {}; - // nodeInfo1Chain['A'] = identityClaimDefToGH; - // const nodeInfo2Linked: NodeInfo = { - // id: nodeIdDEFEncoded, - // chain: nodeInfo2Chain, - // }; - // - // // Identity claim on Github identity: GitHub -> def - // const identityClaimGHToDef = { - // id: 'abcGistId2' as IdentityClaimId, - // payload: { - // hPrev: null, - // seq: 2, - // data: { - // type: 'identity', - // node: nodeIdDEF, - // provider: 'github.com' as ProviderId, - // identity: 'abc' as IdentityId, - // }, - // iat: 1618203162, - // }, - // signatures: defSignature, - // }; - // // IdentityInfo on identity from GitHub. Contains claims: - // // GitHub (abc) -> abc - // // GitHub (abc) -> def - // const identityInfoClaimsAgain: IdentityClaims = {}; - // identityInfoClaimsAgain['abcGistId'] = identityClaimGHToAbc; - // identityInfoClaimsAgain['abcGistId2'] = identityClaimGHToDef; - // const identityInfoLinkedAgain: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: identityInfoClaims, - // }; - // await gestaltGraph.linkNodeAndIdentity( - // nodeInfo2Linked, - // identityInfoLinkedAgain, - // ); - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // const actions3 = await gestaltGraph.getGestaltActionsByNode(nodeIdDEF); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions3).not.toBeUndefined(); - // expect(actions2).toEqual({ notify: null, scan: null }); - // expect(actions1).toEqual(actions2); - // expect(actions2).toEqual(actions3); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + const unlinkNodeCommandArb = fc + .tuple(randomNodeIdArb, randomNodeIdArb) + .map( + ([nodeId1, nodeId2]) => + new testsGestaltsUtils.UnlinkNodeAndNodeCommand( + nodeId1, + nodeId2, + ), + ); - // test('link existing node to new identity', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // const nodeInfo: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: {}, - // }; - // await gestaltGraph.setNode(nodeInfo); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> GitHub - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = identityClaimAbcToGH; - // const nodeInfoLinked: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // IdentityInfo on identity from GitHub. Contains claims: - // // GitHub -> abc - // const identityInfoClaims: IdentityClaims = {}; - // identityInfoClaims['abcGistId'] = identityClaimGHToAbc; - // const identityInfoLinked: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: identityInfoClaims, - // }; - // await gestaltGraph.linkNodeAndIdentity(nodeInfoLinked, identityInfoLinked); - // let actions1, actions2; - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfoLinked.providerId, - // identityInfoLinked.identityId, - // ); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions1).toEqual(actions2); - // expect(actions1).toEqual({}); - // await gestaltGraph.setGestaltActionByIdentity( - // identityInfoLinked.providerId, - // identityInfoLinked.identityId, - // 'scan', - // ); - // await gestaltGraph.setGestaltActionByIdentity( - // identityInfoLinked.providerId, - // identityInfoLinked.identityId, - // 'notify', - // ); - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfoLinked.providerId, - // identityInfoLinked.identityId, - // ); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions1).toEqual(actions2); - // expect(actions1).toEqual({ - // scan: null, - // notify: null, - // }); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + const unlinkIdentityCommandArb = fc + .tuple(randomNodeIdArb, randomProviderIdentityIdArb) + .map( + ([nodeId, identityId]) => + new testsGestaltsUtils.UnlinkNodeAndIdentityCommand( + nodeId, + identityId, + ), + ); - // test('link new node to existing identity', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // const identityInfo: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: {}, - // }; - // await gestaltGraph.setIdentity(identityInfo); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> GitHub - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = identityClaimAbcToGH; - // const nodeInfoLinked: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // IdentityInfo on identity from GitHub. Contains claims: - // // GitHub -> abc - // const identityInfoClaims: IdentityClaims = {}; - // identityInfoClaims['abcGistId'] = identityClaimGHToAbc; - // const identityInfoLinked: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: identityInfoClaims, - // }; - // await gestaltGraph.linkNodeAndIdentity(nodeInfoLinked, identityInfoLinked); - // let actions1, actions2; - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions1).toEqual(actions2); - // expect(actions1).toEqual({}); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'scan'); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'notify'); - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(actions1).not.toBeUndefined(); - // expect(actions2).not.toBeUndefined(); - // expect(actions1).toEqual(actions2); - // expect(actions1).toEqual({ - // scan: null, - // notify: null, - // }); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + const unlinkVertexCommandArb = fc + .tuple( + randomNodeIdArb.map( + (nodeId) => ['node', nodeId] as ['node', NodeId], + ), + randomVertexId, + ) + .map( + ([gestaltId1, gestaltId2]) => + new testsGestaltsUtils.UnlinkVertexAndVertexCommand( + gestaltId1, + gestaltId2, + ), + ); - // test('splitting node and node results in split inherited permissions', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> dee - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = nodeClaimAbcToDee; - // const nodeInfo1: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // NodeInfo on node 'dee'. Contains claims: - // // dee -> abc - // const nodeInfo2Chain: ChainData = {}; - // nodeInfo2Chain['A'] = nodeClaimDeeToAbc; - // const nodeInfo2: NodeInfo = { - // id: nodeIdDEEEncoded, - // chain: nodeInfo2Chain, - // }; - // await gestaltGraph.linkNodeAndNode(nodeInfo1, nodeInfo2); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'scan'); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'notify'); - // let nodePerms; - // nodePerms = await acl.getNodePerms(); - // expect(Object.keys(nodePerms)).toHaveLength(1); - // await gestaltGraph.unlinkNodeAndNode(nodeIdABC, nodeIdDEE); - // let actions1, actions2; - // let perm1, perm2; - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByNode(nodeIdDEE); - // expect(actions1).toEqual({ scan: null, notify: null }); - // expect(actions2).toEqual({ scan: null, notify: null }); - // perm1 = await acl.getNodePerm(nodeIdABC); - // perm2 = await acl.getNodePerm(nodeIdDEE); - // expect(perm1).toEqual(perm2); - // await gestaltGraph.unsetGestaltActionByNode(nodeIdABC, 'notify'); - // await gestaltGraph.unsetGestaltActionByNode(nodeIdDEE, 'scan'); - // actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // actions2 = await gestaltGraph.getGestaltActionsByNode(nodeIdDEE); - // expect(actions1).toEqual({ scan: null }); - // expect(actions2).toEqual({ notify: null }); - // perm1 = await acl.getNodePerm(nodeIdABC); - // perm2 = await acl.getNodePerm(nodeIdDEE); - // expect(perm1).not.toEqual(perm2); - // nodePerms = await acl.getNodePerms(); - // expect(Object.keys(nodePerms)).toHaveLength(2); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + const commandsUnlink = fc.commands( + [ + unsetVertexCommandArb, + unlinkNodeCommandArb, + unlinkIdentityCommandArb, + unlinkVertexCommandArb, + ], + { size: '+1' }, + ); - // test('splitting node and identity results in split inherited permissions unless the identity is a loner', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> GitHub - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = identityClaimAbcToGH; - // const nodeInfo: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // IdentityInfo on identity from GitHub. Contains claims: - // // GitHub -> abc - // const identityInfoClaims: IdentityClaims = {}; - // identityInfoClaims['abcGistId'] = identityClaimGHToAbc; - // const identityInfo: IdentityInfo = { - // providerId: 'github.com' as ProviderId, - // identityId: 'abc' as IdentityId, - // claims: identityInfoClaims, - // }; - // await gestaltGraph.linkNodeAndIdentity(nodeInfo, identityInfo); - // await gestaltGraph.setGestaltActionByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // 'scan', - // ); - // await gestaltGraph.setGestaltActionByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // 'notify', - // ); - // let nodePerms; - // nodePerms = await acl.getNodePerms(); - // expect(Object.keys(nodePerms)).toHaveLength(1); - // await gestaltGraph.unlinkNodeAndIdentity( - // nodeIdABC, - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // const actions1 = await gestaltGraph.getGestaltActionsByNode(nodeIdABC); - // const actions2 = await gestaltGraph.getGestaltActionsByIdentity( - // identityInfo.providerId, - // identityInfo.identityId, - // ); - // expect(actions1).toEqual({ scan: null, notify: null }); - // // Identity no longer has attached node therefore it has no permissions - // expect(actions2).toBeUndefined(); - // nodePerms = await acl.getNodePerms(); - // expect(Object.keys(nodePerms)).toHaveLength(1); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + const commandsLink = fc.commands( + [ + setVertexCommandArb, + linkNodesCommandArb, + linkIdentitiiesCommandArb, + linkVertexCommandArb, + ], + { size: '=' }, + ); + return fc.tuple(commandsLink, commandsUnlink); + }) + .map(([commandsLink, commandsUnlink]) => { + return [...commandsLink, ...commandsUnlink]; + }) + .noShrink(); - // test('removing a gestalt removes the permission', async () => { - // const gestaltGraph = await GestaltGraph.createGestaltGraph({ - // db, - // acl, - // logger, - // }); - // // NodeInfo on node 'abc'. Contains claims: - // // abc -> dee - // const nodeInfo1Chain: ChainData = {}; - // nodeInfo1Chain['A'] = nodeClaimAbcToDee; - // const nodeInfo1: NodeInfo = { - // id: nodeIdABCEncoded, - // chain: nodeInfo1Chain, - // }; - // // NodeInfo on node 'dee'. Contains claims: - // // dee -> abc - // const nodeInfo2Chain: ChainData = {}; - // nodeInfo2Chain['A'] = nodeClaimDeeToAbc; - // const nodeInfo2: NodeInfo = { - // id: nodeIdDEEEncoded, - // chain: nodeInfo2Chain, - // }; - // await gestaltGraph.linkNodeAndNode(nodeInfo1, nodeInfo2); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'scan'); - // await gestaltGraph.setGestaltActionByNode(nodeIdABC, 'notify'); - // let nodePerms = await acl.getNodePerms(); - // expect(Object.keys(nodePerms)).toHaveLength(1); - // await gestaltGraph.unsetNode(nodeIdABC); - // // It's still 1 node perm - // // its just that node 1 is eliminated - // nodePerms = await acl.getNodePerms(); - // expect(Object.keys(nodePerms)).toHaveLength(1); - // expect(nodePerms[0][nodeIdABC.toString()]).toBeUndefined(); - // expect(nodePerms[0][nodeIdDEE.toString()]).toBeDefined(); - // await gestaltGraph.unsetNode(nodeIdDEE); - // nodePerms = await acl.getNodePerms(); - // expect(Object.keys(nodePerms)).toHaveLength(0); - // await gestaltGraph.stop(); - // await gestaltGraph.destroy(); - // }); + testProp( + 'model', + [altCommandsArb], + async (cmds) => { + await acl.start({ fresh: true }); + const gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + fresh: true, + }); + try { + const model: testsGestaltsUtils.GestaltGraphModel = { + matrix: {}, + nodes: {}, + identities: {}, + permissions: {}, + }; + const modelSetup = async () => { + return { + model, + real: gestaltGraph, + }; + }; + await fc.asyncModelRun(modelSetup, cmds); + } finally { + await gestaltGraph.stop(); + await acl.stop(); + } + }, + { numRuns: 50 }, + ); + }); }); diff --git a/tests/gestalts/utils.ts b/tests/gestalts/utils.ts new file mode 100644 index 000000000..da873bc88 --- /dev/null +++ b/tests/gestalts/utils.ts @@ -0,0 +1,787 @@ +import type { + NodeId, + ProviderId, + IdentityId, + GestaltId, + ProviderIdentityId, +} from '@/ids/types'; +import type { KeyPair } from '@/keys/types'; +import type { ClaimLinkNode, ClaimLinkIdentity } from '@/claims/payloads'; +import type { SignedClaim } from '@/claims/types'; +import type { + GestaltNodeInfo, + GestaltIdentityInfo, + GestaltNodes, + GestaltIdentities, + GestaltLinkIdentity, + GestaltLinkNode, + GestaltInfo, + GestaltLink, + GestaltActions, +} from '@/gestalts/types'; +import type { GestaltIdEncoded } from '@/ids/types'; +import type { GestaltGraph } from '../../src/gestalts/index'; +import fc from 'fast-check'; +import * as ids from '@/ids'; +import { gestaltActions } from '@/gestalts/types'; +import Token from '@/tokens/Token'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as gestaltsUtils from '@/gestalts/utils'; +import { never } from '@/utils/index'; +import * as testsIdsUtils from '../ids/utils'; +import * as testsClaimsUtils from '../claims/utils'; + +const gestaltNodeInfoArb = (nodeId: NodeId): fc.Arbitrary => + fc.record({ + nodeId: fc.constant(nodeId), + }); + +const gestaltIdentityInfoArb = ( + providerId: ProviderId, + identityId: IdentityId, +): fc.Arbitrary => + fc.record( + { + providerId: fc.constant(providerId), + identityId: fc.constant(identityId), + name: fc.webFragments(), + email: fc.emailAddress(), + url: fc.domain(), + }, + { + requiredKeys: ['identityId', 'providerId'], + }, + ); + +const gestaltLinkNodeArb = ( + keyPair1: KeyPair, + keyPair2: KeyPair, +): fc.Arbitrary => { + const signedClaimLinkNode = testsClaimsUtils.claimArb + .map((claim) => { + return { + typ: 'ClaimLinkNode', + iss: ids.encodeNodeId(keysUtils.publicKeyToNodeId(keyPair1.publicKey)), + sub: ids.encodeNodeId(keysUtils.publicKeyToNodeId(keyPair2.publicKey)), + ...claim, + }; + }) + .map((payload) => Token.fromPayload(payload)) + .map((token) => { + token.signWithPrivateKey(keyPair1); + token.signWithPrivateKey(keyPair2); + return token.toSigned(); + }) as fc.Arbitrary>; + return fc.record({ + id: testsIdsUtils.gestaltLinkIdArb, + claim: signedClaimLinkNode, + meta: fc.constant({}), + }); +}; + +const linkNodeArb = (keyPair1: KeyPair, keyPair2: KeyPair) => + gestaltLinkNodeArb(keyPair1, keyPair2).map((gestaltLinkNode) => ({ + claim: gestaltLinkNode.claim, + meta: gestaltLinkNode.meta, + })); + +const gestaltLinkIdentityArb = ( + keyPair: KeyPair, + providerId: ProviderId, + identityId: IdentityId, +): fc.Arbitrary => { + const signedClaimLinkIdentity = testsClaimsUtils.claimArb + .map((claim) => { + return { + typ: 'ClaimLinkIdentity', + iss: ids.encodeNodeId(keysUtils.publicKeyToNodeId(keyPair.publicKey)), + sub: ids.encodeProviderIdentityId([providerId, identityId]), + ...claim, + }; + }) + .map((payload) => Token.fromPayload(payload)) + .map((token) => { + token.signWithPrivateKey(keyPair); + return token.toSigned(); + }) as fc.Arbitrary>; + return fc.record({ + id: testsIdsUtils.gestaltLinkIdArb, + claim: signedClaimLinkIdentity, + meta: fc.record({ + providerIdentityClaimId: testsIdsUtils.providerIdentityClaimIdArb, + }), + }); +}; + +const linkIdentityArb = ( + keyPair: KeyPair, + providerId: ProviderId, + identityId: IdentityId, +) => + gestaltLinkIdentityArb(keyPair, providerId, identityId).map( + (gestaltLinkIdentity) => ({ + claim: gestaltLinkIdentity.claim, + meta: gestaltLinkIdentity.meta, + }), + ); + +const gestaltActionsArb = (max?: number) => + fc.dictionary( + fc.oneof(...gestaltActions.map((action) => fc.constant(action))), + fc.constant(null), + { minKeys: 1, maxKeys: max ?? gestaltActions.length }, + ); + +type GestaltGraphModel = { + matrix: Record>; + nodes: GestaltNodes; + identities: GestaltIdentities; + permissions: Record; +}; + +type GestaltGraphCommand = fc.AsyncCommand; + +/** + * Used to set vertex info and actions. + */ +class SetVertexCommand implements GestaltGraphCommand { + constructor( + public readonly vertexInfo: GestaltInfo, + public readonly actions?: GestaltActions, + ) {} + + check() { + return true; + } + + async run(model: GestaltGraphModel, real: GestaltGraph) { + const [type, data] = this.vertexInfo; + const gestaltId = + type === 'node' + ? (['node', data.nodeId] as ['node', NodeId]) + : (['identity', [data.providerId, data.identityId]] as [ + 'identity', + ProviderIdentityId, + ]); + + // Apply the mutation + await real.setVertex(this.vertexInfo); + + // Update the model + modelSetVertex(model, this.vertexInfo); + if (this.actions != null) modelSetActions(model, gestaltId, this.actions); + } + + toString() { + let gestaltInfo: any = this.vertexInfo; + if (this.vertexInfo[0] === 'node') { + gestaltInfo = [ + 'node', + { nodeId: nodesUtils.encodeNodeId(this.vertexInfo[1].nodeId) }, + ]; + } + return `setVertexCommand(${JSON.stringify(gestaltInfo)}, ${JSON.stringify( + this.actions, + )})`; + } +} + +class UnsetVertexCommand implements GestaltGraphCommand { + constructor(public readonly gestaltId: GestaltId) {} + + check() { + return true; + } + + async run(model: GestaltGraphModel, real: GestaltGraph) { + const gestaltIdEncoded = gestaltsUtils.encodeGestaltId(this.gestaltId); + // Apply the mutation + await real.unsetVertex(this.gestaltId); + + // Update the model + const gestaltModelOld = getGestaltFromModel(model, this.gestaltId); + // If no gestalt then vertex didn't exist + if (gestaltModelOld == null) return; + modelUnsetVertex(model, this.gestaltId); + + // Expectations + // We need to check that if the gestalt split + const vertices: Set = new Set(); + Object.keys(gestaltModelOld.nodes).forEach((vertex) => + vertices.add(vertex as GestaltIdEncoded), + ); + Object.keys(gestaltModelOld.identities).forEach((vertex) => + vertices.add(vertex as GestaltIdEncoded), + ); + vertices.delete(gestaltIdEncoded); + let randomVertex1: GestaltIdEncoded | undefined; + for (const vertex of vertices) { + randomVertex1 = vertex; + } + // If null then there was no gestalt to begin with + if (randomVertex1 == null) return; + // Starting from the random vertex we want to delete vertices existing in the new gestalt + const gestalt1ModelNew = getGestaltFromModel( + model, + gestaltsUtils.decodeGestaltId(randomVertex1)!, + )!; + Object.keys(gestalt1ModelNew.nodes).forEach((vertex) => + vertices.delete(vertex as GestaltIdEncoded), + ); + Object.keys(gestalt1ModelNew.identities).forEach((vertex) => + vertices.delete(vertex as GestaltIdEncoded), + ); + // Whatever is left is part of a new gestalt, if empty then stop here + if (vertices.size === 0) return; + let randomVertex2: GestaltIdEncoded | undefined; + for (const vertex of vertices) { + randomVertex2 = vertex; + } + if (randomVertex2 == null) never(); + const gestalt2ModelNew = getGestaltFromModel( + model, + gestaltsUtils.decodeGestaltId(randomVertex2)!, + )!; + + // From here we can check if the two gestalts are mutually exclusive + const gestalt1New = (await real.getGestalt( + gestaltsUtils.decodeGestaltId(randomVertex1)!, + ))!; + const gestalt2New = (await real.getGestalt( + gestaltsUtils.decodeGestaltId(randomVertex2)!, + ))!; + expect(gestalt1New).toMatchObject(gestalt1ModelNew); + expect(gestalt1New).not.toMatchObject(gestalt2ModelNew); + expect(gestalt2New).not.toMatchObject(gestalt1ModelNew); + expect(gestalt2New).toMatchObject(gestalt2ModelNew); + // Permission should be removed + expect(await real.getGestaltActions(this.gestaltId)).toStrictEqual({}); + } + + toString() { + const gestaltId = + this.gestaltId[0] === 'node' + ? ['node', nodesUtils.encodeNodeId(this.gestaltId[1])] + : this.gestaltId; + return `unsetVertexCommand(${JSON.stringify(gestaltId)})`; + } +} + +class LinkNodeAndNodeCommand implements GestaltGraphCommand { + constructor( + public readonly nodeInfo1: GestaltNodeInfo, + public readonly nodeInfo2: GestaltNodeInfo, + public readonly nodeLink: Omit, + ) {} + + check() { + return true; + } + + async run(model: GestaltGraphModel, real: GestaltGraph) { + const gestaltId1: GestaltId = ['node', this.nodeInfo1.nodeId]; + const gestaltId2: GestaltId = ['node', this.nodeInfo2.nodeId]; + + // Apply the mutation + await real.linkNodeAndNode(this.nodeInfo1, this.nodeInfo2, this.nodeLink); + + // Expectations + await expectLinkBeforeModel(model, real, gestaltId1, gestaltId2); + + // Update the model + modelLink(model, ['node', this.nodeInfo1], ['node', this.nodeInfo2]); + } + + toString() { + const nodeInfo1 = { + nodeId: nodesUtils.encodeNodeId(this.nodeInfo1.nodeId), + }; + const nodeInfo2 = { + nodeId: nodesUtils.encodeNodeId(this.nodeInfo2.nodeId), + }; + // Ignoring the claim here, it's complex not really needed here + return `linkNodeAndNodeCommand(${JSON.stringify( + nodeInfo1, + )}, ${JSON.stringify(nodeInfo2)})`; + } +} + +class UnlinkNodeAndNodeCommand implements GestaltGraphCommand { + constructor( + public readonly nodeId1: NodeId, + public readonly nodeId2: NodeId, + ) {} + + check() { + return true; + } + + async run(model: GestaltGraphModel, real: GestaltGraph) { + const gestaltId1: GestaltId = ['node', this.nodeId1]; + const gestaltId2: GestaltId = ['node', this.nodeId2]; + + // Apply the mutation + await real.unlinkNodeAndNode(gestaltId1[1], gestaltId2[1]); + + // Update the model + modelUnlink(model, gestaltId1, gestaltId2); + + // Expectation + await expectUnlinkAfterModel(model, real, gestaltId1, gestaltId2); + } + + toString() { + return `unlinkNodeAndNodeCommand(${nodesUtils.encodeNodeId( + this.nodeId1, + )}, ${nodesUtils.encodeNodeId(this.nodeId2)})`; + } +} + +class LinkNodeAndIdentityCommand implements GestaltGraphCommand { + constructor( + public readonly nodeInfo: GestaltNodeInfo, + public readonly identityInfo: GestaltIdentityInfo, + public readonly identityLink: Omit, + ) {} + + check() { + return true; + } + + async run(model: GestaltGraphModel, real: GestaltGraph) { + const gestaltId1: GestaltId = ['node', this.nodeInfo.nodeId]; + const providerIdentityId: ProviderIdentityId = [ + this.identityInfo.providerId, + this.identityInfo.identityId, + ]; + const gestaltId2: GestaltId = ['identity', providerIdentityId]; + + // Apply the mutation + await real.linkNodeAndIdentity( + this.nodeInfo, + this.identityInfo, + this.identityLink, + ); + + // Expectations + await expectLinkBeforeModel(model, real, gestaltId1, gestaltId2); + + // Update the model + modelLink(model, ['node', this.nodeInfo], ['identity', this.identityInfo]); + } + + toString() { + const nodeInfo = { nodeId: this.nodeInfo.nodeId }; + // Ignoring the claim here, it's complex not really needed here + return `linkNodeAndIdentityCommand(${JSON.stringify( + nodeInfo, + )}, ${JSON.stringify(this.identityInfo)})`; + } +} + +class UnlinkNodeAndIdentityCommand implements GestaltGraphCommand { + constructor( + public readonly nodeId: NodeId, + public readonly providerIdentityId: ProviderIdentityId, + ) {} + + check() { + return true; + } + + async run(model: GestaltGraphModel, real: GestaltGraph) { + const gestaltId1: GestaltId = ['node', this.nodeId]; + const gestaltId2: GestaltId = ['identity', this.providerIdentityId]; + + // Apply the mutation + await real.unlinkNodeAndIdentity(gestaltId1[1], gestaltId2[1]); + + // Update the model + modelUnlink(model, gestaltId1, gestaltId2); + + // Expectation + await expectUnlinkAfterModel(model, real, gestaltId1, gestaltId2); + } + + toString() { + return `unlinkNodeAndIdentityCommand(${nodesUtils.encodeNodeId( + this.nodeId, + )}, ${JSON.stringify(this.providerIdentityId)})`; + } +} + +class LinkVertexAndVertexCommand implements GestaltGraphCommand { + constructor( + public readonly nodeInfo: ['node', GestaltNodeInfo], + public readonly vertexInfo: GestaltInfo, + public readonly gestaltLink: GestaltLink, + ) {} + + check() { + return true; + } + + async run(model: GestaltGraphModel, real: GestaltGraph) { + const gestaltId1: GestaltId = ['node', this.nodeInfo[1].nodeId]; + const [type, data] = this.vertexInfo; + const gestaltId2: GestaltId = + type === 'node' + ? ['node', data.nodeId] + : ['identity', [data.providerId, data.identityId]]; + + // Apply the mutation + if (type === 'node') { + await real.linkVertexAndVertex( + this.nodeInfo, + this.vertexInfo, + this.gestaltLink as ['node', GestaltLinkNode], + ); + } else { + await real.linkVertexAndVertex( + this.nodeInfo, + this.vertexInfo, + this.gestaltLink as ['identity', GestaltLinkIdentity], + ); + } + + // Expectation + await expectLinkBeforeModel(model, real, gestaltId1, gestaltId2); + + // Update the model + modelLink(model, this.nodeInfo, this.vertexInfo); + } + + toString() { + const nodeId1 = this.nodeInfo[1].nodeId; + const nodeInfo = ['node', { nodeId: nodesUtils.encodeNodeId(nodeId1) }]; + let vertexInfo = this.vertexInfo; + if (this.vertexInfo[0] === 'node') { + vertexInfo = ['node', { nodeId: this.vertexInfo[1].nodeId }]; + } + // Ignoring the claim here, it's complex not really needed here + return `linkVertexAndVertexCommand(${JSON.stringify( + nodeInfo, + )}, ${JSON.stringify(vertexInfo)})`; + } +} + +class UnlinkVertexAndVertexCommand implements GestaltGraphCommand { + constructor( + public readonly gestaltId1: ['node', NodeId], + public readonly gestaltId2: GestaltId, + ) {} + + check() { + return true; + } + + async run(model: GestaltGraphModel, real: GestaltGraph) { + // Apply the mutation + if (this.gestaltId2[0] === 'node') { + await real.unlinkVertexAndVertex(this.gestaltId1, this.gestaltId2); + } else { + await real.unlinkVertexAndVertex(this.gestaltId1, this.gestaltId2); + } + + // Update the model + modelUnlink(model, this.gestaltId1, this.gestaltId2); + + // Expectation + await expectUnlinkAfterModel(model, real, this.gestaltId1, this.gestaltId2); + } + + toString() { + const gestaltId1 = ['node', nodesUtils.encodeNodeId(this.gestaltId1[1])]; + const gestaltId2 = + this.gestaltId2[0] === 'node' + ? ['node', nodesUtils.encodeNodeId(this.gestaltId1[1])] + : this.gestaltId2; + return `unlinkVertexAndVertexCommand(${JSON.stringify( + gestaltId1, + )}, ${JSON.stringify(gestaltId2)})`; + } +} + +async function expectLinkBeforeModel( + model: GestaltGraphModel, + real: GestaltGraph, + gestaltId1: GestaltId, + gestaltId2: GestaltId, +): Promise { + // Getting gestalts from model + const gestalt1Old = getGestaltFromModel(model, gestaltId1) ?? {}; + const gestalt2Old = getGestaltFromModel(model, gestaltId2) ?? {}; + const gestalt1ActionsOld = await real.getGestaltActions(gestaltId1); + const gestalt2ActionsOld = await real.getGestaltActions(gestaltId2); + const gestaltNew = (await real.getGestalt(gestaltId1))!; + // We want to do the following checks + // 1. the gestaltNew must be a union of gestalt1 and gestalt2. + expect(gestaltNew).toMatchObject(gestalt1Old); + expect(gestaltNew).toMatchObject(gestalt2Old); + // 2. check if the resulting permissions are the union of the gestalt1 and gestalt2 permissions. + const gestalt1ActionsNew = await real.getGestaltActions(gestaltId1); + const gestalt2ActionsNew = await real.getGestaltActions(gestaltId2); + // New permissions are a union of the old ones + expect(gestalt1ActionsNew).toMatchObject(gestalt1ActionsOld); + expect(gestalt1ActionsNew).toMatchObject(gestalt2ActionsOld); + expect(gestalt2ActionsNew).toMatchObject(gestalt1ActionsOld); + expect(gestalt2ActionsNew).toMatchObject(gestalt2ActionsOld); + // 3. Check that the gestalt actions are the same for every vertex of the gestaltNew + const keys = [ + ...Object.keys(gestaltNew.nodes), + ...Object.keys(gestaltNew.identities), + ]; + for (const gestaltIdEncoded of keys) { + const gestaltId = gestaltsUtils.decodeGestaltId(gestaltIdEncoded)!; + const actions = await real.getGestaltActions(gestaltId); + expect(actions).toStrictEqual(gestalt1ActionsNew); + } +} + +async function expectUnlinkAfterModel( + model: GestaltGraphModel, + real: GestaltGraph, + gestaltId1: GestaltId, + gestaltId2: GestaltId, +) { + // If either gestalt is missing then the link never existed + const gestalt1New = await real.getGestalt(gestaltId1); + if (gestalt1New == null) return; + const gestalt2New = await real.getGestalt(gestaltId2); + if (gestalt2New == null) return; + const gestalt1ModelNew = getGestaltFromModel(model, gestaltId1) ?? {}; + const gestalt2ModelNew = getGestaltFromModel(model, gestaltId2) ?? {}; + expect(gestalt1New).toMatchObject(gestalt1ModelNew); + expect(gestalt2New).toMatchObject(gestalt2ModelNew); + if (gestalt2New.nodes[gestaltsUtils.encodeGestaltId(gestaltId1)] == null) { + // If they are separate gestalts then they should be mutually exclusive + if (gestalt2ModelNew != null) { + expect(gestalt1New).not.toMatchObject(gestalt2ModelNew); + } + if (gestalt1ModelNew != null) { + expect(gestalt2New).not.toMatchObject(gestalt1ModelNew); + } + } +} + +function gestaltInfoToId( + gestaltInfo: ['node', GestaltNodeInfo], +): ['node', NodeId]; +function gestaltInfoToId( + gestaltInfo: ['identity', GestaltIdentityInfo], +): ['identity', ProviderIdentityId]; +function gestaltInfoToId(gestaltInfo: GestaltInfo): GestaltId; +function gestaltInfoToId(gestaltInfo: GestaltInfo): GestaltId { + if (gestaltInfo[0] === 'node') { + return ['node', gestaltInfo[1].nodeId]; + } else { + return ['identity', [gestaltInfo[1].providerId, gestaltInfo[1].identityId]]; + } +} + +function modelSetVertex( + model: GestaltGraphModel, + gestaltInfo: GestaltInfo, +): void { + if (gestaltInfo[0] === 'node') { + const gestaltIdEncoded = gestaltsUtils.encodeGestaltNodeId( + gestaltInfoToId(gestaltInfo), + ); + model.nodes[gestaltIdEncoded] = gestaltInfo[1]; + } else { + const gestaltIdEncoded = gestaltsUtils.encodeGestaltIdentityId( + gestaltInfoToId(gestaltInfo), + ); + model.identities[gestaltIdEncoded] = gestaltInfo[1]; + } +} + +function modelUnsetVertex( + model: GestaltGraphModel, + gestaltId: GestaltId, +): void { + const gestaltIdEncoded = gestaltsUtils.encodeGestaltId(gestaltId); + // Break all links for this vertex + const link = model.matrix[gestaltIdEncoded]; + if (link != null) { + for (const key of Object.keys(link)) { + const link2 = model.matrix[key]; + if (link2 != null) delete link2[gestaltIdEncoded]; + } + delete model.matrix[gestaltIdEncoded]; + } + // Remove the vertex + if (gestaltId[0] === 'node') delete model.nodes[gestaltIdEncoded]; + else delete model.identities[gestaltIdEncoded]; + // Remove permissions + delete model.permissions[gestaltIdEncoded]; +} + +function modelSetActions( + model: GestaltGraphModel, + gestaltId: GestaltId, + actions: GestaltActions, +) { + const actionsOld = + model.permissions[gestaltsUtils.encodeGestaltId(gestaltId)] ?? {}; + const actionsNew = { ...actionsOld, ...actions }; + const expectedGestalt = getGestaltFromModel(model, gestaltId); + const keys = + expectedGestalt != null + ? [ + ...Object.keys(expectedGestalt.nodes), + ...Object.keys(expectedGestalt.identities), + ] + : []; + for (const key of keys) { + model.permissions[key] = actionsNew; + } +} + +function modelLink( + model: GestaltGraphModel, + gestaltInfo1: GestaltInfo, + gestaltInfo2: GestaltInfo, +) { + const gestaltId1 = gestaltInfoToId(gestaltInfo1); + const gestaltId1Encoded = gestaltsUtils.encodeGestaltId(gestaltId1); + const gestaltId2 = gestaltInfoToId(gestaltInfo2); + const gestaltId2Encoded = gestaltsUtils.encodeGestaltId(gestaltId2); + // This needs to: + // 1. set infos for each vertex + modelSetVertex(model, gestaltInfo1); + modelSetVertex(model, gestaltInfo2); + // 2. create the link + let links1 = model.matrix[gestaltId1Encoded]; + if (links1 == null) { + links1 = {}; + model.matrix[gestaltId1Encoded] = links1; + } + let links2 = model.matrix[gestaltId2Encoded]; + if (links2 == null) { + links2 = {}; + model.matrix[gestaltId2Encoded] = links2; + } + links2[gestaltId1Encoded] = null; + links1[gestaltId2Encoded] = null; + // 3. union the permissions for every vertex in the gestalt + const permissions1Old = model.permissions[gestaltId1Encoded] ?? {}; + const permissions2Old = model.permissions[gestaltId2Encoded] ?? {}; + const permissionsNew = { ...permissions1Old, ...permissions2Old }; + modelSetActions(model, gestaltId1, permissionsNew); +} + +function modelUnlink( + model: GestaltGraphModel, + gestaltId1: GestaltId, + gestaltId2: GestaltId, +): void { + // This just needs to break the link between vertices + const gestaltId1Encoded = gestaltsUtils.encodeGestaltId(gestaltId1); + const gestaltId2Encoded = gestaltsUtils.encodeGestaltId(gestaltId2); + const links1 = model.matrix[gestaltId1Encoded]; + if (links1 != null) { + delete links1[gestaltId2Encoded]; + if (Object.keys(links1).length === 0) { + delete model.matrix[gestaltId1Encoded]; + } + } + const links2 = model.matrix[gestaltId2Encoded]; + if (links2 != null) { + delete links2[gestaltId1Encoded]; + if (Object.keys(links2).length === 0) { + delete model.matrix[gestaltId2Encoded]; + } + } +} + +function getGestaltFromModel( + model: GestaltGraphModel, + gestaltId: GestaltId, +): + | { + matrix: Record>; + nodes: Record; + identities: Record; + } + | undefined { + // This must closely mimic the Gestalt type. + // Any specific data must be replaced with a expect.anything() + const visited: Set = new Set(); + const gestaltIdEncoded = gestaltsUtils.encodeGestaltId(gestaltId); + if ( + model.nodes[gestaltIdEncoded] == null && + model.identities[gestaltIdEncoded] == null + ) { + return; + } + const queue = [gestaltsUtils.encodeGestaltId(gestaltId)]; + const gestalt: { + matrix: Record>; + nodes: Record; + identities: Record; + } = { + matrix: {}, + nodes: {}, + identities: {}, + }; + while (true) { + const gestaltIdEncoded = queue.shift(); + if (gestaltIdEncoded == null) break; + if ( + model.nodes[gestaltIdEncoded] == null && + model.identities[gestaltIdEncoded] == null + ) { + continue; + } + const [type] = gestaltsUtils.decodeGestaltId(gestaltIdEncoded)!; + if (type === 'node') { + gestalt.nodes[gestaltIdEncoded] = model.nodes[gestaltIdEncoded]; + } else { + gestalt.identities[gestaltIdEncoded] = model.identities[gestaltIdEncoded]; + } + // Checking links + + let gestaltLinks = gestalt.matrix[gestaltIdEncoded]; + if (gestaltLinks == null) { + gestaltLinks = {}; + gestalt.matrix[gestaltIdEncoded] = gestaltLinks; + } + const links = model.matrix[gestaltIdEncoded]; + if (links == null) continue; + for (const linkIdEncoded of Object.keys(links) as Array) { + // Adding the links + gestaltLinks[linkIdEncoded] = expect.anything(); + let gestaltLinks2 = gestalt.matrix[linkIdEncoded]; + if (gestaltLinks2 == null) { + gestaltLinks2 = {}; + gestalt.matrix[linkIdEncoded] = gestaltLinks2; + } + gestaltLinks2[gestaltIdEncoded] = expect.anything(); + // Adding to queue + if (!visited.has(linkIdEncoded)) queue.push(linkIdEncoded); + visited.add(linkIdEncoded); + } + } + return gestalt; +} + +export type { GestaltGraphModel, GestaltGraphCommand }; +export { + gestaltNodeInfoArb, + gestaltIdentityInfoArb, + gestaltLinkNodeArb, + gestaltLinkIdentityArb, + linkNodeArb, + linkIdentityArb, + gestaltActionsArb, + SetVertexCommand, + UnsetVertexCommand, + LinkNodeAndNodeCommand, + UnlinkNodeAndNodeCommand, + LinkNodeAndIdentityCommand, + UnlinkNodeAndIdentityCommand, + LinkVertexAndVertexCommand, + UnlinkVertexAndVertexCommand, +}; diff --git a/tests/git/utils.test.ts b/tests/git/utils.test.ts index a157ffb86..0ea8abe06 100644 --- a/tests/git/utils.test.ts +++ b/tests/git/utils.test.ts @@ -26,7 +26,7 @@ describe('Git utils', () => { path.join(os.tmpdir(), 'polykey-test-'), ); objectsPath = path.join('.git', 'objects'); - dbKey = await keysUtils.generateKey(); + dbKey = keysUtils.generateKey(); efs = await EncryptedFS.createEncryptedFS({ dbKey, dbPath: dataDir, diff --git a/tests/grpc/GRPCClient.test.ts b/tests/grpc/GRPCClient.test.ts index ff3d99335..cc3263287 100644 --- a/tests/grpc/GRPCClient.test.ts +++ b/tests/grpc/GRPCClient.test.ts @@ -46,12 +46,15 @@ describe('GRPCClient', () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: serverKeyPair.privateKey, - subjectKeyPair: { privateKey: serverKeyPair.privateKey, publicKey: serverKeyPair.publicKey } + subjectKeyPair: { + privateKey: serverKeyPair.privateKey, + publicKey: serverKeyPair.publicKey, + }, }); nodeIdServer = keysUtils.certNodeId(serverCert)!; const dbPath = path.join(dataDir, 'db'); @@ -59,7 +62,7 @@ describe('GRPCClient', () => { dbPath, logger, crypto: { - key: await keysUtils.generateKey(), + key: keysUtils.generateKey(), ops: { encrypt: async (key, plainText) => { return keysUtils.encryptWithKey( @@ -93,12 +96,15 @@ describe('GRPCClient', () => { authenticate, logger, ); - clientKeyPair = await keysUtils.generateKeyPair(); + clientKeyPair = keysUtils.generateKeyPair(); clientCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: clientKeyPair.privateKey, - subjectKeyPair: { privateKey: clientKeyPair.privateKey, publicKey: clientKeyPair.publicKey } + subjectKeyPair: { + privateKey: clientKeyPair.privateKey, + publicKey: clientKeyPair.publicKey, + }, }); }); afterAll(async () => { @@ -123,7 +129,9 @@ describe('GRPCClient', () => { port: port as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(clientKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(clientCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + clientCert, + ) as unknown as CertificatePEMChain, }, timer: timerStart(1000), logger, @@ -137,7 +145,9 @@ describe('GRPCClient', () => { port: port as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(clientKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(clientCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + clientCert, + ) as unknown as CertificatePEMChain, }, timer: timerStart(1000), logger, @@ -169,7 +179,9 @@ describe('GRPCClient', () => { port: port as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(clientKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(clientCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + clientCert, + ) as unknown as CertificatePEMChain, }, session, timer: timerStart(1000), @@ -206,7 +218,9 @@ describe('GRPCClient', () => { port: port as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(clientKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(clientCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + clientCert, + ) as unknown as CertificatePEMChain, }, timer: timerStart(1000), logger, @@ -248,7 +262,9 @@ describe('GRPCClient', () => { port: port as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(clientKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(clientCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + clientCert, + ) as unknown as CertificatePEMChain, }, session, timer: timerStart(1000), @@ -274,7 +290,9 @@ describe('GRPCClient', () => { port: port as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(clientKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(clientCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + clientCert, + ) as unknown as CertificatePEMChain, }, timer: timerStart(1000), logger, @@ -311,7 +329,9 @@ describe('GRPCClient', () => { port: port as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(clientKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(clientCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + clientCert, + ) as unknown as CertificatePEMChain, }, session, timer: timerStart(1000), @@ -335,7 +355,9 @@ describe('GRPCClient', () => { port: port as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(clientKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(clientCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + clientCert, + ) as unknown as CertificatePEMChain, }, timer: timerStart(1000), logger, @@ -369,7 +391,9 @@ describe('GRPCClient', () => { port: port as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(clientKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(clientCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + clientCert, + ) as unknown as CertificatePEMChain, }, session, timer: timerStart(1000), diff --git a/tests/grpc/GRPCServer.test.ts b/tests/grpc/GRPCServer.test.ts index ec43e9964..3a27cd432 100644 --- a/tests/grpc/GRPCServer.test.ts +++ b/tests/grpc/GRPCServer.test.ts @@ -9,7 +9,6 @@ import { DB } from '@matrixai/db'; import GRPCServer from '@/grpc/GRPCServer'; import KeyRing from '@/keys/KeyRing'; import SessionManager from '@/sessions/SessionManager'; -import * as testsUtils from '../utils'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as grpcErrors from '@/grpc/errors'; import * as grpcUtils from '@/grpc/utils'; @@ -17,6 +16,7 @@ import * as keysUtils from '@/keys/utils'; import * as clientUtils from '@/client/utils'; import * as utils from '@/utils/index'; import * as testGrpcUtils from './utils'; +import * as testsUtils from '../utils'; describe('GRPCServer', () => { const logger = new Logger('GRPCServer Test', LogLevel.WARN, [ @@ -109,19 +109,22 @@ describe('GRPCServer', () => { ], host: '127.0.0.1' as Host, port: 0 as Port, - tlsConfig: await testsUtils.createTLSConfig(await keysUtils.generateKeyPair()), + tlsConfig: await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), }); expect(typeof server.getPort()).toBe('number'); expect(server.getPort()).toBeGreaterThan(0); await server.stop(); }); test('connecting to the server securely', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: serverKeyPair.privateKey, - subjectKeyPair: { privateKey: serverKeyPair.privateKey, publicKey: serverKeyPair.publicKey } + subjectKeyPair: { + privateKey: serverKeyPair.privateKey, + publicKey: serverKeyPair.publicKey, + }, }); const server = new GRPCServer({ logger: logger, @@ -135,15 +138,18 @@ describe('GRPCServer', () => { ], host: '127.0.0.1' as Host, port: 0 as Port, - tlsConfig: await testsUtils.createTLSConfig(await keysUtils.generateKeyPair()), + tlsConfig: await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), }); const nodeIdServer = keysUtils.certNodeId(serverCert)!; - const clientKeyPair = await keysUtils.generateKeyPair(); + const clientKeyPair = keysUtils.generateKeyPair(); const clientCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: clientKeyPair.privateKey, - subjectKeyPair: { privateKey: clientKeyPair.privateKey, publicKey: clientKeyPair.publicKey } + subjectKeyPair: { + privateKey: clientKeyPair.privateKey, + publicKey: clientKeyPair.publicKey, + }, }); const client = await testGrpcUtils.openTestClientSecure( nodeIdServer, @@ -171,12 +177,15 @@ describe('GRPCServer', () => { await server.stop(); }); test('changing the private key and certificate on the fly', async () => { - const serverKeyPair1 = await keysUtils.generateKeyPair(); + const serverKeyPair1 = keysUtils.generateKeyPair(); const serverCert1 = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: serverKeyPair1.privateKey, - subjectKeyPair: { privateKey: serverKeyPair1.privateKey, publicKey: serverKeyPair1.publicKey } + subjectKeyPair: { + privateKey: serverKeyPair1.privateKey, + publicKey: serverKeyPair1.publicKey, + }, }); const server = new GRPCServer({ logger: logger, @@ -192,15 +201,20 @@ describe('GRPCServer', () => { port: 0 as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(serverKeyPair1.privateKey), - certChainPem: keysUtils.certToPEM(serverCert1) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + serverCert1, + ) as unknown as CertificatePEMChain, }, }); - const clientKeyPair = await keysUtils.generateKeyPair(); + const clientKeyPair = keysUtils.generateKeyPair(); const clientCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: clientKeyPair.privateKey, - subjectKeyPair: { privateKey: clientKeyPair.privateKey, publicKey: clientKeyPair.publicKey } + subjectKeyPair: { + privateKey: clientKeyPair.privateKey, + publicKey: clientKeyPair.publicKey, + }, }); // First client connection const nodeIdServer1 = keysUtils.certNodeId(serverCert1)!; @@ -227,16 +241,21 @@ describe('GRPCServer', () => { const m1_ = await pCall1; expect(m1_.getChallenge()).toBe(m1.getChallenge()); // Change key and certificate - const serverKeyPair2 = await keysUtils.generateKeyPair(); + const serverKeyPair2 = keysUtils.generateKeyPair(); const serverCert2 = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: serverKeyPair2.privateKey, - subjectKeyPair: { privateKey: serverKeyPair2.privateKey, publicKey: serverKeyPair2.publicKey } + subjectKeyPair: { + privateKey: serverKeyPair2.privateKey, + publicKey: serverKeyPair2.publicKey, + }, }); server.setTLSConfig({ keyPrivatePem: keysUtils.privateKeyToPEM(serverKeyPair2.privateKey), - certChainPem: keysUtils.certToPEM(serverCert2) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + serverCert2, + ) as unknown as CertificatePEMChain, }); // Still using first connection const m2 = new utilsPB.EchoMessage(); @@ -274,12 +293,15 @@ describe('GRPCServer', () => { await server.stop(); }); test('authenticated commands acquire a token', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: serverKeyPair.privateKey, - subjectKeyPair: { privateKey: serverKeyPair.privateKey, publicKey: serverKeyPair.publicKey } + subjectKeyPair: { + privateKey: serverKeyPair.privateKey, + publicKey: serverKeyPair.publicKey, + }, }); const server = new GRPCServer({ logger: logger, @@ -295,16 +317,21 @@ describe('GRPCServer', () => { port: 0 as Port, tlsConfig: { keyPrivatePem: keysUtils.privateKeyToPEM(serverKeyPair.privateKey), - certChainPem: keysUtils.certToPEM(serverCert) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + serverCert, + ) as unknown as CertificatePEMChain, }, }); const nodeIdServer = keysUtils.certNodeId(serverCert)!; - const clientKeyPair = await keysUtils.generateKeyPair(); + const clientKeyPair = keysUtils.generateKeyPair(); const clientCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: clientKeyPair.privateKey, - subjectKeyPair: { privateKey: clientKeyPair.privateKey, publicKey: clientKeyPair.publicKey } + subjectKeyPair: { + privateKey: clientKeyPair.privateKey, + publicKey: clientKeyPair.publicKey, + }, }); const client = await testGrpcUtils.openTestClientSecure( nodeIdServer, diff --git a/tests/grpc/utils.test.ts b/tests/grpc/utils.test.ts index f89819693..bb5cd9f7a 100644 --- a/tests/grpc/utils.test.ts +++ b/tests/grpc/utils.test.ts @@ -416,7 +416,7 @@ describe('GRPC utils', () => { }); test('serialising and deserialising Polykey errors', async () => { const timestamp = new Date(); - const error = new errors.ErrorPolykey('test error', { + const error = new errors.ErrorPolykey('test error', { timestamp, data: { int: 1, @@ -532,7 +532,7 @@ describe('GRPC utils', () => { }); test('serialising and deserialising sensitive errors', async () => { const timestamp = new Date(); - const error = new errors.ErrorPolykey('test error', { + const error = new errors.ErrorPolykey('test error', { timestamp, data: { int: 1, diff --git a/tests/identities/IdentitiesManager.test.ts b/tests/identities/IdentitiesManager.test.ts index 3ac03d608..f16ebf721 100644 --- a/tests/identities/IdentitiesManager.test.ts +++ b/tests/identities/IdentitiesManager.test.ts @@ -3,14 +3,15 @@ import type { IdentityId, ProviderToken, IdentityData, + IdentitySignedClaim, } from '@/identities/types'; -import type { NodeId } from '@/ids/types'; -import type { Claim, ClaimData, SignatureData } from '@/claims/types'; -import type { IdentityClaim } from '@/identities/types'; import type { Key } from '@/keys/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; import os from 'os'; import path from 'path'; import fs from 'fs'; +import { testProp } from '@fast-check/jest'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { IdentitiesManager, providers } from '@/identities'; @@ -18,13 +19,23 @@ import * as identitiesErrors from '@/identities/errors'; import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; import * as utils from '@/utils/index'; +import KeyRing from '@/keys/KeyRing'; +import Sigchain from '@/sigchain/Sigchain'; +import { encodeProviderIdentityId } from '@/ids/index'; +import Token from '@/tokens/Token'; +import * as identitiesTestUtils from './utils'; import TestProvider from './TestProvider'; +import * as claimsTestUtils from '../claims/utils'; +import * as keysTestUtils from '../keys/utils'; import * as testNodesUtils from '../nodes/utils'; describe('IdentitiesManager', () => { const logger = new Logger('IdentitiesManager Test', LogLevel.WARN, [ new StreamHandler(), ]); + const dummyKeyRing = {} as KeyRing; + const dummySigchain = {} as Sigchain; + const dummyGestaltGraph = {} as GestaltGraph; let dataDir: string; let db: DB; beforeEach(async () => { @@ -36,7 +47,7 @@ describe('IdentitiesManager', () => { dbPath, logger, crypto: { - key: await keysUtils.generateKey(), + key: keysUtils.generateKey(), ops: { encrypt: async (key, plainText) => { return keysUtils.encryptWithKey( @@ -66,6 +77,9 @@ describe('IdentitiesManager', () => { test('IdentitiesManager readiness', async () => { const identitiesManager = await IdentitiesManager.createIdentitiesManager({ db, + keyRing: dummyKeyRing, + gestaltGraph: dummyGestaltGraph, + sigchain: dummySigchain, logger, }); await expect(async () => { @@ -85,84 +99,115 @@ describe('IdentitiesManager', () => { await identitiesManager.getTokens('abc' as ProviderId); }).rejects.toThrow(identitiesErrors.ErrorIdentitiesManagerNotRunning); }); - test('get, set and unset tokens', async () => { - const identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - logger, - }); - const providerId = 'test-provider' as ProviderId; - const identityId = 'test-user' as IdentityId; - const providerToken = { - accessToken: 'abc', - }; - await identitiesManager.putToken(providerId, identityId, providerToken); - const providerToken_ = await identitiesManager.getToken(providerId, identityId); - expect(providerToken).toStrictEqual(providerToken_); - await identitiesManager.delToken(providerId, identityId); - await identitiesManager.delToken(providerId, identityId); - const providerToken__ = await identitiesManager.getToken( - providerId, - identityId, - ); - expect(providerToken__).toBeUndefined(); - await identitiesManager.stop(); - }); - test('start and stop preserves state', async () => { - // FIXME, save some actual state to check. - let identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - logger, - }); - const providerId = 'test-provider' as ProviderId; - const identityId = 'test-user' as IdentityId; - const providerToken = { - accessToken: 'abc', - }; - await identitiesManager.putToken(providerId, identityId, providerToken); - const testProvider = new TestProvider(); - identitiesManager.registerProvider(testProvider); - await identitiesManager.stop(); + testProp( + 'get, set and unset tokens', + [identitiesTestUtils.identitiyIdArb, identitiesTestUtils.providerTokenArb], + async (identityId, providerToken) => { + const identitiesManager = await IdentitiesManager.createIdentitiesManager( + { + db, + keyRing: dummyKeyRing, + gestaltGraph: dummyGestaltGraph, + sigchain: dummySigchain, + logger, + fresh: true, + }, + ); + const providerId = 'test-provider' as ProviderId; + await identitiesManager.putToken(providerId, identityId, providerToken); + const providerToken_ = await identitiesManager.getToken( + providerId, + identityId, + ); + expect(providerToken).toStrictEqual(providerToken_); + await identitiesManager.delToken(providerId, identityId); + await identitiesManager.delToken(providerId, identityId); + const providerToken__ = await identitiesManager.getToken( + providerId, + identityId, + ); + expect(providerToken__).toBeUndefined(); + await identitiesManager.stop(); + }, + ); + testProp( + 'start and stop preserves state', + [identitiesTestUtils.identitiyIdArb, identitiesTestUtils.providerTokenArb], + async (identityId, providerToken) => { + let identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + keyRing: dummyKeyRing, + gestaltGraph: dummyGestaltGraph, + sigchain: dummySigchain, + logger, + fresh: true, + }); + const providerId = 'test-provider' as ProviderId; + await identitiesManager.putToken(providerId, identityId, providerToken); + const testProvider = new TestProvider(); + identitiesManager.registerProvider(testProvider); + await identitiesManager.stop(); - identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - logger, - }); - identitiesManager.registerProvider(testProvider); - const providerToken_ = await identitiesManager.getToken(providerId, identityId); - expect(providerToken).toStrictEqual(providerToken_); - expect(identitiesManager.getProviders()).toStrictEqual({ - [testProvider.id]: testProvider, - }); - await identitiesManager.stop(); - }); - test('fresh start deletes all state', async () => { - let identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - logger, - }); - const providerId = 'test-provider' as ProviderId; - const identityId = 'test-user' as IdentityId; - const providerToken = { - accessToken: 'abc', - }; - await identitiesManager.putToken(providerId, identityId, providerToken); - const testProvider = new TestProvider(); - identitiesManager.registerProvider(testProvider); - await identitiesManager.stop(); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + keyRing: dummyKeyRing, + gestaltGraph: dummyGestaltGraph, + sigchain: dummySigchain, + logger, + }); + identitiesManager.registerProvider(testProvider); + const providerToken_ = await identitiesManager.getToken( + providerId, + identityId, + ); + expect(providerToken).toStrictEqual(providerToken_); + expect(identitiesManager.getProviders()).toStrictEqual({ + [testProvider.id]: testProvider, + }); + await identitiesManager.stop(); + }, + ); + testProp( + 'fresh start deletes all state', + [identitiesTestUtils.identitiyIdArb, identitiesTestUtils.providerTokenArb], + async (identityId, providerToken) => { + let identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + keyRing: dummyKeyRing, + gestaltGraph: dummyGestaltGraph, + sigchain: dummySigchain, + logger, + fresh: true, + }); + const providerId = 'test-provider' as ProviderId; + await identitiesManager.putToken(providerId, identityId, providerToken); + const testProvider = new TestProvider(); + identitiesManager.registerProvider(testProvider); + await identitiesManager.stop(); - identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - logger, - fresh: true, - }); - const providerToken_ = await identitiesManager.getToken(providerId, identityId); - expect(providerToken_).toBeUndefined(); - expect(identitiesManager.getProviders()).toStrictEqual({}); - await identitiesManager.stop(); - }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + keyRing: dummyKeyRing, + gestaltGraph: dummyGestaltGraph, + sigchain: dummySigchain, + logger, + fresh: true, + }); + const providerToken_ = await identitiesManager.getToken( + providerId, + identityId, + ); + expect(providerToken_).toBeUndefined(); + expect(identitiesManager.getProviders()).toStrictEqual({}); + await identitiesManager.stop(); + }, + ); test('register and unregister providers', async () => { const identitiesManager = await IdentitiesManager.createIdentitiesManager({ db, + keyRing: dummyKeyRing, + gestaltGraph: dummyGestaltGraph, + sigchain: dummySigchain, logger, }); const testProvider = new TestProvider(); @@ -187,91 +232,136 @@ describe('IdentitiesManager', () => { expect(ps).toStrictEqual({}); await identitiesManager.stop(); }); - test('using TestProvider', async () => { - const identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - logger, - }); - const testProvider = new TestProvider(); - identitiesManager.registerProvider(testProvider); - // We are going to run authenticate - const authProcess = testProvider.authenticate(); - const result1 = await authProcess.next(); - // The test provider will provider a dummy authcode - expect(result1.value).toBeDefined(); - expect(typeof result1.value).toBe('object'); - expect(result1.done).toBe(false); - // This is when we have completed it - const result2 = await authProcess.next(); - expect(result2.value).toBeDefined(); - expect(result2.done).toBe(true); - const identityId = result2.value as IdentityId; - const providerToken = (await testProvider.getToken(identityId)) as ProviderToken; - expect(providerToken).toBeDefined(); - const identityId_ = await testProvider.getIdentityId(providerToken); - expect(identityId).toBe(identityId_); - const authIdentityIds = await testProvider.getAuthIdentityIds(); - expect(authIdentityIds).toContain(identityId); - const identityData = await testProvider.getIdentityData( - identityId, - identityId, - ); - expect(identityData).toBeDefined(); - expect(identityData).toHaveProperty('providerId', testProvider.id); - expect(identityData).toHaveProperty('identityId', identityId); - // Give the provider a connected identity to discover - testProvider.users['some-user'] = {}; - testProvider.users[identityId].connected = ['some-user']; - const identityDatas: Array = []; - for await (const identityData_ of testProvider.getConnectedIdentityDatas( - identityId, - )) { - identityDatas.push(identityData_); - } - expect(identityDatas).toHaveLength(1); - expect(identityDatas).not.toContainEqual(identityData); - // Now publish a claim - const nodeIdSome = testNodesUtils.generateRandomNodeId(); - const nodeIdSomeEncoded = nodesUtils.encodeNodeId(nodeIdSome); - const signatures: Record = {}; - signatures[nodeIdSome] = { - signature: 'examplesignature', - header: { - alg: 'RS256', - kid: nodeIdSomeEncoded, - }, - }; - const rawClaim: Claim = { - payload: { - hPrev: null, - seq: 1, + testProp( + 'using TestProvider', + [claimsTestUtils.claimArb, keysTestUtils.privateKeyArb], + async (claim, privateKey) => { + const identitiesManager = await IdentitiesManager.createIdentitiesManager( + { + db, + keyRing: dummyKeyRing, + gestaltGraph: dummyGestaltGraph, + sigchain: dummySigchain, + logger, + fresh: true, + }, + ); + const testProvider = new TestProvider(); + identitiesManager.registerProvider(testProvider); + // We are going to run authenticate + const authProcess = testProvider.authenticate(); + const result1 = await authProcess.next(); + // The test provider will provider a dummy authcode + expect(result1.value).toBeDefined(); + expect(typeof result1.value).toBe('object'); + expect(result1.done).toBe(false); + // This is when we have completed it + const result2 = await authProcess.next(); + expect(result2.value).toBeDefined(); + expect(result2.done).toBe(true); + const identityId = result2.value as IdentityId; + const providerToken = (await testProvider.getToken( + identityId, + )) as ProviderToken; + expect(providerToken).toBeDefined(); + const identityId_ = await testProvider.getIdentityId(providerToken); + expect(identityId).toBe(identityId_); + const authIdentityIds = await testProvider.getAuthIdentityIds(); + expect(authIdentityIds).toContain(identityId); + const identityData = await testProvider.getIdentityData( + identityId, + identityId, + ); + expect(identityData).toBeDefined(); + expect(identityData).toHaveProperty('providerId', testProvider.id); + expect(identityData).toHaveProperty('identityId', identityId); + // Give the provider a connected identity to discover + testProvider.users['some-user'] = {}; + testProvider.users[identityId].connected = ['some-user']; + const identityDatas: Array = []; + for await (const identityData_ of testProvider.getConnectedIdentityDatas( + identityId, + )) { + identityDatas.push(identityData_); + } + expect(identityDatas).toHaveLength(1); + expect(identityDatas).not.toContainEqual(identityData); + // Now publish a claim + const nodeIdSome = testNodesUtils.generateRandomNodeId(); + const claimPayload: ClaimLinkIdentity = { + ...claim, + typ: 'ClaimLinkIdentity', iat: Math.floor(Date.now() / 1000), - data: { - type: 'identity', - node: nodesUtils.encodeNodeId(nodeIdSome), - provider: testProvider.id, - identity: identityId, - } as ClaimData, - }, - signatures: signatures, - }; - const publishedClaim = await testProvider.publishClaim( - identityId, - rawClaim, - ); - expect(publishedClaim).toBeDefined(); - // PublishedClaim will contain 2 extra metadata fields: URL and id - expect(publishedClaim).toMatchObject(rawClaim); - const publishedClaim_ = await testProvider.getClaim( - identityId, - publishedClaim.id, - ); - expect(publishedClaim).toStrictEqual(publishedClaim_); - const publishedClaims: Array = []; - for await (const claim of testProvider.getClaims(identityId, identityId)) { - publishedClaims.push(claim); - } - expect(publishedClaims).toContainEqual(publishedClaim); - await identitiesManager.stop(); - }); + iss: nodesUtils.encodeNodeId(nodeIdSome), + sub: encodeProviderIdentityId([testProvider.id, identityId]), + seq: 1, + }; + const claimToken = Token.fromPayload(claimPayload); + claimToken.signWithPrivateKey(privateKey); + + const publishedClaim = await testProvider.publishClaim( + identityId, + claimToken.toSigned(), + ); + expect(publishedClaim).toBeDefined(); + // PublishedClaim will contain 2 extra metadata fields: URL and id + expect(publishedClaim.claim.payload).toMatchObject(claimPayload); + const publishedClaim_ = await testProvider.getClaim( + identityId, + publishedClaim.id, + ); + expect(publishedClaim).toMatchObject(publishedClaim_!); + const publishedClaims: Array = []; + for await (const claim of testProvider.getClaims( + identityId, + identityId, + )) { + publishedClaims.push(claim); + } + expect(publishedClaims).toContainEqual(publishedClaim); + await identitiesManager.stop(); + }, + ); + testProp( + 'handleClaimIdentity', + [identitiesTestUtils.identitiyIdArb, identitiesTestUtils.providerTokenArb], + async (identitiyId, providerToken) => { + const keyRing = await KeyRing.createKeyRing({ + password: 'password', + keysPath: path.join(dataDir, 'keys'), + logger, + fresh: true, + strictMemoryLock: false, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + }); + const sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger, + fresh: true, + }); + const mockedLinkNodeAndIdentity = jest.fn(); + const identitiesManager = await IdentitiesManager.createIdentitiesManager( + { + db, + keyRing, + gestaltGraph: { + linkNodeAndIdentity: mockedLinkNodeAndIdentity, + } as unknown as GestaltGraph, + sigchain, + logger, + fresh: true, + }, + ); + const providerId = 'test-provider' as ProviderId; + const testProvider = new TestProvider(); + identitiesManager.registerProvider(testProvider); + await identitiesManager.putToken(providerId, identitiyId, providerToken); + await identitiesManager.handleClaimIdentity(providerId, identitiyId); + // Gestalt graph `linkNodeAndIdentity` should've been called + expect(mockedLinkNodeAndIdentity).toHaveBeenCalled(); + }, + { numRuns: 1 }, + ); }); diff --git a/tests/identities/TestProvider.ts b/tests/identities/TestProvider.ts index 132a4e0df..85208cb36 100644 --- a/tests/identities/TestProvider.ts +++ b/tests/identities/TestProvider.ts @@ -6,12 +6,16 @@ import type { IdentityData, ProviderAuthenticateRequest, } from '@/identities/types'; +import type { + IdentitySignedClaim, + ProviderIdentityClaimId, +} from '@/identities/types'; +import type { SignedClaim } from '@/claims/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; import { Provider } from '@/identities'; import * as identitiesUtils from '@/identities/utils'; import * as identitiesErrors from '@/identities/errors'; -import { IdentitySignedClaim, ProviderIdentityClaimId } from '@/identities/types'; -import { SignedClaim } from '@/claims/types'; -import { ClaimLinkIdentity } from '@/claims/payloads/index'; +import * as tokenUtils from '@/tokens/utils'; class TestProvider extends Provider { public readonly id: ProviderId; @@ -19,10 +23,7 @@ class TestProvider extends Provider { public linkIdCounter: number = 0; public users: Record; public links: Record; - protected userLinks: Record< - IdentityId, - Array - >; + protected userLinks: Record>; protected userTokens: Record; public constructor(providerId: ProviderId = 'test-provider' as ProviderId) { @@ -70,7 +71,9 @@ class TestProvider extends Provider { return Object.keys(providerTokens) as Array; } - public async getIdentityId(providerToken: ProviderToken): Promise { + public async getIdentityId( + providerToken: ProviderToken, + ): Promise { providerToken = await this.checkToken(providerToken); return this.userTokens[providerToken.accessToken]; } @@ -140,16 +143,17 @@ class TestProvider extends Provider { authIdentityId: IdentityId, identityClaim: SignedClaim, ): Promise { - let providerToken = await this.getToken(authIdentityId); + const providerToken = await this.getToken(authIdentityId); if (!providerToken) { throw new identitiesErrors.ErrorProviderUnauthenticated( `${authIdentityId} has not been authenticated`, ); } - providerToken = await this.checkToken(providerToken, authIdentityId); + await this.checkToken(providerToken, authIdentityId); const linkId = this.linkIdCounter.toString() as ProviderIdentityClaimId; this.linkIdCounter++; - this.links[linkId] = JSON.stringify(identityClaim); + const identityClainEncoded = tokenUtils.generateSignedToken(identityClaim); + this.links[linkId] = JSON.stringify(identityClainEncoded); this.userLinks[authIdentityId] = this.userLinks[authIdentityId] ? this.userLinks[authIdentityId] : []; @@ -166,13 +170,13 @@ class TestProvider extends Provider { authIdentityId: IdentityId, claimId: ProviderIdentityClaimId, ): Promise { - let providerToken = await this.getToken(authIdentityId); + const providerToken = await this.getToken(authIdentityId); if (!providerToken) { throw new identitiesErrors.ErrorProviderUnauthenticated( `${authIdentityId} has not been authenticated`, ); } - providerToken = await this.checkToken(providerToken, authIdentityId); + await this.checkToken(providerToken, authIdentityId); const linkClaimData = this.links[claimId]; if (!linkClaimData) { return; @@ -192,20 +196,17 @@ class TestProvider extends Provider { authIdentityId: IdentityId, identityId: IdentityId, ): AsyncGenerator { - let providerToken = await this.getToken(authIdentityId); + const providerToken = await this.getToken(authIdentityId); if (!providerToken) { throw new identitiesErrors.ErrorProviderUnauthenticated( `${authIdentityId} has not been authenticated`, ); } - providerToken = await this.checkToken(providerToken, authIdentityId); + await this.checkToken(providerToken, authIdentityId); const claimIds = this.userLinks[identityId] ?? []; for (const claimId of claimIds) { - const claimInfo = await this.getClaim( - authIdentityId, - claimId, - ); - if (claimInfo) { + const claimInfo = await this.getClaim(authIdentityId, claimId); + if (claimInfo != null) { yield claimInfo; } } diff --git a/tests/identities/utils.ts b/tests/identities/utils.ts new file mode 100644 index 000000000..c9db50d2f --- /dev/null +++ b/tests/identities/utils.ts @@ -0,0 +1,18 @@ +import type { IdentityId, ProviderId } from '@/ids'; +import type { ProviderToken } from '@/identities/types'; +import { fc } from '@fast-check/jest'; + +const providerTokenArb = fc + .record({ + accessToken: fc.string({ minLength: 10, maxLength: 32 }), + refreshToken: fc.string({ minLength: 0, maxLength: 32 }), + accessTokenExpiresIn: fc.integer(), + refreshTokenExpiresIn: fc.integer(), + }) + .map((item) => item as ProviderToken); + +const identitiyIdArb = fc.string().map((item) => item as IdentityId); + +const providerIdArb = fc.string().map((item) => item as ProviderId); + +export { providerTokenArb, identitiyIdArb, providerIdArb }; diff --git a/tests/ids/utils.ts b/tests/ids/utils.ts index ae6006ea5..0c5e64ec5 100644 --- a/tests/ids/utils.ts +++ b/tests/ids/utils.ts @@ -4,28 +4,37 @@ import type { CertId, ProviderId, IdentityId, + VaultId, + GestaltLinkId, + ProviderIdentityClaimId, } from '@/ids/types'; import { fc } from '@fast-check/jest'; import { IdInternal } from '@matrixai/id'; import * as ids from '@/ids'; -const nodeIdArb = fc.uint8Array({ minLength: 32, maxLength: 32 }).map( - IdInternal.create -) as fc.Arbitrary; +const nodeIdArb = fc + .uint8Array({ minLength: 32, maxLength: 32 }) + .map(IdInternal.create) as fc.Arbitrary; + +const nodeIdStringArb = nodeIdArb.map((id) => id.toString()); const nodeIdEncodedArb = nodeIdArb.map(ids.encodeNodeId); -const claimIdArb = fc.uint8Array({ - minLength: 16, - maxLength: 16, -}).map(IdInternal.create) as fc.Arbitrary; +const claimIdArb = fc + .uint8Array({ + minLength: 16, + maxLength: 16, + }) + .map(IdInternal.create) as fc.Arbitrary; const claimIdEncodedArb = claimIdArb.map(ids.encodeClaimId); -const certIdArb = fc.uint8Array({ - minLength: 16, - maxLength: 16, -}).map(IdInternal.create) as fc.Arbitrary; +const certIdArb = fc + .uint8Array({ + minLength: 16, + maxLength: 16, + }) + .map(IdInternal.create) as fc.Arbitrary; const certIdEncodedArb = certIdArb.map(ids.encodeCertId); @@ -34,7 +43,7 @@ const providerIdArb = fc.constantFrom( 'facebook.com', 'twitter.com', 'google.com', - 'linkedin.com' + 'linkedin.com', ) as fc.Arbitrary; const identityIdArb = fc.string() as fc.Arbitrary; @@ -42,11 +51,33 @@ const identityIdArb = fc.string() as fc.Arbitrary; const providerIdentityIdArb = fc.tuple(providerIdArb, identityIdArb); const providerIdentityIdEncodedArb = providerIdentityIdArb.map( - ids.encodeProviderIdentityId + ids.encodeProviderIdentityId, ); +const providerIdentityClaimIdArb = + fc.string() as fc.Arbitrary; + +const vaultIdArb = fc + .uint8Array({ + minLength: 16, + maxLength: 16, + }) + .map(IdInternal.create) as fc.Arbitrary; + +const vaultIdStringArb = vaultIdArb.map((id) => id.toString()); + +const vaultIdEncodedArb = vaultIdArb.map(ids.encodeVaultId); + +const gestaltLinkIdArb = fc + .uint8Array({ + minLength: 16, + maxLength: 16, + }) + .map(IdInternal.create) as fc.Arbitrary; + export { nodeIdArb, + nodeIdStringArb, nodeIdEncodedArb, claimIdArb, claimIdEncodedArb, @@ -56,4 +87,9 @@ export { identityIdArb, providerIdentityIdArb, providerIdentityIdEncodedArb, + providerIdentityClaimIdArb, + vaultIdArb, + vaultIdStringArb, + vaultIdEncodedArb, + gestaltLinkIdArb, }; diff --git a/tests/integration/testnet/testnetConnection.test.ts b/tests/integration/testnet/testnetConnection.test.ts index a7a84dac1..9bb37f663 100644 --- a/tests/integration/testnet/testnetConnection.test.ts +++ b/tests/integration/testnet/testnetConnection.test.ts @@ -7,7 +7,6 @@ import Logger, { LogLevel, StreamHandler, formatting } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; import config from '@/config'; import * as testUtils from '../../utils'; -import { globalRootKeyPems } from '../../fixtures/globalRootKeyPems'; import { sleep } from '../../../src/utils/index'; describe.skip('testnet connection', () => { @@ -49,7 +48,6 @@ describe.skip('testnet connection', () => { env: { PK_NODE_PATH: nodePath, PK_PASSWORD: password, - PK_ROOT_KEY: globalRootKeyPems[0], }, cwd: dataDir, }, @@ -100,7 +98,6 @@ describe.skip('testnet connection', () => { env: { PK_NODE_PATH: nodePathA, PK_PASSWORD: password, - PK_ROOT_KEY: globalRootKeyPems[0], }, cwd: dataDir, }, @@ -122,7 +119,6 @@ describe.skip('testnet connection', () => { env: { PK_NODE_PATH: nodePathB, PK_PASSWORD: password, - PK_ROOT_KEY: globalRootKeyPems[1], }, cwd: dataDir, }, @@ -242,9 +238,6 @@ describe.skip('testnet connection', () => { password, nodePath: nodePath1, seedNodes, - keysConfig: { - privateKeyPemOverride: globalRootKeyPems[1], - }, networkConfig: { // ProxyHost: localhost, agentHost: localhost, @@ -261,9 +254,6 @@ describe.skip('testnet connection', () => { password, nodePath: nodePath2, seedNodes, - keysConfig: { - privateKeyPemOverride: globalRootKeyPems[2], - }, networkConfig: { // ProxyHost: localhost, agentHost: localhost, @@ -299,7 +289,7 @@ describe.skip('testnet connection', () => { // ); // console.log('Attempting ping'); const pingResult = await agent2.nodeManager.pingNode( - agent1.keyManager.getNodeId(), + agent1.keyRing.getNodeId(), ); // Console.log(pingResult); expect(pingResult).toBe(true); diff --git a/tests/keys/CertManager.test.ts b/tests/keys/CertManager.test.ts index 917c6307f..0a1cb7ea8 100644 --- a/tests/keys/CertManager.test.ts +++ b/tests/keys/CertManager.test.ts @@ -2,7 +2,7 @@ import type { Key, Certificate, CertificatePEM, - CertificatePEMChain + CertificatePEMChain, } from '@/keys/types'; import fs from 'fs'; import os from 'os'; @@ -84,7 +84,7 @@ describe(CertManager.name, () => { keyRing, taskManager, logger, - lazy: true + lazy: true, }); await expect(async () => { await certManager.destroy(); @@ -110,8 +110,12 @@ describe(CertManager.name, () => { }); const cert = await certManager.getCurrentCert(); expect(keysUtils.certNodeId(cert)).toStrictEqual(keyRing.getNodeId()); - expect(keysUtils.certPublicKey(cert)).toStrictEqual(keyRing.keyPair.publicKey); - expect(await keysUtils.certSignedBy(cert, keyRing.keyPair.publicKey)).toBe(true); + expect(keysUtils.certPublicKey(cert)).toStrictEqual( + keyRing.keyPair.publicKey, + ); + expect(await keysUtils.certSignedBy(cert, keyRing.keyPair.publicKey)).toBe( + true, + ); expect(keysUtils.certIssuedBy(cert, cert)).toBe(true); expect(keysUtils.certNotExpiredBy(cert, new Date())).toBe(true); expect(await keysUtils.certNodeSigned(cert)).toBe(true); @@ -131,10 +135,9 @@ describe(CertManager.name, () => { certs = await certManager.getCertsChain(); const certOld = cert; const certId = keysUtils.certCertId(cert)!; - expect(keysUtils.certEqual( - (await certManager.getCert(certId))!, - cert - )).toBe(true); + expect( + keysUtils.certEqual((await certManager.getCert(certId))!, cert), + ).toBe(true); expect(certs).toHaveLength(1); expect(keysUtils.certEqual(certs[0], cert)).toBe(true); // After renewal there will be 2 certificates @@ -163,9 +166,15 @@ describe(CertManager.name, () => { ); const currentCert = keysUtils.certFromPEM(certPEM)!; const currentCertPEM = certPEM; - expect(keysUtils.certNodeId(currentCert)).toStrictEqual(keyRing.getNodeId()); - expect(keysUtils.certPublicKey(currentCert)).toStrictEqual(keyRing.keyPair.publicKey); - expect(await keysUtils.certSignedBy(currentCert, keyRing.keyPair.publicKey)).toBe(true); + expect(keysUtils.certNodeId(currentCert)).toStrictEqual( + keyRing.getNodeId(), + ); + expect(keysUtils.certPublicKey(currentCert)).toStrictEqual( + keyRing.keyPair.publicKey, + ); + expect( + await keysUtils.certSignedBy(currentCert, keyRing.keyPair.publicKey), + ).toBe(true); expect(keysUtils.certIssuedBy(currentCert, currentCert)).toBe(true); expect(keysUtils.certNotExpiredBy(currentCert, new Date())).toBe(true); expect(await keysUtils.certNodeSigned(currentCert)).toBe(true); @@ -182,12 +191,9 @@ describe(CertManager.name, () => { certChainPEM = await certManager.getCertPEMsChainPEM(); expect(certPEM).not.toStrictEqual(currentCertPEM); expect(certPEMs).toHaveLength(2); - expect( - keysUtils.certEqual( - keysUtils.certFromPEM(certPEMs[1])!, - cert - ) - ).toBe(true); + expect(keysUtils.certEqual(keysUtils.certFromPEM(certPEMs[1])!, cert)).toBe( + true, + ); expect(certChainPEM).toMatch( /-----BEGIN CERTIFICATE-----\n([A-Za-z0-9+/=\n]+)-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n([A-Za-z0-9+/=\n]+)-----END CERTIFICATE-----\n/, ); @@ -208,9 +214,15 @@ describe(CertManager.name, () => { certs = await asynciterable.toArray(certManager.getCerts()); currentCert = certs[0]; expect(certs).toHaveLength(1); - expect(keysUtils.certNodeId(currentCert)).toStrictEqual(keyRing.getNodeId()); - expect(keysUtils.certPublicKey(currentCert)).toStrictEqual(keyRing.keyPair.publicKey); - expect(await keysUtils.certSignedBy(currentCert, keyRing.keyPair.publicKey)).toBe(true); + expect(keysUtils.certNodeId(currentCert)).toStrictEqual( + keyRing.getNodeId(), + ); + expect(keysUtils.certPublicKey(currentCert)).toStrictEqual( + keyRing.keyPair.publicKey, + ); + expect( + await keysUtils.certSignedBy(currentCert, keyRing.keyPair.publicKey), + ).toBe(true); expect(keysUtils.certIssuedBy(currentCert, currentCert)).toBe(true); expect(keysUtils.certNotExpiredBy(currentCert, new Date())).toBe(true); expect(await keysUtils.certNodeSigned(currentCert)).toBe(true); @@ -229,8 +241,8 @@ describe(CertManager.name, () => { expect( await keysUtils.certSignedBy( currentCert, - keysUtils.certPublicKey(certs[1])! - ) + keysUtils.certPublicKey(certs[1])!, + ), ).toBe(true); await certManager.stop(); }); @@ -280,13 +292,19 @@ describe(CertManager.name, () => { await utils.sleep(1500); const certNew = await certMgr.getCurrentCert(); // New certificate with have a greater `CertId` - expect(keysUtils.certCertId(certNew)! > keysUtils.certCertId(certOld)!).toBe(true); + expect( + keysUtils.certCertId(certNew)! > keysUtils.certCertId(certOld)!, + ).toBe(true); // Same key pair preserves the NodeId - expect(keysUtils.certNodeId(certNew)).toStrictEqual(keysUtils.certNodeId(certOld)); + expect(keysUtils.certNodeId(certNew)).toStrictEqual( + keysUtils.certNodeId(certOld), + ); // New certificate issued by old certificate expect(keysUtils.certIssuedBy(certNew, certOld)).toBe(true); // New certificate signed by old certificate - expect(await keysUtils.certSignedBy(certNew, keysUtils.certPublicKey(certOld)!)).toBe(true); + expect( + await keysUtils.certSignedBy(certNew, keysUtils.certPublicKey(certOld)!), + ).toBe(true); // New certificate is self-signed via the node signature extension expect(await keysUtils.certNodeSigned(certNew)).toBe(true); await certMgr.stop(); @@ -295,20 +313,22 @@ describe(CertManager.name, () => { testProp( 'renewing and resetting with current key pair', [ - fc.commands( - [ - // Sleep command - fc.integer({ min: 250, max: 250 }).map( - (ms) => new testsUtilsFastCheck.SleepCommand(ms) - ), - fc.integer({ min: 0, max: 2 }).map( - (d) => new testsKeysUtils.RenewCertWithCurrentKeyPairCommand(d) + fc.commands([ + // Sleep command + fc + .integer({ min: 250, max: 250 }) + .map((ms) => new testsUtilsFastCheck.SleepCommand(ms)), + fc + .integer({ min: 0, max: 2 }) + .map( + (d) => new testsKeysUtils.RenewCertWithCurrentKeyPairCommand(d), ), - fc.integer({ min: 0, max: 3 }).map( - (d) => new testsKeysUtils.ResetCertWithCurrentKeyPairCommand(d) + fc + .integer({ min: 0, max: 3 }) + .map( + (d) => new testsKeysUtils.ResetCertWithCurrentKeyPairCommand(d), ), - ], - ), + ]), ], async (cmds) => { // Start a fresh certificate manager for each property test @@ -319,7 +339,7 @@ describe(CertManager.name, () => { taskManager, logger, lazy: true, - fresh: true + fresh: true, }); try { const model = { @@ -338,31 +358,29 @@ describe(CertManager.name, () => { }, { numRuns: 10, - } + }, ); testProp( 'renewing and resetting with new key pair', [ - fc.commands( - [ - // Sleep command - fc.integer({ min: 250, max: 250 }).map( - (ms) => new testsUtilsFastCheck.SleepCommand(ms) - ), - fc.tuple( - testsKeysUtils.passwordArb, - fc.integer({ min: 0, max: 2 }), - ).map(([p, d]) => - new testsKeysUtils.RenewCertWithNewKeyPairCommand(p, d) + fc.commands([ + // Sleep command + fc + .integer({ min: 250, max: 250 }) + .map((ms) => new testsUtilsFastCheck.SleepCommand(ms)), + fc + .tuple(testsKeysUtils.passwordArb, fc.integer({ min: 0, max: 2 })) + .map( + ([p, d]) => + new testsKeysUtils.RenewCertWithNewKeyPairCommand(p, d), ), - fc.tuple( - testsKeysUtils.passwordArb, - fc.integer({ min: 0, max: 3 }), - ).map(([p, d]) => - new testsKeysUtils.ResetCertWithNewKeyPairCommand(p, d) + fc + .tuple(testsKeysUtils.passwordArb, fc.integer({ min: 0, max: 3 })) + .map( + ([p, d]) => + new testsKeysUtils.ResetCertWithNewKeyPairCommand(p, d), ), - ], - ), + ]), ], async (cmds) => { // Start a fresh certificate manager for each property test @@ -373,7 +391,7 @@ describe(CertManager.name, () => { taskManager, logger, lazy: true, - fresh: true + fresh: true, }); try { const model = { @@ -392,28 +410,28 @@ describe(CertManager.name, () => { }, { numRuns: 10, - } + }, ); testProp( 'renewing with current and new key pair', [ - fc.commands( - [ - // Sleep command - fc.integer({ min: 250, max: 250 }).map( - (ms) => new testsUtilsFastCheck.SleepCommand(ms) - ), - fc.integer({ min: 0, max: 2 }).map( - (d) => new testsKeysUtils.RenewCertWithCurrentKeyPairCommand(d) + fc.commands([ + // Sleep command + fc + .integer({ min: 250, max: 250 }) + .map((ms) => new testsUtilsFastCheck.SleepCommand(ms)), + fc + .integer({ min: 0, max: 2 }) + .map( + (d) => new testsKeysUtils.RenewCertWithCurrentKeyPairCommand(d), ), - fc.tuple( - testsKeysUtils.passwordArb, - fc.integer({ min: 0, max: 2 }), - ).map(([p, d]) => - new testsKeysUtils.RenewCertWithNewKeyPairCommand(p, d) + fc + .tuple(testsKeysUtils.passwordArb, fc.integer({ min: 0, max: 2 })) + .map( + ([p, d]) => + new testsKeysUtils.RenewCertWithNewKeyPairCommand(p, d), ), - ], - ), + ]), ], async (cmds) => { // Start a fresh certificate manager for each property test @@ -424,7 +442,7 @@ describe(CertManager.name, () => { taskManager, logger, lazy: true, - fresh: true + fresh: true, }); try { const model = { @@ -443,28 +461,28 @@ describe(CertManager.name, () => { }, { numRuns: 10, - } + }, ); testProp( 'resetting with current and new key pair', [ - fc.commands( - [ - // Sleep command - fc.integer({ min: 250, max: 250 }).map( - (ms) => new testsUtilsFastCheck.SleepCommand(ms) - ), - fc.integer({ min: 0, max: 2 }).map( - (d) => new testsKeysUtils.ResetCertWithCurrentKeyPairCommand(d) + fc.commands([ + // Sleep command + fc + .integer({ min: 250, max: 250 }) + .map((ms) => new testsUtilsFastCheck.SleepCommand(ms)), + fc + .integer({ min: 0, max: 2 }) + .map( + (d) => new testsKeysUtils.ResetCertWithCurrentKeyPairCommand(d), ), - fc.tuple( - testsKeysUtils.passwordArb, - fc.integer({ min: 0, max: 3 }), - ).map(([p, d]) => - new testsKeysUtils.ResetCertWithNewKeyPairCommand(p, d) + fc + .tuple(testsKeysUtils.passwordArb, fc.integer({ min: 0, max: 3 })) + .map( + ([p, d]) => + new testsKeysUtils.ResetCertWithNewKeyPairCommand(p, d), ), - ], - ), + ]), ], async (cmds) => { // Start a fresh certificate manager for each property test @@ -475,7 +493,7 @@ describe(CertManager.name, () => { taskManager, logger, lazy: true, - fresh: true + fresh: true, }); try { const model = { @@ -494,37 +512,39 @@ describe(CertManager.name, () => { }, { numRuns: 10, - } + }, ); testProp( 'renewing and resetting with current and new key pair', [ - fc.commands( - [ - // Sleep command - fc.integer({ min: 250, max: 250 }).map( - (ms) => new testsUtilsFastCheck.SleepCommand(ms) - ), - fc.integer({ min: 0, max: 2 }).map( - (d) => new testsKeysUtils.RenewCertWithCurrentKeyPairCommand(d) + fc.commands([ + // Sleep command + fc + .integer({ min: 250, max: 250 }) + .map((ms) => new testsUtilsFastCheck.SleepCommand(ms)), + fc + .integer({ min: 0, max: 2 }) + .map( + (d) => new testsKeysUtils.RenewCertWithCurrentKeyPairCommand(d), ), - fc.integer({ min: 0, max: 3 }).map( - (d) => new testsKeysUtils.ResetCertWithCurrentKeyPairCommand(d) + fc + .integer({ min: 0, max: 3 }) + .map( + (d) => new testsKeysUtils.ResetCertWithCurrentKeyPairCommand(d), ), - fc.tuple( - testsKeysUtils.passwordArb, - fc.integer({ min: 0, max: 2 }), - ).map(([p, d]) => - new testsKeysUtils.RenewCertWithNewKeyPairCommand(p, d) + fc + .tuple(testsKeysUtils.passwordArb, fc.integer({ min: 0, max: 2 })) + .map( + ([p, d]) => + new testsKeysUtils.RenewCertWithNewKeyPairCommand(p, d), ), - fc.tuple( - testsKeysUtils.passwordArb, - fc.integer({ min: 0, max: 3 }), - ).map(([p, d]) => - new testsKeysUtils.ResetCertWithNewKeyPairCommand(p, d) + fc + .tuple(testsKeysUtils.passwordArb, fc.integer({ min: 0, max: 3 })) + .map( + ([p, d]) => + new testsKeysUtils.ResetCertWithNewKeyPairCommand(p, d), ), - ], - ), + ]), ], async (cmds) => { // Start a fresh certificate manager for each property test @@ -535,7 +555,7 @@ describe(CertManager.name, () => { taskManager, logger, lazy: true, - fresh: true + fresh: true, }); try { const model = { @@ -554,7 +574,7 @@ describe(CertManager.name, () => { }, { numRuns: 10, - } + }, ); }); }); diff --git a/tests/keys/KeyRing.test.ts b/tests/keys/KeyRing.test.ts index 7f0813ee6..931661017 100644 --- a/tests/keys/KeyRing.test.ts +++ b/tests/keys/KeyRing.test.ts @@ -79,19 +79,19 @@ describe(KeyRing.name, () => { password, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); const nodeId = keyRing.getNodeId(); const keyPair = { publicKey: Buffer.from(keyRing.keyPair.publicKey), privateKey: Buffer.from(keyRing.keyPair.privateKey), - secretKey: Buffer.from(keyRing.keyPair.secretKey) + secretKey: Buffer.from(keyRing.keyPair.secretKey), }; const dbKey = Buffer.from(keyRing.dbKey); expect(keyRing.recoveryCode).toBeDefined(); await keyRing.stop(); await keyRing.start({ - password + password, }); expect(keyRing.getNodeId()).toStrictEqual(nodeId); expect(keyRing.keyPair).toStrictEqual(keyPair); @@ -106,7 +106,7 @@ describe(KeyRing.name, () => { password, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); const keysPathContents1 = await fs.promises.readdir(keysPath); expect(keysPathContents1).toContain('public.jwk'); @@ -126,7 +126,7 @@ describe(KeyRing.name, () => { password, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); expect(await keyRing.checkPassword(password)).toBe(true); await keyRing.changePassword('new password'); @@ -140,7 +140,7 @@ describe(KeyRing.name, () => { password: 'first password', logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); await keyRing.changePassword('second password'); await keyRing.stop(); @@ -155,7 +155,7 @@ describe(KeyRing.name, () => { keysPath, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); }).rejects.toThrow(keysErrors.ErrorKeyPairParse); }); @@ -168,14 +168,14 @@ describe(KeyRing.name, () => { password, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); const nodeId = keyRing.getNodeId(); const recoveryCode = keyRing.recoveryCode!; const keyPair = { publicKey: Buffer.from(keyRing.keyPair.publicKey), privateKey: Buffer.from(keyRing.keyPair.privateKey), - secretKey: Buffer.from(keyRing.keyPair.secretKey) + secretKey: Buffer.from(keyRing.keyPair.secretKey), }; expect(recoveryCode).toBeDefined(); await keyRing.stop(); @@ -209,7 +209,7 @@ describe(KeyRing.name, () => { keysPath: keysPath1, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); expect(keyRing1.recoveryCode).toBe(recoveryCode); const nodeId1 = keyRing1.getNodeId(); @@ -221,7 +221,7 @@ describe(KeyRing.name, () => { keysPath: keysPath2, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); expect(keyRing2.recoveryCode).toBe(recoveryCode); const nodeId2 = keyRing2.getNodeId(); @@ -237,12 +237,12 @@ describe(KeyRing.name, () => { password, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); const keyPair = { publicKey: Buffer.from(keyRing.keyPair.publicKey), privateKey: Buffer.from(keyRing.keyPair.privateKey), - secretKey: Buffer.from(keyRing.keyPair.secretKey) + secretKey: Buffer.from(keyRing.keyPair.secretKey), }; await keyRing.rotateKeyPair('new password'); expect(keyRing.keyPair).not.toStrictEqual(keyPair); @@ -264,7 +264,7 @@ describe(KeyRing.name, () => { privateKey: keyPair.privateKey, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); expect(keyRing.keyPair).toStrictEqual(keyPair); // There cannot be a recovery code if private key was supplied @@ -279,12 +279,12 @@ describe(KeyRing.name, () => { 'newpassword', privateKeyJWK, keysUtils.passwordOpsLimits.min, - keysUtils.passwordMemLimits.min + keysUtils.passwordMemLimits.min, ); await fs.promises.writeFile( `${dataDir}/private-key.jwe`, JSON.stringify(privateKeyJWE), - 'utf-8' + 'utf-8', ); const keyRing = await KeyRing.createKeyRing({ keysPath, @@ -292,7 +292,7 @@ describe(KeyRing.name, () => { privateKeyPath: `${dataDir}/private-key.jwe`, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); expect(keyRing.keyPair).toStrictEqual(keyPair); // There cannot be a recovery code if private key was supplied @@ -308,7 +308,7 @@ describe(KeyRing.name, () => { await fs.promises.writeFile( `${dataDir}/private-key.jwk`, JSON.stringify(privateKeyJWK), - 'utf-8' + 'utf-8', ); const keyRing = await KeyRing.createKeyRing({ keysPath, @@ -316,7 +316,7 @@ describe(KeyRing.name, () => { privateKeyPath: `${dataDir}/private-key.jwk`, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); expect(keyRing.keyPair).toStrictEqual(keyPair); // There cannot be a recovery code if private key was supplied @@ -338,7 +338,7 @@ describe(KeyRing.name, () => { keysPath, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); }); afterAll(async () => { @@ -346,21 +346,28 @@ describe(KeyRing.name, () => { }); testProp( 'encrypting and decrypting with root key', - [ testsKeysUtils.bufferArb({ minLength: 0, maxLength: 1024 }), ], + [testsKeysUtils.bufferArb({ minLength: 0, maxLength: 1024 })], async (plainText) => { - const cipherText = keyRing.encrypt(keyRing.keyPair.publicKey, plainText); + const cipherText = keyRing.encrypt( + keyRing.keyPair.publicKey, + plainText, + ); const plainText_ = keyRing.decrypt(cipherText)!; expect(plainText_.equals(plainText)).toBe(true); }, ); testProp( 'signing and verifying with root key', - [ testsKeysUtils.bufferArb({ minLength: 0, maxLength: 1024 }), ], + [testsKeysUtils.bufferArb({ minLength: 0, maxLength: 1024 })], async (data) => { const signature = keyRing.sign(data); - const signed = keyRing.verify(keyRing.keyPair.publicKey, data, signature); + const signed = keyRing.verify( + keyRing.keyPair.publicKey, + data, + signature, + ); expect(signed).toBe(true); - } + }, ); }); describe('DB key', () => { @@ -371,7 +378,7 @@ describe(KeyRing.name, () => { keysPath, logger, passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min + passwordMemLimit: keysUtils.passwordMemLimits.min, }); // Make a copy of the existing DB key const dbKey = Buffer.from(keyRing.dbKey); @@ -380,5 +387,4 @@ describe(KeyRing.name, () => { await keyRing.stop(); }); }); - // WORKER MANAGER TESTS }); diff --git a/tests/keys/utils.ts b/tests/keys/utils.ts index 82e1f3f6c..d8a1d7809 100644 --- a/tests/keys/utils.ts +++ b/tests/keys/utils.ts @@ -1,5 +1,4 @@ import type { - CertId, Certificate, PrivateKey, KeyPair, @@ -108,15 +107,13 @@ const macArb = fc const passwordArb = fc.string({ minLength: 0, maxLength: 20 }).noShrink(); type CertManagerModel = { - certs: Array, + certs: Array; }; type CertManagerCommand = fc.AsyncCommand; class RenewCertWithCurrentKeyPairCommand implements CertManagerCommand { - constructor( - public readonly duration: number = 31536000, - ) {} + constructor(public readonly duration: number = 31536000) {} check() { return true; @@ -125,10 +122,7 @@ class RenewCertWithCurrentKeyPairCommand implements CertManagerCommand { async run(model: CertManagerModel, real: CertManager) { // Update the real const now = new Date(); - await real.renewCertWithCurrentKeyPair( - this.duration, - now - ); + await real.renewCertWithCurrentKeyPair(this.duration, now); // Update the model const certOld = model.certs[0]; const certNew = await real.getCurrentCert(); @@ -138,15 +132,19 @@ class RenewCertWithCurrentKeyPairCommand implements CertManagerCommand { return !x509.certNotExpiredBy(cert, now); }); model.certs = [certNew].concat( - Iterable.as(model.certs).takeWhile((cert) => { - return x509.certNotExpiredBy(cert, now); - }).toArray(), + Iterable.as(model.certs) + .takeWhile((cert) => { + return x509.certNotExpiredBy(cert, now); + }) + .toArray(), ); if (firstExpiredCert != null) { model.certs.push(firstExpiredCert); } // Check consistency - const [certNew_, certOld_] = await AsyncIterable.as(real.getCerts()).take(2).toArray(); + const [certNew_, certOld_] = await AsyncIterable.as(real.getCerts()) + .take(2) + .toArray(); // New certificate with have a greater `CertId` expect(x509.certCertId(certNew)! > x509.certCertId(certOld)!).toBe(true); // Same key pair preserves the NodeId @@ -158,7 +156,9 @@ class RenewCertWithCurrentKeyPairCommand implements CertManagerCommand { // New certificate issued by old certificate expect(x509.certIssuedBy(certNew, certOld)).toBe(true); // New certificate signed by old certificate - expect(await x509.certSignedBy(certNew, x509.certPublicKey(certOld)!)).toBe(true); + expect(await x509.certSignedBy(certNew, x509.certPublicKey(certOld)!)).toBe( + true, + ); // New certificate is self-signed via the node signature extension expect(await x509.certNodeSigned(certNew)).toBe(true); // New certificate is not expired from now and inclusive of the duration @@ -166,8 +166,8 @@ class RenewCertWithCurrentKeyPairCommand implements CertManagerCommand { expect( x509.certNotExpiredBy( certNew, - new Date(now.getTime() + this.duration * 1000) - ) + new Date(now.getTime() + this.duration * 1000), + ), ).toBe(true); expect(await real.getCertsChain()).toStrictEqual(model.certs); } @@ -190,11 +190,7 @@ class RenewCertWithNewKeyPairCommand implements CertManagerCommand { async run(model: CertManagerModel, real: CertManager) { // Update the real const now = new Date(); - await real.renewCertWithNewKeyPair( - this.password, - this.duration, - now - ); + await real.renewCertWithNewKeyPair(this.password, this.duration, now); // Update the model const certOld = model.certs[0]; const certNew = await real.getCurrentCert(); @@ -204,19 +200,25 @@ class RenewCertWithNewKeyPairCommand implements CertManagerCommand { return !x509.certNotExpiredBy(cert, now); }); model.certs = [certNew].concat( - Iterable.as(model.certs).takeWhile((cert) => { - return x509.certNotExpiredBy(cert, now); - }).toArray(), + Iterable.as(model.certs) + .takeWhile((cert) => { + return x509.certNotExpiredBy(cert, now); + }) + .toArray(), ); if (firstExpiredCert != null) { model.certs.push(firstExpiredCert); } // Check consistency - const [certNew_, certOld_] = await AsyncIterable.as(real.getCerts()).take(2).toArray(); + const [certNew_, certOld_] = await AsyncIterable.as(real.getCerts()) + .take(2) + .toArray(); // New certificate with have a greater `CertId` expect(x509.certCertId(certNew)! > x509.certCertId(certOld)!).toBe(true); // Different key pair changes the the NodeId - expect(x509.certNodeId(certNew),).not.toStrictEqual(x509.certNodeId(certOld)); + expect(x509.certNodeId(certNew)).not.toStrictEqual( + x509.certNodeId(certOld), + ); // New certificates should match expect(x509.certEqual(certNew_, certNew)).toBe(true); // Old certificate was the previous current certificate @@ -224,7 +226,9 @@ class RenewCertWithNewKeyPairCommand implements CertManagerCommand { // New certificate issued by old certificate expect(x509.certIssuedBy(certNew, certOld)).toBe(true); // New certificate signed by old certificate - expect(await x509.certSignedBy(certNew, x509.certPublicKey(certOld)!)).toBe(true); + expect(await x509.certSignedBy(certNew, x509.certPublicKey(certOld)!)).toBe( + true, + ); // New certificate is self-signed via the node signature extension expect(await x509.certNodeSigned(certNew)).toBe(true); // New certificate is not expired from now and inclusive of the duration @@ -232,8 +236,8 @@ class RenewCertWithNewKeyPairCommand implements CertManagerCommand { expect( x509.certNotExpiredBy( certNew, - new Date(now.getTime() + this.duration * 1000) - ) + new Date(now.getTime() + this.duration * 1000), + ), ).toBe(true); expect(await real.getCertsChain()).toStrictEqual(model.certs); } @@ -244,9 +248,7 @@ class RenewCertWithNewKeyPairCommand implements CertManagerCommand { } class ResetCertWithCurrentKeyPairCommand implements CertManagerCommand { - constructor( - public readonly duration: number = 31536000, - ) {} + constructor(public readonly duration: number = 31536000) {} check() { return true; @@ -255,19 +257,18 @@ class ResetCertWithCurrentKeyPairCommand implements CertManagerCommand { async run(model: CertManagerModel, real: CertManager) { // Update the real const now = new Date(); - await real.resetCertWithCurrentKeyPair( - this.duration, - now - ); + await real.resetCertWithCurrentKeyPair(this.duration, now); // Update the model const certOld = model.certs[0]; const certNew = await real.getCurrentCert(); model.certs = [certNew]; - const [certNew_, certOld_] = await AsyncIterable.as(real.getCerts()).take(2).toArray(); + const [certNew_, certOld_] = await AsyncIterable.as(real.getCerts()) + .take(2) + .toArray(); // New certificate with have a greater `CertId` expect(x509.certCertId(certNew)! > x509.certCertId(certOld)!).toBe(true); // Different key pair changes the the NodeId - expect(x509.certNodeId(certNew),).toStrictEqual(x509.certNodeId(certOld)); + expect(x509.certNodeId(certNew)).toStrictEqual(x509.certNodeId(certOld)); // New certificates should match expect(x509.certEqual(certNew_, certNew)).toBe(true); // Old certificate no longer exists @@ -275,7 +276,9 @@ class ResetCertWithCurrentKeyPairCommand implements CertManagerCommand { // New certificate issued by itself expect(x509.certIssuedBy(certNew, certNew)).toBe(true); // New certificate is self-signed - expect(await x509.certSignedBy(certNew, x509.certPublicKey(certNew)!)).toBe(true); + expect(await x509.certSignedBy(certNew, x509.certPublicKey(certNew)!)).toBe( + true, + ); // New certificate is self-signed via the node signature extension expect(await x509.certNodeSigned(certNew)).toBe(true); // New certificate is not expired from now and inclusive of the duration @@ -283,8 +286,8 @@ class ResetCertWithCurrentKeyPairCommand implements CertManagerCommand { expect( x509.certNotExpiredBy( certNew, - new Date(now.getTime() + this.duration * 1000) - ) + new Date(now.getTime() + this.duration * 1000), + ), ).toBe(true); expect(await real.getCertsChain()).toStrictEqual(model.certs); } @@ -307,20 +310,20 @@ class ResetCertWithNewKeyPairCommand implements CertManagerCommand { async run(model: CertManagerModel, real: CertManager) { // Update the real const now = new Date(); - await real.resetCertWithNewKeyPair( - this.password, - this.duration, - now - ); + await real.resetCertWithNewKeyPair(this.password, this.duration, now); // Update the model const certOld = model.certs[0]; const certNew = await real.getCurrentCert(); model.certs = [certNew]; - const [certNew_, certOld_] = await AsyncIterable.as(real.getCerts()).take(2).toArray(); + const [certNew_, certOld_] = await AsyncIterable.as(real.getCerts()) + .take(2) + .toArray(); // New certificate with have a greater `CertId` expect(x509.certCertId(certNew)! > x509.certCertId(certOld)!).toBe(true); // Different key pair changes the the NodeId - expect(x509.certNodeId(certNew),).not.toStrictEqual(x509.certNodeId(certOld)); + expect(x509.certNodeId(certNew)).not.toStrictEqual( + x509.certNodeId(certOld), + ); // New certificates should match expect(x509.certEqual(certNew_, certNew)).toBe(true); // Old certificate no longer exists @@ -328,7 +331,9 @@ class ResetCertWithNewKeyPairCommand implements CertManagerCommand { // New certificate issued by itself expect(x509.certIssuedBy(certNew, certNew)).toBe(true); // New certificate is self-signed - expect(await x509.certSignedBy(certNew, x509.certPublicKey(certNew)!)).toBe(true); + expect(await x509.certSignedBy(certNew, x509.certPublicKey(certNew)!)).toBe( + true, + ); // New certificate is self-signed via the node signature extension expect(await x509.certNodeSigned(certNew)).toBe(true); // New certificate is not expired from now and inclusive of the duration @@ -336,8 +341,8 @@ class ResetCertWithNewKeyPairCommand implements CertManagerCommand { expect( x509.certNotExpiredBy( certNew, - new Date(now.getTime() + this.duration * 1000) - ) + new Date(now.getTime() + this.duration * 1000), + ), ).toBe(true); expect(await real.getCertsChain()).toStrictEqual(model.certs); } @@ -366,7 +371,4 @@ export { ResetCertWithNewKeyPairCommand, }; -export type { - CertManagerModel, - CertManagerCommand -}; +export type { CertManagerModel, CertManagerCommand }; diff --git a/tests/keys/utils/generate.test.ts b/tests/keys/utils/generate.test.ts index 2c1cc1d71..4e45ac001 100644 --- a/tests/keys/utils/generate.test.ts +++ b/tests/keys/utils/generate.test.ts @@ -20,9 +20,13 @@ describe('keys/utils/generate', () => { expect(keyPair2.privateKey).toHaveLength(32); expect(keyPair2.secretKey).toHaveLength(64); expect(keyPair1.publicKey).not.toEqual(keyPair1.privateKey); - expect(keyPair1.secretKey).toStrictEqual(Buffer.concat([keyPair1.privateKey, keyPair1.publicKey])); + expect(keyPair1.secretKey).toStrictEqual( + Buffer.concat([keyPair1.privateKey, keyPair1.publicKey]), + ); expect(keyPair2.publicKey).not.toEqual(keyPair2.privateKey); - expect(keyPair2.secretKey).toStrictEqual(Buffer.concat([keyPair2.privateKey, keyPair2.publicKey])); + expect(keyPair2.secretKey).toStrictEqual( + Buffer.concat([keyPair2.privateKey, keyPair2.publicKey]), + ); expect(keyPair1).not.toEqual(keyPair2); // Valid Ed25519 public keys expect(sodium.crypto_core_ed25519_is_valid_point(keyPair1.publicKey)).toBe( @@ -45,14 +49,18 @@ describe('keys/utils/generate', () => { expect(keyPair1.publicKey).toHaveLength(32); expect(keyPair1.privateKey).toHaveLength(32); expect(keyPair1.publicKey).not.toEqual(keyPair1.privateKey); - expect(keyPair1.secretKey).toStrictEqual(Buffer.concat([keyPair1.privateKey, keyPair1.publicKey])); + expect(keyPair1.secretKey).toStrictEqual( + Buffer.concat([keyPair1.privateKey, keyPair1.publicKey]), + ); const keyPair2 = await generate.generateDeterministicKeyPair( recoveryCode1, ); expect(keyPair2.publicKey).toHaveLength(32); expect(keyPair2.privateKey).toHaveLength(32); expect(keyPair2.publicKey).not.toEqual(keyPair2.privateKey); - expect(keyPair2.secretKey).toStrictEqual(Buffer.concat([keyPair2.privateKey, keyPair2.publicKey])); + expect(keyPair2.secretKey).toStrictEqual( + Buffer.concat([keyPair2.privateKey, keyPair2.publicKey]), + ); expect(keyPair2).toStrictEqual(keyPair1); // Valid Ed25519 public keys expect( diff --git a/tests/keys/utils/hash.test.ts b/tests/keys/utils/hash.test.ts index 8edd3bb6e..95ffdbdbe 100644 --- a/tests/keys/utils/hash.test.ts +++ b/tests/keys/utils/hash.test.ts @@ -11,7 +11,7 @@ describe('keys/utils/hash', () => { const digest2 = hash.sha2256(data); expect(digest1).toHaveLength(32); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'sha2-512', @@ -21,7 +21,7 @@ describe('keys/utils/hash', () => { const digest2 = hash.sha2512(data); expect(digest1).toHaveLength(64); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'sha2-512-256', @@ -31,7 +31,7 @@ describe('keys/utils/hash', () => { const digest2 = hash.sha2512256(data); expect(digest1).toHaveLength(32); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'blake2b-256', @@ -41,61 +41,51 @@ describe('keys/utils/hash', () => { const digest2 = hash.blake2b256(data); expect(digest1).toHaveLength(32); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'sha2-256 iterable', - [ - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const digest1 = hash.sha2256I(datas); const digest2 = hash.sha2256(Buffer.concat(datas)); expect(digest1).toHaveLength(32); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'sha2-512 iterable', - [ - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const digest1 = hash.sha2512I(datas); const digest2 = hash.sha2512(Buffer.concat(datas)); expect(digest1).toHaveLength(64); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'sha2-512-256 iterable', - [ - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const digest1 = hash.sha2512256I(datas); const digest2 = hash.sha2512256(Buffer.concat(datas)); expect(digest1).toHaveLength(32); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'blake2b-256 iterable', - [ - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const digest1 = hash.blake2b256I(datas); const digest2 = hash.blake2b256(Buffer.concat(datas)); expect(digest1).toHaveLength(32); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'sha2-256 generator', - [ - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const hasher = hash.sha2256G(); hasher.next(); @@ -108,13 +98,11 @@ describe('keys/utils/hash', () => { expect(digest1).toHaveLength(32); const digest2 = hash.sha2256(Buffer.concat(datas)); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'sha2-512 generator', - [ - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const hasher = hash.sha2512G(); hasher.next(); @@ -127,15 +115,11 @@ describe('keys/utils/hash', () => { expect(digest1).toHaveLength(64); const digest2 = hash.sha2512(Buffer.concat(datas)); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'sha2-512-256 generator', - [ - fc.array( - fc.uint8Array({ minLength: 0, maxLength: 1024 }) - ) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const hasher = hash.sha2512256G(); hasher.next(); @@ -149,13 +133,11 @@ describe('keys/utils/hash', () => { const digest2 = hash.sha2512256(Buffer.concat(datas)); expect(digest1).toStrictEqual(digest2); }, - { seed: 1150342642, path: "0:0", endOnFailure: true } + { seed: 1150342642, path: '0:0', endOnFailure: true }, ); testProp( 'blake2b-256 generator', - [ - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const hasher = hash.blake2b256G(); hasher.next(); @@ -168,7 +150,7 @@ describe('keys/utils/hash', () => { expect(digest1).toHaveLength(32); const digest2 = hash.blake2b256(Buffer.concat(datas)); expect(digest1).toStrictEqual(digest2); - } + }, ); testProp( 'hash', @@ -182,13 +164,11 @@ describe('keys/utils/hash', () => { expect(digestSHA2512).toStrictEqual(hash.sha2512(data)); expect(digestSHA2512256).toStrictEqual(hash.sha2512256(data)); expect(digestBLAKE2b256).toStrictEqual(hash.blake2b256(data)); - } + }, ); testProp( 'hash iterable', - [ - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const digestSHA2256 = hash.hashI(datas, 'sha2-256'); const digestSHA2512 = hash.hashI(datas, 'sha2-512'); @@ -198,13 +178,11 @@ describe('keys/utils/hash', () => { expect(digestSHA2512).toStrictEqual(hash.sha2512I(datas)); expect(digestSHA2512256).toStrictEqual(hash.sha2512256I(datas)); expect(digestBLAKE2b256).toStrictEqual(hash.blake2b256I(datas)); - } + }, ); testProp( 'hash generator', - [ - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) - ], + [fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 }))], (datas) => { const digestSHA2256 = hash.hashG('sha2-256'); const digestSHA2512 = hash.hashG('sha2-512'); @@ -228,11 +206,19 @@ describe('keys/utils/hash', () => { expect(resultSHA2512.done).toBe(true); expect(resultSHA2512256.done).toBe(true); expect(resultBLAKE2b256.done).toBe(true); - expect(resultSHA2256.value).toStrictEqual(hash.sha2256(Buffer.concat(datas))); - expect(resultSHA2512.value).toStrictEqual(hash.sha2512(Buffer.concat(datas))); - expect(resultSHA2512256.value).toStrictEqual(hash.sha2512256(Buffer.concat(datas))); - expect(resultBLAKE2b256.value).toStrictEqual(hash.blake2b256(Buffer.concat(datas))); - } + expect(resultSHA2256.value).toStrictEqual( + hash.sha2256(Buffer.concat(datas)), + ); + expect(resultSHA2512.value).toStrictEqual( + hash.sha2512(Buffer.concat(datas)), + ); + expect(resultSHA2512256.value).toStrictEqual( + hash.sha2512256(Buffer.concat(datas)), + ); + expect(resultBLAKE2b256.value).toStrictEqual( + hash.blake2b256(Buffer.concat(datas)), + ); + }, ); testProp( 'to and from multidigest', @@ -242,18 +228,42 @@ describe('keys/utils/hash', () => { const digestSHA2512 = hash.hash(data, 'sha2-512'); const digestSHA2512256 = hash.hash(data, 'sha2-512-256'); const digestBLAKE2b256 = hash.hash(data, 'blake2b-256'); - const mDigestSHA2256 = hash.digestToMultidigest(digestSHA2256, 'sha2-256'); - const mDigestSHA2512 = hash.digestToMultidigest(digestSHA2512, 'sha2-512'); - const mDigestSHA2512256 = hash.digestToMultidigest(digestSHA2512256, 'sha2-512-256'); - const mDigestBLAKE2b256 = hash.digestToMultidigest(digestBLAKE2b256, 'blake2b-256'); - const digestSHA2256_ = hash.digestFromMultidigest(mDigestSHA2256.bytes)!.digest - const digestSHA2512_ = hash.digestFromMultidigest(mDigestSHA2512.bytes)!.digest - const digestSHA2512256_ = hash.digestFromMultidigest(mDigestSHA2512256.bytes)!.digest - const digestBLAKE2b256_ = hash.digestFromMultidigest(mDigestBLAKE2b256.bytes)!.digest + const mDigestSHA2256 = hash.digestToMultidigest( + digestSHA2256, + 'sha2-256', + ); + const mDigestSHA2512 = hash.digestToMultidigest( + digestSHA2512, + 'sha2-512', + ); + const mDigestSHA2512256 = hash.digestToMultidigest( + digestSHA2512256, + 'sha2-512-256', + ); + const mDigestBLAKE2b256 = hash.digestToMultidigest( + digestBLAKE2b256, + 'blake2b-256', + ); + const digestSHA2256_ = hash.digestFromMultidigest( + mDigestSHA2256.bytes, + )!.digest; + const digestSHA2512_ = hash.digestFromMultidigest( + mDigestSHA2512.bytes, + )!.digest; + const digestSHA2512256_ = hash.digestFromMultidigest( + mDigestSHA2512256.bytes, + )!.digest; + const digestBLAKE2b256_ = hash.digestFromMultidigest( + mDigestBLAKE2b256.bytes, + )!.digest; expect(utils.bufferWrap(digestSHA2256_)).toStrictEqual(digestSHA2256); expect(utils.bufferWrap(digestSHA2512_)).toStrictEqual(digestSHA2512); - expect(utils.bufferWrap(digestSHA2512256_)).toStrictEqual(digestSHA2512256); - expect(utils.bufferWrap(digestBLAKE2b256_)).toStrictEqual(digestBLAKE2b256); - } + expect(utils.bufferWrap(digestSHA2512256_)).toStrictEqual( + digestSHA2512256, + ); + expect(utils.bufferWrap(digestBLAKE2b256_)).toStrictEqual( + digestBLAKE2b256, + ); + }, ); }); diff --git a/tests/keys/utils/pem.test.ts b/tests/keys/utils/pem.test.ts index 3ce342ddc..bd2fa9a18 100644 --- a/tests/keys/utils/pem.test.ts +++ b/tests/keys/utils/pem.test.ts @@ -1,12 +1,10 @@ import { testProp } from '@fast-check/jest'; import webcrypto, { importKeyPair } from '@/keys/utils/webcrypto'; import * as pem from '@/keys/utils/pem'; -import * as ids from '@/ids'; import * as utils from '@/utils'; import * as testsKeysUtils from '../utils'; describe('keys/utils/pem', () => { - const certIdGenerator = ids.createCertIdGenerator(); testProp( 'keypair convert to and from PEM', [testsKeysUtils.keyPairArb], @@ -27,11 +25,19 @@ describe('keys/utils/pem', () => { const spki = utils.bufferWrap( await webcrypto.subtle.exportKey('spki', cryptoKeyPair.publicKey), ); - const spkiContents = spki.toString('base64').replace(/(.{64})/g, '$1\n').trimEnd() + '\n'; + const spkiContents = + spki + .toString('base64') + .replace(/(.{64})/g, '$1\n') + .trimEnd() + '\n'; const pkcs8 = utils.bufferWrap( await webcrypto.subtle.exportKey('pkcs8', cryptoKeyPair.privateKey), ); - const pkcs8Contents = pkcs8.toString('base64').replace(/(.{64})/g, '$1\n').trimEnd() + '\n'; + const pkcs8Contents = + pkcs8 + .toString('base64') + .replace(/(.{64})/g, '$1\n') + .trimEnd() + '\n'; const spkiPEM = `-----BEGIN PUBLIC KEY-----\n${spkiContents}-----END PUBLIC KEY-----\n`; const pkcs8PEM = `-----BEGIN PRIVATE KEY-----\n${pkcs8Contents}-----END PRIVATE KEY-----\n`; expect(spkiPEM).toStrictEqual(keyPairPEM.publicKey); diff --git a/tests/keys/utils/symmetric.test.ts b/tests/keys/utils/symmetric.test.ts index 1c0062bb5..5ad5c6d2f 100644 --- a/tests/keys/utils/symmetric.test.ts +++ b/tests/keys/utils/symmetric.test.ts @@ -41,21 +41,35 @@ describe('keys/utils/symmetric', () => { fc.pre(!dataCorrect.equals(dataWrong)); const macCorrect = symmetric.macWithKey(keyCorrect, dataCorrect); expect(macCorrect).toHaveLength(32); - expect(symmetric.authWithKey(keyCorrect, dataCorrect, macCorrect)).toBe(true); - expect(symmetric.authWithKey(keyCorrect, dataWrong, macWrong)).toBe(false); - expect(symmetric.authWithKey(keyCorrect, dataWrong, macCorrect)).toBe(false); - expect(symmetric.authWithKey(keyCorrect, dataCorrect, macWrong)).toBe(false); - expect(symmetric.authWithKey(keyWrong, dataCorrect, macCorrect)).toBe(false); - expect(symmetric.authWithKey(keyWrong, dataWrong, macCorrect)).toBe(false); + expect(symmetric.authWithKey(keyCorrect, dataCorrect, macCorrect)).toBe( + true, + ); + expect(symmetric.authWithKey(keyCorrect, dataWrong, macWrong)).toBe( + false, + ); + expect(symmetric.authWithKey(keyCorrect, dataWrong, macCorrect)).toBe( + false, + ); + expect(symmetric.authWithKey(keyCorrect, dataCorrect, macWrong)).toBe( + false, + ); + expect(symmetric.authWithKey(keyWrong, dataCorrect, macCorrect)).toBe( + false, + ); + expect(symmetric.authWithKey(keyWrong, dataWrong, macCorrect)).toBe( + false, + ); expect(symmetric.authWithKey(keyWrong, dataWrong, macWrong)).toBe(false); - expect(symmetric.authWithKey(keyWrong, dataCorrect, macWrong)).toBe(false); + expect(symmetric.authWithKey(keyWrong, dataCorrect, macWrong)).toBe( + false, + ); }, ); testProp( 'mac with key generator', [ testsKeysUtils.keyArb, - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) + fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })), ], (key, datas) => { const maccer = symmetric.macWithKeyG(key); @@ -74,20 +88,24 @@ describe('keys/utils/symmetric', () => { const result2 = auther.next(null); expect(result2.done).toBe(true); expect(result2.value).toBe(true); - expect(symmetric.macWithKey(key, Buffer.concat(datas))).toStrictEqual(result1.value); - } + expect(symmetric.macWithKey(key, Buffer.concat(datas))).toStrictEqual( + result1.value, + ); + }, ); testProp( 'mac & auth with key iterator', [ testsKeysUtils.keyArb, - fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })) + fc.array(fc.uint8Array({ minLength: 0, maxLength: 1024 })), ], (key, datas) => { const digest = symmetric.macWithKeyI(key, datas); expect(symmetric.authWithKeyI(key, datas, digest)).toBe(true); - expect(symmetric.macWithKey(key, Buffer.concat(datas))).toStrictEqual(digest); - } + expect(symmetric.macWithKey(key, Buffer.concat(datas))).toStrictEqual( + digest, + ); + }, ); testProp( 'wrap & unwrap with random password', diff --git a/tests/keys/utils/x509.test.ts b/tests/keys/utils/x509.test.ts index eb9064e0c..2ba435f28 100644 --- a/tests/keys/utils/x509.test.ts +++ b/tests/keys/utils/x509.test.ts @@ -31,16 +31,14 @@ describe('keys/utils/x509', () => { subjectKeyPair, issuerPrivateKey: issuerKeyPair.privateKey, duration, - now + now, }); expect(cert.notBefore.getTime()).toBe(nowS.getTime()); expect(cert.notAfter.getTime()).toBe(nowS.getTime() + duration * 1000); // Certificate is equal to itself expect(x509.certEqual(cert, cert)).toBe(true); // Certificate public key is equal to the subject public key - expect(x509.certPublicKey(cert)).toStrictEqual( - subjectKeyPair.publicKey, - ); + expect(x509.certPublicKey(cert)).toStrictEqual(subjectKeyPair.publicKey); // Certificate node ID is equal to the subject public key node ID expect(x509.certNodeId(cert)).toStrictEqual( asymmetric.publicKeyToNodeId(subjectKeyPair.publicKey), @@ -48,9 +46,7 @@ describe('keys/utils/x509', () => { // The cert is not self-issued expect(x509.certIssuedBy(cert, cert)).toBe(false); // The certificate is signed by the issuer - expect(await x509.certSignedBy(cert, issuerKeyPair.publicKey)).toBe( - true, - ); + expect(await x509.certSignedBy(cert, issuerKeyPair.publicKey)).toBe(true); // The certificate has a node signature and it is valid expect(await x509.certNodeSigned(cert)).toBe(true); // It is not expired now diff --git a/tests/network/Proxy.test.ts b/tests/network/Proxy.test.ts index f355de297..498e2c64b 100644 --- a/tests/network/Proxy.test.ts +++ b/tests/network/Proxy.test.ts @@ -110,12 +110,15 @@ const generateCertId = keysUtils.createCertIdGenerator(); async function createTLSSocketConfig(serverKeyPair: KeyPair) { const serverKeyPairPem = keysUtils.keyPairToPEM(serverKeyPair); - const serverCert = (await keysUtils.generateCertificate({ + const serverCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: serverKeyPair.privateKey, - subjectKeyPair: { privateKey: serverKeyPair.privateKey, publicKey: serverKeyPair.publicKey } - })); + subjectKeyPair: { + privateKey: serverKeyPair.privateKey, + publicKey: serverKeyPair.publicKey, + }, + }); const serverCertPem = keysUtils.certToPEM(serverCert); return { key: Buffer.from(serverKeyPairPem.privateKey, 'ascii'), @@ -123,7 +126,7 @@ async function createTLSSocketConfig(serverKeyPair: KeyPair) { isServer: true, requestCert: true, rejectUnauthorized: false, - } + }; } describe(Proxy.name, () => { @@ -141,7 +144,6 @@ describe(Proxy.name, () => { // The Proxy acts like both a client and a server. // This is the TLSConfig for the Proxy. let tlsConfig: TLSConfig; - let certPem: string; beforeEach(async () => { tlsConfig = await testsUtils.createTLSConfig(keysUtils.generateKeyPair()); }); @@ -607,7 +609,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('open connection fails due to invalid node id', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const proxy = new Proxy({ authToken, logger: logger.getChild('Proxy invalid node id'), @@ -643,7 +645,10 @@ describe(Proxy.name, () => { utpConn.on('end', async () => { utpConn.destroy(); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket.on('secure', () => { secured = true; }); @@ -717,7 +722,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('HTTP CONNECT fails due to invalid node id', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const proxy = new Proxy({ authToken, logger: logger.getChild('Proxy invalid node id'), @@ -753,7 +758,10 @@ describe(Proxy.name, () => { utpConn.on('end', async () => { utpConn.destroy(); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket.on('secure', () => { secured = true; }); @@ -830,7 +838,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('open connection success - forward initiates end', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; const proxy = new Proxy({ authToken, @@ -870,7 +878,10 @@ describe(Proxy.name, () => { utpConn.on('end', async () => { utpConn.destroy(); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket.on('secure', () => { resolveRemoteSecureP(); }); @@ -950,7 +961,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('open connection success - reverse initiates end', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; const proxy = new Proxy({ authToken, @@ -984,7 +995,10 @@ describe(Proxy.name, () => { utpConn.on('error', (e) => { utpConnError(e); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket_ = tlsSocket; tlsSocket.on('secure', () => { resolveRemoteSecureP(); @@ -1084,7 +1098,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('HTTP CONNECT success - forward initiates end', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const proxy = new Proxy({ @@ -1125,7 +1139,10 @@ describe(Proxy.name, () => { utpConn.on('end', async () => { utpConn.destroy(); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket.on('secure', () => { resolveRemoteSecureP(); }); @@ -1227,7 +1244,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('HTTP CONNECT success - reverse initiates end', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const proxy = new Proxy({ @@ -1261,7 +1278,10 @@ describe(Proxy.name, () => { utpConn.on('error', (e) => { utpConnError(e); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket_ = tlsSocket; tlsSocket.on('secure', () => { resolveRemoteSecureP(); @@ -1383,7 +1403,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('HTTP CONNECT success - client initiates end', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const proxy = new Proxy({ @@ -1415,7 +1435,10 @@ describe(Proxy.name, () => { utpConn.on('error', (e) => { utpConnError(e); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket.on('secure', () => { resolveRemoteSecureP(); }); @@ -1526,7 +1549,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('HTTP CONNECT success by opening connection first', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const proxy = new Proxy({ @@ -1556,7 +1579,10 @@ describe(Proxy.name, () => { utpConn.on('error', (e) => { utpConnError(e); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket.on('secure', () => { resolveRemoteSecureP(); }); @@ -1642,7 +1668,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('open connection keepalive timeout', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; const proxy = new Proxy({ authToken, @@ -1673,7 +1699,10 @@ describe(Proxy.name, () => { utpConn.on('error', (e) => { utpConnError(e); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket.on('secure', () => { resolveRemoteSecureP(); }); @@ -1744,7 +1773,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('HTTP CONNECT keepalive timeout', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const proxy = new Proxy({ @@ -1776,7 +1805,10 @@ describe(Proxy.name, () => { utpConn.on('error', (e) => { utpConnError(e); }); - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket.on('secure', () => { resolveRemoteSecureP(); }); @@ -1870,7 +1902,7 @@ describe(Proxy.name, () => { await proxy.stop(); }); test('stopping the proxy with open forward connections', async () => { - const serverKeyPair = await keysUtils.generateKeyPair(); + const serverKeyPair = keysUtils.generateKeyPair(); const serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; const proxy = new Proxy({ authToken, @@ -1891,7 +1923,10 @@ describe(Proxy.name, () => { const { p: remoteClosedP, resolveP: resolveRemoteClosedP } = promise(); const utpSocket = UTP.createServer(async (utpConn) => { - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair), + ); tlsSocket.on('secure', () => { resolveRemoteSecureP(); }); @@ -1951,11 +1986,15 @@ describe(Proxy.name, () => { }); test('open connection to multiple servers', async () => { // First server keys - const serverKeyPair1 = await keysUtils.generateKeyPair(); - const serverNodeId1 = keysUtils.publicKeyToNodeId(serverKeyPair1.publicKey)!; + const serverKeyPair1 = keysUtils.generateKeyPair(); + const serverNodeId1 = keysUtils.publicKeyToNodeId( + serverKeyPair1.publicKey, + )!; // Second server keys - const serverKeyPair2 = await keysUtils.generateKeyPair(); - const serverNodeId2 = keysUtils.publicKeyToNodeId(serverKeyPair2.publicKey)!; + const serverKeyPair2 = keysUtils.generateKeyPair(); + const serverNodeId2 = keysUtils.publicKeyToNodeId( + serverKeyPair2.publicKey, + )!; const proxy = new Proxy({ authToken, logger, @@ -1980,7 +2019,10 @@ describe(Proxy.name, () => { const { p: remoteClosedP2, resolveP: resolveRemoteClosedP2 } = promise(); const utpSocket1 = UTP.createServer(async (utpConn) => { - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair1)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair1), + ); tlsSocket.on('close', () => { resolveRemoteClosedP1(); }); @@ -2017,7 +2059,10 @@ describe(Proxy.name, () => { const utpSocketHost1 = utpSocket1.address().address; const utpSocketPort1 = utpSocket1.address().port; const utpSocket2 = UTP.createServer(async (utpConn) => { - const tlsSocket = new tls.TLSSocket(utpConn, await createTLSSocketConfig(serverKeyPair2)); + const tlsSocket = new tls.TLSSocket( + utpConn, + await createTLSSocketConfig(serverKeyPair2), + ); tlsSocket.on('close', () => { resolveRemoteClosedP2(); }); @@ -2527,14 +2572,17 @@ describe(Proxy.name, () => { await serverClose(); }); test('connect success', async () => { - const clientKeyPair = await keysUtils.generateKeyPair(); + const clientKeyPair = keysUtils.generateKeyPair(); const clientKeyPairPem = keysUtils.keyPairToPEM(clientKeyPair); - const clientCert = (await keysUtils.generateCertificate({ + const clientCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: clientKeyPair.privateKey, - subjectKeyPair: { privateKey: clientKeyPair.privateKey, publicKey: clientKeyPair.publicKey } - })); + subjectKeyPair: { + privateKey: clientKeyPair.privateKey, + publicKey: clientKeyPair.publicKey, + }, + }); const clientCertPem = keysUtils.certToPEM(clientCert); const { serverListen, @@ -2623,14 +2671,17 @@ describe(Proxy.name, () => { await serverClose(); }); test('stopping the proxy with open reverse connections', async () => { - const clientKeyPair = await keysUtils.generateKeyPair(); + const clientKeyPair = keysUtils.generateKeyPair(); const clientKeyPairPem = keysUtils.keyPairToPEM(clientKeyPair); - const clientCert = (await keysUtils.generateCertificate({ + const clientCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: clientKeyPair.privateKey, - subjectKeyPair: { privateKey: clientKeyPair.privateKey, publicKey: clientKeyPair.publicKey } - })); + subjectKeyPair: { + privateKey: clientKeyPair.privateKey, + publicKey: clientKeyPair.publicKey, + }, + }); const clientCertPem = keysUtils.certToPEM(clientCert); const { serverListen, @@ -2718,14 +2769,17 @@ describe(Proxy.name, () => { await serverClose(); }); test('connectionEstablishedCallback is called when a ReverseConnection is established', async () => { - const clientKeyPair = await keysUtils.generateKeyPair(); + const clientKeyPair = keysUtils.generateKeyPair(); const clientKeyPairPem = keysUtils.keyPairToPEM(clientKeyPair); - const clientCert = (await keysUtils.generateCertificate({ + const clientCert = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: clientKeyPair.privateKey, - subjectKeyPair: { privateKey: clientKeyPair.privateKey, publicKey: clientKeyPair.publicKey } - })); + subjectKeyPair: { + privateKey: clientKeyPair.privateKey, + publicKey: clientKeyPair.publicKey, + }, + }); const clientCertPem = keysUtils.certToPEM(clientCert); const { serverListen, diff --git a/tests/network/index.test.ts b/tests/network/index.test.ts index cd1a029cb..70cca7abe 100644 --- a/tests/network/index.test.ts +++ b/tests/network/index.test.ts @@ -22,10 +22,10 @@ describe('network index', () => { let serverNodeId: NodeId; beforeAll(async () => { // Client keys - clientKeyPair = await keysUtils.generateKeyPair(); + clientKeyPair = keysUtils.generateKeyPair(); clientNodeId = keysUtils.publicKeyToNodeId(clientKeyPair.publicKey)!; // Server keys - serverKeyPair = await keysUtils.generateKeyPair(); + serverKeyPair = keysUtils.generateKeyPair(); serverNodeId = keysUtils.publicKeyToNodeId(serverKeyPair.publicKey)!; }); let server; diff --git a/tests/nodes/NodeConnection.test.ts b/tests/nodes/NodeConnection.test.ts index 992bdcbe8..348a13b17 100644 --- a/tests/nodes/NodeConnection.test.ts +++ b/tests/nodes/NodeConnection.test.ts @@ -1,9 +1,10 @@ import type { AddressInfo } from 'net'; import type { ConnectionInfo, Host, Port, TLSConfig } from '@/network/types'; -import type { NodeId, NodeInfo } from '@/nodes/types'; +import type { NodeId } from '@/nodes/types'; import type { Key } from '@/keys/types'; import type { Server } from '@grpc/grpc-js'; import type { ChildProcessWithoutNullStreams } from 'child_process'; +import type { GestaltNodeInfo } from '@/gestalts/types'; import net from 'net'; import os from 'os'; import path from 'path'; @@ -32,7 +33,6 @@ import { poll, promise, promisify, sleep } from '@/utils'; import PolykeyAgent from '@/PolykeyAgent'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as GRPCErrors from '@/grpc/errors'; -import * as nodesUtils from '@/nodes/utils'; import * as agentErrors from '@/agent/errors'; import * as grpcUtils from '@/grpc/utils'; import * as utils from '@/utils'; @@ -50,9 +50,8 @@ describe(`${NodeConnection.name} test`, () => { grpcUtils.setLogger(logger.getChild('grpc')); const password = 'password'; - const node: NodeInfo = { - id: nodesUtils.encodeNodeId(testNodesUtils.generateRandomNodeId()), - chain: {}, + const nodeInfo: GestaltNodeInfo = { + nodeId: testNodesUtils.generateRandomNodeId(), }; // Server @@ -234,6 +233,7 @@ describe(`${NodeConnection.name} test`, () => { serverNodeManager = new NodeManager({ db: serverDb, sigchain: serverSigchain, + gestaltGraph: serverGestaltGraph, keyRing: serverKeyRing, nodeGraph: serverNodeGraph, nodeConnectionManager: serverNodeConnectionManager, @@ -262,7 +262,7 @@ describe(`${NodeConnection.name} test`, () => { keyRing: serverKeyRing, logger: logger, }); - await serverGestaltGraph.setNode(node); + await serverGestaltGraph.setNode(nodeInfo); [agentServer, serverPort] = await agentTestUtils.openTestAgentServer({ db: serverDb, keyRing: serverKeyRing, @@ -300,7 +300,9 @@ describe(`${NodeConnection.name} test`, () => { strictMemoryLock: false, }); - const clientTLSConfig = await testsUtils.createTLSConfig(clientKeyRing.keyPair); + const clientTLSConfig = await testsUtils.createTLSConfig( + clientKeyRing.keyPair, + ); sourceNodeId = clientKeyRing.getNodeId(); clientProxy = new Proxy({ @@ -317,7 +319,7 @@ describe(`${NodeConnection.name} test`, () => { sourcePort = clientProxy.getProxyPort(); clientNodeConnectionManager = new NodeConnectionManager({ - keyManager: clientKeyManager, + keyRing: clientKeyRing, nodeGraph: {} as NodeGraph, proxy: clientProxy, taskManager: {} as TaskManager, @@ -332,32 +334,23 @@ describe(`${NodeConnection.name} test`, () => { afterEach(async () => { await clientProxy.stop(); await clientKeyRing.stop(); - await clientKeyRing.destroy(); await fs.promises.rm(clientDataDir, { force: true, recursive: true, }); await serverACL.stop(); - await serverACL.destroy(); await serverSigchain.stop(); - await serverSigchain.destroy(); await serverGestaltGraph.stop(); - await serverGestaltGraph.destroy(); await serverVaultManager.stop(); - await serverVaultManager.destroy(); await serverNodeGraph.stop(); - await serverNodeGraph.destroy(); await serverNodeConnectionManager.stop(); await serverNodeManager.stop(); await serverNotificationsManager.stop(); - await serverNotificationsManager.destroy(); await agentTestUtils.closeTestAgentServer(agentServer); await serverProxy.stop(); await serverKeyRing.stop(); - await serverKeyRing.destroy(); await serverDb.stop(); - await serverDb.destroy(); await fs.promises.rm(serverDataDir, { force: true, recursive: true, @@ -492,7 +485,6 @@ describe(`${NodeConnection.name} test`, () => { // Resolves if the shutdownCallback was called await polykeyAgent.stop(); - await polykeyAgent.destroy(); const client = nodeConnection.getClient(); const echoMessage = new utilsPB.EchoMessage().setChallenge( @@ -502,9 +494,8 @@ describe(`${NodeConnection.name} test`, () => { agentErrors.ErrorAgentClientDestroyed, ); } finally { - await polykeyAgent?.stop(); - await polykeyAgent?.destroy(); await nodeConnection?.destroy(); + await polykeyAgent?.stop(); } }); test('fails to connect to target (times out)', async () => { @@ -599,19 +590,6 @@ describe(`${NodeConnection.name} test`, () => { }, { timer: new Timer({ delay: 500 }) }, ); - const nodeConnectionP = NodeConnection.createNodeConnection({ - timer: timerStart(500), - proxy: clientProxy, - keyRing: clientKeyRing, - logger: logger, - nodeConnectionManager: dummyNodeConnectionManager, - destroyCallback: killSelf, - targetHost: proxy.getProxyHost(), - targetNodeId: targetNodeId, - targetPort: proxy.getProxyPort(), - clientFactory: (args) => GRPCClientAgent.createGRPCClientAgent(args), - }); - // Expecting the connection to fail await expect(nodeConnectionP).rejects.toThrow( nodesErrors.ErrorNodeConnectionTimeout, @@ -700,14 +678,12 @@ describe(`${NodeConnection.name} test`, () => { // Resolves if the shutdownCallback was called await polykeyAgent.stop(); - await polykeyAgent.destroy(); // Kill callback should've been called expect(killSelf.mock.calls.length).toBe(1); // Node connection should've destroyed itself in response to connection being destroyed expect(nodeConnection[destroyed]).toBe(true); } finally { await polykeyAgent?.stop(); - await polykeyAgent?.destroy(); await nodeConnection?.destroy(); } }); @@ -890,7 +866,9 @@ describe(`${NodeConnection.name} test`, () => { await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); // Simulate key change - clientProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); + clientProxy.setTLSConfig( + await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), + ); // Try again await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); @@ -918,7 +896,9 @@ describe(`${NodeConnection.name} test`, () => { await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); // Simulate key change - clientProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); + clientProxy.setTLSConfig( + await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), + ); // Try again await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); @@ -946,7 +926,9 @@ describe(`${NodeConnection.name} test`, () => { await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); // Simulate key change - clientProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); + clientProxy.setTLSConfig( + await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), + ); // Try again await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); @@ -974,7 +956,9 @@ describe(`${NodeConnection.name} test`, () => { await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); // Simulate key change - serverProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); + serverProxy.setTLSConfig( + await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), + ); // Try again await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); @@ -1002,7 +986,9 @@ describe(`${NodeConnection.name} test`, () => { await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); // Simulate key change - serverProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); + serverProxy.setTLSConfig( + await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), + ); // Try again await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); @@ -1030,7 +1016,9 @@ describe(`${NodeConnection.name} test`, () => { await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); // Simulate key change - serverProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); + serverProxy.setTLSConfig( + await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), + ); // Try again await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); @@ -1042,7 +1030,9 @@ describe(`${NodeConnection.name} test`, () => { let conn: NodeConnection | undefined; try { // Simulate key change - clientProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); + clientProxy.setTLSConfig( + await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), + ); conn = await NodeConnection.createNodeConnection( { @@ -1068,7 +1058,9 @@ describe(`${NodeConnection.name} test`, () => { let conn: NodeConnection | undefined; try { // Simulate key change - clientProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); + clientProxy.setTLSConfig( + await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), + ); conn = await NodeConnection.createNodeConnection( { @@ -1094,7 +1086,9 @@ describe(`${NodeConnection.name} test`, () => { let conn: NodeConnection | undefined; try { // Simulate key change - clientProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); + clientProxy.setTLSConfig( + await testsUtils.createTLSConfig(keysUtils.generateKeyPair()), + ); conn = await NodeConnection.createNodeConnection( { @@ -1118,8 +1112,9 @@ describe(`${NodeConnection.name} test`, () => { }); test('new connection handles a resetRootKeyPair on receiving side', async () => { // Simulate key change - serverProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); - + const keyPair = keysUtils.generateKeyPair(); + serverProxy.setTLSConfig(await testsUtils.createTLSConfig(keyPair)); + const newNodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); const connProm = NodeConnection.createNodeConnection( { targetNodeId: targetNodeId, @@ -1141,57 +1136,9 @@ describe(`${NodeConnection.name} test`, () => { // Connect with the new NodeId let conn: NodeConnection | undefined; try { - conn = await NodeConnection.createNodeConnection({ - targetNodeId: serverKeyRing.getNodeId(), - targetHost: localHost, - targetPort: targetPort, - proxy: clientProxy, - destroyCallback, - logger: logger, - clientFactory: async (args) => - GRPCClientAgent.createGRPCClientAgent(args), - }); - const client = conn.getClient(); - await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); - } finally { - await conn?.destroy(); - } - }); - test('new connection handles a renewRootKeyPair on receiving side', async () => { - let conn: NodeConnection | undefined; - try { - // Simulate key change - serverProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); - - conn = await NodeConnection.createNodeConnection( - { - targetNodeId: targetNodeId, - targetHost: localHost, - targetPort: targetPort, - proxy: clientProxy, - destroyCallback, - logger: logger, - clientFactory: async (args) => - GRPCClientAgent.createGRPCClientAgent(args), - }, - { timer: new Timer({ delay: 2000 }) }, - ); - - const client = conn.getClient(); - await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); - } finally { - await conn?.destroy(); - } - }); - test('new connection handles a resetRootCert on receiving side', async () => { - let conn: NodeConnection | undefined; - try { - // Simulate key change - serverProxy.setTLSConfig(await testsUtils.createTLSConfig(keysUtils.generateKeyPair())); - conn = await NodeConnection.createNodeConnection( { - targetNodeId: targetNodeId, + targetNodeId: newNodeId, targetHost: localHost, targetPort: targetPort, proxy: clientProxy, @@ -1202,7 +1149,6 @@ describe(`${NodeConnection.name} test`, () => { }, { timer: new Timer({ delay: 2000 }) }, ); - const client = conn.getClient(); await client.echo(new utilsPB.EchoMessage().setChallenge('hello!')); } finally { diff --git a/tests/nodes/NodeConnectionManager.general.test.ts b/tests/nodes/NodeConnectionManager.general.test.ts index a489efead..9cfb2ea7b 100644 --- a/tests/nodes/NodeConnectionManager.general.test.ts +++ b/tests/nodes/NodeConnectionManager.general.test.ts @@ -176,9 +176,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { afterAll(async () => { await remoteNode1.stop(); - await remoteNode1.destroy(); await remoteNode2.stop(); - await remoteNode2.destroy(); await fs.promises.rm(dataDir2, { force: true, recursive: true }); }); @@ -245,11 +243,8 @@ describe(`${NodeConnectionManager.name} general test`, () => { afterEach(async () => { await nodeGraph.stop(); - await nodeGraph.destroy(); await db.stop(); - await db.destroy(); await keyRing.stop(); - await keyRing.destroy(); await proxy.stop(); }); @@ -469,7 +464,6 @@ describe(`${NodeConnectionManager.name} general test`, () => { expect(closest).toEqual(addedClosestNodes); } finally { await serverPKAgent?.stop(); - await serverPKAgent?.destroy(); await nodeConnectionManager?.stop(); } }); diff --git a/tests/nodes/NodeConnectionManager.lifecycle.test.ts b/tests/nodes/NodeConnectionManager.lifecycle.test.ts index 427b12736..a92111a6f 100644 --- a/tests/nodes/NodeConnectionManager.lifecycle.test.ts +++ b/tests/nodes/NodeConnectionManager.lifecycle.test.ts @@ -130,9 +130,7 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { afterAll(async () => { await remoteNode1.stop(); - await remoteNode1.destroy(); await remoteNode2.stop(); - await remoteNode2.destroy(); await fs.promises.rm(dataDir2, { force: true, recursive: true }); }); @@ -306,7 +304,7 @@ describe(`${NodeConnectionManager.name} lifecycle test`, () => { let nodeConnectionManager: NodeConnectionManager | undefined; try { nodeConnectionManager = new NodeConnectionManager({ - keyManager, + keyRing, nodeGraph, proxy, taskManager, diff --git a/tests/nodes/NodeConnectionManager.seednodes.test.ts b/tests/nodes/NodeConnectionManager.seednodes.test.ts index 4fba40fdd..6d632db02 100644 --- a/tests/nodes/NodeConnectionManager.seednodes.test.ts +++ b/tests/nodes/NodeConnectionManager.seednodes.test.ts @@ -2,6 +2,7 @@ import type { NodeId, NodeIdEncoded, SeedNodes } from '@/nodes/types'; import type { Host, Port } from '@/network/types'; import type { Sigchain } from '@/sigchain'; import type { Key } from '@/keys/types'; +import type { GestaltGraph } from '@/gestalts/index'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -132,9 +133,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { afterAll(async () => { await remoteNode1.stop(); - await remoteNode1.destroy(); await remoteNode2.stop(); - await remoteNode2.destroy(); await fs.promises.rm(dataDir2, { force: true, recursive: true }); }); @@ -213,11 +212,8 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { afterEach(async () => { await nodeGraph.stop(); - await nodeGraph.destroy(); await db.stop(); - await db.destroy(); await keyRing.stop(); - await keyRing.destroy(); await proxy.stop(); await taskManager.stop(); }); @@ -238,6 +234,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { nodeManager = new NodeManager({ db, keyRing, + gestaltGraph: {} as GestaltGraph, logger, nodeConnectionManager, nodeGraph, @@ -316,6 +313,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { nodeManager = new NodeManager({ db, keyRing, + gestaltGraph: {} as GestaltGraph, logger, nodeConnectionManager, nodeGraph, @@ -379,6 +377,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { nodeManager = new NodeManager({ db, keyRing, + gestaltGraph: {} as GestaltGraph, logger, nodeConnectionManager, nodeGraph, @@ -458,6 +457,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { nodeManager = new NodeManager({ db, keyRing, + gestaltGraph: {} as GestaltGraph, logger, nodeConnectionManager, nodeGraph, @@ -571,9 +571,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { } finally { mockedPingNode.mockRestore(); await node1?.stop(); - await node1?.destroy(); await node2?.stop(); - await node2?.destroy(); } }, globalThis.defaultTimeout * 2, @@ -641,7 +639,6 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { } finally { mockedPingNode.mockRestore(); await node1?.stop(); - await node1?.destroy(); } }, globalThis.defaultTimeout * 2, diff --git a/tests/nodes/NodeConnectionManager.termination.test.ts b/tests/nodes/NodeConnectionManager.termination.test.ts index c0199e5b0..e436985c6 100644 --- a/tests/nodes/NodeConnectionManager.termination.test.ts +++ b/tests/nodes/NodeConnectionManager.termination.test.ts @@ -26,7 +26,6 @@ import * as agentErrors from '@/agent/errors'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import { promise, promisify } from '@/utils'; import * as utils from '@/utils/index'; -import * as testUtils from '../utils'; import * as testsUtils from '../utils/index'; describe(`${NodeConnectionManager.name} termination test`, () => { @@ -149,11 +148,8 @@ describe(`${NodeConnectionManager.name} termination test`, () => { afterEach(async () => { await nodeGraph.stop(); - await nodeGraph.destroy(); await db.stop(); - await db.destroy(); await keyRing.stop(); - await keyRing.destroy(); await defaultProxy.stop(); }); diff --git a/tests/nodes/NodeConnectionManager.timeout.test.ts b/tests/nodes/NodeConnectionManager.timeout.test.ts index a7b0e8b2b..08a7f3890 100644 --- a/tests/nodes/NodeConnectionManager.timeout.test.ts +++ b/tests/nodes/NodeConnectionManager.timeout.test.ts @@ -121,9 +121,7 @@ describe(`${NodeConnectionManager.name} timeout test`, () => { afterAll(async () => { await remoteNode1.stop(); - await remoteNode1.destroy(); await remoteNode2.stop(); - await remoteNode2.destroy(); await fs.promises.rm(dataDir2, { force: true, recursive: true }); }); diff --git a/tests/nodes/NodeGraph.test.ts b/tests/nodes/NodeGraph.test.ts index 459e8d02b..a0765f7a8 100644 --- a/tests/nodes/NodeGraph.test.ts +++ b/tests/nodes/NodeGraph.test.ts @@ -12,7 +12,7 @@ import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { IdInternal } from '@matrixai/id'; -import * as fc from 'fast-check'; +import { testProp, fc } from '@fast-check/jest'; import NodeGraph from '@/nodes/NodeGraph'; import KeyRing from '@/keys/KeyRing'; import * as keysUtils from '@/keys/utils'; @@ -31,7 +31,7 @@ describe(`${NodeGraph.name} test`, () => { let dbKey: Buffer; let dbPath: string; let db: DB; - beforeAll(async () => { + beforeEach(async () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); @@ -44,17 +44,8 @@ describe(`${NodeGraph.name} test`, () => { passwordMemLimit: keysUtils.passwordMemLimits.min, strictMemoryLock: false, }); - dbKey = await keysUtils.generateKey(); + dbKey = keysUtils.generateKey(); dbPath = `${dataDir}/db`; - }); - afterAll(async () => { - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - beforeEach(async () => { db = await DB.createDB({ dbPath, logger, @@ -80,6 +71,11 @@ describe(`${NodeGraph.name} test`, () => { afterEach(async () => { await db.stop(); await db.destroy(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); }); test('get, set and unset node IDs', async () => { const nodeGraph = await NodeGraph.createNodeGraph({ @@ -571,310 +567,223 @@ describe(`${NodeGraph.name} test`, () => { } await nodeGraph.stop(); }); - test('reset buckets', async () => { - const getNodeIdMock = jest.fn(); - const dummyKeyRing = { - getNodeId: getNodeIdMock, - } as unknown as KeyRing; - - const nodeIdArb = fc - .int8Array({ minLength: 32, maxLength: 32 }) - .map((value) => IdInternal.fromBuffer(Buffer.from(value))); - const nodeIdArrayArb = fc - .array(nodeIdArb, { maxLength: 100, minLength: 100 }) - .noShrink(); - const uniqueNodeIdArb = fc - .array(nodeIdArb, { maxLength: 3, minLength: 3 }) - .noShrink() - .filter((values) => { - return ( - !values[0].equals(values[1]) && - !values[0].equals(values[2]) && - !values[1].equals(values[2]) - ); + testProp( + 'reset buckets', + [testNodesUtils.uniqueNodeIdArb(3), testNodesUtils.nodeIdArrayArb(100)], + async (nodeIds, initialNodes) => { + const getNodeIdMock = jest.fn(); + const dummyKeyRing = { + getNodeId: getNodeIdMock, + } as unknown as KeyRing; + getNodeIdMock.mockImplementation(() => nodeIds[0]); + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyRing: dummyKeyRing, + logger, }); - await fc.assert( - fc.asyncProperty( - uniqueNodeIdArb, - nodeIdArrayArb, - async (nodeIds, initialNodes) => { - getNodeIdMock.mockImplementation(() => nodeIds[0]); - const nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing: dummyKeyRing, - logger, - }); - for (const nodeId of initialNodes) { - await nodeGraph.setNode(nodeId, { - host: '127.0.0.1', - port: utils.getRandomInt(0, 2 ** 16), - } as NodeAddress); - } - const buckets0 = await utils.asyncIterableArray( - nodeGraph.getBuckets(), - ); - // Reset the buckets according to the new node ID - // Note that this should normally be only executed when the key manager NodeID changes - // This means methods that use the KeyRing's node ID cannot be used here in this test - getNodeIdMock.mockImplementation(() => nodeIds[1]); - const nodeIdNew1 = nodeIds[1]; - await nodeGraph.resetBuckets(nodeIdNew1); - const buckets1 = await utils.asyncIterableArray( - nodeGraph.getBuckets(), - ); - expect(buckets1.length > 0).toBe(true); - for (const [bucketIndex, bucket] of buckets1) { - expect(bucket.length > 0).toBe(true); - for (const [nodeId, nodeData] of bucket) { - expect(nodeId.byteLength).toBe(32); - expect(nodesUtils.bucketIndex(nodeIdNew1, nodeId)).toBe( - bucketIndex, - ); - expect(nodeData.address.host).toBe('127.0.0.1'); - // Port of 0 is not allowed - expect(nodeData.address.port > 0).toBe(true); - expect(nodeData.address.port < 2 ** 16).toBe(true); - } - } - expect(buckets1).not.toStrictEqual(buckets0); - // Resetting again should change the space - getNodeIdMock.mockImplementation(() => nodeIds[2]); - const nodeIdNew2 = nodeIds[2]; - await nodeGraph.resetBuckets(nodeIdNew2); - const buckets2 = await utils.asyncIterableArray( - nodeGraph.getBuckets(), - ); - expect(buckets2.length > 0).toBe(true); - for (const [bucketIndex, bucket] of buckets2) { - expect(bucket.length > 0).toBe(true); - for (const [nodeId, nodeData] of bucket) { - expect(nodeId.byteLength).toBe(32); - expect(nodesUtils.bucketIndex(nodeIdNew2, nodeId)).toBe( - bucketIndex, - ); - expect(nodeData.address.host).toBe('127.0.0.1'); - // Port of 0 is not allowed - expect(nodeData.address.port > 0).toBe(true); - expect(nodeData.address.port < 2 ** 16).toBe(true); - } + for (const nodeId of initialNodes) { + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + const buckets0 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + // Reset the buckets according to the new node ID + // Note that this should normally be only executed when the key manager NodeID changes + // This means methods that use the KeyRing's node ID cannot be used here in this test + getNodeIdMock.mockImplementation(() => nodeIds[1]); + const nodeIdNew1 = nodeIds[1]; + await nodeGraph.resetBuckets(nodeIdNew1); + const buckets1 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets1.length > 0).toBe(true); + for (const [bucketIndex, bucket] of buckets1) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(nodeIdNew1, nodeId)).toBe(bucketIndex); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + } + } + expect(buckets1).not.toStrictEqual(buckets0); + // Resetting again should change the space + getNodeIdMock.mockImplementation(() => nodeIds[2]); + const nodeIdNew2 = nodeIds[2]; + await nodeGraph.resetBuckets(nodeIdNew2); + const buckets2 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets2.length > 0).toBe(true); + for (const [bucketIndex, bucket] of buckets2) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(nodeIdNew2, nodeId)).toBe(bucketIndex); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + } + } + expect(buckets2).not.toStrictEqual(buckets1); + // Resetting to the same NodeId results in the same bucket structure + await nodeGraph.resetBuckets(nodeIdNew2); + const buckets3 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets3).toStrictEqual(buckets2); + // Resetting to an existing NodeId + const nodeIdExisting = buckets3[0][1][0][0]; + let nodeIdExistingFound = false; + await nodeGraph.resetBuckets(nodeIdExisting); + const buckets4 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets4.length > 0).toBe(true); + for (const [bucketIndex, bucket] of buckets4) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + if (nodeId.equals(nodeIdExisting)) { + nodeIdExistingFound = true; } - expect(buckets2).not.toStrictEqual(buckets1); - // Resetting to the same NodeId results in the same bucket structure - await nodeGraph.resetBuckets(nodeIdNew2); - const buckets3 = await utils.asyncIterableArray( - nodeGraph.getBuckets(), - ); - expect(buckets3).toStrictEqual(buckets2); - // Resetting to an existing NodeId - const nodeIdExisting = buckets3[0][1][0][0]; - let nodeIdExistingFound = false; - await nodeGraph.resetBuckets(nodeIdExisting); - const buckets4 = await utils.asyncIterableArray( - nodeGraph.getBuckets(), + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(nodeIdExisting, nodeId)).toBe( + bucketIndex, ); - expect(buckets4.length > 0).toBe(true); - for (const [bucketIndex, bucket] of buckets4) { - expect(bucket.length > 0).toBe(true); - for (const [nodeId, nodeData] of bucket) { - if (nodeId.equals(nodeIdExisting)) { - nodeIdExistingFound = true; - } - expect(nodeId.byteLength).toBe(32); - expect(nodesUtils.bucketIndex(nodeIdExisting, nodeId)).toBe( - bucketIndex, - ); - expect(nodeData.address.host).toBe('127.0.0.1'); - // Port of 0 is not allowed - expect(nodeData.address.port > 0).toBe(true); - expect(nodeData.address.port < 2 ** 16).toBe(true); - } - } - expect(buckets4).not.toStrictEqual(buckets3); - // The existing node ID should not be put into the NodeGraph - expect(nodeIdExistingFound).toBe(false); - await nodeGraph.stop(); - }, - ), - { numRuns: 1 }, - ); - }); - test('reset buckets should re-order the buckets', async () => { - const getNodeIdMock = jest.fn(); - const dummyKeyRing = { - getNodeId: getNodeIdMock, - } as unknown as KeyRing; - - const nodeIdArb = fc - .int8Array({ minLength: 32, maxLength: 32 }) - .map((value) => IdInternal.fromBuffer(Buffer.from(value))); - const nodeIdArrayArb = fc - .array(nodeIdArb, { maxLength: 50, minLength: 50 }) - .noShrink(); - const uniqueNodeIdArb = fc - .array(nodeIdArb, { maxLength: 2, minLength: 2 }) - .noShrink() - .filter((values) => { - return !values[0].equals(values[1]); + expect(nodeData.address.host).toBe('127.0.0.1'); + // Port of 0 is not allowed + expect(nodeData.address.port > 0).toBe(true); + expect(nodeData.address.port < 2 ** 16).toBe(true); + } + } + expect(buckets4).not.toStrictEqual(buckets3); + // The existing node ID should not be put into the NodeGraph + expect(nodeIdExistingFound).toBe(false); + await nodeGraph.stop(); + }, + { numRuns: 1 }, + ); + testProp( + 'reset buckets should re-order the buckets', + [testNodesUtils.uniqueNodeIdArb(2), testNodesUtils.nodeIdArrayArb(50)], + async (nodeIds, initialNodes) => { + const getNodeIdMock = jest.fn(); + const dummyKeyRing = { + getNodeId: getNodeIdMock, + } as unknown as KeyRing; + getNodeIdMock.mockImplementation(() => nodeIds[0]); + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyRing: dummyKeyRing, + fresh: true, + logger, }); - await fc.assert( - fc.asyncProperty( - uniqueNodeIdArb, - nodeIdArrayArb, - async (nodeIds, initialNodes) => { - getNodeIdMock.mockImplementation(() => nodeIds[0]); - const nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing: dummyKeyRing, - fresh: true, - logger, - }); - for (const nodeId of initialNodes) { - await nodeGraph.setNode(nodeId, { - host: '127.0.0.1', - port: utils.getRandomInt(0, 2 ** 16), - } as NodeAddress); - } - const buckets0 = await utils.asyncIterableArray( - nodeGraph.getBuckets(), - ); - // Reset the buckets according to the new node ID - // Note that this should normally be only executed when the key manager NodeID changes - // This means methods that use the KeyRing's node ID cannot be used here in this test - getNodeIdMock.mockImplementation(() => nodeIds[1]); - const nodeIdNew1 = nodeIds[1]; - await nodeGraph.resetBuckets(nodeIdNew1); - const buckets1 = await utils.asyncIterableArray( - nodeGraph.getBuckets(), - ); - expect(buckets1).not.toStrictEqual(buckets0); - await nodeGraph.stop(); - }, - ), - { numRuns: 20 }, - ); - }); - test('reset buckets should not corrupt data', async () => { - const getNodeIdMock = jest.fn(); - const dummyKeyRing = { - getNodeId: getNodeIdMock, - } as unknown as KeyRing; - - const nodeIdArb = fc - .int8Array({ minLength: 32, maxLength: 32 }) - .map((value) => IdInternal.fromBuffer(Buffer.from(value))); - const nodeIdArrayArb = fc - .array(nodeIdArb, { maxLength: 10, minLength: 10 }) - .noShrink(); - const uniqueNodeIdArb = fc - .array(nodeIdArb, { maxLength: 2, minLength: 2 }) - .noShrink() - .filter((values) => { - return !values[0].equals(values[1]); + for (const nodeId of initialNodes) { + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + const buckets0 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + // Reset the buckets according to the new node ID + // Note that this should normally be only executed when the key manager NodeID changes + // This means methods that use the KeyRing's node ID cannot be used here in this test + getNodeIdMock.mockImplementation(() => nodeIds[1]); + const nodeIdNew1 = nodeIds[1]; + await nodeGraph.resetBuckets(nodeIdNew1); + const buckets1 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets1).not.toStrictEqual(buckets0); + await nodeGraph.stop(); + }, + { numRuns: 20 }, + ); + testProp( + 'reset buckets should not corrupt data', + [testNodesUtils.uniqueNodeIdArb(2), testNodesUtils.nodeIdArrayArb(10)], + async (nodeIds, initialNodes) => { + const getNodeIdMock = jest.fn(); + const dummyKeyRing = { + getNodeId: getNodeIdMock, + } as unknown as KeyRing; + getNodeIdMock.mockImplementation(() => nodeIds[0]); + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyRing: dummyKeyRing, + fresh: true, + logger, }); - await fc.assert( - fc.asyncProperty( - uniqueNodeIdArb, - nodeIdArrayArb, - async (nodeIds, initialNodes) => { - getNodeIdMock.mockImplementation(() => nodeIds[0]); - const nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing: dummyKeyRing, - fresh: true, - logger, - }); - const nodeAddresses: Map = new Map(); - for (const nodeId of initialNodes) { - const nodeAddress = { - host: '127.0.0.1', - port: utils.getRandomInt(0, 2 ** 16), - } as NodeAddress; - await nodeGraph.setNode(nodeId, nodeAddress); - nodeAddresses.set(nodeId.toString(), nodeAddress); - } - // Reset the buckets according to the new node ID - // Note that this should normally be only executed when the key manager NodeID changes - // This means methods that use the KeyRing's node ID cannot be used here in this test - getNodeIdMock.mockImplementation(() => nodeIds[1]); - const nodeIdNew1 = nodeIds[1]; - await nodeGraph.resetBuckets(nodeIdNew1); - const buckets1 = await utils.asyncIterableArray( - nodeGraph.getBuckets(), - ); - expect(buckets1.length > 0).toBe(true); - for (const [bucketIndex, bucket] of buckets1) { - expect(bucket.length > 0).toBe(true); - for (const [nodeId, nodeData] of bucket) { - expect(nodeId.byteLength).toBe(32); - expect(nodesUtils.bucketIndex(nodeIdNew1, nodeId)).toBe( - bucketIndex, - ); - expect(nodeData.address.host).toBe('127.0.0.1'); - expect(nodeAddresses.get(nodeId.toString())).toBeDefined(); - expect(nodeAddresses.get(nodeId.toString())?.port).toBe( - nodeData.address.port, - ); - } - } - await nodeGraph.stop(); - }, - ), - { numRuns: 20 }, - ); - }); - test('reset buckets to an existing node should remove node', async () => { - const getNodeIdMock = jest.fn(); - const dummyKeyRing = { - getNodeId: getNodeIdMock, - } as unknown as KeyRing; - - const nodeIdArb = fc - .int8Array({ minLength: 32, maxLength: 32 }) - .map((value) => IdInternal.fromBuffer(Buffer.from(value))); - const nodeIdArrayArb = fc - .array(nodeIdArb, { maxLength: 20, minLength: 20 }) - .noShrink(); - await fc.assert( - fc.asyncProperty( - nodeIdArb, - nodeIdArrayArb, - fc.integer({ min: 0, max: 19 }), - async (nodeId, initialNodes, nodeIndex) => { - getNodeIdMock.mockImplementation(() => nodeId); - const nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing: dummyKeyRing, - logger, - }); - for (const nodeId of initialNodes) { - await nodeGraph.setNode(nodeId, { - host: '127.0.0.1', - port: utils.getRandomInt(0, 2 ** 16), - } as NodeAddress); - } - // Reset the buckets according to the new node ID - // Note that this should normally be only executed when the key manager NodeID changes - // This means methods that use the KeyRing's node ID cannot be used here in this test - getNodeIdMock.mockImplementation(() => initialNodes[nodeIndex]); - const nodeIdNew1 = initialNodes[nodeIndex]; - await nodeGraph.resetBuckets(nodeIdNew1); - const buckets1 = await utils.asyncIterableArray( - nodeGraph.getBuckets(), + const nodeAddresses: Map = new Map(); + for (const nodeId of initialNodes) { + const nodeAddress = { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress; + await nodeGraph.setNode(nodeId, nodeAddress); + nodeAddresses.set(nodeId.toString(), nodeAddress); + } + // Reset the buckets according to the new node ID + // Note that this should normally be only executed when the key manager NodeID changes + // This means methods that use the KeyRing's node ID cannot be used here in this test + getNodeIdMock.mockImplementation(() => nodeIds[1]); + const nodeIdNew1 = nodeIds[1]; + await nodeGraph.resetBuckets(nodeIdNew1); + const buckets1 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets1.length > 0).toBe(true); + for (const [bucketIndex, bucket] of buckets1) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId, nodeData] of bucket) { + expect(nodeId.byteLength).toBe(32); + expect(nodesUtils.bucketIndex(nodeIdNew1, nodeId)).toBe(bucketIndex); + expect(nodeData.address.host).toBe('127.0.0.1'); + expect(nodeAddresses.get(nodeId.toString())).toBeDefined(); + expect(nodeAddresses.get(nodeId.toString())?.port).toBe( + nodeData.address.port, ); - expect(buckets1.length > 0).toBe(true); - for (const [, bucket] of buckets1) { - expect(bucket.length > 0).toBe(true); - for (const [nodeId] of bucket) { - // The new node should not be in the graph - expect(nodeIdNew1.equals(nodeId)).toBeFalse(); - } - } - await nodeGraph.stop(); - }, - ), - { numRuns: 15 }, - ); - }); + } + } + await nodeGraph.stop(); + }, + { numRuns: 20 }, + ); + testProp( + 'reset buckets to an existing node should remove node', + [ + testNodesUtils.nodeIdArb, + testNodesUtils.nodeIdArrayArb(20), + fc.integer({ min: 0, max: 19 }), + ], + async (nodeId, initialNodes, nodeIndex) => { + const getNodeIdMock = jest.fn(); + const dummyKeyRing = { + getNodeId: getNodeIdMock, + } as unknown as KeyRing; + getNodeIdMock.mockImplementation(() => nodeId); + const nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyRing: dummyKeyRing, + logger, + }); + for (const nodeId of initialNodes) { + await nodeGraph.setNode(nodeId, { + host: '127.0.0.1', + port: utils.getRandomInt(0, 2 ** 16), + } as NodeAddress); + } + // Reset the buckets according to the new node ID + // Note that this should normally be only executed when the key manager NodeID changes + // This means methods that use the KeyRing's node ID cannot be used here in this test + getNodeIdMock.mockImplementation(() => initialNodes[nodeIndex]); + const nodeIdNew1 = initialNodes[nodeIndex]; + await nodeGraph.resetBuckets(nodeIdNew1); + const buckets1 = await utils.asyncIterableArray(nodeGraph.getBuckets()); + expect(buckets1.length > 0).toBe(true); + for (const [, bucket] of buckets1) { + expect(bucket.length > 0).toBe(true); + for (const [nodeId] of bucket) { + // The new node should not be in the graph + expect(nodeIdNew1.equals(nodeId)).toBeFalse(); + } + } + await nodeGraph.stop(); + }, + { numRuns: 15 }, + ); test('reset buckets is persistent', async () => { const nodeGraph = await NodeGraph.createNodeGraph({ db, diff --git a/tests/nodes/NodeManager.test.ts b/tests/nodes/NodeManager.test.ts index bb9403fa6..704415d8f 100644 --- a/tests/nodes/NodeManager.test.ts +++ b/tests/nodes/NodeManager.test.ts @@ -1,8 +1,8 @@ -import type { CertificatePEM, KeyPairPEM, PublicKeyPEM, PublicKey } from '@/keys/types'; import type { Host, Port } from '@/network/types'; import type { NodeId, NodeAddress } from '@/nodes/types'; import type { Task } from '@/tasks/types'; import type { Key } from '@/keys/types'; +import type { SignedClaim } from '@/claims/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; @@ -20,13 +20,15 @@ import NodeGraph from '@/nodes/NodeGraph'; import NodeManager from '@/nodes/NodeManager'; import Proxy from '@/network/Proxy'; import Sigchain from '@/sigchain/Sigchain'; -import * as claimsUtils from '@/claims/utils'; import { never, promise, promisify, sleep } from '@/utils'; import * as nodesUtils from '@/nodes/utils'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as utils from '@/utils/index'; -import * as nodesTestUtils from './utils'; +import ACL from '@/acl/ACL'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import Token from '@/tokens/Token'; import { generateNodeIdForBucket } from './utils'; +import * as nodesTestUtils from './utils'; import * as testsUtils from '../utils'; describe(`${NodeManager.name} test`, () => { @@ -37,6 +39,8 @@ describe(`${NodeManager.name} test`, () => { let dataDir: string; let nodeGraph: NodeGraph; let taskManager: TaskManager; + let acl: ACL; + let gestaltGraph: GestaltGraph; let nodeConnectionManager: NodeConnectionManager; let proxy: Proxy; let keyRing: KeyRing; @@ -134,6 +138,15 @@ describe(`${NodeManager.name} test`, () => { pingTimeout: 4000, logger, }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + acl, + db, + logger, + }); }); afterEach(async () => { await taskManager.stopProcessing(); @@ -142,14 +155,10 @@ describe(`${NodeManager.name} test`, () => { mockedPingNode.mockImplementation(async (_) => true); await nodeConnectionManager.stop(); await nodeGraph.stop(); - await nodeGraph.destroy(); await sigchain.stop(); - await sigchain.destroy(); await taskManager.stop(); await db.stop(); - await db.destroy(); await keyRing.stop(); - await keyRing.destroy(); await proxy.stop(); utpSocket.close(); utpSocket.unref(); @@ -183,10 +192,10 @@ describe(`${NodeManager.name} test`, () => { port: server.proxy.getProxyPort(), }; await nodeGraph.setNode(serverNodeId, serverNodeAddress); - nodeManager = new NodeManager({ db, sigchain, + gestaltGraph, keyRing, nodeGraph, nodeConnectionManager, @@ -225,7 +234,6 @@ describe(`${NodeManager.name} test`, () => { expect(active2).toBe(true); // Turn server node offline again await server.stop(); - await server.destroy(); // Check if active // Case 3: pre-existing connection no longer active, so offline const active3 = await nodeManager.pingNode(serverNodeId, undefined, { @@ -236,58 +244,10 @@ describe(`${NodeManager.name} test`, () => { // Clean up await nodeManager?.stop(); await server?.stop(); - await server?.destroy(); } }, globalThis.failedConnectionTimeout * 2, ); - test('getPublicKey', async () => { - let server: PolykeyAgent | undefined; - let nodeManager: NodeManager | undefined; - try { - server = await PolykeyAgent.createPolykeyAgent({ - password: 'password', - nodePath: path.join(dataDir, 'server'), - networkConfig: { - proxyHost: '127.0.0.1' as Host, - }, - logger: logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, - }); - const serverNodeId = server.keyRing.getNodeId(); - const serverNodeAddress: NodeAddress = { - host: server.proxy.getProxyHost(), - port: server.proxy.getProxyPort(), - }; - await nodeGraph.setNode(serverNodeId, serverNodeAddress); - - nodeManager = new NodeManager({ - db, - sigchain, - keyRing, - nodeGraph, - nodeConnectionManager, - taskManager, - logger, - }); - await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager }); - - // We want to get the public key of the server - const key = await nodeManager.getPublicKey(serverNodeId); - const expectedKey = server.keyRing.keyPair.publicKey; - expect(keysUtils.publicKeyToPEM(key)).toEqual(keysUtils.publicKeyToPEM(expectedKey)); - } finally { - // Clean up - await nodeManager?.stop(); - await server?.stop(); - await server?.destroy(); - } - }); describe('Cross signing claims', () => { // These tests follow the following process (from the perspective of Y): // 1. X -> sends notification (to start cross signing request) -> Y @@ -301,13 +261,11 @@ describe(`${NodeManager.name} test`, () => { let x: PolykeyAgent; let xNodeId: NodeId; let xNodeAddress: NodeAddress; - let xPublicKey: PublicKey; let yDataDir: string; let y: PolykeyAgent; let yNodeId: NodeId; let yNodeAddress: NodeAddress; - let yPublicKey: PublicKey; beforeAll(async () => { xDataDir = await fs.promises.mkdtemp( @@ -332,7 +290,6 @@ describe(`${NodeManager.name} test`, () => { host: externalHost, port: x.proxy.getProxyPort(), }; - xPublicKey = x.keyRing.keyPair.publicKey; yDataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), @@ -355,7 +312,6 @@ describe(`${NodeManager.name} test`, () => { host: externalHost, port: y.proxy.getProxyPort(), }; - yPublicKey = y.keyRing.keyPair.publicKey; await x.nodeGraph.setNode(yNodeId, yNodeAddress); await y.nodeGraph.setNode(xNodeId, xNodeAddress); @@ -386,83 +342,56 @@ describe(`${NodeManager.name} test`, () => { // 2. X <- sends its intermediary signed claim <- Y // 3. X -> sends doubly signed claim (Y's intermediary) + its own intermediary claim -> Y // 4. X <- sends doubly signed claim (X's intermediary) <- Y + await x.acl.setNodeAction(yNodeId, 'claim'); await y.nodeManager.claimNode(xNodeId); // Check X's sigchain state - const xChain = await x.sigchain.getChainData(); - expect(Object.keys(xChain).length).toBe(1); - // Iterate just to be safe, but expected to only have this single claim - for (const claimId of Object.keys(xChain)) { - const claim = xChain[claimId]; - const decoded = claimsUtils.decodeClaim(claim); - expect(decoded).toStrictEqual({ - payload: { - hPrev: null, - seq: 1, - data: { - type: 'node', - node1: nodesUtils.encodeNodeId(xNodeId), - node2: nodesUtils.encodeNodeId(yNodeId), - }, - iat: expect.any(Number), - }, - signatures: expect.any(Object), - }); - const signatureNodeIds = Object.keys(decoded.signatures); - expect(signatureNodeIds.length).toBe(2); - // Verify the 2 signatures - expect(signatureNodeIds).toContain(nodesUtils.encodeNodeId(xNodeId)); - expect(await claimsUtils.verifyClaimSignature(claim, xPublicKey)).toBe( - true, - ); - expect(signatureNodeIds).toContain(nodesUtils.encodeNodeId(yNodeId)); - expect(await claimsUtils.verifyClaimSignature(claim, yPublicKey)).toBe( - true, - ); + let claimX: SignedClaim | undefined; + for await (const [, claim_] of x.sigchain.getSignedClaims()) { + claimX = claim_; } + if (claimX == null) fail('No claims exist'); + expect(claimX.payload.typ).toBe('ClaimLinkNode'); + expect(claimX.payload.iss).toBe(nodesUtils.encodeNodeId(yNodeId)); + expect(claimX.payload.sub).toBe(nodesUtils.encodeNodeId(xNodeId)); + // Expect it to be signed by both sides + const tokenX = Token.fromSigned(claimX); + expect( + tokenX.verifyWithPublicKey(x.keyRing.keyPair.publicKey), + ).toBeTrue(); + expect( + tokenX.verifyWithPublicKey(y.keyRing.keyPair.publicKey), + ).toBeTrue(); // Check Y's sigchain state - const yChain = await y.sigchain.getChainData(); - expect(Object.keys(yChain).length).toBe(1); - // Iterate just to be safe, but expected to only have this single claim - for (const claimId of Object.keys(yChain)) { - const claim = yChain[claimId]; - const decoded = claimsUtils.decodeClaim(claim); - expect(decoded).toStrictEqual({ - payload: { - hPrev: null, - seq: 1, - data: { - type: 'node', - node1: nodesUtils.encodeNodeId(yNodeId), - node2: nodesUtils.encodeNodeId(xNodeId), - }, - iat: expect.any(Number), - }, - signatures: expect.any(Object), - }); - const signatureNodeIds = Object.keys(decoded.signatures); - expect(signatureNodeIds.length).toBe(2); - // Verify the 2 signatures - expect(signatureNodeIds).toContain(nodesUtils.encodeNodeId(xNodeId)); - expect(await claimsUtils.verifyClaimSignature(claim, xPublicKey)).toBe( - true, - ); - expect(signatureNodeIds).toContain(nodesUtils.encodeNodeId(yNodeId)); - expect(await claimsUtils.verifyClaimSignature(claim, yPublicKey)).toBe( - true, - ); + let claimY: SignedClaim | undefined; + for await (const [, claim_] of y.sigchain.getSignedClaims()) { + claimY = claim_; } + if (claimY == null) fail('No claims exist'); + expect(claimY.payload.typ).toBe('ClaimLinkNode'); + expect(claimY.payload.iss).toBe(nodesUtils.encodeNodeId(yNodeId)); + expect(claimY.payload.sub).toBe(nodesUtils.encodeNodeId(xNodeId)); + // Expect it to be signed by both sides + const tokenY = Token.fromSigned(claimY); + expect( + tokenY.verifyWithPublicKey(x.keyRing.keyPair.publicKey), + ).toBeTrue(); + expect( + tokenY.verifyWithPublicKey(y.keyRing.keyPair.publicKey), + ).toBeTrue(); }); test('can request chain data', async () => { let nodeManager: NodeManager | undefined; try { // Cross signing claims + await x.acl.setNodeAction(yNodeId, 'claim'); await y.nodeManager.claimNode(xNodeId); nodeManager = new NodeManager({ db, sigchain, + gestaltGraph, keyRing, nodeGraph, nodeConnectionManager, @@ -473,7 +402,6 @@ describe(`${NodeManager.name} test`, () => { await nodeConnectionManager.start({ nodeManager }); await nodeGraph.setNode(xNodeId, xNodeAddress); - // We want to get the public key of the server const chainData = JSON.stringify( await nodeManager.requestChainData(xNodeId), @@ -490,6 +418,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -518,6 +447,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -558,6 +488,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -609,6 +540,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -662,6 +594,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -708,6 +641,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -756,7 +690,6 @@ describe(`${NodeManager.name} test`, () => { } finally { // Clean up await server?.stop(); - await server?.destroy(); await nodeManager.stop(); } }); @@ -766,6 +699,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -805,6 +739,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -856,6 +791,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph: tempNodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -887,7 +823,6 @@ describe(`${NodeManager.name} test`, () => { } finally { await nodeManager.stop(); await tempNodeGraph.stop(); - await tempNodeGraph.destroy(); } }); test('should update deadline when updating a bucket', async () => { @@ -895,6 +830,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -953,6 +889,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager, taskManager, @@ -971,6 +908,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -1024,6 +962,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, @@ -1065,6 +1004,7 @@ describe(`${NodeManager.name} test`, () => { db, sigchain: {} as Sigchain, keyRing, + gestaltGraph, nodeGraph, nodeConnectionManager: dummyNodeConnectionManager, taskManager, diff --git a/tests/nodes/utils.test.ts b/tests/nodes/utils.test.ts index 03e19eb49..21cb7108a 100644 --- a/tests/nodes/utils.test.ts +++ b/tests/nodes/utils.test.ts @@ -22,7 +22,7 @@ describe('nodes/utils', () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); - const dbKey = await keysUtils.generateKey(); + const dbKey = keysUtils.generateKey(); const dbPath = `${dataDir}/db`; db = await DB.createDB({ dbPath, diff --git a/tests/nodes/utils.ts b/tests/nodes/utils.ts index dae30cbb0..100ed7c3e 100644 --- a/tests/nodes/utils.ts +++ b/tests/nodes/utils.ts @@ -1,6 +1,7 @@ import type { NodeId, NodeAddress } from '@/nodes/types'; import type PolykeyAgent from '@/PolykeyAgent'; import { IdInternal } from '@matrixai/id'; +import * as fc from 'fast-check'; import * as keysUtils from '@/keys/utils'; import { bigInt2Bytes } from '@/utils'; @@ -79,4 +80,30 @@ async function nodesConnect(localNode: PolykeyAgent, remoteNode: PolykeyAgent) { } as NodeAddress); } -export { generateRandomNodeId, generateNodeIdForBucket, nodesConnect }; +const nodeIdArb = fc + .int8Array({ minLength: 32, maxLength: 32 }) + .map((value) => IdInternal.fromBuffer(Buffer.from(value))); + +const nodeIdArrayArb = (length: number) => + fc.array(nodeIdArb, { maxLength: length, minLength: length }).noShrink(); + +const uniqueNodeIdArb = (length: number) => + fc + .array(nodeIdArb, { maxLength: length, minLength: length }) + .noShrink() + .filter((values) => { + for (let i = 0; i < values.length; i++) { + for (let j = i; j < values.length; j++) { + if (values[i].equals(values[j])) return true; + } + } + return false; + }); +export { + generateRandomNodeId, + generateNodeIdForBucket, + nodesConnect, + nodeIdArb, + nodeIdArrayArb, + uniqueNodeIdArb, +}; diff --git a/tests/notifications/NotificationsManager.test.ts b/tests/notifications/NotificationsManager.test.ts index bfae01ed6..ebf9e0879 100644 --- a/tests/notifications/NotificationsManager.test.ts +++ b/tests/notifications/NotificationsManager.test.ts @@ -1,8 +1,9 @@ -import type { NodeId } from '@/ids/types'; +import type { NodeId, NodeIdEncoded } from '@/ids/types'; import type { Host, Port } from '@/network/types'; import type { VaultActions, VaultName } from '@/vaults/types'; import type { Notification, NotificationData } from '@/notifications/types'; import type { Key } from '@/keys/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; import fs from 'fs'; import os from 'os'; import path from 'path'; @@ -25,7 +26,6 @@ import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; import * as utils from '@/utils/index'; import * as testUtils from '../utils'; -import { CertificatePEMChain } from '@/keys/types'; import * as testsUtils from '../utils/index'; describe('NotificationsManager', () => { @@ -39,12 +39,8 @@ describe('NotificationsManager', () => { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, ]); - const senderIdEncoded = nodesUtils.encodeNodeId( - IdInternal.create([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 5, - ]), - ); + const senderIdEncoded = nodesUtils.encodeNodeId(senderId); + const targetIdEncoded = 'Target' as NodeIdEncoded; const vaultIdGenerator = vaultsUtils.createVaultIdGenerator(); /** * Shared ACL, DB, NodeManager, KeyRing for all tests @@ -138,6 +134,7 @@ describe('NotificationsManager', () => { nodeConnectionManager, nodeGraph, taskManager, + gestaltGraph: {} as GestaltGraph, logger, }); await nodeManager.start(); @@ -255,15 +252,15 @@ describe('NotificationsManager', () => { await receiver.notificationsManager.readNotifications(); expect(receivedNotifications).toHaveLength(3); expect(receivedNotifications[0].data).toEqual(vaultNotification); - expect(receivedNotifications[0].senderId).toBe( + expect(receivedNotifications[0].iss).toBe( nodesUtils.encodeNodeId(keyRing.getNodeId()), ); expect(receivedNotifications[1].data).toEqual(gestaltNotification); - expect(receivedNotifications[1].senderId).toBe( + expect(receivedNotifications[1].iss).toBe( nodesUtils.encodeNodeId(keyRing.getNodeId()), ); expect(receivedNotifications[2].data).toEqual(generalNotification); - expect(receivedNotifications[2].senderId).toBe( + expect(receivedNotifications[2].iss).toBe( nodesUtils.encodeNodeId(keyRing.getNodeId()), ); // Reverse side-effects @@ -336,21 +333,26 @@ describe('NotificationsManager', () => { logger, }); const notification1: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification2: Notification = { + typ: 'notification', data: { type: 'GestaltInvite', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification3: Notification = { + typ: 'notification', data: { type: 'VaultShare', vaultId: vaultsUtils.encodeVaultId(vaultIdGenerator()), @@ -360,7 +362,8 @@ describe('NotificationsManager', () => { pull: null, } as VaultActions, }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -376,11 +379,11 @@ describe('NotificationsManager', () => { await notificationsManager.readNotifications(); expect(receivedNotifications).toHaveLength(3); expect(receivedNotifications[0].data).toEqual(notification3.data); - expect(receivedNotifications[0].senderId).toEqual(senderIdEncoded); + expect(receivedNotifications[0].iss).toEqual(senderIdEncoded); expect(receivedNotifications[1].data).toEqual(notification2.data); - expect(receivedNotifications[1].senderId).toEqual(senderIdEncoded); + expect(receivedNotifications[1].iss).toEqual(senderIdEncoded); expect(receivedNotifications[2].data).toEqual(notification1.data); - expect(receivedNotifications[2].senderId).toEqual(senderIdEncoded); + expect(receivedNotifications[2].iss).toEqual(senderIdEncoded); // Reverse side-effects await notificationsManager.clearNotifications(); await acl.unsetNodePerm(senderId); @@ -397,11 +400,13 @@ describe('NotificationsManager', () => { logger, }); const notification: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; // No permissions @@ -435,11 +440,13 @@ describe('NotificationsManager', () => { logger, }); const notification: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -469,27 +476,33 @@ describe('NotificationsManager', () => { logger, }); const notification1: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg1', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification2: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg2', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification3: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg3', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -523,27 +536,33 @@ describe('NotificationsManager', () => { logger, }); const notification1: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg1', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification2: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg2', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification3: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg3', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -576,27 +595,33 @@ describe('NotificationsManager', () => { logger, }); const notification1: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg1', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification2: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg2', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification3: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg3', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -628,27 +653,33 @@ describe('NotificationsManager', () => { logger, }); const notification1: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg1', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification2: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg2', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification3: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg3', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -684,27 +715,33 @@ describe('NotificationsManager', () => { logger, }); const notification1: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg1', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification2: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg2', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification3: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg3', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -737,10 +774,12 @@ describe('NotificationsManager', () => { logger, }); const notification: Notification = { + typ: 'notification', data: { type: 'GestaltInvite', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -770,11 +809,13 @@ describe('NotificationsManager', () => { logger, }); const notification: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -803,19 +844,23 @@ describe('NotificationsManager', () => { logger, }); const notification1: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg1', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; const notification2: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg2', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { @@ -834,13 +879,13 @@ describe('NotificationsManager', () => { }); expect(unreadNotifications).toHaveLength(1); expect(unreadNotifications[0].data).toEqual(notification1.data); - expect(unreadNotifications[0].senderId).toBe(notification1.senderId); + expect(unreadNotifications[0].iss).toBe(notification1.iss); const latestNotification = await notificationsManager.readNotifications({ number: 1, }); expect(latestNotification).toHaveLength(1); expect(latestNotification[0].data).toEqual(notification2.data); - expect(latestNotification[0].senderId).toBe(notification2.senderId); + expect(latestNotification[0].iss).toBe(notification2.iss); // Reverse side-effects await notificationsManager.clearNotifications(); await acl.unsetNodePerm(senderId); @@ -857,11 +902,13 @@ describe('NotificationsManager', () => { logger, }); const notification: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg', }, - senderId: senderIdEncoded, + iss: senderIdEncoded, + sub: targetIdEncoded, isRead: false, }; await acl.setNodePerm(senderId, { diff --git a/tests/notifications/utils.test.ts b/tests/notifications/utils.test.ts index dfd801c6d..22bfe9f12 100644 --- a/tests/notifications/utils.test.ts +++ b/tests/notifications/utils.test.ts @@ -1,17 +1,19 @@ import type { Notification, NotificationData } from '@/notifications/types'; import type { VaultActions, VaultName } from '@/vaults/types'; -import { createPublicKey } from 'crypto'; -import { EmbeddedJWK, jwtVerify, exportJWK } from 'jose'; +import type { KeyPairLocked } from '@/keys/types'; import * as keysUtils from '@/keys/utils'; import * as notificationsUtils from '@/notifications/utils'; -import * as notificationsErrors from '@/notifications/errors'; import * as vaultsUtils from '@/vaults/utils'; import * as nodesUtils from '@/nodes/utils'; +import * as validationErrors from '@/validation/errors'; import * as testNodesUtils from '../nodes/utils'; describe('Notifications utils', () => { - const nodeId = testNodesUtils.generateRandomNodeId(); + const keyPair = keysUtils.generateKeyPair() as KeyPairLocked; + const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); + const targetNodeId = testNodesUtils.generateRandomNodeId(); + const targetNodeIdEncoded = nodesUtils.encodeNodeId(targetNodeId); const vaultIdGenerator = vaultsUtils.createVaultIdGenerator(); const vaultId = vaultIdGenerator(); const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId); @@ -40,23 +42,28 @@ describe('Notifications utils', () => { } }); - test('signs notifications', async () => { + test('verifies and decodes signed notifications', async () => { const generalNotification: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg', } as NotificationData, - senderId: nodeIdEncoded, + iss: nodeIdEncoded, + sub: targetNodeIdEncoded, isRead: false, }; const gestaltInviteNotification: Notification = { + typ: 'notification', data: { type: 'GestaltInvite', } as NotificationData, - senderId: nodeIdEncoded, + iss: nodeIdEncoded, + sub: targetNodeIdEncoded, isRead: false, }; const vaultShareNotification: Notification = { + typ: 'notification', data: { type: 'VaultShare', vaultId: vaultIdEncoded, @@ -66,129 +73,54 @@ describe('Notifications utils', () => { pull: null, } as VaultActions, } as NotificationData, - senderId: nodeIdEncoded, + iss: nodeIdEncoded, + sub: targetNodeIdEncoded, isRead: false, }; - const keyPair = keysUtils.generateKeyPair(); - const jwkPublicKey = await exportJWK(createPublicKey(keyPair.publicKey)); - - const signedGeneralNotification = await notificationsUtils.signNotification( - generalNotification, - keyPair, - ); - const signedGestaltInviteNotification = - await notificationsUtils.signNotification( - gestaltInviteNotification, - keyPair, - ); - const signedVaultShareNotification = - await notificationsUtils.signNotification( - vaultShareNotification, + const signedGeneralNotification = + await notificationsUtils.generateNotification( + generalNotification, keyPair, ); - - let result = await jwtVerify(signedGeneralNotification, EmbeddedJWK, {}); - expect(result.payload.data).toEqual({ - type: 'General', - message: 'msg', - }); - expect(result.payload.senderId).toEqual(nodeIdEncoded); - expect(result.payload.isRead).toBeFalsy(); - expect(result.protectedHeader.jwk).toEqual(jwkPublicKey); - - result = await jwtVerify(signedGestaltInviteNotification, EmbeddedJWK, {}); - expect(result.payload.data).toEqual({ - type: 'GestaltInvite', - }); - expect(result.payload.senderId).toEqual(nodeIdEncoded); - expect(result.payload.isRead).toBeFalsy(); - expect(result.protectedHeader.jwk).toEqual(jwkPublicKey); - - result = await jwtVerify(signedVaultShareNotification, EmbeddedJWK, {}); - expect(result.payload.data).toEqual({ - type: 'VaultShare', - vaultId: vaultIdEncoded, - vaultName: 'vaultName', - actions: { - clone: null, - pull: null, - }, - }); - expect(result.payload.senderId).toEqual(nodeIdEncoded); - expect(result.payload.isRead).toBeFalsy(); - expect(result.protectedHeader.jwk).toEqual(jwkPublicKey); - }); - - test('verifies and decodes signed notifications', async () => { - const generalNotification: Notification = { - data: { - type: 'General', - message: 'msg', - } as NotificationData, - senderId: nodeIdEncoded, - isRead: false, - }; - const gestaltInviteNotification: Notification = { - data: { - type: 'GestaltInvite', - } as NotificationData, - senderId: nodeIdEncoded, - isRead: false, - }; - const vaultShareNotification: Notification = { - data: { - type: 'VaultShare', - vaultId: vaultIdEncoded, - vaultName: 'vaultName' as VaultName, - actions: { - clone: null, - pull: null, - } as VaultActions, - } as NotificationData, - senderId: nodeIdEncoded, - isRead: false, - }; - - const keyPair = keysUtils.generateKeyPair(); - - const signedGeneralNotification = await notificationsUtils.signNotification( - generalNotification, - keyPair, - ); const signedGestaltInviteNotification = - await notificationsUtils.signNotification( + await notificationsUtils.generateNotification( gestaltInviteNotification, keyPair, ); const signedVaultShareNotification = - await notificationsUtils.signNotification( + await notificationsUtils.generateNotification( vaultShareNotification, keyPair, ); const decodedGeneralNotification = - await notificationsUtils.verifyAndDecodeNotif(signedGeneralNotification); + await notificationsUtils.verifyAndDecodeNotif( + signedGeneralNotification, + targetNodeId, + ); expect(decodedGeneralNotification.data).toEqual({ type: 'General', message: 'msg', }); - expect(decodedGeneralNotification.senderId).toEqual(nodeIdEncoded); + expect(decodedGeneralNotification.iss).toEqual(nodeIdEncoded); expect(decodedGeneralNotification.isRead).toBeFalsy(); const decodedGestaltInviteNotification = await notificationsUtils.verifyAndDecodeNotif( signedGestaltInviteNotification, + targetNodeId, ); expect(decodedGestaltInviteNotification.data).toEqual({ type: 'GestaltInvite', }); - expect(decodedGestaltInviteNotification.senderId).toEqual(nodeIdEncoded); + expect(decodedGestaltInviteNotification.iss).toEqual(nodeIdEncoded); expect(decodedGestaltInviteNotification.isRead).toBeFalsy(); const decodedVaultShareNotification = await notificationsUtils.verifyAndDecodeNotif( signedVaultShareNotification, + targetNodeId, ); expect(decodedVaultShareNotification.data).toEqual({ type: 'VaultShare', @@ -199,37 +131,37 @@ describe('Notifications utils', () => { pull: null, }, }); - expect(decodedVaultShareNotification.senderId).toEqual(nodeIdEncoded); + expect(decodedVaultShareNotification.iss).toEqual(nodeIdEncoded); expect(decodedVaultShareNotification.isRead).toBeFalsy(); }); - test('validates correct notifications', async () => { const nodeIdOther = testNodesUtils.generateRandomNodeId(); const nodeIdOtherEncoded = nodesUtils.encodeNodeId(nodeIdOther); const generalNotification: Notification = { + typ: 'notification', data: { type: 'General', message: 'msg', } as NotificationData, - senderId: nodeIdOtherEncoded, + iss: nodeIdOtherEncoded, + sub: targetNodeIdEncoded, isRead: false, }; - expect( - notificationsUtils.validateNotification(generalNotification), - ).toEqual(generalNotification); + notificationsUtils.parseNotification(generalNotification); - const gestaltInviteNotification: Notification = { + const gestaltInviteNotification = { + typ: 'notification', data: { type: 'GestaltInvite', } as NotificationData, - senderId: nodeIdEncoded, + iss: nodeIdEncoded, + sub: targetNodeIdEncoded, isRead: false, }; - expect( - notificationsUtils.validateNotification(gestaltInviteNotification), - ).toEqual(gestaltInviteNotification); + notificationsUtils.parseNotification(gestaltInviteNotification); const vaultShareNotification: Notification = { + typ: 'notification', data: { type: 'VaultShare', vaultId: vaultIdEncoded, @@ -239,12 +171,11 @@ describe('Notifications utils', () => { pull: null, } as VaultActions, } as NotificationData, - senderId: nodeIdEncoded, + iss: nodeIdEncoded, + sub: targetNodeIdEncoded, isRead: false, }; - expect( - notificationsUtils.validateNotification(vaultShareNotification), - ).toEqual(vaultShareNotification); + notificationsUtils.parseNotification(vaultShareNotification); }); test('does not validate incorrect notifications', async () => { @@ -253,24 +184,24 @@ describe('Notifications utils', () => { data: { type: 'Invalid Type', }, - senderId: nodeIdEncoded, + iss: nodeIdEncoded, isRead: false, }; - expect(() => - notificationsUtils.validateNotification(notification1), - ).toThrow(notificationsErrors.ErrorNotificationsInvalidType); + expect(() => notificationsUtils.parseNotification(notification1)).toThrow( + validationErrors.ErrorParse, + ); // Missing field (message) const notification2 = { data: { type: 'General', }, - senderId: nodeIdEncoded, + iss: nodeIdEncoded, isRead: false, }; - expect(() => - notificationsUtils.validateNotification(notification2), - ).toThrow(notificationsErrors.ErrorNotificationsInvalidType); + expect(() => notificationsUtils.parseNotification(notification2)).toThrow( + validationErrors.ErrorParse, + ); // Extra field (message) const notification3 = { @@ -278,12 +209,12 @@ describe('Notifications utils', () => { type: 'GestaltInvite', message: 'msg', }, - senderId: nodeIdEncoded, + iss: nodeIdEncoded, isRead: false, }; - expect(() => - notificationsUtils.validateNotification(notification3), - ).toThrow(notificationsErrors.ErrorNotificationsInvalidType); + expect(() => notificationsUtils.parseNotification(notification3)).toThrow( + validationErrors.ErrorParse, + ); // Incorrect field type (actions) const notification4 = { @@ -293,12 +224,12 @@ describe('Notifications utils', () => { vaultName: 'vaultName' as VaultName, actions: 'clone + pull', }, - senderId: nodeIdEncoded, + iss: nodeIdEncoded, isRead: false, }; - expect(() => - notificationsUtils.validateNotification(notification4), - ).toThrow(notificationsErrors.ErrorNotificationsInvalidType); + expect(() => notificationsUtils.parseNotification(notification4)).toThrow( + validationErrors.ErrorParse, + ); // Incorrect field name (sendingId) const notification5 = { @@ -309,8 +240,8 @@ describe('Notifications utils', () => { sendingId: nodeIdEncoded, isRead: false, }; - expect(() => - notificationsUtils.validateNotification(notification5), - ).toThrow(notificationsErrors.ErrorNotificationsValidationFailed); + expect(() => notificationsUtils.parseNotification(notification5)).toThrow( + validationErrors.ErrorParse, + ); }); }); diff --git a/tests/scratch.test.ts b/tests/scratch.test.ts index c73456c42..d314e5b37 100644 --- a/tests/scratch.test.ts +++ b/tests/scratch.test.ts @@ -4,6 +4,7 @@ import type KeyRing from '@/keys/KeyRing'; import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; import type NodeGraph from '@/nodes/NodeGraph'; import type Sigchain from '@/sigchain/Sigchain'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import NodeManager from '@/nodes/NodeManager'; @@ -21,6 +22,7 @@ describe('scratch', () => { nodeGraph: {} as NodeGraph, nodeConnectionManager: {} as NodeConnectionManager, taskManager: {} as TaskManager, + gestaltGraph: {} as GestaltGraph, logger, }); logger.info('checking names'); diff --git a/tests/sigchain/Sigchain.old.test.ts b/tests/sigchain/Sigchain.old.test.ts deleted file mode 100644 index 8d99731ab..000000000 --- a/tests/sigchain/Sigchain.old.test.ts +++ /dev/null @@ -1,527 +0,0 @@ -import type { ProviderId, IdentityId } from '@/identities/types'; -import type { NodeIdEncoded } from '@/ids/types'; -import type { Claim, ClaimData } from '@/claims/types'; -import type { Key } from '@/keys/types'; -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -import KeyRing from '@/keys/KeyRing'; -import Sigchain from '@/sigchain/Sigchain'; -import * as claimsUtils from '@/claims/utils'; -import * as sigchainErrors from '@/sigchain/errors'; -import * as nodesUtils from '@/nodes/utils'; -import * as keysUtils from '@/keys/utils'; -import * as utils from '@/utils/index'; -import * as testNodesUtils from '../nodes/utils'; - -describe('Sigchain', () => { - const logger = new Logger('Sigchain Test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'password'; - const srcNodeIdEncoded = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - const nodeId2Encoded = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - const nodeId3Encoded = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - const nodeIdAEncoded = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - const nodeIdBEncoded = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - const nodeIdCEncoded = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - const nodeIdDEncoded = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const keysPath = `${dataDir}/keys`; - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = `${dataDir}/db`; - db = await DB.createDB({ - dbPath, - logger, - crypto: { - key: keyRing.dbKey, - ops: { - encrypt: async (key, plainText) => { - return keysUtils.encryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(plainText), - ); - }, - decrypt: async (key, cipherText) => { - return keysUtils.decryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(cipherText), - ); - }, - }, - }, - }); - }); - afterEach(async () => { - await db.stop(); - await db.destroy(); - await keyRing.stop(); - await keyRing.destroy(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - - test('sigchain readiness', async () => { - const sigchain = await Sigchain.createSigchain({ keyRing, db, logger }); - await expect(async () => { - await sigchain.destroy(); - }).rejects.toThrow(sigchainErrors.ErrorSigchainRunning); - // Should be a noop - await sigchain.start(); - await sigchain.stop(); - await sigchain.destroy(); - await expect(async () => { - await sigchain.start(); - }).rejects.toThrow(sigchainErrors.ErrorSigchainDestroyed); - }); - test('async start initialises the sequence number', async () => { - const sigchain = await Sigchain.createSigchain({ keyRing, db, logger }); - const sequenceNumber = await db.withTransactionF(async (tran) => - // @ts-ignore - get protected method - sigchain.getSequenceNumber(tran), - ); - expect(sequenceNumber).toBe(0); - await sigchain.stop(); - }); - test('adds and retrieves a cryptolink, verifies signature', async () => { - const sigchain = await Sigchain.createSigchain({ keyRing, db, logger }); - const cryptolink: ClaimData = { - type: 'node', - node1: srcNodeIdEncoded, - node2: nodeId2Encoded, - }; - const [claimId] = await sigchain.addClaim(cryptolink); - - expect(claimId).toBeTruthy(); - const claim = await sigchain.getClaim(claimId!); - - // Check the claim is correct - const decoded = claimsUtils.decodeClaim(claim); - const expected: Claim = { - payload: { - hPrev: null, - seq: 1, - data: { - type: 'node', - node1: srcNodeIdEncoded, - node2: nodeId2Encoded, - }, - iat: expect.any(Number), - }, - signatures: expect.any(Object), - }; - expect(decoded).toStrictEqual(expected); - - // Check the signature is valid - expect(Object.keys(decoded.signatures).length).toBe(1); - expect(decoded.signatures[srcNodeIdEncoded]).toBeDefined; - expect(decoded.signatures[srcNodeIdEncoded].header).toStrictEqual({ - alg: 'RS256', - kid: srcNodeIdEncoded, - }); - const verified = await claimsUtils.verifyClaimSignature( - claim, - keyRing.keyPair.publicKey, - ); - expect(verified).toBe(true); - - await sigchain.stop(); - }); - test('adds and retrieves 2 cryptolinks, verifies signatures and hash', async () => { - const sigchain = await Sigchain.createSigchain({ keyRing, db, logger }); - const cryptolink: ClaimData = { - type: 'node', - node1: srcNodeIdEncoded, - node2: nodeId2Encoded, - }; - const [claimId1] = await sigchain.addClaim(cryptolink); - - const cryptolink2: ClaimData = { - type: 'node', - node1: srcNodeIdEncoded, - node2: nodeId3Encoded, - }; - const [claimId2] = await sigchain.addClaim(cryptolink2); - - const claim1 = await sigchain.getClaim(claimId1!); - const claim2 = await sigchain.getClaim(claimId2!); - - // Check the claim is correct - const decoded1 = claimsUtils.decodeClaim(claim1); - const expected1: Claim = { - payload: { - hPrev: null, - seq: 1, - data: { - type: 'node', - node1: srcNodeIdEncoded, - node2: nodeId2Encoded, - }, - iat: expect.any(Number), - }, - signatures: expect.any(Object), - }; - expect(decoded1).toStrictEqual(expected1); - const decoded2 = claimsUtils.decodeClaim(claim2); - const expected2: Claim = { - payload: { - hPrev: expect.any(String), - seq: 2, - data: { - type: 'node', - node1: srcNodeIdEncoded, - node2: nodeId3Encoded, - }, - iat: expect.any(Number), - }, - signatures: expect.any(Object), - }; - expect(decoded2).toStrictEqual(expected2); - - // Check the signature is valid in each claim - expect(Object.keys(decoded1.signatures).length).toBe(1); - expect(decoded1.signatures[srcNodeIdEncoded]).toBeDefined; - expect(decoded1.signatures[srcNodeIdEncoded].header).toStrictEqual({ - alg: 'RS256', - kid: srcNodeIdEncoded, - }); - const verified1 = await claimsUtils.verifyClaimSignature( - claim1, - keyRing.keyPair.publicKey, - ); - expect(verified1).toBe(true); - - expect(Object.keys(decoded2.signatures).length).toBe(1); - expect(decoded2.signatures[srcNodeIdEncoded]).toBeDefined; - expect(decoded2.signatures[srcNodeIdEncoded].header).toStrictEqual({ - alg: 'RS256', - kid: srcNodeIdEncoded, - }); - const verified2 = await claimsUtils.verifyClaimSignature( - claim2, - keyRing.keyPair.publicKey, - ); - expect(verified2).toBe(true); - - // Check the hash of the previous claim is correct - const verifiedHash = claimsUtils.verifyHashOfClaim( - claim1, - decoded2.payload.hPrev as string, - ); - expect(verifiedHash).toBe(true); - - await sigchain.stop(); - }); - test('adds an existing claim', async () => { - const sigchain = await Sigchain.createSigchain({ keyRing, db, logger }); - // Create a claim - // Firstly, check that we can add an existing claim if it's the first claim - // in the sigchain - const hPrev1 = await db.withTransactionF(async (tran) => - // @ts-ignore - get protected method - sigchain.getHashPrevious(tran), - ); - const seq1 = await db.withTransactionF(async (tran) => - // @ts-ignore - get protected method - sigchain.getSequenceNumber(tran), - ); - expect(hPrev1).toBeNull(); - expect(seq1).toBe(0); - const claim1 = await claimsUtils.createClaim({ - privateKey: keyRing.keyPair.privateKey, - hPrev: hPrev1, - seq: seq1 + 1, - data: { - type: 'node', - node1: nodeIdAEncoded, - node2: nodeIdBEncoded, - }, - kid: nodeIdAEncoded, - }); - await sigchain.addExistingClaim(claim1); - const hPrev2 = await db.withTransactionF(async (tran) => - // @ts-ignore - get protected method - sigchain.getHashPrevious(tran), - ); - const seq2 = await db.withTransactionF(async (tran) => - // @ts-ignore - get protected method - sigchain.getSequenceNumber(tran), - ); - expect(hPrev2).not.toBeNull(); - expect(seq2).toBe(1); - - // Now check we can add an additional claim after the first - const claim2 = await claimsUtils.createClaim({ - privateKey: keyRing.keyPair.privateKey, - hPrev: hPrev2, - seq: seq2 + 1, - data: { - type: 'node', - node1: nodeIdAEncoded, - node2: nodeIdCEncoded, - }, - kid: nodeIdAEncoded, - }); - await sigchain.addExistingClaim(claim2); - const hPrev3 = await db.withTransactionF(async (tran) => - // @ts-ignore - get protected method - sigchain.getHashPrevious(tran), - ); - const seq3 = await db.withTransactionF(async (tran) => - // @ts-ignore - get protected method - sigchain.getSequenceNumber(tran), - ); - expect(hPrev3).not.toBeNull(); - expect(seq3).toBe(2); - - // Check a claim with an invalid hash will throw an exception - const claimInvalidHash = await claimsUtils.createClaim({ - privateKey: keyRing.keyPair.privateKey, - hPrev: 'invalidHash', - seq: seq3 + 1, - data: { - type: 'node', - node1: nodeIdAEncoded, - node2: nodeIdDEncoded, - }, - kid: nodeIdDEncoded, - }); - await expect(() => - sigchain.addExistingClaim(claimInvalidHash), - ).rejects.toThrow(sigchainErrors.ErrorSigchainInvalidHash); - - // Check a claim with an invalid sequence number will throw an exception - const claimInvalidSeqNum = await claimsUtils.createClaim({ - privateKey: keyRing.keyPair.privateKey, - hPrev: hPrev3, - seq: 1, - data: { - type: 'node', - node1: nodeIdAEncoded, - node2: nodeIdDEncoded, - }, - kid: nodeIdDEncoded, - }); - await expect(() => - sigchain.addExistingClaim(claimInvalidSeqNum), - ).rejects.toThrow(sigchainErrors.ErrorSigchainInvalidSequenceNum); - }); - test('retrieves chain data', async () => { - const sigchain = await Sigchain.createSigchain({ keyRing, db, logger }); - const node2s: NodeIdEncoded[] = []; - - // Add 10 claims - for (let i = 1; i <= 5; i++) { - const node2 = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - node2s.push(node2); - const nodeLink: ClaimData = { - type: 'node', - node1: srcNodeIdEncoded, - node2: node2, - }; - await sigchain.addClaim(nodeLink); - } - for (let i = 6; i <= 10; i++) { - const identityLink: ClaimData = { - type: 'identity', - node: srcNodeIdEncoded, - provider: ('ProviderId' + i.toString()) as ProviderId, - identity: ('IdentityId' + i.toString()) as IdentityId, - }; - await sigchain.addClaim(identityLink); - } - - const chainData = await sigchain.getChainData(); - const chainDataKeys = Object.keys(chainData).sort(); - for (let i = 1; i <= 10; i++) { - const claim = chainData[chainDataKeys[i - 1]]; - const decodedClaim = claimsUtils.decodeClaim(claim); - if (i <= 5) { - const node2 = node2s[i - 1]; - expect(decodedClaim.payload.data).toEqual({ - type: 'node', - node1: srcNodeIdEncoded, - node2: node2, - }); - } else { - expect(decodedClaim.payload.data).toEqual({ - type: 'identity', - node: srcNodeIdEncoded, - provider: ('ProviderId' + i.toString()) as ProviderId, - identity: ('IdentityId' + i.toString()) as IdentityId, - }); - } - } - }); - test('retrieves all cryptolinks (nodes and identities) from sigchain (in expected lexicographic order)', async () => { - const sigchain = await Sigchain.createSigchain({ keyRing, db, logger }); - const nodes: NodeIdEncoded[] = []; - - // Add 30 claims - for (let i = 1; i <= 30; i++) { - // If even, add a node link - if (i % 2 === 0) { - const node2 = nodesUtils.encodeNodeId( - testNodesUtils.generateRandomNodeId(), - ); - nodes[i] = node2; - const nodeLink: ClaimData = { - type: 'node', - node1: srcNodeIdEncoded, - node2: node2, - }; - await sigchain.addClaim(nodeLink); - // If odd, add an identity link - } else { - const identityLink: ClaimData = { - type: 'identity', - node: srcNodeIdEncoded, - provider: ('ProviderId' + i.toString()) as ProviderId, - identity: ('IdentityId' + i.toString()) as IdentityId, - }; - await sigchain.addClaim(identityLink); - } - } - - // Creating a map of seq -> claimId - const seqMap = await sigchain.getSeqMap(); - - // Verify the nodes: - const nodeLinks = await sigchain.getClaims('node'); - const decodedNodes = nodeLinks.map((n) => { - return claimsUtils.decodeClaim(n); - }); - let expectedSeqNum = 2; - let i = 0; - for (const d of decodedNodes) { - // Check they've been returned in numerical order (according to the - // lexicographic integer num) - const seqNum = d.payload.seq; - expect(seqNum).toBe(expectedSeqNum); - - // Verify the structure of claim - const node2 = nodes[expectedSeqNum]; - const expected: Claim = { - payload: { - hPrev: claimsUtils.hashClaim( - await sigchain.getClaim(seqMap[seqNum - 1]), - ), - seq: expectedSeqNum, - data: { - type: 'node', - node1: srcNodeIdEncoded, - node2: node2, - }, - iat: expect.any(Number), - }, - signatures: expect.any(Object), - }; - expect(d).toEqual(expected); - // Verify the signature - expect(Object.keys(d.signatures).length).toBe(1); - expect(d.signatures[srcNodeIdEncoded]).toBeDefined; - expect(d.signatures[srcNodeIdEncoded].header).toStrictEqual({ - alg: 'RS256', - kid: srcNodeIdEncoded, - }); - const verified = await claimsUtils.verifyClaimSignature( - nodeLinks[i], - keyRing.keyPair.publicKey, - ); - expect(verified).toBe(true); - // Because every node link was an even number, we can simply add 2 to - // the current sequence number to get the next expected one. - expectedSeqNum = seqNum + 2; - i++; - } - - // Verify the identities: - const identityLinks = await sigchain.getClaims('identity'); - const decodedIdentities = identityLinks.map((n) => { - return claimsUtils.decodeClaim(n); - }); - // Reset these counts - expectedSeqNum = 1; - i = 0; - for (const id of decodedIdentities) { - // Check they've been returned in numerical order (according to the - // lexicographic integer num) - const seqNum = id.payload.seq; - expect(seqNum).toBe(expectedSeqNum); - - // Verify the structure of claim - const expected: Claim = { - payload: { - hPrev: - expectedSeqNum === 1 - ? null - : claimsUtils.hashClaim( - await sigchain.getClaim(seqMap[seqNum - 1]), - ), - seq: expectedSeqNum, - data: { - type: 'identity', - node: srcNodeIdEncoded, - provider: ('ProviderId' + expectedSeqNum.toString()) as ProviderId, - identity: ('IdentityId' + expectedSeqNum.toString()) as IdentityId, - }, - iat: expect.any(Number), - }, - signatures: expect.any(Object), - }; - expect(id).toEqual(expected); - // Verify the signature - expect(Object.keys(id.signatures).length).toBe(1); - expect(id.signatures[srcNodeIdEncoded]).toBeDefined; - expect(id.signatures[srcNodeIdEncoded].header).toStrictEqual({ - alg: 'RS256', - kid: srcNodeIdEncoded, - }); - const verified = await claimsUtils.verifyClaimSignature( - nodeLinks[i], - keyRing.keyPair.publicKey, - ); - expect(verified).toBe(true); - // Because every identity link was an odd number, we can simply add 2 to - // the current sequence number to get the next expected one. - expectedSeqNum = seqNum + 2; - i++; - } - - await sigchain.stop(); - }); -}); diff --git a/tests/sigchain/Sigchain.test.ts b/tests/sigchain/Sigchain.test.ts index 16e2eba7f..c213f40c0 100644 --- a/tests/sigchain/Sigchain.test.ts +++ b/tests/sigchain/Sigchain.test.ts @@ -82,7 +82,7 @@ describe(Sigchain.name, () => { await sigchain.stop(); await sigchain.destroy(); await expect(sigchain.start()).rejects.toThrow( - sigchainErrors.ErrorSigchainDestroyed + sigchainErrors.ErrorSigchainDestroyed, ); await expect(async () => { for await (const _ of sigchain.getClaims()) { @@ -92,18 +92,14 @@ describe(Sigchain.name, () => { }); testProp( 'claims must have claim default properties', - [ - fc.array(fc.object(), { minLength: 1, maxLength: 32 }), - ], + [fc.array(fc.object(), { minLength: 1, maxLength: 32 })], async (datas) => { - const sigchain = await Sigchain.createSigchain( - { - keyRing, - db, - logger, - fresh: true - } - ); + const sigchain = await Sigchain.createSigchain({ + keyRing, + db, + logger, + fresh: true, + }); const now = new Date(); for (const data of datas) { // Force adding properties that will be overridden @@ -117,7 +113,7 @@ describe(Sigchain.name, () => { prevClaimId: 12345, prevDigest: 55555, } as unknown as ClaimInput, - now + now, ); // Other properties may exist, but these must always exist expect(signedClaim.payload).toMatchObject({ @@ -126,54 +122,49 @@ describe(Sigchain.name, () => { nbf: utils.getUnixtime(now), prevClaimId: expect.toBeOneOf([null, expect.any(String)]), prevDigest: expect.toBeOneOf([null, expect.any(String)]), - seq: expect.any(Number) + seq: expect.any(Number), }); } await sigchain.stop(); - } + }, ); - testProp('claim sequence number is monotonic', [ - fc.array(fc.object(), { minLength: 1, maxLength: 32 }), - ], async (datas) => { - const sigchain = await Sigchain.createSigchain( - { + testProp( + 'claim sequence number is monotonic', + [fc.array(fc.object(), { minLength: 1, maxLength: 32 })], + async (datas) => { + const sigchain = await Sigchain.createSigchain({ keyRing, db, logger, - fresh: true + fresh: true, + }); + let seq = 0; + for (const data of datas) { + const [, signedClaim] = await sigchain.addClaim(data as ClaimInput); + seq++; + expect(signedClaim.payload.seq).toBe(seq); } - ); - let seq = 0; - for (const data of datas) { - const [, signedClaim] = await sigchain.addClaim( - data as ClaimInput, - ); - seq++; - expect(signedClaim.payload.seq).toBe(seq); - } - await sigchain.stop(); - }); + await sigchain.stop(); + }, + ); testProp( 'adding claims is serialised', - [ - fc.scheduler(), - fc.array(fc.object(), { minLength: 1, maxLength: 32 }), - ], + [fc.scheduler(), fc.array(fc.object(), { minLength: 1, maxLength: 32 })], async (s, datas) => { - const sigchain = await Sigchain.createSigchain( - { - keyRing, - db, - logger, - fresh: true - } - ); + const sigchain = await Sigchain.createSigchain({ + keyRing, + db, + logger, + fresh: true, + }); // Build up concurrent calls to add claim - let addClaimPs: Array> = []; + const addClaimPs: Array> = []; for (const data of datas) { addClaimPs.push( // Delay the `Sigchain.addClaim` call - s.schedule(Promise.resolve()).then(() => sigchain.addClaim(data as ClaimInput)) + s + .schedule(Promise.resolve()) + .then(() => sigchain.addClaim(data as ClaimInput)), ); } // Scheduler will randomly call add claim @@ -184,139 +175,204 @@ describe(Sigchain.name, () => { expect(result.status).toBe('fulfilled'); } // Get all chain of claims in descending order - const signedClaims = await AsyncIterable.as(sigchain.getSignedClaims({ - order: 'desc' - })).toArray(); + const signedClaims = await AsyncIterable.as( + sigchain.getSignedClaims({ + order: 'desc', + }), + ).toArray(); expect(signedClaims.length).toBe(datas.length); let digest: string | null = null; for (const [, signedClaim] of signedClaims) { if (digest != null) { const currentDigest = claimsUtils.hashSignedClaim( signedClaim, - 'blake2b-256' + 'blake2b-256', ); const currentDigestEncoded = claimsUtils.encodeSignedClaimDigest( currentDigest, - 'blake2b-256' + 'blake2b-256', ); expect(currentDigestEncoded).toBe(digest); } digest = signedClaim.payload.prevDigest; } await sigchain.stop(); - } + }, ); testProp( 'claims are all signed by the current node', - [ - fc.array(fc.object(), { minLength: 1, maxLength: 32 }), - ], + [fc.array(fc.object(), { minLength: 1, maxLength: 32 })], async (datas) => { - const sigchain = await Sigchain.createSigchain( - { - keyRing, - db, - logger, - fresh: true - } - ); + const sigchain = await Sigchain.createSigchain({ + keyRing, + db, + logger, + fresh: true, + }); for (const data of datas) { const [, signedClaim] = await sigchain.addClaim(data as ClaimInput); const token = Token.fromSigned(signedClaim); expect(token.verifyWithPublicKey(keyRing.keyPair.publicKey)).toBe(true); } await sigchain.stop(); - } + }, ); - testProp('claims form a hash chain', [ - fc.array(fc.object(), { minLength: 1, maxLength: 32 }), - ], async (datas) => { - const sigchain = await Sigchain.createSigchain( - { + testProp( + 'claims form a hash chain', + [fc.array(fc.object(), { minLength: 1, maxLength: 32 })], + async (datas) => { + const sigchain = await Sigchain.createSigchain({ keyRing, db, logger, - fresh: true - } - ); - const claimIdSignedClaims: Array<[ClaimId, SignedClaim]> = []; - for (const [index, data] of datas.entries()) { - const claimIdSignedClaim = await sigchain.addClaim(data as ClaimInput); - if (claimIdSignedClaims.length > 0) { - const prevDigest = claimsUtils.hashSignedClaim( - claimIdSignedClaims[index - 1][1], - 'blake2b-256' - ); - const prevDigestEncoded = claimsUtils.encodeSignedClaimDigest( - prevDigest, - 'blake2b-256' - ); - expect(claimIdSignedClaim[1].payload.prevDigest).toBe(prevDigestEncoded); - } else { - expect(claimIdSignedClaim[1].payload.prevDigest).toBeNull(); + fresh: true, + }); + const claimIdSignedClaims: Array<[ClaimId, SignedClaim]> = []; + for (const [index, data] of datas.entries()) { + const claimIdSignedClaim = await sigchain.addClaim(data as ClaimInput); + if (claimIdSignedClaims.length > 0) { + const prevDigest = claimsUtils.hashSignedClaim( + claimIdSignedClaims[index - 1][1], + 'blake2b-256', + ); + const prevDigestEncoded = claimsUtils.encodeSignedClaimDigest( + prevDigest, + 'blake2b-256', + ); + expect(claimIdSignedClaim[1].payload.prevDigest).toBe( + prevDigestEncoded, + ); + } else { + expect(claimIdSignedClaim[1].payload.prevDigest).toBeNull(); + } + claimIdSignedClaims.push(claimIdSignedClaim); } - claimIdSignedClaims.push(claimIdSignedClaim); - } - await sigchain.stop(); - }); - testProp('get claim(s), get signed claim(s) and get signatures', [ - fc.array(fc.object(), { minLength: 1, maxLength: 32 }), - ], async (datas) => { - const sigchain = await Sigchain.createSigchain( - { + await sigchain.stop(); + }, + ); + testProp( + 'get claim(s), get signed claim(s) and get signatures', + [fc.array(fc.object(), { minLength: 1, maxLength: 32 })], + async (datas) => { + const sigchain = await Sigchain.createSigchain({ keyRing, db, logger, - fresh: true + fresh: true, + }); + const claimIdSignedClaims: Array<[ClaimId, SignedClaim]> = []; + for (const data of datas) { + const claimIdSignedClaim = await sigchain.addClaim(data as ClaimInput); + claimIdSignedClaims.push(claimIdSignedClaim); } - ); - const claimIdSignedClaims: Array<[ClaimId, SignedClaim]> = []; - for (const data of datas) { - const claimIdSignedClaim = await sigchain.addClaim(data as ClaimInput); - claimIdSignedClaims.push(claimIdSignedClaim); - } - for (const [claimId, signedClaim] of claimIdSignedClaims) { - const claim_ = await sigchain.getClaim(claimId); - expect(claim_).toEqual(signedClaim.payload); - const signedClaim_ = await sigchain.getSignedClaim(claimId); - expect(signedClaim_).toEqual(signedClaim); - const signatures = await sigchain.getSignatures(claimId); - expect(signatures).toEqual(signedClaim.signatures); - } - const signedClaims = await AsyncIterable.as(sigchain.getSignedClaims()).toArray(); - expect(signedClaims).toEqual(claimIdSignedClaims); - const claims = await AsyncIterable.as(sigchain.getClaims()).toArray(); - expect(claims).toEqual(claimIdSignedClaims.map(c => [c[0], c[1].payload])); - await sigchain.stop(); - }); - testProp('get last claim, get last signed claim, get last claim ID, get last sequence', [ - fc.array(fc.object(), { minLength: 1, maxLength: 32 }), - ], async (datas) => { - const sigchain = await Sigchain.createSigchain( - { + for (const [claimId, signedClaim] of claimIdSignedClaims) { + const claim_ = await sigchain.getClaim(claimId); + expect(claim_).toEqual(signedClaim.payload); + const signedClaim_ = await sigchain.getSignedClaim(claimId); + expect(signedClaim_).toEqual(signedClaim); + const signatures = await sigchain.getSignatures(claimId); + expect(signatures).toEqual(signedClaim.signatures); + } + const signedClaims = await AsyncIterable.as( + sigchain.getSignedClaims(), + ).toArray(); + expect(signedClaims).toEqual(claimIdSignedClaims); + const claims = await AsyncIterable.as(sigchain.getClaims()).toArray(); + expect(claims).toEqual( + claimIdSignedClaims.map((c) => [c[0], c[1].payload]), + ); + await sigchain.stop(); + }, + ); + testProp( + 'get last claim, get last signed claim, get last claim ID, get last sequence', + [fc.array(fc.object(), { minLength: 1, maxLength: 32 })], + async (datas) => { + const sigchain = await Sigchain.createSigchain({ keyRing, db, logger, - fresh: true + fresh: true, + }); + const claimIdSignedClaims: Array<[ClaimId, SignedClaim]> = []; + for (const data of datas) { + const claimIdSignedClaim = await sigchain.addClaim(data as ClaimInput); + claimIdSignedClaims.push(claimIdSignedClaim); } - ); - const claimIdSignedClaims: Array<[ClaimId, SignedClaim]> = []; - for (const data of datas) { - const claimIdSignedClaim = await sigchain.addClaim(data as ClaimInput); - claimIdSignedClaims.push(claimIdSignedClaim); + const lastClaimIdSignedClaims = + claimIdSignedClaims[claimIdSignedClaims.length - 1]; + const lastClaimId = await sigchain.getLastClaimId(); + expect(lastClaimId).toEqual(lastClaimIdSignedClaims[0]); + const lastSequenceNumber = await sigchain.getLastSequenceNumber(); + expect(lastSequenceNumber).toEqual( + lastClaimIdSignedClaims[1].payload.seq, + ); + const lastClaim = await sigchain.getLastClaim(); + expect(lastClaim).toEqual([ + lastClaimIdSignedClaims[0], + lastClaimIdSignedClaims[1].payload, + ]); + const lastSignedClaim = await sigchain.getLastSignedClaim(); + expect(lastSignedClaim).toEqual(lastClaimIdSignedClaims); + await sigchain.stop(); + }, + ); + test('getClaims with seek ascending', async () => { + const sigchain = await Sigchain.createSigchain({ + keyRing, + db, + logger, + fresh: true, + }); + const claims: Array<[ClaimId, SignedClaim]> = []; + for (let i = 0; i < 3; i++) { + claims.push(await sigchain.addClaim({})); } - const lastClaimIdSignedClaims = claimIdSignedClaims[claimIdSignedClaims.length - 1]; - const lastClaimId = await sigchain.getLastClaimId(); - expect(lastClaimId).toEqual(lastClaimIdSignedClaims[0]); - const lastSequenceNumber = await sigchain.getLastSequenceNumber(); - expect(lastSequenceNumber).toEqual(lastClaimIdSignedClaims[1].payload.seq); - const lastClaim = await sigchain.getLastClaim(); - expect(lastClaim).toEqual([ - lastClaimIdSignedClaims[0], - lastClaimIdSignedClaims[1].payload - ]); - const lastSignedClaim = await sigchain.getLastSignedClaim(); - expect(lastSignedClaim).toEqual(lastClaimIdSignedClaims); - await sigchain.stop(); + const claimsAsc = await AsyncIterable.as( + sigchain.getClaims({ seek: claims[1][0], order: 'asc' }), + ).toArray(); + expect(claimsAsc).toHaveLength(2); + // The claim we seeked to is included + expect(claimsAsc[0][0].equals(claims[1][0])).toBeTrue(); + // And the claim after + expect(claimsAsc[1][0].equals(claims[2][0])).toBeTrue(); + }); + test('getClaims with seek descending', async () => { + const sigchain = await Sigchain.createSigchain({ + keyRing, + db, + logger, + fresh: true, + }); + const claims: Array<[ClaimId, SignedClaim]> = []; + for (let i = 0; i < 3; i++) { + claims.push(await sigchain.addClaim({})); + } + const claimsAsc = await AsyncIterable.as( + sigchain.getClaims({ seek: claims[1][0], order: 'desc' }), + ).toArray(); + expect(claimsAsc).toHaveLength(2); + // The claim we seeked to is included + expect(claimsAsc[0][0].equals(claims[1][0])).toBeTrue(); + // And the claim after + expect(claimsAsc[1][0].equals(claims[0][0])).toBeTrue(); + }); + test('getClaims with seek with limit', async () => { + const sigchain = await Sigchain.createSigchain({ + keyRing, + db, + logger, + fresh: true, + }); + const claims: Array<[ClaimId, SignedClaim]> = []; + for (let i = 0; i < 3; i++) { + claims.push(await sigchain.addClaim({})); + } + const claimsAsc = await AsyncIterable.as( + sigchain.getClaims({ seek: claims[1][0], limit: 1 }), + ).toArray(); + expect(claimsAsc).toHaveLength(1); + // The claim we seeked to is included + expect(claimsAsc[0][0].equals(claims[1][0])).toBeTrue(); }); }); diff --git a/tests/tokens/Token.test.ts b/tests/tokens/Token.test.ts index 9121be29f..217b3e7aa 100644 --- a/tests/tokens/Token.test.ts +++ b/tests/tokens/Token.test.ts @@ -1,6 +1,6 @@ import type { TokenHeaderSignatureEncoded, - TokenPayloadEncoded + TokenPayloadEncoded, } from '@/tokens/types'; import { testProp, fc } from '@fast-check/jest'; import Token from '@/tokens/Token'; @@ -12,59 +12,53 @@ import * as testsKeysUtils from '../keys/utils'; describe(Token.name, () => { testProp( 'creating Token from payload', - [ - testsTokensUtils.tokenPayloadArb - ], + [testsTokensUtils.tokenPayloadArb], (tokenPayload) => { const token = Token.fromPayload(tokenPayload); expect(token.payload).toStrictEqual(tokenPayload); expect(token.payloadEncoded).toStrictEqual( - tokensUtils.generateTokenPayload(tokenPayload) + tokensUtils.generateTokenPayload(tokenPayload), ); - } + }, ); testProp( 'creating Token from signed token', - [ - testsTokensUtils.signedTokenArb - ], + [testsTokensUtils.signedTokenArb], (signedToken) => { const token = Token.fromSigned(signedToken); expect(token.payload).toStrictEqual(signedToken.payload); expect(token.payloadEncoded).toStrictEqual( - tokensUtils.generateTokenPayload(signedToken.payload) + tokensUtils.generateTokenPayload(signedToken.payload), ); expect(token.signatures).toStrictEqual(signedToken.signatures); expect(token.signaturesEncoded).toStrictEqual( - signedToken.signatures.map( - headerSignature => tokensUtils.generateTokenHeaderSignature(headerSignature) - ) + signedToken.signatures.map((headerSignature) => + tokensUtils.generateTokenHeaderSignature(headerSignature), + ), ); const signedToken_ = token.toSigned(); expect(signedToken_).toEqual(signedToken); - } + }, ); testProp( 'creating Token from signed token encoded', - [ - testsTokensUtils.signedTokenEncodedArb - ], + [testsTokensUtils.signedTokenEncodedArb], (signedTokenEncoded) => { const token = Token.fromEncoded(signedTokenEncoded); expect(token.payload).toStrictEqual(token.payload); expect(token.payloadEncoded).toStrictEqual( - tokensUtils.generateTokenPayload(token.payload) + tokensUtils.generateTokenPayload(token.payload), ); const signedToken = tokensUtils.parseSignedToken(signedTokenEncoded); expect(token.signatures).toStrictEqual(signedToken.signatures); expect(token.signaturesEncoded).toStrictEqual( - signedToken.signatures.map( - headerSignature => tokensUtils.generateTokenHeaderSignature(headerSignature) - ) + signedToken.signatures.map((headerSignature) => + tokensUtils.generateTokenHeaderSignature(headerSignature), + ), ); const signedTokenEncoded_ = token.toEncoded(); expect(signedTokenEncoded_).toStrictEqual(signedTokenEncoded); - } + }, ); testProp( 'creating Token from invalid signed token encoded results in parse error', @@ -74,16 +68,16 @@ describe(Token.name, () => { signatures: fc.array( fc.record({ protected: fc.string(), - signature: fc.string() - }) as fc.Arbitrary - ) - }) + signature: fc.string(), + }) as fc.Arbitrary, + ), + }), ], (signedTokenEncodedIncorrect) => { expect(() => { Token.fromEncoded(signedTokenEncodedIncorrect); }).toThrow(tokensErrors.ErrorTokensSignedParse); - } + }, ); testProp( 'signing and verifying', @@ -99,30 +93,18 @@ describe(Token.name, () => { keyCorrect, keyIncorrect, keyPairCorrect, - keyPairIncorrect + keyPairIncorrect, ) => { const token = Token.fromPayload(tokenPayload); token.signWithKey(keyCorrect); token.signWithPrivateKey(keyPairCorrect.privateKey); - expect( - token.verifyWithKey(keyCorrect) - ).toBe(true); - expect( - token.verifyWithPublicKey( - keyPairCorrect.publicKey - ) - ).toBe(true); - expect( - token.verifyWithKey(keyIncorrect) - ).toBe(false); - expect( - token.verifyWithPublicKey( - keyPairIncorrect.publicKey - ) - ).toBe(false); + expect(token.verifyWithKey(keyCorrect)).toBe(true); + expect(token.verifyWithPublicKey(keyPairCorrect.publicKey)).toBe(true); + expect(token.verifyWithKey(keyIncorrect)).toBe(false); + expect(token.verifyWithPublicKey(keyPairIncorrect.publicKey)).toBe(false); expect(token.signatures).toHaveLength(2); expect(token.signaturesEncoded).toHaveLength(2); - } + }, ); testProp( 'signing with the same key results in duplicate signature error', @@ -141,32 +123,26 @@ describe(Token.name, () => { expect(() => { token.signWithPrivateKey(keyPair); }).toThrow(tokensErrors.ErrorTokensDuplicateSignature); - } + }, ); testProp( 'encode and decode', - [ - testsTokensUtils.signedTokenArb, - ], + [testsTokensUtils.signedTokenArb], (signedToken) => { const token = Token.fromSigned(signedToken); const signedTokenEncoded = token.toEncoded(); const token_ = Token.fromEncoded(signedTokenEncoded); const signedToken_ = token_.toSigned(); expect(signedToken_).toEqual(signedToken); - } + }, ); testProp( 'JSON stringify stringifies the signed token encoded', - [ - testsTokensUtils.signedTokenEncodedArb, - ], + [testsTokensUtils.signedTokenEncodedArb], (signedTokenEncoded) => { const token = Token.fromEncoded(signedTokenEncoded); const signedTokenEncoded_ = JSON.stringify(token); - expect(signedTokenEncoded_).toEqual( - JSON.stringify(signedTokenEncoded) - ); - } + expect(signedTokenEncoded_).toEqual(JSON.stringify(signedTokenEncoded)); + }, ); }); diff --git a/tests/tokens/schemas.test.ts b/tests/tokens/schemas.test.ts index 69759c700..67f70f859 100644 --- a/tests/tokens/schemas.test.ts +++ b/tests/tokens/schemas.test.ts @@ -5,19 +5,19 @@ import * as testsTokensUtils from './utils'; describe('tokens/schemas', () => { testProp( 'validate signed token encoded', - [ - testsTokensUtils.signedTokenEncodedArb, - fc.object() - ], - ( - signedTokenEncodedCorrect, - signedTokenEncodedIncorrect - ) => { - expect(tokensSchemas.validateSignedTokenEncoded(signedTokenEncodedCorrect)).toBe(true); + [testsTokensUtils.signedTokenEncodedArb, fc.object()], + (signedTokenEncodedCorrect, signedTokenEncodedIncorrect) => { + expect( + tokensSchemas.validateSignedTokenEncoded(signedTokenEncodedCorrect), + ).toBe(true); expect(tokensSchemas.validateSignedTokenEncoded.errors).toBeNull(); - expect(tokensSchemas.validateSignedTokenEncoded(signedTokenEncodedIncorrect)).toBe(false); + expect( + tokensSchemas.validateSignedTokenEncoded(signedTokenEncodedIncorrect), + ).toBe(false); expect(tokensSchemas.validateSignedTokenEncoded.errors).not.toBeNull(); - expect(tokensSchemas.validateSignedTokenEncoded.errors!.length).toBeGreaterThan(0); - } + expect( + tokensSchemas.validateSignedTokenEncoded.errors!.length, + ).toBeGreaterThan(0); + }, ); }); diff --git a/tests/tokens/utils.test.ts b/tests/tokens/utils.test.ts index 564caa785..a928af6ad 100644 --- a/tests/tokens/utils.test.ts +++ b/tests/tokens/utils.test.ts @@ -7,47 +7,42 @@ import * as testsTokensUtils from './utils'; describe('tokens/utils', () => { testProp( 'generate token signature', - [ testsTokensUtils.tokenSignatureArb, ], - ( tokenSignature) => { - const tokenSignatureEncoded = tokensUtils.generateTokenSignature(tokenSignature); - const tokenSignature_ = tokensUtils.parseTokenSignature(tokenSignatureEncoded); + [testsTokensUtils.tokenSignatureArb], + (tokenSignature) => { + const tokenSignatureEncoded = + tokensUtils.generateTokenSignature(tokenSignature); + const tokenSignature_ = tokensUtils.parseTokenSignature( + tokenSignatureEncoded, + ); expect(tokenSignature_).toStrictEqual(tokenSignature); - } + }, ); testProp( 'parse token signature', - [ - testsTokensUtils.tokenSignatureEncodedArb, - fc.string() - ], - ( - tokenSignatureEncodedCorrect, - tokenSignatureEncodedIncorrect - ) => { + [testsTokensUtils.tokenSignatureEncodedArb, fc.string()], + (tokenSignatureEncodedCorrect, tokenSignatureEncodedIncorrect) => { const tokenSignatureEncodedIncorrectBuffer = Buffer.from( - tokenSignatureEncodedIncorrect, 'base64url' + tokenSignatureEncodedIncorrect, + 'base64url', ); fc.pre( !keysUtils.isSignature(tokenSignatureEncodedIncorrectBuffer) && - !keysUtils.isMAC(tokenSignatureEncodedIncorrectBuffer) + !keysUtils.isMAC(tokenSignatureEncodedIncorrectBuffer), ); expect(() => { - tokensUtils.parseTokenSignature( - tokenSignatureEncodedCorrect - ); + tokensUtils.parseTokenSignature(tokenSignatureEncodedCorrect); }).not.toThrow(); expect(() => { - tokensUtils.parseTokenSignature( - tokenSignatureEncodedIncorrect - ); + tokensUtils.parseTokenSignature(tokenSignatureEncodedIncorrect); }).toThrow(validationErrors.ErrorParse); - } + }, ); testProp( 'generate token payload', - [ testsTokensUtils.tokenPayloadArb, ], - ( tokenPayload ) => { - const tokenPayloadEncoded = tokensUtils.generateTokenPayload(tokenPayload); + [testsTokensUtils.tokenPayloadArb], + (tokenPayload) => { + const tokenPayloadEncoded = + tokensUtils.generateTokenPayload(tokenPayload); const tokenPayload_ = tokensUtils.parseTokenPayload(tokenPayloadEncoded); // Use `toEqual` to avoid matching `undefined` properties expect(tokenPayload_).toEqual(tokenPayload); @@ -55,32 +50,24 @@ describe('tokens/utils', () => { ); testProp( 'parse token payload', - [ - testsTokensUtils.tokenPayloadEncodedArb, - fc.string() - ], + [testsTokensUtils.tokenPayloadEncodedArb, fc.string()], (tokenPayloadEncodedCorrect, tokenPayloadEncodedIncorrect) => { expect(() => { - tokensUtils.parseTokenPayload( - tokenPayloadEncodedCorrect - ); + tokensUtils.parseTokenPayload(tokenPayloadEncodedCorrect); }).not.toThrow(); expect(() => { - tokensUtils.parseTokenPayload( - tokenPayloadEncodedIncorrect - ); + tokensUtils.parseTokenPayload(tokenPayloadEncodedIncorrect); }).toThrow(validationErrors.ErrorParse); - } + }, ); testProp( 'generate token protected header', - [ testsTokensUtils.tokenProtectedHeaderArb, ], - ( tokenProtectedHeader ) => { - const tokenProtectedHeaderEncoded = tokensUtils.generateTokenProtectedHeader( - tokenProtectedHeader - ); + [testsTokensUtils.tokenProtectedHeaderArb], + (tokenProtectedHeader) => { + const tokenProtectedHeaderEncoded = + tokensUtils.generateTokenProtectedHeader(tokenProtectedHeader); const tokenProtectedHeader_ = tokensUtils.parseTokenProtectedHeader( - tokenProtectedHeaderEncoded + tokenProtectedHeaderEncoded, ); // Use `toEqual` to avoid matching `undefined` properties expect(tokenProtectedHeader_).toEqual(tokenProtectedHeader); @@ -88,70 +75,64 @@ describe('tokens/utils', () => { ); testProp( 'parse token protected header', - [ - testsTokensUtils.tokenProtectedHeaderEncodedArb, - fc.string() - ], - (tokenProtectedHeaderEncodedCorrect, tokenProtectedHeaderEncodedIncorrect) => { + [testsTokensUtils.tokenProtectedHeaderEncodedArb, fc.string()], + ( + tokenProtectedHeaderEncodedCorrect, + tokenProtectedHeaderEncodedIncorrect, + ) => { expect(() => { tokensUtils.parseTokenProtectedHeader( - tokenProtectedHeaderEncodedCorrect + tokenProtectedHeaderEncodedCorrect, ); }).not.toThrow(); expect(() => { tokensUtils.parseTokenProtectedHeader( - tokenProtectedHeaderEncodedIncorrect + tokenProtectedHeaderEncodedIncorrect, ); }).toThrow(validationErrors.ErrorParse); - } + }, ); testProp( 'generate token header signature', - [ - testsTokensUtils.tokenHeaderSignatureArb, - ], - ( tokenHeaderSignature ) => { - const tokenHeaderSignatureEncoded = tokensUtils.generateTokenHeaderSignature( - tokenHeaderSignature - ); + [testsTokensUtils.tokenHeaderSignatureArb], + (tokenHeaderSignature) => { + const tokenHeaderSignatureEncoded = + tokensUtils.generateTokenHeaderSignature(tokenHeaderSignature); const tokenHeaderSignature_ = tokensUtils.parseTokenHeaderSignature( - tokenHeaderSignatureEncoded + tokenHeaderSignatureEncoded, ); // Use `toEqual` to avoid matching `undefined` properties expect(tokenHeaderSignature_).toEqual(tokenHeaderSignature); - } + }, ); testProp( 'parse token header signature', - [ - testsTokensUtils.tokenHeaderSignatureEncodedArb, - fc.string() - ], + [testsTokensUtils.tokenHeaderSignatureEncodedArb, fc.string()], ( tokenHeaderSignatureEncodedCorrect, - tokenHeaderSignatureEncodedIncorrect + tokenHeaderSignatureEncodedIncorrect, ) => { expect(() => { tokensUtils.parseTokenHeaderSignature( - tokenHeaderSignatureEncodedCorrect + tokenHeaderSignatureEncodedCorrect, ); }).not.toThrow(); expect(() => { tokensUtils.parseTokenHeaderSignature( - tokenHeaderSignatureEncodedIncorrect + tokenHeaderSignatureEncodedIncorrect, ); }).toThrow(validationErrors.ErrorParse); - } + }, ); testProp( 'generate signed token', - [ testsTokensUtils.signedTokenArb, ], - ( signedToken ) => { + [testsTokensUtils.signedTokenArb], + (signedToken) => { const signedTokenEncoded = tokensUtils.generateSignedToken(signedToken); const signedToken_ = tokensUtils.parseSignedToken(signedTokenEncoded); // Use `toEqual` to avoid matching `undefined` properties expect(signedToken_).toEqual(signedToken); - } + }, ); testProp( 'parse signed token', @@ -159,20 +140,16 @@ describe('tokens/utils', () => { testsTokensUtils.signedTokenEncodedArb, fc.record({ payload: fc.string(), - signatures: fc.array(fc.string()) - }) + signatures: fc.array(fc.string()), + }), ], (signedTokenEncodedCorrect, signedTokenEncodedIncorrect) => { expect(() => { - tokensUtils.parseSignedToken( - signedTokenEncodedCorrect - ); + tokensUtils.parseSignedToken(signedTokenEncodedCorrect); }).not.toThrow(); expect(() => { - tokensUtils.parseSignedToken( - signedTokenEncodedIncorrect - ); + tokensUtils.parseSignedToken(signedTokenEncodedIncorrect); }).toThrow(validationErrors.ErrorParse); - } + }, ); }); diff --git a/tests/tokens/utils.ts b/tests/tokens/utils.ts index 12ed2f831..f09810c56 100644 --- a/tests/tokens/utils.ts +++ b/tests/tokens/utils.ts @@ -1,85 +1,87 @@ import type { SignedToken, TokenHeaderSignature, - TokenProtectedHeader + TokenProtectedHeader, } from '@/tokens/types'; import { fc } from '@fast-check/jest'; import * as tokensUtils from '@/tokens/utils'; import * as testsKeysUtils from '../keys/utils'; import * as testsIdsUtils from '../ids/utils'; -const tokenPayloadArb = fc.record({ - jti: fc.option(fc.string(), { nil: undefined }), - iat: fc.option(fc.nat(), { nil: undefined }), - nbf: fc.option(fc.nat(), { nil: undefined }), - exp: fc.option(fc.nat(), { nil: undefined }), - iss: fc.option(fc.string(), { nil: undefined }), - sub: fc.option(fc.string(), { nil: undefined }), - aud: fc.option( - fc.oneof( - fc.string(), - fc.array(fc.string()) - ), - { nil: undefined} - ), -}).chain((value) => { - return fc.jsonValue().chain((json) => { - return fc.constant({ - ...json as object, - ...value +const tokenPayloadArb = fc + .record({ + jti: fc.option(fc.string(), { nil: undefined }), + iat: fc.option(fc.nat(), { nil: undefined }), + nbf: fc.option(fc.nat(), { nil: undefined }), + exp: fc.option(fc.nat(), { nil: undefined }), + iss: fc.option(fc.string(), { nil: undefined }), + sub: fc.option(fc.string(), { nil: undefined }), + aud: fc.option(fc.oneof(fc.string(), fc.array(fc.string())), { + nil: undefined, + }), + }) + .chain((value) => { + return fc.jsonValue().chain((json) => { + return fc.constant({ + ...(json as object), + ...value, + }); }); }); -}); -const tokenProtectedHeaderArb = fc.oneof( - fc.record({ - alg: fc.constant('EdDSA'), - kid: testsIdsUtils.nodeIdEncodedArb, - }), - fc.record({ - alg: fc.constant('BLAKE2b') - }), -).chain((value) => { - return fc.jsonValue().chain((json) => { - return fc.constant({ - ...json as object, - ...value +const tokenProtectedHeaderArb = fc + .oneof( + fc.record({ + alg: fc.constant('EdDSA'), + kid: testsIdsUtils.nodeIdEncodedArb, + }), + fc.record({ + alg: fc.constant('BLAKE2b'), + }), + ) + .chain((value) => { + return fc.jsonValue().chain((json) => { + return fc.constant({ + ...(json as object), + ...value, + }); }); - }); -}) as fc.Arbitrary; + }) as fc.Arbitrary; const tokenSignatureArb = fc.oneof( testsKeysUtils.signatureArb, - testsKeysUtils.macArb + testsKeysUtils.macArb, ); const tokenHeaderSignatureArb = fc.record({ protected: tokenProtectedHeaderArb, - signature: tokenSignatureArb + signature: tokenSignatureArb, }) as fc.Arbitrary; const signedTokenArb = fc.record({ payload: tokenPayloadArb, - signatures: fc.array(tokenHeaderSignatureArb) + signatures: fc.array(tokenHeaderSignatureArb), }) as fc.Arbitrary; const tokenPayloadEncodedArb = tokenPayloadArb.map( - tokensUtils.generateTokenPayload + tokensUtils.generateTokenPayload, ); const tokenProtectedHeaderEncodedArb = tokenProtectedHeaderArb.map( - tokensUtils.generateTokenProtectedHeader + tokensUtils.generateTokenProtectedHeader, ); const tokenSignatureEncodedArb = tokenSignatureArb.map( - tokensUtils.generateTokenSignature + tokensUtils.generateTokenSignature, ); const tokenHeaderSignatureEncodedArb = tokenHeaderSignatureArb.map( - tokensUtils.generateTokenHeaderSignature + tokensUtils.generateTokenHeaderSignature, ); -const signedTokenEncodedArb = signedTokenArb.map(tokensUtils.generateSignedToken); +const signedTokenEncodedArb = signedTokenArb.map( + tokensUtils.generateSignedToken, +); export { tokenPayloadArb, diff --git a/tests/utils/fastCheck.ts b/tests/utils/fastCheck.ts index 32e2b45ff..650f00640 100644 --- a/tests/utils/fastCheck.ts +++ b/tests/utils/fastCheck.ts @@ -1,16 +1,14 @@ -import { fc } from '@fast-check/jest'; +import type { fc } from '@fast-check/jest'; import * as utils from '@/utils'; class SleepCommand implements fc.AsyncCommand { - constructor( - public readonly ms: number, - ) {} + constructor(public readonly ms: number) {} check() { return true; } - async run () { + async run() { await utils.sleep(this.ms); } @@ -24,12 +22,7 @@ class SleepCommand implements fc.AsyncCommand { * This enables the `f` call to be randomly delayed by the fast check scheduler. * You must still await the result of this call if you want to see the results. */ -const scheduleCall = ( - s: fc.Scheduler, - f: () => Promise, -) => s.schedule(Promise.resolve()).then(() => f()); +const scheduleCall = (s: fc.Scheduler, f: () => Promise) => + s.schedule(Promise.resolve()).then(() => f()); -export { - SleepCommand, - scheduleCall -}; +export { SleepCommand, scheduleCall }; diff --git a/tests/utils/utils.ts b/tests/utils/utils.ts index 299494983..f3dcb388c 100644 --- a/tests/utils/utils.ts +++ b/tests/utils/utils.ts @@ -103,17 +103,25 @@ function describeIf(condition: boolean) { return condition ? describe : describe.skip; } -async function createTLSConfig(keyPair: KeyPair, generateCertId?: () => CertId): Promise { - generateCertId = generateCertId ?? keysUtils.createCertIdGenerator(); +async function createTLSConfig( + keyPair: KeyPair, + generateCertId?: () => CertId, +): Promise { + generateCertId = generateCertId ?? keysUtils.createCertIdGenerator(); const certificate = await keysUtils.generateCertificate({ certId: generateCertId(), duration: 31536000, issuerPrivateKey: keyPair.privateKey, - subjectKeyPair: { privateKey: keyPair.privateKey, publicKey: keyPair.publicKey } + subjectKeyPair: { + privateKey: keyPair.privateKey, + publicKey: keyPair.publicKey, + }, }); return { keyPrivatePem: keysUtils.privateKeyToPEM(keyPair.privateKey), - certChainPem: keysUtils.certToPEM(certificate) as unknown as CertificatePEMChain, + certChainPem: keysUtils.certToPEM( + certificate, + ) as unknown as CertificatePEMChain, }; } diff --git a/tests/vaults/VaultInternal.test.ts b/tests/vaults/VaultInternal.test.ts index c4f785289..b4393f838 100644 --- a/tests/vaults/VaultInternal.test.ts +++ b/tests/vaults/VaultInternal.test.ts @@ -54,7 +54,7 @@ describe('VaultInternal', () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); - dbKey = await keysUtils.generateKey(); + dbKey = keysUtils.generateKey(); efsDbPath = path.join(dataDir, 'efsDb'); await fs.promises.mkdir(efsDbPath); efs = await EncryptedFS.createEncryptedFS({ @@ -66,7 +66,7 @@ describe('VaultInternal', () => { db = await DB.createDB({ crypto: { - key: await keysUtils.generateKey(), + key: keysUtils.generateKey(), ops: { encrypt: async (key, plainText) => { return keysUtils.encryptWithKey( diff --git a/tests/vaults/VaultManager.test.ts b/tests/vaults/VaultManager.test.ts index 343eec822..876d26d6d 100644 --- a/tests/vaults/VaultManager.test.ts +++ b/tests/vaults/VaultManager.test.ts @@ -1,4 +1,4 @@ -import type { NodeId, NodeIdEncoded } from '@/ids/types'; +import type { NodeId } from '@/ids/types'; import type { VaultAction, VaultId, @@ -26,15 +26,13 @@ import PolykeyAgent from '@/PolykeyAgent'; import VaultManager from '@/vaults/VaultManager'; import * as vaultsErrors from '@/vaults/errors'; import NodeGraph from '@/nodes/NodeGraph'; -import * as nodesUtils from '@/nodes/utils'; import Proxy from '@/network/Proxy'; import * as vaultsUtils from '@/vaults/utils'; import { sleep } from '@/utils'; import VaultInternal from '@/vaults/VaultInternal'; +import * as keysUtils from '@/keys/utils/index'; import * as nodeTestUtils from '../nodes/utils'; import * as testUtils from '../utils'; -import * as keysUtils from '@/keys/utils/index'; -import { CertificatePEMChain } from '@/keys/types'; import * as testsUtils from '../utils/index'; describe('VaultManager', () => { @@ -49,9 +47,7 @@ describe('VaultManager', () => { let remoteVaultId: VaultId; let remoteKeynode1Id: NodeId; - let remoteKeynode1IdEncoded: NodeIdEncoded; let remoteKeynode2Id: NodeId; - let remoteKeynode2IdEncoded: NodeIdEncoded; const secretNames = ['Secret1', 'Secret2', 'Secret3', 'Secret4']; @@ -482,7 +478,6 @@ describe('VaultManager', () => { let nodeConnectionManager: NodeConnectionManager; let remoteKeynode1: PolykeyAgent, remoteKeynode2: PolykeyAgent; let localNodeId: NodeId; - let localNodeIdEncoded: NodeIdEncoded; let taskManager: TaskManager; beforeAll(async () => { @@ -505,7 +500,6 @@ describe('VaultManager', () => { }, }); remoteKeynode1Id = remoteKeynode1.keyRing.getNodeId(); - remoteKeynode1IdEncoded = nodesUtils.encodeNodeId(remoteKeynode1Id); remoteKeynode2 = await PolykeyAgent.createPolykeyAgent({ password, logger: logger.getChild('Remote Keynode 2'), @@ -520,7 +514,6 @@ describe('VaultManager', () => { }, }); remoteKeynode2Id = remoteKeynode2.keyRing.getNodeId(); - remoteKeynode2IdEncoded = nodesUtils.encodeNodeId(remoteKeynode2Id); // Adding details to each agent await remoteKeynode1.nodeGraph.setNode(remoteKeynode2Id, { @@ -533,19 +526,15 @@ describe('VaultManager', () => { }); await remoteKeynode1.gestaltGraph.setNode({ - id: remoteKeynode2IdEncoded, - chain: {}, + nodeId: remoteKeynode2Id, }); await remoteKeynode2.gestaltGraph.setNode({ - id: remoteKeynode1IdEncoded, - chain: {}, + nodeId: remoteKeynode1Id, }); }); afterAll(async () => { await remoteKeynode2.stop(); - await remoteKeynode2.destroy(); await remoteKeynode1.stop(); - await remoteKeynode1.destroy(); await fs.promises.rm(allDataDir, { recursive: true, force: true, @@ -578,9 +567,10 @@ describe('VaultManager', () => { strictMemoryLock: false, }); localNodeId = keyRing.getNodeId(); - localNodeIdEncoded = nodesUtils.encodeNodeId(localNodeId); - const tlsConfig: TLSConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const tlsConfig: TLSConfig = await testsUtils.createTLSConfig( + keyRing.keyPair, + ); await proxy.start({ tlsConfig, @@ -650,11 +640,10 @@ describe('VaultManager', () => { // Setting permissions await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -703,11 +692,10 @@ describe('VaultManager', () => { try { // Setting permissions await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -743,11 +731,10 @@ describe('VaultManager', () => { try { // Setting permissions await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -798,11 +785,10 @@ describe('VaultManager', () => { // Setting permissions await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -875,11 +861,10 @@ describe('VaultManager', () => { try { // Setting permissions await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -928,11 +913,10 @@ describe('VaultManager', () => { // Setting permissions await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -1018,11 +1002,10 @@ describe('VaultManager', () => { // Setting permissions await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -1037,11 +1020,10 @@ describe('VaultManager', () => { ); await remoteKeynode1.gestaltGraph.setNode({ - id: remoteKeynode2IdEncoded, - chain: {}, + nodeId: remoteKeynode2Id, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - remoteKeynode2Id, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', remoteKeynode2Id], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -1062,11 +1044,10 @@ describe('VaultManager', () => { ); await remoteKeynode2.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode2.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode2.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode2.acl.setVaultAction( @@ -1228,11 +1209,10 @@ describe('VaultManager', () => { // Setting permissions await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -1314,11 +1294,10 @@ describe('VaultManager', () => { // Setting permissions await remoteKeynode1.gestaltGraph.setNode({ - id: localNodeIdEncoded, - chain: {}, + nodeId: localNodeId, }); - await remoteKeynode1.gestaltGraph.setGestaltActionByNode( - localNodeId, + await remoteKeynode1.gestaltGraph.setGestaltAction( + ['node', localNodeId], 'scan', ); await remoteKeynode1.acl.setVaultAction( @@ -1428,14 +1407,12 @@ describe('VaultManager', () => { const nodeId1 = nodeTestUtils.generateRandomNodeId(); const nodeId2 = nodeTestUtils.generateRandomNodeId(); await gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(nodeId1), - chain: {}, + nodeId: nodeId1, }); await gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(nodeId2), - chain: {}, + nodeId: nodeId2, }); - await gestaltGraph.setGestaltActionByNode(nodeId1, 'scan'); + await gestaltGraph.setGestaltAction(['node', nodeId1], 'scan'); const vault1 = await vaultManager.createVault('testVault1' as VaultName); const vault2 = await vaultManager.createVault('testVault2' as VaultName); @@ -1464,7 +1441,7 @@ describe('VaultManager', () => { } }).rejects.toThrow(vaultsErrors.ErrorVaultsPermissionDenied); // Should throw due to lack of scan permission - await gestaltGraph.setGestaltActionByNode(nodeId2, 'notify'); + await gestaltGraph.setGestaltAction(['node', nodeId2], 'notify'); await expect(async () => { for await (const _ of vaultManager.handleScanVaults(nodeId2)) { // Should throw @@ -1565,8 +1542,7 @@ describe('VaultManager', () => { }); await remoteAgent.gestaltGraph.setNode({ - id: nodesUtils.encodeNodeId(nodeId1), - chain: {}, + nodeId: nodeId1, }); const vault1 = await remoteAgent.vaultManager.createVault( @@ -1592,14 +1568,20 @@ describe('VaultManager', () => { vaultsErrors.ErrorVaultsPermissionDenied, ); // Should throw due to lack of scan permission - await remoteAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'notify'); + await remoteAgent.gestaltGraph.setGestaltAction( + ['node', nodeId1], + 'notify', + ); await testUtils.expectRemoteError( testFun(), vaultsErrors.ErrorVaultsPermissionDenied, ); // Setting permissions - await remoteAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'scan'); + await remoteAgent.gestaltGraph.setGestaltAction( + ['node', nodeId1], + 'scan', + ); await remoteAgent.acl.setVaultAction(vault1, nodeId1, 'clone'); await remoteAgent.acl.setVaultAction(vault1, nodeId1, 'pull'); await remoteAgent.acl.setVaultAction(vault2, nodeId1, 'clone'); @@ -1637,7 +1619,6 @@ describe('VaultManager', () => { await acl.stop(); await acl.destroy(); await remoteAgent.stop(); - await remoteAgent.destroy(); await taskManager.stop(); } }); diff --git a/tests/vaults/VaultOps.test.ts b/tests/vaults/VaultOps.test.ts index 5e517cb46..86dd18c46 100644 --- a/tests/vaults/VaultOps.test.ts +++ b/tests/vaults/VaultOps.test.ts @@ -37,7 +37,7 @@ describe('VaultOps', () => { path.join(os.tmpdir(), 'polykey-test-'), ); const dbPath = path.join(dataDir, 'efsDb'); - const dbKey = await keysUtils.generateKey(); + const dbKey = keysUtils.generateKey(); baseEfs = await EncryptedFS.createEncryptedFS({ dbKey, dbPath, @@ -363,7 +363,7 @@ describe('VaultOps', () => { ); const secretDirName = path.basename(secretDir); const name = 'secret'; - const content = await keysUtils.getRandomBytes(5); + const content = keysUtils.getRandomBytes(5); await fs.promises.writeFile(path.join(secretDir, name), content); await vaultOps.addSecretDirectory(vault, secretDir, fs); diff --git a/tests/vaults/utils.test.ts b/tests/vaults/utils.test.ts index df7ab4d28..ef510a179 100644 --- a/tests/vaults/utils.test.ts +++ b/tests/vaults/utils.test.ts @@ -28,7 +28,7 @@ describe('Vaults utils', () => { }); test('EFS can be read recursively', async () => { - const key = await keysUtils.generateKey(); + const key = keysUtils.generateKey(); const efs = await EncryptedFS.createEncryptedFS({ dbKey: key, dbPath: dataDir, diff --git a/tests/vaults/utils.ts b/tests/vaults/utils.ts new file mode 100644 index 000000000..76125efa1 --- /dev/null +++ b/tests/vaults/utils.ts @@ -0,0 +1,12 @@ +import type { VaultActions } from '@/vaults/types'; +import fc from 'fast-check'; +import { vaultActions } from '@/vaults/types'; + +const vaultActionArb = fc.constantFrom(...vaultActions); + +const vaultActionsArb = fc.dictionary(vaultActionArb, fc.constant(null), { + minKeys: 0, + maxKeys: vaultActions.length, +}) as fc.Arbitrary; + +export { vaultActionArb, vaultActionsArb }; diff --git a/tests/workers/polykeyWorker.test.ts b/tests/workers/polykeyWorker.test.ts index ea202e31d..59bf203d9 100644 --- a/tests/workers/polykeyWorker.test.ts +++ b/tests/workers/polykeyWorker.test.ts @@ -1,6 +1,7 @@ import type { PolykeyWorkerManagerInterface } from '@/workers/types'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { createWorkerManager } from '@/workers/utils'; +import * as keysUtils from '@/keys/utils'; describe('Polykey worker', () => { const logger = new Logger('PolyKey Worker Test', LogLevel.WARN, [ @@ -16,58 +17,42 @@ describe('Polykey worker', () => { afterAll(async () => { await workerManager.destroy(); }); - test('generateKeyPairAsn1', async () => { + test('hashPassword', async () => { await workerManager.call(async (w) => { - await w.generateKeyPairAsn1(4096); + await w.hashPassword('password'); }); }); - test('encryptWithPublicKeyAsn1', async () => { - const message = 'Hello world!'; + test('checkPassword', async () => { await workerManager.call(async (w) => { - const keyPair = await w.generateKeyPairAsn1(4096); - const encrypted = w.encryptWithPublicKeyAsn1( - keyPair.privateKey, - // @ts-ignore: threads.js types are wrong - message, - ); - expect(encrypted).not.toEqual(message); + const [hash, salt] = await w.hashPassword('password'); + expect(await w.checkPassword('password', hash, salt)).toBeTrue(); }); }); - test('decryptWithPrivateKeyAsn1', async () => { + test('generateDeterministicKeyPair', async () => { + const recoveryCode = keysUtils.generateRecoveryCode(); await workerManager.call(async (w) => { - const message = 'Hello world!'; - const keyPair = await w.generateKeyPairAsn1(4096); - const encrypted = await w.encryptWithPublicKeyAsn1( - keyPair.publicKey, - message, - ); - expect(encrypted).not.toEqual(message); - const decrypted = await w.decryptWithPrivateKeyAsn1( - keyPair.privateKey, - encrypted, - ); - expect(decrypted).toEqual(message); + await w.generateDeterministicKeyPair(recoveryCode); }); }); - test('signWithPrivateKeyAsn1', async () => { + test('generateCertificate', async () => { + const keyPair = keysUtils.generateKeyPair(); + const certId = keysUtils.createCertIdGenerator()(); await workerManager.call(async (w) => { - const message = 'Hello world!'; - const keyPair = await w.generateKeyPairAsn1(4096); - const signature = w.signWithPrivateKeyAsn1(keyPair.privateKey, message); - expect(signature).toBeTruthy(); + await w.generateCertificate({ + certId, + subjectKeyPair: keyPair, + issuerPrivateKey: keyPair.privateKey, + duration: 0, + }); }); }); - test('verifyWithPublicKeyAsn1', async () => { + test('encrypt, decrypt', async () => { + const key = keysUtils.generateKey(); + const message = 'HelloWorld!'; await workerManager.call(async (w) => { - const message = 'Hello world!'; - const keyPair = await w.generateKeyPairAsn1(4096); - const signature = await w.signWithPrivateKeyAsn1( - keyPair.privateKey, - message, - ); - expect( - w.verifyWithPublicKeyAsn1(keyPair.publicKey, message, signature), - ).toBeTruthy(); + const encrypted = await w.encrypt(key, Buffer.from(message)); + const decrypted = await w.decrypt(key, encrypted); + expect(Buffer.from(decrypted!).toString()).toBe(message); }); }); });