diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index 7eaafaff2..87c605c77 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -491,10 +491,10 @@ class PolykeyAgent { this.gestaltGraph = gestaltGraph; this.fwdProxy = fwdProxy; this.revProxy = revProxy; + this.discovery = discovery; this.nodeGraph = nodeGraph; this.nodeConnectionManager = nodeConnectionManager; this.nodeManager = nodeManager; - this.discovery = discovery; this.vaultManager = vaultManager; this.notificationsManager = notificationsManager; this.sessionManager = sessionManager; @@ -516,8 +516,8 @@ class PolykeyAgent { try { this.logger.info(`Starting ${this.constructor.name}`); // Set up error handling for event handlers - this.events[captureRejectionSymbol] = (err, event) => { - let msg = `EventBus error for ${event}`; + this.events[captureRejectionSymbol] = (err, event: symbol) => { + let msg = `EventBus error for ${event.toString()}`; if (err instanceof errors.ErrorPolykey) { msg += `: ${err.name}: ${err.description}`; if (err.message !== '') { @@ -617,8 +617,8 @@ class PolykeyAgent { tlsConfig, }); await this.revProxy.start({ - serverHost: this.grpcServerAgent.host, - serverPort: this.grpcServerAgent.port, + serverHost: this.grpcServerAgent.getHost(), + serverPort: this.grpcServerAgent.getPort(), ingressHost: networkConfig_.ingressHost, ingressPort: networkConfig_.ingressPort, tlsConfig, @@ -633,8 +633,8 @@ class PolykeyAgent { await this.status.finishStart({ pid: process.pid, nodeId: this.keyManager.getNodeId(), - clientHost: this.grpcServerClient.host, - clientPort: this.grpcServerClient.port, + clientHost: this.grpcServerClient.getHost(), + clientPort: this.grpcServerClient.getPort(), ingressHost: this.revProxy.getIngressHost(), ingressPort: this.revProxy.getIngressPort(), }); @@ -672,9 +672,9 @@ class PolykeyAgent { await this.sessionManager.stop(); await this.notificationsManager.stop(); await this.vaultManager.stop(); + await this.discovery.stop(); await this.nodeConnectionManager.stop(); await this.nodeGraph.stop(); - await this.discovery.stop(); await this.revProxy.stop(); await this.fwdProxy.stop(); await this.grpcServerAgent.stop(); @@ -698,8 +698,8 @@ class PolykeyAgent { await this.sessionManager.destroy(); await this.notificationsManager.destroy(); await this.vaultManager.destroy(); - await this.nodeGraph.destroy(); await this.discovery.destroy(); + await this.nodeGraph.destroy(); await this.gestaltGraph.destroy(); await this.acl.destroy(); await this.sigchain.destroy(); diff --git a/src/bin/identities/CommandAllow.ts b/src/bin/identities/CommandAllow.ts index ca6dfc5b7..090b52e79 100644 --- a/src/bin/identities/CommandAllow.ts +++ b/src/bin/identities/CommandAllow.ts @@ -16,7 +16,11 @@ class CommandAllow extends CommandPolykey { 'Node ID or `Provider ID:Identity ID`', binParsers.parseGestaltId, ); - this.argument('', 'permission to set'); + this.argument( + '', + 'Permission to set', + binParsers.parseGestaltAction, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); diff --git a/src/bin/identities/CommandAuthenticate.ts b/src/bin/identities/CommandAuthenticate.ts index 1ecebc0de..4bd49c7b1 100644 --- a/src/bin/identities/CommandAuthenticate.ts +++ b/src/bin/identities/CommandAuthenticate.ts @@ -2,6 +2,7 @@ import type PolykeyClient from '../../PolykeyClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; +import * as parsers from '../utils/parsers'; import * as binProcessors from '../utils/processors'; import * as identitiesUtils from '../../identities/utils'; @@ -10,8 +11,16 @@ class CommandAuthenticate extends CommandPolykey { super(...args); this.name('authenticate'); this.description('Authenticate a Digital Identity Provider'); - this.argument('', 'Name of the digital identity provider'); - this.argument('', 'Digital identity to authenticate'); + this.argument( + '', + 'Name of the digital identity provider', + parsers.parseProviderId, + ); + this.argument( + '', + 'Digital identity to authenticate', + parsers.parseIdentityId, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); diff --git a/src/bin/identities/CommandClaim.ts b/src/bin/identities/CommandClaim.ts index b8c1aa6fb..377c9a86d 100644 --- a/src/bin/identities/CommandClaim.ts +++ b/src/bin/identities/CommandClaim.ts @@ -2,6 +2,7 @@ import type PolykeyClient from '../../PolykeyClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; +import * as parsers from '../utils/parsers'; import * as binProcessors from '../utils/processors'; class CommandClaim extends CommandPolykey { @@ -9,8 +10,16 @@ class CommandClaim extends CommandPolykey { super(...args); this.name('claim'); this.description('Claim a Digital Identity for this Keynode'); - this.argument('', 'Name of the digital identity provider'); - this.argument('', 'Digital identity to claim'); + this.argument( + '', + 'Name of the digital identity provider', + parsers.parseProviderId, + ); + this.argument( + '', + 'Digital identity to claim', + parsers.parseIdentityId, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); diff --git a/src/bin/identities/CommandDisallow.ts b/src/bin/identities/CommandDisallow.ts index d1115c30b..b0f655a78 100644 --- a/src/bin/identities/CommandDisallow.ts +++ b/src/bin/identities/CommandDisallow.ts @@ -16,7 +16,11 @@ class CommandDisallow extends CommandPolykey { 'Node ID or `Provider Id:Identity Id`', parsers.parseGestaltId, ); - this.argument('', 'Permission to unset'); + this.argument( + '', + 'Permission to unset', + parsers.parseGestaltAction, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); diff --git a/src/bin/identities/CommandPermissions.ts b/src/bin/identities/CommandPermissions.ts index 365b5f2ef..70c456e3c 100644 --- a/src/bin/identities/CommandPermissions.ts +++ b/src/bin/identities/CommandPermissions.ts @@ -77,8 +77,10 @@ class CommandPermissions extends CommandPolykey { } process.stdout.write( binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [`Permissions: ${actions}`], + type: options.format === 'json' ? 'json' : 'dict', + data: { + permissions: actions, + }, }), ); } finally { diff --git a/src/bin/identities/CommandSearch.ts b/src/bin/identities/CommandSearch.ts index 1eea64c2d..0b0e82aea 100644 --- a/src/bin/identities/CommandSearch.ts +++ b/src/bin/identities/CommandSearch.ts @@ -18,10 +18,9 @@ class CommandSearch extends CommandPolykey { this.option( '-pi, --provider-id [providerId...]', 'Digital identity provider(s) to search on', - parsers.parseProviderIdList, ); this.option( - '-aii, --auth-identity-id, [authIdentityId]', + '-aii, --auth-identity-id [authIdentityId]', 'Name of your own authenticated identity to find connected identities of', parsers.parseIdentityId, ); @@ -87,11 +86,11 @@ class CommandSearch extends CommandPolykey { providerSearchMessage.setDisconnected(true); } if (options.limit) { - providerSearchMessage.setLimit(options.limit); + providerSearchMessage.setLimit(options.limit.toString()); } await binUtils.retryAuthentication(async (auth) => { - if (options.identity) { - providerSearchMessage.setIdentityId(options.identity); + if (options.identityId) { + providerSearchMessage.setIdentityId(options.identityId); genReadable = pkClient.grpcClient.identitiesInfoGet( providerSearchMessage, auth, diff --git a/src/bin/keys/CommandCert.ts b/src/bin/keys/CommandCert.ts index f16937a48..9765f1783 100644 --- a/src/bin/keys/CommandCert.ts +++ b/src/bin/keys/CommandCert.ts @@ -44,10 +44,17 @@ class CommandCert extends CommandPolykey { (auth) => pkClient.grpcClient.keysCertsGet(emptyMessage, auth), meta, ); + const result = { + cert: response.getCert(), + }; + let output: any = result; + if (options.format === 'human') { + output = [`Root certificate:\t\t${result.cert}`]; + } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', - data: [`Root certificate:\t\t${response.getCert()}`], + data: output, }), ); } finally { diff --git a/src/bin/keys/CommandCertchain.ts b/src/bin/keys/CommandCertchain.ts index a3065a842..d4a18cd6d 100644 --- a/src/bin/keys/CommandCertchain.ts +++ b/src/bin/keys/CommandCertchain.ts @@ -47,14 +47,21 @@ class CommandsCertchain extends CommandPolykey { auth, ); for await (const cert of stream) { - data.push(`Certificate:\t\t${cert.getCert()}`); + data.push(cert.getCert()); } return data; }, meta); + const result = { + certchain: data, + }; + let output: any = result; + if (options.format === 'human') { + output = [`Root Certificate Chain:\t\t${result.certchain}`]; + } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', - data: data, + data: output, }), ); } finally { diff --git a/src/bin/keys/CommandDecrypt.ts b/src/bin/keys/CommandDecrypt.ts index dec93bd68..137757bbb 100644 --- a/src/bin/keys/CommandDecrypt.ts +++ b/src/bin/keys/CommandDecrypt.ts @@ -63,10 +63,17 @@ class CommandDecrypt extends CommandPolykey { (auth) => pkClient.grpcClient.keysDecrypt(cryptoMessage, auth), meta, ); + const result = { + decryptedData: response.getData(), + }; + let output: any = result; + if (options.format === 'human') { + output = [`Decrypted data:\t\t${result.decryptedData}`]; + } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', - data: [`Decrypted data:\t\t${response.getData()}`], + data: output, }), ); } finally { diff --git a/src/bin/keys/CommandEncrypt.ts b/src/bin/keys/CommandEncrypt.ts index 7488a0193..dac9d52eb 100644 --- a/src/bin/keys/CommandEncrypt.ts +++ b/src/bin/keys/CommandEncrypt.ts @@ -63,10 +63,17 @@ class CommandEncypt extends CommandPolykey { (auth) => pkClient.grpcClient.keysEncrypt(cryptoMessage, auth), meta, ); + const result = { + encryptedData: response.getData(), + }; + let output: any = result; + if (options.format === 'human') { + output = [`Encrypted data:\t\t${result.encryptedData}`]; + } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', - data: [`Encrypted data:\t\t${response.getData()}`], + data: output, }), ); } finally { diff --git a/src/bin/keys/CommandRoot.ts b/src/bin/keys/CommandRoot.ts index 8303871e8..2158283b5 100644 --- a/src/bin/keys/CommandRoot.ts +++ b/src/bin/keys/CommandRoot.ts @@ -45,20 +45,23 @@ class CommandRoot extends CommandPolykey { (auth) => pkClient.grpcClient.keysKeyPairRoot(emptyMessage, auth), meta, ); + const result: any = { + publicKey: keyPair.getPublic(), + }; + if (options.privateKey) result.privateKey = keyPair.getPrivate(); + let output: any = result; + if (options.format === 'human') { + output = [`Public key:\t\t${result.publicKey}`]; + if (options.privateKey) { + output.push(`Private key:\t\t${result.private}`); + } + } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', - data: [`public key:\t\t${keyPair.getPublic()}...`], + data: output, }), ); - if (options.privateKey) { - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [`private key:\t\t${keyPair.getPrivate()}...`], - }), - ); - } } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/bin/keys/CommandSign.ts b/src/bin/keys/CommandSign.ts index bf195e9dc..4d94d2a24 100644 --- a/src/bin/keys/CommandSign.ts +++ b/src/bin/keys/CommandSign.ts @@ -63,10 +63,17 @@ class CommandSign extends CommandPolykey { (auth) => pkClient.grpcClient.keysSign(cryptoMessage, auth), meta, ); + const result = { + signature: response.getSignature(), + }; + let output: any = result; + if (options.format === 'human') { + output = [`Signature:\t\t${result.signature}`]; + } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', - data: [`Signature:\t\t${response.getSignature()}`], + data: output, }), ); } finally { diff --git a/src/bin/keys/CommandVerify.ts b/src/bin/keys/CommandVerify.ts index 88e3da00c..42dabed70 100644 --- a/src/bin/keys/CommandVerify.ts +++ b/src/bin/keys/CommandVerify.ts @@ -72,10 +72,17 @@ class CommandVerify extends CommandPolykey { (auth) => pkClient.grpcClient.keysVerify(cryptoMessage, auth), meta, ); + const result = { + signatureVerified: response.getSuccess(), + }; + let output: any = result; + if (options.format === 'human') { + output = [`Signature verified:\t\t${result.signatureVerified}`]; + } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', - data: [`Signature verification:\t\t${response.getSuccess()}`], + data: output, }), ); } finally { diff --git a/src/bin/notifications/CommandRead.ts b/src/bin/notifications/CommandRead.ts index 2735a5154..7760e63f3 100644 --- a/src/bin/notifications/CommandRead.ts +++ b/src/bin/notifications/CommandRead.ts @@ -115,47 +115,11 @@ class CommandRead extends CommandPolykey { notificationsUtils.validateNotification(notification), ); } - if (notifications.length === 0) { + for (const notification of notifications) { process.stdout.write( binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [`No notifications to display`], - }), - ); - } else { - const output: any = []; - let notifCount = 1; - for (const notification of notifications) { - output.push(`${notifCount} - `); - switch (notification.data.type) { - case 'General': { - output.push( - `Message from Keynode with ID ${notification.senderId}: ${notification.data.message}`, - ); - output.push(''); - break; - } - case 'GestaltInvite': { - output.push( - `Keynode with ID ${notification.senderId} has invited you to join their Gestalt. Type the following command to accept their invitation: nodes claim ${notification.senderId}`, - ); - output.push(''); - break; - } - case 'VaultShare': { - output.push( - `Keynode with ID ${notification.senderId} has shared their vault '${notification.data.vaultName}' with you.`, - ); - output.push(''); - break; - } - } - notifCount++; - } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: output, + type: options.format === 'json' ? 'json' : 'dict', + data: notification, }), ); } diff --git a/src/bin/utils/parsers.ts b/src/bin/utils/parsers.ts index d1859418f..bcab0e8d3 100644 --- a/src/bin/utils/parsers.ts +++ b/src/bin/utils/parsers.ts @@ -47,6 +47,9 @@ const parseNodeId = validateParserToArgParser(validationUtils.parseNodeId); const parseGestaltId = validateParserToArgParser( validationUtils.parseGestaltId, ); +const parseGestaltAction = validateParserToArgParser( + validationUtils.parseGestaltAction, +); const parseHost = validateParserToArgParser(validationUtils.parseHost); const parseHostname = validateParserToArgParser(validationUtils.parseHostname); const parseHostOrHostname = validateParserToArgParser( @@ -94,6 +97,7 @@ export { parseNumber, parseNodeId, parseGestaltId, + parseGestaltAction, parseHost, parseHostname, parseHostOrHostname, diff --git a/src/client/service/agentStatus.ts b/src/client/service/agentStatus.ts index ddae856b2..d478c9c29 100644 --- a/src/client/service/agentStatus.ts +++ b/src/client/service/agentStatus.ts @@ -35,14 +35,14 @@ function agentStatus({ call.sendMetadata(metadata); response.setPid(process.pid); response.setNodeId(nodeUtils.encodeNodeId(keyManager.getNodeId())); - response.setClientHost(grpcServerClient.host); - response.setClientPort(grpcServerClient.port); + response.setClientHost(grpcServerClient.getHost()); + response.setClientPort(grpcServerClient.getPort()); response.setIngressHost(revProxy.getIngressHost()); response.setIngressPort(revProxy.getIngressPort()); response.setEgressHost(fwdProxy.getEgressHost()); response.setEgressPort(fwdProxy.getEgressPort()); - response.setAgentHost(grpcServerAgent.host); - response.setAgentPort(grpcServerAgent.port); + response.setAgentHost(grpcServerAgent.getHost()); + response.setAgentPort(grpcServerAgent.getPort()); response.setProxyHost(fwdProxy.getProxyHost()); response.setProxyPort(fwdProxy.getProxyPort()); response.setRootPublicKeyPem(keyManager.getRootKeyPairPem().publicKey); diff --git a/src/client/service/identitiesInfoConnectedGet.ts b/src/client/service/identitiesInfoConnectedGet.ts index 8c358c8a1..399600900 100644 --- a/src/client/service/identitiesInfoConnectedGet.ts +++ b/src/client/service/identitiesInfoConnectedGet.ts @@ -81,7 +81,7 @@ function identitiesInfoConnectedGet({ // if not authenticated const authIdentities = await provider.getAuthIdentityIds(); if (authIdentities.length === 0) { - break; + continue; } const authIdentityId = identityId === undefined ? authIdentities[0] : identityId; diff --git a/src/client/service/identitiesInfoGet.ts b/src/client/service/identitiesInfoGet.ts index a2210a3f3..bbf159f55 100644 --- a/src/client/service/identitiesInfoGet.ts +++ b/src/client/service/identitiesInfoGet.ts @@ -75,7 +75,7 @@ function identitiesInfoGet({ // require the identity to be connected const authIdentities = await provider.getAuthIdentityIds(); if (authIdentities.length === 0) { - break; + continue; } // Get identity data identities.push( diff --git a/src/client/service/keysSign.ts b/src/client/service/keysSign.ts index b7c94923d..5f2eee691 100644 --- a/src/client/service/keysSign.ts +++ b/src/client/service/keysSign.ts @@ -22,6 +22,7 @@ function keysSign({ const signature = await keyManager.signWithRootKeyPair( Buffer.from(call.request.getData(), 'binary'), ); + response.setData(call.request.getData()); response.setSignature(signature.toString('binary')); callback(null, response); return; diff --git a/src/discovery/Discovery.ts b/src/discovery/Discovery.ts index b63b6a757..900b6b63f 100644 --- a/src/discovery/Discovery.ts +++ b/src/discovery/Discovery.ts @@ -2,9 +2,11 @@ import type { MutexInterface } from 'async-mutex'; import type { DB, DBLevel } from '@matrixai/db'; import type { DiscoveryQueueId, DiscoveryQueueIdGenerator } from './types'; import type { NodeId, NodeInfo } from '../nodes/types'; -import type { GestaltGraph } from '../gestalts'; +import type NodeManager from '../nodes/NodeManager'; +import type GestaltGraph from '../gestalts/GestaltGraph'; import type { GestaltKey } from '../gestalts/types'; -import type { Provider, IdentitiesManager } from '../identities'; +import type Provider from '../identities/Provider'; +import type IdentitiesManager from '../identities/IdentitiesManager'; import type { IdentityInfo, ProviderId, @@ -12,7 +14,6 @@ import type { IdentityClaimId, IdentityClaims, } from '../identities/types'; -import type { NodeManager } from '../nodes'; import type { Sigchain } from '../sigchain'; import type { KeyManager } from '../keys'; import type { ClaimIdEncoded, Claim, ClaimLinkIdentity } from '../claims/types'; @@ -29,6 +30,7 @@ import { IdInternal } from '@matrixai/id'; import * as idUtils from '@matrixai/id/dist/utils'; import * as discoveryUtils from './utils'; import * as discoveryErrors from './errors'; +import * as nodesErrors from '../nodes/errors'; import * as utils from '../utils'; import * as gestaltsUtils from '../gestalts/utils'; import * as claimsUtils from '../claims/utils'; @@ -160,6 +162,7 @@ class Discovery { if (this.queuePlugRelease != null) { this.queuePlugRelease(); } + await this.discoveryQueue.return(); await this.discoveryProcess; this.logger.info(`Stopped ${this.constructor.name}`); } @@ -228,7 +231,19 @@ class Discovery { } // Otherwise, request the verified chain data from the node } else { - vertexChainData = await this.nodeManager.requestChainData(nodeId); + try { + vertexChainData = await this.nodeManager.requestChainData( + nodeId, + ); + } catch (e) { + this.visitedVertices.add(vertex); + await this.removeKeyFromDiscoveryQueue(vertexId); + this.logger.error( + `Failed to discover ${vertexGId.nodeId} - ${e.toString()}`, + ); + yield; + continue; + } } // TODO: for now, the chain data is treated as a 'disjoint' set of // cryptolink claims from a node to another node/identity @@ -260,8 +275,31 @@ class Discovery { const linkedVertexNodeId = node1Id.equals(nodeId) ? node2Id : node1Id; - const linkedVertexChainData = - await this.nodeManager.requestChainData(linkedVertexNodeId); + const linkedVertexGK = + gestaltsUtils.keyFromNode(linkedVertexNodeId); + let linkedVertexChainData: ChainData; + try { + linkedVertexChainData = + await this.nodeManager.requestChainData(linkedVertexNodeId); + } catch (e) { + if ( + e instanceof nodesErrors.ErrorNodeConnectionDestroyed || + e instanceof nodesErrors.ErrorNodeConnectionTimeout + ) { + if (!this.visitedVertices.has(linkedVertexGK)) { + await this.pushKeyToDiscoveryQueue(linkedVertexGK); + } + this.logger.error( + `Failed to discover ${nodesUtils.encodeNodeId( + linkedVertexNodeId, + )} - ${e.toString()}`, + ); + yield; + continue; + } else { + throw e; + } + } // With this verified chain, we can link const linkedVertexNodeInfo: NodeInfo = { id: nodesUtils.encodeNodeId(linkedVertexNodeId), @@ -272,8 +310,6 @@ class Discovery { linkedVertexNodeInfo, ); // Add this vertex to the queue if it hasn't already been visited - const linkedVertexGK = - gestaltsUtils.keyFromNode(linkedVertexNodeId); if (!this.visitedVertices.has(linkedVertexGK)) { await this.pushKeyToDiscoveryQueue(linkedVertexGK); } @@ -324,9 +360,31 @@ class Discovery { // So just cast payload data as such const data = claim.payload.data as ClaimLinkIdentity; const linkedVertexNodeId = nodesUtils.decodeNodeId(data.node)!; + const linkedVertexGK = + gestaltsUtils.keyFromNode(linkedVertexNodeId); // Get the chain data of this claimed node (so that we can link in GG) - const linkedVertexChainData = - await this.nodeManager.requestChainData(linkedVertexNodeId); + let linkedVertexChainData: ChainData; + try { + linkedVertexChainData = await this.nodeManager.requestChainData( + linkedVertexNodeId, + ); + } catch (e) { + if ( + e instanceof nodesErrors.ErrorNodeConnectionDestroyed || + e instanceof nodesErrors.ErrorNodeConnectionTimeout + ) { + if (!this.visitedVertices.has(linkedVertexGK)) { + await this.pushKeyToDiscoveryQueue(linkedVertexGK); + } + yield; + this.logger.error( + `Failed to discover ${data.node} - ${e.toString()}`, + ); + continue; + } else { + throw e; + } + } // With this verified chain, we can link const linkedVertexNodeInfo: NodeInfo = { id: nodesUtils.encodeNodeId(linkedVertexNodeId), @@ -337,8 +395,6 @@ class Discovery { vertexIdentityInfo, ); // Add this vertex to the queue if it is not present - const linkedVertexGK = - gestaltsUtils.keyFromNode(linkedVertexNodeId); if (!this.visitedVertices.has(linkedVertexGK)) { await this.pushKeyToDiscoveryQueue(linkedVertexGK); } @@ -393,10 +449,17 @@ class Discovery { /** * Push a Gestalt Key to the Discovery Queue. This process also unlocks - * the queue if it was previously locked (due to being empty). + * the queue if it was previously locked (due to being empty) + * Will only add the Key if it does not already exist in the queue */ protected async pushKeyToDiscoveryQueue(gk: GestaltKey) { await utils.withF([this.transaction], async () => { + const valueStream = this.discoveryQueueDb.createValueStream({}); + for await (const key of valueStream) { + if (key === gk) { + return; + } + } const discoveryQueueId = this.discoveryQueueIdGenerator(); await this.db.put( this.discoveryQueueDbDomain, diff --git a/src/grpc/GRPCServer.ts b/src/grpc/GRPCServer.ts index 6dd7a041c..7d6ab3d35 100644 --- a/src/grpc/GRPCServer.ts +++ b/src/grpc/GRPCServer.ts @@ -20,8 +20,8 @@ interface GRPCServer extends StartStop {} class GRPCServer { protected services: Services; protected logger: Logger; - protected _host: Host; - protected _port: Port; + protected host: Host; + protected port: Port; protected server: grpc.Server; protected clientCertChains: WeakMap> = new WeakMap(); @@ -47,10 +47,10 @@ class GRPCServer { port?: Port; tlsConfig?: TLSConfig; }): Promise { - this._host = host; + this.host = host; this.tlsConfig = tlsConfig; this.services = services; - let address = networkUtils.buildAddress(this._host, port); + let address = networkUtils.buildAddress(this.host, port); this.logger.info(`Starting ${this.constructor.name} on ${address}`); let serverCredentials: ServerCredentials; if (this.tlsConfig == null) { @@ -68,7 +68,7 @@ class GRPCServer { } const bindAsync = promisify(server.bindAsync).bind(server); try { - this._port = await bindAsync(address, serverCredentials); + this.port = await bindAsync(address, serverCredentials); } catch (e) { throw new grpcErrors.ErrorGRPCServerBind(e.message); } @@ -117,7 +117,7 @@ class GRPCServer { if (serverCredentials._isSecure()) { this._secured = true; } - address = networkUtils.buildAddress(this._host, this._port); + address = networkUtils.buildAddress(this.host, this.port); this.logger.info(`Started ${this.constructor.name} on ${address}`); } @@ -154,14 +154,22 @@ class GRPCServer { this.logger.info(`Stopped ${this.constructor.name}`); } + /** + * Gets the host + * Wildcard host `0.0.0.0` or `::` is not resolved + */ @ready(new grpcErrors.ErrorGRPCServerNotRunning()) - get host(): Host { - return this._host; + public getHost(): Host { + return this.host; } + /** + * Gets the resolved port + * Use the resolved port for connections + */ @ready(new grpcErrors.ErrorGRPCServerNotRunning()) - get port(): Port { - return this._port; + public getPort(): Port { + return this.port; } @ready(new grpcErrors.ErrorGRPCServerNotRunning()) diff --git a/src/notifications/NotificationsManager.ts b/src/notifications/NotificationsManager.ts index ee40f9f0c..69766e41e 100644 --- a/src/notifications/NotificationsManager.ts +++ b/src/notifications/NotificationsManager.ts @@ -120,7 +120,9 @@ class NotificationsManager { return this.lock.isLocked(); } - async start({ fresh }: { fresh: boolean }): Promise { + public async start({ + fresh = false, + }: { fresh?: boolean } = {}): Promise { this.logger.info(`Starting ${this.constructor.name}`); // Sub-level stores MESSAGE_COUNT_KEY -> number (of messages) const notificationsDb = await this.db.level(this.notificationsDomain); @@ -148,12 +150,12 @@ class NotificationsManager { this.logger.info(`Started ${this.constructor.name}`); } - async stop() { + public async stop() { this.logger.info(`Stopping ${this.constructor.name}`); this.logger.info(`Stopped ${this.constructor.name}`); } - async destroy() { + public async destroy() { this.logger.info(`Destroying ${this.constructor.name}`); const notificationsDb = await this.db.level(this.notificationsDomain); await notificationsDb.clear(); @@ -165,7 +167,6 @@ class NotificationsManager { * This does not ensure atomicity of the underlying database * Database atomicity still depends on the underlying operation */ - @ready(new notificationsErrors.ErrorNotificationsNotRunning()) public async transaction( f: (notificationsManager: NotificationsManager) => Promise, ): Promise { @@ -181,7 +182,6 @@ class NotificationsManager { * Transaction wrapper that will not lock if the operation was executed * within a transaction context */ - @ready(new notificationsErrors.ErrorNotificationsNotRunning()) public async _transaction(f: () => Promise): Promise { if (this.lock.isLocked()) { return await f(); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 636f92bd5..547e1c6b2 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -205,7 +205,7 @@ function arrayUnset(items: Array, item: T) { function debounce

( f: (...params: P) => any, - timeout: number = 0 + timeout: number = 0, ): (...param: P) => void { let timer: ReturnType; return function (this: any, ...args: any[]) { @@ -231,5 +231,5 @@ export { timerStop, arraySet, arrayUnset, - debounce + debounce, }; diff --git a/tests/PolykeyClient.test.ts b/tests/PolykeyClient.test.ts index 2082c3cf5..20cc8889a 100644 --- a/tests/PolykeyClient.test.ts +++ b/tests/PolykeyClient.test.ts @@ -49,8 +49,8 @@ describe('PolykeyClient', () => { test('create PolykeyClient and connect to PolykeyAgent', async () => { const pkClient = await PolykeyClient.createPolykeyClient({ nodeId: pkAgent.keyManager.getNodeId(), - host: pkAgent.grpcServerClient.host, - port: pkAgent.grpcServerClient.port, + host: pkAgent.grpcServerClient.getHost(), + port: pkAgent.grpcServerClient.getPort(), nodePath, fs, logger, @@ -58,8 +58,8 @@ describe('PolykeyClient', () => { expect(pkClient.grpcClient.nodeId).toStrictEqual( pkAgent.keyManager.getNodeId(), ); - expect(pkClient.grpcClient.host).toBe(pkAgent.grpcServerClient.host); - expect(pkClient.grpcClient.port).toBe(pkAgent.grpcServerClient.port); + expect(pkClient.grpcClient.host).toBe(pkAgent.grpcServerClient.getHost()); + expect(pkClient.grpcClient.port).toBe(pkAgent.grpcServerClient.getPort()); expect(pkClient.grpcClient.secured).toBe(true); await pkClient.stop(); }); @@ -73,8 +73,8 @@ describe('PolykeyClient', () => { // Using fresh: true means that any token would be destroyed const pkClient = await PolykeyClient.createPolykeyClient({ nodeId: pkAgent.keyManager.getNodeId(), - host: pkAgent.grpcServerClient.host, - port: pkAgent.grpcServerClient.port, + host: pkAgent.grpcServerClient.getHost(), + port: pkAgent.grpcServerClient.getPort(), nodePath, fs, logger, diff --git a/tests/agent/GRPCClientAgent.test.ts b/tests/agent/GRPCClientAgent.test.ts index 027d28ce9..eca793c49 100644 --- a/tests/agent/GRPCClientAgent.test.ts +++ b/tests/agent/GRPCClientAgent.test.ts @@ -1,32 +1,31 @@ +import type { TLSConfig } from '@/network/types'; +import type { NodeIdEncoded, NodeInfo } from '@/nodes/types'; import type * as grpc from '@grpc/grpc-js'; -import type { NodeAddress, NodeIdEncoded, NodeInfo } from '@/nodes/types'; -import type { ClaimIdEncoded, ClaimIntermediary } from '@/claims/types'; -import type { Host, Port, TLSConfig } from '@/network/types'; import fs from 'fs'; -import os from 'os'; import path from 'path'; +import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { GRPCClientAgent } from '@/agent'; -import { KeyManager } from '@/keys'; -import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { VaultManager } from '@/vaults'; -import { Sigchain } from '@/sigchain'; -import { ACL } from '@/acl'; -import { GestaltGraph } from '@/gestalts'; -import { errors as agentErrors } from '@/agent'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { NotificationsManager } from '@/notifications'; -import { utils as claimsUtils, errors as claimsErrors } from '@/claims'; -import * as keysUtils from '@/keys/utils'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import KeyManager from '@/keys/KeyManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GRPCClientAgent from '@/agent/GRPCClientAgent'; +import VaultManager from '@/vaults/VaultManager'; +import NotificationsManager from '@/notifications/NotificationsManager'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as nodesUtils } from '@/nodes'; -import { RWLock } from '@/utils'; +import * as agentErrors from '@/agent/errors'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; import * as testAgentUtils from './utils'; import * as testUtils from '../utils'; -import TestNodeConnection from '../nodes/TestNodeConnection'; describe(GRPCClientAgent.name, () => { const password = 'password'; @@ -177,7 +176,7 @@ describe(GRPCClientAgent.name, () => { notificationsManager, }); client = await testAgentUtils.openTestAgentClient(port); - }, global.polykeyStartupTimeout); + }, global.defaultTimeout); afterEach(async () => { await testAgentUtils.closeTestAgentClient(client); await testAgentUtils.closeTestAgentServer(server); @@ -257,199 +256,4 @@ describe(GRPCClientAgent.name, () => { expect(response.getChallenge()).toBe('yes'); expect(client.secured).toBeFalsy(); }); - describe('Cross signing claims', () => { - // These tests follow the following process (from the perspective of X): - // 1. X -> sends notification (to start cross signing request) -> Y - // 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 - // We mock the actions of Y (the client: NodeConnection) in the test cases, - // and test the responses of X (the server: agentService). - - let yKeysPath: string; - let yKeyManager: KeyManager; - - let xToYNodeConnection: TestNodeConnection; - - const nodeIdX = nodesUtils.decodeNodeId( - 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', - )!; - const nodeIdXEncoded = nodesUtils.encodeNodeId(nodeIdX); - const nodeIdY = nodesUtils.decodeNodeId( - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - )!; - const nodeIdYEncoded = nodesUtils.encodeNodeId(nodeIdY); - - beforeEach(async () => { - yKeysPath = path.join(dataDir, 'keys-y'); - yKeyManager = await KeyManager.createKeyManager({ - password, - keysPath: yKeysPath, - fs, - logger, - }); - - // Manually inject Y's public key into a dummy NodeConnection object, such - // that it can be used to verify the claim signature - xToYNodeConnection = await TestNodeConnection.createTestNodeConnection({ - publicKey: yKeyManager.getRootKeyPairPem().publicKey, - targetHost: 'unnecessary' as Host, - targetPort: 0 as Port, - fwdProxy: fwdProxy, - destroyCallback: async () => {}, - logger: logger, - }); - // @ts-ignore - force push into the protected connections map - nodeConnectionManager.connections.set(nodeIdY.toString(), { - connection: xToYNodeConnection, - lock: new RWLock(), - }); - await nodeGraph.setNode(nodeIdY, { - host: 'unnecessary' as Host, - port: 0 as Port, - } as NodeAddress); - }); - - afterEach(async () => { - await yKeyManager.stop(); - await yKeyManager.destroy(); - }); - - test( - 'can successfully cross sign a claim', - async () => { - const genClaims = client.nodesCrossSignClaim(); - expect(genClaims.stream.destroyed).toBe(false); - // 2. X <- sends its intermediary signed claim <- Y - // Create a dummy intermediary claim to "receive" - const claim = await claimsUtils.createClaim({ - privateKey: yKeyManager.getRootKeyPairPem().privateKey, - hPrev: null, - seq: 1, - data: { - type: 'node', - node1: nodeIdYEncoded, - node2: nodeIdXEncoded, - }, - kid: nodeIdYEncoded, - }); - const intermediary: ClaimIntermediary = { - payload: claim.payload, - signature: claim.signatures[0], - }; - const crossSignMessage = claimsUtils.createCrossSignMessage({ - singlySignedClaim: intermediary, - }); - await genClaims.write(crossSignMessage); - - // 3. X -> sends doubly signed claim (Y's intermediary) + its own intermediary claim -> Y - // 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(sigchain.locked).toBe(true); - 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, - keyManager.getRootKeyPairPem().publicKey, - ); - expect(verifiedSingly).toBe(true); - // Verify the doubly signed claim with both public keys - const verifiedDoubly = - (await claimsUtils.verifyClaimSignature( - constructedDoubly, - yKeyManager.getRootKeyPairPem().publicKey, - )) && - (await claimsUtils.verifyClaimSignature( - constructedDoubly, - keyManager.getRootKeyPairPem().publicKey, - )); - expect(verifiedDoubly).toBe(true); - - // 4. X <- sends doubly signed claim (X's intermediary) <- Y - const doublyResponse = await claimsUtils.signIntermediaryClaim({ - claim: constructedIntermediary, - privateKey: yKeyManager.getRootKeyPairPem().privateKey, - signeeNodeId: nodeIdYEncoded, - }); - const doublyMessage = claimsUtils.createCrossSignMessage({ - doublySignedClaim: doublyResponse, - }); - // Just before we complete the last step, check X's sigchain is still locked - expect(sigchain.locked).toBe(true); - await genClaims.write(doublyMessage); - - // Expect the stream to be closed. - const finalResponse = await genClaims.read(); - expect(finalResponse.done).toBe(true); - expect(genClaims.stream.destroyed).toBe(true); - - // Check X's sigchain is released at end. - expect(sigchain.locked).toBe(false); - // Check claim is in both node's sigchains - // Rather, check it's in X's sigchain - const chain = await 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 ClaimIdEncoded; - expect(chain[claimId]).toStrictEqual(doublyResponse); - } - }, - global.defaultTimeout * 4, - ); - test( - 'fails after receiving undefined singly signed claim', - async () => { - const genClaims = client.nodesCrossSignClaim(); - expect(genClaims.stream.destroyed).toBe(false); - // 2. X <- sends its intermediary signed claim <- Y - const crossSignMessageUndefinedSingly = new nodesPB.CrossSign(); - await genClaims.write(crossSignMessageUndefinedSingly); - await expect(() => genClaims.read()).rejects.toThrow( - claimsErrors.ErrorUndefinedSinglySignedClaim, - ); - expect(genClaims.stream.destroyed).toBe(true); - // Check sigchain's lock is released - expect(sigchain.locked).toBe(false); - }, - global.defaultTimeout * 4, - ); - test( - 'fails after receiving singly signed claim with no signature', - async () => { - const genClaims = client.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( - claimsErrors.ErrorUndefinedSignature, - ); - expect(genClaims.stream.destroyed).toBe(true); - // Check sigchain's lock is released - expect(sigchain.locked).toBe(false); - }, - global.defaultTimeout * 4, - ); - }); }); diff --git a/tests/agent/service/nodesCrossSignClaim.test.ts b/tests/agent/service/nodesCrossSignClaim.test.ts new file mode 100644 index 000000000..517ae1494 --- /dev/null +++ b/tests/agent/service/nodesCrossSignClaim.test.ts @@ -0,0 +1,235 @@ +import type { ClaimIdString, ClaimIntermediary } from '@/claims/types'; +import type { Host, Port } from '@/network/types'; +import type { NodeId } from '@/nodes/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import PolykeyAgent from '@/PolykeyAgent'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientAgent from '@/agent/GRPCClientAgent'; +import nodesCrossSignClaim from '@/agent/service/nodesCrossSignClaim'; +import { AgentServiceService } from '@/proto/js/polykey/v1/agent_service_grpc_pb'; +import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as claimsUtils from '@/claims/utils'; +import * as claimsErrors from '@/claims/errors'; +import * as testNodesUtils from '../../nodes/utils'; +import * as testUtils from '../../utils'; + +describe('nodesCrossSignClaim', () => { + const logger = new Logger('nodesCrossSignClaim test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + let dataDir: string; + let nodePath: string; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientAgent; + let pkAgent: PolykeyAgent; + let remoteNode: PolykeyAgent; + let localId: NodeId; + let remoteId: NodeId; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'keynode'); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + keysConfig: { + rootKeyPairBits: 2048, + }, + seedNodes: {}, // Explicitly no seed nodes on startup + logger, + }); + localId = pkAgent.keyManager.getNodeId(); + // Setting up a remote keynode + remoteNode = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath: path.join(dataDir, 'remoteNode'), + keysConfig: { + rootKeyPairBits: 2048, + }, + seedNodes: {}, // Explicitly no seed nodes on startup + logger, + }); + remoteId = remoteNode.keyManager.getNodeId(); + await testNodesUtils.nodesConnect(pkAgent, remoteNode); + const agentService = { + nodesCrossSignClaim: nodesCrossSignClaim({ + keyManager: pkAgent.keyManager, + nodeManager: pkAgent.nodeManager, + sigchain: pkAgent.sigchain, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[AgentServiceService, agentService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientAgent.createGRPCClientAgent({ + nodeId: pkAgent.keyManager.getNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }, global.defaultTimeout); + afterAll(async () => { + 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, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + 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.keyManager.getRootKeyPairPem().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 crossSignMessage = claimsUtils.createCrossSignMessage({ + singlySignedClaim: intermediary, + }); + await genClaims.write(crossSignMessage); + // 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(pkAgent.sigchain.locked).toBe(true); + 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.keyManager.getRootKeyPairPem().publicKey, + ); + expect(verifiedSingly).toBe(true); + // Verify the doubly signed claim with both public keys + const verifiedDoubly = + (await claimsUtils.verifyClaimSignature( + constructedDoubly, + remoteNode.keyManager.getRootKeyPairPem().publicKey, + )) && + (await claimsUtils.verifyClaimSignature( + constructedDoubly, + pkAgent.keyManager.getRootKeyPairPem().publicKey, + )); + expect(verifiedDoubly).toBe(true); + // 4. X <- sends doubly signed claim (X's intermediary) <- Y + const doublyResponse = await claimsUtils.signIntermediaryClaim({ + claim: constructedIntermediary, + privateKey: remoteNode.keyManager.getRootKeyPairPem().privateKey, + signeeNodeId: nodesUtils.encodeNodeId(remoteId), + }); + const doublyMessage = claimsUtils.createCrossSignMessage({ + doublySignedClaim: doublyResponse, + }); + // Just before we complete the last step, check X's sigchain is still locked + expect(pkAgent.sigchain.locked).toBe(true); + await genClaims.write(doublyMessage); + // Expect the stream to be closed. + const finalResponse = await genClaims.read(); + expect(finalResponse.done).toBe(true); + expect(genClaims.stream.destroyed).toBe(true); + // Check X's sigchain is released at end. + expect(pkAgent.sigchain.locked).toBe(false); + // 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); + } + // 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(); + await genClaims.write(crossSignMessageUndefinedSingly); + await expect(() => genClaims.read()).rejects.toThrow( + claimsErrors.ErrorUndefinedSinglySignedClaim, + ); + expect(genClaims.stream.destroyed).toBe(true); + // Check sigchain's lock is released + expect(pkAgent.sigchain.locked).toBe(false); + // 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( + claimsErrors.ErrorUndefinedSignature, + ); + expect(genClaims.stream.destroyed).toBe(true); + // Check sigchain's lock is released + expect(pkAgent.sigchain.locked).toBe(false); + // 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/notificationsSend.test.ts b/tests/agent/service/notificationsSend.test.ts new file mode 100644 index 000000000..207a01a2c --- /dev/null +++ b/tests/agent/service/notificationsSend.test.ts @@ -0,0 +1,295 @@ +import type { Host, Port } from '@/network/types'; +import type { Notification } from '@/notifications/types'; +import type { NodeId } from '@/nodes/types'; +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 KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import NotificationsManager from '@/notifications/NotificationsManager'; +import ACL from '@/acl/ACL'; +import GRPCClientAgent from '@/agent/GRPCClientAgent'; +import notificationsSend from '@/agent/service/notificationsSend'; +import { AgentServiceService } from '@/proto/js/polykey/v1/agent_service_grpc_pb'; +import * as notificationsErrors from '@/notifications/errors'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as notificationsPB from '@/proto/js/polykey/v1/notifications/notifications_pb'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as notificationsUtils from '@/notifications/utils'; +import * as testUtils from '../../utils'; + +describe('notificationsSend', () => { + const logger = new Logger('notificationsSend test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authToken = 'abc123'; + let senderId: NodeId; + let senderKeyManager: KeyManager; + let dataDir: string; + let nodeGraph: NodeGraph; + let nodeConnectionManager: NodeConnectionManager; + let nodeManager: NodeManager; + let notificationsManager: NotificationsManager; + let acl: ACL; + let sigchain: Sigchain; + let fwdProxy: ForwardProxy; + let revProxy: ReverseProxy; + let db: DB; + let keyManager: KeyManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientAgent; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const senderKeysPath = path.join(dataDir, 'senderKeys'); + senderKeyManager = await KeyManager.createKeyManager({ + password, + keysPath: senderKeysPath, + logger, + }); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + rootKeyPairBits: 1024, + logger, + }); + senderId = senderKeyManager.getNodeId(); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + acl = await ACL.createACL({ + db, + logger, + }); + fwdProxy = new ForwardProxy({ + authToken, + logger, + }); + await fwdProxy.start({ + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + revProxy = new ReverseProxy({ logger }); + await revProxy.start({ + serverHost: '1.1.1.1' as Host, + serverPort: 1 as Port, + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyManager, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger: logger.getChild('NodeGraph'), + }); + nodeConnectionManager = new NodeConnectionManager({ + keyManager, + nodeGraph, + fwdProxy, + revProxy, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + await nodeConnectionManager.start(); + nodeManager = new NodeManager({ + db, + keyManager, + nodeGraph, + nodeConnectionManager, + sigchain, + logger, + }); + notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, + logger, + }); + const agentService = { + notificationsSend: notificationsSend({ + notificationsManager, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[AgentServiceService, agentService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientAgent.createGRPCClientAgent({ + nodeId: keyManager.getNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }, global.defaultTimeout); + afterAll(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await notificationsManager.stop(); + await nodeConnectionManager.stop(); + await sigchain.stop(); + await sigchain.stop(); + await revProxy.stop(); + await fwdProxy.stop(); + await acl.stop(); + await db.stop(); + await senderKeyManager.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('successfully sends a notification', async () => { + // Set notify permission for sender on receiver + await acl.setNodePerm(senderId, { + gestalt: { notify: null }, + vaults: {}, + }); + // Construct and send notification + const notification: Notification = { + data: { + type: 'General', + message: 'test', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: false, + }; + const signedNotification = await notificationsUtils.signNotification( + notification, + senderKeyManager.getRootKeyPairPem(), + ); + const request = new notificationsPB.AgentNotification(); + request.setContent(signedNotification); + const response = await grpcClient.notificationsSend(request); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + // Check notification was received + const receivedNotifications = + await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(1); + expect(receivedNotifications[0].data).toEqual(notification.data); + expect(receivedNotifications[0].senderId).toEqual(notification.senderId); + // Reverse side effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + }); + test('cannot send invalidly formatted notification', async () => { + // Set notify permission for sender on receiver + await acl.setNodePerm(senderId, { + gestalt: { notify: null }, + vaults: {}, + }); + // Unsigned notification + const notification1: Notification = { + data: { + type: 'General', + message: 'test', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: false, + }; + const request1 = new notificationsPB.AgentNotification(); + request1.setContent(notification1.toString()); + await expect(async () => + grpcClient.notificationsSend(request1), + ).rejects.toThrow(notificationsErrors.ErrorNotificationsParse); + // Check notification was not received + let receivedNotifications = await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(0); + // Improperly typed notification + const notification2 = { + data: { + type: 'invalid', + }, + senderId, + isRead: false, + }; + const publicKey = createPublicKey( + senderKeyManager.getRootKeyPairPem().publicKey, + ); + const privateKey = createPrivateKey( + senderKeyManager.getRootKeyPairPem().privateKey, + ); + 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 expect(async () => + grpcClient.notificationsSend(request2), + ).rejects.toThrow(notificationsErrors.ErrorNotificationsValidationFailed); + // Check notification was not received + receivedNotifications = await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(0); + // Reverse side effects + await acl.unsetNodePerm(senderId); + }); + test('cannot send notification without permission', async () => { + // Construct and send notification + const notification: Notification = { + data: { + type: 'General', + message: 'test', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: false, + }; + const signedNotification = await notificationsUtils.signNotification( + notification, + senderKeyManager.getRootKeyPairPem(), + ); + const request = new notificationsPB.AgentNotification(); + request.setContent(signedNotification); + await expect(async () => + grpcClient.notificationsSend(request), + ).rejects.toThrow( + notificationsErrors.ErrorNotificationsPermissionsNotFound, + ); + // Check notification was not received + const receivedNotifications = + await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(0); + }); +}); diff --git a/tests/bin/agent/lock.test.ts b/tests/bin/agent/lock.test.ts index eb43b122b..012bbcaf1 100644 --- a/tests/bin/agent/lock.test.ts +++ b/tests/bin/agent/lock.test.ts @@ -1,10 +1,9 @@ -import os from 'os'; import path from 'path'; import fs from 'fs'; import prompts from 'prompts'; import { mocked } from 'ts-jest/utils'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Session } from '@/sessions'; +import Session from '@/sessions/Session'; import config from '@/config'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; @@ -24,18 +23,6 @@ describe('lock', () => { afterAll(async () => { await globalAgentClose(); }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); test('lock deletes the session token', async () => { await testBinUtils.pkStdio( ['agent', 'unlock'], diff --git a/tests/bin/agent/lockall.test.ts b/tests/bin/agent/lockall.test.ts index 3c93cc1f8..c0096ff14 100644 --- a/tests/bin/agent/lockall.test.ts +++ b/tests/bin/agent/lockall.test.ts @@ -1,10 +1,9 @@ -import os from 'os'; import path from 'path'; import fs from 'fs'; import prompts from 'prompts'; import { mocked } from 'ts-jest/utils'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Session } from '@/sessions'; +import Session from '@/sessions/Session'; import config from '@/config'; import * as clientErrors from '@/client/errors'; import * as testBinUtils from '../utils'; @@ -30,18 +29,6 @@ describe('lockall', () => { afterAll(async () => { await globalAgentClose(); }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); test('lockall deletes the session token', async () => { await testBinUtils.pkStdio( ['agent', 'unlock'], diff --git a/tests/bin/agent/start.test.ts b/tests/bin/agent/start.test.ts index 11efce264..b1c99affe 100644 --- a/tests/bin/agent/start.test.ts +++ b/tests/bin/agent/start.test.ts @@ -5,9 +5,10 @@ import fs from 'fs'; import readline from 'readline'; import * as jestMockProps from 'jest-mock-props'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { PolykeyAgent } from '@'; -import { Status, errors as statusErrors } from '@/status'; +import PolykeyAgent from '@/PolykeyAgent'; +import Status from '@/status/Status'; import config from '@/config'; +import * as statusErrors from '@/status/errors'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; @@ -37,6 +38,10 @@ describe('start', () => { path.join(dataDir, 'polykey'), '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -93,6 +98,10 @@ describe('start', () => { passwordPath, '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--background', '--background-out-file', path.join(dataDir, 'out.log'), @@ -173,6 +182,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -190,6 +203,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -263,6 +280,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -343,6 +364,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -371,6 +396,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -415,6 +444,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -452,6 +485,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--fresh', @@ -517,6 +554,10 @@ describe('start', () => { path.join(dataDir, 'polykey'), '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -548,6 +589,10 @@ describe('start', () => { recoveryCodePath, '--root-key-pair-bits', '2048', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -593,6 +638,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -700,8 +749,8 @@ describe('start', () => { seedNodeHost1 = globalAgentStatus.data.ingressHost; seedNodePort1 = globalAgentStatus.data.ingressPort; seedNodeId2 = agent.keyManager.getNodeId(); - seedNodeHost2 = agent.grpcServerAgent.host; - seedNodePort2 = agent.grpcServerAgent.port; + seedNodeHost2 = agent.grpcServerAgent.getHost(); + seedNodePort2 = agent.grpcServerAgent.getPort(); }, globalThis.maxTimeout); afterAll(async () => { await agent.stop(); @@ -744,6 +793,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--seed-nodes', @@ -804,6 +857,10 @@ describe('start', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', diff --git a/tests/bin/agent/status.test.ts b/tests/bin/agent/status.test.ts index 644f6a932..e8df32dd7 100644 --- a/tests/bin/agent/status.test.ts +++ b/tests/bin/agent/status.test.ts @@ -2,10 +2,10 @@ import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Status } from '@/status'; -import * as binErrors from '@/bin/errors'; +import Status from '@/status/Status'; import config from '@/config'; -import { utils as nodesUtils } from '@/nodes'; +import * as nodesUtils from '@/nodes/utils'; +import * as binErrors from '@/bin/errors'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; @@ -46,6 +46,10 @@ describe('status', () => { 'start', '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', diff --git a/tests/bin/agent/stop.test.ts b/tests/bin/agent/stop.test.ts index 2710fbc08..c3052c0b7 100644 --- a/tests/bin/agent/stop.test.ts +++ b/tests/bin/agent/stop.test.ts @@ -2,11 +2,11 @@ import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Status } from '@/status'; +import Status from '@/status/Status'; import config from '@/config'; +import { sleep } from '@/utils'; import * as binErrors from '@/bin/errors'; import * as clientErrors from '@/client/errors'; -import { sleep } from '@/utils'; import * as testBinUtils from '../utils'; describe('stop', () => { @@ -34,6 +34,10 @@ describe('stop', () => { // 1024 is the smallest size and is faster to start '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', ], @@ -89,6 +93,10 @@ describe('stop', () => { // 1024 is the smallest size and is faster to start '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', ], @@ -172,6 +180,10 @@ describe('stop', () => { // 1024 is the smallest size and is faster to start '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', '--verbose', @@ -220,6 +232,10 @@ describe('stop', () => { // 1024 is the smallest size and is faster to start '--root-key-pair-bits', '1024', + '--client-host', + '127.0.0.1', + '--ingress-host', + '127.0.0.1', '--workers', '0', ], diff --git a/tests/bin/agent/unlock.test.ts b/tests/bin/agent/unlock.test.ts index 530fb0492..ffff756f3 100644 --- a/tests/bin/agent/unlock.test.ts +++ b/tests/bin/agent/unlock.test.ts @@ -1,8 +1,7 @@ -import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Session } from '@/sessions'; +import Session from '@/sessions/Session'; import config from '@/config'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; @@ -21,18 +20,6 @@ describe('unlock', () => { afterAll(async () => { await globalAgentClose(); }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); test('unlock acquires session token', async () => { // Fresh session, to delete the token const session = await Session.createSession({ diff --git a/tests/bin/identities/allowDisallowPermissions.test.ts b/tests/bin/identities/allowDisallowPermissions.test.ts new file mode 100644 index 000000000..f3dbd2096 --- /dev/null +++ b/tests/bin/identities/allowDisallowPermissions.test.ts @@ -0,0 +1,417 @@ +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 '@/nodes/types'; +import os from 'os'; +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 * as nodesUtils from '@/nodes/utils'; +import * as keysUtils from '@/keys/utils'; +import * as claimsUtils from '@/claims/utils'; +import * as identitiesUtils from '@/identities/utils'; +import * as testBinUtils from '../utils'; +import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('allow/disallow/permissions', () => { + const logger = new Logger('allow/disallow/permissions 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 dataDir: string; + let nodePath: string; + let pkAgent: PolykeyAgent; + let node: PolykeyAgent; + let nodeId: NodeId; + let nodeHost: Host; + let nodePort: Port; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + const nodeKeyPair = await keysUtils.generateKeyPair(2048); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair) + .mockResolvedValue(nodeKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair) + .mockResolvedValue(nodeKeyPair); + // Cannot use global shared agent since we need to register a provider + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'polykey'); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + pkAgent.identitiesManager.registerProvider(provider); + // Set up a gestalt to modify the permissions of + const nodePathGestalt = path.join(dataDir, 'gestalt'); + node = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath: nodePathGestalt, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + nodeId = node.keyManager.getNodeId(); + nodeHost = node.revProxy.getIngressHost(); + nodePort = node.revProxy.getIngressPort(); + node.identitiesManager.registerProvider(provider); + await node.identitiesManager.putToken(provider.id, identity, { + accessToken: 'def456', + }); + provider.users[identity] = {}; + const identityClaim: ClaimLinkIdentity = { + type: 'identity', + node: nodesUtils.encodeNodeId(node.keyManager.getNodeId()), + provider: provider.id, + identity: identity, + }; + const [, claimEncoded] = await node.sigchain.addClaim(identityClaim); + const claim = claimsUtils.decodeClaim(claimEncoded); + await provider.publishClaim(identity, claim); + }, globalThis.maxTimeout); + afterAll(async () => { + await node.stop(); + await pkAgent.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('allows/disallows/gets gestalt permissions by node', async () => { + let exitCode, stdout; + // Add the node to our node graph, otherwise we won't be able to contact it + await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(nodeId), + nodeHost, + `${nodePort}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // Must first trust node before we can set permissions + // This is because trusting the node sets it in our gestalt graph, which + // we need in order to set permissions + await testBinUtils.pkStdio( + ['identities', 'trust', nodesUtils.encodeNodeId(nodeId)], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // We should now have the 'notify' permission, so we'll set the 'scan' + // permission as well + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'allow', nodesUtils.encodeNodeId(nodeId), 'scan'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Check that both permissions are set + ({ exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'identities', + 'permissions', + nodesUtils.encodeNodeId(nodeId), + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + permissions: ['notify', 'scan'], + }); + // Disallow both permissions + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'disallow', nodesUtils.encodeNodeId(nodeId), 'notify'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'disallow', nodesUtils.encodeNodeId(nodeId), 'scan'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Check that both permissions were unset + ({ exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'identities', + 'permissions', + nodesUtils.encodeNodeId(nodeId), + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + permissions: [], + }); + // Revert side-effects + await pkAgent.gestaltGraph.unsetNode(nodeId); + await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); + await pkAgent.nodeGraph.unsetNode(nodeId); + // @ts-ignore - get protected property + pkAgent.discovery.visitedVertices.clear(); + }); + test('allows/disallows/gets gestalt permissions by identity', async () => { + let exitCode, stdout; + // Add the node to our node graph, otherwise we won't be able to contact it + await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(nodeId), + nodeHost, + `${nodePort}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // Authenticate our own identity in order to query the provider + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + await testBinUtils.pkStdio( + [ + 'identities', + 'authenticate', + testToken.providerId, + testToken.identityId, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + mockedBrowser.mockRestore(); + // Must first trust identity before we can set permissions + // This is because trusting the identity sets it in our gestalt graph, + // which we need in order to set permissions + // This command should fail first time since the identity won't be linked + // to any nodes. It will trigger this process via discovery and we must + // wait and then retry + await testBinUtils.pkStdio( + ['identities', 'trust', providerString], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + 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, + ); + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'trust', providerString], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // We should now have the 'notify' permission, so we'll set the 'scan' + // permission as well + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'allow', providerString, 'scan'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Check that both permissions are set + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'permissions', providerString, '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + permissions: ['notify', 'scan'], + }); + // Disallow both permissions + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'disallow', providerString, 'notify'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'disallow', providerString, 'scan'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Check that both permissions were unset + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'permissions', providerString, '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + permissions: [], + }); + // Revert side effects + await pkAgent.gestaltGraph.unsetNode(nodeId); + await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); + await pkAgent.nodeGraph.unsetNode(nodeId); + await pkAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + // @ts-ignore - get protected property + pkAgent.discovery.visitedVertices.clear(); + }); + test('should fail on invalid inputs', async () => { + let exitCode; + // Allow + // Invalid gestalt id + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'allow', 'invalid', 'notify'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Invalid permission + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'allow', nodesUtils.encodeNodeId(nodeId), 'invalid'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Permissions + // Invalid gestalt id + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'permissions', 'invalid'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Disallow + // Invalid gestalt id + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'disallow', 'invalid', 'notify'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Invalid permission + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'disallow', nodesUtils.encodeNodeId(nodeId), 'invalid'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + }); +}); diff --git a/tests/bin/identities/authenticateAuthenticated.test.ts b/tests/bin/identities/authenticateAuthenticated.test.ts new file mode 100644 index 000000000..ef286b595 --- /dev/null +++ b/tests/bin/identities/authenticateAuthenticated.test.ts @@ -0,0 +1,165 @@ +import type { IdentityId, ProviderId } from '@/identities/types'; +import type { Host } from '@/network/types'; +import os from 'os'; +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 identitiesUtils from '@/identities/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testBinUtils from '../utils'; +import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('authenticate/authenticated', () => { + const logger = new Logger('authenticate/authenticated test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const testToken = { + providerId: 'test-provider' as ProviderId, + identityId: 'test_user' as IdentityId, + }; + let dataDir: string; + let nodePath: string; + let pkAgent: PolykeyAgent; + let testProvider: TestProvider; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'polykey'); + // Cannot use global shared agent since we need to register a provider + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + testProvider = new TestProvider(); + pkAgent.identitiesManager.registerProvider(testProvider); + }); + afterAll(async () => { + await pkAgent.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('authenticates identity with a provider and gets authenticated identity', async () => { + let exitCode, stdout; + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + // Authenticate an identity + ({ exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'identities', + 'authenticate', + testToken.providerId, + testToken.identityId, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(stdout).toContain('randomtestcode'); + // Check that the identity was authenticated + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'authenticated', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + providerId: testToken.providerId, + identityId: testToken.identityId, + }); + // Check using providerId flag + ({ exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'identities', + 'authenticated', + '--provider-id', + testToken.providerId, + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + providerId: testToken.providerId, + identityId: testToken.identityId, + }); + // Revert side effects + await pkAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + mockedBrowser.mockRestore(); + }); + test('should fail on invalid inputs', async () => { + let exitCode; + // Authenticate + // Invalid provider + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'authenticate', '', testToken.identityId], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Invalid identity + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'authenticate', testToken.providerId, ''], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Authenticated + // Invalid provider + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'authenticate', '--provider-id', ''], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + }); +}); diff --git a/tests/bin/identities/claim.test.ts b/tests/bin/identities/claim.test.ts new file mode 100644 index 000000000..901812f9a --- /dev/null +++ b/tests/bin/identities/claim.test.ts @@ -0,0 +1,154 @@ +import type { + IdentityClaimId, + IdentityId, + ProviderId, +} from '@/identities/types'; +import type { Host } from '@/network/types'; +import os from 'os'; +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 identitiesUtils from '@/identities/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testBinUtils from '../utils'; +import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('claim', () => { + const logger = new Logger('claim test', LogLevel.WARN, [new StreamHandler()]); + const password = 'helloworld'; + const testToken = { + providerId: 'test-provider' as ProviderId, + identityId: 'test_user' as IdentityId, + }; + let dataDir: string; + let nodePath: string; + let pkAgent: PolykeyAgent; + let testProvider: TestProvider; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'polykey'); + // Cannot use global shared agent since we need to register a provider + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + testProvider = new TestProvider(); + pkAgent.identitiesManager.registerProvider(testProvider); + }); + afterAll(async () => { + await pkAgent.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('claims an identity', async () => { + // Need an authenticated identity + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + await testBinUtils.pkStdio( + [ + 'identities', + 'authenticate', + testToken.providerId, + testToken.identityId, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // Claim identity + const { exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'identities', + 'claim', + testToken.providerId, + testToken.identityId, + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual(['Claim Id: 0', 'Url: test.com']); + // Check for claim on the provider + const claim = await testProvider.getClaim( + testToken.identityId, + '0' as IdentityClaimId, + ); + expect(claim).toBeDefined(); + expect(claim!.id).toBe('0'); + expect(claim!.payload.data.type).toBe('identity'); + // Revert side effects + await pkAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + mockedBrowser.mockRestore(); + }); + test('cannot claim unauthenticated identities', async () => { + const { exitCode } = await testBinUtils.pkStdio( + ['identities', 'claim', testToken.providerId, testToken.identityId], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(1); + }); + test('should fail on invalid inputs', async () => { + let exitCode; + // Invalid provider + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'claim', '', testToken.identityId], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Invalid identity + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'claim', testToken.providerId, ''], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + }); +}); diff --git a/tests/bin/identities/discoverGet.test.ts b/tests/bin/identities/discoverGet.test.ts new file mode 100644 index 000000000..968dfa27a --- /dev/null +++ b/tests/bin/identities/discoverGet.test.ts @@ -0,0 +1,344 @@ +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 '@/nodes/types'; +import os from 'os'; +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 * as identitiesUtils from '@/identities/utils'; +import * as claimsUtils from '@/claims/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testBinUtils from '../utils'; +import * as testUtils from '../../utils'; +import * as testNodesUtils from '../../nodes/utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('discover/get', () => { + const logger = new Logger('discover/get test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const testProvider = new TestProvider(); + const identityId = 'abc' as IdentityId; + const providerString = `${testProvider.id}:${identityId}`; + const testToken = { + providerId: 'test-provider' as ProviderId, + identityId: 'test_user' as IdentityId, + }; + let dataDir: string; + let nodePath: string; + let pkAgent: PolykeyAgent; + let nodeA: PolykeyAgent; + let nodeB: PolykeyAgent; + let nodeAId: NodeId; + let nodeBId: NodeId; + let nodeAHost: Host; + let nodeAPort: Port; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + // Setup the remote gestalt state here + // Setting up remote nodes + nodeA = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath: path.join(dataDir, 'nodeA'), + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + keysConfig: { + rootKeyPairBits: 2048, + }, + logger, + }); + nodeAId = nodeA.keyManager.getNodeId(); + nodeAHost = nodeA.revProxy.getIngressHost(); + nodeAPort = nodeA.revProxy.getIngressPort(); + nodeB = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath: path.join(dataDir, 'nodeB'), + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + keysConfig: { + rootKeyPairBits: 2048, + }, + logger, + }); + nodeBId = nodeB.keyManager.getNodeId(); + await testNodesUtils.nodesConnect(nodeA, nodeB); + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); + nodePath = path.join(dataDir, 'polykey'); + // Cannot use global shared agent since we need to register a provider + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + pkAgent.identitiesManager.registerProvider(testProvider); + // Add node claim to gestalt + await nodeA.nodeManager.claimNode(nodeBId); + // Add identity claim to gestalt + testProvider.users[identityId] = {}; + nodeA.identitiesManager.registerProvider(testProvider); + await nodeA.identitiesManager.putToken(testProvider.id, identityId, { + accessToken: 'abc123', + }); + const identityClaim: ClaimLinkIdentity = { + type: 'identity', + node: nodesUtils.encodeNodeId(nodeAId), + provider: testProvider.id, + identity: identityId, + }; + const [, claimEncoded] = await nodeA.sigchain.addClaim(identityClaim); + const claim = claimsUtils.decodeClaim(claimEncoded); + await testProvider.publishClaim(identityId, claim); + }, global.maxTimeout); + afterAll(async () => { + await pkAgent.stop(); + await nodeB.stop(); + await nodeA.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('discovers and gets gestalt by node', async () => { + // Need an authenticated identity + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + await testBinUtils.pkStdio( + [ + 'identities', + 'authenticate', + testToken.providerId, + testToken.identityId, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // Add one of the nodes to our gestalt graph so that we'll be able to + // contact the gestalt during discovery + await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(nodeAId), + nodeAHost, + `${nodeAPort}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // Discover gestalt by node + const discoverResponse = await testBinUtils.pkStdio( + ['identities', 'discover', nodesUtils.encodeNodeId(nodeAId)], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + 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, + ); + // Now we can get the gestalt + const getResponse = await testBinUtils.pkStdio( + ['identities', 'get', nodesUtils.encodeNodeId(nodeAId)], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(getResponse.exitCode).toBe(0); + expect(getResponse.stdout).toContain(nodesUtils.encodeNodeId(nodeAId)); + expect(getResponse.stdout).toContain(nodesUtils.encodeNodeId(nodeBId)); + expect(getResponse.stdout).toContain(providerString); + // Revert side effects + await pkAgent.gestaltGraph.unsetNode(nodeAId); + await pkAgent.gestaltGraph.unsetNode(nodeBId); + await pkAgent.gestaltGraph.unsetIdentity(testProvider.id, identityId); + await pkAgent.nodeGraph.unsetNode(nodeAId); + await pkAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + mockedBrowser.mockRestore(); + // @ts-ignore - get protected property + pkAgent.discovery.visitedVertices.clear(); + }); + test('discovers and gets gestalt by identity', async () => { + // Need an authenticated identity + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + await testBinUtils.pkStdio( + [ + 'identities', + 'authenticate', + testToken.providerId, + testToken.identityId, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // Add one of the nodes to our gestalt graph so that we'll be able to + // contact the gestalt during discovery + await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(nodeAId), + nodeAHost, + `${nodeAPort}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // Discover gestalt by node + const discoverResponse = await testBinUtils.pkStdio( + ['identities', 'discover', providerString], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + 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, + ); + // Now we can get the gestalt + const getResponse = await testBinUtils.pkStdio( + ['identities', 'get', providerString], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(getResponse.exitCode).toBe(0); + expect(getResponse.stdout).toContain(nodesUtils.encodeNodeId(nodeAId)); + expect(getResponse.stdout).toContain(nodesUtils.encodeNodeId(nodeBId)); + expect(getResponse.stdout).toContain(providerString); + // Revert side effects + await pkAgent.gestaltGraph.unsetNode(nodeAId); + await pkAgent.gestaltGraph.unsetNode(nodeBId); + await pkAgent.gestaltGraph.unsetIdentity(testProvider.id, identityId); + await pkAgent.nodeGraph.unsetNode(nodeAId); + await pkAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + mockedBrowser.mockRestore(); + // @ts-ignore - get protected property + pkAgent.discovery.visitedVertices.clear(); + }); + test('should fail on invalid inputs', async () => { + let exitCode; + // Discover + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'discover', 'invalid'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Get + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'get', 'invalid'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + }); +}); diff --git a/tests/bin/identities/identities.test.ts b/tests/bin/identities/identities.test.ts deleted file mode 100644 index 1f9753a5c..000000000 --- a/tests/bin/identities/identities.test.ts +++ /dev/null @@ -1,889 +0,0 @@ -import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; -import type { NodeIdEncoded, NodeInfo } from '@/nodes/types'; -import type { ClaimLinkIdentity } from '@/claims/types'; -import type { Gestalt } from '@/gestalts/types'; -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { PolykeyAgent } from '@'; -import { poll } from '@/utils'; -import * as claimsUtils from '@/claims/utils'; -import * as identitiesUtils from '@/identities/utils'; -import * as keysUtils from '@/keys/utils'; -import * as nodesUtils from '@/nodes/utils'; -import * as testBinUtils from '../utils'; -import * as testNodesUtils from '../../nodes/utils'; -import TestProvider from '../../identities/TestProvider'; - -function identityString( - providerId: ProviderId, - identityId: IdentityId, -): string { - return `${providerId}:${identityId}`; -} - -describe('CLI Identities', () => { - const password = 'password'; - // Test dependent variables - let dataDir: string; - let nodePath: string; - let passwordFile: string; - let polykeyAgent: PolykeyAgent; - let testProvider: TestProvider; - let mockedBrowser: jest.SpyInstance; - - // Defining constants - const nodeId1Encoded = - 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded; - const nodeId1 = nodesUtils.decodeNodeId(nodeId1Encoded)!; - const nodeId2Encoded = - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg' as NodeIdEncoded; - const nodeId2 = nodesUtils.decodeNodeId(nodeId2Encoded)!; - const nodeId3Encoded = - 'v359vgrgmqf1r5g4fvisiddjknjko6bmm4qv7646jr7fi9enbfuug' as NodeIdEncoded; - // Const nodeId3 = nodesUtils.decodeNodeId(nodeId3Encoded); - const nodeId4Encoded = - 'vm5guqfrrhlrsa70qpauen8jd0lmb0v6j8r8c94p34n738vlvu7vg' as NodeIdEncoded; - // Const nodeId4 = nodesUtils.decodeNodeId(nodeId4Encoded); - const dummyNodeEncoded = - 'vi3et1hrpv2m2lrplcm7cu913kr45v51cak54vm68anlbvuf83ra0' as NodeIdEncoded; - // Const dummyNode = nodesUtils.decodeNodeId(dummyNodeEncoded); - - const logger = new Logger('pkStdio Test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const node1: NodeInfo = { - id: nodeId1Encoded, - chain: {}, - }; - const node2: NodeInfo = { - id: nodeId2Encoded, - chain: {}, - }; - const node3: NodeInfo = { - id: nodeId3Encoded, - chain: {}, - }; - const keynode: NodeInfo = { - id: nodeId4Encoded, - chain: {}, - }; - const invaldNode: NodeInfo = { - id: dummyNodeEncoded, - chain: {}, - }; - const identity1: IdentityInfo = { - providerId: 'github.com' as ProviderId, - identityId: 'abc' as IdentityId, - claims: {}, - }; - const invalidIdentity: IdentityInfo = { - providerId: 'github.com' as ProviderId, - identityId: 'onetwothree' as IdentityId, - claims: {}, - }; - const testToken = { - providerId: 'test-provider' as ProviderId, - identityId: 'test_user' as IdentityId, - tokenData: { - accessToken: 'abc123', - }, - }; - - // Helper functions - function genCommands(options: Array) { - return ['identities', ...options, '-np', nodePath]; - } - - const mockedGenerateDeterministicKeyPair = jest.spyOn( - keysUtils, - 'generateDeterministicKeyPair', - ); - - // Setup and teardown - beforeAll(async () => { - mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { - return keysUtils.generateKeyPair(bits); - }); - - // This handles the expensive setting up of the polykey agent. - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - nodePath = path.join(dataDir, 'keynode'); - passwordFile = path.join(dataDir, 'passwordFile'); - await fs.promises.writeFile(passwordFile, 'password'); - polykeyAgent = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath: nodePath, - logger: logger, - }); - - keynode.id = nodesUtils.encodeNodeId(polykeyAgent.keyManager.getNodeId()); - - testProvider = new TestProvider(); - polykeyAgent.identitiesManager.registerProvider(testProvider); - - mockedBrowser = jest - .spyOn(identitiesUtils, 'browser') - .mockImplementation(() => {}); - - // Authorize session - await testBinUtils.pkStdio( - ['agent', 'unlock', '-np', nodePath, '--password-file', passwordFile], - {}, - dataDir, - ); - }, global.polykeyStartupTimeout * 2); - afterAll(async () => { - await polykeyAgent.stop(); - await polykeyAgent.destroy(); - mockedBrowser.mockRestore(); - await fs.promises.rmdir(dataDir, { recursive: true }); - }); - beforeEach(async () => { - // Setting up gestalt state - await polykeyAgent.gestaltGraph.setNode(keynode); - await polykeyAgent.gestaltGraph.setNode(node1); - await polykeyAgent.gestaltGraph.setNode(node2); - await polykeyAgent.gestaltGraph.setNode(node3); - await polykeyAgent.gestaltGraph.setIdentity(identity1); - await polykeyAgent.gestaltGraph.linkNodeAndIdentity(node1, identity1); - }); - afterEach(async () => { - // This handles the cheap teardown between tests. - // Clean up any dangling permissions. - await polykeyAgent.gestaltGraph.clearDB(); - }); - - // Tests - describe('commandAllowGestalts', () => { - test('Should allow permissions on node.', async () => { - const commands = genCommands(['allow', node1.id, 'notify']); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - const actions = await polykeyAgent.gestaltGraph.getGestaltActionsByNode( - nodeId1, - ); - const actionKeys = Object.keys(actions!); - expect(actionKeys).toContain('notify'); - - const command2 = genCommands(['allow', node1.id, 'scan']); - const result2 = await testBinUtils.pkStdio(command2, {}, dataDir); - expect(result2.exitCode).toBe(0); // Succeeds. - - const actions2 = await polykeyAgent.gestaltGraph.getGestaltActionsByNode( - nodeId1, - ); - const actionKeys2 = Object.keys(actions2!); - expect(actionKeys2).toContain('notify'); - expect(actionKeys2).toContain('scan'); - - // Should fail for invalid action. - const command3 = genCommands(['allow', node1.id, 'invalid']); - const result3 = await testBinUtils.pkStdio(command3, {}, dataDir); - expect(result3.exitCode).toBe(1); // Should fail. - }); - test('Should allow permissions on Identity.', async () => { - const commands = genCommands([ - 'allow', - identityString(identity1.providerId, identity1.identityId), - 'notify', - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - - const actions = - await polykeyAgent.gestaltGraph.getGestaltActionsByIdentity( - identity1.providerId, - identity1.identityId, - ); - const actionKeys = Object.keys(actions!); - expect(actionKeys).toContain('notify'); - - const command2 = genCommands([ - 'allow', - identityString(identity1.providerId, identity1.identityId), - 'scan', - ]); - const result2 = await testBinUtils.pkStdio(command2, {}, dataDir); - expect(result2.exitCode).toBe(0); // Succeedes. - - const actions2 = - await polykeyAgent.gestaltGraph.getGestaltActionsByIdentity( - identity1.providerId, - identity1.identityId, - ); - const actionKeys2 = Object.keys(actions2!); - expect(actionKeys2).toContain('notify'); - expect(actionKeys2).toContain('scan'); - - // Should fail for invalid action. - const command3 = genCommands([ - 'allow', - identityString(identity1.providerId, identity1.identityId), - 'invalid', - ]); - const result3 = await testBinUtils.pkStdio(command3, {}, dataDir); - expect(result3.exitCode).toBe(1); // Should fail. - }); - test('Should fail on invalid inputs.', async () => { - let result; - // Invalid node. - result = await testBinUtils.pkStdio( - genCommands(['allow', invaldNode.id, 'scan']), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - - // invalid identity - result = await testBinUtils.pkStdio( - genCommands([ - 'allow', - identityString( - invalidIdentity.providerId, - invalidIdentity.identityId, - ), - 'scan', - ]), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - - // invalid permission. - result = await testBinUtils.pkStdio( - genCommands(['allow', invaldNode.id, 'invalidPermission']), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - }); - }); - describe('commandDisallowGestalts', () => { - test('Should disallow permissions on Node.', async () => { - // Setting permissions. - await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'notify'); - await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'scan'); - - const commands = genCommands(['disallow', node1.id, 'notify']); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - - const actions = await polykeyAgent.gestaltGraph.getGestaltActionsByNode( - nodeId1, - ); - const actionKeys = Object.keys(actions!); - expect(actionKeys).toContain('scan'); - expect(actionKeys).not.toContain('notify'); - }); - test('Should disallow permissions on Identity.', async () => { - // Setting permissions. - await polykeyAgent.gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'notify', - ); - await polykeyAgent.gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'scan', - ); - - const commands = genCommands([ - 'disallow', - identityString(identity1.providerId, identity1.identityId), - 'scan', - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - - const actions = - await polykeyAgent.gestaltGraph.getGestaltActionsByIdentity( - identity1.providerId, - identity1.identityId, - ); - const actionKeys = Object.keys(actions!); - expect(actionKeys).toContain('notify'); - expect(actionKeys).not.toContain('scan'); - }); - test('Should fail on invalid inputs.', async () => { - let result; - // Invalid node. - result = await testBinUtils.pkStdio( - genCommands(['disallow', invaldNode.id, 'scan']), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - - // invalid identity - result = await testBinUtils.pkStdio( - genCommands([ - 'disallow', - identityString( - invalidIdentity.providerId, - invalidIdentity.identityId, - ), - 'scan', - ]), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - - // invalid permission. - result = await testBinUtils.pkStdio( - genCommands(['disallow', node1.id, 'invalidPermission']), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - }); - }); - describe('commandPermissionsGestalts', () => { - test('Should get permissions on Node.', async () => { - // Setting permissions. - await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'notify'); - await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'scan'); - - const commands = genCommands(['permissions', node1.id]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - // Print result. - expect(result.stdout).toContain('notify'); - expect(result.stdout).toContain('scan'); - }); - test('Should get permissions on Identity.', async () => { - // Setting permissions. - await polykeyAgent.gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'notify', - ); - await polykeyAgent.gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'scan', - ); - - const commands = genCommands([ - 'permissions', - identityString(identity1.providerId, identity1.identityId), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - // Print result. - expect(result.stdout).toContain('scan'); - expect(result.stdout).toContain('notify'); - }); - }); - describe('commandTrustGestalts', () => { - test('Should set trust on Node.', async () => { - const commands = genCommands(['trust', node1.id]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - - const actions = await polykeyAgent.gestaltGraph.getGestaltActionsByNode( - nodeId1, - ); - const actionKeys = Object.keys(actions!); - expect(actionKeys).toContain('notify'); - }); - test('Should set trust on Identity.', async () => { - const commands = genCommands([ - 'trust', - identityString(identity1.providerId, identity1.identityId), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - - const actions = - await polykeyAgent.gestaltGraph.getGestaltActionsByIdentity( - identity1.providerId, - identity1.identityId, - ); - const actionKeys = Object.keys(actions!); - expect(actionKeys).toContain('notify'); - }); - test('Should fail on invalid inputs.', async () => { - // Invalid identity - const result = await testBinUtils.pkStdio( - genCommands([ - 'trust', - identityString( - invalidIdentity.providerId, - invalidIdentity.identityId, - ), - ]), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - }); - }); - describe('commandUntrustGestalts', () => { - test('Should unset trust on Node.', async () => { - // Setting permissions. - await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'notify'); - await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'scan'); - - const commands = genCommands(['untrust', node1.id]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - - const actions = await polykeyAgent.gestaltGraph.getGestaltActionsByNode( - nodeId1, - ); - const actionKeys = Object.keys(actions!); - expect(actionKeys).toContain('scan'); - expect(actionKeys).not.toContain('notify'); - }); - test('Should unset trust on Identity.', async () => { - // Setting permissions. - await polykeyAgent.gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'notify', - ); - await polykeyAgent.gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'scan', - ); - - const commands = genCommands([ - 'untrust', - identityString(identity1.providerId, identity1.identityId), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - - const actions = - await polykeyAgent.gestaltGraph.getGestaltActionsByIdentity( - identity1.providerId, - identity1.identityId, - ); - const actionKeys = Object.keys(actions!); - expect(actionKeys).toContain('scan'); - expect(actionKeys).not.toContain('notify'); - }); - test('Should fail on invalid inputs.', async () => { - let result; - // Invalid node. - result = await testBinUtils.pkStdio( - genCommands(['untrust', invaldNode.id]), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - - // invalid identity - result = await testBinUtils.pkStdio( - genCommands([ - 'untrust', - identityString( - invalidIdentity.providerId, - invalidIdentity.identityId, - ), - ]), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - }); - }); - describe('commandClaimIdentity', () => { - test('Should claim an identity.', async () => { - // Need an authenticated identity. - await polykeyAgent.identitiesManager.putToken( - testToken.providerId, - testToken.identityId, - testToken.tokenData, - ); - const commands = [ - 'identities', - 'claim', - '-np', - nodePath, - testToken.providerId, - testToken.identityId, - ]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - // Unauthenticate identity - await polykeyAgent.identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - // Unclaim identity - testProvider.links = {}; - testProvider.linkIdCounter = 0; - }); - test('Should fail for unauthenticated identities.', async () => { - const commands = [ - 'identities', - 'claim', - '-np', - nodePath, - testToken.providerId, - testToken.identityId, - ]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - }); - }); - describe('commandAuthenticateProvider', () => { - test('Should authenticate an identity with a provider.', async () => { - // Attempt to authenticate. - const commands = [ - 'identities', - 'authenticate', - '-np', - nodePath, - testToken.providerId, - testToken.identityId, - ]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - expect(result.stdout).toContain('randomtestcode'); - // Unauthenticate identity - await polykeyAgent.identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - }); - }); - describe('commandAuthenticatedGet', () => { - test('Should get authenticated identities', async () => { - // Need an authenticated identity. - await polykeyAgent.identitiesManager.putToken( - testToken.providerId, - testToken.identityId, - testToken.tokenData, - ); - const commands = ['identities', 'authenticated', '-np', nodePath]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - expect(result.stdout).toContain(testToken.providerId); - expect(result.stdout).toContain(testToken.identityId); - // Unauthenticate identity - await polykeyAgent.identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - }); - test('Should get authenticated identities from specific provider', async () => { - // Need an authenticated identity. - await polykeyAgent.identitiesManager.putToken( - testToken.providerId, - testToken.identityId, - testToken.tokenData, - ); - const commands = [ - 'identities', - 'authenticated', - '--provider-id', - testToken.providerId, - '-np', - nodePath, - ]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - expect(result.stdout).toContain(testToken.providerId); - expect(result.stdout).toContain(testToken.identityId); - // Unauthenticate identity - await polykeyAgent.identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - }); - }); - describe('commandGetGestalts', () => { - const nodeIdEncoded = node1.id; - test('Should list gestalt by Node', async () => { - const commands = ['identities', 'get', '-np', nodePath, nodeIdEncoded]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain(nodeIdEncoded); - expect(result.stdout).toContain(identity1.providerId); - expect(result.stdout).toContain(identity1.identityId); - }); - test('Should list gestalt by Identity', async () => { - const nodeIdEncoded = node1.id; - const commands = [ - 'identities', - 'get', - '-np', - nodePath, - identityString(identity1.providerId, identity1.identityId), - ]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain(nodeIdEncoded); - expect(result.stdout).toContain(identity1.providerId); - expect(result.stdout).toContain(identity1.identityId); - }); - }); - describe('commandListGestalts', () => { - test('Should list gestalts with permissions.', async () => { - await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'notify'); - await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId1, 'scan'); - await polykeyAgent.gestaltGraph.setGestaltActionByNode(nodeId2, 'scan'); - - const commands = ['identities', 'list', '-np', nodePath]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - expect(result.stdout).toContain('notify'); - expect(result.stdout).toContain('scan'); - expect(result.stdout).toContain(node1.id); - expect(result.stdout).toContain(node2.id); - expect(result.stdout).toContain(node3.id); - expect(result.stdout).toContain(identity1.providerId); - expect(result.stdout).toContain(identity1.identityId); - - const commands2 = [ - 'identities', - 'list', - '-np', - nodePath, - '--format', - 'json', - ]; - const result2 = await testBinUtils.pkStdio(commands2, {}, dataDir); - expect(result2.exitCode).toBe(0); // Succeeds. - expect(result2.stdout).toContain('notify'); - expect(result2.stdout).toContain('scan'); - expect(result2.stdout).toContain(node1.id); - expect(result2.stdout).toContain(node2.id); - expect(result2.stdout).toContain(node3.id); - expect(result2.stdout).toContain(identity1.providerId); - expect(result2.stdout).toContain(identity1.identityId); - }); - }); - describe('commandSearchIdentities', () => { - test('Should find a connected identity.', async () => { - const provider = new TestProvider('provider' as ProviderId); - const identity = { - providerId: provider.id, - identityId: 'connected_user' as IdentityId, - name: 'User', - email: 'user@test.com', - url: 'test.com/user', - }; - provider.users['connected_user'] = identity; - provider.users[testToken.identityId].connected = ['connected_user']; - polykeyAgent.identitiesManager.registerProvider(provider); - // Need an authenticated identity - await polykeyAgent.identitiesManager.putToken( - provider.id, - testToken.identityId, - testToken.tokenData, - ); - const commands = [ - 'identities', - 'search', - '-np', - nodePath, - '--provider-id', - 'provider', - '--format', - 'json', - ]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - expect(JSON.parse(result.stdout)).toEqual(identity); - await polykeyAgent.identitiesManager.delToken( - provider.id, - testToken.identityId, - ); - polykeyAgent.identitiesManager.unregisterProvider(provider.id); - }); - }); - describe('commandDiscoverGestalts', () => { - let rootDataDir; - // Test variables - const testProvider = new TestProvider('discovery-provider' as ProviderId); - const identityId = 'connected-identity' as IdentityId; - let nodeB: PolykeyAgent; - let nodeC: PolykeyAgent; - beforeAll(async () => { - rootDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - // Setup the remote gestalt state here - // Setting up remote nodes. - nodeB = await PolykeyAgent.createPolykeyAgent({ - password: 'password', - nodePath: path.join(rootDataDir, 'nodeB'), - keysConfig: { - rootKeyPairBits: 2048, - }, - logger, - }); - nodeC = await PolykeyAgent.createPolykeyAgent({ - password: 'password', - nodePath: path.join(rootDataDir, 'nodeC'), - keysConfig: { - rootKeyPairBits: 2048, - }, - logger, - }); - - // Forming links - // B->C - // Adding connection details. - await testNodesUtils.nodesConnect(polykeyAgent, nodeB); - await testNodesUtils.nodesConnect(nodeB, nodeC); - await testNodesUtils.nodesConnect(polykeyAgent, nodeC); - // Adding sigchain details. - await nodeB.nodeManager.claimNode(nodeC.keyManager.getNodeId()); - - // Setting up identtiy. - testProvider.users[identityId] = {}; - polykeyAgent.identitiesManager.registerProvider(testProvider); - nodeB.identitiesManager.registerProvider(testProvider); - await nodeB.identitiesManager.putToken(testProvider.id, identityId, { - accessToken: 'def456', - }); - - const claimIdentToB: ClaimLinkIdentity = { - type: 'identity', - node: nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), - provider: testProvider.id, - identity: identityId, - }; - const [, claimEncoded] = await nodeB.sigchain.addClaim(claimIdentToB); - const claim = claimsUtils.decodeClaim(claimEncoded); - await testProvider.publishClaim(identityId, claim); - }, global.defaultTimeout * 3); - afterAll(async () => { - await nodeC.stop(); - await nodeB.stop(); - // Unclaim identity - testProvider.links = {}; - testProvider.linkIdCounter = 0; - await fs.promises.rm(rootDataDir, { - force: true, - recursive: true, - }); - }); - beforeEach(async () => { - await polykeyAgent.gestaltGraph.clearDB(); - }); - afterEach(async () => { - // Clean the local nodes gestalt graph here. - await polykeyAgent.gestaltGraph.clearDB(); - await nodeB.gestaltGraph.clearDB(); - await nodeC.gestaltGraph.clearDB(); - }); - test('Should start discovery by Node', async () => { - // Authenticate identity - await polykeyAgent.identitiesManager.putToken( - testProvider.id, - testToken.identityId, - testToken.tokenData, - ); - - const commands = [ - 'identities', - 'discover', - '-np', - nodePath, - nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), - '-vvvv', - ]; - const result = await testBinUtils.pkStdio(commands); - expect(result.exitCode).toBe(0); - // Should eventually discover entire gestalt - const gestalt = await poll( - async () => { - const gestalts = await poll>( - async () => { - return await polykeyAgent.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, - ); - const gestaltString = JSON.stringify(gestalt); - expect(gestaltString).toContain( - nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), - ); - expect(gestaltString).toContain( - nodesUtils.encodeNodeId(nodeC.keyManager.getNodeId()), - ); - expect(gestaltString).toContain(identityId); - // Unauthenticate identity - await polykeyAgent.identitiesManager.delToken( - testProvider.id, - testToken.identityId, - ); - }); - test('Should start discovery by Identity', async () => { - // Authenticate identity - await polykeyAgent.identitiesManager.putToken( - testProvider.id, - testToken.identityId, - testToken.tokenData, - ); - const commands = [ - 'identities', - 'discover', - '-np', - nodePath, - identityString(testProvider.id, identityId), - ]; - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); - // Should eventually discover entire gestalt - const gestalt = await poll( - async () => { - const gestalts = await poll>( - async () => { - return await polykeyAgent.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, - ); - const gestaltString = JSON.stringify(gestalt); - expect(gestaltString).toContain( - nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), - ); - expect(gestaltString).toContain( - nodesUtils.encodeNodeId(nodeC.keyManager.getNodeId()), - ); - expect(gestaltString).toContain(identityId); - // Unauthenticate identity - await polykeyAgent.identitiesManager.delToken( - testProvider.id, - testToken.identityId, - ); - }); - }); -}); diff --git a/tests/bin/identities/search.test.ts b/tests/bin/identities/search.test.ts new file mode 100644 index 000000000..e4377d19b --- /dev/null +++ b/tests/bin/identities/search.test.ts @@ -0,0 +1,363 @@ +import type { IdentityData, IdentityId, ProviderId } from '@/identities/types'; +import type { Host } from '@/network/types'; +import os from 'os'; +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 identitiesUtils from '@/identities/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testBinUtils from '../utils'; +import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('search', () => { + const logger = new Logger('search test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const identityId = 'test_user' as IdentityId; + // Provider setup + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const provider3 = new TestProvider('provider3' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + const user2 = { + providerId: provider1.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + const user3 = { + providerId: provider1.id, + identityId: 'user3' as IdentityId, + name: 'User3', + email: 'user3@test.com', + url: 'test.com/user3', + }; + const user4 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User4', + email: 'user4@test.com', + url: 'test.com/user4', + }; + const user5 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User5', + email: 'user5@test.com', + url: 'test.com/user5', + }; + const user6 = { + providerId: provider2.id, + identityId: 'user3' as IdentityId, + name: 'User6', + email: 'user6@test.com', + url: 'test.com/user6', + }; + const user7 = { + providerId: provider3.id, + identityId: 'user1' as IdentityId, + name: 'User7', + email: 'user7@test.com', + url: 'test.com/user7', + }; + const user8 = { + providerId: provider3.id, + identityId: 'user2' as IdentityId, + name: 'User8', + email: 'user8@test.com', + url: 'test.com/user8', + }; + const user9 = { + providerId: provider3.id, + identityId: 'user3' as IdentityId, + name: 'User9', + email: 'user9@test.com', + url: 'test.com/user9', + }; + provider1.users['user1'] = user1; + provider1.users['user2'] = user2; + provider1.users['user3'] = user3; + provider2.users['user1'] = user4; + provider2.users['user2'] = user5; + provider2.users['user3'] = user6; + provider3.users['user1'] = user7; + provider3.users['user2'] = user8; + provider3.users['user3'] = user9; + // Connect all identities to our own except for user9 + provider1.users[identityId].connected = [ + user1.identityId, + user2.identityId, + user3.identityId, + ]; + provider2.users[identityId].connected = [ + user4.identityId, + user5.identityId, + user6.identityId, + ]; + provider3.users[identityId].connected = [user7.identityId, user8.identityId]; + let dataDir: string; + let nodePath: string; + let pkAgent: PolykeyAgent; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'polykey'); + // Cannot use global shared agent since we need to register a provider + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + pkAgent.identitiesManager.registerProvider(provider1); + pkAgent.identitiesManager.registerProvider(provider2); + pkAgent.identitiesManager.registerProvider(provider3); + }); + afterAll(async () => { + await pkAgent.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('finds connected identities', async () => { + let exitCode, stdout; + let searchResults: Array; + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + // Search with no authenticated identities + // Should return nothing + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'search', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(stdout).toBe(''); + // Authenticate an identity for provider1 + await testBinUtils.pkStdio( + ['identities', 'authenticate', provider1.id, identityId], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // Now our search should include the identities from provider1 + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'search', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + searchResults = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(searchResults).toHaveLength(3); + expect(searchResults).toContainEqual(user1); + expect(searchResults).toContainEqual(user2); + expect(searchResults).toContainEqual(user3); + // Authenticate an identity for provider2 + await testBinUtils.pkStdio( + ['identities', 'authenticate', provider2.id, identityId], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // Now our search should include the identities from provider1 and + // provider2 + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'search', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + searchResults = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(searchResults).toHaveLength(6); + expect(searchResults).toContainEqual(user1); + expect(searchResults).toContainEqual(user2); + expect(searchResults).toContainEqual(user3); + expect(searchResults).toContainEqual(user4); + expect(searchResults).toContainEqual(user5); + expect(searchResults).toContainEqual(user6); + // We can narrow this search by providing search terms + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'search', '4', '5', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + searchResults = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(searchResults).toHaveLength(2); + expect(searchResults).toContainEqual(user4); + expect(searchResults).toContainEqual(user5); + // Authenticate an identity for provider3 + await testBinUtils.pkStdio( + ['identities', 'authenticate', provider3.id, identityId], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + // We can get results from only some providers using the --provider-id + // option + ({ exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'identities', + 'search', + '--provider-id', + provider2.id, + provider3.id, + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + searchResults = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(searchResults).toHaveLength(5); + expect(searchResults).toContainEqual(user4); + expect(searchResults).toContainEqual(user5); + expect(searchResults).toContainEqual(user6); + expect(searchResults).toContainEqual(user7); + expect(searchResults).toContainEqual(user8); + ({ exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'identities', + 'search', + '--provider-id', + provider2.id, + '--provider-id', + provider3.id, + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + searchResults = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(searchResults).toHaveLength(5); + expect(searchResults).toContainEqual(user4); + expect(searchResults).toContainEqual(user5); + expect(searchResults).toContainEqual(user6); + expect(searchResults).toContainEqual(user7); + expect(searchResults).toContainEqual(user8); + // We can search for a specific identity id across providers + // This will find identities even if they're disconnected + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'search', '--identity-id', 'user3', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + searchResults = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(searchResults).toHaveLength(3); + expect(searchResults).toContainEqual(user3); + expect(searchResults).toContainEqual(user6); + expect(searchResults).toContainEqual(user9); + // We can limit the number of search results to display + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'search', '--limit', '2', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + searchResults = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(searchResults).toHaveLength(2); + // Revert side effects + await pkAgent.identitiesManager.delToken(provider1.id, identityId); + await pkAgent.identitiesManager.delToken(provider2.id, identityId); + await pkAgent.identitiesManager.delToken(provider3.id, identityId); + mockedBrowser.mockRestore(); + }); + test('should fail on invalid inputs', async () => { + let exitCode; + // Invalid identity id + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'search', '--identity-id', ''], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Invalid auth identity id + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'search', '--auth-identity-id', ''], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Invalid value for limit + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'search', '--limit', 'NaN'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + }); +}); diff --git a/tests/bin/identities/trustUntrustList.test.ts b/tests/bin/identities/trustUntrustList.test.ts new file mode 100644 index 000000000..7b933d33e --- /dev/null +++ b/tests/bin/identities/trustUntrustList.test.ts @@ -0,0 +1,417 @@ +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 '@/nodes/types'; +import os from 'os'; +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 * as nodesUtils from '@/nodes/utils'; +import * as keysUtils from '@/keys/utils'; +import * as claimsUtils from '@/claims/utils'; +import * as identitiesUtils from '@/identities/utils'; +import * as testBinUtils from '../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 dataDir: string; + let nodePath: string; + let pkAgent: PolykeyAgent; + let node: PolykeyAgent; + let nodeId: NodeId; + let nodeHost: Host; + let nodePort: Port; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + const nodeKeyPair = await keysUtils.generateKeyPair(2048); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair) + .mockResolvedValue(nodeKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair) + .mockResolvedValue(nodeKeyPair); + // Cannot use global shared agent since we need to register a provider + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'polykey'); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + pkAgent.identitiesManager.registerProvider(provider); + // Set up a gestalt to trust + const nodePathGestalt = path.join(dataDir, 'gestalt'); + node = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath: nodePathGestalt, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + nodeId = node.keyManager.getNodeId(); + nodeHost = node.revProxy.getIngressHost(); + nodePort = node.revProxy.getIngressPort(); + node.identitiesManager.registerProvider(provider); + await node.identitiesManager.putToken(provider.id, identity, { + accessToken: 'def456', + }); + provider.users[identity] = {}; + const identityClaim: ClaimLinkIdentity = { + type: 'identity', + node: nodesUtils.encodeNodeId(node.keyManager.getNodeId()), + provider: provider.id, + identity: identity, + }; + const [, claimEncoded] = await node.sigchain.addClaim(identityClaim); + const claim = claimsUtils.decodeClaim(claimEncoded); + await provider.publishClaim(identity, claim); + }, globalThis.maxTimeout); + afterAll(async () => { + await node.stop(); + await pkAgent.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('trusts and untrusts a gestalt by node, adds it to the gestalt graph, and lists the gestalt with notify permission', async () => { + let exitCode, stdout; + // Add the node to our node graph and authenticate an identity on the + // provider + // This allows us to contact the members of the gestalt we want to trust + await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(nodeId), + nodeHost, + `${nodePort}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + await testBinUtils.pkStdio( + [ + 'identities', + 'authenticate', + testToken.providerId, + testToken.identityId, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + mockedBrowser.mockRestore(); + // Trust node - this should trigger discovery on the gestalt the node + // belongs to and add it to our gestalt graph + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'trust', nodesUtils.encodeNodeId(nodeId)], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(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 === 2) return true; + return false; + }, + 100, + ); + // Check that gestalt was discovered and permission was set + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'list', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)[0]).toEqual({ + permissions: ['notify'], + nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + identities: [ + { + providerId: provider.id, + identityId: identity, + }, + ], + }); + // Untrust the gestalt by node + // This should remove the permission, but not the gestalt (from the gestalt + // graph) + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'untrust', nodesUtils.encodeNodeId(nodeId)], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Check that gestalt still exists but has no permissions + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'list', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)[0]).toEqual({ + permissions: null, + nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + identities: [ + { + providerId: provider.id, + identityId: identity, + }, + ], + }); + // Revert side-effects + await pkAgent.gestaltGraph.unsetNode(nodeId); + await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); + await pkAgent.nodeGraph.unsetNode(nodeId); + await pkAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + // @ts-ignore - get protected property + pkAgent.discovery.visitedVertices.clear(); + }); + test('trusts and untrusts a gestalt by identity, adds it to the gestalt graph, and lists the gestalt with notify permission', async () => { + let exitCode, stdout; + // Add the node to our node graph and authenticate an identity on the + // provider + // This allows us to contact the members of the gestalt we want to trust + await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(nodeId), + nodeHost, + `${nodePort}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + const mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); + await testBinUtils.pkStdio( + [ + 'identities', + 'authenticate', + testToken.providerId, + testToken.identityId, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + mockedBrowser.mockRestore(); + // Trust identity - this should trigger discovery on the gestalt the node + // belongs to and add it to our gestalt graph + // This command should fail first time as we need to allow time for the + // identity to be linked to a node in the node graph + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'trust', providerString], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(1); + // 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 === 2) return true; + return false; + }, + 100, + ); + // This time the command should succeed + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'trust', providerString], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Check that gestalt was discovered and permission was set + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'list', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)[0]).toEqual({ + permissions: ['notify'], + nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + identities: [ + { + providerId: provider.id, + identityId: identity, + }, + ], + }); + // Untrust the gestalt by node + // This should remove the permission, but not the gestalt (from the gestalt + // graph) + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'untrust', providerString], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Check that gestalt still exists but has no permissions + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['identities', 'list', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toHaveLength(1); + expect(JSON.parse(stdout)[0]).toEqual({ + permissions: null, + nodes: [{ id: nodesUtils.encodeNodeId(nodeId) }], + identities: [ + { + providerId: provider.id, + identityId: identity, + }, + ], + }); + // Revert side-effects + await pkAgent.gestaltGraph.unsetNode(nodeId); + await pkAgent.gestaltGraph.unsetIdentity(provider.id, identity); + await pkAgent.nodeGraph.unsetNode(nodeId); + await pkAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + // @ts-ignore - get protected property + pkAgent.discovery.visitedVertices.clear(); + }); + test('should fail on invalid inputs', async () => { + let exitCode; + // Trust + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'trust', 'invalid'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + // Untrust + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'untrust', 'invalid'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(sysexits.USAGE); + }); +}); diff --git a/tests/bin/keys/cert.test.ts b/tests/bin/keys/cert.test.ts index 00bac9aeb..e0411d7fd 100644 --- a/tests/bin/keys/cert.test.ts +++ b/tests/bin/keys/cert.test.ts @@ -1,6 +1,3 @@ -import os from 'os'; -import path from 'path'; -import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; @@ -17,20 +14,8 @@ describe('cert', () => { afterAll(async () => { await globalAgentClose(); }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); test('cert gets the certificate', async () => { - const { exitCode, stdout } = await testBinUtils.pkStdio( + let { exitCode, stdout } = await testBinUtils.pkStdio( ['keys', 'cert', '--format', 'json'], { PK_NODE_PATH: globalAgentDir, @@ -39,6 +24,20 @@ describe('cert', () => { globalAgentDir, ); expect(exitCode).toBe(0); - expect(stdout).toContain('Root certificate:'); + expect(JSON.parse(stdout)).toEqual({ + cert: expect.any(String), + }); + const certCommand = JSON.parse(stdout).cert; + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['agent', 'status', '--format', 'json'], + { + PK_NODE_PATH: globalAgentDir, + PK_PASSWORD: globalAgentPassword, + }, + globalAgentDir, + )); + expect(exitCode).toBe(0); + const certStatus = JSON.parse(stdout).rootCertPem; + expect(certCommand).toBe(certStatus); }); }); diff --git a/tests/bin/keys/certchain.test.ts b/tests/bin/keys/certchain.test.ts index 8626042fc..1391b997d 100644 --- a/tests/bin/keys/certchain.test.ts +++ b/tests/bin/keys/certchain.test.ts @@ -1,6 +1,3 @@ -import os from 'os'; -import path from 'path'; -import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; @@ -19,20 +16,8 @@ describe('certchain', () => { afterAll(async () => { await globalAgentClose(); }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); test('certchain gets the certificate chain', async () => { - const { exitCode, stdout } = await testBinUtils.pkStdio( + let { exitCode, stdout } = await testBinUtils.pkStdio( ['keys', 'certchain', '--format', 'json'], { PK_NODE_PATH: globalAgentDir, @@ -41,6 +26,20 @@ describe('certchain', () => { globalAgentDir, ); expect(exitCode).toBe(0); - expect(stdout).toContain('Certificate:'); + expect(JSON.parse(stdout)).toEqual({ + certchain: expect.any(Array), + }); + const certChainCommand = JSON.parse(stdout).certchain.join('\n'); + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['agent', 'status', '--format', 'json'], + { + PK_NODE_PATH: globalAgentDir, + PK_PASSWORD: globalAgentPassword, + }, + globalAgentDir, + )); + expect(exitCode).toBe(0); + const certChainStatus = JSON.parse(stdout).rootCertChainPem; + expect(certChainCommand).toBe(certChainStatus); }); }); diff --git a/tests/bin/keys/decrypt.test.ts b/tests/bin/keys/decrypt.test.ts deleted file mode 100644 index 92dc14141..000000000 --- a/tests/bin/keys/decrypt.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { PublicKey } from '@/keys/types'; -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import * as keysUtils from '@/keys/utils'; -import * as testBinUtils from '../utils'; -import * as testUtils from '../../utils'; - -describe('decrypt', () => { - const logger = new Logger('decrypt test', LogLevel.WARN, [ - new StreamHandler(), - ]); - let globalAgentDir; - let globalAgentPassword; - let globalAgentClose; - beforeAll(async () => { - ({ globalAgentDir, globalAgentPassword, globalAgentClose } = - await testUtils.setupGlobalAgent(logger)); - }, globalThis.maxTimeout); - afterAll(async () => { - await globalAgentClose(); - }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('decrypts data', async () => { - const dataPath = path.join(globalAgentDir, 'data'); - const secret = Buffer.from('this is the secret', 'binary'); - const { stdout } = await testBinUtils.pkStdio( - ['keys', 'root'], - { - PK_NODE_PATH: globalAgentDir, - PK_PASSWORD: globalAgentPassword, - }, - globalAgentDir, - ); - const pubKey = keysUtils.publicKeyFromPem(stdout.slice(13)) as PublicKey; - const encrypted = keysUtils.encryptWithPublicKey(pubKey, secret); - await fs.promises.writeFile(dataPath, encrypted, { encoding: 'binary' }); - const res = await testBinUtils.pkStdio( - ['keys', 'decrypt', dataPath, '--format', 'json'], - { - PK_NODE_PATH: globalAgentDir, - PK_PASSWORD: globalAgentPassword, - }, - globalAgentDir, - ); - expect(res.exitCode).toBe(0); - expect(res.stdout).toContain('Decrypted data:'); - }); -}); diff --git a/tests/bin/keys/encrypt.test.ts b/tests/bin/keys/encrypt.test.ts deleted file mode 100644 index 23bc4211a..000000000 --- a/tests/bin/keys/encrypt.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import * as testBinUtils from '../utils'; -import * as testUtils from '../../utils'; - -describe('encrypt', () => { - const logger = new Logger('encrypt test', LogLevel.WARN, [ - new StreamHandler(), - ]); - let globalAgentDir; - let globalAgentPassword; - let globalAgentClose; - beforeAll(async () => { - ({ globalAgentDir, globalAgentPassword, globalAgentClose } = - await testUtils.setupGlobalAgent(logger)); - }, globalThis.maxTimeout); - afterAll(async () => { - await globalAgentClose(); - }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('encrypts data', async () => { - const dataPath = path.join(globalAgentDir, 'data'); - await fs.promises.writeFile(dataPath, 'encrypt-me', { - encoding: 'binary', - }); - const { exitCode, stdout } = await testBinUtils.pkStdio( - ['keys', 'encrypt', dataPath, '--format', 'json'], - { - PK_NODE_PATH: globalAgentDir, - PK_PASSWORD: globalAgentPassword, - }, - globalAgentDir, - ); - expect(exitCode).toBe(0); - expect(stdout).toContain('Encrypted data:'); - }); -}); diff --git a/tests/bin/keys/encryptDecrypt.test.ts b/tests/bin/keys/encryptDecrypt.test.ts new file mode 100644 index 000000000..cae1e0b42 --- /dev/null +++ b/tests/bin/keys/encryptDecrypt.test.ts @@ -0,0 +1,56 @@ +import path from 'path'; +import fs from 'fs'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import * as testBinUtils from '../utils'; +import * as testUtils from '../../utils'; + +describe('encrypt-decrypt', () => { + const logger = new Logger('encrypt-decrypt test', LogLevel.WARN, [ + new StreamHandler(), + ]); + let globalAgentDir; + let globalAgentPassword; + let globalAgentClose; + beforeAll(async () => { + ({ globalAgentDir, globalAgentPassword, globalAgentClose } = + await testUtils.setupGlobalAgent(logger)); + }, globalThis.maxTimeout); + afterAll(async () => { + await globalAgentClose(); + }); + test('encrypts and decrypts data', async () => { + let exitCode, stdout; + const dataPath = path.join(globalAgentDir, 'data'); + await fs.promises.writeFile(dataPath, 'abc', { + encoding: 'binary', + }); + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['keys', 'encrypt', dataPath, '--format', 'json'], + { + PK_NODE_PATH: globalAgentDir, + PK_PASSWORD: globalAgentPassword, + }, + globalAgentDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + encryptedData: expect.any(String), + }); + const encrypted = JSON.parse(stdout).encryptedData; + await fs.promises.writeFile(dataPath, encrypted, { + encoding: 'binary', + }); + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['keys', 'decrypt', dataPath, '--format', 'json'], + { + PK_NODE_PATH: globalAgentDir, + PK_PASSWORD: globalAgentPassword, + }, + globalAgentDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + decryptedData: 'abc', + }); + }); +}); diff --git a/tests/bin/keys/password.test.ts b/tests/bin/keys/password.test.ts index 7cb01e12e..83cf8c26f 100644 --- a/tests/bin/keys/password.test.ts +++ b/tests/bin/keys/password.test.ts @@ -1,4 +1,3 @@ -import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; @@ -19,23 +18,11 @@ describe('password', () => { afterAll(async () => { await globalAgentClose(); }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); test('password changes the root password', async () => { const passPath = path.join(globalAgentDir, 'passwordChange'); await fs.promises.writeFile(passPath, 'password-change'); let { exitCode } = await testBinUtils.pkStdio( - ['keys', 'password', '-pnf', passPath], + ['keys', 'password', '--password-new-file', passPath], { PK_NODE_PATH: globalAgentDir, PK_PASSWORD: globalAgentPassword, @@ -43,10 +30,20 @@ describe('password', () => { globalAgentDir, ); expect(exitCode).toBe(0); - // Revert side effects + // Old password should no longer work + ({ exitCode } = await testBinUtils.pkStdio( + ['keys', 'root'], + { + PK_NODE_PATH: globalAgentDir, + PK_PASSWORD: globalAgentPassword, + }, + globalAgentDir, + )); + expect(exitCode).not.toBe(0); + // Revert side effects using new password await fs.promises.writeFile(passPath, globalAgentPassword); ({ exitCode } = await testBinUtils.pkStdio( - ['keys', 'password', '-pnf', passPath], + ['keys', 'password', '--password-new-file', passPath], { PK_NODE_PATH: globalAgentDir, PK_PASSWORD: 'password-change', diff --git a/tests/bin/keys/renew.test.ts b/tests/bin/keys/renew.test.ts index a4721d757..043e5bf6b 100644 --- a/tests/bin/keys/renew.test.ts +++ b/tests/bin/keys/renew.test.ts @@ -1,12 +1,10 @@ -import type { TLSConfig } from '@/network/types'; +import type { Host } from '@/network/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import PolykeyAgent from '@/PolykeyAgent'; import * as keysUtils from '@/keys/utils'; -import { Status } from '@/status'; -import { PolykeyAgent } from '@'; -import config from '@/config'; import * as testUtils from '../../utils'; import * as testBinUtils from '../utils'; @@ -36,9 +34,16 @@ describe('renew', () => { pkAgent = await PolykeyAgent.createPolykeyAgent({ password, nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, logger, }); - }, globalThis.maxTimeout); + }, global.defaultTimeout * 2); afterAll(async () => { await pkAgent.stop(); await fs.promises.rm(dataDir, { @@ -49,66 +54,69 @@ describe('renew', () => { mockedGenerateDeterministicKeyPair.mockRestore(); }); test('renews the keypair', async () => { - const rootKeyPair1 = pkAgent.keyManager.getRootKeyPairPem(); - const nodeId1 = pkAgent.keyManager.getNodeId(); - // @ts-ignore - get protected property - const fwdTLSConfig1 = pkAgent.fwdProxy.tlsConfig; - // @ts-ignore - get protected property - const revTLSConfig1 = pkAgent.revProxy.tlsConfig; - // @ts-ignore - get protected property - const serverTLSConfig1 = pkAgent.grpcServerClient.tlsConfig; - const expectedTLSConfig1: TLSConfig = { - keyPrivatePem: rootKeyPair1.privateKey, - certChainPem: await pkAgent.keyManager.getRootCertChainPem(), - }; - const status = new Status({ - statusPath: path.join(nodePath, config.defaults.statusBase), - statusLockPath: path.join(nodePath, config.defaults.statusLockBase), - fs, - logger, - }); - const nodeIdStatus1 = (await status.readStatus())!.data.nodeId; - expect(fwdTLSConfig1).toEqual(expectedTLSConfig1); - expect(revTLSConfig1).toEqual(expectedTLSConfig1); - expect(serverTLSConfig1).toEqual(expectedTLSConfig1); - expect(nodeId1.equals(nodeIdStatus1)).toBe(true); + // Get previous keypair and nodeId + let { exitCode, stdout } = await testBinUtils.pkStdio( + ['keys', 'root', '--private-key', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + const prevPublicKey = JSON.parse(stdout).publicKey; + const prevPrivateKey = JSON.parse(stdout).privateKey; + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['agent', 'status', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + const prevNodeId = JSON.parse(stdout).nodeId; // Renew keypair - const passPath = path.join(dataDir, 'passwordNew'); + const passPath = path.join(dataDir, 'renew-password'); await fs.promises.writeFile(passPath, 'password-new'); - let { exitCode } = await testBinUtils.pkStdio( - ['keys', 'renew', '-pnf', passPath], + ({ exitCode } = await testBinUtils.pkStdio( + ['keys', 'renew', '--password-new-file', passPath], { PK_NODE_PATH: nodePath, PK_PASSWORD: password, }, dataDir, - ); + )); + expect(exitCode).toBe(0); + // Get new keypair and nodeId and compare against old + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['keys', 'root', '--private-key', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: 'password-new', + }, + dataDir, + )); + expect(exitCode).toBe(0); + const newPublicKey = JSON.parse(stdout).publicKey; + const newPrivateKey = JSON.parse(stdout).privateKey; + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['agent', 'status', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: 'password-new', + }, + dataDir, + )); expect(exitCode).toBe(0); - const rootKeyPair2 = pkAgent.keyManager.getRootKeyPairPem(); - const nodeId2 = pkAgent.keyManager.getNodeId(); - // @ts-ignore - get protected property - const fwdTLSConfig2 = pkAgent.fwdProxy.tlsConfig; - // @ts-ignore - get protected property - const revTLSConfig2 = pkAgent.revProxy.tlsConfig; - // @ts-ignore - get protected property - const serverTLSConfig2 = pkAgent.grpcServerClient.tlsConfig; - const expectedTLSConfig2: TLSConfig = { - keyPrivatePem: rootKeyPair2.privateKey, - certChainPem: await pkAgent.keyManager.getRootCertChainPem(), - }; - const nodeIdStatus2 = (await status.readStatus())!.data.nodeId; - expect(fwdTLSConfig2).toEqual(expectedTLSConfig2); - expect(revTLSConfig2).toEqual(expectedTLSConfig2); - expect(serverTLSConfig2).toEqual(expectedTLSConfig2); - expect(rootKeyPair2.privateKey).not.toBe(rootKeyPair1.privateKey); - expect(rootKeyPair2.publicKey).not.toBe(rootKeyPair1.publicKey); - expect(nodeId1).not.toBe(nodeId2); - expect(nodeIdStatus1).not.toBe(nodeIdStatus2); - expect(nodeId2.equals(nodeIdStatus2)).toBe(true); + const newNodeId = JSON.parse(stdout).nodeId; + expect(newPublicKey).not.toBe(prevPublicKey); + expect(newPrivateKey).not.toBe(prevPrivateKey); + expect(newNodeId).not.toBe(prevNodeId); // Revert side effects await fs.promises.writeFile(passPath, password); ({ exitCode } = await testBinUtils.pkStdio( - ['keys', 'password', '-pnf', passPath], + ['keys', 'password', '--password-new-file', passPath], { PK_NODE_PATH: nodePath, PK_PASSWORD: 'password-new', diff --git a/tests/bin/keys/reset.test.ts b/tests/bin/keys/reset.test.ts index 5a220343b..3acb10600 100644 --- a/tests/bin/keys/reset.test.ts +++ b/tests/bin/keys/reset.test.ts @@ -1,17 +1,15 @@ -import type { TLSConfig } from '@/network/types'; +import type { Host } from '@/network/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import PolykeyAgent from '@/PolykeyAgent'; import * as keysUtils from '@/keys/utils'; -import { Status } from '@/status'; -import { PolykeyAgent } from '@'; -import config from '@/config'; import * as testUtils from '../../utils'; import * as testBinUtils from '../utils'; -describe('renew', () => { - const logger = new Logger('renew test', LogLevel.WARN, [new StreamHandler()]); +describe('reset', () => { + const logger = new Logger('reset test', LogLevel.WARN, [new StreamHandler()]); const password = 'helloworld'; let dataDir: string; let nodePath: string; @@ -36,9 +34,16 @@ describe('renew', () => { pkAgent = await PolykeyAgent.createPolykeyAgent({ password, nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, logger, }); - }, globalThis.maxTimeout); + }, global.defaultTimeout * 2); afterAll(async () => { await pkAgent.stop(); await fs.promises.rm(dataDir, { @@ -49,34 +54,9 @@ describe('renew', () => { mockedGenerateDeterministicKeyPair.mockRestore(); }); test('resets the keypair', async () => { - const rootKeyPair1 = pkAgent.keyManager.getRootKeyPairPem(); - const nodeId1 = pkAgent.keyManager.getNodeId(); - // @ts-ignore - get protected property - const fwdTLSConfig1 = pkAgent.fwdProxy.tlsConfig; - // @ts-ignore - get protected property - const revTLSConfig1 = pkAgent.revProxy.tlsConfig; - // @ts-ignore - get protected property - const serverTLSConfig1 = pkAgent.grpcServerClient.tlsConfig; - const expectedTLSConfig1: TLSConfig = { - keyPrivatePem: rootKeyPair1.privateKey, - certChainPem: await pkAgent.keyManager.getRootCertChainPem(), - }; - const status = new Status({ - statusPath: path.join(nodePath, config.defaults.statusBase), - statusLockPath: path.join(nodePath, config.defaults.statusLockBase), - fs, - logger, - }); - const nodeIdStatus1 = (await status.readStatus())!.data.nodeId; - expect(fwdTLSConfig1).toEqual(expectedTLSConfig1); - expect(revTLSConfig1).toEqual(expectedTLSConfig1); - expect(serverTLSConfig1).toEqual(expectedTLSConfig1); - expect(nodeId1.equals(nodeIdStatus1)).toBe(true); - // Renew keypair - const passPath = path.join(dataDir, 'passwordNew'); - await fs.promises.writeFile(passPath, 'password-new'); - let { exitCode } = await testBinUtils.pkStdio( - ['keys', 'reset', '-pnf', passPath], + // Get previous keypair and nodeId + let { exitCode, stdout } = await testBinUtils.pkStdio( + ['keys', 'root', '--private-key', '--format', 'json'], { PK_NODE_PATH: nodePath, PK_PASSWORD: password, @@ -84,31 +64,59 @@ describe('renew', () => { dataDir, ); expect(exitCode).toBe(0); - const rootKeyPair2 = pkAgent.keyManager.getRootKeyPairPem(); - const nodeId2 = pkAgent.keyManager.getNodeId(); - // @ts-ignore - get protected property - const fwdTLSConfig2 = pkAgent.fwdProxy.tlsConfig; - // @ts-ignore - get protected property - const revTLSConfig2 = pkAgent.revProxy.tlsConfig; - // @ts-ignore - get protected property - const serverTLSConfig2 = pkAgent.grpcServerClient.tlsConfig; - const expectedTLSConfig2: TLSConfig = { - keyPrivatePem: rootKeyPair2.privateKey, - certChainPem: await pkAgent.keyManager.getRootCertChainPem(), - }; - const nodeIdStatus2 = (await status.readStatus())!.data.nodeId; - expect(fwdTLSConfig2).toEqual(expectedTLSConfig2); - expect(revTLSConfig2).toEqual(expectedTLSConfig2); - expect(serverTLSConfig2).toEqual(expectedTLSConfig2); - expect(rootKeyPair2.privateKey).not.toBe(rootKeyPair1.privateKey); - expect(rootKeyPair2.publicKey).not.toBe(rootKeyPair1.publicKey); - expect(nodeId1).not.toBe(nodeId2); - expect(nodeIdStatus1).not.toBe(nodeIdStatus2); - expect(nodeId2.equals(nodeIdStatus2)).toBe(true); + const prevPublicKey = JSON.parse(stdout).publicKey; + const prevPrivateKey = JSON.parse(stdout).privateKey; + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['agent', 'status', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + const prevNodeId = JSON.parse(stdout).nodeId; + // Reset keypair + const passPath = path.join(dataDir, 'reset-password'); + await fs.promises.writeFile(passPath, 'password-new'); + ({ exitCode } = await testBinUtils.pkStdio( + ['keys', 'reset', '--password-new-file', passPath], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Get new keypair and nodeId and compare against old + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['keys', 'root', '--private-key', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: 'password-new', + }, + dataDir, + )); + expect(exitCode).toBe(0); + const newPublicKey = JSON.parse(stdout).publicKey; + const newPrivateKey = JSON.parse(stdout).privateKey; + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['agent', 'status', '--format', 'json'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: 'password-new', + }, + dataDir, + )); + expect(exitCode).toBe(0); + const newNodeId = JSON.parse(stdout).nodeId; + expect(newPublicKey).not.toBe(prevPublicKey); + expect(newPrivateKey).not.toBe(prevPrivateKey); + expect(newNodeId).not.toBe(prevNodeId); // Revert side effects await fs.promises.writeFile(passPath, password); ({ exitCode } = await testBinUtils.pkStdio( - ['keys', 'password', '-pnf', passPath], + ['keys', 'password', '--password-new-file', passPath], { PK_NODE_PATH: nodePath, PK_PASSWORD: 'password-new', diff --git a/tests/bin/keys/root.test.ts b/tests/bin/keys/root.test.ts index d4cffd3f7..3cc9286e6 100644 --- a/tests/bin/keys/root.test.ts +++ b/tests/bin/keys/root.test.ts @@ -1,6 +1,3 @@ -import os from 'os'; -import path from 'path'; -import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; @@ -17,18 +14,6 @@ describe('root', () => { afterAll(async () => { await globalAgentClose(); }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); test('root gets the public key', async () => { const { exitCode, stdout } = await testBinUtils.pkStdio( ['keys', 'root', '--format', 'json'], @@ -39,11 +24,13 @@ describe('root', () => { globalAgentDir, ); expect(exitCode).toBe(0); - expect(stdout).toContain('public key:'); + expect(JSON.parse(stdout)).toEqual({ + publicKey: expect.any(String), + }); }); - test('root gets the keypair', async () => { + test('root gets public and private keys', async () => { const { exitCode, stdout } = await testBinUtils.pkStdio( - ['keys', 'root', '-pk', '--format', 'json'], + ['keys', 'root', '--private-key', '--format', 'json'], { PK_NODE_PATH: globalAgentDir, PK_PASSWORD: globalAgentPassword, @@ -51,7 +38,9 @@ describe('root', () => { globalAgentDir, ); expect(exitCode).toBe(0); - expect(stdout).toContain('public key:'); - expect(stdout).toContain('private key:'); + expect(JSON.parse(stdout)).toEqual({ + publicKey: expect.any(String), + privateKey: expect.any(String), + }); }); }); diff --git a/tests/bin/keys/sign.test.ts b/tests/bin/keys/sign.test.ts deleted file mode 100644 index eeddf912e..000000000 --- a/tests/bin/keys/sign.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import * as testBinUtils from '../utils'; -import * as testUtils from '../../utils'; - -describe('sign', () => { - const logger = new Logger('sign test', LogLevel.WARN, [new StreamHandler()]); - let globalAgentDir; - let globalAgentPassword; - let globalAgentClose; - beforeAll(async () => { - ({ globalAgentDir, globalAgentPassword, globalAgentClose } = - await testUtils.setupGlobalAgent(logger)); - }, globalThis.maxTimeout); - afterAll(async () => { - await globalAgentClose(); - }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('signs a file', async () => { - const dataPath = path.join(globalAgentDir, 'data'); - await fs.promises.writeFile(dataPath, 'sign-me', { - encoding: 'binary', - }); - const { exitCode, stdout } = await testBinUtils.pkStdio( - ['keys', 'sign', dataPath, '--format', 'json'], - { - PK_NODE_PATH: globalAgentDir, - PK_PASSWORD: globalAgentPassword, - }, - globalAgentDir, - ); - expect(exitCode).toBe(0); - expect(stdout).toContain('Signature:'); - }); -}); diff --git a/tests/bin/keys/signVerify.test.ts b/tests/bin/keys/signVerify.test.ts new file mode 100644 index 000000000..8a72142a7 --- /dev/null +++ b/tests/bin/keys/signVerify.test.ts @@ -0,0 +1,57 @@ +import path from 'path'; +import fs from 'fs'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import * as testBinUtils from '../utils'; +import * as testUtils from '../../utils'; + +describe('sign-verify', () => { + const logger = new Logger('sign-verify test', LogLevel.WARN, [ + new StreamHandler(), + ]); + let globalAgentDir; + let globalAgentPassword; + let globalAgentClose; + beforeAll(async () => { + ({ globalAgentDir, globalAgentPassword, globalAgentClose } = + await testUtils.setupGlobalAgent(logger)); + }, globalThis.maxTimeout); + afterAll(async () => { + await globalAgentClose(); + }); + test('signs and verifies a file', async () => { + let exitCode, stdout; + const dataPath = path.join(globalAgentDir, 'data'); + await fs.promises.writeFile(dataPath, 'sign-me', { + encoding: 'binary', + }); + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['keys', 'sign', dataPath, '--format', 'json'], + { + PK_NODE_PATH: globalAgentDir, + PK_PASSWORD: globalAgentPassword, + }, + globalAgentDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + signature: expect.any(String), + }); + const signed = JSON.parse(stdout).signature; + const signaturePath = path.join(globalAgentDir, 'data2'); + await fs.promises.writeFile(signaturePath, signed, { + encoding: 'binary', + }); + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['keys', 'verify', dataPath, signaturePath, '--format', 'json'], + { + PK_NODE_PATH: globalAgentDir, + PK_PASSWORD: globalAgentPassword, + }, + globalAgentDir, + )); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + signatureVerified: true, + }); + }); +}); diff --git a/tests/bin/keys/verify.test.ts b/tests/bin/keys/verify.test.ts deleted file mode 100644 index cbd707f7c..000000000 --- a/tests/bin/keys/verify.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { PrivateKey } from '@/keys/types'; -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import * as keysUtils from '@/keys/utils'; -import * as testBinUtils from '../utils'; -import * as testUtils from '../../utils'; - -describe('verify', () => { - const logger = new Logger('verify test', LogLevel.WARN, [ - new StreamHandler(), - ]); - let globalAgentDir; - let globalAgentPassword; - let globalAgentClose; - beforeAll(async () => { - ({ globalAgentDir, globalAgentPassword, globalAgentClose } = - await testUtils.setupGlobalAgent(logger)); - }, globalThis.maxTimeout); - afterAll(async () => { - await globalAgentClose(); - }); - let dataDir: string; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - }); - afterEach(async () => { - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('verifies a file', async () => { - const dataPath = path.join(globalAgentDir, 'data'); - await fs.promises.writeFile(dataPath, 'sign-me', { encoding: 'binary' }); - const { stdout } = await testBinUtils.pkStdio( - ['keys', 'root', '-pk'], - { - PK_NODE_PATH: globalAgentDir, - PK_PASSWORD: globalAgentPassword, - }, - globalAgentDir, - ); - const keyPair = stdout.split('\t\t'); - const privKey = keysUtils.privateKeyFromPem(keyPair[2]) as PrivateKey; - const signed = keysUtils.signWithPrivateKey( - privKey, - Buffer.from('sign-me', 'binary'), - ); - const signatureTrue = path.join(globalAgentDir, 'data2'); - await fs.promises.writeFile(signatureTrue, signed, { - encoding: 'binary', - }); - const res = await testBinUtils.pkStdio( - ['keys', 'verify', dataPath, signatureTrue, '--format', 'json'], - { - PK_NODE_PATH: globalAgentDir, - PK_PASSWORD: globalAgentPassword, - }, - globalAgentDir, - ); - expect(res.exitCode).toBe(0); - expect(res.stdout).toContain('Signature verification:'); - }); -}); diff --git a/tests/bin/nodes/add.test.ts b/tests/bin/nodes/add.test.ts index b52bbd47a..48aae33c4 100644 --- a/tests/bin/nodes/add.test.ts +++ b/tests/bin/nodes/add.test.ts @@ -1,119 +1,108 @@ import type { NodeId } from '@/nodes/types'; +import type { Host } from '@/network/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { IdInternal } from '@matrixai/id'; +import { sysexits } from '@/utils'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; -import { sysexits } from '@/utils'; import * as keysUtils from '@/keys/utils'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; describe('add', () => { - const password = 'password'; const logger = new Logger('add test', LogLevel.WARN, [new StreamHandler()]); - let dataDir: string; - let nodePath: string; - let passwordFile: string; - let polykeyAgent: PolykeyAgent; - + const password = 'helloworld'; const validNodeId = testUtils.generateRandomNodeId(); const invalidNodeId = IdInternal.fromString('INVALIDID'); const validHost = '0.0.0.0'; const invalidHost = 'INVALIDHOST'; - const port = 55555; - - // Helper functions - function genCommands(options: Array) { - return ['nodes', ...options, '-np', nodePath]; - } - - const mockedGenerateDeterministicKeyPair = jest.spyOn( - keysUtils, - 'generateDeterministicKeyPair', - ); - - beforeEach(async () => { - mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { - return keysUtils.generateKeyPair(bits); - }); - + const port = '55555'; + let dataDir: string; + let nodePath: string; + let pkAgent: PolykeyAgent; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); - nodePath = path.join(dataDir, 'keynode'); - passwordFile = path.join(dataDir, 'passwordFile'); - await fs.promises.writeFile(passwordFile, 'password'); - polykeyAgent = await PolykeyAgent.createPolykeyAgent({ + nodePath = path.join(dataDir, 'polykey'); + // Cannot use the shared global agent since we can't 'un-add' a node + pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath: nodePath, - logger: logger, - }); - - // Authorize session - await testBinUtils.pkStdio( - ['agent', 'unlock', '-np', nodePath, '--password-file', passwordFile], - {}, nodePath, - ); - }, global.polykeyStartupTimeout); - afterEach(async () => { - await polykeyAgent.stop(); - await polykeyAgent.destroy(); + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + }); + afterAll(async () => { + await pkAgent.stop(); + await pkAgent.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); }); - - test('add a node', async () => { - const commands = genCommands([ - 'add', - nodesUtils.encodeNodeId(validNodeId), - validHost, - port.toString(), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); - + test('adds a node', async () => { + const { exitCode } = await testBinUtils.pkStdio( + ['nodes', 'add', nodesUtils.encodeNodeId(validNodeId), validHost, port], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); // Checking if node was added. - const res = await polykeyAgent.nodeGraph.getNode(validNodeId); - expect(res).toBeTruthy(); - expect(res!.host).toEqual(validHost); - expect(res!.port).toEqual(port); + const { stdout } = await testBinUtils.pkStdio( + ['nodes', 'find', nodesUtils.encodeNodeId(validNodeId)], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(stdout).toContain(validHost); + expect(stdout).toContain(port); + }); + test('fails to add a node (invalid node ID)', async () => { + const { exitCode } = await testBinUtils.pkStdio( + ['nodes', 'add', nodesUtils.encodeNodeId(invalidNodeId), validHost, port], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(sysexits.USAGE); + }); + test('fails to add a node (invalid IP address)', async () => { + const { exitCode } = await testBinUtils.pkStdio( + ['nodes', 'add', nodesUtils.encodeNodeId(validNodeId), invalidHost, port], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(sysexits.USAGE); }); - test( - 'fail to add a node (invalid node ID)', - async () => { - const commands = genCommands([ - 'add', - nodesUtils.encodeNodeId(invalidNodeId), - validHost, - port.toString(), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(sysexits.USAGE); - }, - global.failedConnectionTimeout, - ); - test( - 'fail to add a node (invalid IP address)', - async () => { - const commands = genCommands([ - 'add', - nodesUtils.encodeNodeId(validNodeId), - invalidHost, - port.toString(), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(sysexits.USAGE); - - // Checking if node was added. - const res = await polykeyAgent.nodeGraph.getNode(validNodeId); - expect(res).toBeUndefined(); - }, - global.failedConnectionTimeout, - ); }); diff --git a/tests/bin/nodes/claim.test.ts b/tests/bin/nodes/claim.test.ts index b215e203b..fd9787465 100644 --- a/tests/bin/nodes/claim.test.ts +++ b/tests/bin/nodes/claim.test.ts @@ -1,156 +1,154 @@ import type { NodeId, NodeIdEncoded } from '@/nodes/types'; +import type { Host } from '@/network/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import PolykeyAgent from '@/PolykeyAgent'; -import { utils as nodesUtils } from '@/nodes'; +import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; import * as testBinUtils from '../utils'; import * as testNodesUtils from '../../nodes/utils'; +import * as testUtils from '../../utils'; describe('claim', () => { - const password = 'password'; const logger = new Logger('claim test', LogLevel.WARN, [new StreamHandler()]); - let rootDataDir: string; + const password = 'helloworld'; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; let dataDir: string; let nodePath: string; - let passwordFile: string; - let polykeyAgent: PolykeyAgent; - let remoteOnline: PolykeyAgent; - - let keynodeId: NodeId; - let remoteOnlineNodeId: NodeId; - let remoteOnlineNodeIdEncoded: NodeIdEncoded; - - // Helper functions - function genCommands(options: Array) { - return ['nodes', ...options, '-np', nodePath]; - } - - const mockedGenerateDeterministicKeyPair = jest.spyOn( - keysUtils, - 'generateDeterministicKeyPair', - ); - + let pkAgent: PolykeyAgent; + let remoteNode: PolykeyAgent; + let localId: NodeId; + let remoteId: NodeId; + let remoteIdEncoded: NodeIdEncoded; beforeAll(async () => { - mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { - return keysUtils.generateKeyPair(bits); - }); - - rootDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); nodePath = path.join(dataDir, 'keynode'); - passwordFile = path.join(dataDir, 'passwordFile'); - await fs.promises.writeFile(passwordFile, 'password'); - polykeyAgent = await PolykeyAgent.createPolykeyAgent({ + pkAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath: nodePath, - logger: logger, + nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + keysConfig: { + rootKeyPairBits: 2048, + }, + seedNodes: {}, // Explicitly no seed nodes on startup + logger, }); - keynodeId = polykeyAgent.keyManager.getNodeId(); + localId = pkAgent.keyManager.getNodeId(); // Setting up a remote keynode - remoteOnline = await PolykeyAgent.createPolykeyAgent({ - password: 'password', - nodePath: path.join(rootDataDir, 'remoteOnline'), + remoteNode = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath: path.join(dataDir, 'remoteNode'), + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, keysConfig: { rootKeyPairBits: 2048, }, + seedNodes: {}, // Explicitly no seed nodes on startup logger, }); - remoteOnlineNodeId = remoteOnline.keyManager.getNodeId(); - remoteOnlineNodeIdEncoded = nodesUtils.encodeNodeId(remoteOnlineNodeId); - await testNodesUtils.nodesConnect(polykeyAgent, remoteOnline); - - await remoteOnline.nodeGraph.setNode(keynodeId, { - host: polykeyAgent.revProxy.getIngressHost(), - port: polykeyAgent.revProxy.getIngressPort(), - }); - await polykeyAgent.acl.setNodePerm(remoteOnlineNodeId, { + remoteId = remoteNode.keyManager.getNodeId(); + remoteIdEncoded = nodesUtils.encodeNodeId(remoteId); + await testNodesUtils.nodesConnect(pkAgent, remoteNode); + await pkAgent.acl.setNodePerm(remoteId, { gestalt: { notify: null, }, vaults: {}, }); - await remoteOnline.acl.setNodePerm(keynodeId, { + await remoteNode.acl.setNodePerm(localId, { gestalt: { notify: null, }, vaults: {}, }); - - // Authorize session - await testBinUtils.pkStdio( - ['agent', 'unlock', '-np', nodePath, '--password-file', passwordFile], - {}, - nodePath, - ); - }, global.polykeyStartupTimeout * 2); - - afterEach(async () => { - await polykeyAgent.notificationsManager.clearNotifications(); - await remoteOnline.notificationsManager.clearNotifications(); - await polykeyAgent.sigchain.clearDB(); - await remoteOnline.sigchain.clearDB(); - }); + }, global.defaultTimeout * 2); afterAll(async () => { - await polykeyAgent.stop(); - await polykeyAgent.destroy(); - await remoteOnline.stop(); - await fs.promises.rm(rootDataDir, { - force: true, - recursive: true, - }); + await pkAgent.stop(); + await pkAgent.destroy(); + await remoteNode.stop(); + await remoteNode.destroy(); await fs.promises.rm(dataDir, { force: true, recursive: true, }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('sends a gestalt invite', async () => { + const { exitCode, stdout } = await testBinUtils.pkStdio( + ['nodes', 'claim', remoteIdEncoded], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + expect(stdout).toContain('Gestalt Invite'); + expect(stdout).toContain(remoteIdEncoded); + // Clear side-effects + await remoteNode.notificationsManager.clearNotifications(); }); - test( - 'send a gestalt invite', - async () => { - const commands = genCommands([ - 'claim', - nodesUtils.encodeNodeId(remoteOnlineNodeId), - ]); - const result = await testBinUtils.pkStdio(commands); - expect(result.exitCode).toBe(0); // Succeeds. - expect(result.stdout).toContain('Gestalt Invite'); - expect(result.stdout).toContain(remoteOnlineNodeIdEncoded); - }, - global.polykeyStartupTimeout * 4, - ); - test('send a gestalt invite (force invite)', async () => { - await remoteOnline.notificationsManager.sendNotification(keynodeId, { + test('sends a gestalt invite (force invite)', async () => { + await remoteNode.notificationsManager.sendNotification(localId, { type: 'GestaltInvite', }); - // Needs to be forced, as the local node has already received an invitation - const commands = genCommands([ - 'claim', - nodesUtils.encodeNodeId(remoteOnlineNodeId), - '--force-invite', - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - expect(result.stdout).toContain('Gestalt Invite'); - expect(result.stdout).toContain(remoteOnlineNodeIdEncoded); + const { exitCode, stdout } = await testBinUtils.pkStdio( + ['nodes', 'claim', remoteIdEncoded, '--force-invite'], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + expect(stdout).toContain('Gestalt Invite'); + expect(stdout).toContain(nodesUtils.encodeNodeId(remoteId)); + // Clear side effects + await pkAgent.notificationsManager.clearNotifications(); + await remoteNode.notificationsManager.clearNotifications(); }); - test('claim the remote node', async () => { - await remoteOnline.notificationsManager.sendNotification(keynodeId, { + test('claims a node', async () => { + await remoteNode.notificationsManager.sendNotification(localId, { type: 'GestaltInvite', }); - // Received an invitation, so will attempt to perform the claiming process - const commands = genCommands([ - 'claim', - nodesUtils.encodeNodeId(remoteOnlineNodeId), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); // Succeeds. - expect(result.stdout).toContain('cryptolink claim'); - expect(result.stdout).toContain(remoteOnlineNodeIdEncoded); + const { exitCode, stdout } = await testBinUtils.pkStdio( + ['nodes', 'claim', remoteIdEncoded], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + expect(stdout).toContain('cryptolink claim'); + expect(stdout).toContain(remoteIdEncoded); + // Clear side effects + await pkAgent.notificationsManager.clearNotifications(); + await pkAgent.sigchain.stop(); + await pkAgent.sigchain.start({ fresh: true }); }); }); diff --git a/tests/bin/nodes/find.test.ts b/tests/bin/nodes/find.test.ts index e64786086..802691a8a 100644 --- a/tests/bin/nodes/find.test.ts +++ b/tests/bin/nodes/find.test.ts @@ -9,62 +9,66 @@ import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; import * as testBinUtils from '../utils'; import * as testNodesUtils from '../../nodes/utils'; +import * as testUtils from '../../utils'; describe('find', () => { - const password = 'password'; const logger = new Logger('find test', LogLevel.WARN, [new StreamHandler()]); - let rootDataDir: string; + const password = 'helloworld'; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; let dataDir: string; let nodePath: string; - let passwordFile: string; let polykeyAgent: PolykeyAgent; let remoteOnline: PolykeyAgent; let remoteOffline: PolykeyAgent; - let remoteOnlineNodeId: NodeId; let remoteOfflineNodeId: NodeId; - let remoteOnlineHost: Host; let remoteOnlinePort: Port; let remoteOfflineHost: Host; let remoteOfflinePort: Port; - - // Helper functions - function genCommands(options: Array) { - return ['nodes', ...options, '-np', nodePath]; - } - - const mockedGenerateDeterministicKeyPair = jest.spyOn( - keysUtils, - 'generateDeterministicKeyPair', - ); - beforeAll(async () => { - mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { - return keysUtils.generateKeyPair(bits); - }); - - rootDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); nodePath = path.join(dataDir, 'keynode'); - passwordFile = path.join(dataDir, 'passwordFile'); - await fs.promises.writeFile(passwordFile, 'password'); polykeyAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath: nodePath, - logger: logger, + nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + nodeConnectionManagerConfig: { + connConnectTime: 2000, + connTimeoutTime: 2000, + }, + seedNodes: {}, // Explicitly no seed nodes on startup + logger, }); - // Setting up a remote keynode remoteOnline = await PolykeyAgent.createPolykeyAgent({ - password: 'password', - nodePath: path.join(rootDataDir, 'remoteOnline'), + password, + nodePath: path.join(dataDir, 'remoteOnline'), + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, keysConfig: { - rootKeyPairBits: 2048, + rootKeyPairBits: 1024, }, logger, }); @@ -72,13 +76,19 @@ describe('find', () => { remoteOnlineHost = remoteOnline.revProxy.getIngressHost(); remoteOnlinePort = remoteOnline.revProxy.getIngressPort(); await testNodesUtils.nodesConnect(polykeyAgent, remoteOnline); - // Setting up an offline remote keynode remoteOffline = await PolykeyAgent.createPolykeyAgent({ - password: 'password', - nodePath: path.join(rootDataDir, 'remoteOffline'), + password, + nodePath: path.join(dataDir, 'remoteOffline'), + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, keysConfig: { - rootKeyPairBits: 2048, + rootKeyPairBits: 1024, }, logger, }); @@ -87,132 +97,94 @@ describe('find', () => { remoteOfflinePort = remoteOffline.revProxy.getIngressPort(); await testNodesUtils.nodesConnect(polykeyAgent, remoteOffline); await remoteOffline.stop(); - - // Authorize session - await testBinUtils.pkStdio( - ['agent', 'unlock', '-np', nodePath, '--password-file', passwordFile], - {}, - nodePath, - ); - }, global.polykeyStartupTimeout * 3); + }, global.defaultTimeout * 3); afterAll(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, }); - await fs.promises.rm(rootDataDir, { - force: true, - recursive: true, - }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); }); - - test('find an online node', async () => { - const commands = genCommands([ - 'find', - nodesUtils.encodeNodeId(remoteOnlineNodeId), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('Found node at'); - expect(result.stdout).toContain(remoteOnlineHost); - expect(result.stdout).toContain(remoteOnlinePort); - - // Checking json format. - const commands2 = genCommands([ - 'find', - nodesUtils.encodeNodeId(remoteOnlineNodeId), - '--format', - 'json', - ]); - const result2 = await testBinUtils.pkStdio(commands2, {}, dataDir); - expect(result2.exitCode).toBe(0); - expect(result2.stdout).toContain('success'); - expect(result2.stdout).toContain('true'); - expect(result2.stdout).toContain('message'); - expect(result2.stdout).toContain( - `Found node at ${remoteOnlineHost}:${remoteOnlinePort}`, - ); - expect(result2.stdout).toContain('host'); - expect(result2.stdout).toContain('port'); - expect(result2.stdout).toContain('id'); - expect(result2.stdout).toContain( - nodesUtils.encodeNodeId(remoteOnlineNodeId), + test('finds an online node', async () => { + const { exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'nodes', + 'find', + nodesUtils.encodeNodeId(remoteOnlineNodeId), + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, ); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: `Found node at ${remoteOnlineHost}:${remoteOnlinePort}`, + id: nodesUtils.encodeNodeId(remoteOnlineNodeId), + host: remoteOnlineHost, + port: remoteOnlinePort, + }); }); - test('find an offline node', async () => { - const commands = genCommands([ - 'find', - nodesUtils.encodeNodeId(remoteOfflineNodeId), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('Found node at'); - expect(result.stdout).toContain(remoteOfflineHost); - expect(result.stdout).toContain(remoteOfflinePort); - - // Checking json format. - const commands2 = genCommands([ - 'find', - nodesUtils.encodeNodeId(remoteOfflineNodeId), - '--format', - 'json', - ]); - const result2 = await testBinUtils.pkStdio(commands2, {}, dataDir); - expect(result2.exitCode).toBe(0); - expect(result2.stdout).toContain('success'); - expect(result2.stdout).toContain('true'); - expect(result2.stdout).toContain('message'); - expect(result2.stdout).toContain( - `Found node at ${remoteOfflineHost}:${remoteOfflinePort}`, - ); - expect(result2.stdout).toContain('host'); - expect(result2.stdout).toContain('port'); - expect(result2.stdout).toContain('id'); - expect(result2.stdout).toContain( - nodesUtils.encodeNodeId(remoteOfflineNodeId), + test('finds an offline node', async () => { + const { exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'nodes', + 'find', + nodesUtils.encodeNodeId(remoteOfflineNodeId), + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, ); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: `Found node at ${remoteOfflineHost}:${remoteOfflinePort}`, + id: nodesUtils.encodeNodeId(remoteOfflineNodeId), + host: remoteOfflineHost, + port: remoteOfflinePort, + }); }); - test( - 'fail to find an unknown node', - async () => { - const unknownNodeId = nodesUtils.decodeNodeId( - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - )!; - const commands = genCommands([ - 'find', - nodesUtils.encodeNodeId(unknownNodeId), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(1); - expect(result.stdout).toContain( - `Failed to find node ${nodesUtils.encodeNodeId(unknownNodeId)}`, - ); - - // Checking json format. - const commands2 = genCommands([ + test('fails to find an unknown node', async () => { + const unknownNodeId = nodesUtils.decodeNodeId( + 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', + ); + const { exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'nodes', 'find', - nodesUtils.encodeNodeId(unknownNodeId), + nodesUtils.encodeNodeId(unknownNodeId!), '--format', 'json', - ]); - const result2 = await testBinUtils.pkStdio(commands2, {}, dataDir); - expect(result2.exitCode).toBe(1); - expect(result2.stdout).toContain(`message`); - expect(result2.stdout).toContain( - `Failed to find node ${nodesUtils.encodeNodeId(unknownNodeId)}`, - ); - expect(result2.stdout).toContain('id'); - expect(result2.stdout).toContain(nodesUtils.encodeNodeId(unknownNodeId)); - expect(result2.stdout).toContain('port'); - expect(result2.stdout).toContain('0'); - expect(result2.stdout).toContain('host'); - expect(result2.stdout).toContain('success'); - expect(result2.stdout).toContain('false'); - }, - global.failedConnectionTimeout * 2, - ); + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(1); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to find node ${nodesUtils.encodeNodeId(unknownNodeId!)}`, + id: nodesUtils.encodeNodeId(unknownNodeId!), + host: '', + port: 0, + }); + }); }); diff --git a/tests/bin/nodes/ping.test.ts b/tests/bin/nodes/ping.test.ts index 4ad7cad43..afe076555 100644 --- a/tests/bin/nodes/ping.test.ts +++ b/tests/bin/nodes/ping.test.ts @@ -1,4 +1,5 @@ import type { NodeId } from '@/nodes/types'; +import type { Host } from '@/network/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; @@ -8,172 +9,171 @@ import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; import * as testBinUtils from '../utils'; import * as testNodesUtils from '../../nodes/utils'; +import * as testUtils from '../../utils'; describe('ping', () => { - const password = 'password'; const logger = new Logger('ping test', LogLevel.WARN, [new StreamHandler()]); - let rootDataDir: string; + const password = 'helloworld'; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; let dataDir: string; let nodePath: string; - let passwordFile: string; let polykeyAgent: PolykeyAgent; let remoteOnline: PolykeyAgent; let remoteOffline: PolykeyAgent; - let remoteOnlineNodeId: NodeId; let remoteOfflineNodeId: NodeId; - - // Helper functions - function genCommands(options: Array) { - return ['nodes', ...options, '-np', nodePath]; - } - - const mockedGenerateDeterministicKeyPair = jest.spyOn( - keysUtils, - 'generateDeterministicKeyPair', - ); - beforeAll(async () => { - mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { - return keysUtils.generateKeyPair(bits); - }); - + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); - rootDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); nodePath = path.join(dataDir, 'keynode'); - passwordFile = path.join(dataDir, 'passwordFile'); - await fs.promises.writeFile(passwordFile, 'password'); polykeyAgent = await PolykeyAgent.createPolykeyAgent({ password, - nodePath: nodePath, - logger: logger, + nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + forwardProxyConfig: { + connConnectTime: 2000, + }, + nodeConnectionManagerConfig: { + connConnectTime: 2000, + connTimeoutTime: 1000, + }, + seedNodes: {}, // Explicitly no seed nodes on startup + logger, }); - // Setting up a remote keynode remoteOnline = await PolykeyAgent.createPolykeyAgent({ - password: 'password', - nodePath: path.join(rootDataDir, 'remoteOnline'), + password, + nodePath: path.join(dataDir, 'remoteOnline'), + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, keysConfig: { - rootKeyPairBits: 2048, + rootKeyPairBits: 1024, }, logger, }); remoteOnlineNodeId = remoteOnline.keyManager.getNodeId(); await testNodesUtils.nodesConnect(polykeyAgent, remoteOnline); - // Setting up an offline remote keynode remoteOffline = await PolykeyAgent.createPolykeyAgent({ - password: 'password', - nodePath: path.join(rootDataDir, 'remoteOffline'), + password, + nodePath: path.join(dataDir, 'remoteOffline'), + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, keysConfig: { - rootKeyPairBits: 2048, + rootKeyPairBits: 1024, }, logger, }); remoteOfflineNodeId = remoteOffline.keyManager.getNodeId(); await testNodesUtils.nodesConnect(polykeyAgent, remoteOffline); await remoteOffline.stop(); - - // Authorize session - await testBinUtils.pkStdio( - ['agent', 'unlock', '-np', nodePath, '--password-file', passwordFile], - {}, - nodePath, - ); - }, global.polykeyStartupTimeout * 3); + }, global.defaultTimeout * 3); afterAll(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, }); - await fs.promises.rm(rootDataDir, { - force: true, - recursive: true, - }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); }); - test( - 'fail when pinging an offline node', - async () => { - const commands = genCommands([ - 'ping', - nodesUtils.encodeNodeId(remoteOfflineNodeId), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(1); // Should fail with no response. for automation purposes. - expect(result.stdout).toContain('No response received'); - - // Checking for json output - const commands2 = genCommands([ + test('fails when pinging an offline node', async () => { + const { exitCode, stdout, stderr } = await testBinUtils.pkStdio( + [ + 'nodes', 'ping', nodesUtils.encodeNodeId(remoteOfflineNodeId), '--format', 'json', - ]); - const result2 = await testBinUtils.pkStdio(commands2, {}, dataDir); - expect(result2.exitCode).toBe(1); // Should fail with no response. for automation purposes. - expect(result2.stdout).toContain('No response received'); - }, - global.failedConnectionTimeout * 2, - ); - test( - 'fail if node cannot be found', - async () => { - const fakeNodeId = nodesUtils.decodeNodeId( - 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', - )!; - const commands = genCommands([ - 'ping', - nodesUtils.encodeNodeId(fakeNodeId), - ]); - const result = await testBinUtils.pkStdio(commands); - expect(result.exitCode).not.toBe(0); // Should fail if node doesn't exist. - expect(result.stdout).toContain('Failed to resolve node ID'); - - // Json format. - const commands2 = genCommands([ + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(1); // Should fail with no response. for automation purposes. + expect(stderr).toContain('No response received'); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: 'No response received', + }); + }); + test('fails if node cannot be found', async () => { + const fakeNodeId = nodesUtils.decodeNodeId( + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', + ); + const { exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'nodes', 'ping', - nodesUtils.encodeNodeId(fakeNodeId), + nodesUtils.encodeNodeId(fakeNodeId!), '--format', 'json', - ]); - const result2 = await testBinUtils.pkStdio(commands2, {}, dataDir); - expect(result2.exitCode).not.toBe(0); // Should fail if node doesn't exist. - expect(result2.stdout).toContain('success'); - expect(result2.stdout).toContain('false'); - expect(result2.stdout).toContain('message'); - expect(result2.stdout).toContain('Failed to resolve node ID'); - }, - global.failedConnectionTimeout * 2, - ); + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).not.toBe(0); // Should fail if node doesn't exist. + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to resolve node ID ${nodesUtils.encodeNodeId( + fakeNodeId!, + )} to an address.`, + }); + }); test('succeed when pinging a live node', async () => { - const commands = genCommands([ - 'ping', - nodesUtils.encodeNodeId(remoteOnlineNodeId), - ]); - const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('Node is Active.'); - - // Checking for Json output. - const commands2 = genCommands([ - 'ping', - nodesUtils.encodeNodeId(remoteOnlineNodeId), - '--format', - 'json', - ]); - const result2 = await testBinUtils.pkStdio(commands2, {}, dataDir); - expect(result2.exitCode).toBe(0); - expect(result2.stdout).toContain('success'); - expect(result2.stdout).toContain('true'); - expect(result2.stdout).toContain('message'); - expect(result2.stdout).toContain('Node is Active'); + const { exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'nodes', + 'ping', + nodesUtils.encodeNodeId(remoteOnlineNodeId), + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + expect(JSON.parse(stdout)).toEqual({ + success: true, + message: 'Node is Active.', + }); }); }); diff --git a/tests/bin/notifications/notifications.test.ts b/tests/bin/notifications/notifications.test.ts deleted file mode 100644 index caea650e7..000000000 --- a/tests/bin/notifications/notifications.test.ts +++ /dev/null @@ -1,425 +0,0 @@ -import type { NodeId, NodeAddress } from '@/nodes/types'; -import type { NotificationData } from '@/notifications/types'; -import type { VaultName } from '@/vaults/types'; -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { utils as idUtils } from '@matrixai/id'; -import PolykeyAgent from '@/PolykeyAgent'; -import { makeVaultId } from '@/vaults/utils'; -import { utils as nodesUtils } from '@/nodes'; -import * as keysUtils from '@/keys/utils'; -import * as testBinUtils from '../utils'; - -describe('CLI Notifications', () => { - const password = 'password'; - const logger = new Logger('pkStdio Test', LogLevel.WARN, [ - new StreamHandler(), - ]); - let senderDataDir: string, receiverDataDir: string; - let senderNodePath: string, receiverNodePath: string; - let senderPasswordFile: string, receiverPasswordFile: string; - let senderPolykeyAgent: PolykeyAgent, receiverPolykeyAgent: PolykeyAgent; - let senderNodeId: NodeId; - let receiverNodeId: NodeId; - - // Helper functions - function genCommandsSender(options: Array) { - return ['notifications', ...options, '-np', senderNodePath]; - } - - function genCommandsReceiver(options: Array) { - return ['notifications', ...options, '-np', receiverNodePath]; - } - - const mockedGenerateDeterministicKeyPair = jest.spyOn( - keysUtils, - 'generateDeterministicKeyPair', - ); - - beforeAll(async () => { - mockedGenerateDeterministicKeyPair.mockImplementation((bits, _) => { - return keysUtils.generateKeyPair(bits); - }); - - senderDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - receiverDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - senderNodePath = path.join(senderDataDir, 'sender'); - receiverNodePath = path.join(receiverDataDir, 'receiver'); - senderPasswordFile = path.join(senderDataDir, 'passwordFile'); - receiverPasswordFile = path.join(senderDataDir, 'passwordFile'); - await fs.promises.writeFile(senderPasswordFile, 'password'); - await fs.promises.writeFile(receiverPasswordFile, 'password'); - senderPolykeyAgent = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath: senderNodePath, - logger: logger, - }); - receiverPolykeyAgent = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath: receiverNodePath, - logger: logger, - }); - senderNodeId = senderPolykeyAgent.keyManager.getNodeId(); - receiverNodeId = receiverPolykeyAgent.keyManager.getNodeId(); - await senderPolykeyAgent.nodeGraph.setNode(receiverNodeId, { - host: receiverPolykeyAgent.revProxy.getIngressHost(), - port: receiverPolykeyAgent.revProxy.getIngressPort(), - } as NodeAddress); - - // Authorize session - await testBinUtils.pkStdio([ - 'agent', - 'unlock', - '-np', - senderNodePath, - '--password-file', - senderPasswordFile, - ]); - await testBinUtils.pkStdio([ - 'agent', - 'unlock', - '-np', - receiverNodePath, - '--password-file', - receiverPasswordFile, - ]); - await receiverPolykeyAgent.notificationsManager.clearNotifications(); - }, global.polykeyStartupTimeout * 2); - afterAll(async () => { - await senderPolykeyAgent.stop(); - await senderPolykeyAgent.destroy(); - await receiverPolykeyAgent.stop(); - await receiverPolykeyAgent.destroy(); - await fs.promises.rmdir(senderDataDir, { recursive: true }); - await fs.promises.rmdir(receiverDataDir, { recursive: true }); - }); - afterEach(async () => { - await receiverPolykeyAgent.notificationsManager.clearNotifications(); - }); - - describe('commandSendNotification', () => { - test('Should send notification with permission.', async () => { - await receiverPolykeyAgent.acl.setNodePerm(senderNodeId, { - gestalt: { - notify: null, - }, - vaults: {}, - }); - const commands = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg', - ]); - const result = await testBinUtils.pkStdio(commands, {}, senderDataDir); - expect(result.exitCode).toBe(0); // Succeeds - const notifications = - await receiverPolykeyAgent.notificationsManager.readNotifications(); - expect(notifications[0].data).toEqual({ - type: 'General', - message: 'msg', - }); - }); - test('Should send notification without permission.', async () => { - await receiverPolykeyAgent.acl.setNodePerm(senderNodeId, { - gestalt: {}, - vaults: {}, - }); - const commands = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg', - ]); - const result = await testBinUtils.pkStdio(commands, {}, senderDataDir); - expect(result.exitCode).toBe(0); // Succeeds - const notifications = - await receiverPolykeyAgent.notificationsManager.readNotifications(); - expect(notifications).toEqual([]); // Notification should be sent but not received - }); - }); - describe('commandReadNotifications', () => { - test('Should read all notifications.', async () => { - await receiverPolykeyAgent.acl.setNodePerm(senderNodeId, { - gestalt: { - notify: null, - }, - vaults: {}, - }); - const senderCommands1 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg1', - ]); - const senderCommands2 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg2', - ]); - const senderCommands3 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg3', - ]); - await testBinUtils.pkStdio(senderCommands1, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands2, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands3, {}, senderDataDir); - const receiverCommands = genCommandsReceiver(['read']); - const result1 = await testBinUtils.pkStdio( - receiverCommands, - {}, - receiverDataDir, - ); - expect(result1.exitCode).toBe(0); - expect(result1.stdout).toContain('msg1'); - expect(result1.stdout).toContain('msg2'); - expect(result1.stdout).toContain('msg3'); - const senderCommands4 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg4', - ]); - await testBinUtils.pkStdio(senderCommands4, {}, senderDataDir); - const result2 = await testBinUtils.pkStdio( - receiverCommands, - {}, - receiverDataDir, - ); - expect(result2.exitCode).toBe(0); - expect(result2.stdout).toContain('msg1'); - expect(result2.stdout).toContain('msg2'); - expect(result2.stdout).toContain('msg3'); - expect(result2.stdout).toContain('msg4'); - }); - test('Should read all unread notifications.', async () => { - await receiverPolykeyAgent.acl.setNodePerm(senderNodeId, { - gestalt: { - notify: null, - }, - vaults: {}, - }); - const senderCommands1 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg1', - ]); - const senderCommands2 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg2', - ]); - const senderCommands3 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg3', - ]); - await testBinUtils.pkStdio(senderCommands1, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands2, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands3, {}, senderDataDir); - const receiverCommands1 = genCommandsReceiver(['read']); - await testBinUtils.pkStdio(receiverCommands1, {}, receiverDataDir); - const senderCommands4 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg4', - ]); - await testBinUtils.pkStdio(senderCommands4, {}, senderDataDir); - const receiverCommands2 = genCommandsReceiver(['read', '--unread']); - const result = await testBinUtils.pkStdio( - receiverCommands2, - {}, - receiverDataDir, - ); - expect(result.exitCode).toBe(0); - expect(result.stdout).not.toContain('msg1'); // Previously read notifications should be ignored - expect(result.stdout).not.toContain('msg2'); - expect(result.stdout).not.toContain('msg3'); - expect(result.stdout).toContain('msg4'); - }); - test('Should read a fixed number of notifications.', async () => { - await receiverPolykeyAgent.acl.setNodePerm(senderNodeId, { - gestalt: { - notify: null, - }, - vaults: {}, - }); - const senderCommands1 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg1', - ]); - const senderCommands2 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg2', - ]); - const senderCommands3 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg3', - ]); - await testBinUtils.pkStdio(senderCommands1, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands2, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands3, {}, senderDataDir); - const receiverCommands = genCommandsReceiver(['read', '--number', '2']); - const result = await testBinUtils.pkStdio( - receiverCommands, - {}, - receiverDataDir, - ); - expect(result.exitCode).toBe(0); - expect(result.stdout).not.toContain('msg1'); // Oldest notification not included - expect(result.stdout).toContain('msg2'); - expect(result.stdout).toContain('msg3'); - }); - test('Should read notifications in reverse order.', async () => { - await receiverPolykeyAgent.acl.setNodePerm(senderNodeId, { - gestalt: { - notify: null, - }, - vaults: {}, - }); - const senderCommands1 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg1', - ]); - const senderCommands2 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg2', - ]); - const senderCommands3 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg3', - ]); - await testBinUtils.pkStdio(senderCommands1, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands2, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands3, {}, senderDataDir); - const receiverCommands = genCommandsReceiver([ - 'read', - '--unread', - '--number', - '1', - '--order', - 'oldest', - ]); - const result1 = await testBinUtils.pkStdio( - receiverCommands, - {}, - receiverDataDir, - ); - const result2 = await testBinUtils.pkStdio( - receiverCommands, - {}, - receiverDataDir, - ); - const result3 = await testBinUtils.pkStdio( - receiverCommands, - {}, - receiverDataDir, - ); - expect(result1.exitCode).toBe(0); - expect(result2.exitCode).toBe(0); - expect(result3.exitCode).toBe(0); - expect(result1.stdout).toContain('msg1'); - expect(result2.stdout).toContain('msg2'); - expect(result3.stdout).toContain('msg3'); - }); - test('Should read no notifications.', async () => { - const receiverCommands = genCommandsReceiver(['read']); - const result = await testBinUtils.pkStdio( - receiverCommands, - {}, - receiverDataDir, - ); - expect(result.exitCode).toBe(0); - expect(result.stdout).toEqual('No notifications to display\n'); - }); - test('Should read all types of notifications.', async () => { - await receiverPolykeyAgent.acl.setNodePerm(senderNodeId, { - gestalt: { - notify: null, - }, - vaults: {}, - }); - const notificationData1: NotificationData = { - type: 'General', - message: 'msg', - }; - const notificationData2: NotificationData = { - type: 'GestaltInvite', - }; - const notificationData3: NotificationData = { - type: 'VaultShare', - vaultId: makeVaultId(idUtils.fromString('vaultIdxxxxxxxxx')).toString(), - vaultName: 'vaultName' as VaultName, - actions: { - clone: null, - pull: null, - }, - }; - await senderPolykeyAgent.notificationsManager.sendNotification( - receiverNodeId, - notificationData1, - ); - await senderPolykeyAgent.notificationsManager.sendNotification( - receiverNodeId, - notificationData2, - ); - await senderPolykeyAgent.notificationsManager.sendNotification( - receiverNodeId, - notificationData3, - ); - const commands = genCommandsReceiver(['read']); - const result = await testBinUtils.pkStdio(commands, {}, receiverDataDir); - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain('Message from Keynode'); - expect(result.stdout).toContain('invited you to join their Gestalt'); - expect(result.stdout).toContain('shared their vault'); - }); - }); - describe('commandClearNotifications', () => { - test('Should remove all notifications.', async () => { - await receiverPolykeyAgent.acl.setNodePerm(senderNodeId, { - gestalt: { - notify: null, - }, - vaults: {}, - }); - const senderCommands1 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg1', - ]); - const senderCommands2 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg2', - ]); - const senderCommands3 = genCommandsSender([ - 'send', - nodesUtils.encodeNodeId(receiverNodeId), - 'msg3', - ]); - await testBinUtils.pkStdio(senderCommands1, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands2, {}, senderDataDir); - await testBinUtils.pkStdio(senderCommands3, {}, senderDataDir); - const receiverCommandsClear = genCommandsReceiver(['clear']); - const receiverCommandsRead = genCommandsReceiver(['read']); - await testBinUtils.pkStdio(receiverCommandsClear); - const result = await testBinUtils.pkStdio( - receiverCommandsRead, - {}, - receiverDataDir, - ); - expect(result.exitCode).toBe(0); - expect(result.stdout).toEqual('No notifications to display\n'); // Should be no notifications left - }); - }); -}); diff --git a/tests/bin/notifications/sendReadClear.test.ts b/tests/bin/notifications/sendReadClear.test.ts new file mode 100644 index 000000000..f83863e5a --- /dev/null +++ b/tests/bin/notifications/sendReadClear.test.ts @@ -0,0 +1,305 @@ +import type { NodeId } from '@/nodes/types'; +import type { Host, Port } from '@/network/types'; +import type { Notification } from '@/notifications/types'; +import os from 'os'; +import path from 'path'; +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'; +import * as testBinUtils from '../utils'; +import * as testUtils from '../../utils'; + +describe('send/read/claim', () => { + const logger = new Logger('send/read/clear test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + let dataDir: string; + let nodePathSender: string; + let nodePathReceiver: string; + let sender: PolykeyAgent; + let senderId: NodeId; + let senderHost: Host; + let senderPort: Port; + let receiver: PolykeyAgent; + let receiverId: NodeId; + let receiverHost: Host; + let receiverPort: Port; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + const otherKeyPair = await keysUtils.generateKeyPair(1024); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair) + .mockResolvedValue(otherKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair) + .mockResolvedValue(otherKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePathSender = path.join(dataDir, 'sender'); + nodePathReceiver = path.join(dataDir, 'receiver'); + // Cannot use the shared global agent since we can't 'un-add' a node + // which we need in order to trust it and send notifications to it + sender = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath: nodePathSender, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + senderId = sender.keyManager.getNodeId(); + senderHost = sender.revProxy.getIngressHost(); + senderPort = sender.revProxy.getIngressPort(); + receiver = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath: nodePathReceiver, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, + logger, + }); + receiverId = receiver.keyManager.getNodeId(); + receiverHost = receiver.revProxy.getIngressHost(); + receiverPort = receiver.revProxy.getIngressPort(); + }); + afterAll(async () => { + await receiver.stop(); + await sender.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + test('sends, receives, and clears notifications', async () => { + let exitCode, stdout; + let readNotifications: Array; + // Add receiver to sender's node graph so it can be contacted + ({ exitCode } = await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(receiverId), + receiverHost, + receiverPort.toString(), + ], + { + PK_NODE_PATH: nodePathSender, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Add sender to receiver's node graph so it can be trusted + ({ exitCode } = await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(senderId), + senderHost, + senderPort.toString(), + ], + { + PK_NODE_PATH: nodePathReceiver, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Trust sender so notification can be received + ({ exitCode } = await testBinUtils.pkStdio( + ['identities', 'trust', nodesUtils.encodeNodeId(senderId)], + { + PK_NODE_PATH: nodePathReceiver, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Send some notifications + ({ exitCode } = await testBinUtils.pkStdio( + [ + 'notifications', + 'send', + nodesUtils.encodeNodeId(receiverId), + 'test message 1', + ], + { + PK_NODE_PATH: nodePathSender, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + ({ exitCode } = await testBinUtils.pkStdio( + [ + 'notifications', + 'send', + nodesUtils.encodeNodeId(receiverId), + 'test message 2', + ], + { + PK_NODE_PATH: nodePathSender, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + ({ exitCode } = await testBinUtils.pkStdio( + [ + 'notifications', + 'send', + nodesUtils.encodeNodeId(receiverId), + 'test message 3', + ], + { + PK_NODE_PATH: nodePathSender, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + // Read notifications + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['notifications', 'read', '--format', 'json'], + { + PK_NODE_PATH: nodePathReceiver, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + readNotifications = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(readNotifications).toHaveLength(3); + expect(readNotifications[0]).toMatchObject({ + data: { + type: 'General', + message: 'test message 3', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: true, + }); + expect(readNotifications[1]).toMatchObject({ + data: { + type: 'General', + message: 'test message 2', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: true, + }); + expect(readNotifications[2]).toMatchObject({ + data: { + type: 'General', + message: 'test message 1', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: true, + }); + // Read only unread (none) + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['notifications', 'read', '--unread', '--format', 'json'], + { + PK_NODE_PATH: nodePathReceiver, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + readNotifications = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(readNotifications).toHaveLength(0); + // Read notifications on reverse order + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['notifications', 'read', '--order=oldest', '--format', 'json'], + { + PK_NODE_PATH: nodePathReceiver, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + readNotifications = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(readNotifications).toHaveLength(3); + expect(readNotifications[0]).toMatchObject({ + data: { + type: 'General', + message: 'test message 1', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: true, + }); + expect(readNotifications[1]).toMatchObject({ + data: { + type: 'General', + message: 'test message 2', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: true, + }); + expect(readNotifications[2]).toMatchObject({ + data: { + type: 'General', + message: 'test message 3', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: true, + }); + // Read only one notification + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['notifications', 'read', '--number=1', '--format', 'json'], + { + PK_NODE_PATH: nodePathReceiver, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + readNotifications = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(readNotifications).toHaveLength(1); + expect(readNotifications[0]).toMatchObject({ + data: { + type: 'General', + message: 'test message 3', + }, + senderId: nodesUtils.encodeNodeId(senderId), + isRead: true, + }); + // Clear notifications + ({ exitCode } = await testBinUtils.pkStdio( + ['notifications', 'clear'], + { + PK_NODE_PATH: nodePathReceiver, + PK_PASSWORD: password, + }, + dataDir, + )); + // Check there are no more notifications + ({ exitCode, stdout } = await testBinUtils.pkStdio( + ['notifications', 'read', '--format', 'json'], + { + PK_NODE_PATH: nodePathReceiver, + PK_PASSWORD: password, + }, + dataDir, + )); + expect(exitCode).toBe(0); + readNotifications = stdout.split('\n').slice(undefined, -1).map(JSON.parse); + expect(readNotifications).toHaveLength(0); + }); +}); diff --git a/tests/bin/utils.test.ts b/tests/bin/utils.test.ts index 1b00ad9f0..9bfcbf9d1 100644 --- a/tests/bin/utils.test.ts +++ b/tests/bin/utils.test.ts @@ -15,7 +15,7 @@ describe('bin/utils', () => { type: 'json', data: ['Testing', 'the', 'list', 'output'], }), - ).toBe('["Testing","the","list","output"]'); + ).toBe('["Testing","the","list","output"]\n'); }); test('table in human and in json format', () => { // Table @@ -38,7 +38,7 @@ describe('bin/utils', () => { ], }), ).toBe( - '[{"key1":"value1","key2":"value2"},{"key1":"data1","key2":"data2"}]', + '[{"key1":"value1","key2":"value2"},{"key1":"data1","key2":"data2"}]\n', ); }); test('dict in human and in json format', () => { @@ -61,6 +61,6 @@ describe('bin/utils', () => { type: 'json', data: { key1: 'value1', key2: 'value2' }, }), - ).toBe('{"key1":"value1","key2":"value2"}'); + ).toBe('{"key1":"value1","key2":"value2"}\n'); }); }); diff --git a/tests/client/GRPCClientClient.test.ts b/tests/client/GRPCClientClient.test.ts index 3d8f7cc52..a6ce3f3bb 100644 --- a/tests/client/GRPCClientClient.test.ts +++ b/tests/client/GRPCClientClient.test.ts @@ -1,20 +1,16 @@ -import type * as grpc from '@grpc/grpc-js'; import type { Host, Port } from '@/network/types'; import type { NodeId } from '@/nodes/types'; +import type * as grpc from '@grpc/grpc-js'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { GRPCClientClient } from '@/client'; -import { PolykeyAgent } from '@'; -import { utils as keysUtils } from '@/keys'; -import { Status } from '@/status'; -import { Session } from '@/sessions'; -import { errors as clientErrors } from '@/client'; -import config from '@/config'; -import * as binProcessors from '@/bin/utils/processors'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import PolykeyAgent from '@/PolykeyAgent'; +import Session from '@/sessions/Session'; +import * as keysUtils from '@/keys/utils'; +import * as clientErrors from '@/client/errors'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import { utils as nodesUtils } from '@/nodes'; import * as testClientUtils from './utils'; import * as testUtils from '../utils'; @@ -88,47 +84,4 @@ describe(GRPCClientClient.name, () => { await client.agentStatus(new utilsPB.EmptyMessage()); }).rejects.toThrow(clientErrors.ErrorClientClientDestroyed); }); - test('can get status', async () => { - client = await GRPCClientClient.createGRPCClientClient({ - nodeId: nodeId, - host: '127.0.0.1' as Host, - port: port as Port, - tlsConfig: { keyPrivatePem: undefined, certChainPem: undefined }, - logger: logger, - timeout: 10000, - session: session, - }); - await fs.promises.writeFile(path.join(dataDir, 'password'), password); - const meta = await binProcessors.processAuthentication( - path.join(dataDir, 'password'), - fs, - ); - const status = new Status({ - statusPath: path.join(nodePath, config.defaults.statusBase), - statusLockPath: path.join(nodePath, config.defaults.statusLockBase), - fs, - logger, - }); - const statusInfo = (await status.readStatus())!; - const emptyMessage = new utilsPB.EmptyMessage(); - const response = await client.agentStatus(emptyMessage, meta); - expect(typeof response.getPid()).toBe('number'); - expect(response.getNodeId()).toBe( - nodesUtils.encodeNodeId(statusInfo.data.nodeId), - ); - expect(response.getClientHost()).toBe(statusInfo.data.clientHost); - expect(response.getClientPort()).toBe(statusInfo.data.clientPort); - expect(response.getIngressHost()).toBe(statusInfo.data.ingressHost); - expect(response.getIngressPort()).toBe(statusInfo.data.ingressPort); - expect(typeof response.getEgressHost()).toBe('string'); - expect(typeof response.getEgressPort()).toBe('number'); - expect(typeof response.getAgentHost()).toBe('string'); - expect(typeof response.getAgentPort()).toBe('number'); - expect(typeof response.getProxyHost()).toBe('string'); - expect(typeof response.getProxyPort()).toBe('number'); - expect(typeof response.getRootPublicKeyPem()).toBe('string'); - expect(typeof response.getRootCertPem()).toBe('string'); - expect(typeof response.getRootCertChainPem()).toBe('string'); - await client.destroy(); - }); }); diff --git a/tests/client/rpcGestalts.test.ts b/tests/client/rpcGestalts.test.ts deleted file mode 100644 index bd9fde312..000000000 --- a/tests/client/rpcGestalts.test.ts +++ /dev/null @@ -1,501 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { IdentitiesManager } from '@/identities'; -import type { GestaltGraph } from '@/gestalts'; -import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; -import type { NodeIdEncoded, NodeInfo } from '@/nodes/types'; -import type { Discovery } from '@/discovery'; -import type { ClientServiceClient } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import type * as gestaltsPB from '@/proto/js/polykey/v1/gestalts/gestalts_pb'; -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { PolykeyAgent } from '@'; -import { NodeManager } from '@/nodes'; -import { KeyManager } from '@/keys'; -import { ForwardProxy } from '@/network'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; -import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; -import * as permissionsPB from '@/proto/js/polykey/v1/permissions/permissions_pb'; -import * as grpcUtils from '@/grpc/utils'; -import * as gestaltsUtils from '@/gestalts/utils'; -import * as nodesUtils from '@/nodes/utils'; -import * as testUtils from './utils'; -import TestProvider from '../identities/TestProvider'; - -jest.mock('@/keys/utils', () => ({ - ...jest.requireActual('@/keys/utils'), - generateDeterministicKeyPair: - jest.requireActual('@/keys/utils').generateKeyPair, -})); - -describe('Client service', () => { - const password = 'password'; - const logger = new Logger('ClientServerTest', LogLevel.WARN, [ - new StreamHandler(), - ]); - let client: ClientServiceClient; - let server: grpc.Server; - let port: number; - let dataDir: string; - let pkAgent: PolykeyAgent; - let keyManager: KeyManager; - let gestaltGraph: GestaltGraph; - let identitiesManager: IdentitiesManager; - let discovery: Discovery; - let passwordFile: string; - let callCredentials: grpc.Metadata; - - const nodeId2Encoded = - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg' as NodeIdEncoded; - const nodeId2 = nodesUtils.decodeNodeId(nodeId2Encoded)!; - - const testToken = { - providerId: 'test-provider' as ProviderId, - identityId: 'test_user' as IdentityId, - tokenData: { - accessToken: 'abc123', - }, - }; - const node2: NodeInfo = { - id: nodeId2Encoded, - chain: {}, - }; - const identity1: IdentityInfo = { - providerId: 'github.com' as ProviderId, - identityId: 'IdentityIdABC' as IdentityId, - claims: {}, - }; - let node1: NodeInfo; - - async function createGestaltState() { - await gestaltGraph.setNode(node1); - await gestaltGraph.setNode(node2); - await gestaltGraph.setIdentity(identity1); - await gestaltGraph.linkNodeAndIdentity(node2, identity1); - } - - beforeAll(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - - passwordFile = path.join(dataDir, 'password'); - await fs.promises.writeFile(passwordFile, 'password'); - const keysPath = path.join(dataDir, 'keys'); - - keyManager = await KeyManager.createKeyManager({ - keysPath, - password, - logger, - }); - - const fwdProxy = new ForwardProxy({ - authToken: 'abc', - logger: logger, - }); - - pkAgent = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath: dataDir, - logger, - fwdProxy, - keyManager, - }); - - gestaltGraph = pkAgent.gestaltGraph; - identitiesManager = pkAgent.identitiesManager; - discovery = pkAgent.discovery; - - // Adding provider - const testProvider = new TestProvider(); - identitiesManager.registerProvider(testProvider); - - [server, port] = await testUtils.openTestClientServer({ - pkAgent, - secure: false, - }); - - client = await testUtils.openSimpleClientClient(port); - - node1 = { - id: nodesUtils.encodeNodeId(pkAgent.keyManager.getNodeId()), - chain: {}, - }; - }, global.polykeyStartupTimeout); - afterAll(async () => { - await testUtils.closeTestClientServer(server); - testUtils.closeSimpleClientClient(client); - - await pkAgent.stop(); - await pkAgent.destroy(); - - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - await fs.promises.rm(passwordFile); - }); - beforeEach(async () => { - const sessionToken = await pkAgent.sessionManager.createToken(); - callCredentials = testUtils.createCallCredentials(sessionToken); - }); - afterEach(async () => { - await gestaltGraph.clearDB(); - }); - - test('should get all gestalts', async () => { - const listGestalts = - grpcUtils.promisifyReadableStreamCall( - client, - client.gestaltsGestaltList, - ); - - await gestaltGraph.setNode(node2); - await gestaltGraph.setIdentity(identity1); - - const m = new utilsPB.EmptyMessage(); - - const res = listGestalts(m, callCredentials); - - const gestalts: Array = []; - for await (const val of res) { - gestalts.push(JSON.parse(val.getName())); - } - await gestaltGraph.getGestaltByIdentity( - identity1.providerId, - identity1.identityId, - ); - await gestaltGraph.getGestaltByNode(nodeId2); - const gestaltsString = JSON.stringify(gestalts); - expect(gestaltsString).toContain(identity1.providerId); - expect(gestaltsString).toContain(identity1.identityId); - expect(gestaltsString).toContain(nodesUtils.encodeNodeId(nodeId2)); - expect(gestalts).toHaveLength(2); - - await gestaltGraph.unsetNode(nodeId2); - await gestaltGraph.unsetIdentity( - identity1.providerId, - identity1.identityId, - ); - }); - test('should set independent node and identity gestalts', async () => { - await gestaltGraph.setNode(node2); - await gestaltGraph.setIdentity(identity1); - const gestaltNode = await gestaltGraph.getGestaltByNode(nodeId2); - const gestaltIdentity = await gestaltGraph.getGestaltByIdentity( - identity1.providerId, - identity1.identityId, - ); - const gkNode = gestaltsUtils.keyFromNode(nodeId2); - const gkIdentity = gestaltsUtils.keyFromIdentity( - identity1.providerId, - identity1.identityId, - ); - expect(gestaltNode).toStrictEqual({ - matrix: { [gkNode]: {} }, - nodes: { - [gkNode]: { - id: nodesUtils.encodeNodeId(nodeId2), - chain: {}, - }, - }, - identities: {}, - }); - expect(gestaltIdentity).toStrictEqual({ - matrix: { [gkIdentity]: {} }, - nodes: {}, - identities: { [gkIdentity]: identity1 }, - }); - }); - test('should get gestalt from Node.', async () => { - const gestaltsGetNode = grpcUtils.promisifyUnaryCall( - client, - client.gestaltsGestaltGetByNode, - ); - await createGestaltState(); - - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId2)); - - // Making the call - const res = await gestaltsGetNode(nodeMessage, callCredentials); - const jsonString = res.getGestaltGraph(); - - expect(jsonString).toContain('IdentityIdABC'); // Contains IdentityID - expect(jsonString).toContain('github.com'); // Contains github provider - expect(jsonString).toContain(nodesUtils.encodeNodeId(nodeId2)); // Contains NodeId - }); - test('should get gestalt from identity.', async () => { - const gestaltsGetIdentity = grpcUtils.promisifyUnaryCall( - client, - client.gestaltsGestaltGetByIdentity, - ); - await createGestaltState(); - // Testing the call - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(identity1.providerId); - providerMessage.setIdentityId(identity1.identityId); - const res = await gestaltsGetIdentity(providerMessage, callCredentials); - const jsonString = res.getGestaltGraph(); - - expect(jsonString).toContain('IdentityIdABC'); // Contains IdentityID - expect(jsonString).toContain('github.com'); // Contains github provider - expect(jsonString).toContain(nodesUtils.encodeNodeId(nodeId2)); // Contains NodeId - }); - test('should discover gestalt via Node.', async () => { - const mockedRequestChainData = jest - .spyOn(NodeManager.prototype, 'requestChainData') - .mockResolvedValue({}); - - const gestaltsDiscoverNode = - grpcUtils.promisifyUnaryCall( - client, - client.gestaltsDiscoveryByNode, - ); - - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId2)); - expect( - await gestaltsDiscoverNode(nodeMessage, callCredentials), - ).toBeInstanceOf(utilsPB.EmptyMessage); - - // Revert side-effects - await discovery.stop(); - await discovery.start({ fresh: true }); - mockedRequestChainData.mockRestore(); - }); - test('should discover gestalt via Identity.', async () => { - const mockedRequestChainData = jest - .spyOn(NodeManager.prototype, 'requestChainData') - .mockResolvedValue({}); - - const gestaltsDiscoverIdentity = - grpcUtils.promisifyUnaryCall( - client, - client.gestaltsDiscoveryByIdentity, - ); - - await identitiesManager.putToken( - testToken.providerId, - testToken.identityId, - testToken.tokenData, - ); - - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(testToken.providerId); - providerMessage.setIdentityId(testToken.identityId); - expect( - await gestaltsDiscoverIdentity(providerMessage, callCredentials), - ).toBeInstanceOf(utilsPB.EmptyMessage); - - // Revert side-effects - await discovery.stop(); - await discovery.start({ fresh: true }); - mockedRequestChainData.mockRestore(); - }); - test('should get gestalt permissions by node.', async () => { - const gestaltsGetActionsByNode = - grpcUtils.promisifyUnaryCall( - client, - client.gestaltsActionsGetByNode, - ); - await gestaltGraph.setNode(node1); - await gestaltGraph.setNode(node2); - await gestaltGraph.setGestaltActionByNode(nodeId2, 'scan'); - await gestaltGraph.setGestaltActionByNode(nodeId2, 'notify'); - - const nodeMessage = new nodesPB.Node(); - - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId2)); - // Should have permissions scan and notify as above - const test1 = await gestaltsGetActionsByNode(nodeMessage, callCredentials); - expect(test1.getActionList().length).toBe(2); - expect(test1.getActionList().includes('scan')).toBeTruthy(); - expect(test1.getActionList().includes('notify')).toBeTruthy(); - - nodeMessage.setNodeId( - nodesUtils.encodeNodeId(pkAgent.keyManager.getNodeId()), - ); - // Should have no permissions - const test2 = await gestaltsGetActionsByNode(nodeMessage, callCredentials); - expect(test2.getActionList().length).toBe(0); - }); - test('should get gestalt permissions by Identity.', async () => { - const gestaltsGetActionsByIdentity = - grpcUtils.promisifyUnaryCall( - client, - client.gestaltsActionsGetByIdentity, - ); - await gestaltGraph.setNode(node1); - await gestaltGraph.setNode(node2); - await gestaltGraph.setIdentity(identity1); - await gestaltGraph.linkNodeAndIdentity(node2, identity1); - await gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'scan', - ); - await gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'notify', - ); - - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(identity1.providerId); - providerMessage.setIdentityId(identity1.identityId); - // Should have permissions scan and notify as above - const test1 = await gestaltsGetActionsByIdentity( - providerMessage, - callCredentials, - ); - expect(test1.getActionList().length).toBe(2); - expect(test1.getActionList().includes('scan')).toBeTruthy(); - expect(test1.getActionList().includes('notify')).toBeTruthy(); - - providerMessage.setProviderId(identity1.providerId); - providerMessage.setIdentityId('Not a real identity'); - // Should have no permissions - const test2 = await gestaltsGetActionsByIdentity( - providerMessage, - callCredentials, - ); - expect(test2.getActionList().length).toBe(0); - }); - test('should set gestalt permissions by node.', async () => { - const gestaltsSetActionByNode = - grpcUtils.promisifyUnaryCall( - client, - client.gestaltsActionsSetByNode, - ); - await gestaltGraph.setNode(node1); - await gestaltGraph.setNode(node2); - - const setActionsMessage = new permissionsPB.ActionSet(); - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId2)); - setActionsMessage.setNode(nodeMessage); - setActionsMessage.setAction('scan'); - // Should have permissions scan and notify as above - await gestaltsSetActionByNode(setActionsMessage, callCredentials); - - const check1 = await gestaltGraph.getGestaltActionsByNode(nodeId2); - expect(Object.keys(check1!)).toContain('scan'); - - setActionsMessage.setAction('notify'); - await gestaltsSetActionByNode(setActionsMessage, callCredentials); - const check2 = await gestaltGraph.getGestaltActionsByNode(nodeId2); - expect(Object.keys(check2!)).toContain('notify'); - }); - test('should set gestalt permissions by Identity.', async () => { - const gestaltsSetActionByIdentity = - grpcUtils.promisifyUnaryCall( - client, - client.gestaltsActionsSetByIdentity, - ); - await gestaltGraph.setNode(node1); - await gestaltGraph.setNode(node2); - await gestaltGraph.setIdentity(identity1); - await gestaltGraph.linkNodeAndIdentity(node2, identity1); - - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(identity1.providerId); - providerMessage.setIdentityId(identity1.identityId); - - const setActionsMessage = new permissionsPB.ActionSet(); - setActionsMessage.setIdentity(providerMessage); - setActionsMessage.setAction('scan'); - // Should have permissions scan and notify as above - await gestaltsSetActionByIdentity(setActionsMessage, callCredentials); - - const check1 = await gestaltGraph.getGestaltActionsByIdentity( - identity1.providerId, - identity1.identityId, - ); - expect(Object.keys(check1!)).toContain('scan'); - - setActionsMessage.setAction('notify'); - await gestaltsSetActionByIdentity(setActionsMessage, callCredentials); - const check2 = await gestaltGraph.getGestaltActionsByIdentity( - identity1.providerId, - identity1.identityId, - ); - expect(Object.keys(check2!)).toContain('notify'); - }); - test('should unset gestalt permissions by node.', async () => { - const gestaltsUnsetActionByNode = - grpcUtils.promisifyUnaryCall( - client, - client.gestaltsActionsUnsetByNode, - ); - await gestaltGraph.setNode(node1); - await gestaltGraph.setNode(node2); - await gestaltGraph.setGestaltActionByNode(nodeId2, 'scan'); - await gestaltGraph.setGestaltActionByNode(nodeId2, 'notify'); - - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId2)); - - const setActionsMessage = new permissionsPB.ActionSet(); - setActionsMessage.setNode(nodeMessage); - setActionsMessage.setAction('scan'); - - // Should have permissions scan and notify as above - await gestaltsUnsetActionByNode(setActionsMessage, callCredentials); - const check1 = await gestaltGraph.getGestaltActionsByNode(nodeId2); - const keys = Object.keys(check1!); - expect(keys.length).toBe(1); - expect(keys).toContain('notify'); - expect(keys.includes('scan')).toBeFalsy(); - - setActionsMessage.setAction('notify'); - await gestaltsUnsetActionByNode(setActionsMessage, callCredentials); - const check2 = await gestaltGraph.getGestaltActionsByNode(nodeId2); - const keys2 = Object.keys(check2!); - expect(keys2.length).toBe(0); - }); - test('should unset gestalt permissions by Identity.', async () => { - const gestaltsUnsetActionByIdentity = - grpcUtils.promisifyUnaryCall( - client, - client.gestaltsActionsUnsetByIdentity, - ); - await gestaltGraph.setNode(node1); - await gestaltGraph.setNode(node2); - await gestaltGraph.setIdentity(identity1); - await gestaltGraph.linkNodeAndIdentity(node2, identity1); - await gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'scan', - ); - await gestaltGraph.setGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'notify', - ); - - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(identity1.providerId); - providerMessage.setIdentityId(identity1.identityId); - - const setActionsMessage = new permissionsPB.ActionSet(); - setActionsMessage.setIdentity(providerMessage); - setActionsMessage.setAction('scan'); - - // Should have permissions scan and notify as above - await gestaltsUnsetActionByIdentity(setActionsMessage, callCredentials); - const check1 = await gestaltGraph.getGestaltActionsByNode(nodeId2); - const keys = Object.keys(check1!); - expect(keys.length).toBe(1); - expect(keys).toContain('notify'); - expect(keys.includes('scan')).toBeFalsy(); - setActionsMessage.setAction('notify'); - await gestaltsUnsetActionByIdentity(setActionsMessage, callCredentials); - const check2 = await gestaltGraph.getGestaltActionsByNode(nodeId2); - const keys2 = Object.keys(check2!); - expect(keys2.length).toBe(0); - }); -}); diff --git a/tests/client/service/agentLockAll.test.ts b/tests/client/service/agentLockAll.test.ts index 3744adee4..21a533dd6 100644 --- a/tests/client/service/agentLockAll.test.ts +++ b/tests/client/service/agentLockAll.test.ts @@ -3,18 +3,17 @@ 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 { Metadata } from '@grpc/grpc-js'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { SessionManager } from '@/sessions'; -import { GRPCServer } from '@/grpc'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { DB } from '@matrixai/db'; +import SessionManager from '@/sessions/SessionManager'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import agentLockAll from '@/client/service/agentLockAll'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as keysUtils from '@/keys/utils'; +import * as clientUtils from '@/client/utils/utils'; import * as testUtils from '../../utils'; describe('agentLockall', () => { @@ -87,7 +86,7 @@ describe('agentLockall', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/agentStatus.test.ts b/tests/client/service/agentStatus.test.ts index 6ec94b667..6be7ae2c4 100644 --- a/tests/client/service/agentStatus.test.ts +++ b/tests/client/service/agentStatus.test.ts @@ -4,17 +4,17 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { GRPCServer } from '@/grpc'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import agentStatus from '@/client/service/agentStatus'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as agentPB from '@/proto/js/polykey/v1/agent/agent_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as keysUtils from '@/keys/utils'; +import * as clientUtils from '@/client/utils/utils'; import * as testUtils from '../../utils'; describe('agentStatus', () => { @@ -104,7 +104,7 @@ describe('agentStatus', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/agentStop.test.ts b/tests/client/service/agentStop.test.ts index 5fcad9d3b..6a5e00dd4 100644 --- a/tests/client/service/agentStop.test.ts +++ b/tests/client/service/agentStop.test.ts @@ -5,18 +5,16 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; import { running } from '@matrixai/async-init'; -import { PolykeyAgent } from '@'; -import { utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { Status } from '@/status'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import PolykeyAgent from '@/PolykeyAgent'; +import Status from '@/status/Status'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import agentStop from '@/client/service/agentStop'; import config from '@/config'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as keysUtils from '@/keys/utils'; +import * as clientUtils from '@/client/utils/utils'; import * as testUtils from '../../utils'; describe('agentStop', () => { @@ -72,7 +70,7 @@ describe('agentStop', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: pkAgent.keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/agentUnlock.test.ts b/tests/client/service/agentUnlock.test.ts index a2fd589db..4ca05aba0 100644 --- a/tests/client/service/agentUnlock.test.ts +++ b/tests/client/service/agentUnlock.test.ts @@ -1,15 +1,13 @@ import type { Host, Port } from '@/network/types'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { GRPCServer } from '@/grpc'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import agentUnlock from '@/client/service/agentUnlock'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import { utils as nodesUtils } from '@/nodes'; +import * as nodesUtils from '@/nodes/utils'; +import * as clientUtils from '@/client/utils/utils'; describe('agentUnlock', () => { const logger = new Logger('agentUnlock test', LogLevel.WARN, [ @@ -37,7 +35,7 @@ describe('agentUnlock', () => { 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', )!, host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts b/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts new file mode 100644 index 000000000..4ea77f742 --- /dev/null +++ b/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts @@ -0,0 +1,146 @@ +import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; +import type { NodeId, NodeInfo } from '@/nodes/types'; +import type { Host, Port } from '@/network/types'; +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 { IdInternal } from '@matrixai/id'; +import { Metadata } from '@grpc/grpc-js'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import gestaltsActionsSetByIdentity from '@/client/service/gestaltsActionsSetByIdentity'; +import gestaltsActionsGetByIdentity from '@/client/service/gestaltsActionsGetByIdentity'; +import gestaltsActionsUnsetByIdentity from '@/client/service/gestaltsActionsUnsetByIdentity'; +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 permissionsPB from '@/proto/js/polykey/v1/permissions/permissions_pb'; +import * as nodesUtils from '@/nodes/utils'; +import * as clientUtils from '@/client/utils/utils'; + +describe('gestaltsActionsByIdentity', () => { + const logger = new Logger('gestaltsActionsByIdentity test', LogLevel.WARN, [ + new StreamHandler(), + ]); + 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 identity: IdentityInfo = { + identityId: 'identityId' as IdentityId, + providerId: 'providerId' as ProviderId, + claims: {}, + }; + let dataDir: string; + let gestaltGraph: GestaltGraph; + let acl: ACL; + let db: DB; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + // Need identity set in GG with linked node to set permissions + await gestaltGraph.linkNodeAndIdentity(node, identity); + const clientService = { + gestaltsActionsSetByIdentity: gestaltsActionsSetByIdentity({ + authenticate, + gestaltGraph, + }), + gestaltsActionsGetByIdentity: gestaltsActionsGetByIdentity({ + authenticate, + gestaltGraph, + }), + gestaltsActionsUnsetByIdentity: gestaltsActionsUnsetByIdentity({ + authenticate, + gestaltGraph, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + 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, 8, + ]), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('sets/unsets/gets actions by identity', async () => { + // Set permission + const providerMessage = new identitiesPB.Provider(); + providerMessage.setIdentityId(identity.identityId); + providerMessage.setProviderId(identity.providerId); + const request = new permissionsPB.ActionSet(); + request.setIdentity(providerMessage); + request.setAction('notify'); + const setResponse = await grpcClient.gestaltsActionsSetByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(setResponse).toBeInstanceOf(utilsPB.EmptyMessage); + // Check for permission + const getSetResponse = await grpcClient.gestaltsActionsGetByIdentity( + providerMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(getSetResponse).toBeInstanceOf(permissionsPB.Actions); + expect(getSetResponse.getActionList()).toContainEqual('notify'); + // Unset permission + const unsetResponse = await grpcClient.gestaltsActionsUnsetByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(unsetResponse).toBeInstanceOf(utilsPB.EmptyMessage); + // Check permission was removed + const getUnsetResponse = await grpcClient.gestaltsActionsGetByIdentity( + providerMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(getUnsetResponse).toBeInstanceOf(permissionsPB.Actions); + expect(getUnsetResponse.getActionList()).toHaveLength(0); + }); +}); diff --git a/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts b/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts new file mode 100644 index 000000000..5eb2e40bf --- /dev/null +++ b/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts @@ -0,0 +1,139 @@ +import type { NodeId, NodeInfo } from '@/nodes/types'; +import type { Host, Port } from '@/network/types'; +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 { IdInternal } from '@matrixai/id'; +import { Metadata } from '@grpc/grpc-js'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import gestaltsActionsSetByNode from '@/client/service/gestaltsActionsSetByNode'; +import gestaltsActionsGetByNode from '@/client/service/gestaltsActionsGetByNode'; +import gestaltsActionsUnsetByNode from '@/client/service/gestaltsActionsUnsetByNode'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_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 permissionsPB from '@/proto/js/polykey/v1/permissions/permissions_pb'; +import * as nodesUtils from '@/nodes/utils'; +import * as clientUtils from '@/client/utils/utils'; + +describe('gestaltsActionsByNode', () => { + const logger = new Logger('gestaltsActionsByNode test', LogLevel.WARN, [ + new StreamHandler(), + ]); + 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: {}, + }; + let dataDir: string; + let gestaltGraph: GestaltGraph; + let acl: ACL; + let db: DB; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + // Need node to be set in GG to set permissions for it + await gestaltGraph.setNode(node); + const clientService = { + gestaltsActionsSetByNode: gestaltsActionsSetByNode({ + authenticate, + gestaltGraph, + }), + gestaltsActionsGetByNode: gestaltsActionsGetByNode({ + authenticate, + gestaltGraph, + }), + gestaltsActionsUnsetByNode: gestaltsActionsUnsetByNode({ + authenticate, + gestaltGraph, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + 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, 8, + ]), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('sets/unsets/gets actions by node', async () => { + // Set permission + const nodeMessage = new nodesPB.Node(); + nodeMessage.setNodeId(node.id); + const request = new permissionsPB.ActionSet(); + request.setNode(nodeMessage); + request.setAction('notify'); + const setResponse = await grpcClient.gestaltsActionsSetByNode( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(setResponse).toBeInstanceOf(utilsPB.EmptyMessage); + // Check for permission + const getSetResponse = await grpcClient.gestaltsActionsGetByNode( + nodeMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(getSetResponse).toBeInstanceOf(permissionsPB.Actions); + expect(getSetResponse.getActionList()).toContainEqual('notify'); + // Unset permission + const unsetResponse = await grpcClient.gestaltsActionsUnsetByNode( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(unsetResponse).toBeInstanceOf(utilsPB.EmptyMessage); + // Check permission was removed + const getUnsetResponse = await grpcClient.gestaltsActionsGetByNode( + nodeMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(getUnsetResponse).toBeInstanceOf(permissionsPB.Actions); + expect(getUnsetResponse.getActionList()).toHaveLength(0); + }); +}); diff --git a/tests/client/service/gestaltsDiscoveryByIdentity.test.ts b/tests/client/service/gestaltsDiscoveryByIdentity.test.ts new file mode 100644 index 000000000..b12e726e9 --- /dev/null +++ b/tests/client/service/gestaltsDiscoveryByIdentity.test.ts @@ -0,0 +1,217 @@ +import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; +import type { Host, Port } from '@/network/types'; +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 { Metadata } from '@grpc/grpc-js'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import KeyManager from '@/keys/KeyManager'; +import Discovery from '@/discovery/Discovery'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import gestaltsDiscoveryByIdentity from '@/client/service/gestaltsDiscoveryByIdentity'; +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 clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; + +describe('gestaltsDiscoveryByIdentity', () => { + const logger = new Logger('gestaltsDiscoveryByIdentity test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + const identity: IdentityInfo = { + identityId: 'identityId' as IdentityId, + providerId: 'providerId' as ProviderId, + claims: {}, + }; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + const authToken = 'abc123'; + let dataDir: string; + let discovery: Discovery; + let gestaltGraph: GestaltGraph; + let identitiesManager: IdentitiesManager; + let nodeGraph: NodeGraph; + let nodeConnectionManager: NodeConnectionManager; + let nodeManager: NodeManager; + let sigchain: Sigchain; + let fwdProxy: ForwardProxy; + let revProxy: ReverseProxy; + let acl: ACL; + let db: DB; + let keyManager: KeyManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + crypto: { + key: keyManager.dbKey, + ops: { + encrypt: keysUtils.encryptWithKey, + decrypt: keysUtils.decryptWithKey, + }, + }, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + logger, + }); + fwdProxy = new ForwardProxy({ + authToken, + logger, + }); + await fwdProxy.start({ + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + revProxy = new ReverseProxy({ logger }); + await revProxy.start({ + serverHost: '1.1.1.1' as Host, + serverPort: 1 as Port, + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyManager, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger: logger.getChild('NodeGraph'), + }); + nodeConnectionManager = new NodeConnectionManager({ + keyManager, + nodeGraph, + fwdProxy, + revProxy, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + await nodeConnectionManager.start(); + nodeManager = new NodeManager({ + db, + keyManager, + nodeConnectionManager, + nodeGraph, + sigchain, + logger, + }); + discovery = await Discovery.createDiscovery({ + db, + keyManager, + gestaltGraph, + identitiesManager, + nodeManager, + sigchain, + logger, + }); + const clientService = { + gestaltsDiscoveryByIdentity: gestaltsDiscoveryByIdentity({ + authenticate, + discovery, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: keyManager.getNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await discovery.stop(); + await nodeGraph.stop(); + await nodeConnectionManager.stop(); + await sigchain.stop(); + await revProxy.stop(); + await fwdProxy.stop(); + await identitiesManager.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('discovers by identity', async () => { + const mockDiscoveryByIdentity = jest + .spyOn(Discovery.prototype, 'queueDiscoveryByIdentity') + .mockResolvedValue(); + const request = new identitiesPB.Provider(); + request.setIdentityId(identity.identityId); + request.setProviderId(identity.providerId); + const response = await grpcClient.gestaltsDiscoveryByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + expect(mockDiscoveryByIdentity).toHaveBeenCalled(); + mockDiscoveryByIdentity.mockRestore(); + }); +}); diff --git a/tests/client/service/gestaltsDiscoveryByNode.test.ts b/tests/client/service/gestaltsDiscoveryByNode.test.ts new file mode 100644 index 000000000..97510dda2 --- /dev/null +++ b/tests/client/service/gestaltsDiscoveryByNode.test.ts @@ -0,0 +1,216 @@ +import type { NodeInfo } from '@/nodes/types'; +import type { Host, Port } from '@/network/types'; +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 { Metadata } from '@grpc/grpc-js'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import KeyManager from '@/keys/KeyManager'; +import Discovery from '@/discovery/Discovery'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import gestaltsDiscoveryByNode from '@/client/service/gestaltsDiscoveryByNode'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_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 clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as testUtils from '../../utils'; + +describe('gestaltsDiscoveryByNode', () => { + const logger = new Logger('gestaltsDiscoveryByNode test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + const node: NodeInfo = { + id: nodesUtils.encodeNodeId(testUtils.generateRandomNodeId()), + chain: {}, + }; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValue(globalKeyPair); + }); + afterAll(async () => { + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + }); + const authToken = 'abc123'; + let dataDir: string; + let discovery: Discovery; + let gestaltGraph: GestaltGraph; + let identitiesManager: IdentitiesManager; + let nodeGraph: NodeGraph; + let nodeConnectionManager: NodeConnectionManager; + let nodeManager: NodeManager; + let sigchain: Sigchain; + let fwdProxy: ForwardProxy; + let revProxy: ReverseProxy; + let acl: ACL; + let db: DB; + let keyManager: KeyManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + crypto: { + key: keyManager.dbKey, + ops: { + encrypt: keysUtils.encryptWithKey, + decrypt: keysUtils.decryptWithKey, + }, + }, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + logger, + }); + fwdProxy = new ForwardProxy({ + authToken, + logger, + }); + await fwdProxy.start({ + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + revProxy = new ReverseProxy({ logger }); + await revProxy.start({ + serverHost: '1.1.1.1' as Host, + serverPort: 1 as Port, + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyManager, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger: logger.getChild('NodeGraph'), + }); + nodeConnectionManager = new NodeConnectionManager({ + keyManager, + nodeGraph, + fwdProxy, + revProxy, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + await nodeConnectionManager.start(); + nodeManager = new NodeManager({ + db, + keyManager, + nodeConnectionManager, + nodeGraph, + sigchain, + logger, + }); + discovery = await Discovery.createDiscovery({ + db, + keyManager, + gestaltGraph, + identitiesManager, + nodeManager, + sigchain, + logger, + }); + const clientService = { + gestaltsDiscoveryByNode: gestaltsDiscoveryByNode({ + authenticate, + discovery, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: keyManager.getNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await discovery.stop(); + await nodeGraph.stop(); + await nodeConnectionManager.stop(); + await sigchain.stop(); + await revProxy.stop(); + await fwdProxy.stop(); + await identitiesManager.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('discovers by node', async () => { + const mockDiscoveryByNode = jest + .spyOn(Discovery.prototype, 'queueDiscoveryByNode') + .mockResolvedValue(); + const request = new nodesPB.Node(); + request.setNodeId(node.id); + const response = await grpcClient.gestaltsDiscoveryByNode( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + expect(mockDiscoveryByNode).toHaveBeenCalled(); + mockDiscoveryByNode.mockRestore(); + }); +}); diff --git a/tests/client/service/gestaltsGestaltGetByIdentity.test.ts b/tests/client/service/gestaltsGestaltGetByIdentity.test.ts new file mode 100644 index 000000000..a8aa0b5bf --- /dev/null +++ b/tests/client/service/gestaltsGestaltGetByIdentity.test.ts @@ -0,0 +1,132 @@ +import type { Gestalt } from '@/gestalts/types'; +import type { NodeId } from '@/nodes/types'; +import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; +import type { NodeInfo } from '@/nodes/types'; +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { IdInternal } from '@matrixai/id'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import gestaltsGestaltGetByIdentity from '@/client/service/gestaltsGestaltGetByIdentity'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +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'; + +describe('gestaltsGestaltGetByIdentity', () => { + const logger = new Logger( + 'gestaltsGestaltGetByIdentity test', + LogLevel.WARN, + [new StreamHandler()], + ); + 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 identity: IdentityInfo = { + 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 = { + matrix: {}, + nodes: {}, + identities: {}, + }; + expectedGestalt.matrix[identityKey] = {}; + expectedGestalt.matrix[nodeKey] = {}; + expectedGestalt.matrix[identityKey][nodeKey] = null; + expectedGestalt.matrix[nodeKey][identityKey] = null; + expectedGestalt.nodes[nodeKey] = node; + expectedGestalt.identities[identityKey] = identity; + let dataDir: string; + let gestaltGraph: GestaltGraph; + let acl: ACL; + let db: DB; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + await gestaltGraph.linkNodeAndIdentity(node, identity); + const clientService = { + gestaltsGestaltGetByIdentity: gestaltsGestaltGetByIdentity({ + authenticate, + gestaltGraph, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + 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, 8, + ]), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets gestalt by identity', async () => { + const request = new identitiesPB.Provider(); + request.setIdentityId(identity.identityId); + request.setProviderId(identity.providerId); + const response = await grpcClient.gestaltsGestaltGetByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(gestaltsPB.Graph); + expect(JSON.parse(response.getGestaltGraph())).toEqual(expectedGestalt); + }); +}); diff --git a/tests/client/service/gestaltsGestaltGetByNode.test.ts b/tests/client/service/gestaltsGestaltGetByNode.test.ts new file mode 100644 index 000000000..ebff5bf7d --- /dev/null +++ b/tests/client/service/gestaltsGestaltGetByNode.test.ts @@ -0,0 +1,128 @@ +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 fs from 'fs'; +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 { IdInternal } from '@matrixai/id'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import gestaltsGestaltGetByNode from '@/client/service/gestaltsGestaltGetByNode'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; +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'; + +describe('gestaltsGestaltGetByNode', () => { + const logger = new Logger('gestaltsGestaltGetByNode test', LogLevel.WARN, [ + new StreamHandler(), + ]); + 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 identity: IdentityInfo = { + 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 = { + matrix: {}, + nodes: {}, + identities: {}, + }; + expectedGestalt.matrix[identityKey] = {}; + expectedGestalt.matrix[nodeKey] = {}; + expectedGestalt.matrix[identityKey][nodeKey] = null; + expectedGestalt.matrix[nodeKey][identityKey] = null; + expectedGestalt.nodes[nodeKey] = node; + expectedGestalt.identities[identityKey] = identity; + let dataDir: string; + let gestaltGraph: GestaltGraph; + let acl: ACL; + let db: DB; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + await gestaltGraph.linkNodeAndIdentity(node, identity); + const clientService = { + gestaltsGestaltGetByNode: gestaltsGestaltGetByNode({ + authenticate, + gestaltGraph, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + 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, 8, + ]), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets gestalt by node', async () => { + const request = new nodesPB.Node(); + request.setNodeId(node.id); + const response = await grpcClient.gestaltsGestaltGetByNode( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(gestaltsPB.Graph); + expect(JSON.parse(response.getGestaltGraph())).toEqual(expectedGestalt); + }); +}); diff --git a/tests/client/service/gestaltsGestaltList.test.ts b/tests/client/service/gestaltsGestaltList.test.ts new file mode 100644 index 000000000..6b714e073 --- /dev/null +++ b/tests/client/service/gestaltsGestaltList.test.ts @@ -0,0 +1,139 @@ +import type { Gestalt } from '@/gestalts/types'; +import type { NodeId } from '@/nodes/types'; +import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; +import type { NodeInfo } from '@/nodes/types'; +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { IdInternal } from '@matrixai/id'; +import { DB } from '@matrixai/db'; +import { Metadata } from '@grpc/grpc-js'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import gestaltsGestaltList from '@/client/service/gestaltsGestaltList'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +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, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + // Creating gestalts (gestalt1 with one node and gestalt2 with one identity) + 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 identity: IdentityInfo = { + identityId: 'identityId' as IdentityId, + providerId: 'providerId' as ProviderId, + claims: {}, + }; + const nodeKey = gestaltUtils.keyFromNode(nodeId); + const identityKey = gestaltUtils.keyFromIdentity( + identity.providerId, + identity.identityId, + ); + const gestalt1: Gestalt = { + matrix: {}, + nodes: {}, + identities: {}, + }; + gestalt1.matrix[nodeKey] = {}; + gestalt1.nodes[nodeKey] = node; + const gestalt2: Gestalt = { + matrix: {}, + nodes: {}, + identities: {}, + }; + gestalt2.matrix[identityKey] = {}; + gestalt2.identities[identityKey] = identity; + let dataDir: string; + let gestaltGraph: GestaltGraph; + let acl: ACL; + let db: DB; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + await gestaltGraph.setNode(node); + await gestaltGraph.setIdentity(identity); + const clientService = { + gestaltsGestaltList: gestaltsGestaltList({ + authenticate, + gestaltGraph, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + 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, 8, + ]), + host: '127.0.0.1' as Host, + port: grpcServer.getPort(), + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('lists gestalts', async () => { + const request = new utilsPB.EmptyMessage(); + const response = grpcClient.gestaltsGestaltList( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const gestalts: Array = []; + for await (const gestalt of response) { + expect(gestalt).toBeInstanceOf(gestaltsPB.Gestalt); + gestalts.push(JSON.parse(gestalt.getName())); + } + expect(gestalts).toHaveLength(2); + expect(gestalts).toContainEqual(gestalt1); + expect(gestalts).toContainEqual(gestalt2); + }); +}); diff --git a/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts b/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts index b28a46770..6b87fc7c3 100644 --- a/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts +++ b/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts @@ -1,35 +1,39 @@ -import type { Host, Port } from '@/network/types'; import type { NodeIdEncoded } from '@/nodes/types'; -import type { IdentityId } from '@/identities/types'; import type { ClaimLinkIdentity } from '@/claims/types'; import type { ChainData } from '@/sigchain/types'; import type { Gestalt } from '@/gestalts/types'; +import type { IdentityId } from '@/identities/types'; +import type { Host, Port } from '@/network/types'; import fs from 'fs'; 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 { PolykeyAgent } from '@'; -import { KeyManager } from '@/keys'; -import { GestaltGraph } from '@/gestalts'; -import { ACL } from '@/acl'; -import { GRPCServer } from '@/grpc'; -import { Discovery } from '@/discovery'; -import { IdentitiesManager } from '@/identities'; -import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { GRPCClientClient, ClientServiceService } from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import PolykeyAgent from '@/PolykeyAgent'; +import KeyManager from '@/keys/KeyManager'; +import Discovery from '@/discovery/Discovery'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import gestaltsGestaltTrustByIdentity from '@/client/service/gestaltsGestaltTrustByIdentity'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import { poll } from '@/utils'; -import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as nodesUtils from '@/nodes/utils'; +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 clientUtils from '@/client/utils'; import * as keysUtils from '@/keys/utils'; +import * as clientUtils from '@/client/utils/utils'; +import * as nodesUtils from '@/nodes/utils'; import * as testUtils from '../../utils'; import TestProvider from '../../identities/TestProvider'; @@ -73,6 +77,13 @@ describe('gestaltsGestaltTrustByIdentity', () => { node = await PolykeyAgent.createPolykeyAgent({ password, nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, logger, }); nodeId = nodesUtils.encodeNodeId(node.keyManager.getNodeId()); @@ -237,7 +248,7 @@ describe('gestaltsGestaltTrustByIdentity', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/gestaltsGestaltTrustByNode.test.ts b/tests/client/service/gestaltsGestaltTrustByNode.test.ts index 0b37758be..aa3618bd0 100644 --- a/tests/client/service/gestaltsGestaltTrustByNode.test.ts +++ b/tests/client/service/gestaltsGestaltTrustByNode.test.ts @@ -1,34 +1,38 @@ -import type { Host, Port } from '@/network/types'; import type { NodeIdEncoded } from '@/nodes/types'; -import type { IdentityId } from '@/identities/types'; import type { ClaimLinkIdentity } from '@/claims/types'; import type { ChainData } from '@/sigchain/types'; import type { Gestalt } from '@/gestalts/types'; +import type { IdentityId } from '@/identities/types'; +import type { Host, Port } from '@/network/types'; import fs from 'fs'; 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 { PolykeyAgent } from '@'; -import { KeyManager } from '@/keys'; -import { GestaltGraph } from '@/gestalts'; -import { ACL } from '@/acl'; -import { GRPCServer } from '@/grpc'; -import { Discovery } from '@/discovery'; -import { IdentitiesManager } from '@/identities'; -import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { GRPCClientClient, ClientServiceService } from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import PolykeyAgent from '@/PolykeyAgent'; +import KeyManager from '@/keys/KeyManager'; +import Discovery from '@/discovery/Discovery'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +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 nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as nodesUtils from '@/nodes/utils'; +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'; +import * as clientUtils from '@/client/utils/utils'; +import * as nodesUtils from '@/nodes/utils'; import * as testUtils from '../../utils'; import TestProvider from '../../identities/TestProvider'; @@ -70,6 +74,13 @@ describe('gestaltsGestaltTrustByNode', () => { node = await PolykeyAgent.createPolykeyAgent({ password, nodePath, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, logger, }); nodeId = nodesUtils.encodeNodeId(node.keyManager.getNodeId()); @@ -234,7 +245,7 @@ describe('gestaltsGestaltTrustByNode', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/identitiesAuthenticate.test.ts b/tests/client/service/identitiesAuthenticate.test.ts index 0d45a6896..fb3c2a9ff 100644 --- a/tests/client/service/identitiesAuthenticate.test.ts +++ b/tests/client/service/identitiesAuthenticate.test.ts @@ -4,19 +4,17 @@ import fs from 'fs'; 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 { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import identitiesAuthenticate from '@/client/service/identitiesAuthenticate'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; -import { utils as nodesUtils } from '@/nodes'; import * as validationErrors from '@/validation/errors'; +import * as clientUtils from '@/client/utils/utils'; +import * as nodesUtils from '@/nodes/utils'; import TestProvider from '../../identities/TestProvider'; describe('identitiesAuthenticate', () => { @@ -71,7 +69,7 @@ describe('identitiesAuthenticate', () => { 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', )!, host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/identitiesAuthenticatedGet.test.ts b/tests/client/service/identitiesAuthenticatedGet.test.ts index 8e650ca13..8233589d5 100644 --- a/tests/client/service/identitiesAuthenticatedGet.test.ts +++ b/tests/client/service/identitiesAuthenticatedGet.test.ts @@ -4,15 +4,16 @@ import fs from 'fs'; 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 { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { GRPCClientClient, ClientServiceService } from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import identitiesAuthenticatedGet from '@/client/service/identitiesAuthenticatedGet'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; -import * as clientUtils from '@/client/utils'; import TestProvider from '../../identities/TestProvider'; describe('identitiesAuthenticatedGet', () => { @@ -60,7 +61,7 @@ describe('identitiesAuthenticatedGet', () => { 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', )!, host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/identitiesClaim.test.ts b/tests/client/service/identitiesClaim.test.ts index 8b6aa167e..f385e1cf4 100644 --- a/tests/client/service/identitiesClaim.test.ts +++ b/tests/client/service/identitiesClaim.test.ts @@ -1,30 +1,31 @@ -import type { Host, Port } from '@/network/types'; -import type { IdentityId, ProviderId } from '@/identities/types'; import type { ClaimLinkIdentity } from '@/claims/types'; import type { NodeIdEncoded } 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'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Metadata } from '@grpc/grpc-js'; import { DB } from '@matrixai/db'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { NodeConnectionManager, NodeGraph } from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import identitiesClaim from '@/client/service/identitiesClaim'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; import * as claimsUtils from '@/claims/utils'; import * as validationErrors from '@/validation/errors'; -import TestProvider from '../../identities/TestProvider'; import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; describe('identitiesClaim', () => { const logger = new Logger('identitiesClaim test', LogLevel.WARN, [ @@ -163,7 +164,7 @@ describe('identitiesClaim', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/identitiesInfoConnectedGet.test.ts b/tests/client/service/identitiesInfoConnectedGet.test.ts index bd7372a43..0dce4a1bb 100644 --- a/tests/client/service/identitiesInfoConnectedGet.test.ts +++ b/tests/client/service/identitiesInfoConnectedGet.test.ts @@ -4,16 +4,17 @@ import fs from 'fs'; 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 { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { GRPCClientClient, ClientServiceService } from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import identitiesInfoConnectedGet from '@/client/service/identitiesInfoConnectedGet'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; import * as identitiesErrors from '@/identities/errors'; -import * as clientUtils from '@/client/utils'; import TestProvider from '../../identities/TestProvider'; describe('identitiesInfoConnectedGet', () => { @@ -64,7 +65,7 @@ describe('identitiesInfoConnectedGet', () => { 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', )!, host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/identitiesInfoGet.test.ts b/tests/client/service/identitiesInfoGet.test.ts index a6eaaef7a..41bc2a1e0 100644 --- a/tests/client/service/identitiesInfoGet.test.ts +++ b/tests/client/service/identitiesInfoGet.test.ts @@ -4,15 +4,16 @@ import fs from 'fs'; 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 { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { GRPCClientClient, ClientServiceService } from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import identitiesInfoGet from '@/client/service/identitiesInfoGet'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '@/client/utils/utils'; import * as nodesUtils from '@/nodes/utils'; -import * as clientUtils from '@/client/utils'; import TestProvider from '../../identities/TestProvider'; describe('identitiesInfoGet', () => { @@ -63,7 +64,7 @@ describe('identitiesInfoGet', () => { 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', )!, host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/identitiesProvidersList.test.ts b/tests/client/service/identitiesProvidersList.test.ts index 5d6fc579c..071703386 100644 --- a/tests/client/service/identitiesProvidersList.test.ts +++ b/tests/client/service/identitiesProvidersList.test.ts @@ -1,22 +1,20 @@ -import type { Host, Port } from '@/network/types'; import type { ProviderId } from '@/identities/types'; +import type { Host, Port } from '@/network/types'; import fs from 'fs'; 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 { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import identitiesProvidersList from '@/client/service/identitiesProvidersList'; -import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import { utils as nodesUtils } from '@/nodes'; +import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as nodesUtils from '@/nodes/utils'; import TestProvider from '../../identities/TestProvider'; describe('identitiesProvidersList', () => { @@ -75,7 +73,7 @@ describe('identitiesProvidersList', () => { 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', )!, host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/identitiesTokenGet.test.ts b/tests/client/service/identitiesTokenGet.test.ts deleted file mode 100644 index 6d8a2873c..000000000 --- a/tests/client/service/identitiesTokenGet.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type { IdentityId, ProviderId } from '@/identities/types'; -import fs from 'fs'; -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 { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; -import identitiesTokenGet from '@/client/service/identitiesTokenGet'; -import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; -import { utils as nodesUtils } from '@/nodes'; -import TestProvider from '../../identities/TestProvider'; - -describe('identitiesTokenGet', () => { - const logger = new Logger('identitiesTokenGet test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const testToken = { - providerId: 'test-provider' as ProviderId, - identityId: 'test_user' as IdentityId, - tokenData: { - accessToken: 'abc123', - }, - }; - let dataDir: string; - let identitiesManager: IdentitiesManager; - let db: DB; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - logger, - }); - identitiesManager.registerProvider(new TestProvider()); - const clientService = { - identitiesTokenGet: identitiesTokenGet({ - authenticate, - identitiesManager, - }), - }; - grpcServer = new GRPCServer({ logger }); - await grpcServer.start({ - services: [[ClientServiceService, clientService]], - host: '127.0.0.1' as Host, - port: 0 as Port, - }); - grpcClient = await GRPCClientClient.createGRPCClientClient({ - nodeId: nodesUtils.decodeNodeId( - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - )!, - host: '127.0.0.1' as Host, - port: grpcServer.port, - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await identitiesManager.stop(); - await db.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('gets provider tokens', async () => { - // Needs an authenticated identity - await identitiesManager.putToken( - testToken.providerId, - testToken.identityId, - testToken.tokenData, - ); - const request = new identitiesPB.Provider(); - request.setProviderId(testToken.providerId); - request.setIdentityId(testToken.identityId); - const response = await grpcClient.identitiesTokenGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(identitiesPB.Token); - expect(JSON.parse(response.getToken())).toEqual(testToken.tokenData); - // Unauthenticate - await identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - }); -}); diff --git a/tests/client/service/identitiesTokenPut.test.ts b/tests/client/service/identitiesTokenPut.test.ts deleted file mode 100644 index d5e3a7192..000000000 --- a/tests/client/service/identitiesTokenPut.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type { IdentityId, ProviderId } from '@/identities/types'; -import fs from 'fs'; -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 { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; -import identitiesTokenPut from '@/client/service/identitiesTokenPut'; -import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import { utils as nodesUtils } from '@/nodes'; -import TestProvider from '../../identities/TestProvider'; - -describe('identitiesTokenPut', () => { - const logger = new Logger('identitiesTokenPut test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const testToken = { - providerId: 'test-provider' as ProviderId, - identityId: 'test_user' as IdentityId, - tokenData: { - accessToken: 'abc123', - }, - }; - let dataDir: string; - let identitiesManager: IdentitiesManager; - let db: DB; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - logger, - }); - identitiesManager.registerProvider(new TestProvider()); - const clientService = { - identitiesTokenPut: identitiesTokenPut({ - authenticate, - identitiesManager, - }), - }; - grpcServer = new GRPCServer({ logger }); - await grpcServer.start({ - services: [[ClientServiceService, clientService]], - host: '127.0.0.1' as Host, - port: 0 as Port, - }); - grpcClient = await GRPCClientClient.createGRPCClientClient({ - nodeId: nodesUtils.decodeNodeId( - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - )!, - host: '127.0.0.1' as Host, - port: grpcServer.port, - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await identitiesManager.stop(); - await db.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('authenticates providers', async () => { - const request = new identitiesPB.TokenSpecific(); - const provider = new identitiesPB.Provider(); - provider.setProviderId(testToken.providerId); - provider.setIdentityId(testToken.identityId); - request.setProvider(provider); - request.setToken(testToken.tokenData.accessToken); - const response = await grpcClient.identitiesTokenPut( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - expect(await identitiesManager.getTokens(testToken.providerId)).toEqual({ - test_user: testToken.tokenData, - }); - // Unauthenticate - await identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - }); -}); diff --git a/tests/client/service/identitiesTokenDelete.test.ts b/tests/client/service/identitiesTokenPutDeleteGet.test.ts similarity index 51% rename from tests/client/service/identitiesTokenDelete.test.ts rename to tests/client/service/identitiesTokenPutDeleteGet.test.ts index 91ac6da26..6fca93e68 100644 --- a/tests/client/service/identitiesTokenDelete.test.ts +++ b/tests/client/service/identitiesTokenPutDeleteGet.test.ts @@ -1,26 +1,26 @@ -import type { Host, Port } from '@/network/types'; import type { IdentityId, ProviderId } from '@/identities/types'; +import type { Host, Port } from '@/network/types'; import fs from 'fs'; 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 { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import identitiesTokenPut from '@/client/service/identitiesTokenPut'; +import identitiesTokenGet from '@/client/service/identitiesTokenGet'; import identitiesTokenDelete from '@/client/service/identitiesTokenDelete'; -import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import { utils as nodesUtils } from '@/nodes'; +import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as nodesUtils from '@/nodes/utils'; import TestProvider from '../../identities/TestProvider'; -describe('identitiesTokenDelete', () => { - const logger = new Logger('identitiesTokenDelete test', LogLevel.WARN, [ +describe('identitiesTokenPutDeleteGet', () => { + const logger = new Logger('identitiesTokenPutDeleteGet test', LogLevel.WARN, [ new StreamHandler(), ]); const password = 'helloworld'; @@ -53,6 +53,14 @@ describe('identitiesTokenDelete', () => { }); identitiesManager.registerProvider(new TestProvider()); const clientService = { + identitiesTokenPut: identitiesTokenPut({ + authenticate, + identitiesManager, + }), + identitiesTokenGet: identitiesTokenGet({ + authenticate, + identitiesManager, + }), identitiesTokenDelete: identitiesTokenDelete({ authenticate, identitiesManager, @@ -69,7 +77,7 @@ describe('identitiesTokenDelete', () => { 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', )!, host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); @@ -83,21 +91,38 @@ describe('identitiesTokenDelete', () => { recursive: true, }); }); - test('un-authenticates providers', async () => { - // Needs an authenticated identity - await identitiesManager.putToken( - testToken.providerId, - testToken.identityId, - testToken.tokenData, + test('puts/deletes/gets tokens', async () => { + // Put token + const putRequest = new identitiesPB.TokenSpecific(); + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(testToken.providerId); + providerMessage.setIdentityId(testToken.identityId); + putRequest.setProvider(providerMessage); + putRequest.setToken(testToken.tokenData.accessToken); + const putResponse = await grpcClient.identitiesTokenPut( + putRequest, + clientUtils.encodeAuthFromPassword(password), + ); + expect(putResponse).toBeInstanceOf(utilsPB.EmptyMessage); + // Get token + const getPutResponse = await grpcClient.identitiesTokenGet( + providerMessage, + clientUtils.encodeAuthFromPassword(password), + ); + expect(getPutResponse).toBeInstanceOf(identitiesPB.Token); + expect(JSON.parse(getPutResponse.getToken())).toEqual(testToken.tokenData); + // Delete token + const deleteResponse = await grpcClient.identitiesTokenDelete( + providerMessage, + clientUtils.encodeAuthFromPassword(password), ); - const request = new identitiesPB.Provider(); - request.setProviderId(testToken.providerId); - request.setIdentityId(testToken.identityId); - const response = await grpcClient.identitiesTokenDelete( - request, + expect(deleteResponse).toBeInstanceOf(utilsPB.EmptyMessage); + // Check token was deleted + const getDeleteResponse = await grpcClient.identitiesTokenGet( + providerMessage, clientUtils.encodeAuthFromPassword(password), ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - expect(await identitiesManager.getTokens(testToken.providerId)).toEqual({}); + expect(getDeleteResponse).toBeInstanceOf(identitiesPB.Token); + expect(getDeleteResponse.getToken()).toEqual(''); }); }); diff --git a/tests/client/service/keysCertsChainGet.test.ts b/tests/client/service/keysCertsChainGet.test.ts index 43dae2b0c..f0b97a681 100644 --- a/tests/client/service/keysCertsChainGet.test.ts +++ b/tests/client/service/keysCertsChainGet.test.ts @@ -4,16 +4,15 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { GRPCServer } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import keysCertsChainGet from '@/client/service/keysCertsChainGet'; -import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; 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'; import * as testUtils from '../../utils'; describe('keysCertsChainGet', () => { @@ -73,7 +72,7 @@ describe('keysCertsChainGet', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/keysCertsGet.test.ts b/tests/client/service/keysCertsGet.test.ts index 17159e501..ad2fb28ba 100644 --- a/tests/client/service/keysCertsGet.test.ts +++ b/tests/client/service/keysCertsGet.test.ts @@ -4,16 +4,15 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { GRPCServer } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import keysCertsGet from '@/client/service/keysCertsGet'; -import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; 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'; import * as testUtils from '../../utils'; describe('keysCertsGet', () => { @@ -72,7 +71,7 @@ describe('keysCertsGet', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/keysEncrypt.test.ts b/tests/client/service/keysEncrypt.test.ts deleted file mode 100644 index 47835e21f..000000000 --- a/tests/client/service/keysEncrypt.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Metadata } from '@grpc/grpc-js'; -import { GRPCServer } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; -import keysEncrypt from '@/client/service/keysEncrypt'; -import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; -import * as testUtils from '../../utils'; - -describe('keysEncrypt', () => { - const logger = new Logger('keysEncrypt test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedGenerateKeyPair: jest.SpyInstance; - let mockedGenerateDeterministicKeyPair: jest.SpyInstance; - beforeAll(async () => { - const globalKeyPair = await testUtils.setupGlobalKeypair(); - mockedGenerateKeyPair = jest - .spyOn(keysUtils, 'generateKeyPair') - .mockResolvedValue(globalKeyPair); - mockedGenerateDeterministicKeyPair = jest - .spyOn(keysUtils, 'generateDeterministicKeyPair') - .mockResolvedValue(globalKeyPair); - }); - afterAll(async () => { - mockedGenerateKeyPair.mockRestore(); - mockedGenerateDeterministicKeyPair.mockRestore(); - }); - let dataDir: string; - let keyManager: KeyManager; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const keysPath = path.join(dataDir, 'keys'); - keyManager = await KeyManager.createKeyManager({ - password, - keysPath, - logger, - }); - const clientService = { - keysEncrypt: keysEncrypt({ - authenticate, - keyManager, - }), - }; - grpcServer = new GRPCServer({ logger }); - await grpcServer.start({ - services: [[ClientServiceService, clientService]], - host: '127.0.0.1' as Host, - port: 0 as Port, - }); - grpcClient = await GRPCClientClient.createGRPCClientClient({ - nodeId: keyManager.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.port, - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await keyManager.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('encrypts data', async () => { - const plainText = Buffer.from('abc'); - const request = new keysPB.Crypto(); - request.setData(plainText.toString('binary')); - const response = await grpcClient.keysEncrypt( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(keysPB.Crypto); - const decrypted = await keyManager.decryptWithRootKeyPair( - Buffer.from(response.getData(), 'binary'), - ); - expect(decrypted.toString()).toBe(plainText.toString()); - }); -}); diff --git a/tests/client/service/keysDecrypt.test.ts b/tests/client/service/keysEncryptDecrypt.test.ts similarity index 73% rename from tests/client/service/keysDecrypt.test.ts rename to tests/client/service/keysEncryptDecrypt.test.ts index d0e62ed80..0c2a1259a 100644 --- a/tests/client/service/keysDecrypt.test.ts +++ b/tests/client/service/keysEncryptDecrypt.test.ts @@ -4,19 +4,19 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { GRPCServer } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import keysEncrypt from '@/client/service/keysEncrypt'; import keysDecrypt from '@/client/service/keysDecrypt'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_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'; import * as testUtils from '../../utils'; -describe('keysDecrypt', () => { - const logger = new Logger('keysDecrypt test', LogLevel.WARN, [ +describe('keysEncryptDecrypt', () => { + const logger = new Logger('keysEncryptDecrypt test', LogLevel.WARN, [ new StreamHandler(), ]); const password = 'helloworld'; @@ -52,6 +52,10 @@ describe('keysDecrypt', () => { logger, }); const clientService = { + keysEncrypt: keysEncrypt({ + authenticate, + keyManager, + }), keysDecrypt: keysDecrypt({ authenticate, keyManager, @@ -66,7 +70,7 @@ describe('keysDecrypt', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); @@ -79,15 +83,19 @@ describe('keysDecrypt', () => { recursive: true, }); }); - test('decrypts data', async () => { + test('encrypts and decrypts data', async () => { const plainText = Buffer.from('abc'); - const cipherText = await keyManager.encryptWithRootKeyPair(plainText); const request = new keysPB.Crypto(); - request.setData(cipherText.toString('binary')); - const response = await grpcClient.keysDecrypt( + request.setData(plainText.toString('binary')); + const encrypted = await grpcClient.keysEncrypt( request, clientUtils.encodeAuthFromPassword(password), ); + expect(encrypted).toBeInstanceOf(keysPB.Crypto); + const response = await grpcClient.keysDecrypt( + encrypted, + clientUtils.encodeAuthFromPassword(password), + ); expect(response).toBeInstanceOf(keysPB.Crypto); expect(response.getData()).toBe('abc'); }); diff --git a/tests/client/service/keysKeyPairRenew.test.ts b/tests/client/service/keysKeyPairRenew.test.ts index 6d5f822c8..699b73ca5 100644 --- a/tests/client/service/keysKeyPairRenew.test.ts +++ b/tests/client/service/keysKeyPairRenew.test.ts @@ -1,25 +1,23 @@ import type { Host, Port, TLSConfig } from '@/network/types'; -import type { ReverseProxy } from '@/network'; -import type { KeyManager } from '@/keys'; -import type { ForwardProxy } from '@/network'; -import type { Status } from '@/status'; +import type ForwardProxy from '@/network/ForwardProxy'; +import type ReverseProxy from '@/network/ReverseProxy'; +import type Status from '@/status/Status'; +import type KeyManager from '@/keys/KeyManager'; import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { NodeGraph } from '@/nodes'; -import { utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { PolykeyAgent } from '@'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import NodeGraph from '@/nodes/NodeGraph'; +import PolykeyAgent from '@/PolykeyAgent'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import keysKeyPairRenew from '@/client/service/keysKeyPairRenew'; +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 * as testUtils from '../../utils'; describe('keysKeyPairRenew', () => { @@ -89,7 +87,7 @@ describe('keysKeyPairRenew', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/keysKeyPairReset.test.ts b/tests/client/service/keysKeyPairReset.test.ts index f3a96ceed..d4eed5d93 100644 --- a/tests/client/service/keysKeyPairReset.test.ts +++ b/tests/client/service/keysKeyPairReset.test.ts @@ -1,25 +1,23 @@ import type { Host, Port, TLSConfig } from '@/network/types'; -import type { ReverseProxy } from '@/network'; -import type { KeyManager } from '@/keys'; -import type { ForwardProxy } from '@/network'; -import type { Status } from '@/status'; +import type ForwardProxy from '@/network/ForwardProxy'; +import type ReverseProxy from '@/network/ReverseProxy'; +import type Status from '@/status/Status'; +import type KeyManager from '@/keys/KeyManager'; import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { NodeGraph } from '@/nodes'; -import { utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { PolykeyAgent } from '@'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import NodeGraph from '@/nodes/NodeGraph'; +import PolykeyAgent from '@/PolykeyAgent'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import keysKeyPairReset from '@/client/service/keysKeyPairReset'; +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 * as testUtils from '../../utils'; describe('keysKeyPairReset', () => { @@ -89,7 +87,7 @@ describe('keysKeyPairReset', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/keysKeyPairRoot.test.ts b/tests/client/service/keysKeyPairRoot.test.ts index d70655dc5..19330b0cc 100644 --- a/tests/client/service/keysKeyPairRoot.test.ts +++ b/tests/client/service/keysKeyPairRoot.test.ts @@ -4,16 +4,15 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { GRPCServer } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import keysKeyPairRoot from '@/client/service/keysKeyPairRoot'; +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 * as testUtils from '../../utils'; describe('keysKeyPairRoot', () => { @@ -68,7 +67,7 @@ describe('keysKeyPairRoot', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/keysPasswordChange.test.ts b/tests/client/service/keysPasswordChange.test.ts index 87d373506..8f22e95e2 100644 --- a/tests/client/service/keysPasswordChange.test.ts +++ b/tests/client/service/keysPasswordChange.test.ts @@ -4,16 +4,15 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { GRPCServer } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import keysPasswordChange from '@/client/service/keysPasswordChange'; -import * as sessionsPB from '@/proto/js/polykey/v1/sessions/sessions_pb'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_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 * as testUtils from '../../utils'; describe('keysPasswordChange', () => { @@ -74,7 +73,7 @@ describe('keysPasswordChange', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/keysSign.test.ts b/tests/client/service/keysSign.test.ts deleted file mode 100644 index 371fd6e69..000000000 --- a/tests/client/service/keysSign.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Metadata } from '@grpc/grpc-js'; -import { GRPCServer } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; -import keysSign from '@/client/service/keysSign'; -import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; -import * as testUtils from '../../utils'; - -describe('keysSign', () => { - const logger = new Logger('keysSign test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedGenerateKeyPair: jest.SpyInstance; - let mockedGenerateDeterministicKeyPair: jest.SpyInstance; - beforeAll(async () => { - const globalKeyPair = await testUtils.setupGlobalKeypair(); - mockedGenerateKeyPair = jest - .spyOn(keysUtils, 'generateKeyPair') - .mockResolvedValue(globalKeyPair); - mockedGenerateDeterministicKeyPair = jest - .spyOn(keysUtils, 'generateDeterministicKeyPair') - .mockResolvedValue(globalKeyPair); - }); - afterAll(async () => { - mockedGenerateKeyPair.mockRestore(); - mockedGenerateDeterministicKeyPair.mockRestore(); - }); - let dataDir: string; - let keyManager: KeyManager; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const keysPath = path.join(dataDir, 'keys'); - keyManager = await KeyManager.createKeyManager({ - password, - keysPath, - logger, - }); - const clientService = { - keysSign: keysSign({ - authenticate, - keyManager, - }), - }; - grpcServer = new GRPCServer({ logger }); - await grpcServer.start({ - services: [[ClientServiceService, clientService]], - host: '127.0.0.1' as Host, - port: 0 as Port, - }); - grpcClient = await GRPCClientClient.createGRPCClientClient({ - nodeId: keyManager.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.port, - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await keyManager.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('signs with root keypair', async () => { - const data = Buffer.from('abc'); - const request = new keysPB.Crypto(); - request.setData(data.toString('binary')); - const response = await grpcClient.keysSign( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(keysPB.Crypto); - const verified = await keyManager.verifyWithRootKeyPair( - data, - Buffer.from(response.getSignature(), 'binary'), - ); - expect(verified).toBeTruthy(); - }); -}); diff --git a/tests/client/service/keysVerify.test.ts b/tests/client/service/keysSignVerify.test.ts similarity index 76% rename from tests/client/service/keysVerify.test.ts rename to tests/client/service/keysSignVerify.test.ts index a541d8f40..34cf424fd 100644 --- a/tests/client/service/keysVerify.test.ts +++ b/tests/client/service/keysSignVerify.test.ts @@ -4,20 +4,20 @@ import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; -import { GRPCServer } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import keysSign from '@/client/service/keysSign'; import keysVerify from '@/client/service/keysVerify'; -import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; 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'; import * as testUtils from '../../utils'; -describe('keysVerify', () => { - const logger = new Logger('keysVerify test', LogLevel.WARN, [ +describe('keysSignVerify', () => { + const logger = new Logger('keysSignVerify test', LogLevel.WARN, [ new StreamHandler(), ]); const password = 'helloworld'; @@ -53,6 +53,10 @@ describe('keysVerify', () => { logger, }); const clientService = { + keysSign: keysSign({ + authenticate, + keyManager, + }), keysVerify: keysVerify({ authenticate, keyManager, @@ -67,7 +71,7 @@ describe('keysVerify', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); @@ -80,16 +84,19 @@ describe('keysVerify', () => { recursive: true, }); }); - test('verifies signatures', async () => { + test('signs and verifies with root keypair', async () => { const data = Buffer.from('abc'); const request = new keysPB.Crypto(); - const signed = await keyManager.signWithRootKeyPair(data); request.setData(data.toString('binary')); - request.setSignature(signed.toString('binary')); - const response = await grpcClient.keysVerify( + const signed = await grpcClient.keysSign( request, clientUtils.encodeAuthFromPassword(password), ); + expect(signed).toBeInstanceOf(keysPB.Crypto); + const response = await grpcClient.keysVerify( + signed, + clientUtils.encodeAuthFromPassword(password), + ); expect(response).toBeInstanceOf(utilsPB.StatusMessage); expect(response.getSuccess()).toBeTruthy(); }); diff --git a/tests/client/service/nodesAdd.test.ts b/tests/client/service/nodesAdd.test.ts index a9e837a43..97c2d2cb4 100644 --- a/tests/client/service/nodesAdd.test.ts +++ b/tests/client/service/nodesAdd.test.ts @@ -3,22 +3,24 @@ import fs from 'fs'; 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 { KeyManager, utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import nodesAdd from '@/client/service/nodesAdd'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import { utils as nodesUtils } from '@/nodes'; +import * as nodesUtils from '@/nodes/utils'; +import * as clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; import * as validationErrors from '@/validation/errors'; import * as testUtils from '../../utils'; @@ -133,7 +135,7 @@ describe('nodesAdd', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/nodesClaim.test.ts b/tests/client/service/nodesClaim.test.ts index b133be3d3..ea2d926e3 100644 --- a/tests/client/service/nodesClaim.test.ts +++ b/tests/client/service/nodesClaim.test.ts @@ -1,27 +1,29 @@ -import type { Host, Port } from '@/network/types'; import type { Notification } from '@/notifications/types'; import type { NodeIdEncoded } from '@/nodes/types'; +import type { Host, Port } from '@/network/types'; import fs from 'fs'; 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 { KeyManager, utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { NotificationsManager } from '@/notifications'; -import { ACL } from '@/acl'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import NotificationsManager from '@/notifications/NotificationsManager'; +import ACL from '@/acl/ACL'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import nodesClaim from '@/client/service/nodesClaim'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; 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 keysUtils from '@/keys/utils'; import * as validationErrors from '@/validation/errors'; import * as testUtils from '../../utils'; @@ -176,7 +178,7 @@ describe('nodesClaim', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/nodesFind.test.ts b/tests/client/service/nodesFind.test.ts index bd6d277a8..b64d9dd35 100644 --- a/tests/client/service/nodesFind.test.ts +++ b/tests/client/service/nodesFind.test.ts @@ -3,20 +3,21 @@ import fs from 'fs'; 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 { KeyManager, utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { NodeConnectionManager, NodeGraph } from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import nodesFind from '@/client/service/nodesFind'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; +import * as clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; import * as validationErrors from '@/validation/errors'; import * as testUtils from '../../utils'; @@ -130,7 +131,7 @@ describe('nodesFind', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/nodesPing.test.ts b/tests/client/service/nodesPing.test.ts index 6c7873c54..f653eb62d 100644 --- a/tests/client/service/nodesPing.test.ts +++ b/tests/client/service/nodesPing.test.ts @@ -3,21 +3,23 @@ import fs from 'fs'; 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 { KeyManager, utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { Metadata } from '@grpc/grpc-js'; +import KeyManager from '@/keys/KeyManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import GRPCServer from '@/grpc/GRPCServer'; +import GRPCClientClient from '@/client/GRPCClientClient'; import nodesPing from '@/client/service/nodesPing'; -import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_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 clientUtils from '@/client/utils/utils'; +import * as keysUtils from '@/keys/utils'; import * as validationErrors from '@/validation/errors'; import * as testUtils from '../../utils'; @@ -138,7 +140,7 @@ describe('nodesPing', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/notificationsClear.test.ts b/tests/client/service/notificationsClear.test.ts index 094b2b007..2b32d03e1 100644 --- a/tests/client/service/notificationsClear.test.ts +++ b/tests/client/service/notificationsClear.test.ts @@ -5,20 +5,22 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; import { DB } from '@matrixai/db'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { NotificationsManager } from '@/notifications'; -import { ACL } from '@/acl'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import NotificationsManager from '@/notifications/NotificationsManager'; +import ACL from '@/acl/ACL'; +import GRPCClientClient from '@/client/GRPCClientClient'; import notificationsClear from '@/client/service/notificationsClear'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as keysUtils from '@/keys/utils'; +import * as clientUtils from '@/client/utils/utils'; import * as testUtils from '../../utils'; describe('notificationsClear', () => { @@ -152,7 +154,7 @@ describe('notificationsClear', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/notificationsRead.test.ts b/tests/client/service/notificationsRead.test.ts index a2d1e4cea..c3882bc8a 100644 --- a/tests/client/service/notificationsRead.test.ts +++ b/tests/client/service/notificationsRead.test.ts @@ -6,25 +6,23 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; import { DB } from '@matrixai/db'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { - NodeConnectionManager, - NodeGraph, - NodeManager, - utils as nodesUtils, -} from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { NotificationsManager } from '@/notifications'; -import { ACL } from '@/acl'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import NotificationsManager from '@/notifications/NotificationsManager'; +import ACL from '@/acl/ACL'; +import GRPCClientClient from '@/client/GRPCClientClient'; import notificationsRead from '@/client/service/notificationsRead'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as notificationsPB from '@/proto/js/polykey/v1/notifications/notifications_pb'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as clientUtils from '@/client/utils'; import * as testUtils from '../../utils'; describe('notificationsRead', () => { @@ -230,7 +228,7 @@ describe('notificationsRead', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); diff --git a/tests/client/service/notificationsSend.test.ts b/tests/client/service/notificationsSend.test.ts index db4bb5b8e..7786f69fe 100644 --- a/tests/client/service/notificationsSend.test.ts +++ b/tests/client/service/notificationsSend.test.ts @@ -6,30 +6,25 @@ import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { Metadata } from '@grpc/grpc-js'; import { DB } from '@matrixai/db'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { GRPCServer } from '@/grpc'; -import { - NodeConnectionManager, - NodeGraph, - NodeManager, - utils as nodesUtils, -} from '@/nodes'; -import { Sigchain } from '@/sigchain'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { - NotificationsManager, - utils as notificationsUtils, -} from '@/notifications'; -import { ACL } from '@/acl'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import KeyManager from '@/keys/KeyManager'; +import GRPCServer from '@/grpc/GRPCServer'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import Sigchain from '@/sigchain/Sigchain'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import NotificationsManager from '@/notifications/NotificationsManager'; +import ACL from '@/acl/ACL'; +import GRPCClientClient from '@/client/GRPCClientClient'; import notificationsSend from '@/client/service/notificationsSend'; +import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as notificationsPB from '@/proto/js/polykey/v1/notifications/notifications_pb'; -import { GRPCClientAgent } from '@/agent'; +import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as notificationsUtils from '@/notifications/utils'; +import * as clientUtils from '@/client/utils'; import * as testUtils from '../../utils'; describe('notificationsSend', () => { @@ -57,8 +52,8 @@ describe('notificationsSend', () => { return 'signedNotification' as SignedNotification; }); mockedSendNotification = jest - .spyOn(GRPCClientAgent.prototype, 'notificationsSend') - .mockResolvedValue(new notificationsPB.AgentNotification()); + .spyOn(NodeConnectionManager.prototype, 'withConnF') + .mockImplementation(); }); afterAll(async () => { mockedGenerateKeyPair.mockRestore(); @@ -170,7 +165,7 @@ describe('notificationsSend', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: keyManager.getNodeId(), host: '127.0.0.1' as Host, - port: grpcServer.port, + port: grpcServer.getPort(), logger, }); }); @@ -209,8 +204,7 @@ describe('notificationsSend', () => { expect(mockedSendNotification.mock.calls.length).toBe(1); expect( nodesUtils.encodeNodeId(mockedSendNotification.mock.calls[0][0]), - ).toBe(receiverNodeIdEncoded); - expect(mockedSendNotification.mock.calls[0][1]).toBe('signedNotification'); + ).toEqual(receiverNodeIdEncoded); // Check notification content expect(mockedSignNotification.mock.calls[0][0]).toEqual({ data: { diff --git a/tests/discovery/Discovery.test.ts b/tests/discovery/Discovery.test.ts index 4d3270b44..3f56a17a4 100644 --- a/tests/discovery/Discovery.test.ts +++ b/tests/discovery/Discovery.test.ts @@ -117,6 +117,8 @@ describe('Discovery', () => { logger: logger.getChild('fwxProxy'), }); await fwdProxy.start({ + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, tlsConfig: { keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, certChainPem: await keyManager.getRootCertChainPem(), @@ -126,6 +128,7 @@ describe('Discovery', () => { await revProxy.start({ serverHost: '127.0.0.1' as Host, serverPort: 55555 as Port, + ingressHost: '127.0.0.1' as Host, tlsConfig: { keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, certChainPem: await keyManager.getRootCertChainPem(), @@ -158,6 +161,13 @@ describe('Discovery', () => { nodeA = await PolykeyAgent.createPolykeyAgent({ password: password, nodePath: path.join(dataDir, 'nodeA'), + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, keysConfig: { rootKeyPairBits: 2048, }, @@ -166,6 +176,13 @@ describe('Discovery', () => { nodeB = await PolykeyAgent.createPolykeyAgent({ password: password, nodePath: path.join(dataDir, 'nodeB'), + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, keysConfig: { rootKeyPairBits: 2048, }, @@ -195,9 +212,7 @@ describe('Discovery', () => { }, global.maxTimeout); afterAll(async () => { await nodeA.stop(); - await nodeA.destroy(); await nodeB.stop(); - await nodeB.destroy(); await nodeConnectionManager.stop(); await nodeGraph.stop(); await revProxy.stop(); diff --git a/tests/grpc/GRPCServer.test.ts b/tests/grpc/GRPCServer.test.ts index 780ebdea5..2c62b70f9 100644 --- a/tests/grpc/GRPCServer.test.ts +++ b/tests/grpc/GRPCServer.test.ts @@ -82,7 +82,7 @@ describe('GRPCServer', () => { // Noop await server.stop(); expect(() => { - server.port; + server.getPort(); }).toThrow(grpcErrors.ErrorGRPCServerNotRunning); expect(() => { server.closeServerForce(); @@ -113,8 +113,8 @@ describe('GRPCServer', () => { certChainPem: keysUtils.certToPem(cert), }, }); - expect(typeof server.port).toBe('number'); - expect(server.port).toBeGreaterThan(0); + expect(typeof server.getPort()).toBe('number'); + expect(server.getPort()).toBeGreaterThan(0); await server.stop(); }); test('connecting to the server securely', async () => { @@ -152,7 +152,7 @@ describe('GRPCServer', () => { ); const client = await testGrpcUtils.openTestClientSecure( nodeIdServer, - server.port, + server.getPort(), keysUtils.privateKeyToPem(clientKeyPair.privateKey), keysUtils.certToPem(clientCert), ); @@ -163,7 +163,7 @@ describe('GRPCServer', () => { const m = new utilsPB.EchoMessage(); m.setChallenge('a98u3e4d'); const pCall = unary(m); - expect(pCall.call.getPeer()).toBe(`dns:127.0.0.1:${server.port}`); + expect(pCall.call.getPeer()).toBe(`dns:127.0.0.1:${server.getPort()}`); const m_ = await pCall; expect(m_.getChallenge()).toBe(m.getChallenge()); testGrpcUtils.closeTestClientSecure(client); @@ -205,7 +205,7 @@ describe('GRPCServer', () => { const nodeIdServer1 = keysUtils.certNodeId(serverCert1)!; const client1 = await testGrpcUtils.openTestClientSecure( nodeIdServer1, - server.port, + server.getPort(), keysUtils.privateKeyToPem(clientKeyPair.privateKey), keysUtils.certToPem(clientCert), ); @@ -216,7 +216,7 @@ describe('GRPCServer', () => { const m1 = new utilsPB.EchoMessage(); m1.setChallenge('98f7g98dfg71'); const pCall1 = unary1(m1); - expect(pCall1.call.getPeer()).toBe(`dns:127.0.0.1:${server.port}`); + expect(pCall1.call.getPeer()).toBe(`dns:127.0.0.1:${server.getPort()}`); const m1_ = await pCall1; expect(m1_.getChallenge()).toBe(m1.getChallenge()); // Change key and certificate @@ -235,14 +235,14 @@ describe('GRPCServer', () => { const m2 = new utilsPB.EchoMessage(); m2.setChallenge('12308947239847'); const pCall2 = unary1(m2); - expect(pCall2.call.getPeer()).toBe(`dns:127.0.0.1:${server.port}`); + expect(pCall2.call.getPeer()).toBe(`dns:127.0.0.1:${server.getPort()}`); const m2_ = await pCall2; expect(m2_.getChallenge()).toBe(m2.getChallenge()); // Second client connection const nodeIdServer2 = keysUtils.certNodeId(serverCert2)!; const client2 = await testGrpcUtils.openTestClientSecure( nodeIdServer2, - server.port, + server.getPort(), keysUtils.privateKeyToPem(clientKeyPair.privateKey), keysUtils.certToPem(clientCert), ); @@ -253,7 +253,7 @@ describe('GRPCServer', () => { const m3 = new utilsPB.EchoMessage(); m3.setChallenge('aa89fusd98f'); const pCall3 = unary2(m3); - expect(pCall3.call.getPeer()).toBe(`dns:127.0.0.1:${server.port}`); + expect(pCall3.call.getPeer()).toBe(`dns:127.0.0.1:${server.getPort()}`); const m3_ = await pCall3; expect(m3_.getChallenge()).toBe(m3.getChallenge()); testGrpcUtils.closeTestClientSecure(client1); @@ -295,7 +295,7 @@ describe('GRPCServer', () => { ); const client = await testGrpcUtils.openTestClientSecure( nodeIdServer, - server.port, + server.getPort(), keysUtils.privateKeyToPem(clientKeyPair.privateKey), keysUtils.certToPem(clientCert), ); diff --git a/tests/grpc/utils.test.ts b/tests/grpc/utils.test.ts index f29b86c4a..c17f0457d 100644 --- a/tests/grpc/utils.test.ts +++ b/tests/grpc/utils.test.ts @@ -18,7 +18,7 @@ describe('GRPC utils', () => { metaServer; [server, port] = await utils.openTestServer(authenticate, logger); client = await utils.openTestClient(port); - }, global.polykeyStartupTimeout); + }, global.defaultTimeout); afterAll(async () => { utils.closeTestClient(client); setTimeout(() => { diff --git a/tests/nodes/NodeConnection.test.ts b/tests/nodes/NodeConnection.test.ts index 5e1ea13bc..9d50ae94a 100644 --- a/tests/nodes/NodeConnection.test.ts +++ b/tests/nodes/NodeConnection.test.ts @@ -289,7 +289,7 @@ describe(`${NodeConnection.name} test`, () => { }); await serverRevProxy.start({ serverHost: localHost, - serverPort: agentServer.port, + serverPort: agentServer.getPort(), ingressHost: localHost, tlsConfig: serverTLSConfig, }); diff --git a/tests/notifications/NotificationsManager.test.ts b/tests/notifications/NotificationsManager.test.ts index a1e23b564..d52aa0968 100644 --- a/tests/notifications/NotificationsManager.test.ts +++ b/tests/notifications/NotificationsManager.test.ts @@ -1,605 +1,872 @@ -import type { NodeId, NodeInfo, NodeAddress } from '@/nodes/types'; -import type { Host, Port, TLSConfig } from '@/network/types'; -import type { KeyPairPem, CertificatePem } from '@/keys/types'; +import type { NodeId } from '@/nodes/types'; +import type { Host, Port } from '@/network/types'; import type { VaultActions, VaultName } from '@/vaults/types'; -import type { NotificationData } from '@/notifications/types'; +import type { Notification, NotificationData } from '@/notifications/types'; import fs from 'fs'; import os from 'os'; import path from 'path'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; - -import { ACL } from '@/acl'; -import { Sigchain } from '@/sigchain'; -import { GRPCServer } from '@/grpc'; -import { KeyManager, utils as keysUtils } from '@/keys'; -import { VaultManager } from '@/vaults'; -import { GestaltGraph } from '@/gestalts'; -import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { NotificationsManager } from '@/notifications'; -import { ForwardProxy, ReverseProxy } from '@/network'; -import { AgentServiceService, createAgentService } from '@/agent'; -import { generateVaultId } from '@/vaults/utils'; -import { utils as nodesUtils } from '@/nodes'; +import { IdInternal } from '@matrixai/id'; +import PolykeyAgent from '@/PolykeyAgent'; +import ACL from '@/acl/ACL'; +import Sigchain from '@/sigchain/Sigchain'; +import KeyManager from '@/keys/KeyManager'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeManager from '@/nodes/NodeManager'; +import NotificationsManager from '@/notifications/NotificationsManager'; +import ForwardProxy from '@/network/ForwardProxy'; +import ReverseProxy from '@/network/ReverseProxy'; +import * as notificationsErrors from '@/notifications/errors'; +import * as vaultsUtils from '@/vaults/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as keysUtils from '@/keys/utils'; import * as testUtils from '../utils'; -// Mocks. -jest.mock('@/keys/utils', () => ({ - ...jest.requireActual('@/keys/utils'), - generateDeterministicKeyPair: - jest.requireActual('@/keys/utils').generateKeyPair, -})); - describe('NotificationsManager', () => { const password = 'password'; - const node: NodeInfo = { - id: nodesUtils.encodeNodeId(testUtils.generateRandomNodeId()), - chain: {}, - }; - const logger = new Logger('NotificationsManager Test', LogLevel.WARN, [ - new StreamHandler(), + const logger = new Logger( + `${NotificationsManager.name} Test`, + LogLevel.WARN, + [new StreamHandler()], + ); + const senderId = 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 authToken = 'AUTH'; - let senderFwdProxy: ForwardProxy; - let receiverRevProxy: ReverseProxy; - let fwdTLSConfig: TLSConfig; - - let keysDataDir: string; - let receiverDataDir: string; - let receiverKeyManager: KeyManager; - let receiverVaultManager: VaultManager; - let receiverNodeGraph: NodeGraph; - let receiverNodeManager: NodeManager; - let receiverNodeConnectionManager: NodeConnectionManager; - let receiverSigchain: Sigchain; - let receiverACL: ACL; - let receiverGestaltGraph: GestaltGraph; - let receiverDb: DB; - let receiverNotificationsManager: NotificationsManager; - - let senderDataDir: string; - let senderKeyManager: KeyManager; - let senderDb: DB; - let senderACL: ACL; - let senderSigchain: Sigchain; - let senderNodeGraph: NodeGraph; - let senderNodeConnectionManager: NodeConnectionManager; - let senderNodeManager: NodeManager; - - let senderNodeId: NodeId, receiverNodeId: NodeId; - let senderKeyPairPem: KeyPairPem, receiverKeyPairPem: KeyPairPem; - let senderCertPem: CertificatePem, receiverCertPem: CertificatePem; - - let agentService; - let agentServer: GRPCServer; - - // Keep IPs unique. ideally we'd use the generated IP and port. but this is good for now. - // If this fails again we shouldn't specify the port and IP. - const senderHost = '127.0.0.1' as Host; - const receiverHost = '127.0.0.2' as Host; - let receiverIngressPort: Port; - + 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, + ]), + ); + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + /** + * Shared ACL, DB, NodeManager, KeyManager for all tests + */ + let dataDir: string; + let acl: ACL; + let db: DB; + let nodeGraph: NodeGraph; + let nodeConnectionManager: NodeConnectionManager; + let nodeManager: NodeManager; + let keyManager: KeyManager; + let sigchain: Sigchain; + let fwdProxy: ForwardProxy; + let revProxy: ReverseProxy; + let receiver: PolykeyAgent; beforeAll(async () => { - keysDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-server'), + const globalKeyPair = await testUtils.setupGlobalKeypair(); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(globalKeyPair); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), ); - - receiverDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-server'), - ); - - const senderKeysPath = path.join(keysDataDir, 'senderKeys'); - senderKeyManager = await KeyManager.createKeyManager({ + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ password, - keysPath: senderKeysPath, - fs, + keysPath, logger, }); - senderKeyPairPem = senderKeyManager.getRootKeyPairPem(); - senderCertPem = senderKeyManager.getRootCertPem(); - senderNodeId = keysUtils.certNodeId(senderKeyManager.getRootCert())!; - fwdTLSConfig = { - keyPrivatePem: senderKeyPairPem.privateKey, - certChainPem: senderCertPem, - }; - - const receiverKeysPath = path.join(keysDataDir, 'receiverKeys'); - receiverKeyManager = await KeyManager.createKeyManager({ - password, - keysPath: receiverKeysPath, - fs: fs, - logger: logger, - }); - receiverKeyPairPem = receiverKeyManager.getRootKeyPairPem(); - receiverCertPem = receiverKeyManager.getRootCertPem(); - const revTLSConfig = { - keyPrivatePem: receiverKeyPairPem.privateKey, - certChainPem: receiverCertPem, - }; - - // Server setup - const receiverVaultsPath = path.join(receiverDataDir, 'receiverVaults'); - const receiverDbPath = path.join(receiverDataDir, 'receiverDb'); - - receiverDb = await DB.createDB({ - dbPath: receiverDbPath, - fs: fs, - logger: logger, + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, crypto: { - key: receiverKeyManager.dbKey, + key: keyManager.dbKey, ops: { encrypt: keysUtils.encryptWithKey, decrypt: keysUtils.decryptWithKey, }, }, }); - receiverACL = await ACL.createACL({ - db: receiverDb, - logger: logger, - }); - receiverSigchain = await Sigchain.createSigchain({ - keyManager: receiverKeyManager, - db: receiverDb, - logger: logger, - }); - receiverGestaltGraph = await GestaltGraph.createGestaltGraph({ - db: receiverDb, - acl: receiverACL, - logger: logger, - }); - // Won't be used so don't need to start - const receiverFwdProxy = new ForwardProxy({ - authToken: '', - logger: logger, - }); - receiverRevProxy = new ReverseProxy({ - logger: logger, - }); - receiverNodeGraph = await NodeGraph.createNodeGraph({ - db: receiverDb, - keyManager: receiverKeyManager, - logger: logger, - }); - receiverNodeConnectionManager = new NodeConnectionManager({ - keyManager: receiverKeyManager, - nodeGraph: receiverNodeGraph, - fwdProxy: receiverFwdProxy, - revProxy: receiverRevProxy, + acl = await ACL.createACL({ + db, logger, }); - await receiverNodeConnectionManager.start(); - receiverNodeManager = new NodeManager({ - db: receiverDb, - sigchain: receiverSigchain, - keyManager: receiverKeyManager, - nodeGraph: receiverNodeGraph, - nodeConnectionManager: receiverNodeConnectionManager, - logger: logger, - }); - receiverVaultManager = await VaultManager.createVaultManager({ - keyManager: receiverKeyManager, - vaultsPath: receiverVaultsPath, - nodeConnectionManager: receiverNodeConnectionManager, - vaultsKey: receiverKeyManager.vaultKey, - db: receiverDb, - acl: receiverACL, - gestaltGraph: receiverGestaltGraph, - fs: fs, - logger: logger, - }); - receiverNotificationsManager = - await NotificationsManager.createNotificationsManager({ - acl: receiverACL, - db: receiverDb, - nodeConnectionManager: receiverNodeConnectionManager, - nodeManager: receiverNodeManager, - keyManager: receiverKeyManager, - messageCap: 5, - logger: logger, - }); - receiverNodeId = keysUtils.certNodeId(receiverKeyManager.getRootCert())!; - await receiverGestaltGraph.setNode(node); - - agentService = createAgentService({ - keyManager: receiverKeyManager, - vaultManager: receiverVaultManager, - nodeManager: receiverNodeManager, - nodeGraph: receiverNodeGraph, - sigchain: receiverSigchain, - nodeConnectionManager: receiverNodeConnectionManager, - notificationsManager: receiverNotificationsManager, - }); - agentServer = new GRPCServer({ - logger: logger, - }); - await agentServer.start({ - services: [[AgentServiceService, agentService]], - host: receiverHost, - }); - - await receiverRevProxy.start({ - serverHost: receiverHost, - serverPort: agentServer.port, - ingressHost: receiverHost, - tlsConfig: revTLSConfig, + sigchain = await Sigchain.createSigchain({ + db, + keyManager, + logger, }); - receiverIngressPort = receiverRevProxy.getIngressPort(); - }, global.polykeyStartupTimeout * 2); - - beforeEach(async () => { - senderDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-sender'), - ); - const senderDbPath = await fs.promises.mkdtemp( - path.join(senderDataDir, 'senderDb'), - ); - // Won't be used so don't need to start - const senderRevProxy = new ReverseProxy({ - logger: logger, + fwdProxy = new ForwardProxy({ + authToken: 'abc123', + logger, }); - senderFwdProxy = new ForwardProxy({ - authToken: authToken, - logger: logger, + await fwdProxy.start({ + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, }); - senderDb = await DB.createDB({ - dbPath: senderDbPath, - fs, - logger, - crypto: { - key: senderKeyManager.dbKey, - ops: { - encrypt: keysUtils.encryptWithKey, - decrypt: keysUtils.decryptWithKey, - }, + revProxy = new ReverseProxy({ logger }); + await revProxy.start({ + serverHost: '127.0.0.1' as Host, + serverPort: 55555 as Port, + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), }, }); - senderACL = await ACL.createACL({ db: senderDb, logger }); - senderSigchain = await Sigchain.createSigchain({ - keyManager: senderKeyManager, - db: senderDb, + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, logger, }); - senderNodeGraph = await NodeGraph.createNodeGraph({ - db: senderDb, - keyManager: senderKeyManager, + nodeConnectionManager = new NodeConnectionManager({ + nodeGraph, + keyManager, + fwdProxy, + revProxy, logger, }); - senderNodeConnectionManager = new NodeConnectionManager({ - keyManager: senderKeyManager, - nodeGraph: senderNodeGraph, - fwdProxy: senderFwdProxy, - revProxy: senderRevProxy, + await nodeConnectionManager.start(); + nodeManager = new NodeManager({ + db, + keyManager, + sigchain, + nodeConnectionManager, + nodeGraph, logger, }); - await senderNodeConnectionManager.start(); - senderNodeManager = new NodeManager({ - db: senderDb, - sigchain: senderSigchain, - keyManager: senderKeyManager, - nodeGraph: senderNodeGraph, - nodeConnectionManager: senderNodeConnectionManager, + // Set up node for receiving notifications + receiver = await PolykeyAgent.createPolykeyAgent({ + password: password, + nodePath: path.join(dataDir, 'receiver'), + keysConfig: { + rootKeyPairBits: 1024, + }, logger, }); - - await senderACL.stop(); - await senderFwdProxy.start({ - tlsConfig: fwdTLSConfig, - proxyHost: senderHost, - // ProxyPort: senderPort, - egressHost: senderHost, - // EgressPort: senderPort, - }); - await senderNodeGraph.setNode(receiverNodeId, { - host: receiverHost, - port: receiverIngressPort, - } as NodeAddress); - - await receiverNotificationsManager.clearNotifications(); - expect(await receiverNotificationsManager.readNotifications()).toEqual([]); - }, global.polykeyStartupTimeout * 2); - - afterEach(async () => { - await senderNodeConnectionManager.stop(); - await senderNodeGraph.stop(); - await senderACL.stop(); - await senderFwdProxy.stop(); - await senderDb.stop(); - await fs.promises.rm(senderDataDir, { - force: true, - recursive: true, + await nodeGraph.setNode(receiver.keyManager.getNodeId(), { + host: receiver.revProxy.getIngressHost(), + port: receiver.revProxy.getIngressPort(), }); - }); - + }, global.defaultTimeout); afterAll(async () => { - await senderKeyManager.stop(); - await receiverACL.stop(); - await receiverSigchain.stop(); - await receiverGestaltGraph.stop(); - await receiverVaultManager.stop(); - await receiverNodeConnectionManager.stop(); - await receiverNodeGraph.stop(); - await receiverNotificationsManager.stop(); - await agentServer.stop(); - await receiverRevProxy.stop(); - await receiverKeyManager.stop(); - await receiverDb.stop(); - await fs.promises.rm(receiverDataDir, { + await receiver.stop(); + await nodeConnectionManager.stop(); + await nodeGraph.stop(); + await revProxy.stop(); + await fwdProxy.stop(); + await sigchain.stop(); + await acl.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { force: true, recursive: true, }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); }); - - test('can send notifications', async () => { - const senderNotificationsManager = + test('notifications manager readiness', async () => { + const notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, + logger, + }); + await expect(notificationsManager.destroy()).rejects.toThrow( + notificationsErrors.ErrorNotificationsRunning, + ); + // Should be a noop + await notificationsManager.start(); + await notificationsManager.stop(); + await notificationsManager.destroy(); + await expect(notificationsManager.start()).rejects.toThrow( + notificationsErrors.ErrorNotificationsDestroyed, + ); + await expect(async () => { + await notificationsManager.readNotifications(); + }).rejects.toThrow(notificationsErrors.ErrorNotificationsNotRunning); + await expect(async () => { + await notificationsManager.clearNotifications(); + }).rejects.toThrow(notificationsErrors.ErrorNotificationsNotRunning); + }); + test('can send notifications with permission', async () => { + const notificationsManager = await NotificationsManager.createNotificationsManager({ - acl: senderACL, - db: senderDb, - nodeConnectionManager: senderNodeConnectionManager, - nodeManager: senderNodeManager, - keyManager: senderKeyManager, + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, logger, }); - const notificationData: NotificationData = { + const generalNotification: NotificationData = { type: 'General', message: 'msg', }; - // Can send with permissions - await receiverACL.setNodePerm(senderNodeId, { + const gestaltNotification: NotificationData = { + type: 'GestaltInvite', + }; + const vaultNotification: NotificationData = { + type: 'VaultShare', + vaultId: vaultsUtils.generateVaultId().toString(), + vaultName: 'vaultName' as VaultName, + actions: { + clone: null, + pull: null, + } as VaultActions, + }; + await receiver.acl.setNodePerm(keyManager.getNodeId(), { gestalt: { notify: null, }, vaults: {}, }); - // Can send without permissions - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData, + await notificationsManager.sendNotification( + receiver.keyManager.getNodeId(), + generalNotification, ); - await receiverACL.setNodePerm(senderNodeId, { - gestalt: {}, - vaults: {}, - }); - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData, + await notificationsManager.sendNotification( + receiver.keyManager.getNodeId(), + gestaltNotification, + ); + await notificationsManager.sendNotification( + receiver.keyManager.getNodeId(), + vaultNotification, + ); + const receivedNotifications = + await receiver.notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(3); + expect(receivedNotifications[0].data).toEqual(vaultNotification); + expect(receivedNotifications[0].senderId).toBe( + nodesUtils.encodeNodeId(keyManager.getNodeId()), + ); + expect(receivedNotifications[1].data).toEqual(gestaltNotification); + expect(receivedNotifications[1].senderId).toBe( + nodesUtils.encodeNodeId(keyManager.getNodeId()), ); - await senderNotificationsManager.stop(); + expect(receivedNotifications[2].data).toEqual(generalNotification); + expect(receivedNotifications[2].senderId).toBe( + nodesUtils.encodeNodeId(keyManager.getNodeId()), + ); + // Reverse side-effects + await receiver.notificationsManager.clearNotifications(); + await receiver.acl.unsetNodePerm(keyManager.getNodeId()); + await notificationsManager.stop(); }); - - test('can receive and read sent notifications', async () => { - const senderNotificationsManager = + test('cannot send notifications without permission', async () => { + const notificationsManager = await NotificationsManager.createNotificationsManager({ - acl: senderACL, - db: senderDb, - nodeConnectionManager: senderNodeConnectionManager, - nodeManager: senderNodeManager, - keyManager: senderKeyManager, + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, logger, }); - - const notificationData: NotificationData = { + const generalNotification: NotificationData = { type: 'General', message: 'msg', }; - await receiverACL.setNodePerm(senderNodeId, { + const gestaltNotification: NotificationData = { + type: 'GestaltInvite', + }; + const vaultNotification: NotificationData = { + type: 'VaultShare', + vaultId: vaultsUtils.generateVaultId().toString(), + vaultName: 'vaultName' as VaultName, + actions: { + clone: null, + pull: null, + } as VaultActions, + }; + await expect(async () => + notificationsManager.sendNotification( + receiver.keyManager.getNodeId(), + generalNotification, + ), + ).rejects.toThrow( + notificationsErrors.ErrorNotificationsPermissionsNotFound, + ); + await expect(async () => + notificationsManager.sendNotification( + receiver.keyManager.getNodeId(), + gestaltNotification, + ), + ).rejects.toThrow( + notificationsErrors.ErrorNotificationsPermissionsNotFound, + ); + await expect(async () => + notificationsManager.sendNotification( + receiver.keyManager.getNodeId(), + vaultNotification, + ), + ).rejects.toThrow( + notificationsErrors.ErrorNotificationsPermissionsNotFound, + ); + const receivedNotifications = + await receiver.notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(0); + // Reverse side-effects + await notificationsManager.stop(); + }); + test('can receive notifications from senders with permission', async () => { + const notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, + logger, + }); + const notification1: Notification = { + data: { + type: 'General', + message: 'msg', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification2: Notification = { + data: { + type: 'GestaltInvite', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification3: Notification = { + data: { + type: 'VaultShare', + vaultId: vaultsUtils.generateVaultId().toString(), + vaultName: 'vaultName' as VaultName, + actions: { + clone: null, + pull: null, + } as VaultActions, + }, + senderId: senderIdEncoded, + isRead: false, + }; + await acl.setNodePerm(senderId, { gestalt: { notify: null, }, vaults: {}, }); - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData, - ); - const notifs = await receiverNotificationsManager.readNotifications(); - expect(notifs[0].data).toEqual(notificationData); - expect(notifs[0].senderId).toEqual(nodesUtils.encodeNodeId(senderNodeId)); - expect(notifs[0].isRead).toBeTruthy(); - - await senderNotificationsManager.stop(); + await notificationsManager.receiveNotification(notification1); + await notificationsManager.receiveNotification(notification2); + await notificationsManager.receiveNotification(notification3); + const receivedNotifications = + await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(3); + expect(receivedNotifications[0].data).toEqual(notification3.data); + expect(receivedNotifications[0].senderId).toEqual(senderIdEncoded); + expect(receivedNotifications[1].data).toEqual(notification2.data); + expect(receivedNotifications[1].senderId).toEqual(senderIdEncoded); + expect(receivedNotifications[2].data).toEqual(notification1.data); + expect(receivedNotifications[2].senderId).toEqual(senderIdEncoded); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); }); - - test('cannot receive notifications without notify permission', async () => { - const senderNotificationsManager = + test('cannot receive notifications from senders without permission', async () => { + const notificationsManager = await NotificationsManager.createNotificationsManager({ - acl: senderACL, - db: senderDb, - nodeConnectionManager: senderNodeConnectionManager, - nodeManager: senderNodeManager, - keyManager: senderKeyManager, + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, logger, }); - - const notificationData: NotificationData = { - type: 'General', - message: 'msg', + const notification: Notification = { + data: { + type: 'General', + message: 'msg', + }, + senderId: senderIdEncoded, + isRead: false, }; - await receiverACL.setNodePerm(senderNodeId, { + // No permissions + await expect(async () => + notificationsManager.receiveNotification(notification), + ).rejects.toThrow( + notificationsErrors.ErrorNotificationsPermissionsNotFound, + ); + let receivedNotifications = await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(0); + // Missing permission + await acl.setNodePerm(senderId, { gestalt: {}, vaults: {}, }); - - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData, - ); - const notifs = await receiverNotificationsManager.readNotifications(); - expect(notifs).toEqual([]); - - await senderNotificationsManager.stop(); + await notificationsManager.receiveNotification(notification); + receivedNotifications = await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(0); + // Reverse side-effects + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); }); - - test('notifications are read in order they were sent', async () => { - const senderNotificationsManager = + test('marks notifications as read', async () => { + const notificationsManager = await NotificationsManager.createNotificationsManager({ - acl: senderACL, - db: senderDb, - nodeConnectionManager: senderNodeConnectionManager, - nodeManager: senderNodeManager, - keyManager: senderKeyManager, + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, logger, }); - - const notificationData1: NotificationData = { - type: 'General', - message: 'msg1', + const notification: Notification = { + data: { + type: 'General', + message: 'msg', + }, + senderId: senderIdEncoded, + isRead: false, }; - const notificationData2: NotificationData = { - type: 'General', - message: 'msg2', + await acl.setNodePerm(senderId, { + gestalt: { + notify: null, + }, + vaults: {}, + }); + await notificationsManager.receiveNotification(notification); + const receivedNotifications = + await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(1); + expect(receivedNotifications[0].isRead).toBeTruthy(); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); + }); + test('all notifications are read oldest to newest by default', async () => { + const notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, + logger, + }); + const notification1: Notification = { + data: { + type: 'General', + message: 'msg1', + }, + senderId: senderIdEncoded, + isRead: false, }; - const notificationData3: NotificationData = { - type: 'General', - message: 'msg3', + const notification2: Notification = { + data: { + type: 'General', + message: 'msg2', + }, + senderId: senderIdEncoded, + isRead: false, }; - - await receiverACL.setNodePerm(senderNodeId, { + const notification3: Notification = { + data: { + type: 'General', + message: 'msg3', + }, + senderId: senderIdEncoded, + isRead: false, + }; + await acl.setNodePerm(senderId, { gestalt: { notify: null, }, vaults: {}, }); - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData1, - ); - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData2, - ); - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData3, - ); - const notifs = await receiverNotificationsManager.readNotifications(); - expect(notifs[0].data).toEqual(notificationData3); - expect(notifs[1].data).toEqual(notificationData2); - expect(notifs[2].data).toEqual(notificationData1); - - await senderNotificationsManager.stop(); + await notificationsManager.receiveNotification(notification1); + await notificationsManager.receiveNotification(notification2); + await notificationsManager.receiveNotification(notification3); + const receivedNotifications = + await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(3); + expect(receivedNotifications[0].data['message']).toBe('msg3'); + expect(receivedNotifications[1].data['message']).toBe('msg2'); + expect(receivedNotifications[2].data['message']).toBe('msg1'); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); }); - - test('notifications can be capped', async () => { - const senderNotificationsManager = + test('can read only unread notifications', async () => { + const notificationsManager = await NotificationsManager.createNotificationsManager({ - acl: senderACL, - db: senderDb, - nodeConnectionManager: senderNodeConnectionManager, - nodeManager: senderNodeManager, - keyManager: senderKeyManager, + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, logger, }); - - await receiverACL.setNodePerm(senderNodeId, { + const notification1: Notification = { + data: { + type: 'General', + message: 'msg1', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification2: Notification = { + data: { + type: 'General', + message: 'msg2', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification3: Notification = { + data: { + type: 'General', + message: 'msg3', + }, + senderId: senderIdEncoded, + isRead: false, + }; + await acl.setNodePerm(senderId, { gestalt: { notify: null, }, vaults: {}, }); - for (let i = 0; i <= 5; i++) { - const notificationData: NotificationData = { + await notificationsManager.receiveNotification(notification1); + await notificationsManager.receiveNotification(notification2); + await notificationsManager.receiveNotification(notification3); + await notificationsManager.readNotifications(); + const unreadNotifications = await notificationsManager.readNotifications({ + unread: true, + }); + expect(unreadNotifications).toHaveLength(0); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); + }); + test('can read a single notification', async () => { + const notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, + logger, + }); + const notification1: Notification = { + data: { type: 'General', - message: i.toString(), - }; - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData, - ); - } - const notifs = await receiverNotificationsManager.readNotifications(); - expect(notifs[0].data).toEqual({ - type: 'General', - message: '5', - }); - expect(notifs[1].data).toEqual({ - type: 'General', - message: '4', - }); - expect(notifs[2].data).toEqual({ - type: 'General', - message: '3', + message: 'msg1', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification2: Notification = { + data: { + type: 'General', + message: 'msg2', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification3: Notification = { + data: { + type: 'General', + message: 'msg3', + }, + senderId: senderIdEncoded, + isRead: false, + }; + await acl.setNodePerm(senderId, { + gestalt: { + notify: null, + }, + vaults: {}, }); - expect(notifs[3].data).toEqual({ - type: 'General', - message: '2', + await notificationsManager.receiveNotification(notification1); + await notificationsManager.receiveNotification(notification2); + await notificationsManager.receiveNotification(notification3); + const lastNotification = await notificationsManager.readNotifications({ + number: 1, + }); + expect(lastNotification).toHaveLength(1); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); + }); + test('can read notifications in reverse order', async () => { + const notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, + logger, + }); + const notification1: Notification = { + data: { + type: 'General', + message: 'msg1', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification2: Notification = { + data: { + type: 'General', + message: 'msg2', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification3: Notification = { + data: { + type: 'General', + message: 'msg3', + }, + senderId: senderIdEncoded, + isRead: false, + }; + await acl.setNodePerm(senderId, { + gestalt: { + notify: null, + }, + vaults: {}, }); - expect(notifs[4].data).toEqual({ - type: 'General', - message: '1', + await notificationsManager.receiveNotification(notification1); + await notificationsManager.receiveNotification(notification2); + await notificationsManager.receiveNotification(notification3); + const reversedNotifications = await notificationsManager.readNotifications({ + order: 'oldest', + }); + expect(reversedNotifications).toHaveLength(3); + expect(reversedNotifications[0].data['message']).toBe('msg1'); + expect(reversedNotifications[1].data['message']).toBe('msg2'); + expect(reversedNotifications[2].data['message']).toBe('msg3'); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); + }); + test('notifications can be capped and oldest notifications deleted', async () => { + const notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, + messageCap: 2, + logger, + }); + const notification1: Notification = { + data: { + type: 'General', + message: 'msg1', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification2: Notification = { + data: { + type: 'General', + message: 'msg2', + }, + senderId: senderIdEncoded, + isRead: false, + }; + const notification3: Notification = { + data: { + type: 'General', + message: 'msg3', + }, + senderId: senderIdEncoded, + isRead: false, + }; + await acl.setNodePerm(senderId, { + gestalt: { + notify: null, + }, + vaults: {}, }); - - await senderNotificationsManager.stop(); + await notificationsManager.receiveNotification(notification1); + await notificationsManager.receiveNotification(notification2); + await notificationsManager.receiveNotification(notification3); + const receivedNotifications = + await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(2); + expect(receivedNotifications[0].data['message']).toBe('msg3'); + expect(receivedNotifications[1].data['message']).toBe('msg2'); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); }); - - test('can send and receive Gestalt Invite notifications', async () => { - const senderNotificationsManager = + test('can find a gestalt invite notification', async () => { + const notificationsManager = await NotificationsManager.createNotificationsManager({ - acl: senderACL, - db: senderDb, - nodeConnectionManager: senderNodeConnectionManager, - nodeManager: senderNodeManager, - keyManager: senderKeyManager, + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, logger, }); - - const notificationData: NotificationData = { - type: 'GestaltInvite', + const notification: Notification = { + data: { + type: 'GestaltInvite', + }, + senderId: senderIdEncoded, + isRead: false, }; - await receiverACL.setNodePerm(senderNodeId, { + await acl.setNodePerm(senderId, { gestalt: { notify: null, }, vaults: {}, }); - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData, + await notificationsManager.receiveNotification(notification); + const receivedInvite = await notificationsManager.findGestaltInvite( + senderId, ); - const notifs = await receiverNotificationsManager.readNotifications(); - expect(notifs[0].data).toEqual(notificationData); - expect(notifs[0].senderId).toEqual(nodesUtils.encodeNodeId(senderNodeId)); - expect(notifs[0].isRead).toBeTruthy(); - - await senderNotificationsManager.stop(); + expect(receivedInvite).toEqual(notification); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); }); - - test('can send and receive Vault Share notifications', async () => { - const senderNotificationsManager = + test('clears notifications', async () => { + const notificationsManager = await NotificationsManager.createNotificationsManager({ - acl: senderACL, - db: senderDb, - nodeConnectionManager: senderNodeConnectionManager, - nodeManager: senderNodeManager, - keyManager: senderKeyManager, + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, logger, }); - - await receiverACL.setNodePerm(senderNodeId, { + const notification: Notification = { + data: { + type: 'General', + message: 'msg', + }, + senderId: senderIdEncoded, + isRead: false, + }; + await acl.setNodePerm(senderId, { gestalt: { notify: null, }, vaults: {}, }); - - const notificationData: NotificationData = { - type: 'VaultShare', - vaultId: generateVaultId().toString(), - vaultName: 'vaultName' as VaultName, - actions: { - clone: null, - pull: null, - } as VaultActions, + await notificationsManager.receiveNotification(notification); + await notificationsManager.clearNotifications(); + const receivedNotifications = + await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(0); + // Reverse side-effects + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); + }); + test('notifications are persistent across restarts', async () => { + const notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, + logger, + }); + const notification1: Notification = { + data: { + type: 'General', + message: 'msg1', + }, + senderId: senderIdEncoded, + isRead: false, }; - - await senderNotificationsManager.sendNotification( - receiverNodeId, - notificationData, - ); - const notifs = await receiverNotificationsManager.readNotifications(); - expect(notifs[0].data).toEqual(notificationData); - expect(notifs[0].senderId).toEqual(nodesUtils.encodeNodeId(senderNodeId)); - expect(notifs[0].isRead).toBeTruthy(); - - await senderNotificationsManager.stop(); + const notification2: Notification = { + data: { + type: 'General', + message: 'msg2', + }, + senderId: senderIdEncoded, + isRead: false, + }; + await acl.setNodePerm(senderId, { + gestalt: { + notify: null, + }, + vaults: {}, + }); + await notificationsManager.receiveNotification(notification1); + await notificationsManager.receiveNotification(notification2); + await notificationsManager.readNotifications({ number: 1 }); + await notificationsManager.stop(); + await notificationsManager.start(); + const unreadNotifications = await notificationsManager.readNotifications({ + unread: true, + }); + expect(unreadNotifications).toHaveLength(1); + expect(unreadNotifications[0].data).toEqual(notification1.data); + expect(unreadNotifications[0].senderId).toBe(notification1.senderId); + 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); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); + }); + test('creating fresh notifications manager', async () => { + const notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyManager, + logger, + }); + const notification: Notification = { + data: { + type: 'General', + message: 'msg', + }, + senderId: senderIdEncoded, + isRead: false, + }; + await acl.setNodePerm(senderId, { + gestalt: { + notify: null, + }, + vaults: {}, + }); + await notificationsManager.receiveNotification(notification); + await notificationsManager.stop(); + await notificationsManager.start({ fresh: true }); + const receivedNotifications = + await notificationsManager.readNotifications(); + expect(receivedNotifications).toHaveLength(0); + // Reverse side-effects + await notificationsManager.clearNotifications(); + await acl.unsetNodePerm(senderId); + await notificationsManager.stop(); }); }); diff --git a/tests/notifications/utils.test.ts b/tests/notifications/utils.test.ts index 5e2ba2808..a57e393bf 100644 --- a/tests/notifications/utils.test.ts +++ b/tests/notifications/utils.test.ts @@ -2,26 +2,24 @@ 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 { utils as idUtils } from '@matrixai/id'; - +import { IdInternal } from '@matrixai/id'; +import { sleep } from '@/utils'; import * as keysUtils from '@/keys/utils'; import * as notificationsUtils from '@/notifications/utils'; import * as notificationsErrors from '@/notifications/errors'; -import { createNotificationIdGenerator } from '@/notifications/utils'; -import { sleep } from '@/utils'; -import { makeVaultId } from '@/vaults/utils'; -import { utils as nodesUtils } from '@/nodes'; +import * as vaultsUtils from '@/vaults/utils'; +import * as nodesUtils from '@/nodes/utils'; import * as testUtils from '../utils'; describe('Notifications utils', () => { const nodeId = testUtils.generateRandomNodeId(); const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); - const vaultId = makeVaultId( - idUtils.fromString('vaultIdxxxxxxxxx'), - ).toString(); + const vaultId = vaultsUtils + .makeVaultId(IdInternal.fromString('vaultIdxxxxxxxxx')) + .toString(); test('generates notification ids', async () => { - const generator = createNotificationIdGenerator(); + const generator = notificationsUtils.createNotificationIdGenerator(); let oldId = generator(); let currentId; @@ -32,12 +30,12 @@ describe('Notifications utils', () => { } }); test('Generator maintains order between instances', async () => { - let generator = createNotificationIdGenerator(); + let generator = notificationsUtils.createNotificationIdGenerator(); let lastId = generator(); let currentId; for (let i = 0; i < 100; i++) { - generator = createNotificationIdGenerator(lastId); + generator = notificationsUtils.createNotificationIdGenerator(lastId); currentId = generator(); expect(Buffer.compare(lastId, currentId)).toBeTruthy(); lastId = currentId; diff --git a/tests/utils.ts b/tests/utils.ts index 59d9ff205..833cc1330 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,17 +1,19 @@ import type { StatusLive } from '@/status/types'; import type { NodeId } from '@/nodes/types'; +import type { Host } from '@/network/types'; import path from 'path'; import fs from 'fs'; import lock from 'fd-lock'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { IdInternal } from '@matrixai/id'; -import { PolykeyAgent } from '@'; -import { Status } from '@/status'; -import { utils as keysUtils } from '@/keys'; -import { GRPCClientClient, utils as clientUtils } from '@/client'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import { sleep } from '@/utils'; import config from '@/config'; +import PolykeyAgent from '@/PolykeyAgent'; +import Status from '@/status/Status'; +import GRPCClientClient from '@/client/GRPCClientClient'; +import * as keysUtils from '@/keys/utils'; +import * as clientUtils from '@/client/utils'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; /** * Setup the global keypair @@ -112,6 +114,13 @@ async function setupGlobalAgent( await PolykeyAgent.createPolykeyAgent({ password: globalAgentPassword, nodePath: globalAgentDir, + networkConfig: { + proxyHost: '127.0.0.1' as Host, + egressHost: '127.0.0.1' as Host, + ingressHost: '127.0.0.1' as Host, + agentHost: '127.0.0.1' as Host, + clientHost: '127.0.0.1' as Host, + }, keysConfig: { rootKeyPairBits: 2048, }, diff --git a/tests/validation/utils.nodes.test.ts b/tests/validation/utils.nodes.test.ts new file mode 100644 index 000000000..8374312ef --- /dev/null +++ b/tests/validation/utils.nodes.test.ts @@ -0,0 +1,52 @@ +import * as validationUtils from '@/validation/utils'; +import * as validationErrors from '@/validation/errors'; + +describe('nodes validationUtils', () => { + const nodeIdEncoded1 = + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'; + const nodeIdEncoded2 = + 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg'; + const nodeIdEncoded3 = + 'vi3et1hrpv2m2lrplcm7cu913kr45v51cak54vm68anlbvuf83ra0'; + const hostname = 'testnet.polykey.io'; + const hostIPv4 = '127.0.0.1'; + const hostIPv6 = '[2001:db8:85a3:8d3:1319:8a2e:370:7348]'; + const port1 = 1314; + const port2 = 1315; + const port3 = 1316; + test('parseSeedNodes - valid seed nodes (using hostname, IPv4, IPv6)', () => { + const rawSeedNodes = + `${nodeIdEncoded1}@${hostname}:${port1};` + + `${nodeIdEncoded2}@${hostIPv4}:${port2};` + + `${nodeIdEncoded3}@${hostIPv6}:${port3};`; + const parsed = validationUtils.parseSeedNodes(rawSeedNodes); + const seeds = parsed[0]; + expect(seeds[nodeIdEncoded1]).toStrictEqual({ + host: hostname, + port: port1, + }); + expect(seeds[nodeIdEncoded2]).toStrictEqual({ + host: hostIPv4, + port: port2, + }); + expect(seeds[nodeIdEncoded3]).toStrictEqual({ + host: hostIPv6.replace(/\[|\]/g, ''), + port: port3, + }); + expect(parsed[1]).toBeFalsy(); + }); + test('parseSeedNodes - invalid node ID', () => { + const rawSeedNodes = `INVALIDNODEID@${hostname}:${port1}`; + expect(() => validationUtils.parseSeedNodes(rawSeedNodes)).toThrow( + validationErrors.ErrorParse, + ); + }); + test('parseSeedNodes - invalid hostname', () => { + const rawSeedNodes = `${nodeIdEncoded1}@$invalidHost:${port1}`; + expect(() => validationUtils.parseSeedNodes(rawSeedNodes)).toThrow( + validationErrors.ErrorParse, + ); + }); + test.todo('parseSeedNodes - invalid port'); + test.todo('parseSeedNodes - invalid structure'); +}); diff --git a/tests/vaults/VaultManager.test.ts b/tests/vaults/VaultManager.test.ts index 467f04526..8235ad70e 100644 --- a/tests/vaults/VaultManager.test.ts +++ b/tests/vaults/VaultManager.test.ts @@ -814,7 +814,7 @@ describe('VaultManager', () => { await revProxy.start({ serverHost: targetHost, - serverPort: targetAgentServer.port, + serverPort: targetAgentServer.getPort(), ingressHost: targetHost, ingressPort: targetPort, tlsConfig: revTLSConfig, @@ -822,7 +822,7 @@ describe('VaultManager', () => { await altRevProxy.start({ serverHost: altHostIn, - serverPort: altAgentServer.port, + serverPort: altAgentServer.getPort(), ingressHost: altHostIn, ingressPort: altPortIn, tlsConfig: altRevTLSConfig,