diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index 188289d43..12d14c2b9 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -1,4 +1,4 @@ -import type { FileSystem } from './types'; +import type { FileSystem, PromiseDeconstructed } from './types'; import type { PolykeyWorkerManagerInterface } from './workers/types'; import type { ConnectionData, Host, Port, TLSConfig } from './network/types'; import type { SeedNodes } from './nodes/types'; @@ -10,6 +10,10 @@ import process from 'process'; import Logger from '@matrixai/logger'; import { DB } from '@matrixai/db'; import { CreateDestroyStartStop } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import RPCServer from './rpc/RPCServer'; +import WebSocketServer from './websockets/WebSocketServer'; +import * as middlewareUtils from './rpc/utils/middleware'; +import * as authMiddleware from './client/utils/authenticationMiddleware'; import { WorkerManager } from './workers'; import * as networkUtils from './network/utils'; import KeyRing from './keys/KeyRing'; @@ -32,7 +36,6 @@ import { providers } from './identities'; import Proxy from './network/Proxy'; import { EventBus, captureRejectionSymbol } from './events'; import createAgentService, { AgentServiceService } from './agent/service'; -import createClientService, { ClientServiceService } from './client/service'; import config from './config'; import * as errors from './errors'; import * as utils from './utils'; @@ -40,6 +43,7 @@ import * as keysUtils from './keys/utils'; import * as nodesUtils from './nodes/utils'; import * as workersUtils from './workers/utils'; import TaskManager from './tasks/TaskManager'; +import { serverManifest } from './client/handlers'; type NetworkConfig = { forwardHost?: Host; @@ -49,9 +53,13 @@ type NetworkConfig = { // GRPCServer for agent service agentHost?: Host; agentPort?: Port; - // GRPCServer for client service + // RPCServer for client service clientHost?: Host; clientPort?: Port; + maxReadBufferBytes?: number; + idleTimeout?: number; + pingInterval?: number; + pingTimeout?: number; }; interface PolykeyAgent extends CreateDestroyStartStop {} @@ -103,8 +111,9 @@ class PolykeyAgent { vaultManager, notificationsManager, sessionManager, - grpcServerClient, + rpcServerClient, grpcServerAgent, + webSocketServerClient, fs = require('fs'), logger = new Logger(this.name), fresh = false, @@ -156,8 +165,9 @@ class PolykeyAgent { vaultManager?: VaultManager; notificationsManager?: NotificationsManager; sessionManager?: SessionManager; - grpcServerClient?: GRPCServer; + rpcServerClient?: RPCServer; grpcServerAgent?: GRPCServer; + webSocketServerClient?: WebSocketServer; fs?: FileSystem; logger?: Logger; fresh?: boolean; @@ -183,6 +193,10 @@ class PolykeyAgent { ...config.defaults.nodeConnectionManagerConfig, ...utils.filterEmptyObject(nodeConnectionManagerConfig), }; + const _networkConfig = { + ...config.defaults.networkConfig, + ...utils.filterEmptyObject(networkConfig), + }; await utils.mkdirExists(fs, nodePath); const statusPath = path.join(nodePath, config.defaults.statusBase); const statusLockPath = path.join(nodePath, config.defaults.statusLockBase); @@ -193,6 +207,7 @@ class PolykeyAgent { const events = new EventBus({ captureRejections: true, }); + let pkAgentProm: PromiseDeconstructed | undefined; try { status = status ?? @@ -401,11 +416,56 @@ class PolykeyAgent { // If a recovery code is provided then we reset any sessions in case the // password changed. if (keyRingConfig.recoveryCode != null) await sessionManager.resetKey(); - grpcServerClient = - grpcServerClient ?? - new GRPCServer({ - logger: logger.getChild(GRPCServer.name + 'Client'), + if (rpcServerClient == null) { + pkAgentProm = utils.promise(); + rpcServerClient = await RPCServer.createRPCServer({ + manifest: serverManifest({ + acl: acl, + certManager: certManager, + db: db, + discovery: discovery, + fs: fs, + gestaltGraph: gestaltGraph, + identitiesManager: identitiesManager, + keyRing: keyRing, + logger: logger, + nodeConnectionManager: nodeConnectionManager, + nodeGraph: nodeGraph, + nodeManager: nodeManager, + notificationsManager: notificationsManager, + pkAgentProm: pkAgentProm.p, + sessionManager: sessionManager, + vaultManager: vaultManager, + }), + middlewareFactory: middlewareUtils.defaultServerMiddlewareWrapper( + authMiddleware.authenticationMiddlewareServer( + sessionManager, + keyRing, + ), + ), + sensitive: false, + logger: logger.getChild('RPCServerClient'), }); + } + const tlsConfig: TLSConfig = { + keyPrivatePem: keysUtils.privateKeyToPEM(keyRing.keyPair.privateKey), + certChainPem: await certManager.getCertPEMsChainPEM(), + }; + webSocketServerClient = + webSocketServerClient ?? + (await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair) => + rpcServerClient!.handleStream(streamPair, {}), + fs, + host: _networkConfig.clientHost, + port: _networkConfig.clientPort, + tlsConfig, + maxReadBufferBytes: _networkConfig.maxReadBufferBytes, + idleTimeout: _networkConfig.idleTimeout, + pingInterval: _networkConfig.pingInterval, + pingTimeout: _networkConfig.pingTimeout, + logger: logger.getChild('WebSocketServer'), + })); grpcServerAgent = grpcServerAgent ?? new GRPCServer({ @@ -413,6 +473,8 @@ class PolykeyAgent { }); } catch (e) { logger.warn(`Failed Creating ${this.name}`); + await rpcServerClient?.destroy(); + await webSocketServerClient?.stop(true); await sessionManager?.stop(); await notificationsManager?.stop(); await vaultManager?.stop(); @@ -450,12 +512,14 @@ class PolykeyAgent { vaultManager, notificationsManager, sessionManager, + rpcServerClient, grpcServerAgent, - grpcServerClient, + webSocketServerClient, events, fs, logger, }); + pkAgentProm?.resolveP(pkAgent); await pkAgent.start({ password, networkConfig, @@ -486,10 +550,11 @@ class PolykeyAgent { public readonly notificationsManager: NotificationsManager; public readonly sessionManager: SessionManager; public readonly grpcServerAgent: GRPCServer; - public readonly grpcServerClient: GRPCServer; public readonly events: EventBus; public readonly fs: FileSystem; public readonly logger: Logger; + public readonly rpcServerClient: RPCServer; + public readonly webSocketServerClient: WebSocketServer; protected workerManager: PolykeyWorkerManagerInterface | undefined; constructor({ @@ -512,8 +577,9 @@ class PolykeyAgent { vaultManager, notificationsManager, sessionManager, - grpcServerClient, + rpcServerClient, grpcServerAgent, + webSocketServerClient, events, fs, logger, @@ -537,8 +603,9 @@ class PolykeyAgent { vaultManager: VaultManager; notificationsManager: NotificationsManager; sessionManager: SessionManager; - grpcServerClient: GRPCServer; + rpcServerClient: RPCServer; grpcServerAgent: GRPCServer; + webSocketServerClient: WebSocketServer; events: EventBus; fs: FileSystem; logger: Logger; @@ -563,7 +630,8 @@ class PolykeyAgent { this.vaultManager = vaultManager; this.notificationsManager = notificationsManager; this.sessionManager = sessionManager; - this.grpcServerClient = grpcServerClient; + this.rpcServerClient = rpcServerClient; + this.webSocketServerClient = webSocketServerClient; this.grpcServerAgent = grpcServerAgent; this.events = events; this.fs = fs; @@ -614,7 +682,8 @@ class PolykeyAgent { keyPrivatePem: keysUtils.privateKeyToPEM(data.keyPair.privateKey), certChainPem: await this.certManager.getCertPEMsChainPEM(), }; - this.grpcServerClient.setTLSConfig(tlsConfig); + // FIXME: Can we even support updating TLS config anymore? + // this.grpcServerClient.setTLSConfig(tlsConfig); this.proxy.setTLSConfig(tlsConfig); this.logger.info(`${KeyRing.name} change propagated`); }, @@ -641,7 +710,7 @@ class PolykeyAgent { } }, ); - const networkConfig_ = { + const _networkConfig = { ...config.defaults.networkConfig, ...utils.filterEmptyObject(networkConfig), }; @@ -661,28 +730,6 @@ class PolykeyAgent { proxy: this.proxy, logger: this.logger.getChild('GRPCClientAgentService'), }); - const clientService = createClientService({ - pkAgent: this, - db: this.db, - certManager: this.certManager, - discovery: this.discovery, - gestaltGraph: this.gestaltGraph, - identitiesManager: this.identitiesManager, - keyRing: this.keyRing, - nodeGraph: this.nodeGraph, - nodeConnectionManager: this.nodeConnectionManager, - nodeManager: this.nodeManager, - notificationsManager: this.notificationsManager, - sessionManager: this.sessionManager, - vaultManager: this.vaultManager, - sigchain: this.sigchain, - acl: this.acl, - grpcServerClient: this.grpcServerClient, - grpcServerAgent: this.grpcServerAgent, - proxy: this.proxy, - fs: this.fs, - logger: this.logger.getChild('GRPCClientClientService'), - }); // Starting modules await this.keyRing.start({ password, @@ -726,25 +773,26 @@ class PolykeyAgent { certChainPem: await this.certManager.getCertPEMsChainPEM(), }; // Client server - await this.grpcServerClient.start({ - services: [[ClientServiceService, clientService]], - host: networkConfig_.clientHost, - port: networkConfig_.clientPort, + await this.webSocketServerClient.start({ tlsConfig, + host: _networkConfig.clientHost, + port: _networkConfig.clientPort, + connectionCallback: (streamPair) => + this.rpcServerClient.handleStream(streamPair, {}), }); // Agent server await this.grpcServerAgent.start({ services: [[AgentServiceService, agentService]], - host: networkConfig_.agentHost, - port: networkConfig_.agentPort, + host: _networkConfig.agentHost, + port: _networkConfig.agentPort, }); await this.proxy.start({ - forwardHost: networkConfig_.forwardHost, - forwardPort: networkConfig_.forwardPort, + forwardHost: _networkConfig.forwardHost, + forwardPort: _networkConfig.forwardPort, serverHost: this.grpcServerAgent.getHost(), serverPort: this.grpcServerAgent.getPort(), - proxyHost: networkConfig_.proxyHost, - proxyPort: networkConfig_.proxyPort, + proxyHost: _networkConfig.proxyHost, + proxyPort: _networkConfig.proxyPort, tlsConfig, }); await this.nodeManager.start(); @@ -768,10 +816,10 @@ class PolykeyAgent { await this.status.finishStart({ pid: process.pid, nodeId: this.keyRing.getNodeId(), - clientHost: this.grpcServerClient.getHost(), - clientPort: this.grpcServerClient.getPort(), + clientHost: this.webSocketServerClient.getHost(), + clientPort: this.webSocketServerClient.getPort(), agentHost: this.grpcServerAgent.getHost(), - agentPort: this.grpcServerClient.getPort(), + agentPort: this.grpcServerAgent.getPort(), forwardHost: this.proxy.getForwardHost(), forwardPort: this.proxy.getForwardPort(), proxyHost: this.proxy.getProxyHost(), @@ -793,7 +841,7 @@ class PolykeyAgent { await this.nodeManager?.stop(); await this.proxy?.stop(); await this.grpcServerAgent?.stop(); - await this.grpcServerClient?.stop(); + await this.webSocketServerClient.stop(true); await this.identitiesManager?.stop(); await this.gestaltGraph?.stop(); await this.acl?.stop(); @@ -829,7 +877,7 @@ class PolykeyAgent { await this.nodeManager.stop(); await this.proxy.stop(); await this.grpcServerAgent.stop(); - await this.grpcServerClient.stop(); + await this.webSocketServerClient.stop(true); await this.identitiesManager.stop(); await this.gestaltGraph.stop(); await this.acl.stop(); @@ -877,6 +925,7 @@ class PolykeyAgent { await this.vaultManager.destroy(); await this.discovery.destroy(); await this.nodeGraph.destroy(); + await this.rpcServerClient.destroy(); await this.identitiesManager.destroy(); await this.gestaltGraph.destroy(); await this.acl.destroy(); diff --git a/src/PolykeyClient.ts b/src/PolykeyClient.ts index cdfd750fd..d3015b560 100644 --- a/src/PolykeyClient.ts +++ b/src/PolykeyClient.ts @@ -1,49 +1,46 @@ -import type { FileSystem, Timer } from './types'; -import type { NodeId } from './ids/types'; -import type { Host, Port } from './network/types'; +import type { FileSystem } from './types'; +import type { ClientManifest, StreamFactory } from './rpc/types'; import path from 'path'; import Logger from '@matrixai/logger'; import { CreateDestroyStartStop } from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import RPCClient from './rpc/RPCClient'; +import * as middlewareUtils from './rpc/utils/middleware'; +import * as authMiddleware from './client/utils/authenticationMiddleware'; import { Session } from './sessions'; -import { GRPCClientClient } from './client'; import * as errors from './errors'; import * as utils from './utils'; import config from './config'; /** * This PolykeyClient would create a new PolykeyClient object that constructs - * a new GRPCClientClient which attempts to connect to an existing PolykeyAgent's + * a new RPCClient which attempts to connect to an existing PolykeyAgent's * grpc server. */ -interface PolykeyClient extends CreateDestroyStartStop {} +// eslint-disable-next-line @typescript-eslint/no-unused-vars -- False positive for M +interface PolykeyClient + extends CreateDestroyStartStop {} @CreateDestroyStartStop( new errors.ErrorPolykeyClientRunning(), new errors.ErrorPolykeyClientDestroyed(), ) -class PolykeyClient { - static async createPolykeyClient({ - nodeId, - host, - port, +class PolykeyClient { + static async createPolykeyClient({ nodePath = config.defaults.nodePath, session, - grpcClient, - timer, + manifest, + streamFactory, fs = require('fs'), logger = new Logger(this.name), fresh = false, }: { - nodeId: NodeId; - host: Host; - port: Port; nodePath?: string; - timer?: Timer; session?: Session; - grpcClient?: GRPCClientClient; + manifest: RPCClient | M; + streamFactory: StreamFactory; fs?: FileSystem; logger?: Logger; fresh?: boolean; - }): Promise { + }): Promise> { logger.info(`Creating ${this.name}`); if (nodePath == null) { throw new errors.ErrorUtilsNodePath(); @@ -57,20 +54,20 @@ class PolykeyClient { logger: logger.getChild(Session.name), fresh, })); - grpcClient = - grpcClient ?? - (await GRPCClientClient.createGRPCClientClient({ - nodeId, - host, - port, - tlsConfig: { keyPrivatePem: undefined, certChainPem: undefined }, - session, - timer, - logger: logger.getChild(GRPCClientClient.name), - })); + const rpcClient = + manifest instanceof RPCClient + ? manifest + : await RPCClient.createRPCClient({ + manifest, + streamFactory, + middlewareFactory: middlewareUtils.defaultClientMiddlewareWrapper( + authMiddleware.authenticationMiddlewareClient(session), + ), + logger: logger.getChild(RPCClient.name), + }); const pkClient = new this({ nodePath, - grpcClient, + rpcClient, session, fs, logger, @@ -82,20 +79,20 @@ class PolykeyClient { public readonly nodePath: string; public readonly session: Session; - public readonly grpcClient: GRPCClientClient; + public readonly rpcClient: RPCClient; protected fs: FileSystem; protected logger: Logger; constructor({ nodePath, + rpcClient, session, - grpcClient, fs, logger, }: { nodePath: string; - grpcClient: GRPCClientClient; + rpcClient: RPCClient; session: Session; fs: FileSystem; logger: Logger; @@ -103,7 +100,7 @@ class PolykeyClient { this.logger = logger; this.nodePath = nodePath; this.session = session; - this.grpcClient = grpcClient; + this.rpcClient = rpcClient; this.fs = fs; } @@ -114,13 +111,13 @@ class PolykeyClient { public async stop() { this.logger.info(`Stopping ${this.constructor.name}`); - await this.grpcClient.destroy(); await this.session.stop(); this.logger.info(`Stopped ${this.constructor.name}`); } public async destroy() { this.logger.info(`Destroying ${this.constructor.name}`); + await this.rpcClient.destroy(); await this.session.destroy(); this.logger.info(`Destroyed ${this.constructor.name}`); } diff --git a/src/RPC/errors.ts b/src/RPC/errors.ts deleted file mode 100644 index c434efdb8..000000000 --- a/src/RPC/errors.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ErrorPolykey, sysexits } from '../errors'; - -class ErrorRpc extends ErrorPolykey {} - -class ErrorRpcDestroyed extends ErrorRpc { - static description = 'Rpc is destroyed'; - exitCode = sysexits.USAGE; -} - -class ErrorRpcStopping extends ErrorRpc { - static description = 'Rpc is stopping'; - exitCode = sysexits.USAGE; -} - -class ErrorRpcParse extends ErrorRpc { - static description = 'Failed to parse Buffer stream'; - exitCode = sysexits.SOFTWARE; -} - -/** - * This is an internal error, it should not reach the top level. - */ -class ErrorRpcHandlerFailed extends ErrorRpc { - static description = 'Failed to handle stream'; - exitCode = sysexits.SOFTWARE; -} - -class ErrorRpcMessageLength extends ErrorRpc { - static description = 'RPC Message exceeds maximum size'; - exitCode = sysexits.DATAERR; -} - -class ErrorRpcRemoteError extends ErrorRpc { - static description = 'RPC Message exceeds maximum size'; - exitCode = sysexits.UNAVAILABLE; -} - -class ErrorRpcNoMessageError extends ErrorRpc { - static description = 'For errors not to be conveyed to the client'; -} - -class ErrorRpcPlaceholderConnectionError extends ErrorRpcNoMessageError { - static description = 'placeholder error for connection stream failure'; - exitCode = sysexits.UNAVAILABLE; -} - -export { - ErrorRpc, - ErrorRpcDestroyed, - ErrorRpcStopping, - ErrorRpcParse, - ErrorRpcHandlerFailed, - ErrorRpcMessageLength, - ErrorRpcRemoteError, - ErrorRpcNoMessageError, - ErrorRpcPlaceholderConnectionError, -}; diff --git a/src/RPC/index.ts b/src/RPC/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/RPC/middleware.ts b/src/RPC/middleware.ts deleted file mode 100644 index 0f3150d83..000000000 --- a/src/RPC/middleware.ts +++ /dev/null @@ -1,158 +0,0 @@ -import type { - JsonRpcMessage, - JsonRpcRequest, - JsonRpcResponse, - JsonRpcResponseResult, - MiddlewareFactory, -} from './types'; -import { TransformStream } from 'stream/web'; -import * as rpcErrors from './errors'; -import * as rpcUtils from './utils'; -const jsonStreamParsers = require('@streamparser/json'); - -function binaryToJsonMessageStream( - messageParser: (message: unknown) => T, - byteLimit: number = 1024 * 1024, - firstMessage?: T, -) { - const parser = new jsonStreamParsers.JSONParser({ - separator: '', - paths: ['$'], - }); - let bytesWritten: number = 0; - - return new TransformStream({ - start: (controller) => { - if (firstMessage != null) controller.enqueue(firstMessage); - parser.onValue = (value) => { - const jsonMessage = messageParser(value.value); - controller.enqueue(jsonMessage); - bytesWritten = 0; - }; - }, - transform: (chunk) => { - try { - bytesWritten += chunk.byteLength; - parser.write(chunk); - } catch (e) { - throw new rpcErrors.ErrorRpcParse(undefined, { cause: e }); - } - if (bytesWritten > byteLimit) { - throw new rpcErrors.ErrorRpcMessageLength(); - } - }, - }); -} - -function jsonMessageToBinaryStream() { - return new TransformStream({ - transform: (chunk, controller) => { - controller.enqueue(Buffer.from(JSON.stringify(chunk))); - }, - }); -} - -const defaultMiddleware: MiddlewareFactory< - JsonRpcRequest, - JsonRpcRequest, - JsonRpcResponse, - JsonRpcResponse -> = () => { - return { - forward: new TransformStream(), - reverse: new TransformStream(), - }; -}; - -const defaultServerMiddlewareWrapper = ( - middleware: MiddlewareFactory< - JsonRpcRequest, - JsonRpcRequest, - JsonRpcResponse, - JsonRpcResponse - > = defaultMiddleware, -) => { - return (header: JsonRpcRequest) => { - const inputTransformStream = binaryToJsonMessageStream( - rpcUtils.parseJsonRpcRequest, - undefined, - header, - ); - const outputTransformStream = new TransformStream< - JsonRpcResponseResult, - JsonRpcResponseResult - >(); - - const middleMiddleware = middleware(header); - - const forwardReadable = inputTransformStream.readable.pipeThrough( - middleMiddleware.forward, - ); // Usual middleware here - const reverseReadable = outputTransformStream.readable - .pipeThrough(middleMiddleware.reverse) // Usual middleware here - .pipeThrough(jsonMessageToBinaryStream()); - - return { - forward: { - readable: forwardReadable, - writable: inputTransformStream.writable, - }, - reverse: { - readable: reverseReadable, - writable: outputTransformStream.writable, - }, - }; - }; -}; - -const defaultClientMiddlewareWrapper = ( - middleware: MiddlewareFactory< - JsonRpcRequest, - JsonRpcRequest, - JsonRpcResponse, - JsonRpcResponse - > = defaultMiddleware, -): MiddlewareFactory< - Uint8Array, - JsonRpcRequest, - JsonRpcResponse, - Uint8Array -> => { - return () => { - const outputTransformStream = binaryToJsonMessageStream( - rpcUtils.parseJsonRpcResponse, - undefined, - ); - const inputTransformStream = new TransformStream< - JsonRpcRequest, - JsonRpcRequest - >(); - - const middleMiddleware = middleware(); - const forwardReadable = inputTransformStream.readable - .pipeThrough(middleMiddleware.forward) // Usual middleware here - .pipeThrough(jsonMessageToBinaryStream()); - const reverseReadable = outputTransformStream.readable.pipeThrough( - middleMiddleware.reverse, - ); // Usual middleware here - - return { - forward: { - readable: forwardReadable, - writable: inputTransformStream.writable, - }, - reverse: { - readable: reverseReadable, - writable: outputTransformStream.writable, - }, - }; - }; -}; - -export { - binaryToJsonMessageStream, - jsonMessageToBinaryStream, - defaultMiddleware, - defaultServerMiddlewareWrapper, - defaultClientMiddlewareWrapper, -}; diff --git a/src/bin/agent/CommandLockAll.ts b/src/bin/agent/CommandLockAll.ts index 865438286..6d6dd325c 100644 --- a/src/bin/agent/CommandLockAll.ts +++ b/src/bin/agent/CommandLockAll.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import path from 'path'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -16,9 +17,13 @@ class CommandLockAll extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); const { default: Session } = await import('../../sessions/Session'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); - + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -27,7 +32,7 @@ class CommandLockAll extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); @@ -39,27 +44,37 @@ class CommandLockAll extends CommandPolykey { fs: this.fs, logger: this.logger.getChild(Session.name), }); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.agentLockAll(emptyMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.agentLockAll({ + metadata: auth, + }), + auth, ); // Destroy local session await session.destroy(); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/agent/CommandStart.ts b/src/bin/agent/CommandStart.ts index 92a937924..b69494789 100644 --- a/src/bin/agent/CommandStart.ts +++ b/src/bin/agent/CommandStart.ts @@ -227,8 +227,8 @@ class CommandStart extends CommandPolykey { statusLiveData = { pid: process.pid, nodeId: nodesUtils.encodeNodeId(pkAgent.keyRing.getNodeId()), - clientHost: pkAgent.grpcServerClient.getHost(), - clientPort: pkAgent.grpcServerClient.getPort(), + clientHost: pkAgent.webSocketServerClient.getHost(), + clientPort: pkAgent.webSocketServerClient.getPort(), agentHost: pkAgent.grpcServerAgent.getHost(), agentPort: pkAgent.grpcServerAgent.getPort(), proxyHost: pkAgent.proxy.getProxyHost(), diff --git a/src/bin/agent/CommandStatus.ts b/src/bin/agent/CommandStatus.ts index 1f04c406d..b28230ca7 100644 --- a/src/bin/agent/CommandStatus.ts +++ b/src/bin/agent/CommandStatus.ts @@ -1,5 +1,6 @@ import type PolykeyClient from '../../PolykeyClient'; -import type * as agentPB from '../../proto/js/polykey/v1/agent/agent_pb'; +import type WebSocketClient from '../../websockets/WebSocketClient'; +import type { StatusResultMessage } from '../../client/handlers/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,7 +16,12 @@ class CommandStatus extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientStatus = await binProcessors.processClientStatus( options.nodePath, options.nodeId, @@ -38,60 +44,58 @@ class CommandStatus extends CommandPolykey { }), ); } else { - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); - let response: agentPB.InfoMessage; + let response: StatusResultMessage; try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodeId: clientStatus.nodeId!, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientStatus.nodeId!], host: clientStatus.clientHost!, port: clientStatus.clientPort!, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.agentStatus(emptyMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.agentStatus({ + metadata: auth, + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } - const pid = response.getPid(); - const nodeId = response.getNodeId(); - const clientHost = response.getClientHost(); - const clientPort = response.getClientPort(); - const proxyHost = response.getProxyHost(); - const proxyPort = response.getProxyPort(); - const agentHost = response.getAgentHost(); - const agentPort = response.getAgentPort(); - const forwardHost = response.getForwardHost(); - const forwardPort = response.getForwardPort(); - const publicKeyJWK = response.getPublicKeyJwk(); - const certChainPEM = response.getCertChainPem(); process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'dict', data: { status: 'LIVE', - pid, - nodeId, - clientHost, - clientPort, - proxyHost, - proxyPort, - agentHost, - agentPort, - forwardHost, - forwardPort, - publicKeyJWK, - certChainPEM, + pid: response.pid, + nodeId: response.nodeIdEncoded, + clientHost: response.clientHost, + clientPort: response.clientPort, + proxyHost: response.proxyHost, + proxyPort: response.proxyPort, + agentHost: response.agentHost, + agentPort: response.agentPort, + forwardHost: response.forwardHost, + forwardPort: response.forwardPort, + publicKeyJWK: response.publicKeyJwk, + certChainPEM: response.certChainPEM, }, }), ); diff --git a/src/bin/agent/CommandStop.ts b/src/bin/agent/CommandStop.ts index fe4f8552f..db006b7cd 100644 --- a/src/bin/agent/CommandStop.ts +++ b/src/bin/agent/CommandStop.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,7 +16,12 @@ class CommandStop extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientStatus = await binProcessors.processClientStatus( options.nodePath, options.nodeId, @@ -34,32 +40,42 @@ class CommandStop extends CommandPolykey { } else if (statusInfo?.status === 'STARTING') { throw new binErrors.ErrorCLIPolykeyAgentStatus('agent is starting'); } - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); // Either the statusInfo is undefined or LIVE // Either way, the connection parameters now exist - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientStatus.nodeId!, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientStatus.nodeId!], host: clientStatus.clientHost!, port: clientStatus.clientPort!, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.agentStop(emptyMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.agentStop({ + metadata: auth, + }), + auth, ); this.logger.info('Stopping Agent'); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/agent/CommandUnlock.ts b/src/bin/agent/CommandUnlock.ts index b8804d9e6..ca0fe551f 100644 --- a/src/bin/agent/CommandUnlock.ts +++ b/src/bin/agent/CommandUnlock.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -14,7 +15,12 @@ class CommandUnlock extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -23,29 +29,39 @@ class CommandUnlock extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.agentUnlock(emptyMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.agentUnlock({ + metadata: auth, + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandAllow.ts b/src/bin/identities/CommandAllow.ts index c61417ea0..10a8230af 100644 --- a/src/bin/identities/CommandAllow.ts +++ b/src/bin/identities/CommandAllow.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -24,15 +25,14 @@ class CommandAllow extends CommandPolykey { this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (gestaltId: GestaltId, permissions, options) => { + this.action(async (gestaltId: GestaltId, permission, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const permissionsPB = await import( - '../../proto/js/polykey/v1/permissions/permissions_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const utils = await import('../../utils'); const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -43,57 +43,57 @@ class CommandAllow extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const setActionMessage = new permissionsPB.ActionSet(); - setActionMessage.setAction(permissions); const [type, id] = gestaltId; switch (type) { case 'node': { - // Setting by Node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - setActionMessage.setNode(nodeMessage); // Trusting await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsSetByNode( - setActionMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsActionsSetByNode({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(id), + action: permission, + }), + auth, ); } break; case 'identity': { // Setting By Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - setActionMessage.setIdentity(providerMessage); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsSetByIdentity( - setActionMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsActionsSetByIdentity({ + metadata: auth, + providerId: id[0], + identityId: id[1], + action: permission, + }), + auth, ); } break; @@ -102,6 +102,7 @@ class CommandAllow extends CommandPolykey { } } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandAuthenticate.ts b/src/bin/identities/CommandAuthenticate.ts index d52fc2326..fc9f3ea7a 100644 --- a/src/bin/identities/CommandAuthenticate.ts +++ b/src/bin/identities/CommandAuthenticate.ts @@ -1,4 +1,8 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; +import type { ClientRPCResponseResult } from '../../client/types'; +import type { AuthProcessMessage } from '../../client/handlers/types'; +import type { ReadableStream } from 'stream/web'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -21,9 +25,13 @@ class CommandAuthenticate extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (providerId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); + const { never } = await import('../../utils'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -32,73 +40,73 @@ class CommandAuthenticate extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; - let genReadable: ReturnType< - typeof pkClient.grpcClient.identitiesAuthenticate - >; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { - if (genReadable != null) genReadable.stream.cancel(); if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(providerId); + let genReadable: ReadableStream< + ClientRPCResponseResult + >; await binUtils.retryAuthentication(async (auth) => { - genReadable = pkClient.grpcClient.identitiesAuthenticate( - providerMessage, - auth, + genReadable = await pkClient.rpcClient.methods.identitiesAuthenticate( + { + metadata: auth, + providerId: providerId, + }, ); for await (const message of genReadable) { - switch (message.getStepCase()) { - case identitiesPB.AuthenticationProcess.StepCase.REQUEST: { - const authRequest = message.getRequest()!; - this.logger.info( - `Navigate to the URL in order to authenticate`, - ); - this.logger.info( - 'Use any additional additional properties to complete authentication', - ); - identitiesUtils.browser(authRequest.getUrl()); - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'dict', - data: { - url: authRequest.getUrl(), - ...Object.fromEntries(authRequest.getDataMap().entries()), - }, - }), - ); - break; - } - case identitiesPB.AuthenticationProcess.StepCase.RESPONSE: { - const authResponse = message.getResponse()!; - this.logger.info( - `Authenticated digital identity provider ${providerId}`, - ); - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [authResponse.getIdentityId()], - }), - ); - break; - } + if (message.request != null) { + this.logger.info(`Navigate to the URL in order to authenticate`); + this.logger.info( + 'Use any additional additional properties to complete authentication', + ); + identitiesUtils.browser(message.request.url); + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'dict', + data: { + url: message.request.url, + ...message.request.dataMap, + }, + }), + ); + } else if (message.response != null) { + this.logger.info( + `Authenticated digital identity provider ${providerId}`, + ); + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'list', + data: [message.response.identityId], + }), + ); + } else { + never(); } } - }, meta); + }, auth); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandAuthenticated.ts b/src/bin/identities/CommandAuthenticated.ts index a5f9006de..e92280a44 100644 --- a/src/bin/identities/CommandAuthenticated.ts +++ b/src/bin/identities/CommandAuthenticated.ts @@ -1,5 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; -import type { IdentityId, ProviderId } from '../../identities/types'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -21,8 +21,11 @@ class CommandAuthenticated extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -32,39 +35,39 @@ class CommandAuthenticated extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; - let genReadable: ReturnType< - typeof pkClient.grpcClient.identitiesAuthenticatedGet - >; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { - if (genReadable != null) genReadable.stream.cancel(); if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const optionalProviderMessage = new identitiesPB.OptionalProvider(); - if (options.providerId) { - optionalProviderMessage.setProviderId(options.providerId); - } await binUtils.retryAuthentication(async (auth) => { - const genReadable = pkClient.grpcClient.identitiesAuthenticatedGet( - optionalProviderMessage, - auth, - ); - for await (const val of genReadable) { + const readableStream = + await pkClient.rpcClient.methods.identitiesAuthenticatedGet({ + metadata: auth, + providerId: options.providerId, + }); + for await (const identityMessage of readableStream) { const output = { - providerId: val.getProviderId() as ProviderId, - identityId: val.getIdentityId() as IdentityId, + providerId: identityMessage.providerId, + identityId: identityMessage.identityId, }; process.stdout.write( binUtils.outputFormatter({ @@ -73,9 +76,10 @@ class CommandAuthenticated extends CommandPolykey { }), ); } - }, meta); + }, auth); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandClaim.ts b/src/bin/identities/CommandClaim.ts index 377c9a86d..5dc7ba533 100644 --- a/src/bin/identities/CommandClaim.ts +++ b/src/bin/identities/CommandClaim.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -25,8 +26,11 @@ class CommandClaim extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (providerId, identityId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -36,32 +40,41 @@ class CommandClaim extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(providerId); - providerMessage.setIdentityId(identityId); const claimMessage = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.identitiesClaim(providerMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.identitiesClaim({ + metadata: auth, + providerId: providerId, + identityId: identityId, + }), + auth, ); - const output = [`Claim Id: ${claimMessage.getClaimId()}`]; - if (claimMessage.getUrl()) { - output.push(`Url: ${claimMessage.getUrl()}`); + const output = [`Claim Id: ${claimMessage.claimId}`]; + if (claimMessage.url) { + output.push(`Url: ${claimMessage.url}`); } process.stdout.write( binUtils.outputFormatter({ @@ -71,6 +84,7 @@ class CommandClaim extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandDisallow.ts b/src/bin/identities/CommandDisallow.ts index 1b2a30bcd..8634ab7ef 100644 --- a/src/bin/identities/CommandDisallow.ts +++ b/src/bin/identities/CommandDisallow.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; @@ -24,15 +25,14 @@ class CommandDisallow extends CommandPolykey { this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (gestaltId: GestaltId, permissions, options) => { + this.action(async (gestaltId: GestaltId, permission, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const permissionsPB = await import( - '../../proto/js/polykey/v1/permissions/permissions_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const utils = await import('../../utils'); const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -43,58 +43,57 @@ class CommandDisallow extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const setActionMessage = new permissionsPB.ActionSet(); - setActionMessage.setAction(permissions); const [type, id] = gestaltId; switch (type) { case 'node': { - // Setting by Node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - setActionMessage.setNode(nodeMessage); // Trusting await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsUnsetByNode( - setActionMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsActionsUnsetByNode({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(id), + action: permission, + }), + auth, ); } break; case 'identity': { - // Setting by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - setActionMessage.setIdentity(providerMessage); // Trusting. await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsUnsetByIdentity( - setActionMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsActionsUnsetByIdentity({ + metadata: auth, + providerId: id[0], + identityId: id[1], + action: permission, + }), + auth, ); } break; @@ -103,6 +102,7 @@ class CommandDisallow extends CommandPolykey { } } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandDiscover.ts b/src/bin/identities/CommandDiscover.ts index e115eca0c..12f869bba 100644 --- a/src/bin/identities/CommandDiscover.ts +++ b/src/bin/identities/CommandDiscover.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; @@ -21,10 +22,12 @@ class CommandDiscover extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const utils = await import('../../utils'); const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -35,20 +38,27 @@ class CommandDiscover extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); const [type, id] = gestaltId; @@ -56,31 +66,27 @@ class CommandDiscover extends CommandPolykey { case 'node': { // Discovery by Node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsDiscoveryByNode( - nodeMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsDiscoveryByNode({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(id), + }), + auth, ); } break; case 'identity': { // Discovery by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsDiscoveryByIdentity( - providerMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsDiscoveryByIdentity({ + metadata: auth, + providerId: id[0], + identityId: id[1], + }), + auth, ); } break; @@ -89,6 +95,7 @@ class CommandDiscover extends CommandPolykey { } } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandGet.ts b/src/bin/identities/CommandGet.ts index 2b464f594..27908354e 100644 --- a/src/bin/identities/CommandGet.ts +++ b/src/bin/identities/CommandGet.ts @@ -1,6 +1,7 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { GestaltId } from '../../gestalts/types'; -import type gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; +import type { GestaltMessage } from '../../client/handlers/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -24,10 +25,12 @@ class CommandGet extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const utils = await import('../../utils'); const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -38,60 +41,63 @@ class CommandGet extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - let res: gestaltsPB.Graph | null = null; + let res: GestaltMessage | null = null; const [type, id] = gestaltId; switch (type) { case 'node': { // Getting from node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); res = await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsGestaltGetByNode( - nodeMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsGestaltGetByNode({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(id), + }), + auth, ); } break; case 'identity': { // Getting from identity. - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); res = await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsGestaltGetByIdentity( - providerMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsGestaltGetByIdentity({ + metadata: auth, + providerId: id[0], + identityId: id[1], + }), + auth, ); } break; default: utils.never(); } - const gestalt = JSON.parse(res!.getGestaltGraph()); + const gestalt = res!.gestalt; let output: any = gestalt; if (options.format !== 'json') { // Creating a list. @@ -115,6 +121,7 @@ class CommandGet extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandInvite.ts b/src/bin/identities/CommandInvite.ts index 205166b08..f24294e85 100644 --- a/src/bin/identities/CommandInvite.ts +++ b/src/bin/identities/CommandInvite.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -21,8 +22,13 @@ class CommandClaim extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const nodesUtils = await import('../../nodes/utils'); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -31,28 +37,36 @@ class CommandClaim extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const nodeClaimMessage = new nodesPB.Claim(); - nodeClaimMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.identitiesInvite(nodeClaimMessage, auth), - meta, + pkClient.rpcClient.methods.identitiesInvite({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + }), + auth, ); process.stdout.write( binUtils.outputFormatter({ @@ -66,6 +80,7 @@ class CommandClaim extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandList.ts b/src/bin/identities/CommandList.ts index ca578ca84..0ee1cee9a 100644 --- a/src/bin/identities/CommandList.ts +++ b/src/bin/identities/CommandList.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -14,8 +15,12 @@ class CommandList extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -24,32 +29,37 @@ class CommandList extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); let output: any; const gestalts = await binUtils.retryAuthentication(async (auth) => { const gestalts: Array = []; - const stream = pkClient.grpcClient.gestaltsGestaltList( - emptyMessage, - auth, - ); - for await (const val of stream) { - const gestalt = JSON.parse(val.getName()); + const stream = await pkClient.rpcClient.methods.gestaltsGestaltList({ + metadata: auth, + }); + for await (const gestaltMessage of stream) { + const gestalt = gestaltMessage.gestalt; const newGestalt: any = { permissions: [], nodes: [], @@ -67,20 +77,21 @@ class CommandList extends CommandPolykey { }); } // Getting the permissions for the gestalt. - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(newGestalt.nodes[0].nodeId); const actionsMessage = await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsGetByNode(nodeMessage, auth), - meta, + pkClient.rpcClient.methods.gestaltsActionsGetByNode({ + metadata: auth, + nodeIdEncoded: newGestalt.nodes[0].nodeId, + }), + auth, ); - const actionList = actionsMessage.getActionList(); + const actionList = actionsMessage.actionsList; if (actionList.length === 0) newGestalt.permissions = null; else newGestalt.permissions = actionList; gestalts.push(newGestalt); } return gestalts; - }, meta); + }, auth); output = gestalts; if (options.format !== 'json') { // Convert to a human-readable list. @@ -109,6 +120,7 @@ class CommandList extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandPermissions.ts b/src/bin/identities/CommandPermissions.ts index 5d3edf03a..e2ef770f9 100644 --- a/src/bin/identities/CommandPermissions.ts +++ b/src/bin/identities/CommandPermissions.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; @@ -21,10 +22,12 @@ class CommandPermissions extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const utils = await import('../../utils'); const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -35,20 +38,27 @@ class CommandPermissions extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); const [type, id] = gestaltId; @@ -57,34 +67,30 @@ class CommandPermissions extends CommandPolykey { case 'node': { // Getting by Node - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); const res = await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsGetByNode( - nodeMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsActionsGetByNode({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(id), + }), + auth, ); - actions = res.getActionList(); + actions = res.actionsList; } break; case 'identity': { // Getting by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); const res = await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsGetByIdentity( - providerMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsActionsGetByIdentity({ + metadata: auth, + providerId: id[0], + identityId: id[1], + }), + auth, ); - actions = res.getActionList(); + actions = res.actionsList; } break; default: @@ -100,6 +106,7 @@ class CommandPermissions extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandSearch.ts b/src/bin/identities/CommandSearch.ts index 0b0e82aea..d8d60b8ad 100644 --- a/src/bin/identities/CommandSearch.ts +++ b/src/bin/identities/CommandSearch.ts @@ -1,5 +1,8 @@ import type PolykeyClient from '../../PolykeyClient'; -import type { IdentityId, ProviderId } from '../../identities/types'; +import type WebSocketClient from '../../websockets/WebSocketClient'; +import type { IdentityInfoMessage } from '../../client/handlers/types'; +import type { ReadableStream } from 'stream/web'; +import type { ClientRPCResponseResult } from '../../client/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -43,8 +46,11 @@ class CommandSearch extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (searchTerms, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -54,60 +60,64 @@ class CommandSearch extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; - let genReadable: ReturnType< - typeof pkClient.grpcClient.identitiesInfoConnectedGet - >; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { - if (genReadable != null) genReadable.stream.cancel(); if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const providerSearchMessage = new identitiesPB.ProviderSearch(); - providerSearchMessage.setSearchTermList(searchTerms); - if (options.providerId) { - providerSearchMessage.setProviderIdList(options.providerId); - } - if (options.authIdentityId) { - providerSearchMessage.setAuthIdentityId(options.authIdentityId); - } - if (options.disconnected) { - providerSearchMessage.setDisconnected(true); - } - if (options.limit) { - providerSearchMessage.setLimit(options.limit.toString()); - } await binUtils.retryAuthentication(async (auth) => { + let readableStream: ReadableStream< + ClientRPCResponseResult + >; if (options.identityId) { - providerSearchMessage.setIdentityId(options.identityId); - genReadable = pkClient.grpcClient.identitiesInfoGet( - providerSearchMessage, - auth, + readableStream = await pkClient.rpcClient.methods.identitiesInfoGet( + { + metadata: auth, + identityId: options.identityId, + authIdentityId: options.authIdentityId, + disconnected: options.disconnected, + providerIdList: options.providerId ?? [], + searchTermList: searchTerms, + limit: options.limit, + }, ); } else { - genReadable = pkClient.grpcClient.identitiesInfoConnectedGet( - providerSearchMessage, - auth, - ); + readableStream = + await pkClient.rpcClient.methods.identitiesInfoConnectedGet({ + metadata: auth, + identityId: options.identityId, + authIdentityId: options.authIdentityId, + disconnected: options.disconnected, + providerIdList: options.providerId ?? [], + searchTermList: searchTerms, + limit: options.limit, + }); } - for await (const val of genReadable) { + for await (const identityInfoMessage of readableStream) { const output = { - providerId: val.getProvider()!.getProviderId() as ProviderId, - identityId: val.getProvider()!.getIdentityId() as IdentityId, - name: val.getName(), - email: val.getEmail(), - url: val.getUrl(), + providerId: identityInfoMessage.providerId, + identityId: identityInfoMessage.identityId, + name: identityInfoMessage.name, + email: identityInfoMessage.email, + url: identityInfoMessage.url, }; process.stdout.write( binUtils.outputFormatter({ @@ -116,9 +126,10 @@ class CommandSearch extends CommandPolykey { }), ); } - }, meta); + }, auth); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandTrust.ts b/src/bin/identities/CommandTrust.ts index 852c1bed7..664d02b0e 100644 --- a/src/bin/identities/CommandTrust.ts +++ b/src/bin/identities/CommandTrust.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; @@ -21,10 +22,12 @@ class CommandTrust extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const utils = await import('../../utils'); const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -35,20 +38,27 @@ class CommandTrust extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); const [type, id] = gestaltId; @@ -56,31 +66,27 @@ class CommandTrust extends CommandPolykey { case 'node': { // Setting by Node. - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsGestaltTrustByNode( - nodeMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsGestaltTrustByNode({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(id), + }), + auth, ); } break; case 'identity': { // Setting by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsGestaltTrustByIdentity( - providerMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsGestaltTrustByIdentity({ + metadata: auth, + providerId: id[0], + identityId: id[1], + }), + auth, ); } break; @@ -89,6 +95,7 @@ class CommandTrust extends CommandPolykey { } } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/identities/CommandUntrust.ts b/src/bin/identities/CommandUntrust.ts index 1047b60a7..d9b790a6f 100644 --- a/src/bin/identities/CommandUntrust.ts +++ b/src/bin/identities/CommandUntrust.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; @@ -21,13 +22,12 @@ class CommandUntrust extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const identitiesPB = await import( - '../../proto/js/polykey/v1/identities/identities_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const permissionsPB = await import( - '../../proto/js/polykey/v1/permissions/permissions_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const utils = await import('../../utils'); const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -38,57 +38,58 @@ class CommandUntrust extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); const action = 'notify'; - const setActionMessage = new permissionsPB.ActionSet(); - setActionMessage.setAction(action); const [type, id] = gestaltId; switch (type) { case 'node': { // Setting by Node. - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(id)); - setActionMessage.setNode(nodeMessage); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsUnsetByNode( - setActionMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsActionsUnsetByNode({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(id), + action, + }), + auth, ); } break; case 'identity': { // Setting by Identity - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(id[0]); - providerMessage.setIdentityId(id[1]); - setActionMessage.setIdentity(providerMessage); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsUnsetByIdentity( - setActionMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.gestaltsActionsUnsetByIdentity({ + metadata: auth, + providerId: id[0], + identityId: id[1], + action, + }), + auth, ); } break; @@ -97,6 +98,7 @@ class CommandUntrust extends CommandPolykey { } } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandCert.ts b/src/bin/keys/CommandCert.ts index 9765f1783..5c0b9eab4 100644 --- a/src/bin/keys/CommandCert.ts +++ b/src/bin/keys/CommandCert.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -14,7 +15,12 @@ class CommandCert extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -23,29 +29,38 @@ class CommandCert extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysCertsGet(emptyMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysCertsGet({ + metadata: auth, + }), + auth, ); const result = { - cert: response.getCert(), + cert: response.cert, }; let output: any = result; if (options.format === 'human') { @@ -59,6 +74,7 @@ class CommandCert extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandCertchain.ts b/src/bin/keys/CommandCertchain.ts index d4a18cd6d..fd0bb5ec2 100644 --- a/src/bin/keys/CommandCertchain.ts +++ b/src/bin/keys/CommandCertchain.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -14,7 +15,12 @@ class CommandsCertchain extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -23,34 +29,39 @@ class CommandsCertchain extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); const data = await binUtils.retryAuthentication(async (auth) => { const data: Array = []; - const stream = pkClient.grpcClient.keysCertsChainGet( - emptyMessage, - auth, - ); + const stream = await pkClient.rpcClient.methods.keysCertsChainGet({ + metadata: auth, + }); for await (const cert of stream) { - data.push(cert.getCert()); + data.push(cert.cert); } return data; - }, meta); + }, auth); const result = { certchain: data, }; @@ -66,6 +77,7 @@ class CommandsCertchain extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandDecrypt.ts b/src/bin/keys/CommandDecrypt.ts index aeb4a5191..c9463f867 100644 --- a/src/bin/keys/CommandDecrypt.ts +++ b/src/bin/keys/CommandDecrypt.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import * as binErrors from '../errors'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -19,7 +20,12 @@ class CommandDecrypt extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (filePath, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const keysPB = await import('../../proto/js/polykey/v1/keys/keys_pb'); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -28,23 +34,29 @@ class CommandDecrypt extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const cryptoMessage = new keysPB.Crypto(); let cipherText: string; try { cipherText = await this.fs.promises.readFile(filePath, { @@ -61,13 +73,16 @@ class CommandDecrypt extends CommandPolykey { cause: e, }); } - cryptoMessage.setData(cipherText); const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysDecrypt(cryptoMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysDecrypt({ + metadata: auth, + data: cipherText, + }), + auth, ); const result = { - decryptedData: response.getData(), + decryptedData: response.data, }; let output: any = result; if (options.format === 'human') { @@ -81,6 +96,7 @@ class CommandDecrypt extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandEncrypt.ts b/src/bin/keys/CommandEncrypt.ts index 77317c455..6a79a1d19 100644 --- a/src/bin/keys/CommandEncrypt.ts +++ b/src/bin/keys/CommandEncrypt.ts @@ -1,5 +1,6 @@ import type PolykeyClient from '../../PolykeyClient'; -import type { JWK } from '../../keys/types'; +import type WebSocketClient from '../../websockets/WebSocketClient'; +import type { PublicKeyJWK } from '../../keys/types'; import * as binErrors from '../errors'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -21,7 +22,12 @@ class CommandEncypt extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (filePath, nodeIdOrJwkFile, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const keysPB = await import('../../proto/js/polykey/v1/keys/keys_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const nodesUtils = await import('../../nodes/utils'); const keysUtils = await import('../../keys/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -32,23 +38,29 @@ class CommandEncypt extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const cryptoMessage = new keysPB.Crypto(); let plainText: string; try { plainText = await this.fs.promises.readFile(filePath, { @@ -65,7 +77,7 @@ class CommandEncypt extends CommandPolykey { cause: e, }); } - let publicJWK: JWK; + let publicJWK: PublicKeyJWK; const nodeId = nodesUtils.decodeNodeId(nodeIdOrJwkFile); if (nodeId != null) { publicJWK = keysUtils.publicKeyToJWK( @@ -77,7 +89,7 @@ class CommandEncypt extends CommandPolykey { const rawJWK = await this.fs.promises.readFile(nodeIdOrJwkFile, { encoding: 'utf-8', }); - publicJWK = JSON.parse(rawJWK) as JWK; + publicJWK = JSON.parse(rawJWK) as PublicKeyJWK; // Checking if the JWK is valid keysUtils.publicKeyFromJWK(publicJWK); } catch (e) { @@ -87,14 +99,17 @@ class CommandEncypt extends CommandPolykey { ); } } - cryptoMessage.setData(plainText); - cryptoMessage.setPublicKeyJwk(JSON.stringify(publicJWK)); const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysEncrypt(cryptoMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysEncrypt({ + metadata: auth, + publicKeyJwk: publicJWK, + data: plainText, + }), + auth, ); const result = { - encryptedData: response.getData(), + encryptedData: response.data, }; let output: any = result; if (options.format === 'human') { @@ -108,6 +123,7 @@ class CommandEncypt extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandPair.ts b/src/bin/keys/CommandPair.ts index b3d6c0907..dd60f8908 100644 --- a/src/bin/keys/CommandPair.ts +++ b/src/bin/keys/CommandPair.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -17,8 +18,11 @@ class CommandKeypair extends CommandPolykey { this.addOption(binOptions.passwordNewFile); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const sessionsPB = await import( - '../../proto/js/polykey/v1/sessions/sessions_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -28,7 +32,7 @@ class CommandKeypair extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); @@ -37,29 +41,36 @@ class CommandKeypair extends CommandPolykey { this.fs, true, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const passwordMessage = new sessionsPB.Password(); - passwordMessage.setPassword(passwordNew); const keyPairJWK = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysKeyPair(passwordMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysKeyPair({ + metadata: auth, + password: passwordNew, + }), + auth, ); - const publicKeyJWK = JSON.parse(keyPairJWK.getPublicKeyJwk()); - const privateKeyJWE = JSON.parse(keyPairJWK.getPrivateKeyJwe()); const pair = { - publicKey: publicKeyJWK, - privateKey: privateKeyJWE, + publicKey: keyPairJWK.publicKeyJwk, + privateKey: keyPairJWK.privateKeyJwe, }; process.stdout.write( binUtils.outputFormatter({ @@ -69,6 +80,7 @@ class CommandKeypair extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandPassword.ts b/src/bin/keys/CommandPassword.ts index b56da6572..889d3b3cc 100644 --- a/src/bin/keys/CommandPassword.ts +++ b/src/bin/keys/CommandPassword.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,8 +16,11 @@ class CommandPassword extends CommandPolykey { this.addOption(binOptions.passwordNewFile); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const sessionsPB = await import( - '../../proto/js/polykey/v1/sessions/sessions_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -26,7 +30,7 @@ class CommandPassword extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); @@ -35,27 +39,36 @@ class CommandPassword extends CommandPolykey { this.fs, true, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const passwordMessage = new sessionsPB.Password(); - passwordMessage.setPassword(passwordNew); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.keysPasswordChange(passwordMessage, auth), - meta, + pkClient.rpcClient.methods.keysPasswordChange({ + metadata: auth, + password: passwordNew, + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandPrivate.ts b/src/bin/keys/CommandPrivate.ts index 656331e34..df871b086 100644 --- a/src/bin/keys/CommandPrivate.ts +++ b/src/bin/keys/CommandPrivate.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,8 +16,11 @@ class CommandPrivate extends CommandPolykey { this.addOption(binOptions.passwordNewFile); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const sessionsPB = await import( - '../../proto/js/polykey/v1/sessions/sessions_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -26,7 +30,7 @@ class CommandPrivate extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); @@ -35,25 +39,34 @@ class CommandPrivate extends CommandPolykey { this.fs, true, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const passwordMessage = new sessionsPB.Password(); - passwordMessage.setPassword(passwordNew); const keyPairJWK = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysKeyPair(passwordMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysKeyPair({ + metadata: auth, + password: passwordNew, + }), + auth, ); - const privateKeyJWE = JSON.parse(keyPairJWK.getPrivateKeyJwe()); + const privateKeyJWE = keyPairJWK.privateKeyJwe; process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'dict', @@ -62,6 +75,7 @@ class CommandPrivate extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandPublic.ts b/src/bin/keys/CommandPublic.ts index ad6c2ce3a..ecd3d1cc0 100644 --- a/src/bin/keys/CommandPublic.ts +++ b/src/bin/keys/CommandPublic.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -14,7 +15,12 @@ class CommandPublic extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -23,28 +29,37 @@ class CommandPublic extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const passwordMessage = new utilsPB.EmptyMessage(); const keyPairJWK = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysPublicKey(passwordMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysPublicKey({ + metadata: auth, + }), + auth, ); - const publicKeyJWK = JSON.parse(keyPairJWK.getPublicKeyJwk()); + const publicKeyJWK = keyPairJWK.publicKeyJwk; process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'dict', @@ -53,6 +68,7 @@ class CommandPublic extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandRenew.ts b/src/bin/keys/CommandRenew.ts index d7d1d2918..4f801461e 100644 --- a/src/bin/keys/CommandRenew.ts +++ b/src/bin/keys/CommandRenew.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,7 +16,12 @@ class CommandRenew extends CommandPolykey { this.addOption(binOptions.passwordNewFile); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const keysPB = await import('../../proto/js/polykey/v1/keys/keys_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -24,7 +30,7 @@ class CommandRenew extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); @@ -33,26 +39,36 @@ class CommandRenew extends CommandPolykey { this.fs, true, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const keyMessage = new keysPB.Key(); - keyMessage.setName(passwordNew); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysKeyPairRenew(keyMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysKeyPairRenew({ + metadata: auth, + password: passwordNew, + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandReset.ts b/src/bin/keys/CommandReset.ts index 99b67c99b..053386b4d 100644 --- a/src/bin/keys/CommandReset.ts +++ b/src/bin/keys/CommandReset.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,7 +16,12 @@ class CommandReset extends CommandPolykey { this.addOption(binOptions.passwordNewFile); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const keysPB = await import('../../proto/js/polykey/v1/keys/keys_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -24,7 +30,7 @@ class CommandReset extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); @@ -33,28 +39,36 @@ class CommandReset extends CommandPolykey { this.fs, true, ); - this.logger.error('ASDASDASD'); - this.logger.error(passwordNew); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const keyMessage = new keysPB.Key(); - keyMessage.setName(passwordNew); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysKeyPairReset(keyMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysKeyPairReset({ + metadata: auth, + password: passwordNew, + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandSign.ts b/src/bin/keys/CommandSign.ts index 4d31dee5f..54f9c79b9 100644 --- a/src/bin/keys/CommandSign.ts +++ b/src/bin/keys/CommandSign.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import * as binErrors from '../errors'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -19,7 +20,12 @@ class CommandSign extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (filePath, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const keysPB = await import('../../proto/js/polykey/v1/keys/keys_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -28,23 +34,29 @@ class CommandSign extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const cryptoMessage = new keysPB.Crypto(); let data: string; try { data = await this.fs.promises.readFile(filePath, { @@ -61,13 +73,16 @@ class CommandSign extends CommandPolykey { cause: e, }); } - cryptoMessage.setData(data); const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysSign(cryptoMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysSign({ + metadata: auth, + data, + }), + auth, ); const result = { - signature: response.getSignature(), + signature: response.signature, }; let output: any = result; if (options.format === 'human') { @@ -81,6 +96,7 @@ class CommandSign extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/keys/CommandVerify.ts b/src/bin/keys/CommandVerify.ts index b3e58445a..7602a559b 100644 --- a/src/bin/keys/CommandVerify.ts +++ b/src/bin/keys/CommandVerify.ts @@ -1,5 +1,6 @@ import type PolykeyClient from '../../PolykeyClient'; -import type { JWK } from '../../keys/types'; +import type WebSocketClient from '../../websockets/WebSocketClient'; +import type { PublicKeyJWK } from '../../keys/types'; import * as binErrors from '../errors'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -25,7 +26,12 @@ class CommandVerify extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (filePath, signaturePath, nodeIdOrJwkFile, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const keysPB = await import('../../proto/js/polykey/v1/keys/keys_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const nodesUtils = await import('../../nodes/utils'); const keysUtils = await import('../../keys/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -36,23 +42,29 @@ class CommandVerify extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const cryptoMessage = new keysPB.Crypto(); let data: string; let signature: string; try { @@ -73,7 +85,7 @@ class CommandVerify extends CommandPolykey { cause: e, }); } - let publicJWK: JWK; + let publicJWK: PublicKeyJWK; const nodeId = nodesUtils.decodeNodeId(nodeIdOrJwkFile); if (nodeId != null) { publicJWK = keysUtils.publicKeyToJWK( @@ -85,7 +97,7 @@ class CommandVerify extends CommandPolykey { const rawJWK = await this.fs.promises.readFile(nodeIdOrJwkFile, { encoding: 'utf-8', }); - publicJWK = JSON.parse(rawJWK) as JWK; + publicJWK = JSON.parse(rawJWK) as PublicKeyJWK; // Checking if the JWK is valid keysUtils.publicKeyFromJWK(publicJWK); } catch (e) { @@ -95,15 +107,18 @@ class CommandVerify extends CommandPolykey { ); } } - cryptoMessage.setData(data); - cryptoMessage.setSignature(signature); - cryptoMessage.setPublicKeyJwk(JSON.stringify(publicJWK)); const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.keysVerify(cryptoMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.keysVerify({ + metadata: auth, + publicKeyJwk: publicJWK, + data, + signature, + }), + auth, ); const result = { - signatureVerified: response.getSuccess(), + signatureVerified: response.success, }; let output: any = result; if (options.format === 'human') { @@ -117,6 +132,7 @@ class CommandVerify extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/nodes/CommandAdd.ts b/src/bin/nodes/CommandAdd.ts index 796dd269b..7ad79825b 100644 --- a/src/bin/nodes/CommandAdd.ts +++ b/src/bin/nodes/CommandAdd.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import type { Host, Port } from '../../network/types'; import CommandPolykey from '../CommandPolykey'; @@ -22,8 +23,13 @@ class CommandAdd extends CommandPolykey { this.addOption(binOptions.noPing); this.action(async (nodeId: NodeId, host: Host, port: Port, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const nodesUtils = await import('../../nodes/utils'); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -32,35 +38,44 @@ class CommandAdd extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const nodeAddMessage = new nodesPB.NodeAdd(); - nodeAddMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); - nodeAddMessage.setAddress( - new nodesPB.Address().setHost(host).setPort(port), - ); - nodeAddMessage.setForce(options.force); - nodeAddMessage.setPing(options.ping); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.nodesAdd(nodeAddMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.nodesAdd({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + host: host, + port: port, + force: options.force, + ping: options.ping, + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/nodes/CommandClaim.ts b/src/bin/nodes/CommandClaim.ts index bf281e11e..e92ad6c32 100644 --- a/src/bin/nodes/CommandClaim.ts +++ b/src/bin/nodes/CommandClaim.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -25,8 +26,13 @@ class CommandClaim extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const nodesUtils = await import('../../nodes/utils'); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -35,34 +41,39 @@ class CommandClaim extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const nodeClaimMessage = new nodesPB.Claim(); - nodeClaimMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); - if (options.forceInvite) { - nodeClaimMessage.setForceInvite(true); - } else { - nodeClaimMessage.setForceInvite(false); - } const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.nodesClaim(nodeClaimMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.nodesClaim({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + forceInvite: options.forceInvite, + }), + auth, ); - const claimed = response.getSuccess(); + const claimed = response.success; if (claimed) { process.stdout.write( binUtils.outputFormatter({ @@ -88,6 +99,7 @@ class CommandClaim extends CommandPolykey { } } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/nodes/CommandConnections.ts b/src/bin/nodes/CommandConnections.ts index 74148b775..1d3726bc2 100644 --- a/src/bin/nodes/CommandConnections.ts +++ b/src/bin/nodes/CommandConnections.ts @@ -1,5 +1,6 @@ import type PolykeyClient from '../../PolykeyClient'; -import type nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import type WebSocketClient from '../../websockets/WebSocketClient'; +import type { NodeConnectionMessage } from '../../client/handlers/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils/utils'; import * as binProcessors from '../utils/processors'; @@ -11,7 +12,12 @@ class CommandAdd extends CommandPolykey { this.description('list all active node connections'); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -20,43 +26,48 @@ class CommandAdd extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); // DO things here... // Like create the message. - const emptyMessage = new utilsPB.EmptyMessage(); - const connections = await binUtils.retryAuthentication(async (auth) => { - const connections = pkClient.grpcClient.nodesListConnections( - emptyMessage, - auth, - ); - const connectionEntries: Array = []; + const connections = + await pkClient.rpcClient.methods.nodesListConnections({ + metadata: auth, + }); + const connectionEntries: Array = []; for await (const connection of connections) { - connectionEntries.push(connection.toObject()); + connectionEntries.push(connection); } return connectionEntries; - }, meta); + }, auth); if (options.format === 'human') { const output: Array = []; for (const connection of connections) { const hostnameString = connection.hostname === '' ? '' : `(${connection.hostname})`; - const hostString = `${connection.nodeId}@${connection.host}${hostnameString}:${connection.port}`; + const hostString = `${connection.nodeIdEncoded}@${connection.host}${hostnameString}:${connection.port}`; const usageCount = connection.usageCount; const timeout = connection.timeout === -1 ? 'NA' : `${connection.timeout}`; @@ -79,6 +90,7 @@ class CommandAdd extends CommandPolykey { } } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/nodes/CommandFind.ts b/src/bin/nodes/CommandFind.ts index bc871a3e7..fa7942a1c 100644 --- a/src/bin/nodes/CommandFind.ts +++ b/src/bin/nodes/CommandFind.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import type { Host, Port } from '../../network/types'; import CommandPolykey from '../CommandPolykey'; @@ -19,11 +20,15 @@ class CommandFind extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const nodesUtils = await import('../../nodes/utils'); const networkUtils = await import('../../network/utils'); const nodesErrors = await import('../../nodes/errors'); - const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -32,24 +37,29 @@ class CommandFind extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); const result = { success: false, message: '', @@ -59,13 +69,17 @@ class CommandFind extends CommandPolykey { }; try { const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.nodesFind(nodeMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.nodesFind({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + }), + auth, ); result.success = true; - result.id = response.getNodeId(); - result.host = response.getAddress()!.getHost(); - result.port = response.getAddress()!.getPort(); + result.id = nodesUtils.encodeNodeId(nodeId); + result.host = response.host; + result.port = response.port; result.message = `Found node at ${networkUtils.buildAddress( result.host as Host, result.port as Port, @@ -97,6 +111,7 @@ class CommandFind extends CommandPolykey { } } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/nodes/CommandGetAll.ts b/src/bin/nodes/CommandGetAll.ts index 243991fc9..f13309dab 100644 --- a/src/bin/nodes/CommandGetAll.ts +++ b/src/bin/nodes/CommandGetAll.ts @@ -1,5 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; -import type nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,8 +15,12 @@ class CommandGetAll extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); - + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -25,41 +29,45 @@ class CommandGetAll extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); - let result: nodesPB.NodeBuckets; try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); - result = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.nodesGetAll(emptyMessage, auth), - meta, + const result = await binUtils.retryAuthentication( + (auth) => + pkClient.rpcClient.methods.nodesGetAll({ + metadata: auth, + }), + auth, ); - let output: any = {}; - for (const [bucketIndex, bucket] of result.getBucketsMap().entries()) { - output[bucketIndex] = {}; - for (const [encodedId, address] of bucket - .getNodeTableMap() - .entries()) { - output[bucketIndex][encodedId] = {}; - output[bucketIndex][encodedId].host = address.getHost(); - output[bucketIndex][encodedId].port = address.getPort(); - } + let output: Array = []; + for await (const nodesGetMessage of result) { + output.push(nodesGetMessage); } if (options.format === 'human') { - output = [result.getBucketsMap().getEntryList()]; + output = output.map( + (value) => + `NodeId ${value.nodeIdEncoded}, Address ${value.host}:${value.port}, bucketIndex ${value.bucketIndex}`, + ); } process.stdout.write( binUtils.outputFormatter({ @@ -69,6 +77,7 @@ class CommandGetAll extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/nodes/CommandPing.ts b/src/bin/nodes/CommandPing.ts index b835a4ba2..b1da86fb8 100644 --- a/src/bin/nodes/CommandPing.ts +++ b/src/bin/nodes/CommandPing.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -18,8 +19,13 @@ class CommandPing extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const nodesUtils = await import('../../nodes/utils'); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -28,31 +34,40 @@ class CommandPing extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); let error; const statusMessage = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.nodesPing(nodeMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.nodesPing({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + }), + auth, ); const status = { success: false, message: '' }; - status.success = statusMessage ? statusMessage.getSuccess() : false; + status.success = statusMessage ? statusMessage.success : false; if (!status.success && !error) { error = new binErrors.ErrorCLINodePingFailed('No response received'); } @@ -69,6 +84,7 @@ class CommandPing extends CommandPolykey { if (error != null) throw error; } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/notifications/CommandClear.ts b/src/bin/notifications/CommandClear.ts index a149f88cb..316dd5629 100644 --- a/src/bin/notifications/CommandClear.ts +++ b/src/bin/notifications/CommandClear.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -14,7 +15,12 @@ class CommandClear extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -23,29 +29,39 @@ class CommandClear extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.notificationsClear(emptyMessage, auth), - meta, + (auth) => + pkClient.rpcClient.methods.notificationsClear({ + metadata: auth, + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/notifications/CommandRead.ts b/src/bin/notifications/CommandRead.ts index 8ec6479ff..6ab4097ec 100644 --- a/src/bin/notifications/CommandRead.ts +++ b/src/bin/notifications/CommandRead.ts @@ -1,4 +1,5 @@ import type { Notification } from '../../notifications/types'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type PolykeyClient from '../../PolykeyClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -29,8 +30,11 @@ class CommandRead extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const notificationsPB = await import( - '../../proto/js/polykey/v1/notifications/notifications_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const notificationsUtils = await import('../../notifications/utils'); const clientOptions = await binProcessors.processClientOptions( @@ -45,39 +49,39 @@ class CommandRead extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const notificationsReadMessage = new notificationsPB.Read(); - if (options.unread) { - notificationsReadMessage.setUnread(true); - } else { - notificationsReadMessage.setUnread(false); - } - notificationsReadMessage.setNumber(options.number); - notificationsReadMessage.setOrder(options.order); const response = await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.notificationsRead( - notificationsReadMessage, - auth, - ), + pkClient.rpcClient.methods.notificationsRead({ + metadata: auth, + unread: options.unread, + number: options.number, + order: options.order, + }), meta, ); - const notificationMessages = response.getNotificationList(); const notifications: Array = []; - for (const message of notificationMessages) { + for await (const notificationMessage of response) { const notification = notificationsUtils.parseNotification( - JSON.parse(message.getContent()), + notificationMessage.notification, ); notifications.push(notification); } @@ -91,6 +95,7 @@ class CommandRead extends CommandPolykey { } } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/notifications/CommandSend.ts b/src/bin/notifications/CommandSend.ts index 257b87ea4..767820efb 100644 --- a/src/bin/notifications/CommandSend.ts +++ b/src/bin/notifications/CommandSend.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -22,10 +23,13 @@ class CommandSend extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (nodeId: NodeId, message, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const nodesUtils = await import('../../nodes/utils'); - const notificationsPB = await import( - '../../proto/js/polykey/v1/notifications/notifications_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); + const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -34,37 +38,41 @@ class CommandSend extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const notificationsSendMessage = new notificationsPB.Send(); - const generalMessage = new notificationsPB.General(); - generalMessage.setMessage(message); - notificationsSendMessage.setReceiverId(nodesUtils.encodeNodeId(nodeId)); - notificationsSendMessage.setData(generalMessage); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.notificationsSend( - notificationsSendMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.notificationsSend({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + message: message, + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/polykey-agent.ts b/src/bin/polykey-agent.ts index 1229d2073..bcfe3a565 100755 --- a/src/bin/polykey-agent.ts +++ b/src/bin/polykey-agent.ts @@ -114,8 +114,8 @@ async function main(_argv = process.argv): Promise { recoveryCode: pkAgent.keyRing.recoveryCode, pid: process.pid, nodeId: nodesUtils.encodeNodeId(pkAgent.keyRing.getNodeId()), - clientHost: pkAgent.grpcServerClient.getHost(), - clientPort: pkAgent.grpcServerClient.getPort(), + clientHost: pkAgent.webSocketServerClient.getHost(), + clientPort: pkAgent.webSocketServerClient.getPort(), agentHost: pkAgent.grpcServerAgent.getHost(), agentPort: pkAgent.grpcServerAgent.getPort(), proxyHost: pkAgent.proxy.getProxyHost(), diff --git a/src/bin/secrets/CommandCreate.ts b/src/bin/secrets/CommandCreate.ts index 26f22cbbe..cb11cb550 100644 --- a/src/bin/secrets/CommandCreate.ts +++ b/src/bin/secrets/CommandCreate.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import * as binErrors from '../errors'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -25,11 +26,11 @@ class CommandCreate extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (directoryPath, secretPath, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const secretsPB = await import( - '../../proto/js/polykey/v1/secrets/secrets_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -43,23 +44,25 @@ class CommandCreate extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - secretMessage.setVault(vaultMessage); - vaultMessage.setNameOrId(secretPath[0]); - secretMessage.setSecretName(secretPath[1]); let content: Buffer; try { content = await this.fs.promises.readFile(directoryPath); @@ -74,13 +77,19 @@ class CommandCreate extends CommandPolykey { cause: e, }); } - secretMessage.setSecretContent(content); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsSecretsNew(secretMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsSecretsNew({ + metadata: auth, + nameOrId: secretPath[0], + secretName: secretPath[1], + secretContent: content.toString('binary'), + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/secrets/CommandDelete.ts b/src/bin/secrets/CommandDelete.ts index 300879931..9ba776bbf 100644 --- a/src/bin/secrets/CommandDelete.ts +++ b/src/bin/secrets/CommandDelete.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -21,11 +22,11 @@ class CommandDelete extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (secretPath, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const secretsPB = await import( - '../../proto/js/polykey/v1/secrets/secrets_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -35,34 +36,41 @@ class CommandDelete extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - const secretMessage = new secretsPB.Secret(); - vaultMessage.setNameOrId(secretPath[0]); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretPath[1]); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.vaultsSecretsDelete(secretMessage, auth), - meta, + pkClient.rpcClient.methods.vaultsSecretsDelete({ + metadata: auth, + nameOrId: secretPath[0], + secretName: secretPath[1], + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/secrets/CommandDir.ts b/src/bin/secrets/CommandDir.ts index 813ab7aab..f0395c03b 100644 --- a/src/bin/secrets/CommandDir.ts +++ b/src/bin/secrets/CommandDir.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -19,11 +20,11 @@ class CommandDir extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (directoryPath, vaultName, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const secretsPB = await import( - '../../proto/js/polykey/v1/secrets/secrets_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -33,37 +34,41 @@ class CommandDir extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - const pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const secretDirectoryMessage = new secretsPB.Directory(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); - secretDirectoryMessage.setVault(vaultMessage); - secretDirectoryMessage.setSecretDirectory(directoryPath); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.vaultsSecretsNewDir( - secretDirectoryMessage, - auth, - ), - meta, + pkClient.rpcClient.methods.vaultsSecretsNewDir({ + metadata: auth, + nameOrId: vaultName, + dirName: directoryPath, + }), + auth, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/secrets/CommandEdit.ts b/src/bin/secrets/CommandEdit.ts index f86b37499..885b1d9db 100644 --- a/src/bin/secrets/CommandEdit.ts +++ b/src/bin/secrets/CommandEdit.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import * as binErrors from '../errors'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -23,11 +24,11 @@ class CommandEdit extends CommandPolykey { const os = await import('os'); const { execSync } = await import('child_process'); const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const secretsPB = await import( - '../../proto/js/polykey/v1/secrets/secrets_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -41,28 +42,35 @@ class CommandEdit extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(secretPath[0]); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretPath[1]); const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsSecretsGet(secretMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsSecretsGet({ + metadata: auth, + nameOrId: secretPath[0], + secretName: secretPath[1], + }), meta, ); - const secretContent = response.getSecretName(); + const secretContent = response.secretContent; // Linux const tmpDir = `${os.tmpdir}/pksecret`; await this.fs.promises.mkdir(tmpDir); @@ -83,14 +91,17 @@ class CommandEdit extends CommandPolykey { cause: e, }); } - secretMessage.setVault(vaultMessage); - secretMessage.setSecretContent(content); - await pkClient.grpcClient.vaultsSecretsEdit(secretMessage); + await pkClient.rpcClient.methods.vaultsSecretsEdit({ + nameOrId: secretPath[0], + secretName: secretPath[1], + secretContent: content.toString('binary'), + }); await this.fs.promises.rmdir(tmpDir, { recursive: true }); // Windows // TODO: complete windows impl } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/secrets/CommandGet.ts b/src/bin/secrets/CommandGet.ts index 5358cb34d..1d0ff5a77 100644 --- a/src/bin/secrets/CommandGet.ts +++ b/src/bin/secrets/CommandGet.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -20,11 +21,11 @@ class CommandGet extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (secretPath, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const secretsPB = await import( - '../../proto/js/polykey/v1/secrets/secrets_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -38,28 +39,35 @@ class CommandGet extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(secretPath[0]); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretPath[1]); const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsSecretsGet(secretMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsSecretsGet({ + metadata: auth, + nameOrId: secretPath[0], + secretName: secretPath[1], + }), meta, ); - const secretContent = response.getSecretContent_asU8(); + const secretContent = Buffer.from(response.secretContent, 'binary'); process.stdout.write( binUtils.outputFormatter({ type: 'raw', @@ -68,6 +76,7 @@ class CommandGet extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/secrets/CommandList.ts b/src/bin/secrets/CommandList.ts index c792a617c..14490dec4 100644 --- a/src/bin/secrets/CommandList.ts +++ b/src/bin/secrets/CommandList.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -16,8 +17,11 @@ class CommandList extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (vaultName, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -27,35 +31,40 @@ class CommandList extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const meta = await binProcessors.processAuthentication( + const auth = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); const data = await binUtils.retryAuthentication(async (auth) => { const data: Array = []; - const stream = pkClient.grpcClient.vaultsSecretsList( - vaultMessage, - auth, - ); + const stream = await pkClient.rpcClient.methods.vaultsSecretsList({ + metadata: auth, + nameOrId: vaultName, + }); for await (const secret of stream) { - data.push(`${secret.getSecretName()}`); + data.push(secret.secretName); } return data; - }, meta); + }, auth); process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', @@ -64,6 +73,7 @@ class CommandList extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/secrets/CommandMkdir.ts b/src/bin/secrets/CommandMkdir.ts index bba9e0db9..0a2999b4c 100644 --- a/src/bin/secrets/CommandMkdir.ts +++ b/src/bin/secrets/CommandMkdir.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -21,8 +22,11 @@ class CommandMkdir extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (secretPath, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -36,31 +40,38 @@ class CommandMkdir extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMkdirMessage = new vaultsPB.Mkdir(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(secretPath[0]); - vaultMkdirMessage.setVault(vaultMessage); - vaultMkdirMessage.setDirName(secretPath[1]); - vaultMkdirMessage.setRecursive(options.recursive); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.vaultsSecretsMkdir(vaultMkdirMessage, auth), + pkClient.rpcClient.methods.vaultsSecretsMkdir({ + metadata: auth, + nameOrId: secretPath[0], + dirName: secretPath[1], + recursive: options.recursive, + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/secrets/CommandRename.ts b/src/bin/secrets/CommandRename.ts index 9717b1c84..92ed64369 100644 --- a/src/bin/secrets/CommandRename.ts +++ b/src/bin/secrets/CommandRename.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -21,11 +22,11 @@ class CommandRename extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (secretPath, newSecretName, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const secretsPB = await import( - '../../proto/js/polykey/v1/secrets/secrets_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -39,33 +40,38 @@ class CommandRename extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - const secretMessage = new secretsPB.Secret(); - const secretRenameMessage = new secretsPB.Rename(); - secretMessage.setVault(vaultMessage); - secretRenameMessage.setOldSecret(secretMessage); - vaultMessage.setNameOrId(secretPath[0]); - secretMessage.setSecretName(secretPath[1]); - secretRenameMessage.setNewName(newSecretName); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.vaultsSecretsRename(secretRenameMessage, auth), + pkClient.rpcClient.methods.vaultsSecretsRename({ + metadata: auth, + nameOrId: secretPath[0], + secretName: secretPath[1], + newSecretName: newSecretName, + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/secrets/CommandStat.ts b/src/bin/secrets/CommandStat.ts index c2c7063c0..22a8fee07 100644 --- a/src/bin/secrets/CommandStat.ts +++ b/src/bin/secrets/CommandStat.ts @@ -1,5 +1,5 @@ -import type { Stat } from 'encryptedfs'; import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import * as binProcessors from '../utils/processors'; import * as parsers from '../utils/parsers'; import * as binUtils from '../utils'; @@ -21,11 +21,11 @@ class CommandStat extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (secretPath, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const secretsPB = await import( - '../../proto/js/polykey/v1/secrets/secrets_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -39,34 +39,39 @@ class CommandStat extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(secretPath[0]); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretPath[1]); // Get the secret's stat. const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsSecretsStat(secretMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsSecretsStat({ + metadata: auth, + nameOrId: secretPath[0], + secretName: secretPath[1], + }), meta, ); - const stat: Stat = JSON.parse(response.getJson()); const data: string[] = [`Stats for "${secretPath[1]}"`]; - for (const key in stat) { - data.push(`${key}: ${stat[key]}`); + for (const [key, value] of Object.entries(response.stat)) { + data.push(`${key}: ${value}`); } // Print out the result. @@ -78,6 +83,7 @@ class CommandStat extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/secrets/CommandUpdate.ts b/src/bin/secrets/CommandUpdate.ts index c7b9e696d..9f46bb556 100644 --- a/src/bin/secrets/CommandUpdate.ts +++ b/src/bin/secrets/CommandUpdate.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import * as binErrors from '../errors'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -25,11 +26,11 @@ class CommandUpdate extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (directoryPath, secretPath, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const secretsPB = await import( - '../../proto/js/polykey/v1/secrets/secrets_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -43,23 +44,25 @@ class CommandUpdate extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - const secretMessage = new secretsPB.Secret(); - secretMessage.setVault(vaultMessage); - vaultMessage.setNameOrId(secretPath[0]); - secretMessage.setSecretName(secretPath[1]); let content: Buffer; try { content = await this.fs.promises.readFile(directoryPath); @@ -74,13 +77,19 @@ class CommandUpdate extends CommandPolykey { cause: e, }); } - secretMessage.setSecretContent(content); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsSecretsEdit(secretMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsSecretsEdit({ + metadata: auth, + nameOrId: secretPath[0], + secretName: secretPath[1], + secretContent: content.toString('binary'), + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/utils/options.ts b/src/bin/utils/options.ts index ec5812ca8..f658a5582 100644 --- a/src/bin/utils/options.ts +++ b/src/bin/utils/options.ts @@ -186,6 +186,16 @@ const privateKeyFile = new commander.Option( 'Override key creation with a private key JWE from a file', ); +const depth = new commander.Option( + '-d, --depth [depth]', + 'The number of commits to retrieve', +).argParser(parseInt); + +const commitId = new commander.Option( + '-ci, --commit-id [commitId]', + 'Id for a specific commit to read from', +); + export { nodePath, format, @@ -212,4 +222,6 @@ export { privateKeyFile, passwordOpsLimit, passwordMemLimit, + depth, + commitId, }; diff --git a/src/bin/utils/processors.ts b/src/bin/utils/processors.ts index 8b38d26e8..7be1a8854 100644 --- a/src/bin/utils/processors.ts +++ b/src/bin/utils/processors.ts @@ -11,10 +11,9 @@ import type { import type { SessionToken } from '../../sessions/types'; import path from 'path'; import prompts from 'prompts'; -import * as grpc from '@grpc/grpc-js'; import Logger from '@matrixai/logger'; import Status from '../../status/Status'; -import * as clientUtils from '../../client/utils'; +import * as clientUtils from '../../client/utils/utils'; import * as binErrors from '../errors'; import { arrayZip } from '../../utils'; import config from '../../config'; @@ -375,8 +374,7 @@ async function processClientStatus( async function processAuthentication( passwordFile?: string, fs: FileSystem = require('fs'), -): Promise { - let meta; +): Promise<{ authorization?: string }> { if (passwordFile != null) { let password; try { @@ -392,17 +390,24 @@ async function processAuthentication( cause: e, }); } - meta = clientUtils.encodeAuthFromPassword(password); + return { + authorization: clientUtils.encodeAuthFromPassword(password), + }; } else if (typeof process.env['PK_PASSWORD'] === 'string') { - meta = clientUtils.encodeAuthFromPassword(process.env['PK_PASSWORD']); + return { + authorization: clientUtils.encodeAuthFromPassword( + process.env['PK_PASSWORD'], + ), + }; } else if (typeof process.env['PK_TOKEN'] === 'string') { - meta = clientUtils.encodeAuthFromSession( - process.env['PK_TOKEN'] as SessionToken, - ); + return { + authorization: clientUtils.encodeAuthFromSession( + process.env['PK_TOKEN'] as SessionToken, + ), + }; } else { - meta = new grpc.Metadata(); + return {}; } - return meta; } export { diff --git a/src/bin/utils/utils.ts b/src/bin/utils/utils.ts index 08c2f55b3..b50e06b5b 100644 --- a/src/bin/utils/utils.ts +++ b/src/bin/utils/utils.ts @@ -1,16 +1,16 @@ import type { POJO } from '../../types'; import process from 'process'; import { LogLevel } from '@matrixai/logger'; -import * as grpc from '@grpc/grpc-js'; import { AbstractError } from '@matrixai/errors'; import * as binProcessors from './processors'; import ErrorPolykey from '../../ErrorPolykey'; import * as binErrors from '../errors'; -import * as clientUtils from '../../client/utils'; +import * as clientUtils from '../../client/utils/utils'; import * as clientErrors from '../../client/errors'; import * as grpcErrors from '../../grpc/errors'; import * as nodesUtils from '../../nodes/utils'; import * as utils from '../../utils'; +import * as rpcErrors from '../../rpc/errors'; /** * Convert verbosity to LogLevel @@ -112,7 +112,10 @@ function outputFormatter(msg: OutputObject): string | Uint8Array { let currError = msg.data; let indent = ' '; while (currError != null) { - if (currError instanceof grpcErrors.ErrorPolykeyRemote) { + if ( + currError instanceof grpcErrors.ErrorPolykeyRemoteOLD || + currError instanceof rpcErrors.ErrorPolykeyRemote + ) { output += `${currError.name}: ${currError.description}`; if (currError.message && currError.message !== '') { output += ` - ${currError.message}`; @@ -177,15 +180,15 @@ function outputFormatter(msg: OutputObject): string | Uint8Array { * Known as "privilege elevation" */ async function retryAuthentication( - f: (meta: grpc.Metadata) => Promise, - meta: grpc.Metadata = new grpc.Metadata(), + f: (meta: { authorization?: string }) => Promise, + meta: { authorization?: string } = {}, ): Promise { try { return await f(meta); } catch (e) { - // If it is unattended, throw the exception - // Don't enter into a retry loop when unattended - // Unattended means that either the `PK_PASSWORD` or `PK_TOKEN` was set + // If it is unattended, throw the exception. + // Don't enter into a retry loop when unattended. + // Unattended means that either the `PK_PASSWORD` or `PK_TOKEN` was set. if ('PK_PASSWORD' in process.env || 'PK_TOKEN' in process.env) { throw e; } @@ -206,9 +209,11 @@ async function retryAuthentication( throw new binErrors.ErrorCLIPasswordMissing(); } // Augment existing metadata - clientUtils.encodeAuthFromPassword(password, meta); + const auth = { + authorization: clientUtils.encodeAuthFromPassword(password), + }; try { - return await f(meta); + return await f(auth); } catch (e) { const [cause] = remoteErrorCause(e); // The auth cannot be missing, so when it is denied do we retry @@ -222,7 +227,7 @@ async function retryAuthentication( function remoteErrorCause(e: any): [any, number] { let errorCause = e; let depth = 0; - while (errorCause instanceof grpcErrors.ErrorPolykeyRemote) { + while (errorCause instanceof rpcErrors.ErrorPolykeyRemote) { errorCause = errorCause.cause; depth++; } diff --git a/src/bin/vaults/CommandClone.ts b/src/bin/vaults/CommandClone.ts index 593f6eedb..d3b3ea569 100644 --- a/src/bin/vaults/CommandClone.ts +++ b/src/bin/vaults/CommandClone.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -22,11 +23,13 @@ class CommandClone extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (vaultNameOrId, nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const nodesUtils = await import('../../nodes/utils'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); + const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -39,31 +42,37 @@ class CommandClone extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - const pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - const nodeMessage = new nodesPB.Node(); - const vaultCloneMessage = new vaultsPB.Clone(); - vaultCloneMessage.setVault(vaultMessage); - vaultCloneMessage.setNode(nodeMessage); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); - vaultMessage.setNameOrId(vaultNameOrId); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsClone(vaultCloneMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsClone({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + nameOrId: vaultNameOrId, + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandCreate.ts b/src/bin/vaults/CommandCreate.ts index 511d69ed0..3163fd8ad 100644 --- a/src/bin/vaults/CommandCreate.ts +++ b/src/bin/vaults/CommandCreate.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -16,8 +17,11 @@ class CommandCreate extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (vaultName, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -31,32 +35,42 @@ class CommandCreate extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); const response = await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsCreate(vaultMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsCreate({ + metadata: auth, + vaultName: vaultName, + }), meta, ); process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', - data: [`Vault ${response.getNameOrId()} created successfully`], + data: [`Vault ${response.vaultIdEncoded} created successfully`], }), ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandDelete.ts b/src/bin/vaults/CommandDelete.ts index e5bd72b32..b8e21b129 100644 --- a/src/bin/vaults/CommandDelete.ts +++ b/src/bin/vaults/CommandDelete.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,8 +16,11 @@ class CommandDelete extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (vaultName, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -30,26 +34,36 @@ class CommandDelete extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsDelete(vaultMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsDelete({ + metadata: auth, + nameOrId: vaultName, + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandList.ts b/src/bin/vaults/CommandList.ts index efd16a992..0d4cdb809 100644 --- a/src/bin/vaults/CommandList.ts +++ b/src/bin/vaults/CommandList.ts @@ -1,5 +1,5 @@ -import type { Metadata } from '@grpc/grpc-js'; import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,7 +15,12 @@ class CommandList extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const utilsPB = await import('../../proto/js/polykey/v1/utils/utils_pb'); + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -28,30 +33,37 @@ class CommandList extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const emptyMessage = new utilsPB.EmptyMessage(); - const data = await binUtils.retryAuthentication( - async (meta: Metadata) => { - const data: Array = []; - const stream = pkClient.grpcClient.vaultsList(emptyMessage, meta); - for await (const vault of stream) { - data.push(`${vault.getVaultName()}:\t\t${vault.getVaultId()}`); - } - return data; - }, - meta, - ); + const data = await binUtils.retryAuthentication(async (auth) => { + const data: Array = []; + const stream = await pkClient.rpcClient.methods.vaultsList({ + metadata: auth, + }); + for await (const vaultListMessage of stream) { + data.push( + `${vaultListMessage.vaultName}:\t\t${vaultListMessage.vaultIdEncoded}`, + ); + } + return data; + }, meta); process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', @@ -60,6 +72,7 @@ class CommandList extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandLog.ts b/src/bin/vaults/CommandLog.ts index 3177fae99..26d836252 100644 --- a/src/bin/vaults/CommandLog.ts +++ b/src/bin/vaults/CommandLog.ts @@ -1,5 +1,5 @@ -import type { Metadata } from '@grpc/grpc-js'; import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -11,18 +11,18 @@ class CommandLog extends CommandPolykey { this.name('log'); this.description('Get the Version History of a Vault'); this.argument('', 'Name of the vault to obtain the log from'); - this.option( - '-ci, --commit-id [commitId]', - 'Id for a specific commit to read from', - ); - this.option('-d, --depth [depth]', 'The number of commits to retreive'); + this.addOption(binOptions.commitId); + this.addOption(binOptions.depth); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); this.action(async (vault, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -36,43 +36,41 @@ class CommandLog extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vault); - const vaultsLogMessage = new vaultsPB.Log(); - vaultsLogMessage.setVault(vaultMessage); - vaultsLogMessage.setLogDepth(options.depth); - vaultsLogMessage.setCommitId(options.commitId ?? ''); - const data = await binUtils.retryAuthentication( - async (meta: Metadata) => { - const data: Array = []; - const stream = pkClient.grpcClient.vaultsLog( - vaultsLogMessage, - meta, - ); - for await (const commit of stream) { - const timestamp = commit.getTimeStamp(); - const date = timestamp!.toDate(); - data.push(`commit ${commit.getOid()}`); - data.push(`committer ${commit.getCommitter()}`); - data.push(`Date: ${date.toDateString()}`); - data.push(`${commit.getMessage()}`); - } - return data; - }, - meta, - ); + const data = await binUtils.retryAuthentication(async (auth) => { + const data: Array = []; + const logStream = await pkClient.rpcClient.methods.vaultsLog({ + metadata: auth, + nameOrId: vault, + depth: options.depth, + commitId: options.commitId, + }); + for await (const logEntryMessage of logStream) { + data.push(`commit ${logEntryMessage.commitId}`); + data.push(`committer ${logEntryMessage.committer}`); + data.push(`Date: ${logEntryMessage.timestamp}`); + data.push(`${logEntryMessage.message}`); + } + return data; + }, meta); process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', @@ -81,6 +79,7 @@ class CommandLog extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandPermissions.ts b/src/bin/vaults/CommandPermissions.ts index ccd011f1e..3d8da03da 100644 --- a/src/bin/vaults/CommandPermissions.ts +++ b/src/bin/vaults/CommandPermissions.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import * as binProcessors from '../utils/processors'; import * as binUtils from '../utils'; import CommandPolykey from '../CommandPolykey'; @@ -16,8 +17,11 @@ class CommandPermissions extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (vaultName, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -31,34 +35,35 @@ class CommandPermissions extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); - try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); - - await pkClient.start(); - const data: Array = []; await binUtils.retryAuthentication(async (auth) => { - const permissionStream = pkClient.grpcClient.vaultsPermissionGet( - vaultMessage, - auth, - ); + const permissionStream = + await pkClient.rpcClient.methods.vaultsPermissionGet({ + metadata: auth, + nameOrId: vaultName, + }); for await (const permission of permissionStream) { - const nodeId = permission.getNode()?.getNodeId(); - const actions = permission.getVaultPermissionsList().join(', '); + const nodeId = permission.nodeIdEncoded; + const actions = permission.vaultPermissionList.join(', '); data.push(`${nodeId}: ${actions}`); } return true; @@ -73,6 +78,7 @@ class CommandPermissions extends CommandPolykey { ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandPull.ts b/src/bin/vaults/CommandPull.ts index 72f509443..e1d0463c8 100644 --- a/src/bin/vaults/CommandPull.ts +++ b/src/bin/vaults/CommandPull.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -24,13 +25,13 @@ class CommandPull extends CommandPolykey { this.action( async (vaultNameOrId, targetNodeId: NodeId | undefined, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const nodesUtils = await import('../../nodes/utils'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' ); - const nodesPB = await import( - '../../proto/js/polykey/v1/nodes/nodes_pb' + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); + const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -43,38 +44,41 @@ class CommandPull extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - const pullVaultMessage = new vaultsPB.Vault(); - const nodeMessage = new nodesPB.Node(); - const vaultPullMessage = new vaultsPB.Pull(); - vaultPullMessage.setVault(vaultMessage); - vaultMessage.setNameOrId(vaultNameOrId); - if (targetNodeId != null) { - nodeMessage.setNodeId(nodesUtils.encodeNodeId(targetNodeId)); - vaultPullMessage.setNode(nodeMessage); - } - if (options.pullVault) { - vaultPullMessage.setPullVault(pullVaultMessage); - pullVaultMessage.setNameOrId(options.pullVault); - } await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsPull(vaultPullMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsPull({ + metadata: auth, + nodeIdEncoded: + targetNodeId != null + ? nodesUtils.encodeNodeId(targetNodeId) + : undefined, + nameOrId: vaultNameOrId, + pullVault: options.pullVault, + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }, ); diff --git a/src/bin/vaults/CommandRename.ts b/src/bin/vaults/CommandRename.ts index 9df0f2d75..41a9f5e98 100644 --- a/src/bin/vaults/CommandRename.ts +++ b/src/bin/vaults/CommandRename.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -16,8 +17,11 @@ class CommandRename extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (vaultName, newVaultName, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -31,29 +35,37 @@ class CommandRename extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - const vaultRenameMessage = new vaultsPB.Rename(); - vaultRenameMessage.setVault(vaultMessage); - vaultMessage.setNameOrId(vaultName); - vaultRenameMessage.setNewName(newVaultName); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.vaultsRename(vaultRenameMessage, auth), + (auth) => + pkClient.rpcClient.methods.vaultsRename({ + metadata: auth, + nameOrId: vaultName, + newName: newVaultName, + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandScan.ts b/src/bin/vaults/CommandScan.ts index eb827a845..05a5be9ec 100644 --- a/src/bin/vaults/CommandScan.ts +++ b/src/bin/vaults/CommandScan.ts @@ -1,4 +1,5 @@ -import type { Metadata } from '@grpc/grpc-js'; +import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -15,8 +16,12 @@ class CommandScan extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (nodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); - + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' + ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -25,39 +30,43 @@ class CommandScan extends CommandPolykey { this.fs, this.logger.getChild(binProcessors.processClientOptions.name), ); - const client = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, - host: clientOptions.clientHost, - port: clientOptions.clientPort, - logger: this.logger.getChild(PolykeyClient.name), - }); - const meta = await binProcessors.processAuthentication( options.passwordFile, this.fs, ); - + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; + this.exitHandlers.handlers.push(async () => { + if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); + }); try { - const grpcClient = client.grpcClient; - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodeId); - - const data = await binUtils.retryAuthentication( - async (meta: Metadata) => { - const data: Array = []; - const stream = grpcClient.vaultsScan(nodeMessage, meta); - for await (const vault of stream) { - const vaultName = vault.getVaultName(); - const vaultIdEncoded = vault.getVaultId(); - const permissions = vault.getVaultPermissionsList().join(','); - data.push(`${vaultName}\t\t${vaultIdEncoded}\t\t${permissions}`); - } - return data; - }, - meta, - ); - + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], + host: clientOptions.clientHost, + port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, + logger: this.logger.getChild(PolykeyClient.name), + }); + const data = await binUtils.retryAuthentication(async (auth) => { + const data: Array = []; + const stream = await pkClient.rpcClient.methods.vaultsScan({ + metadata: auth, + nodeIdEncoded: nodeId, + }); + for await (const vault of stream) { + const vaultName = vault.vaultName; + const vaultIdEncoded = vault.vaultIdEncoded; + const permissions = vault.permissions.join(','); + data.push(`${vaultName}\t\t${vaultIdEncoded}\t\t${permissions}`); + } + return data; + }, meta); process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', @@ -65,7 +74,8 @@ class CommandScan extends CommandPolykey { }), ); } finally { - await client.stop(); + if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandShare.ts b/src/bin/vaults/CommandShare.ts index 7b7358697..07f4ea1f1 100644 --- a/src/bin/vaults/CommandShare.ts +++ b/src/bin/vaults/CommandShare.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -22,11 +23,13 @@ class CommandShare extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (vaultName, nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const nodesUtils = await import('../../nodes/utils'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); + const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -39,36 +42,38 @@ class CommandShare extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); - const vaultsPermissionsList = new vaultsPB.Permissions(); - vaultsPermissionsList.setVault(vaultMessage); - vaultsPermissionsList.setNode(nodeMessage); - vaultsPermissionsList.setVaultPermissionsList(['pull', 'clone']); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.vaultsPermissionSet( - vaultsPermissionsList, - auth, - ), + pkClient.rpcClient.methods.vaultsPermissionSet({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + nameOrId: vaultName, + vaultPermissionList: ['pull', 'clone'], + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandUnshare.ts b/src/bin/vaults/CommandUnshare.ts index 66a800a3a..b786034cc 100644 --- a/src/bin/vaults/CommandUnshare.ts +++ b/src/bin/vaults/CommandUnshare.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import type { NodeId } from '../../ids/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; @@ -22,11 +23,13 @@ class CommandUnshare extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (vaultName, nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const nodesUtils = await import('../../nodes/utils'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); + const nodesUtils = await import('../../nodes/utils'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -39,36 +42,38 @@ class CommandUnshare extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultsPermissionsMessage = new vaultsPB.Permissions(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); - const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); - vaultsPermissionsMessage.setVault(vaultMessage); - vaultsPermissionsMessage.setNode(nodeMessage); - vaultsPermissionsMessage.setVaultPermissionsList(['clone', 'pull']); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.vaultsPermissionUnset( - vaultsPermissionsMessage, - auth, - ), + pkClient.rpcClient.methods.vaultsPermissionUnset({ + metadata: auth, + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + nameOrId: vaultName, + vaultPermissionList: ['clone', 'pull'], + }), meta, ); } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/bin/vaults/CommandVersion.ts b/src/bin/vaults/CommandVersion.ts index e2262d4fa..17458ccbe 100644 --- a/src/bin/vaults/CommandVersion.ts +++ b/src/bin/vaults/CommandVersion.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type WebSocketClient from '../../websockets/WebSocketClient'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; @@ -16,8 +17,11 @@ class CommandVersion extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (vault, versionId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const vaultsPB = await import( - '../../proto/js/polykey/v1/vaults/vaults_pb' + const { default: WebSocketClient } = await import( + '../../websockets/WebSocketClient' + ); + const { clientManifest } = await import( + '../../client/handlers/clientManifest' ); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -31,26 +35,32 @@ class CommandVersion extends CommandPolykey { options.passwordFile, this.fs, ); - let pkClient: PolykeyClient; + let webSocketClient: WebSocketClient; + let pkClient: PolykeyClient; this.exitHandlers.handlers.push(async () => { if (pkClient != null) await pkClient.stop(); + if (webSocketClient != null) await webSocketClient.destroy(true); }); try { - pkClient = await PolykeyClient.createPolykeyClient({ - nodePath: options.nodePath, - nodeId: clientOptions.nodeId, + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [clientOptions.nodeId], host: clientOptions.clientHost, port: clientOptions.clientPort, + logger: this.logger.getChild(WebSocketClient.name), + }); + pkClient = await PolykeyClient.createPolykeyClient({ + streamFactory: () => webSocketClient.startConnection(), + nodePath: options.nodePath, + manifest: clientManifest, logger: this.logger.getChild(PolykeyClient.name), }); - const vaultMessage = new vaultsPB.Vault(); - const vaultsVersionMessage = new vaultsPB.Version(); - vaultMessage.setNameOrId(vault); - vaultsVersionMessage.setVault(vaultMessage); - vaultsVersionMessage.setVersionId(versionId); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.vaultsVersion(vaultsVersionMessage, auth), + pkClient.rpcClient.methods.vaultsVersion({ + metadata: auth, + nameOrId: vault, + versionId: versionId, + }), meta, ); /** @@ -62,6 +72,7 @@ class CommandVersion extends CommandPolykey { */ } finally { if (pkClient! != null) await pkClient.stop(); + if (webSocketClient! != null) await webSocketClient.destroy(); } }); } diff --git a/src/client/GRPCClientClient.ts b/src/client/GRPCClientClient.ts deleted file mode 100644 index 3e6b55975..000000000 --- a/src/client/GRPCClientClient.ts +++ /dev/null @@ -1,1078 +0,0 @@ -import type { Interceptor } from '@grpc/grpc-js'; -import type { ClientReadableStream } from '@grpc/grpc-js/build/src/call'; -import type { AsyncGeneratorReadableStreamClient } from '../grpc/types'; -import type { Session } from '../sessions'; -import type { NodeId } from '../ids/types'; -import type { Host, Port, ProxyConfig, TLSConfig } from '../network/types'; -import type * as utilsPB from '../proto/js/polykey/v1/utils/utils_pb'; -import type * as agentPB from '../proto/js/polykey/v1/agent/agent_pb'; -import type * as vaultsPB from '../proto/js/polykey/v1/vaults/vaults_pb'; -import type * as nodesPB from '../proto/js/polykey/v1/nodes/nodes_pb'; -import type * as notificationsPB from '../proto/js/polykey/v1/notifications/notifications_pb'; -import type * as gestaltsPB from '../proto/js/polykey/v1/gestalts/gestalts_pb'; -import type * as identitiesPB from '../proto/js/polykey/v1/identities/identities_pb'; -import type * as keysPB from '../proto/js/polykey/v1/keys/keys_pb'; -import type * as permissionsPB from '../proto/js/polykey/v1/permissions/permissions_pb'; -import type * as secretsPB from '../proto/js/polykey/v1/secrets/secrets_pb'; -import type { Timer } from '../types'; -import { CreateDestroy, ready } from '@matrixai/async-init/dist/CreateDestroy'; -import Logger from '@matrixai/logger'; -import * as clientErrors from './errors'; -import * as clientUtils from './utils'; -import { ClientServiceClient } from '../proto/js/polykey/v1/client_service_grpc_pb'; -import { GRPCClient } from '../grpc'; -import * as grpcUtils from '../grpc/utils'; - -interface GRPCClientClient extends CreateDestroy {} -@CreateDestroy() -class GRPCClientClient extends GRPCClient { - /** - * Creates GRPCClientClient - * This connects to the client service - * This connection should be encrypted with TLS with or without - * client authentication - */ - static async createGRPCClientClient({ - nodeId, - host, - port, - tlsConfig, - proxyConfig, - session, - timer, - destroyCallback = async () => {}, - logger = new Logger(this.name), - }: { - nodeId: NodeId; - host: Host; - port: Port; - tlsConfig?: Partial; - proxyConfig?: ProxyConfig; - session?: Session; - timer?: Timer; - destroyCallback?: () => Promise; - logger?: Logger; - }): Promise { - const interceptors: Array = []; - if (session != null) { - interceptors.push(clientUtils.sessionInterceptor(session)); - } - const { client, serverCertChain, flowCountInterceptor } = - await super.createClient({ - clientConstructor: ClientServiceClient, - nodeId, - host, - port, - tlsConfig, - proxyConfig, - timer, - interceptors, - logger, - }); - return new this({ - client, - nodeId, - host, - port, - tlsConfig, - proxyConfig, - serverCertChain, - flowCountInterceptor, - destroyCallback, - logger, - }); - } - - public async destroy() { - await super.destroy(); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public agentStatus(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.agentStatus.name, - }, - this.client.agentStatus, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public agentStop(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.agentStop.name, - }, - this.client.agentStop, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public agentUnlock(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.agentUnlock.name, - }, - this.client.agentUnlock, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public agentLockAll(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.agentLockAll.name, - }, - this.client.agentLockAll, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsList( - ...args - ): AsyncGeneratorReadableStreamClient< - vaultsPB.List, - ClientReadableStream - > { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsList.name, - }, - this.client.vaultsList, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsCreate(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsCreate.name, - }, - this.client.vaultsCreate, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsRename(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsRename.name, - }, - this.client.vaultsRename, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsDelete(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsDelete.name, - }, - this.client.vaultsDelete, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsClone(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsClone.name, - }, - this.client.vaultsClone, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsPull(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsPull.name, - }, - this.client.vaultsPull, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsScan( - ...args - ): AsyncGeneratorReadableStreamClient< - vaultsPB.List, - ClientReadableStream - > { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsScan.name, - }, - this.client.vaultsScan, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsPermissionGet(...args) { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsPermissionGet.name, - }, - this.client.vaultsPermissionGet, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsPermissionSet(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsPermissionSet.name, - }, - this.client.vaultsPermissionSet, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsPermissionUnset(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsPermissionUnset.name, - }, - this.client.vaultsPermissionUnset, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsSecretsList( - ...args - ): AsyncGeneratorReadableStreamClient< - secretsPB.Secret, - ClientReadableStream - > { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsSecretsList.name, - }, - this.client.vaultsSecretsList, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsSecretsMkdir(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsSecretsMkdir.name, - }, - this.client.vaultsSecretsMkdir, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsSecretsDelete(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsSecretsDelete.name, - }, - this.client.vaultsSecretsDelete, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsSecretsEdit(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsSecretsEdit.name, - }, - this.client.vaultsSecretsEdit, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsSecretsGet(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsSecretsGet.name, - }, - this.client.vaultsSecretsGet, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsSecretsStat(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsSecretsStat.name, - }, - this.client.vaultsSecretsStat, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsSecretsRename(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsSecretsRename.name, - }, - this.client.vaultsSecretsRename, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsSecretsNew(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsSecretsNew.name, - }, - this.client.vaultsSecretsNew, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsSecretsNewDir(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsSecretsNewDir.name, - }, - this.client.vaultsSecretsNewDir, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsVersion(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsVersion.name, - }, - this.client.vaultsVersion, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public vaultsLog( - ...args - ): AsyncGeneratorReadableStreamClient< - vaultsPB.LogEntry, - ClientReadableStream - > { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.vaultsLog.name, - }, - this.client.vaultsLog, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysKeyPair(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysKeyPair.name, - }, - this.client.keysKeyPair, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysPublicKey(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysPublicKey.name, - }, - this.client.keysPublicKey, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysKeyPairReset(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysKeyPairReset.name, - }, - this.client.keysKeyPairReset, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysKeyPairRenew(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysKeyPairRenew.name, - }, - this.client.keysKeyPairRenew, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysEncrypt(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysEncrypt.name, - }, - this.client.keysEncrypt, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysDecrypt(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysDecrypt.name, - }, - this.client.keysDecrypt, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysSign(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysSign.name, - }, - this.client.keysSign, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysVerify(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysVerify.name, - }, - this.client.keysVerify, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysPasswordChange(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysPasswordChange.name, - }, - this.client.keysPasswordChange, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysCertsGet(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysCertsGet.name, - }, - this.client.keysCertsGet, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public keysCertsChainGet( - ...args - ): AsyncGeneratorReadableStreamClient< - keysPB.Certificate, - ClientReadableStream - > { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.keysCertsGet.name, - }, - this.client.keysCertsChainGet, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsGestaltList( - ...args - ): AsyncGeneratorReadableStreamClient< - gestaltsPB.Gestalt, - ClientReadableStream - > { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsGestaltList.name, - }, - this.client.gestaltsGestaltList, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsGestaltGetByIdentity(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsGestaltGetByIdentity.name, - }, - this.client.gestaltsGestaltGetByIdentity, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsGestaltGetByNode(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsGestaltGetByNode.name, - }, - this.client.gestaltsGestaltGetByNode, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsDiscoveryByNode(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsDiscoveryByNode.name, - }, - this.client.gestaltsDiscoveryByNode, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsDiscoveryByIdentity(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsDiscoveryByIdentity.name, - }, - this.client.gestaltsDiscoveryByIdentity, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsActionsGetByNode(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsActionsGetByNode.name, - }, - this.client.gestaltsActionsGetByNode, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsActionsGetByIdentity(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsActionsGetByIdentity.name, - }, - this.client.gestaltsActionsGetByIdentity, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsActionsSetByNode(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsActionsSetByNode.name, - }, - this.client.gestaltsActionsSetByNode, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsActionsSetByIdentity(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsActionsSetByIdentity.name, - }, - this.client.gestaltsActionsSetByIdentity, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsActionsUnsetByNode(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsActionsUnsetByNode.name, - }, - this.client.gestaltsActionsUnsetByNode, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsActionsUnsetByIdentity(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsActionsUnsetByIdentity.name, - }, - this.client.gestaltsActionsUnsetByIdentity, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsGestaltTrustByNode(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsGestaltTrustByNode.name, - }, - this.client.gestaltsGestaltTrustByNode, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public gestaltsGestaltTrustByIdentity(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.gestaltsGestaltTrustByIdentity.name, - }, - this.client.gestaltsGestaltTrustByIdentity, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesTokenPut(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesTokenPut.name, - }, - this.client.identitiesTokenPut, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesTokenGet(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesTokenGet.name, - }, - this.client.identitiesTokenGet, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesTokenDelete(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesTokenDelete.name, - }, - this.client.identitiesTokenDelete, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesProvidersList(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesProvidersList.name, - }, - this.client.identitiesProvidersList, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public nodesAdd(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.nodesAdd.name, - }, - this.client.nodesAdd, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public nodesPing(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.nodesPing.name, - }, - this.client.nodesPing, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public nodesClaim(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.nodesClaim.name, - }, - this.client.nodesClaim, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public nodesFind(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.nodesFind.name, - }, - this.client.nodesFind, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public nodesGetAll(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.nodesGetAll.name, - }, - this.client.nodesGetAll, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public nodesListConnections( - ...args - ): AsyncGeneratorReadableStreamClient< - nodesPB.NodeConnection, - ClientReadableStream - > { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.nodesListConnections.name, - }, - this.client.nodesListConnections, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesAuthenticate(...args) { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesAuthenticate.name, - }, - this.client.identitiesAuthenticate, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesInfoConnectedGet(...args) { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesInfoConnectedGet.name, - }, - this.client.identitiesInfoConnectedGet, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesInfoGet(...args) { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesInfoGet.name, - }, - this.client.identitiesInfoGet, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesClaim(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesClaim.name, - }, - this.client.identitiesClaim, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesAuthenticatedGet(...args) { - return grpcUtils.promisifyReadableStreamCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesAuthenticatedGet.name, - }, - this.client.identitiesAuthenticatedGet, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesInvite(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.identitiesInvite.name, - }, - this.client.identitiesInvite, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public notificationsSend(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.notificationsSend.name, - }, - this.client.notificationsSend, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public notificationsRead(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.notificationsRead.name, - }, - this.client.notificationsRead, - )(...args); - } - - @ready(new clientErrors.ErrorClientClientDestroyed()) - public notificationsClear(...args) { - return grpcUtils.promisifyUnaryCall( - this.client, - { - nodeId: this.nodeId, - host: this.host, - port: this.port, - command: this.notificationsClear.name, - }, - this.client.notificationsClear, - )(...args); - } -} - -export default GRPCClientClient; diff --git a/src/client/errors.ts b/src/client/errors.ts index 13baa73b0..e9acc8027 100644 --- a/src/client/errors.ts +++ b/src/client/errors.ts @@ -2,11 +2,6 @@ import { ErrorPolykey, sysexits } from '../errors'; class ErrorClient extends ErrorPolykey {} -class ErrorClientClientDestroyed extends ErrorClient { - static description = 'GRPCClientClient has been destroyed'; - exitCode = sysexits.USAGE; -} - class ErrorClientAuthMissing extends ErrorClient { static description = 'Authorisation metadata is required but missing'; exitCode = sysexits.NOPERM; @@ -24,7 +19,6 @@ class ErrorClientAuthDenied extends ErrorClient { export { ErrorClient, - ErrorClientClientDestroyed, ErrorClientAuthMissing, ErrorClientAuthFormat, ErrorClientAuthDenied, diff --git a/src/client/handlers/agentLockAll.ts b/src/client/handlers/agentLockAll.ts new file mode 100644 index 000000000..b5c0e4d14 --- /dev/null +++ b/src/client/handlers/agentLockAll.ts @@ -0,0 +1,21 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { DB } from '@matrixai/db'; +import type SessionManager from '../../sessions/SessionManager'; +import { UnaryHandler } from '../../rpc/handlers'; + +class AgentLockAllHandler extends UnaryHandler< + { + sessionManager: SessionManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle(): Promise { + const { db, sessionManager } = this.container; + await db.withTransactionF((tran) => sessionManager.resetKey(tran)); + return {}; + } +} + +export { AgentLockAllHandler }; diff --git a/src/client/handlers/agentStatus.ts b/src/client/handlers/agentStatus.ts new file mode 100644 index 000000000..4ffb8908f --- /dev/null +++ b/src/client/handlers/agentStatus.ts @@ -0,0 +1,35 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { StatusResultMessage } from './types'; +import type PolykeyAgent from '../../PolykeyAgent'; +import * as nodesUtils from '../../nodes/utils'; +import * as keysUtils from '../../keys/utils'; +import { UnaryHandler } from '../../rpc/handlers'; + +class AgentStatusHandler extends UnaryHandler< + { + pkAgentProm: Promise; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle(): Promise> { + const { pkAgentProm } = this.container; + const pkAgent = await pkAgentProm; + return { + pid: process.pid, + nodeIdEncoded: nodesUtils.encodeNodeId(pkAgent.keyRing.getNodeId()), + clientHost: pkAgent.webSocketServerClient.getHost(), + clientPort: pkAgent.webSocketServerClient.getPort(), + proxyHost: pkAgent.proxy.getProxyHost(), + proxyPort: pkAgent.proxy.getProxyPort(), + agentHost: pkAgent.grpcServerAgent.getHost(), + agentPort: pkAgent.grpcServerAgent.getPort(), + forwardHost: pkAgent.proxy.getForwardHost(), + forwardPort: pkAgent.proxy.getForwardPort(), + publicKeyJwk: keysUtils.publicKeyToJWK(pkAgent.keyRing.keyPair.publicKey), + certChainPEM: await pkAgent.certManager.getCertPEMsChainPEM(), + }; + } +} + +export { AgentStatusHandler }; diff --git a/src/client/handlers/agentStop.ts b/src/client/handlers/agentStop.ts new file mode 100644 index 000000000..9ad27cf69 --- /dev/null +++ b/src/client/handlers/agentStop.ts @@ -0,0 +1,28 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type PolykeyAgent from '../../PolykeyAgent'; +import { running, status } from '@matrixai/async-init'; +import { UnaryHandler } from '../../rpc/handlers'; + +class AgentStopHandler extends UnaryHandler< + { + pkAgentProm: Promise; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle(): Promise { + const { pkAgentProm } = this.container; + const pkAgent = await pkAgentProm; + // If not running or in stopping status, then respond successfully + if (!pkAgent[running] || pkAgent[status] === 'stopping') { + return {}; + } + // Stop PK agent in the background, allow the RPC time to respond + setTimeout(async () => { + await pkAgent.stop(); + }, 500); + return {}; + } +} + +export { AgentStopHandler }; diff --git a/src/client/handlers/agentUnlock.ts b/src/client/handlers/agentUnlock.ts new file mode 100644 index 000000000..622c0f9e7 --- /dev/null +++ b/src/client/handlers/agentUnlock.ts @@ -0,0 +1,18 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { ContainerType } from 'rpc/types'; +import { UnaryHandler } from '../../rpc/handlers'; + +class AgentUnlockHandler extends UnaryHandler< + ContainerType, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle(): Promise { + // This is a NOP handler, + // authentication and unlocking is handled via middleware. + // Failure to authenticate will be an error from the middleware layer. + return {}; + } +} + +export { AgentUnlockHandler }; diff --git a/src/client/handlers/clientManifest.ts b/src/client/handlers/clientManifest.ts new file mode 100644 index 000000000..c37741dfc --- /dev/null +++ b/src/client/handlers/clientManifest.ts @@ -0,0 +1,548 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { + ActionsListMessage, + AddressMessage, + AuthProcessMessage, + CertMessage, + ClaimIdMessage, + ClaimNodeMessage, + DataMessage, + DecryptMessage, + GestaltMessage, + IdentityInfoMessage, + IdentityMessage, + KeyPairMessage, + NodeConnectionMessage, + NodeIdMessage, + NodesAddMessage, + NodesGetMessage, + NotificationMessage, + NotificationReadMessage, + PasswordMessage, + ProviderSearchMessage, + PublicKeyMessage, + SetIdentityActionMessage, + SetNodeActionMessage, + SignatureMessage, + StatusResultMessage, + SuccessMessage, + TokenMessage, + VerifySignatureMessage, + CloneMessage, + ContentMessage, + LogEntryMessage, + NotificationSendMessage, + PermissionSetMessage, + SecretContentMessage, + SecretDirMessage, + SecretIdentifierMessage, + SecretMkdirMessage, + SecretNameMessage, + SecretRenameMessage, + SecretStatMessage, + VaultIdentifierMessage, + VaultIdMessage, + VaultListMessage, + VaultNameMessage, + VaultPermissionMessage, + VaultsLatestVersionMessage, + VaultsLogMessage, + VaultsPullMessage, + VaultsRenameMessage, + VaultsScanMessage, + VaultsVersionMessage, +} from './types'; +import type { GestaltAction } from '../../gestalts/types'; +import { ServerCaller, UnaryCaller } from '../../rpc/callers'; + +const agentLockAll = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const agentStatus = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const agentStop = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const agentUnlock = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsActionsGetByIdentity = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult<{ + actionsList: Array; + }> +>(); + +const gestaltsActionsGetByNode = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsActionsSetByIdentity = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsActionsSetByNode = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsActionsUnsetByIdentity = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsActionsUnsetByNode = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsDiscoveryByIdentity = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsDiscoveryByNode = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsGestaltGetByIdentity = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsGestaltGetByNode = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsGestaltList = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsGestaltTrustByIdentity = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const gestaltsGestaltTrustByNode = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const identitiesAuthenticate = new ServerCaller< + ClientRPCRequestParams<{ + providerId: string; + }>, + ClientRPCResponseResult +>(); + +const identitiesAuthenticatedGet = new ServerCaller< + ClientRPCRequestParams<{ + providerId?: string; + }>, + ClientRPCResponseResult +>(); + +const identitiesClaim = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const identitiesInfoConnectedGet = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const identitiesInfoGet = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const identitiesInvite = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const identitiesProvidersList = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult<{ + providerIds: Array; + }> +>(); + +const identitiesTokenDelete = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const identitiesTokenGet = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult> +>(); + +const identitiesTokenPut = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysCertsChainGet = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysCertsGet = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysDecrypt = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysEncrypt = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysKeyPair = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysKeyPairRenew = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysKeyPairReset = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysPasswordChange = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysPublicKey = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysSign = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const keysVerify = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const nodesAdd = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const nodesClaim = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const nodesFind = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const nodesGetAll = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const nodesListConnections = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const nodesPing = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const notificationsClear = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const notificationsRead = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const notificationsSend = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsClone = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsCreate = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsDelete = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsList = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsLog = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsPermissionGet = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsPermissionSet = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsPermissionUnset = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsPull = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsRename = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsScan = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsSecretsDelete = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsSecretsEdit = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsSecretsGet = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsSecretsList = new ServerCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsSecretsMkdir = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsSecretsNew = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsSecretsNewDir = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsSecretsRename = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsSecretsStat = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +const vaultsVersion = new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult +>(); + +// No type used here, it will override type inference +const clientManifest = { + agentLockAll, + agentStatus, + agentStop, + agentUnlock, + gestaltsActionsGetByIdentity, + gestaltsActionsGetByNode, + gestaltsActionsSetByIdentity, + gestaltsActionsSetByNode, + gestaltsActionsUnsetByIdentity, + gestaltsActionsUnsetByNode, + gestaltsDiscoveryByIdentity, + gestaltsDiscoveryByNode, + gestaltsGestaltGetByIdentity, + gestaltsGestaltGetByNode, + gestaltsGestaltList, + gestaltsGestaltTrustByIdentity, + gestaltsGestaltTrustByNode, + identitiesAuthenticate, + identitiesAuthenticatedGet, + identitiesClaim, + identitiesInfoConnectedGet, + identitiesInfoGet, + identitiesInvite, + identitiesProvidersList, + identitiesTokenDelete, + identitiesTokenGet, + identitiesTokenPut, + keysCertsChainGet, + keysCertsGet, + keysDecrypt, + keysEncrypt, + keysKeyPair, + keysKeyPairRenew, + keysKeyPairReset, + keysPasswordChange, + keysPublicKey, + keysSign, + keysVerify, + nodesAdd, + nodesClaim, + nodesFind, + nodesGetAll, + nodesListConnections, + nodesPing, + notificationsClear, + notificationsRead, + notificationsSend, + vaultsClone, + vaultsCreate, + vaultsDelete, + vaultsList, + vaultsLog, + vaultsPermissionGet, + vaultsPermissionSet, + vaultsPermissionUnset, + vaultsPull, + vaultsRename, + vaultsScan, + vaultsSecretsDelete, + vaultsSecretsEdit, + vaultsSecretsGet, + vaultsSecretsList, + vaultsSecretsMkdir, + vaultsSecretsNew, + vaultsSecretsNewDir, + vaultsSecretsRename, + vaultsSecretsStat, + vaultsVersion, +}; + +export { + clientManifest, + agentLockAll, + agentStatus, + agentStop, + agentUnlock, + gestaltsActionsGetByIdentity, + gestaltsActionsGetByNode, + gestaltsActionsSetByIdentity, + gestaltsActionsSetByNode, + gestaltsActionsUnsetByIdentity, + gestaltsActionsUnsetByNode, + gestaltsDiscoveryByIdentity, + gestaltsDiscoveryByNode, + gestaltsGestaltGetByIdentity, + gestaltsGestaltGetByNode, + gestaltsGestaltList, + gestaltsGestaltTrustByIdentity, + gestaltsGestaltTrustByNode, + identitiesAuthenticate, + identitiesAuthenticatedGet, + identitiesClaim, + identitiesInfoConnectedGet, + identitiesInfoGet, + identitiesInvite, + identitiesProvidersList, + identitiesTokenDelete, + identitiesTokenGet, + identitiesTokenPut, + keysCertsChainGet, + keysCertsGet, + keysDecrypt, + keysEncrypt, + keysKeyPair, + keysKeyPairRenew, + keysKeyPairReset, + keysPasswordChange, + keysPublicKey, + keysSign, + keysVerify, + nodesAdd, + nodesClaim, + nodesFind, + nodesGetAll, + nodesListConnections, + nodesPing, + notificationsClear, + notificationsRead, + notificationsSend, + vaultsClone, + vaultsCreate, + vaultsDelete, + vaultsList, + vaultsLog, + vaultsPermissionGet, + vaultsPermissionSet, + vaultsPermissionUnset, + vaultsPull, + vaultsRename, + vaultsScan, + vaultsSecretsDelete, + vaultsSecretsEdit, + vaultsSecretsGet, + vaultsSecretsList, + vaultsSecretsMkdir, + vaultsSecretsNew, + vaultsSecretsNewDir, + vaultsSecretsRename, + vaultsSecretsStat, + vaultsVersion, +}; diff --git a/src/client/handlers/gestaltsActionsGetByIdentity.ts b/src/client/handlers/gestaltsActionsGetByIdentity.ts new file mode 100644 index 000000000..1e7d8e3aa --- /dev/null +++ b/src/client/handlers/gestaltsActionsGetByIdentity.ts @@ -0,0 +1,57 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; +import type { DB } from '@matrixai/db'; +import type { IdentityId, ProviderId } from 'ids/index'; +import type { GestaltAction } from 'gestalts/types'; +import type { IdentityMessage } from './types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsActionsGetByIdentityHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult<{ + actionsList: Array; + }> +> { + public async handle(input: ClientRPCRequestParams): Promise< + ClientRPCResponseResult<{ + actionsList: Array; + }> + > { + const { db, gestaltGraph } = this.container; + const { + providerId, + identityId, + }: { providerId: ProviderId; identityId: IdentityId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + identityId: input.identityId, + }, + ); + const result = await db.withTransactionF((tran) => + gestaltGraph.getGestaltActions( + ['identity', [providerId, identityId]], + tran, + ), + ); + + return { + actionsList: Object.keys(result) as Array, + }; + } +} + +export { GestaltsActionsGetByIdentityHandler }; diff --git a/src/client/handlers/gestaltsActionsGetByNode.ts b/src/client/handlers/gestaltsActionsGetByNode.ts new file mode 100644 index 000000000..631c542b3 --- /dev/null +++ b/src/client/handlers/gestaltsActionsGetByNode.ts @@ -0,0 +1,45 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; +import type { DB } from '@matrixai/db'; +import type { GestaltAction } from 'gestalts/types'; +import type { NodeId } from 'ids/index'; +import type { ActionsListMessage, NodeIdMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsActionsGetByNodeHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, gestaltGraph } = this.container; + const { nodeId }: { nodeId: NodeId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + const result = await db.withTransactionF((tran) => + gestaltGraph.getGestaltActions(['node', nodeId], tran), + ); + + return { + actionsList: Object.keys(result) as Array, + }; + } +} + +export { GestaltsActionsGetByNodeHandler }; diff --git a/src/client/handlers/gestaltsActionsSetByIdentity.ts b/src/client/handlers/gestaltsActionsSetByIdentity.ts new file mode 100644 index 000000000..03a5eb6ed --- /dev/null +++ b/src/client/handlers/gestaltsActionsSetByIdentity.ts @@ -0,0 +1,58 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { DB } from '@matrixai/db'; +import type { IdentityId, ProviderId } from 'ids/index'; +import type { GestaltAction } from '../../gestalts/types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; +import type { SetIdentityActionMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsActionsSetByIdentityHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { db, gestaltGraph } = this.container; + const { + action, + providerId, + identityId, + }: { + action: GestaltAction; + providerId: ProviderId; + identityId: IdentityId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['action'], () => validationUtils.parseGestaltAction(value)], + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + action: input.action, + providerId: input.providerId, + identityId: input.identityId, + }, + ); + await db.withTransactionF((tran) => + gestaltGraph.setGestaltAction( + ['identity', [providerId, identityId]], + action, + tran, + ), + ); + return {}; + } +} + +export { GestaltsActionsSetByIdentityHandler }; diff --git a/src/client/handlers/gestaltsActionsSetByNode.ts b/src/client/handlers/gestaltsActionsSetByNode.ts new file mode 100644 index 000000000..3af199b26 --- /dev/null +++ b/src/client/handlers/gestaltsActionsSetByNode.ts @@ -0,0 +1,45 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { DB } from '@matrixai/db'; +import type { GestaltAction } from '../../gestalts/types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; +import type { NodeId } from 'ids/index'; +import type { SetNodeActionMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsActionsSetByNodeHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { db, gestaltGraph } = this.container; + const { nodeId, action }: { nodeId: NodeId; action: GestaltAction } = + validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + [['action'], () => validationUtils.parseGestaltAction(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + action: input.action, + }, + ); + await db.withTransactionF((tran) => + gestaltGraph.setGestaltAction(['node', nodeId], action, tran), + ); + return {}; + } +} + +export { GestaltsActionsSetByNodeHandler }; diff --git a/src/client/handlers/gestaltsActionsUnsetByIdentity.ts b/src/client/handlers/gestaltsActionsUnsetByIdentity.ts new file mode 100644 index 000000000..9ba00ac71 --- /dev/null +++ b/src/client/handlers/gestaltsActionsUnsetByIdentity.ts @@ -0,0 +1,58 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { DB } from '@matrixai/db'; +import type { IdentityId, ProviderId } from 'ids/index'; +import type { GestaltAction } from '../../gestalts/types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; +import type { SetIdentityActionMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsActionsUnsetByIdentityHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { db, gestaltGraph } = this.container; + const { + action, + providerId, + identityId, + }: { + action: GestaltAction; + providerId: ProviderId; + identityId: IdentityId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['action'], () => validationUtils.parseGestaltAction(value)], + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + action: input.action, + providerId: input.providerId, + identityId: input.identityId, + }, + ); + await db.withTransactionF((tran) => + gestaltGraph.unsetGestaltAction( + ['identity', [providerId, identityId]], + action, + tran, + ), + ); + return {}; + } +} + +export { GestaltsActionsUnsetByIdentityHandler }; diff --git a/src/client/handlers/gestaltsActionsUnsetByNode.ts b/src/client/handlers/gestaltsActionsUnsetByNode.ts new file mode 100644 index 000000000..d94eb3b65 --- /dev/null +++ b/src/client/handlers/gestaltsActionsUnsetByNode.ts @@ -0,0 +1,45 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { DB } from '@matrixai/db'; +import type { GestaltAction } from '../../gestalts/types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; +import type { NodeId } from 'ids/index'; +import type { SetNodeActionMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsActionsUnsetByNodeHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { db, gestaltGraph } = this.container; + const { nodeId, action }: { nodeId: NodeId; action: GestaltAction } = + validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + [['action'], () => validationUtils.parseGestaltAction(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + action: input.action, + }, + ); + await db.withTransactionF((tran) => + gestaltGraph.unsetGestaltAction(['node', nodeId], action, tran), + ); + return {}; + } +} + +export { GestaltsActionsUnsetByNodeHandler }; diff --git a/src/client/handlers/gestaltsDiscoveryByIdentity.ts b/src/client/handlers/gestaltsDiscoveryByIdentity.ts new file mode 100644 index 000000000..21eb7eb68 --- /dev/null +++ b/src/client/handlers/gestaltsDiscoveryByIdentity.ts @@ -0,0 +1,44 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { IdentityId, ProviderId } from 'ids/index'; +import type Discovery from '../../discovery/Discovery'; +import type { IdentityMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsDiscoveryByIdentityHandler extends UnaryHandler< + { + discovery: Discovery; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { discovery } = this.container; + const { + providerId, + identityId, + }: { providerId: ProviderId; identityId: IdentityId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + identityId: input.identityId, + }, + ); + + await discovery.queueDiscoveryByIdentity(providerId, identityId); + + return {}; + } +} + +export { GestaltsDiscoveryByIdentityHandler }; diff --git a/src/client/handlers/gestaltsDiscoveryByNode.ts b/src/client/handlers/gestaltsDiscoveryByNode.ts new file mode 100644 index 000000000..e97c180cb --- /dev/null +++ b/src/client/handlers/gestaltsDiscoveryByNode.ts @@ -0,0 +1,39 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { NodeId } from 'ids/index'; +import type Discovery from '../../discovery/Discovery'; +import type { NodeIdMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsDiscoveryByNodeHandler extends UnaryHandler< + { + discovery: Discovery; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { discovery } = this.container; + const { nodeId }: { nodeId: NodeId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + + await discovery.queueDiscoveryByNode(nodeId); + + return {}; + } +} + +export { GestaltsDiscoveryByNodeHandler }; diff --git a/src/client/handlers/gestaltsGestaltGetByIdentity.ts b/src/client/handlers/gestaltsGestaltGetByIdentity.ts new file mode 100644 index 000000000..5d564ea5c --- /dev/null +++ b/src/client/handlers/gestaltsGestaltGetByIdentity.ts @@ -0,0 +1,74 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { IdentityId, ProviderId } from 'ids/index'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; +import type { DB } from '@matrixai/db'; +import type { GestaltMessage, IdentityMessage } from 'client/handlers/types'; +import * as nodesUtils from '../../nodes/utils'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsGestaltGetByIdentityHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, gestaltGraph } = this.container; + const { + providerId, + identityId, + }: { providerId: ProviderId; identityId: IdentityId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + identityId: input.identityId, + }, + ); + const gestalt = await db.withTransactionF((tran) => + gestaltGraph.getGestaltByIdentity([providerId, identityId], tran), + ); + const gestaltMessage: GestaltMessage = { + gestalt: { + matrix: {}, + nodes: {}, + identities: {}, + }, + }; + // Mutating the object directly + const newGestalt = gestaltMessage.gestalt; + if (gestalt != null) { + newGestalt.identities = gestalt.identities; + for (const [key, value] of Object.entries(gestalt.nodes)) { + newGestalt.nodes[key] = { + nodeId: nodesUtils.encodeNodeId(value.nodeId), + }; + } + for (const keyA of Object.keys(gestalt.matrix)) { + let record = newGestalt.matrix[keyA]; + if (record == null) { + record = {}; + newGestalt.matrix[keyA] = record; + } + for (const keyB of Object.keys(gestalt.matrix[keyA])) { + record[keyB] = null; + } + } + } + return gestaltMessage; + } +} + +export { GestaltsGestaltGetByIdentityHandler }; diff --git a/src/client/handlers/gestaltsGestaltGetByNode.ts b/src/client/handlers/gestaltsGestaltGetByNode.ts new file mode 100644 index 000000000..0a8d09038 --- /dev/null +++ b/src/client/handlers/gestaltsGestaltGetByNode.ts @@ -0,0 +1,69 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; +import type { DB } from '@matrixai/db'; +import type { NodeId } from 'ids/index'; +import type { GestaltMessage, NodeIdMessage } from 'client/handlers/types'; +import * as nodesUtils from '../../nodes/utils'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsGestaltGetByNodeHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, gestaltGraph } = this.container; + const { nodeId }: { nodeId: NodeId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + const gestalt = await db.withTransactionF((tran) => + gestaltGraph.getGestaltByNode(nodeId, tran), + ); + const gestaltMessage: GestaltMessage = { + gestalt: { + matrix: {}, + nodes: {}, + identities: {}, + }, + }; + // Mutating the object directly + const newGestalt = gestaltMessage.gestalt; + if (gestalt != null) { + newGestalt.identities = gestalt.identities; + for (const [key, value] of Object.entries(gestalt.nodes)) { + newGestalt.nodes[key] = { + nodeId: nodesUtils.encodeNodeId(value.nodeId), + }; + } + for (const keyA of Object.keys(gestalt.matrix)) { + let record = newGestalt.matrix[keyA]; + if (record == null) { + record = {}; + newGestalt.matrix[keyA] = record; + } + for (const keyB of Object.keys(gestalt.matrix[keyA])) { + record[keyB] = null; + } + } + } + return gestaltMessage; + } +} + +export { GestaltsGestaltGetByNodeHandler }; diff --git a/src/client/handlers/gestaltsGestaltList.ts b/src/client/handlers/gestaltsGestaltList.ts new file mode 100644 index 000000000..7fec8bbd4 --- /dev/null +++ b/src/client/handlers/gestaltsGestaltList.ts @@ -0,0 +1,55 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { DB } from '@matrixai/db'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; +import type { GestaltMessage } from 'client/handlers/types'; +import * as nodesUtils from '../../nodes/utils'; +import { ServerHandler } from '../../rpc/handlers'; + +class GestaltsGestaltListHandler extends ServerHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle(): AsyncGenerator< + ClientRPCResponseResult + > { + const { db, gestaltGraph } = this.container; + yield* db.withTransactionG(async function* ( + tran, + ): AsyncGenerator> { + for await (const gestalt of gestaltGraph.getGestalts(tran)) { + const gestaltMessage: GestaltMessage = { + gestalt: { + matrix: {}, + nodes: {}, + identities: {}, + }, + }; + // Mutating the object directly + const newGestalt = gestaltMessage.gestalt; + newGestalt.identities = gestalt.identities; + for (const [key, value] of Object.entries(gestalt.nodes)) { + newGestalt.nodes[key] = { + nodeId: nodesUtils.encodeNodeId(value.nodeId), + }; + } + for (const keyA of Object.keys(gestalt.matrix)) { + let record = newGestalt.matrix[keyA]; + if (record == null) { + record = {}; + newGestalt.matrix[keyA] = record; + } + for (const keyB of Object.keys(gestalt.matrix[keyA])) { + record[keyB] = null; + } + } + yield gestaltMessage; + } + }); + } +} + +export { GestaltsGestaltListHandler }; diff --git a/src/client/handlers/gestaltsGestaltTrustByIdentity.ts b/src/client/handlers/gestaltsGestaltTrustByIdentity.ts new file mode 100644 index 000000000..ad3892176 --- /dev/null +++ b/src/client/handlers/gestaltsGestaltTrustByIdentity.ts @@ -0,0 +1,70 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; +import type { DB } from '@matrixai/db'; +import type { IdentityId, ProviderId } from 'ids/index'; +import type Discovery from '../../discovery/Discovery'; +import type { IdentityMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsGestaltTrustByIdentityHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + discovery: Discovery; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { db, gestaltGraph, discovery } = this.container; + const { + providerId, + identityId, + }: { providerId: ProviderId; identityId: IdentityId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + identityId: input.identityId, + }, + ); + + // Set the identity in the gestalt graph if not already + await db.withTransactionF(async (tran) => { + if ( + (await gestaltGraph.getGestaltByIdentity( + [providerId, identityId], + tran, + )) == null + ) { + // Queue the new identity for discovery + // This will only add the identity to the GG if it is connected to a + // node (required to set permissions for it) + await discovery.queueDiscoveryByIdentity(providerId, identityId); + } + // We can currently only set permissions for identities that are + // connected to at least one node. If these conditions are not met, this + // will throw an error. Since discovery can take time, you may need to + // reattempt this command if it fails on the first attempt and you expect + // there to be a linked node for the identity. + await gestaltGraph.setGestaltAction( + ['identity', [providerId, identityId]], + 'notify', + tran, + ); + }); + return {}; + } +} + +export { GestaltsGestaltTrustByIdentityHandler }; diff --git a/src/client/handlers/gestaltsGestaltTrustByNode.ts b/src/client/handlers/gestaltsGestaltTrustByNode.ts new file mode 100644 index 000000000..f9b17ac9e --- /dev/null +++ b/src/client/handlers/gestaltsGestaltTrustByNode.ts @@ -0,0 +1,57 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type GestaltGraph from 'gestalts/GestaltGraph'; +import type { DB } from '@matrixai/db'; +import type { NodeId } from 'ids/index'; +import type Discovery from '../../discovery/Discovery'; +import type { NodeIdMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class GestaltsGestaltTrustByNodeHandler extends UnaryHandler< + { + gestaltGraph: GestaltGraph; + db: DB; + discovery: Discovery; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { db, gestaltGraph, discovery } = this.container; + const { nodeId }: { nodeId: NodeId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + + await db.withTransactionF(async (tran) => { + // Set the node in the gestalt graph if not already + if ((await gestaltGraph.getGestaltByNode(nodeId, tran)) == null) { + await gestaltGraph.setNode( + { + nodeId, + }, + tran, + ); + // Queue the new node for discovery + await discovery.queueDiscoveryByNode(nodeId); + } + // Set notify permission + await gestaltGraph.setGestaltAction(['node', nodeId], 'notify', tran); + }); + + return {}; + } +} + +export { GestaltsGestaltTrustByNodeHandler }; diff --git a/src/client/handlers/identitiesAuthenticate.ts b/src/client/handlers/identitiesAuthenticate.ts new file mode 100644 index 000000000..f1e3f4d1f --- /dev/null +++ b/src/client/handlers/identitiesAuthenticate.ts @@ -0,0 +1,68 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { ProviderId } from 'ids/index'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import type { AuthProcessMessage } from 'client/handlers/types'; +import * as identitiesErrors from '../../identities/errors'; +import { ServerHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync, never } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class IdentitiesAuthenticateHandler extends ServerHandler< + { + identitiesManager: IdentitiesManager; + }, + ClientRPCRequestParams<{ + providerId: string; + }>, + ClientRPCResponseResult +> { + public async *handle( + input: ClientRPCRequestParams<{ + providerId: string; + }>, + ): AsyncGenerator> { + const { identitiesManager } = this.container; + const { + providerId, + }: { + providerId: ProviderId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + }, + ); + const provider = identitiesManager.getProvider(providerId); + if (provider == null) { + throw new identitiesErrors.ErrorProviderMissing(); + } + const authFlow = provider.authenticate(); + let authFlowResult = await authFlow.next(); + if (authFlowResult.done) { + never(); + } + yield { + request: { + url: authFlowResult.value.url, + dataMap: authFlowResult.value.data, + }, + }; + authFlowResult = await authFlow.next(); + if (!authFlowResult.done) { + never(); + } + yield { + response: { + identityId: authFlowResult.value, + }, + }; + } +} + +export { IdentitiesAuthenticateHandler }; diff --git a/src/client/handlers/identitiesAuthenticatedGet.ts b/src/client/handlers/identitiesAuthenticatedGet.ts new file mode 100644 index 000000000..265af00c2 --- /dev/null +++ b/src/client/handlers/identitiesAuthenticatedGet.ts @@ -0,0 +1,59 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { ProviderId } from 'ids/index'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import type { IdentityMessage } from 'client/handlers/types'; +import { ServerHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class IdentitiesAuthenticatedGetHandler extends ServerHandler< + { + identitiesManager: IdentitiesManager; + }, + ClientRPCRequestParams<{ + providerId?: string; + }>, + ClientRPCResponseResult +> { + public async *handle( + input: ClientRPCRequestParams<{ + providerId?: string; + }>, + ): AsyncGenerator> { + const { identitiesManager } = this.container; + let providerId: ProviderId | undefined; + if (input.providerId != null) { + providerId = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + }, + ).providerId; + } + const providerIds: Array = + providerId == null + ? (Object.keys(identitiesManager.getProviders()) as Array) + : [providerId]; + for (const providerId of providerIds) { + const provider = identitiesManager.getProvider(providerId); + if (provider == null) { + continue; + } + const identities = await provider.getAuthIdentityIds(); + for (const identityId of identities) { + yield { + providerId: provider.id, + identityId: identityId, + }; + } + } + } +} + +export { IdentitiesAuthenticatedGetHandler }; diff --git a/src/client/handlers/identitiesClaim.ts b/src/client/handlers/identitiesClaim.ts new file mode 100644 index 000000000..4efabb920 --- /dev/null +++ b/src/client/handlers/identitiesClaim.ts @@ -0,0 +1,50 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { IdentityId, ProviderId } from 'ids/index'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import type { ClaimIdMessage, IdentityMessage } from 'client/handlers/types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class IdentitiesClaimHandler extends UnaryHandler< + { + identitiesManager: IdentitiesManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { identitiesManager } = this.container; + const { + providerId, + identityId, + }: { providerId: ProviderId; identityId: IdentityId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + identityId: input.identityId, + }, + ); + + const claimData = await identitiesManager.handleClaimIdentity( + providerId, + identityId, + ); + + return { + claimId: claimData.id, + url: claimData.url, + }; + } +} + +export { IdentitiesClaimHandler }; diff --git a/src/client/handlers/identitiesInfoConnectedGet.ts b/src/client/handlers/identitiesInfoConnectedGet.ts new file mode 100644 index 000000000..a3a3fc954 --- /dev/null +++ b/src/client/handlers/identitiesInfoConnectedGet.ts @@ -0,0 +1,102 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { IdentityId, ProviderId } from 'ids/index'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import type { ProviderSearchMessage, IdentityInfoMessage } from './types'; +import type { IdentityData } from '../../identities/types'; +import { ServerHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; +import * as identitiesErrors from '../../identities/errors'; + +class IdentitiesInfoConnectedGetHandler extends ServerHandler< + { + identitiesManager: IdentitiesManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle( + input: ClientRPCRequestParams, + ): AsyncGenerator> { + const { identitiesManager } = this.container; + const { + providerIds, + }: { + providerIds: Array; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerIds'], () => value.map(validationUtils.parseProviderId)], + () => value, + ); + }, + { + providerIds: input.providerIdList, + }, + ); + let identityId: IdentityId | undefined; + if (input.authIdentityId != null) { + identityId = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + identityId: input.authIdentityId, + }, + ).identityId; + } + // Process options that were set + if (providerIds.length === 0) { + Object.keys(identitiesManager.getProviders()).forEach((id) => + providerIds.push(id as ProviderId), + ); + } + const getDisconnected = input.disconnected; + if (getDisconnected) { + // Can only get connected identities at this stage + throw new identitiesErrors.ErrorProviderUnimplemented(); + } + const identities: Array> = []; + for (const providerId of providerIds) { + // Get provider from id + const provider = identitiesManager.getProvider(providerId); + if (provider === undefined) { + throw new identitiesErrors.ErrorProviderMissing(); + } + // Get our own authenticated identity in order to query, skip provider + // if not authenticated + const authIdentities = await provider.getAuthIdentityIds(); + if (authIdentities.length === 0) { + continue; + } + const authIdentityId = + identityId === undefined ? authIdentities[0] : identityId; + identities.push( + provider.getConnectedIdentityDatas( + authIdentityId, + input.searchTermList, + ), + ); + } + let count = 0; + for (const gen of identities) { + for await (const identity of gen) { + if (input.limit !== undefined && count >= input.limit) break; + yield { + providerId: identity.providerId, + identityId: identity.identityId, + name: identity.name ?? '', + email: identity.email ?? '', + url: identity.url ?? '', + }; + count++; + } + } + } +} + +export { IdentitiesInfoConnectedGetHandler }; diff --git a/src/client/handlers/identitiesInfoGet.ts b/src/client/handlers/identitiesInfoGet.ts new file mode 100644 index 000000000..20d1828fc --- /dev/null +++ b/src/client/handlers/identitiesInfoGet.ts @@ -0,0 +1,96 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { IdentityId, ProviderId } from 'ids/index'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import type { ProviderSearchMessage, IdentityInfoMessage } from './types'; +import type { IdentityData } from '../../identities/types'; +import { ServerHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; +import * as identitiesErrors from '../../identities/errors'; +import * as identitiesUtils from '../../identities/utils'; + +class IdentitiesInfoGetHandler extends ServerHandler< + { + identitiesManager: IdentitiesManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle( + input: ClientRPCRequestParams, + ): AsyncGenerator> { + const { identitiesManager } = this.container; + const { + providerIds, + identityId, + }: { + providerIds: Array; + identityId: IdentityId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerIds'], () => value.map(validationUtils.parseProviderId)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerIds: input.providerIdList, + identityId: input.identityId, + }, + ); + + // Process options that were set + if (providerIds.length === 0) { + Object.keys(identitiesManager.getProviders()).forEach((id) => + providerIds.push(id as ProviderId), + ); + } + const searchTerms = input.searchTermList ?? []; + const getDisconnected = input.disconnected; + if (getDisconnected) { + // Currently, this command performs the same way regardless of whether + // this option is set (i.e. always disconnected) + } + const identities: Array = []; + for (const providerId of providerIds) { + // Get provider from id + const provider = identitiesManager.getProvider(providerId); + if (provider === undefined) { + throw new identitiesErrors.ErrorProviderMissing(); + } + // Get our own authenticated identity in order to query, skip provider + // if not authenticated + // It doesn't matter which one we use since `getIdentityData` does not + // require the identity to be connected + const authIdentities = await provider.getAuthIdentityIds(); + if (authIdentities.length === 0) { + continue; + } + // Get identity data + identities.push( + await provider.getIdentityData(authIdentities[0], identityId), + ); + } + if (input.limit === undefined || input.limit > identities.length) { + input.limit = identities.length; + } + for (let i = 0; i < input.limit; i++) { + const identity = identities[i]; + if (identity !== undefined) { + if (identitiesUtils.matchIdentityData(identity, searchTerms)) { + yield { + providerId: identity.providerId, + identityId: identity.identityId, + name: identity.name ?? '', + email: identity.email ?? '', + url: identity.url ?? '', + }; + } + } + } + } +} + +export { IdentitiesInfoGetHandler }; diff --git a/src/client/handlers/identitiesInvite.ts b/src/client/handlers/identitiesInvite.ts new file mode 100644 index 000000000..2081653c6 --- /dev/null +++ b/src/client/handlers/identitiesInvite.ts @@ -0,0 +1,54 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { NodeId } from 'ids/index'; +import type NotificationsManager from 'notifications/NotificationsManager'; +import type Logger from '@matrixai/logger'; +import type ACL from 'acl/ACL'; +import type { ClaimNodeMessage } from './types'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class IdentitiesInviteHandler extends UnaryHandler< + { + acl: ACL; + notificationsManager: NotificationsManager; + logger: Logger; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { acl, notificationsManager, logger } = this.container; + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + // Sending the notification, we don't care if it fails + try { + await notificationsManager.sendNotification(nodeId, { + type: 'GestaltInvite', + }); + } catch { + logger.warn('Failed to send gestalt invitation to target node'); + } + // Allowing claims from that gestalt + await acl.setNodeAction(nodeId, 'claim'); + return {}; + } +} + +export { IdentitiesInviteHandler }; diff --git a/src/client/handlers/identitiesProvidersList.ts b/src/client/handlers/identitiesProvidersList.ts new file mode 100644 index 000000000..21f569ba3 --- /dev/null +++ b/src/client/handlers/identitiesProvidersList.ts @@ -0,0 +1,27 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import { UnaryHandler } from '../../rpc/handlers'; + +class IdentitiesProvidersListHandler extends UnaryHandler< + { + identitiesManager: IdentitiesManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult<{ + providerIds: Array; + }> +> { + public async handle(): Promise< + ClientRPCResponseResult<{ + providerIds: Array; + }> + > { + const { identitiesManager } = this.container; + const providers = identitiesManager.getProviders(); + return { + providerIds: Object.keys(providers), + }; + } +} + +export { IdentitiesProvidersListHandler }; diff --git a/src/client/handlers/identitiesTokenDelete.ts b/src/client/handlers/identitiesTokenDelete.ts new file mode 100644 index 000000000..9b3b2f0bc --- /dev/null +++ b/src/client/handlers/identitiesTokenDelete.ts @@ -0,0 +1,49 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import type { IdentityMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type { IdentityId, ProviderId } from 'ids/index'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class IdentitiesTokenDeleteHandler extends UnaryHandler< + { + db: DB; + identitiesManager: IdentitiesManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { identitiesManager, db } = this.container; + const { + providerId, + identityId, + }: { + providerId: ProviderId; + identityId: IdentityId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + identityId: input.identityId, + }, + ); + await db.withTransactionF((tran) => + identitiesManager.delToken(providerId, identityId, tran), + ); + return {}; + } +} + +export { IdentitiesTokenDeleteHandler }; diff --git a/src/client/handlers/identitiesTokenGet.ts b/src/client/handlers/identitiesTokenGet.ts new file mode 100644 index 000000000..507b1f646 --- /dev/null +++ b/src/client/handlers/identitiesTokenGet.ts @@ -0,0 +1,52 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import type { IdentityMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type { TokenMessage } from './types'; +import type { IdentityId, ProviderId } from 'ids/index'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class IdentitiesTokenGetHandler extends UnaryHandler< + { + db: DB; + identitiesManager: IdentitiesManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult> +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise>> { + const { identitiesManager, db } = this.container; + const { + providerId, + identityId, + }: { + providerId: ProviderId; + identityId: IdentityId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + identityId: input.identityId, + }, + ); + const token = await db.withTransactionF((tran) => + identitiesManager.getToken(providerId, identityId, tran), + ); + return { + token, + }; + } +} + +export { IdentitiesTokenGetHandler }; diff --git a/src/client/handlers/identitiesTokenPut.ts b/src/client/handlers/identitiesTokenPut.ts new file mode 100644 index 000000000..c43f37be7 --- /dev/null +++ b/src/client/handlers/identitiesTokenPut.ts @@ -0,0 +1,49 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import type { IdentityMessage, TokenMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type { IdentityId, ProviderId } from 'ids/index'; +import { UnaryHandler } from '../../rpc/handlers'; +import { validateSync } from '../../validation/index'; +import { matchSync } from '../../utils/index'; +import * as validationUtils from '../../validation/utils'; + +class IdentitiesTokenPutHandler extends UnaryHandler< + { + db: DB; + identitiesManager: IdentitiesManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { identitiesManager, db } = this.container; + const { + providerId, + identityId, + }: { + providerId: ProviderId; + identityId: IdentityId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerId: input.providerId, + identityId: input.identityId, + }, + ); + await db.withTransactionF((tran) => + identitiesManager.putToken(providerId, identityId, input.token, tran), + ); + return {}; + } +} + +export { IdentitiesTokenPutHandler }; diff --git a/src/client/handlers/index.ts b/src/client/handlers/index.ts new file mode 100644 index 000000000..65357aabe --- /dev/null +++ b/src/client/handlers/index.ts @@ -0,0 +1,2 @@ +export * from './clientManifest'; +export * from './serverManifest'; diff --git a/src/client/handlers/keysCertsChainGet.ts b/src/client/handlers/keysCertsChainGet.ts new file mode 100644 index 000000000..9e08f4842 --- /dev/null +++ b/src/client/handlers/keysCertsChainGet.ts @@ -0,0 +1,23 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { CertMessage } from './types'; +import type CertManager from 'keys/CertManager'; +import { ServerHandler } from '../../rpc/handlers'; + +class KeysCertsChainGetHandler extends ServerHandler< + { + certManager: CertManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle(): AsyncGenerator> { + const { certManager } = this.container; + for (const certPEM of await certManager.getCertPEMsChain()) { + yield { + cert: certPEM, + }; + } + } +} + +export { KeysCertsChainGetHandler }; diff --git a/src/client/handlers/keysCertsGet.ts b/src/client/handlers/keysCertsGet.ts new file mode 100644 index 000000000..991578f38 --- /dev/null +++ b/src/client/handlers/keysCertsGet.ts @@ -0,0 +1,22 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { CertMessage } from './types'; +import type CertManager from 'keys/CertManager'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysCertsGetHandler extends UnaryHandler< + { + certManager: CertManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle(): Promise> { + const { certManager } = this.container; + const cert = await certManager.getCurrentCertPEM(); + return { + cert, + }; + } +} + +export { KeysCertsGetHandler }; diff --git a/src/client/handlers/keysDecrypt.ts b/src/client/handlers/keysDecrypt.ts new file mode 100644 index 000000000..3b1697aae --- /dev/null +++ b/src/client/handlers/keysDecrypt.ts @@ -0,0 +1,26 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type KeyRing from '../../keys/KeyRing'; +import type { DataMessage } from './types'; +import { never } from '../../utils/index'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysDecryptHandler extends UnaryHandler< + { + keyRing: KeyRing; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { keyRing } = this.container; + const data = keyRing.decrypt(Buffer.from(input.data, 'binary')); + if (data == null) never(); + return { + data: data.toString('binary'), + }; + } +} + +export { KeysDecryptHandler }; diff --git a/src/client/handlers/keysEncrypt.ts b/src/client/handlers/keysEncrypt.ts new file mode 100644 index 000000000..b87833088 --- /dev/null +++ b/src/client/handlers/keysEncrypt.ts @@ -0,0 +1,37 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type KeyRing from '../../keys/KeyRing'; +import type { DataMessage, DecryptMessage } from './types'; +import type { PublicKey } from '../../keys/types'; +import { never } from '../../utils/index'; +import * as keysUtils from '../../keys/utils/index'; +import * as keysErrors from '../../keys/errors'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysEncryptHandler extends UnaryHandler< + { + keyRing: KeyRing; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { keyRing } = this.container; + + let publicKey: PublicKey | undefined; + try { + const jwk = input.publicKeyJwk; + publicKey = keysUtils.publicKeyFromJWK(jwk); + if (publicKey == null) never(); + } catch (e) { + throw new keysErrors.ErrorPublicKeyParse(undefined, { cause: e }); + } + const data = keyRing.encrypt(publicKey, Buffer.from(input.data, 'binary')); + return { + data: data.toString('binary'), + }; + } +} + +export { KeysEncryptHandler }; diff --git a/src/client/handlers/keysKeyPair.ts b/src/client/handlers/keysKeyPair.ts new file mode 100644 index 000000000..b4b30d440 --- /dev/null +++ b/src/client/handlers/keysKeyPair.ts @@ -0,0 +1,31 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { KeyPairMessage, PasswordMessage } from './types'; +import type KeyRing from '../../keys/KeyRing'; +import * as keysUtils from '../../keys/utils/index'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysKeyPairHandler extends UnaryHandler< + { + keyRing: KeyRing; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { keyRing } = this.container; + const privateJWK = keysUtils.privateKeyToJWK(keyRing.keyPair.privateKey); + const privateKeyJwe = keysUtils.wrapWithPassword( + input.password, + privateJWK, + ); + const publicKeyJwk = keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey); + return { + privateKeyJwe, + publicKeyJwk, + }; + } +} + +export { KeysKeyPairHandler }; diff --git a/src/client/handlers/keysKeyPairRenew.ts b/src/client/handlers/keysKeyPairRenew.ts new file mode 100644 index 000000000..3560a0409 --- /dev/null +++ b/src/client/handlers/keysKeyPairRenew.ts @@ -0,0 +1,26 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { PasswordMessage } from './types'; +import type CertManager from 'keys/CertManager'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysKeyPairRenewHandler extends UnaryHandler< + { + certManager: CertManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { certManager } = this.container; + + // Other domains will be updated accordingly via the `EventBus` so we + // only need to modify the KeyManager + await certManager.renewCertWithNewKeyPair(input.password); + + return {}; + } +} + +export { KeysKeyPairRenewHandler }; diff --git a/src/client/handlers/keysKeyPairReset.ts b/src/client/handlers/keysKeyPairReset.ts new file mode 100644 index 000000000..02b40cb1f --- /dev/null +++ b/src/client/handlers/keysKeyPairReset.ts @@ -0,0 +1,24 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type CertManager from 'keys/CertManager'; +import type { PasswordMessage } from './types'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysKeyPairResethandler extends UnaryHandler< + { + certManager: CertManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { certManager } = this.container; + // Other domains will be updated accordingly via the `EventBus` so we + // only need to modify the KeyManager + await certManager.resetCertWithNewKeyPair(input.password); + return {}; + } +} + +export { KeysKeyPairResethandler }; diff --git a/src/client/handlers/keysPasswordChange.ts b/src/client/handlers/keysPasswordChange.ts new file mode 100644 index 000000000..1c3d92f91 --- /dev/null +++ b/src/client/handlers/keysPasswordChange.ts @@ -0,0 +1,22 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type KeyRing from 'keys/KeyRing'; +import type { PasswordMessage } from './types'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysPasswordChangeHandler extends UnaryHandler< + { + keyRing: KeyRing; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { keyRing } = this.container; + await keyRing.changePassword(input.password); + return {}; + } +} + +export { KeysPasswordChangeHandler }; diff --git a/src/client/handlers/keysPublicKey.ts b/src/client/handlers/keysPublicKey.ts new file mode 100644 index 000000000..91ac48523 --- /dev/null +++ b/src/client/handlers/keysPublicKey.ts @@ -0,0 +1,23 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type KeyRing from '../../keys/KeyRing'; +import type { PasswordMessage, PublicKeyMessage } from './types'; +import * as keysUtils from '../../keys/utils/index'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysPublicKeyHandler extends UnaryHandler< + { + keyRing: KeyRing; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle(): Promise> { + const { keyRing } = this.container; + const publicKeyJwk = keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey); + return { + publicKeyJwk, + }; + } +} + +export { KeysPublicKeyHandler }; diff --git a/src/client/handlers/keysSign.ts b/src/client/handlers/keysSign.ts new file mode 100644 index 000000000..dc597d8a1 --- /dev/null +++ b/src/client/handlers/keysSign.ts @@ -0,0 +1,24 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type KeyRing from 'keys/KeyRing'; +import type { DataMessage, SignatureMessage } from './types'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysSignHandler extends UnaryHandler< + { + keyRing: KeyRing; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { keyRing } = this.container; + const signature = keyRing.sign(Buffer.from(input.data, 'binary')); + return { + signature: signature.toString('binary'), + }; + } +} + +export { KeysSignHandler }; diff --git a/src/client/handlers/keysVerify.ts b/src/client/handlers/keysVerify.ts new file mode 100644 index 000000000..321939aa1 --- /dev/null +++ b/src/client/handlers/keysVerify.ts @@ -0,0 +1,40 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type KeyRing from '../../keys/KeyRing'; +import type { PublicKey, Signature } from '../../keys/types'; +import type { SuccessMessage, VerifySignatureMessage } from './types'; +import * as keysUtils from '../../keys/utils/index'; +import { never } from '../../utils/index'; +import * as keysErrors from '../../keys/errors'; +import { UnaryHandler } from '../../rpc/handlers'; + +class KeysVerifyHandler extends UnaryHandler< + { + keyRing: KeyRing; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { keyRing } = this.container; + let publicKey: PublicKey | undefined; + try { + const jwk = input.publicKeyJwk; + publicKey = keysUtils.publicKeyFromJWK(jwk); + if (publicKey == null) never(); + } catch (e) { + throw new keysErrors.ErrorPublicKeyParse(undefined, { cause: e }); + } + const success = keyRing.verify( + publicKey, + Buffer.from(input.data, 'binary'), + Buffer.from(input.signature, 'binary') as Signature, + ); + return { + success, + }; + } +} + +export { KeysVerifyHandler }; diff --git a/src/client/handlers/nodesAdd.ts b/src/client/handlers/nodesAdd.ts new file mode 100644 index 000000000..6a8844ac7 --- /dev/null +++ b/src/client/handlers/nodesAdd.ts @@ -0,0 +1,77 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { NodeId } from '../../ids'; +import type { Host, Hostname, Port } from '../../network/types'; +import type { NodeAddress } from '../../nodes/types'; +import type NodeManager from '../../nodes/NodeManager'; +import type { DB } from '@matrixai/db'; +import type { NodesAddMessage } from './types'; +import { matchSync } from '../../utils/index'; +import { validateSync } from '../../validation'; +import * as nodeErrors from '../../nodes/errors'; +import * as validationUtils from '../../validation/utils'; +import { UnaryHandler } from '../../rpc/handlers'; + +class NodesAddHandler extends UnaryHandler< + { + nodeManager: NodeManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { nodeManager, db } = this.container; + const { + nodeId, + host, + port, + }: { + nodeId: NodeId; + host: Host | Hostname; + port: Port; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + [['host'], () => validationUtils.parseHostOrHostname(value)], + [['port'], () => validationUtils.parsePort(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + host: input.host, + port: input.port, + }, + ); + // Pinging to authenticate the node + if ( + (input.ping ?? false) && + !(await nodeManager.pingNode(nodeId, { host, port })) + ) { + throw new nodeErrors.ErrorNodePingFailed( + 'Failed to authenticate target node', + ); + } + + await db.withTransactionF((tran) => + nodeManager.setNode( + nodeId, + { + host, + port, + } as NodeAddress, + true, + input.force ?? false, + 1500, + undefined, + tran, + ), + ); + return {}; + } +} + +export { NodesAddHandler }; diff --git a/src/client/handlers/nodesClaim.ts b/src/client/handlers/nodesClaim.ts new file mode 100644 index 000000000..9ad8f5355 --- /dev/null +++ b/src/client/handlers/nodesClaim.ts @@ -0,0 +1,50 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { NodeId } from '../../ids'; +import type NodeManager from '../../nodes/NodeManager'; +import type { DB } from '@matrixai/db'; +import type { ClaimNodeMessage, SuccessMessage } from './types'; +import { matchSync } from '../../utils/index'; +import { validateSync } from '../../validation'; +import * as validationUtils from '../../validation/utils'; +import { UnaryHandler } from '../../rpc/handlers'; + +class NodesClaimHandler extends UnaryHandler< + { + nodeManager: NodeManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { nodeManager, db } = this.container; + + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + await db.withTransactionF(async (tran) => { + // Attempt to claim the node, + // if there is no permission then we get an error + await nodeManager.claimNode(nodeId, tran); + }); + return { + success: true, + }; + } +} + +export { NodesClaimHandler }; diff --git a/src/client/handlers/nodesFind.ts b/src/client/handlers/nodesFind.ts new file mode 100644 index 000000000..67360385a --- /dev/null +++ b/src/client/handlers/nodesFind.ts @@ -0,0 +1,48 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { AddressMessage, NodeIdMessage } from '../handlers/types'; +import type { NodeId } from '../../ids'; +import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as validationUtils from '../../validation/utils'; +import * as nodesErrors from '../../nodes/errors'; +import { UnaryHandler } from '../../rpc/handlers'; + +class NodesFindHandler extends UnaryHandler< + { + nodeConnectionManager: NodeConnectionManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { nodeConnectionManager } = this.container; + + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + const address = await nodeConnectionManager.findNode(nodeId); + if (address == null) throw new nodesErrors.ErrorNodeGraphNodeIdNotFound(); + + return { + host: address.host, + port: address.port, + }; + } +} + +export { NodesFindHandler }; diff --git a/src/client/handlers/nodesGetAll.ts b/src/client/handlers/nodesGetAll.ts new file mode 100644 index 000000000..1149b5b03 --- /dev/null +++ b/src/client/handlers/nodesGetAll.ts @@ -0,0 +1,47 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type KeyRing from 'keys/KeyRing'; +import type { NodeId } from '../../ids'; +import type NodeGraph from '../../nodes/NodeGraph'; +import type { NodesGetMessage } from '../handlers/types'; +import { IdInternal } from '@matrixai/id'; +import * as nodesUtils from '../../nodes/utils'; +import { ServerHandler } from '../../rpc/handlers'; + +class NodesGetAllHandler extends ServerHandler< + { + nodeGraph: NodeGraph; + keyRing: KeyRing; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle(): AsyncGenerator< + ClientRPCResponseResult + > { + const { nodeGraph, keyRing } = this.container; + + for await (const bucket of nodeGraph.getBuckets()) { + let index; + for (const id of Object.keys(bucket)) { + const encodedId = nodesUtils.encodeNodeId( + IdInternal.fromString(id), + ); + // For every node in every bucket, add it to our message + if (!index) { + index = nodesUtils.bucketIndex( + keyRing.getNodeId(), + IdInternal.fromString(id), + ); + } + yield { + bucketIndex: index, + nodeIdEncoded: encodedId, + host: bucket[id].address.host, + port: bucket[id].address.port, + }; + } + } + } +} + +export { NodesGetAllHandler }; diff --git a/src/client/handlers/nodesListConnections.ts b/src/client/handlers/nodesListConnections.ts new file mode 100644 index 000000000..91b12901c --- /dev/null +++ b/src/client/handlers/nodesListConnections.ts @@ -0,0 +1,32 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { NodeConnectionMessage } from '../handlers/types'; +import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; +import * as nodesUtils from '../../nodes/utils'; +import { ServerHandler } from '../../rpc/handlers'; + +class NodesListConnectionsHandler extends ServerHandler< + { + nodeConnectionManager: NodeConnectionManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle(): AsyncGenerator< + ClientRPCResponseResult + > { + const { nodeConnectionManager } = this.container; + const connections = nodeConnectionManager.listConnections(); + for (const connection of connections) { + yield { + host: connection.address.host, + hostname: connection.address.hostname ?? '', + nodeIdEncoded: nodesUtils.encodeNodeId(connection.nodeId), + port: connection.address.port, + timeout: connection.timeout ?? -1, + usageCount: connection.usageCount, + }; + } + } +} + +export { NodesListConnectionsHandler }; diff --git a/src/client/handlers/nodesPing.ts b/src/client/handlers/nodesPing.ts new file mode 100644 index 000000000..6d69b0f9c --- /dev/null +++ b/src/client/handlers/nodesPing.ts @@ -0,0 +1,43 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { NodeId } from '../../ids'; +import type { NodeIdMessage, SuccessMessage } from '../handlers/types'; +import type NodeManager from '../../nodes/NodeManager'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as validationUtils from '../../validation/utils'; +import { UnaryHandler } from '../../rpc/handlers'; + +class NodesPingHandler extends UnaryHandler< + { + nodeManager: NodeManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { nodeManager } = this.container; + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + const success = await nodeManager.pingNode(nodeId); + return { + success, + }; + } +} + +export { NodesPingHandler }; diff --git a/src/client/handlers/notificationsClear.ts b/src/client/handlers/notificationsClear.ts new file mode 100644 index 000000000..39970805c --- /dev/null +++ b/src/client/handlers/notificationsClear.ts @@ -0,0 +1,23 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type NotificationsManager from '../../notifications/NotificationsManager'; +import type { DB } from '@matrixai/db'; +import { UnaryHandler } from '../../rpc/handlers'; + +class NotificationsClearHandler extends UnaryHandler< + { + db: DB; + notificationsManager: NotificationsManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle(): Promise { + const { db, notificationsManager } = this.container; + await db.withTransactionF((tran) => + notificationsManager.clearNotifications(tran), + ); + return {}; + } +} + +export { NotificationsClearHandler }; diff --git a/src/client/handlers/notificationsRead.ts b/src/client/handlers/notificationsRead.ts new file mode 100644 index 000000000..40df7ff70 --- /dev/null +++ b/src/client/handlers/notificationsRead.ts @@ -0,0 +1,36 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { DB } from '@matrixai/db'; +import type NotificationsManager from '../../notifications/NotificationsManager'; +import type { NotificationMessage, NotificationReadMessage } from './types'; +import { ServerHandler } from '../../rpc/handlers'; + +class NotificationsReadHandler extends ServerHandler< + { + db: DB; + notificationsManager: NotificationsManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle( + input: ClientRPCRequestParams, + ): AsyncGenerator> { + const { db, notificationsManager } = this.container; + + const notifications = await db.withTransactionF((tran) => + notificationsManager.readNotifications({ + unread: input.unread, + number: input.number, + order: input.order, + tran, + }), + ); + for (const notification of notifications) { + yield { + notification: notification, + }; + } + } +} + +export { NotificationsReadHandler }; diff --git a/src/client/handlers/notificationsSend.ts b/src/client/handlers/notificationsSend.ts new file mode 100644 index 000000000..e7a7ee825 --- /dev/null +++ b/src/client/handlers/notificationsSend.ts @@ -0,0 +1,47 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { NodeId } from '../../ids'; +import type { General } from '../../notifications/types'; +import type { NotificationSendMessage } from './types'; +import type NotificationsManager from '../../notifications/NotificationsManager'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as validationUtils from '../../validation/utils'; +import { UnaryHandler } from '../../rpc/handlers'; + +class NotificationsSendHandler extends UnaryHandler< + { + notificationsManager: NotificationsManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise { + const { notificationsManager } = this.container; + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + const data: General = { + type: 'General', + message: input.message, + }; + await notificationsManager.sendNotification(nodeId, data); + + return {}; + } +} + +export { NotificationsSendHandler }; diff --git a/src/client/handlers/serverManifest.ts b/src/client/handlers/serverManifest.ts new file mode 100644 index 000000000..7a5a539d7 --- /dev/null +++ b/src/client/handlers/serverManifest.ts @@ -0,0 +1,197 @@ +import type Logger from '@matrixai/logger'; +import type SessionManager from '../../sessions/SessionManager'; +import type KeyRing from '../../keys/KeyRing'; +import type CertManager from '../../keys/CertManager'; +import type PolykeyAgent from '../../PolykeyAgent'; +import type { DB } from '@matrixai/db'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; +import type Discovery from '../../discovery/Discovery'; +import type IdentitiesManager from '../../identities/IdentitiesManager'; +import type { NotificationsManager } from '../../notifications/index'; +import type ACL from '../../acl/ACL'; +import type NodeManager from '../../nodes/NodeManager'; +import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; +import type NodeGraph from '../../nodes/NodeGraph'; +import type VaultManager from '../../vaults/VaultManager'; +import type { FileSystem } from '../../types'; +import { VaultsCloneHandler } from './vaultsClone'; +import { VaultsCreatehandler } from './vaultsCreate'; +import { VaultsDeleteHandler } from './vaultsDelete'; +import { VaultsListHandler } from './vaultsList'; +import { VaultsLogHandler } from './vaultsLog'; +import { VaultsPermissionGetHandler } from './vaultsPermissionGet'; +import { VaultsPermissionSetHandler } from './vaultsPermissionSet'; +import { VaultsPermissionUnsetHandler } from './vaultsPermissionUnset'; +import { NodesAddHandler } from './nodesAdd'; +import { NodesClaimHandler } from './nodesClaim'; +import { NodesFindHandler } from './nodesFind'; +import { NodesGetAllHandler } from './nodesGetAll'; +import { NodesListConnectionsHandler } from './nodesListConnections'; +import { NodesPingHandler } from './nodesPing'; +import { GestaltsActionsGetByIdentityHandler } from './gestaltsActionsGetByIdentity'; +import { GestaltsActionsGetByNodeHandler } from './gestaltsActionsGetByNode'; +import { GestaltsActionsSetByIdentityHandler } from './gestaltsActionsSetByIdentity'; +import { GestaltsActionsSetByNodeHandler } from './gestaltsActionsSetByNode'; +import { GestaltsActionsUnsetByIdentityHandler } from './gestaltsActionsUnsetByIdentity'; +import { GestaltsActionsUnsetByNodeHandler } from './gestaltsActionsUnsetByNode'; +import { GestaltsDiscoveryByIdentityHandler } from './gestaltsDiscoveryByIdentity'; +import { GestaltsDiscoveryByNodeHandler } from './gestaltsDiscoveryByNode'; +import { GestaltsGestaltGetByIdentityHandler } from './gestaltsGestaltGetByIdentity'; +import { GestaltsGestaltGetByNodeHandler } from './gestaltsGestaltGetByNode'; +import { GestaltsGestaltListHandler } from './gestaltsGestaltList'; +import { GestaltsGestaltTrustByIdentityHandler } from './gestaltsGestaltTrustByIdentity'; +import { GestaltsGestaltTrustByNodeHandler } from './gestaltsGestaltTrustByNode'; +import { IdentitiesAuthenticateHandler } from './identitiesAuthenticate'; +import { IdentitiesAuthenticatedGetHandler } from './identitiesAuthenticatedGet'; +import { IdentitiesClaimHandler } from './identitiesClaim'; +import { AgentStatusHandler } from './agentStatus'; +import { AgentStopHandler } from './agentStop'; +import { AgentUnlockHandler } from './agentUnlock'; +import { AgentLockAllHandler } from './agentLockAll'; +import { IdentitiesInfoGetHandler } from './identitiesInfoGet'; +import { IdentitiesInfoConnectedGetHandler } from './identitiesInfoConnectedGet'; +import { IdentitiesInviteHandler } from './identitiesInvite'; +import { IdentitiesProvidersListHandler } from './identitiesProvidersList'; +import { IdentitiesTokenDeleteHandler } from './identitiesTokenDelete'; +import { IdentitiesTokenGetHandler } from './identitiesTokenGet'; +import { IdentitiesTokenPutHandler } from './identitiesTokenPut'; +import { KeysCertsChainGetHandler } from './keysCertsChainGet'; +import { KeysCertsGetHandler } from './keysCertsGet'; +import { KeysDecryptHandler } from './keysDecrypt'; +import { KeysEncryptHandler } from './keysEncrypt'; +import { KeysKeyPairHandler } from './keysKeyPair'; +import { KeysKeyPairRenewHandler } from './keysKeyPairRenew'; +import { KeysKeyPairResethandler } from './keysKeyPairReset'; +import { KeysPasswordChangeHandler } from './keysPasswordChange'; +import { KeysPublicKeyHandler } from './keysPublicKey'; +import { NotificationsClearHandler } from './notificationsClear'; +import { NotificationsReadHandler } from './notificationsRead'; +import { NotificationsSendHandler } from './notificationsSend'; +import { VaultsPullHandler } from './vaultsPull'; +import { VaultsRenameHandler } from './vaultsRename'; +import { VaultsScanHandler } from './vaultsScan'; +import { VaultsSecretsDeleteHandler } from './vaultsSecretsDelete'; +import { VaultsSecretsEditHandler } from './vaultsSecretsEdit'; +import { VaultsSecretsGetHandler } from './vaultsSecretsGet'; +import { VaultsSecretsListHandler } from './vaultsSecretsList'; +import { VaultsSecretsMkdirHandler } from './vaultsSecretsMkdir'; +import { VaultsSecretsNewHandler } from './vaultsSecretsNew'; +import { VaultsSecretsNewDirHandler } from './vaultsSecretsNewDir'; +import { VaultsSecretsRenameHandler } from './vaultsSecretsRename'; +import { VaultsSecretsStatHandler } from './vaultsSecretsStat'; +import { VaultsVersionHandler } from './vaultsVersion'; +import { KeysVerifyHandler } from '../../client/handlers/keysVerify'; +import { KeysSignHandler } from '../../client/handlers/keysSign'; + +const serverManifest = (container: { + pkAgentProm: Promise; + keyRing: KeyRing; + certManager: CertManager; + db: DB; + sessionManager: SessionManager; + gestaltGraph: GestaltGraph; + identitiesManager: IdentitiesManager; + discovery: Discovery; + acl: ACL; + notificationsManager: NotificationsManager; + nodeManager: NodeManager; + nodeConnectionManager: NodeConnectionManager; + nodeGraph: NodeGraph; + vaultManager: VaultManager; + fs: FileSystem; + logger: Logger; +}) => { + // No type used here, it will override type inference + return { + agentLockAll: new AgentLockAllHandler(container), + agentStatus: new AgentStatusHandler(container), + agentStop: new AgentStopHandler(container), + agentUnlock: new AgentUnlockHandler(container), + gestaltsActionsGetByIdentity: new GestaltsActionsGetByIdentityHandler( + container, + ), + gestaltsActionsGetByNode: new GestaltsActionsGetByNodeHandler(container), + gestaltsActionsSetByIdentity: new GestaltsActionsSetByIdentityHandler( + container, + ), + gestaltsActionsSetByNode: new GestaltsActionsSetByNodeHandler(container), + gestaltsActionsUnsetByIdentity: new GestaltsActionsUnsetByIdentityHandler( + container, + ), + gestaltsActionsUnsetByNode: new GestaltsActionsUnsetByNodeHandler( + container, + ), + gestaltsDiscoveryByIdentity: new GestaltsDiscoveryByIdentityHandler( + container, + ), + gestaltsDiscoveryByNode: new GestaltsDiscoveryByNodeHandler(container), + gestaltsGestaltGetByIdentity: new GestaltsGestaltGetByIdentityHandler( + container, + ), + gestaltsGestaltGetByNode: new GestaltsGestaltGetByNodeHandler(container), + gestaltsGestaltList: new GestaltsGestaltListHandler(container), + gestaltsGestaltTrustByIdentity: new GestaltsGestaltTrustByIdentityHandler( + container, + ), + gestaltsGestaltTrustByNode: new GestaltsGestaltTrustByNodeHandler( + container, + ), + identitiesAuthenticate: new IdentitiesAuthenticateHandler(container), + identitiesAuthenticatedGet: new IdentitiesAuthenticatedGetHandler( + container, + ), + identitiesClaim: new IdentitiesClaimHandler(container), + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler( + container, + ), + identitiesInfoGet: new IdentitiesInfoGetHandler(container), + identitiesInvite: new IdentitiesInviteHandler(container), + identitiesProvidersList: new IdentitiesProvidersListHandler(container), + identitiesTokenDelete: new IdentitiesTokenDeleteHandler(container), + identitiesTokenGet: new IdentitiesTokenGetHandler(container), + identitiesTokenPut: new IdentitiesTokenPutHandler(container), + keysCertsChainGet: new KeysCertsChainGetHandler(container), + keysCertsGet: new KeysCertsGetHandler(container), + keysDecrypt: new KeysDecryptHandler(container), + keysEncrypt: new KeysEncryptHandler(container), + keysKeyPair: new KeysKeyPairHandler(container), + keysKeyPairRenew: new KeysKeyPairRenewHandler(container), + keysKeyPairReset: new KeysKeyPairResethandler(container), + keysPasswordChange: new KeysPasswordChangeHandler(container), + keysPublicKey: new KeysPublicKeyHandler(container), + keysSign: new KeysSignHandler(container), + keysVerify: new KeysVerifyHandler(container), + nodesAdd: new NodesAddHandler(container), + nodesClaim: new NodesClaimHandler(container), + nodesFind: new NodesFindHandler(container), + nodesGetAll: new NodesGetAllHandler(container), + nodesListConnections: new NodesListConnectionsHandler(container), + nodesPing: new NodesPingHandler(container), + notificationsClear: new NotificationsClearHandler(container), + notificationsRead: new NotificationsReadHandler(container), + notificationsSend: new NotificationsSendHandler(container), + vaultsClone: new VaultsCloneHandler(container), + vaultsCreate: new VaultsCreatehandler(container), + vaultsDelete: new VaultsDeleteHandler(container), + vaultsList: new VaultsListHandler(container), + vaultsLog: new VaultsLogHandler(container), + vaultsPermissionGet: new VaultsPermissionGetHandler(container), + vaultsPermissionSet: new VaultsPermissionSetHandler(container), + vaultsPermissionUnset: new VaultsPermissionUnsetHandler(container), + vaultsPull: new VaultsPullHandler(container), + vaultsRename: new VaultsRenameHandler(container), + vaultsScan: new VaultsScanHandler(container), + vaultsSecretsDelete: new VaultsSecretsDeleteHandler(container), + vaultsSecretsEdit: new VaultsSecretsEditHandler(container), + vaultsSecretsGet: new VaultsSecretsGetHandler(container), + vaultsSecretsList: new VaultsSecretsListHandler(container), + vaultsSecretsMkdir: new VaultsSecretsMkdirHandler(container), + vaultsSecretsNew: new VaultsSecretsNewHandler(container), + vaultsSecretsNewDir: new VaultsSecretsNewDirHandler(container), + vaultsSecretsRename: new VaultsSecretsRenameHandler(container), + vaultsSecretsStat: new VaultsSecretsStatHandler(container), + vaultsVersion: new VaultsVersionHandler(container), + }; +}; + +export { serverManifest }; diff --git a/src/client/handlers/types.ts b/src/client/handlers/types.ts new file mode 100644 index 000000000..d7ec1bba3 --- /dev/null +++ b/src/client/handlers/types.ts @@ -0,0 +1,286 @@ +import type { IdentityId, ProviderId, VaultIdEncoded } from '../../ids'; +import type { GestaltAction } from 'gestalts/types'; +import type { ProviderToken } from 'identities/types'; +import type { GestaltIdEncoded, NodeIdEncoded } from '../../ids'; +import type { + CertificatePEM, + CertificatePEMChain, + JWKEncrypted, + PublicKeyJWK, +} from 'keys/types'; +import type { Notification } from '../../notifications/types'; +import type { CommitId, VaultAction, VaultName } from '../../vaults/types'; +import type { Host, Port } from '../../network/types'; + +// Agent messages +export type StatusResultMessage = { + pid: number; +} & NodeIdMessage & + PublicKeyMessage & { + clientHost: Host; + clientPort: Port; + proxyHost: Host; + proxyPort: Port; + agentHost: Host; + agentPort: Port; + forwardHost: Host; + forwardPort: Port; + certChainPEM: CertificatePEMChain; + }; + +// Identity messages +export type IdentityMessage = { + providerId: string; + identityId: string; +}; + +export type ProviderSearchMessage = { + authIdentityId?: string; + identityId: string; + disconnected: boolean; + limit?: number; + searchTermList?: Array; + providerIdList: Array; +}; + +export type IdentityInfoMessage = IdentityMessage & { + name: string; + email: string; + url: string; +}; + +export type AuthProcessMessage = { + request?: { + url: string; + dataMap: Record; + }; + response?: { + identityId: string; + }; +}; + +export type ClaimIdMessage = { + claimId: string; + url?: string; +}; + +export type ClaimNodeMessage = NodeIdMessage & { + forceInvite?: boolean; +}; + +export type TokenMessage = { + token: ProviderToken; +}; + +// Nodes messages +export type NodeIdMessage = { + nodeIdEncoded: NodeIdEncoded; +}; + +export type AddressMessage = { + host: string; + port: number; +}; + +export type NodeAddressMessage = NodeIdMessage & AddressMessage; + +export type NodesGetMessage = NodeAddressMessage & { bucketIndex: number }; + +export type NodesAddMessage = NodeAddressMessage & { + force?: boolean; + ping?: boolean; +}; + +export type NodeConnectionMessage = NodeAddressMessage & { + hostname: string; + usageCount: number; + timeout: number; +}; + +// Gestalts messages +export type ActionsListMessage = { + actionsList: Array; +}; + +export type SetIdentityActionMessage = IdentityMessage & { + action: GestaltAction; +}; + +export type SetNodeActionMessage = NodeIdMessage & { + action: GestaltAction; +}; + +export type GestaltMessage = { + gestalt: { + matrix: Record>; + nodes: Record; + identities: Record< + GestaltIdEncoded, + { + providerId: ProviderId; + identityId: IdentityId; + name?: string; + email?: string; + url?: string; + } + >; + }; +}; + +// Keys messages +export type CertMessage = { + cert: CertificatePEM; +}; + +export type DataMessage = { + data: string; +}; + +export type PublicKeyMessage = { + publicKeyJwk: PublicKeyJWK; +}; + +export type PrivateKeyMessage = { + privateKeyJwe: JWKEncrypted; +}; + +export type DecryptMessage = DataMessage & PublicKeyMessage; + +export type PasswordMessage = { + password: string; +}; + +export type KeyPairMessage = PrivateKeyMessage & PublicKeyMessage; + +export type SignatureMessage = { + signature: string; +}; + +export type VerifySignatureMessage = PublicKeyMessage & + DataMessage & + SignatureMessage; + +export type SuccessMessage = { + success: boolean; +}; + +// Notifications messages +export type NotificationReadMessage = { + unread?: boolean; + number?: number | 'all'; + order?: 'newest' | 'oldest'; +}; + +export type NotificationMessage = { + notification: Notification; +}; + +export type NotificationSendMessage = NodeIdMessage & { + message: string; +}; + +// Vaults messages +export type VaultNameMessage = { + vaultName: VaultName; +}; + +export type VaultIdMessage = { + vaultIdEncoded: VaultIdEncoded; +}; + +export type VaultIdentifierMessage = { + nameOrId: VaultIdEncoded | VaultName; +}; +export type CloneMessage = NodeIdMessage & VaultIdentifierMessage; + +export type VaultListMessage = VaultNameMessage & VaultIdMessage; + +export type VaultsLogMessage = VaultIdentifierMessage & { + depth?: number; + commitId?: string; +}; + +export type LogEntryMessage = { + commitId: CommitId; + committer: string; + timestamp: string; + message: string; +}; + +export type VaultPermissionMessage = VaultIdMessage & + NodeIdMessage & { + vaultPermissionList: Array; + }; + +export type PermissionSetMessage = VaultIdentifierMessage & + NodeIdMessage & { + vaultPermissionList: Array; + }; + +export type VaultsPullMessage = Partial & { + pullVault: VaultIdEncoded | VaultName; +}; + +export type VaultsRenameMessage = VaultIdentifierMessage & { + newName: VaultName; +}; + +export type VaultsScanMessage = VaultListMessage & { + permissions: Array; +}; + +export type VaultsVersionMessage = VaultIdentifierMessage & { + versionId: string; +}; + +export type VaultsLatestVersionMessage = { + latestVersion: boolean; +}; + +// Secrets +export type SecretNameMessage = { + secretName: string; +}; + +export type SecretIdentifierMessage = VaultIdentifierMessage & + SecretNameMessage; + +// Contains binary content as a binary string 'toString('binary')' +export type ContentMessage = { + secretContent: string; +}; + +export type SecretContentMessage = SecretIdentifierMessage & ContentMessage; + +export type SecretMkdirMessage = VaultIdentifierMessage & { + dirName: string; + recursive: boolean; +}; + +export type SecretDirMessage = VaultIdentifierMessage & { + dirName: string; +}; + +export type SecretRenameMessage = SecretIdentifierMessage & { + newSecretName: string; +}; + +// Stat is the 'JSON.stringify version of the file stat +export type SecretStatMessage = { + stat: { + dev: number; + ino: number; + mode: number; + nlink: number; + uid: number; + gid: number; + rdev: number; + size: number; + atime: string; + mtime: string; + ctime: string; + birthtime: string; + blksize: number; + blocks: number; + }; +}; diff --git a/src/client/handlers/vaultsClone.ts b/src/client/handlers/vaultsClone.ts new file mode 100644 index 000000000..2c81d3f57 --- /dev/null +++ b/src/client/handlers/vaultsClone.ts @@ -0,0 +1,48 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { CloneMessage, SuccessMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type { NodeId } from '../../ids'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as validationUtils from '../../validation/utils'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsCloneHandler extends UnaryHandler< + { + db: DB; + vaultManager: VaultManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, vaultManager } = this.container; + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + // Vault id + await db.withTransactionF(async (tran) => { + await vaultManager.cloneVault(nodeId, input.nameOrId, tran); + }); + return { + success: true, + }; + } +} + +export { VaultsCloneHandler }; diff --git a/src/client/handlers/vaultsCreate.ts b/src/client/handlers/vaultsCreate.ts new file mode 100644 index 000000000..3b96c7814 --- /dev/null +++ b/src/client/handlers/vaultsCreate.ts @@ -0,0 +1,31 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { DB } from '@matrixai/db'; +import type { VaultIdMessage, VaultNameMessage } from './types'; +import * as vaultsUtils from '../../vaults/utils'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsCreatehandler extends UnaryHandler< + { + db: DB; + vaultManager: VaultManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, vaultManager } = this.container; + + const vaultId = await db.withTransactionF((tran) => + vaultManager.createVault(input.vaultName, tran), + ); + + return { + vaultIdEncoded: vaultsUtils.encodeVaultId(vaultId), + }; + } +} + +export { VaultsCreatehandler }; diff --git a/src/client/handlers/vaultsDelete.ts b/src/client/handlers/vaultsDelete.ts new file mode 100644 index 000000000..b730f0f71 --- /dev/null +++ b/src/client/handlers/vaultsDelete.ts @@ -0,0 +1,40 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { SuccessMessage, VaultIdentifierMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type { VaultName } from '../../vaults/types'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsDeleteHandler extends UnaryHandler< + { + db: DB; + vaultManager: VaultManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, vaultManager } = this.container; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + await vaultManager.destroyVault(vaultId, tran); + }); + return { + success: true, + }; + } +} + +export { VaultsDeleteHandler }; diff --git a/src/client/handlers/vaultsList.ts b/src/client/handlers/vaultsList.ts new file mode 100644 index 000000000..562df8997 --- /dev/null +++ b/src/client/handlers/vaultsList.ts @@ -0,0 +1,32 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { VaultListMessage } from './types'; +import type { DB } from '@matrixai/db'; +import * as vaultsUtils from '../../vaults/utils'; +import { ServerHandler } from '../../rpc/handlers'; + +class VaultsListHandler extends ServerHandler< + { + db: DB; + vaultManager: VaultManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle(): AsyncGenerator< + ClientRPCResponseResult + > { + const { db, vaultManager } = this.container; + const vaults = await db.withTransactionF((tran) => + vaultManager.listVaults(tran), + ); + for await (const [vaultName, vaultId] of vaults) { + yield { + vaultName, + vaultIdEncoded: vaultsUtils.encodeVaultId(vaultId), + }; + } + } +} + +export { VaultsListHandler }; diff --git a/src/client/handlers/vaultsLog.ts b/src/client/handlers/vaultsLog.ts new file mode 100644 index 000000000..09b6bee17 --- /dev/null +++ b/src/client/handlers/vaultsLog.ts @@ -0,0 +1,52 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { DB } from '@matrixai/db'; +import type { LogEntryMessage, VaultsLogMessage } from './types'; +import type { VaultName } from '../../vaults/types'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import { ServerHandler } from '../../rpc/handlers'; + +class VaultsLogHandler extends ServerHandler< + { + db: DB; + vaultManager: VaultManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle( + input: ClientRPCRequestParams, + ): AsyncGenerator> { + const { db, vaultManager } = this.container; + const log = await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + // Getting the log + return await vaultManager.withVaults( + [vaultId], + async (vault) => { + return await vault.log(input.commitId, input.depth); + }, + tran, + ); + }); + for (const entry of log) { + yield { + commitId: entry.commitId, + committer: entry.committer.name, + timestamp: entry.committer.timestamp.toString(), + message: entry.message, + }; + } + } +} + +export { VaultsLogHandler }; diff --git a/src/client/handlers/vaultsPermissionGet.ts b/src/client/handlers/vaultsPermissionGet.ts new file mode 100644 index 000000000..babecefd0 --- /dev/null +++ b/src/client/handlers/vaultsPermissionGet.ts @@ -0,0 +1,62 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { DB } from '@matrixai/db'; +import type ACL from '../../acl/ACL'; +import type { VaultIdentifierMessage, VaultPermissionMessage } from './types'; +import type { VaultAction, VaultActions } from '../../vaults/types'; +import type { NodeId, NodeIdEncoded } from '../../ids'; +import { IdInternal } from '@matrixai/id'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as nodesUtils from '../../nodes/utils'; +import { ServerHandler } from '../../rpc/handlers'; + +class VaultsPermissionGetHandler extends ServerHandler< + { + db: DB; + vaultManager: VaultManager; + acl: ACL; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle( + input: ClientRPCRequestParams, + ): AsyncGenerator> { + const { db, vaultManager, acl } = this.container; + const [rawPermissions, vaultId] = await db.withTransactionF( + async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + // Getting permissions + return [await acl.getVaultPerm(vaultId, tran), vaultId]; + }, + ); + const permissionList: Record = {}; + // Getting the relevant information + for (const nodeId in rawPermissions) { + permissionList[nodeId] = rawPermissions[nodeId].vaults[vaultId]; + } + // Constructing the message + for (const nodeIdString in permissionList) { + const nodeId = IdInternal.fromString(nodeIdString); + const actions = Object.keys( + permissionList[nodeIdString], + ) as Array; + yield { + vaultIdEncoded: vaultsUtils.encodeVaultId(vaultId), + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + vaultPermissionList: actions, + }; + } + } +} + +export { VaultsPermissionGetHandler }; diff --git a/src/client/handlers/vaultsPermissionSet.ts b/src/client/handlers/vaultsPermissionSet.ts new file mode 100644 index 000000000..1b2a0dd18 --- /dev/null +++ b/src/client/handlers/vaultsPermissionSet.ts @@ -0,0 +1,86 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; +import type ACL from '../../acl/ACL'; +import type { PermissionSetMessage, SuccessMessage } from './types'; +import type NotificationsManager from '../../notifications/NotificationsManager'; +import type { DB } from '@matrixai/db'; +import type { VaultAction, VaultActions } from '../../vaults/types'; +import type { NodeId } from '../../ids'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as validationUtils from '../../validation/utils'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsPermissionSetHandler extends UnaryHandler< + { + db: DB; + vaultManager: VaultManager; + gestaltGraph: GestaltGraph; + acl: ACL; + notificationsManager: NotificationsManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, vaultManager, gestaltGraph, acl, notificationsManager } = + this.container; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const { + nodeId, + actions, + }: { + nodeId: NodeId; + actions: Array; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + [['actions'], () => value.map(validationUtils.parseVaultAction)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + actions: input.vaultPermissionList, + }, + ); + // Checking if vault exists + const vaultMeta = await vaultManager.getVaultMeta(vaultId, tran); + if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); + // Setting permissions + const actionsSet: VaultActions = {}; + await gestaltGraph.setGestaltAction(['node', nodeId], 'scan', tran); + for (const action of actions) { + await acl.setVaultAction(vaultId, nodeId, action, tran); + actionsSet[action] = null; + } + // Sending notification + await notificationsManager.sendNotification(nodeId, { + type: 'VaultShare', + vaultId: vaultsUtils.encodeVaultId(vaultId), + vaultName: vaultMeta.vaultName, + actions: actionsSet, + }); + }); + return { + success: true, + }; + } +} + +export { VaultsPermissionSetHandler }; diff --git a/src/client/handlers/vaultsPermissionUnset.ts b/src/client/handlers/vaultsPermissionUnset.ts new file mode 100644 index 000000000..e696c2e3c --- /dev/null +++ b/src/client/handlers/vaultsPermissionUnset.ts @@ -0,0 +1,88 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { VaultAction } from '../../vaults/types'; +import type { NodeId } from '../../ids'; +import type { DB } from '@matrixai/db'; +import type VaultManager from '../../vaults/VaultManager'; +import type GestaltGraph from '../../gestalts/GestaltGraph'; +import type ACL from '../../acl/ACL'; +import type { PermissionSetMessage, SuccessMessage } from './types'; +import * as vaultsUtils from '../../vaults/utils'; +import * as validationUtils from '../../validation/utils'; +import { matchSync } from '../../utils'; +import { validateSync } from '../../validation'; +import * as vaultsErrors from '../../vaults/errors'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsPermissionUnsetHandler extends UnaryHandler< + { + db: DB; + vaultManager: VaultManager; + gestaltGraph: GestaltGraph; + acl: ACL; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, vaultManager, gestaltGraph, acl } = this.container; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const { + nodeId, + actions, + }: { + nodeId: NodeId; + actions: Array; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + [['actions'], () => value.map(validationUtils.parseVaultAction)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + actions: input.vaultPermissionList, + }, + ); + // Checking if vault exists + const vaultMeta = await vaultManager.getVaultMeta(vaultId, tran); + if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); + // Unsetting permissions + await gestaltGraph.setGestaltAction(['node', nodeId], 'scan', tran); + for (const action of actions) { + await acl.unsetVaultAction(vaultId, nodeId, action, tran); + } + // We need to check if there are still shared vaults + const nodePermissions = await acl.getNodePerm(nodeId, tran); + // Remove scan permissions if no more shared vaults + if (nodePermissions != null) { + // Counting total number of permissions + const totalPermissions = Object.keys(nodePermissions.vaults) + .map((key) => Object.keys(nodePermissions.vaults[key]).length) + .reduce((prev, current) => current + prev); + // If no permissions are left then we remove the scan permission + if (totalPermissions === 0) { + await gestaltGraph.unsetGestaltAction(['node', nodeId], 'scan', tran); + } + } + }); + // Formatting response + return { + success: true, + }; + } +} + +export { VaultsPermissionUnsetHandler }; diff --git a/src/client/handlers/vaultsPull.ts b/src/client/handlers/vaultsPull.ts new file mode 100644 index 000000000..3c0bdb80b --- /dev/null +++ b/src/client/handlers/vaultsPull.ts @@ -0,0 +1,70 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { VaultName } from '../../vaults/types'; +import type { NodeId } from '../../ids'; +import type { SuccessMessage, VaultsPullMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type VaultManager from '../../vaults/VaultManager'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as validationUtils from '../../validation/utils'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsPullHandler extends UnaryHandler< + { + db: DB; + vaultManager: VaultManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, vaultManager } = this.container; + let pullVaultId; + pullVaultId = vaultsUtils.decodeVaultId(input.pullVault); + pullVaultId = pullVaultId ?? input.pullVault; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId as VaultName, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const { + nodeId, + }: { + nodeId: NodeId | undefined; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [ + ['nodeId'], + () => (value ? validationUtils.parseNodeId(value) : undefined), + ], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + await vaultManager.pullVault({ + vaultId, + pullNodeId: nodeId, + pullVaultNameOrId: pullVaultId, + tran, + }); + }); + return { + success: true, + }; + } +} + +export { VaultsPullHandler }; diff --git a/src/client/handlers/vaultsRename.ts b/src/client/handlers/vaultsRename.ts new file mode 100644 index 000000000..9cb025fce --- /dev/null +++ b/src/client/handlers/vaultsRename.ts @@ -0,0 +1,39 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { VaultIdMessage, VaultsRenameMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type VaultManager from '../../vaults/VaultManager'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsRenameHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { db, vaultManager } = this.container; + return await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + await vaultManager.renameVault(vaultId, input.newName, tran); + return { + vaultIdEncoded: vaultsUtils.encodeVaultId(vaultId), + }; + }); + } +} + +export { VaultsRenameHandler }; diff --git a/src/client/handlers/vaultsScan.ts b/src/client/handlers/vaultsScan.ts new file mode 100644 index 000000000..8e739458d --- /dev/null +++ b/src/client/handlers/vaultsScan.ts @@ -0,0 +1,50 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { NodeId } from '../../ids'; +import type { NodeIdMessage, VaultsScanMessage } from './types'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as validationUtils from '../../validation/utils'; +import { ServerHandler } from '../../rpc/handlers'; + +class VaultsScanHandler extends ServerHandler< + { + vaultManager: VaultManager; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle( + input: ClientRPCRequestParams, + ): AsyncGenerator> { + const { vaultManager } = this.container; + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: input.nodeIdEncoded, + }, + ); + for await (const { + vaultIdEncoded, + vaultName, + vaultPermissions, + } of vaultManager.scanVaults(nodeId)) { + yield { + vaultName, + vaultIdEncoded, + permissions: vaultPermissions, + }; + } + } +} + +export { VaultsScanHandler }; diff --git a/src/client/handlers/vaultsSecretsDelete.ts b/src/client/handlers/vaultsSecretsDelete.ts new file mode 100644 index 000000000..280475895 --- /dev/null +++ b/src/client/handlers/vaultsSecretsDelete.ts @@ -0,0 +1,46 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { SecretIdentifierMessage, SuccessMessage } from './types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { DB } from '@matrixai/db'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as vaultOps from '../../vaults/VaultOps'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsSecretsDeleteHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { vaultManager, db } = this.container; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.deleteSecret(vault, input.secretName); + }, + tran, + ); + }); + return { + success: true, + }; + } +} + +export { VaultsSecretsDeleteHandler }; diff --git a/src/client/handlers/vaultsSecretsEdit.ts b/src/client/handlers/vaultsSecretsEdit.ts new file mode 100644 index 000000000..257098b9c --- /dev/null +++ b/src/client/handlers/vaultsSecretsEdit.ts @@ -0,0 +1,47 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { DB } from '@matrixai/db'; +import type { SecretContentMessage, SuccessMessage } from './types'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as vaultOps from '../../vaults/VaultOps'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsSecretsEditHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { vaultManager, db } = this.container; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const secretContent = Buffer.from(input.secretContent, 'binary'); + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.updateSecret(vault, input.secretName, secretContent); + }, + tran, + ); + }); + return { + success: true, + }; + } +} + +export { VaultsSecretsEditHandler }; diff --git a/src/client/handlers/vaultsSecretsGet.ts b/src/client/handlers/vaultsSecretsGet.ts new file mode 100644 index 000000000..af67fbb6e --- /dev/null +++ b/src/client/handlers/vaultsSecretsGet.ts @@ -0,0 +1,46 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { DB } from '@matrixai/db'; +import type { ContentMessage, SecretIdentifierMessage } from './types'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as vaultOps from '../../vaults/VaultOps'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsSecretsGetHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { vaultManager, db } = this.container; + return await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const secretContent = await vaultManager.withVaults( + [vaultId], + async (vault) => { + return await vaultOps.getSecret(vault, input.secretName); + }, + tran, + ); + return { + secretContent: secretContent.toString('binary'), + }; + }); + } +} + +export { VaultsSecretsGetHandler }; diff --git a/src/client/handlers/vaultsSecretsList.ts b/src/client/handlers/vaultsSecretsList.ts new file mode 100644 index 000000000..46d71b41b --- /dev/null +++ b/src/client/handlers/vaultsSecretsList.ts @@ -0,0 +1,48 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { SecretNameMessage, VaultIdentifierMessage } from './types'; +import type { DB } from '@matrixai/db'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as vaultOps from '../../vaults/VaultOps'; +import { ServerHandler } from '../../rpc/handlers'; + +class VaultsSecretsListHandler extends ServerHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async *handle( + input: ClientRPCRequestParams, + ): AsyncGenerator> { + const { vaultManager, db } = this.container; + const secrets = await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + return await vaultManager.withVaults( + [vaultId], + async (vault) => { + return await vaultOps.listSecrets(vault); + }, + tran, + ); + }); + for (const secret of secrets) { + yield { + secretName: secret, + }; + } + } +} + +export { VaultsSecretsListHandler }; diff --git a/src/client/handlers/vaultsSecretsMkdir.ts b/src/client/handlers/vaultsSecretsMkdir.ts new file mode 100644 index 000000000..c7096bccc --- /dev/null +++ b/src/client/handlers/vaultsSecretsMkdir.ts @@ -0,0 +1,48 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { DB } from '@matrixai/db'; +import type { SecretMkdirMessage, SuccessMessage } from './types'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as vaultOps from '../../vaults/VaultOps'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsSecretsMkdirHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { vaultManager, db } = this.container; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.mkdir(vault, input.dirName, { + recursive: input.recursive, + }); + }, + tran, + ); + }); + return { + success: true, + }; + } +} + +export { VaultsSecretsMkdirHandler }; diff --git a/src/client/handlers/vaultsSecretsNew.ts b/src/client/handlers/vaultsSecretsNew.ts new file mode 100644 index 000000000..4de6b4485 --- /dev/null +++ b/src/client/handlers/vaultsSecretsNew.ts @@ -0,0 +1,47 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { SecretContentMessage, SuccessMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type VaultManager from '../../vaults/VaultManager'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as vaultOps from '../../vaults/VaultOps'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsSecretsNewHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { vaultManager, db } = this.container; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const content = Buffer.from(input.secretContent, 'binary'); + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.addSecret(vault, input.secretName, content); + }, + tran, + ); + }); + return { + success: true, + }; + } +} + +export { VaultsSecretsNewHandler }; diff --git a/src/client/handlers/vaultsSecretsNewDir.ts b/src/client/handlers/vaultsSecretsNewDir.ts new file mode 100644 index 000000000..e598df166 --- /dev/null +++ b/src/client/handlers/vaultsSecretsNewDir.ts @@ -0,0 +1,48 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { SecretDirMessage, SuccessMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type { FileSystem } from 'types'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as vaultOps from '../../vaults/VaultOps'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsSecretsNewDirHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + fs: FileSystem; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { vaultManager, db, fs } = this.container; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.addSecretDirectory(vault, input.dirName, fs); + }, + tran, + ); + }); + return { + success: true, + }; + } +} + +export { VaultsSecretsNewDirHandler }; diff --git a/src/client/handlers/vaultsSecretsRename.ts b/src/client/handlers/vaultsSecretsRename.ts new file mode 100644 index 000000000..e78792347 --- /dev/null +++ b/src/client/handlers/vaultsSecretsRename.ts @@ -0,0 +1,50 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { SecretRenameMessage, SuccessMessage } from './types'; +import type { DB } from '@matrixai/db'; +import type VaultManager from '../../vaults/VaultManager'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as vaultOps from '../../vaults/VaultOps'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsSecretsRenameHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { vaultManager, db } = this.container; + await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + await vaultManager.withVaults( + [vaultId], + async (vault) => { + await vaultOps.renameSecret( + vault, + input.secretName, + input.newSecretName, + ); + }, + tran, + ); + }); + return { + success: true, + }; + } +} + +export { VaultsSecretsRenameHandler }; diff --git a/src/client/handlers/vaultsSecretsStat.ts b/src/client/handlers/vaultsSecretsStat.ts new file mode 100644 index 000000000..9b71f1d14 --- /dev/null +++ b/src/client/handlers/vaultsSecretsStat.ts @@ -0,0 +1,62 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { SecretIdentifierMessage, SecretStatMessage } from './types'; +import type { DB } from '@matrixai/db'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import * as vaultOps from '../../vaults/VaultOps'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsSecretsStatHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { vaultManager, db } = this.container; + return await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const secretName = input.secretName; + const stat = await vaultManager.withVaults( + [vaultId], + async (vault) => { + return await vaultOps.statSecret(vault, secretName); + }, + tran, + ); + return { + stat: { + dev: stat.dev, + ino: stat.ino, + mode: stat.mode, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + rdev: stat.rdev, + size: stat.size, + atime: stat.atime.toString(), + mtime: stat.mtime.toString(), + ctime: stat.ctime.toString(), + birthtime: stat.birthtime.toString(), + blksize: stat.blksize, + blocks: stat.blocks, + }, + }; + }); + } +} + +export { VaultsSecretsStatHandler }; diff --git a/src/client/handlers/vaultsVersion.ts b/src/client/handlers/vaultsVersion.ts new file mode 100644 index 000000000..541559bd0 --- /dev/null +++ b/src/client/handlers/vaultsVersion.ts @@ -0,0 +1,51 @@ +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type VaultManager from '../../vaults/VaultManager'; +import type { VaultsLatestVersionMessage, VaultsVersionMessage } from './types'; +import type { DB } from '@matrixai/db'; +import * as vaultsUtils from '../../vaults/utils'; +import * as vaultsErrors from '../../vaults/errors'; +import { UnaryHandler } from '../../rpc/handlers'; + +class VaultsVersionHandler extends UnaryHandler< + { + vaultManager: VaultManager; + db: DB; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public async handle( + input: ClientRPCRequestParams, + ): Promise> { + const { vaultManager, db } = this.container; + return await db.withTransactionF(async (tran) => { + const vaultIdFromName = await vaultManager.getVaultId( + input.nameOrId, + tran, + ); + const vaultId = + vaultIdFromName ?? vaultsUtils.decodeVaultId(input.nameOrId); + if (vaultId == null) { + throw new vaultsErrors.ErrorVaultsVaultUndefined(); + } + const versionId = input.versionId; + const [latestOid, currentVersionId] = await vaultManager.withVaults( + [vaultId], + async (vault) => { + const latestOid = (await vault.log())[0].commitId; + await vault.version(versionId); + const currentVersionId = (await vault.log(versionId, 0))[0]?.commitId; + return [latestOid, currentVersionId]; + }, + tran, + ); + // Checking if latest version ID + const latestVersion = latestOid === currentVersionId; + return { + latestVersion, + }; + }); + } +} + +export { VaultsVersionHandler }; diff --git a/src/client/index.ts b/src/client/index.ts index d5959db3e..5540ddf94 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,8 +1,4 @@ -export { - default as createClientService, - ClientServiceService, -} from './service'; -export { default as GRPCClientClient } from './GRPCClientClient'; -export * as errors from './errors'; +export * from './handlers'; export * as utils from './utils'; export * as types from './types'; +export * as errors from './errors'; diff --git a/src/client/service/agentLockAll.ts b/src/client/service/agentLockAll.ts deleted file mode 100644 index da90e23a5..000000000 --- a/src/client/service/agentLockAll.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { SessionManager } from '../../sessions'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function agentLockAll({ - authenticate, - sessionManager, - db, - logger, -}: { - authenticate: Authenticate; - sessionManager: SessionManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF((tran) => sessionManager.resetKey(tran)); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${agentLockAll.name}:${e}`); - return; - } - }; -} - -export default agentLockAll; diff --git a/src/client/service/agentStatus.ts b/src/client/service/agentStatus.ts deleted file mode 100644 index b8056ca7e..000000000 --- a/src/client/service/agentStatus.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type CertManager from '../../keys/CertManager'; -import type GRPCServer from '../../grpc/GRPCServer'; -import type Proxy from '../../network/Proxy'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import process from 'process'; -import * as grpcUtils from '../../grpc/utils'; -import * as nodesUtils from '../../nodes/utils'; -import * as agentPB from '../../proto/js/polykey/v1/agent/agent_pb'; -import * as clientUtils from '../utils'; -import * as keysUtils from '../../keys/utils'; - -function agentStatus({ - authenticate, - keyRing, - certManager, - grpcServerClient, - grpcServerAgent, - proxy, - logger, -}: { - authenticate: Authenticate; - keyRing: KeyRing; - certManager: CertManager; - grpcServerClient: GRPCServer; - grpcServerAgent: GRPCServer; - proxy: Proxy; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new agentPB.InfoMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - response.setPid(process.pid); - response.setNodeId(nodesUtils.encodeNodeId(keyRing.getNodeId())); - response.setClientHost(grpcServerClient.getHost()); - response.setClientPort(grpcServerClient.getPort()); - response.setAgentHost(grpcServerAgent.getHost()); - response.setAgentPort(grpcServerAgent.getPort()); - response.setForwardHost(proxy.getForwardHost()); - response.setForwardPort(proxy.getForwardPort()); - response.setProxyHost(proxy.getProxyHost()); - response.setProxyPort(proxy.getProxyPort()); - response.setPublicKeyJwk( - JSON.stringify(keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey)), - ); - response.setCertChainPem(await certManager.getCertPEMsChainPEM()); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${agentStatus.name}:${e}`); - return; - } - }; -} - -export default agentStatus; diff --git a/src/client/service/agentStop.ts b/src/client/service/agentStop.ts deleted file mode 100644 index 2332c732e..000000000 --- a/src/client/service/agentStop.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type PolykeyAgent from '../../PolykeyAgent'; -import type Logger from '@matrixai/logger'; -import { status, running } from '@matrixai/async-init'; -import * as grpcUtils from '../../grpc/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function agentStop({ - authenticate, - pkAgent, - logger, -}: { - authenticate: Authenticate; - pkAgent: PolykeyAgent; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - const response = new utilsPB.EmptyMessage(); - // If not running or in stopping status, then respond successfully - if (!pkAgent[running] || pkAgent[status] === 'stopping') { - callback(null, response); - return; - } - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - // Respond first to close the GRPC connection - callback(null, response); - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${agentStop.name}:${e}`); - return; - } - // Stop is called after GRPC resources are cleared - await pkAgent.stop(); - return; - }; -} - -export default agentStop; diff --git a/src/client/service/agentUnlock.ts b/src/client/service/agentUnlock.ts deleted file mode 100644 index 991a51c9f..000000000 --- a/src/client/service/agentUnlock.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function agentUnlock({ - authenticate, - logger, -}: { - authenticate: Authenticate; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${agentUnlock.name}:${e}`); - return; - } - }; -} - -export default agentUnlock; diff --git a/src/client/service/gestaltsActionsGetByIdentity.ts b/src/client/service/gestaltsActionsGetByIdentity.ts deleted file mode 100644 index dc9da528b..000000000 --- a/src/client/service/gestaltsActionsGetByIdentity.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import { matchSync } from '../../utils/matchers'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import * as clientUtils from '../utils'; - -function gestaltsActionsGetByIdentity({ - authenticate, - gestaltGraph, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new permissionsPB.Actions(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - identityId, - }: { providerId: ProviderId; identityId: IdentityId } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerId: call.request.getProviderId(), - identityId: call.request.getIdentityId(), - }, - ); - - const result = await db.withTransactionF((tran) => - gestaltGraph.getGestaltActions( - ['identity', [providerId, identityId]], - tran, - ), - ); - response.setActionList(Object.keys(result)); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${gestaltsActionsGetByIdentity.name}:${e}`); - return; - } - }; -} - -export default gestaltsActionsGetByIdentity; diff --git a/src/client/service/gestaltsActionsGetByNode.ts b/src/client/service/gestaltsActionsGetByNode.ts deleted file mode 100644 index 8089fee47..000000000 --- a/src/client/service/gestaltsActionsGetByNode.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { NodeId } from '../../ids/types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; -import * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import * as clientUtils from '../utils'; - -function gestaltsActionsGetByNode({ - authenticate, - gestaltGraph, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new permissionsPB.Actions(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { nodeId }: { nodeId: NodeId } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNodeId(), - }, - ); - const result = await db.withTransactionF((tran) => - gestaltGraph.getGestaltActions(['node', nodeId], tran), - ); - response.setActionList(Object.keys(result)); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${gestaltsActionsGetByNode.name}:${e}`); - return; - } - }; -} - -export default gestaltsActionsGetByNode; diff --git a/src/client/service/gestaltsActionsSetByIdentity.ts b/src/client/service/gestaltsActionsSetByIdentity.ts deleted file mode 100644 index dc0d445e5..000000000 --- a/src/client/service/gestaltsActionsSetByIdentity.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type { GestaltAction } from '../../gestalts/types'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as gestaltsErrors from '../../gestalts/errors'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function gestaltsActionsSetByIdentity({ - authenticate, - gestaltGraph, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - action, - providerId, - identityId, - }: { - action: GestaltAction; - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['action'], () => validationUtils.parseGestaltAction(value)], - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - action: call.request.getAction(), - providerId: call.request.getIdentity()?.getProviderId(), - identityId: call.request.getIdentity()?.getIdentityId(), - }, - ); - await db.withTransactionF((tran) => - gestaltGraph.setGestaltAction( - ['identity', [providerId, identityId]], - action, - tran, - ), - ); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, - gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(`${gestaltsActionsSetByIdentity.name}:${e}`); - return; - } - }; -} - -export default gestaltsActionsSetByIdentity; diff --git a/src/client/service/gestaltsActionsSetByNode.ts b/src/client/service/gestaltsActionsSetByNode.ts deleted file mode 100644 index c97f889d0..000000000 --- a/src/client/service/gestaltsActionsSetByNode.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type { GestaltAction } from '../../gestalts/types'; -import type { NodeId } from '../../ids/types'; -import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as gestaltsErrors from '../../gestalts/errors'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function gestaltsActionsSetByNode({ - authenticate, - gestaltGraph, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { nodeId, action }: { nodeId: NodeId; action: GestaltAction } = - validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - [['action'], () => validationUtils.parseGestaltAction(value)], - () => value, - ); - }, - { - nodeId: call.request.getNode()?.getNodeId(), - action: call.request.getAction(), - }, - ); - await db.withTransactionF((tran) => - gestaltGraph.setGestaltAction(['node', nodeId], action, tran), - ); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(`${gestaltsActionsSetByNode.name}:${e}`); - return; - } - }; -} - -export default gestaltsActionsSetByNode; diff --git a/src/client/service/gestaltsActionsUnsetByIdentity.ts b/src/client/service/gestaltsActionsUnsetByIdentity.ts deleted file mode 100644 index 8ce32db19..000000000 --- a/src/client/service/gestaltsActionsUnsetByIdentity.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type { GestaltAction } from '../../gestalts/types'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as gestaltsErrors from '../../gestalts/errors'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function gestaltsActionsUnsetByIdentity({ - authenticate, - gestaltGraph, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - action, - providerId, - identityId, - }: { - action: GestaltAction; - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['action'], () => validationUtils.parseGestaltAction(value)], - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - action: call.request.getAction(), - providerId: call.request.getIdentity()?.getProviderId(), - identityId: call.request.getIdentity()?.getIdentityId(), - }, - ); - await db.withTransactionF((tran) => - gestaltGraph.unsetGestaltAction( - ['identity', [providerId, identityId]], - action, - tran, - ), - ); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, - gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(`${gestaltsActionsUnsetByIdentity.name}:${e}`); - return; - } - }; -} - -export default gestaltsActionsUnsetByIdentity; diff --git a/src/client/service/gestaltsActionsUnsetByNode.ts b/src/client/service/gestaltsActionsUnsetByNode.ts deleted file mode 100644 index 7a072184a..000000000 --- a/src/client/service/gestaltsActionsUnsetByNode.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type { GestaltAction } from '../../gestalts/types'; -import type { NodeId } from '../../ids/types'; -import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as gestaltsErrors from '../../gestalts/errors'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function gestaltsActionsUnsetByNode({ - authenticate, - gestaltGraph, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { nodeId, action }: { nodeId: NodeId; action: GestaltAction } = - validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - [['action'], () => validationUtils.parseGestaltAction(value)], - () => value, - ); - }, - { - nodeId: call.request.getNode()?.getNodeId(), - action: call.request.getAction(), - }, - ); - await db.withTransactionF((tran) => - gestaltGraph.unsetGestaltAction(['node', nodeId], action, tran), - ); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(`${gestaltsActionsUnsetByNode.name}:${e}`); - return; - } - }; -} - -export default gestaltsActionsUnsetByNode; diff --git a/src/client/service/gestaltsDiscoveryByIdentity.ts b/src/client/service/gestaltsDiscoveryByIdentity.ts deleted file mode 100644 index 08f2df64e..000000000 --- a/src/client/service/gestaltsDiscoveryByIdentity.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type Discovery from '../../discovery/Discovery'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import type Logger from '@matrixai/logger'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as validationUtils from '../../validation/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function gestaltsDiscoveryByIdentity({ - authenticate, - discovery, - logger, -}: { - authenticate: Authenticate; - discovery: Discovery; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - identityId, - }: { - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerId: call.request.getProviderId(), - identityId: call.request.getIdentityId(), - }, - ); - await discovery.queueDiscoveryByIdentity(providerId, identityId); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${gestaltsDiscoveryByIdentity.name}:${e}`); - return; - } - }; -} - -export default gestaltsDiscoveryByIdentity; diff --git a/src/client/service/gestaltsDiscoveryByNode.ts b/src/client/service/gestaltsDiscoveryByNode.ts deleted file mode 100644 index a28c6b957..000000000 --- a/src/client/service/gestaltsDiscoveryByNode.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type Discovery from '../../discovery/Discovery'; -import type { NodeId } from '../../ids/types'; -import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import type Logger from '@matrixai/logger'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as validationUtils from '../../validation/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function gestaltsDiscoveryByNode({ - authenticate, - discovery, - logger, -}: { - authenticate: Authenticate; - discovery: Discovery; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNodeId(), - }, - ); - await discovery.queueDiscoveryByNode(nodeId); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${gestaltsDiscoveryByNode.name}:${e}`); - return; - } - }; -} - -export default gestaltsDiscoveryByNode; diff --git a/src/client/service/gestaltsGestaltGetByIdentity.ts b/src/client/service/gestaltsGestaltGetByIdentity.ts deleted file mode 100644 index 0ab2dfea5..000000000 --- a/src/client/service/gestaltsGestaltGetByIdentity.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; -import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; -import * as clientUtils from '../utils'; -import * as nodesUtils from '../../nodes/utils'; - -function gestaltsGestaltGetByIdentity({ - authenticate, - gestaltGraph, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new gestaltsPB.Graph(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - identityId, - }: { - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerId: call.request.getProviderId(), - identityId: call.request.getIdentityId(), - }, - ); - const gestalt = await db.withTransactionF((tran) => - gestaltGraph.getGestaltByIdentity([providerId, identityId], tran), - ); - if (gestalt != null) { - const newGestalt = { - matrix: {}, - nodes: {}, - identities: gestalt.identities, - }; - for (const [key, value] of Object.entries(gestalt.nodes)) { - newGestalt.nodes[key] = { - nodeId: nodesUtils.encodeNodeId(value.nodeId), - }; - } - for (const keyA of Object.keys(gestalt.matrix)) { - for (const keyB of Object.keys(gestalt.matrix[keyA])) { - let record = newGestalt.matrix[keyA]; - if (record == null) { - record = {}; - newGestalt.matrix[keyA] = record; - } - record[keyB] = null; - } - } - response.setGestaltGraph(JSON.stringify(newGestalt)); - } - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${gestaltsGestaltGetByIdentity.name}:${e}`); - return; - } - }; -} - -export default gestaltsGestaltGetByIdentity; diff --git a/src/client/service/gestaltsGestaltGetByNode.ts b/src/client/service/gestaltsGestaltGetByNode.ts deleted file mode 100644 index becd0b002..000000000 --- a/src/client/service/gestaltsGestaltGetByNode.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { NodeId } from '../../ids/types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; -import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; -import * as clientUtils from '../utils'; -import * as nodesUtils from '../../nodes/utils'; - -function gestaltsGestaltGetByNode({ - authenticate, - gestaltGraph, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new gestaltsPB.Graph(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNodeId(), - }, - ); - const gestalt = await db.withTransactionF((tran) => - gestaltGraph.getGestaltByNode(nodeId, tran), - ); - if (gestalt != null) { - const newGestalt = { - matrix: {}, - nodes: {}, - identities: gestalt.identities, - }; - for (const [key, value] of Object.entries(gestalt.nodes)) { - newGestalt.nodes[key] = { - nodeId: nodesUtils.encodeNodeId(value.nodeId), - }; - } - for (const keyA of Object.keys(gestalt.matrix)) { - for (const keyB of Object.keys(gestalt.matrix[keyA])) { - let record = newGestalt.matrix[keyA]; - if (record == null) { - record = {}; - newGestalt.matrix[keyA] = record; - } - record[keyB] = null; - } - } - response.setGestaltGraph(JSON.stringify(newGestalt)); - } - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${gestaltsGestaltGetByNode.name}:${e}`); - return; - } - }; -} - -export default gestaltsGestaltGetByNode; diff --git a/src/client/service/gestaltsGestaltList.ts b/src/client/service/gestaltsGestaltList.ts deleted file mode 100644 index f15e64af9..000000000 --- a/src/client/service/gestaltsGestaltList.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; -import * as clientUtils from '../utils'; -import * as nodesUtils from '../../nodes/utils'; - -function gestaltsGestaltList({ - authenticate, - gestaltGraph, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - for await (const gestalt of gestaltGraph.getGestalts(tran)) { - const newGestalt = { - matrix: {}, - nodes: {}, - identities: gestalt.identities, - }; - for (const [key, value] of Object.entries(gestalt.nodes)) { - newGestalt.nodes[key] = { - nodeId: nodesUtils.encodeNodeId(value.nodeId), - }; - } - for (const keyA of Object.keys(gestalt.matrix)) { - let record = newGestalt.matrix[keyA]; - if (record == null) { - record = {}; - newGestalt.matrix[keyA] = record; - } - for (const keyB of Object.keys(gestalt.matrix[keyA])) { - record[keyB] = null; - } - } - const gestaltMessage = new gestaltsPB.Gestalt(); - gestaltMessage.setName(JSON.stringify(newGestalt)); - await genWritable.next(gestaltMessage); - } - }); - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e) && - logger.error(`${gestaltsGestaltList.name}:${e}`); - return; - } - }; -} - -export default gestaltsGestaltList; diff --git a/src/client/service/gestaltsGestaltTrustByIdentity.ts b/src/client/service/gestaltsGestaltTrustByIdentity.ts deleted file mode 100644 index 390e8d1a2..000000000 --- a/src/client/service/gestaltsGestaltTrustByIdentity.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import type Discovery from '../../discovery/Discovery'; -import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import type Logger from '@matrixai/logger'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as gestaltsErrors from '../../gestalts/errors'; -import * as validationUtils from '../../validation/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function gestaltsGestaltTrustByIdentity({ - authenticate, - gestaltGraph, - discovery, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - discovery: Discovery; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - identityId, - }: { - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerId: call.request.getProviderId(), - identityId: call.request.getIdentityId(), - }, - ); - // Set the identity in the gestalt graph if not already - await db.withTransactionF(async (tran) => { - if ( - (await gestaltGraph.getGestaltByIdentity( - [providerId, identityId], - tran, - )) == null - ) { - // Queue the new identity for discovery - // This will only add the identity to the GG if it is connected to a - // node (required to set permissions for it) - await discovery.queueDiscoveryByIdentity(providerId, identityId); - } - // We can currently only set permissions for identities that are - // connected to at least one node. If these conditions are not met, this - // will throw an error. Since discovery can take time, you may need to - // reattempt this command if it fails on the first attempt and you expect - // there to be a linked node for the identity. - await gestaltGraph.setGestaltAction( - ['identity', [providerId, identityId]], - 'notify', - tran, - ); - }); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, - gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(`${gestaltsGestaltTrustByIdentity.name}:${e}`); - return; - } - }; -} - -export default gestaltsGestaltTrustByIdentity; diff --git a/src/client/service/gestaltsGestaltTrustByNode.ts b/src/client/service/gestaltsGestaltTrustByNode.ts deleted file mode 100644 index a4153fb05..000000000 --- a/src/client/service/gestaltsGestaltTrustByNode.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type Discovery from '../../discovery/Discovery'; -import type { NodeId } from '../../ids/types'; -import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import type Logger from '@matrixai/logger'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as gestaltsErrors from '../../gestalts/errors'; -import * as validationUtils from '../../validation/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function gestaltsGestaltTrustByNode({ - authenticate, - gestaltGraph, - discovery, - db, - logger, -}: { - authenticate: Authenticate; - gestaltGraph: GestaltGraph; - discovery: Discovery; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNodeId(), - }, - ); - await db.withTransactionF(async (tran) => { - // Set the node in the gestalt graph if not already - if ((await gestaltGraph.getGestaltByNode(nodeId, tran)) == null) { - await gestaltGraph.setNode( - { - nodeId, - }, - tran, - ); - // Queue the new node for discovery - await discovery.queueDiscoveryByNode(nodeId); - } - // Set notify permission - await gestaltGraph.setGestaltAction(['node', nodeId], 'notify', tran); - }); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(`${gestaltsGestaltTrustByNode.name}:${e}`); - return; - } - }; -} - -export default gestaltsGestaltTrustByNode; diff --git a/src/client/service/identitiesAuthenticate.ts b/src/client/service/identitiesAuthenticate.ts deleted file mode 100644 index 0950abddd..000000000 --- a/src/client/service/identitiesAuthenticate.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type { ProviderId } from '../../identities/types'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as identitiesErrors from '../../identities/errors'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync, never } from '../../utils'; -import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import * as clientUtils from '../utils'; - -function identitiesAuthenticate({ - authenticate, - identitiesManager, - logger, -}: { - authenticate: Authenticate; - identitiesManager: IdentitiesManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream< - identitiesPB.Provider, - identitiesPB.AuthenticationProcess - >, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - }: { - providerId: ProviderId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - () => value, - ); - }, - { - providerId: call.request.getProviderId(), - }, - ); - const provider = identitiesManager.getProvider(providerId); - if (provider == null) { - throw new identitiesErrors.ErrorProviderMissing(); - } - const authFlow = provider.authenticate(); - let authFlowResult = await authFlow.next(); - if (authFlowResult.done) { - never(); - } - const authProcess = new identitiesPB.AuthenticationProcess(); - const authRequest = new identitiesPB.AuthenticationRequest(); - authRequest.setUrl(authFlowResult.value.url); - const map = authRequest.getDataMap(); - for (const [k, v] of Object.entries(authFlowResult.value.data)) { - map.set(k, v); - } - authProcess.setRequest(authRequest); - await genWritable.next(authProcess); - authFlowResult = await authFlow.next(); - if (!authFlowResult.done) { - never(); - } - const authResponse = new identitiesPB.AuthenticationResponse(); - authResponse.setIdentityId(authFlowResult.value); - authProcess.setResponse(authResponse); - await genWritable.next(authProcess); - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e, [ - identitiesErrors.ErrorProviderMissing, - ]) && logger.error(`${identitiesAuthenticate.name}:${e}`); - return; - } - }; -} - -export default identitiesAuthenticate; diff --git a/src/client/service/identitiesAuthenticatedGet.ts b/src/client/service/identitiesAuthenticatedGet.ts deleted file mode 100644 index 106a1f53a..000000000 --- a/src/client/service/identitiesAuthenticatedGet.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type { ProviderId } from '../../identities/types'; -import type Logger from '@matrixai/logger'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as validationUtils from '../../validation/utils'; -import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import * as clientUtils from '../utils'; - -function identitiesAuthenticatedGet({ - authenticate, - identitiesManager, - logger, -}: { - authenticate: Authenticate; - identitiesManager: IdentitiesManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream< - identitiesPB.OptionalProvider, - identitiesPB.Provider - >, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - let providerId: ProviderId | undefined; - if (call.request.hasProviderId()) { - providerId = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - () => value, - ); - }, - { - providerId: call.request.getProviderId(), - }, - ).providerId; - } - const providerIds: Array = - providerId == null - ? (Object.keys(identitiesManager.getProviders()) as Array) - : [providerId]; - for (const providerId of providerIds) { - const provider = identitiesManager.getProvider(providerId); - if (provider == null) { - continue; - } - const identities = await provider.getAuthIdentityIds(); - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(provider.id); - for (const identityId of identities) { - providerMessage.setIdentityId(identityId); - await genWritable.next(providerMessage); - } - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e) && - logger.error(`${identitiesAuthenticatedGet.name}:${e}`); - return; - } - }; -} - -export default identitiesAuthenticatedGet; diff --git a/src/client/service/identitiesClaim.ts b/src/client/service/identitiesClaim.ts deleted file mode 100644 index e5944ee8f..000000000 --- a/src/client/service/identitiesClaim.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as identitiesErrors from '../../identities/errors'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; -import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import * as clientUtils from '../utils'; - -/** - * Augments the keynode with a new identity. - */ -function identitiesClaim({ - authenticate, - identitiesManager, - logger, -}: { - authenticate: Authenticate; - identitiesManager: IdentitiesManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new identitiesPB.Claim(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - identityId, - }: { - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerId: call.request.getProviderId(), - identityId: call.request.getIdentityId(), - }, - ); - const claimData = await identitiesManager.handleClaimIdentity( - providerId, - identityId, - ); - response.setClaimId(claimData.id); - if (claimData.url) { - response.setUrl(claimData.url); - } - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - identitiesErrors.ErrorProviderMissing, - identitiesErrors.ErrorProviderUnauthenticated, - ]) && logger.error(`${identitiesClaim.name}:${e}`); - return; - } - }; -} - -export default identitiesClaim; diff --git a/src/client/service/identitiesInfoConnectedGet.ts b/src/client/service/identitiesInfoConnectedGet.ts deleted file mode 100644 index f8f906807..000000000 --- a/src/client/service/identitiesInfoConnectedGet.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type { - IdentityData, - IdentityId, - ProviderId, -} from '../../identities/types'; -import type Logger from '@matrixai/logger'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as validationUtils from '../../validation/utils'; -import * as identitiesErrors from '../../identities/errors'; -import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import * as clientUtils from '../utils'; - -function identitiesInfoConnectedGet({ - authenticate, - identitiesManager, - logger, -}: { - authenticate: Authenticate; - identitiesManager: IdentitiesManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream< - identitiesPB.ProviderSearch, - identitiesPB.Info - >, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerIds, - }: { - providerIds: Array; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerIds'], () => value.map(validationUtils.parseProviderId)], - () => value, - ); - }, - { - providerIds: call.request.getProviderIdList(), - }, - ); - let identityId: IdentityId | undefined; - if (call.request.hasAuthIdentityId()) { - identityId = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - identityId: call.request.getAuthIdentityId(), - }, - ).identityId; - } - // Process options that were set - if (providerIds.length === 0) { - Object.keys(identitiesManager.getProviders()).forEach((id) => - providerIds.push(id as ProviderId), - ); - } - const getDisconnected = call.request.getDisconnected(); - if (getDisconnected) { - // Can only get connected identities at this stage - throw new identitiesErrors.ErrorProviderUnimplemented(); - } - const identities: Array> = []; - for (const providerId of providerIds) { - // Get provider from id - const provider = identitiesManager.getProvider(providerId); - if (provider === undefined) { - throw new identitiesErrors.ErrorProviderMissing(); - } - // Get our own authenticated identity in order to query, skip provider - // if not authenticated - const authIdentities = await provider.getAuthIdentityIds(); - if (authIdentities.length === 0) { - continue; - } - const authIdentityId = - identityId === undefined ? authIdentities[0] : identityId; - identities.push( - provider.getConnectedIdentityDatas( - authIdentityId, - call.request.getSearchTermList(), - ), - ); - } - let limit: number | undefined; - if (call.request.getLimit() !== '') { - limit = parseInt(call.request.getLimit()); - } - let count = 0; - for (const gen of identities) { - for await (const identity of gen) { - if (limit !== undefined && count >= limit) break; - const identityInfoMessage = new identitiesPB.Info(); - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(identity.providerId); - providerMessage.setIdentityId(identity.identityId); - identityInfoMessage.setProvider(providerMessage); - identityInfoMessage.setName(identity.name ?? ''); - identityInfoMessage.setEmail(identity.email ?? ''); - identityInfoMessage.setUrl(identity.url ?? ''); - await genWritable.next(identityInfoMessage); - count++; - } - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e, [ - identitiesErrors.ErrorProviderMissing, - identitiesErrors.ErrorProviderUnauthenticated, - identitiesErrors.ErrorProviderUnimplemented, - ]) && logger.error(`${identitiesInfoConnectedGet.name}:${e}`); - return; - } - }; -} - -export default identitiesInfoConnectedGet; diff --git a/src/client/service/identitiesInfoGet.ts b/src/client/service/identitiesInfoGet.ts deleted file mode 100644 index 3fa2bdbc1..000000000 --- a/src/client/service/identitiesInfoGet.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type { - IdentityData, - IdentityId, - ProviderId, -} from '../../identities/types'; -import type Logger from '@matrixai/logger'; -import { validateSync } from '../../validation'; -import { matchSync } from '../../utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as validationUtils from '../../validation/utils'; -import * as identitiesUtils from '../../identities/utils'; -import * as identitiesErrors from '../../identities/errors'; -import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import * as clientUtils from '../utils'; - -function identitiesInfoGet({ - authenticate, - identitiesManager, - logger, -}: { - authenticate: Authenticate; - identitiesManager: IdentitiesManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream< - identitiesPB.ProviderSearch, - identitiesPB.Info - >, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerIds, - identityId, - }: { - providerIds: Array; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerIds'], () => value.map(validationUtils.parseProviderId)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerIds: call.request.getProviderIdList(), - identityId: call.request.getIdentityId(), - }, - ); - // Process options that were set - if (providerIds.length === 0) { - Object.keys(identitiesManager.getProviders()).forEach((id) => - providerIds.push(id as ProviderId), - ); - } - const searchTerms = call.request.getSearchTermList(); - const getDisconnected = call.request.getDisconnected(); - if (getDisconnected) { - // Currently this command performs the same way regardless of whether - // this option is set (i.e. always disconnected) - } - const identities: Array = []; - for (const providerId of providerIds) { - // Get provider from id - const provider = identitiesManager.getProvider(providerId); - if (provider === undefined) { - throw new identitiesErrors.ErrorProviderMissing(); - } - // Get our own authenticated identity in order to query, skip provider - // if not authenticated - // It doesn't matter which one we use since `getIdentityData` does not - // require the identity to be connected - const authIdentities = await provider.getAuthIdentityIds(); - if (authIdentities.length === 0) { - continue; - } - // Get identity data - identities.push( - await provider.getIdentityData(authIdentities[0], identityId), - ); - } - let limit: number | undefined; - if (call.request.getLimit() !== '') { - limit = parseInt(call.request.getLimit()); - } - if (limit === undefined || limit > identities.length) { - limit = identities.length; - } - for (let i = 0; i < limit; i++) { - const identity = identities[i]; - if (identity !== undefined) { - if (identitiesUtils.matchIdentityData(identity, searchTerms)) { - const identityInfoMessage = new identitiesPB.Info(); - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(identity.providerId); - providerMessage.setIdentityId(identity.identityId); - identityInfoMessage.setProvider(providerMessage); - identityInfoMessage.setName(identity.name ?? ''); - identityInfoMessage.setEmail(identity.email ?? ''); - identityInfoMessage.setUrl(identity.url ?? ''); - await genWritable.next(identityInfoMessage); - } - } - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e, [ - identitiesErrors.ErrorProviderMissing, - identitiesErrors.ErrorProviderUnauthenticated, - ]) && logger.error(`${identitiesInfoGet.name}:${e}`); - return; - } - }; -} - -export default identitiesInfoGet; diff --git a/src/client/service/identitiesInvite.ts b/src/client/service/identitiesInvite.ts deleted file mode 100644 index 048dcf3f4..000000000 --- a/src/client/service/identitiesInvite.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type { NodeId } from '../../ids/types'; -import type NotificationsManager from '../../notifications/NotificationsManager'; -import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import type Logger from '@matrixai/logger'; -import type ACL from '../../acl/ACL'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as nodesErrors from '../../nodes/errors'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -/** - * Adds permission for a node to claim us using nodes claim. - * Also sends a notification alerting the node of the new permission. - */ -function identitiesInvite({ - authenticate, - notificationsManager, - acl, - logger, -}: { - authenticate: Authenticate; - notificationsManager: NotificationsManager; - acl: ACL; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNodeId(), - }, - ); - // Sending the notification, we don't care if it fails - try { - await notificationsManager.sendNotification(nodeId, { - type: 'GestaltInvite', - }); - } catch { - logger.warn('Failed to send gestalt invitation to target node'); - } - // Allowing claims from that gestalt - await acl.setNodeAction(nodeId, 'claim'); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(`${identitiesInvite.name}:${e}`); - return; - } - }; -} - -export default identitiesInvite; diff --git a/src/client/service/identitiesProvidersList.ts b/src/client/service/identitiesProvidersList.ts deleted file mode 100644 index 17ae7bf31..000000000 --- a/src/client/service/identitiesProvidersList.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import * as clientUtils from '../utils'; - -function identitiesProvidersList({ - authenticate, - identitiesManager, - logger, -}: { - authenticate: Authenticate; - identitiesManager: IdentitiesManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new identitiesPB.Provider(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const providers = identitiesManager.getProviders(); - response.setProviderId(JSON.stringify(Object.keys(providers))); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${identitiesProvidersList.name}:${e}`); - return; - } - }; -} - -export default identitiesProvidersList; diff --git a/src/client/service/identitiesTokenDelete.ts b/src/client/service/identitiesTokenDelete.ts deleted file mode 100644 index da0bbaa20..000000000 --- a/src/client/service/identitiesTokenDelete.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function identitiesTokenDelete({ - authenticate, - identitiesManager, - db, - logger, -}: { - authenticate: Authenticate; - identitiesManager: IdentitiesManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - identityId, - }: { - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerId: call.request.getProviderId(), - identityId: call.request.getIdentityId(), - }, - ); - await db.withTransactionF((tran) => - identitiesManager.delToken(providerId, identityId, tran), - ); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${identitiesTokenDelete.name}:${e}`); - return; - } - }; -} - -export default identitiesTokenDelete; diff --git a/src/client/service/identitiesTokenGet.ts b/src/client/service/identitiesTokenGet.ts deleted file mode 100644 index 3a25c1b06..000000000 --- a/src/client/service/identitiesTokenGet.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; -import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import * as clientUtils from '../utils'; - -function identitiesTokenGet({ - authenticate, - identitiesManager, - db, - logger, -}: { - authenticate: Authenticate; - identitiesManager: IdentitiesManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new identitiesPB.Token(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - identityId, - }: { - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerId: call.request.getProviderId(), - identityId: call.request.getIdentityId(), - }, - ); - const tokens = await db.withTransactionF((tran) => - identitiesManager.getToken(providerId, identityId, tran), - ); - response.setToken(JSON.stringify(tokens)); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${identitiesTokenGet.name}:${e}`); - return; - } - }; -} - -export default identitiesTokenGet; diff --git a/src/client/service/identitiesTokenPut.ts b/src/client/service/identitiesTokenPut.ts deleted file mode 100644 index c75804b57..000000000 --- a/src/client/service/identitiesTokenPut.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type IdentitiesManager from '../../identities/IdentitiesManager'; -import type { - IdentityId, - ProviderId, - ProviderToken, -} from '../../identities/types'; -import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function identitiesTokenPut({ - authenticate, - identitiesManager, - db, - logger, -}: { - authenticate: Authenticate; - identitiesManager: IdentitiesManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall< - identitiesPB.TokenSpecific, - utilsPB.EmptyMessage - >, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - identityId, - }: { - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerId: call.request.getProvider()?.getProviderId(), - identityId: call.request.getProvider()?.getIdentityId(), - }, - ); - await db.withTransactionF((tran) => - identitiesManager.putToken( - providerId, - identityId, - { - accessToken: call.request.getToken(), - } as ProviderToken, - tran, - ), - ); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${identitiesTokenPut.name}:${e}`); - return; - } - }; -} - -export default identitiesTokenPut; diff --git a/src/client/service/index.ts b/src/client/service/index.ts deleted file mode 100644 index 9809cf9cc..000000000 --- a/src/client/service/index.ts +++ /dev/null @@ -1,210 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type PolykeyAgent from '../../PolykeyAgent'; -import type KeyRing from '../../keys/KeyRing'; -import type CertManager from '../../keys/CertManager'; -import type { VaultManager } from '../../vaults'; -import type { - NodeManager, - NodeConnectionManager, - NodeGraph, -} from '../../nodes'; -import type { IdentitiesManager } from '../../identities'; -import type { GestaltGraph } from '../../gestalts'; -import type { SessionManager } from '../../sessions'; -import type NotificationsManager from '../../notifications/NotificationsManager'; -import type { Discovery } from '../../discovery'; -import type { Sigchain } from '../../sigchain'; -import type { GRPCServer } from '../../grpc'; -import type ACL from '../../acl/ACL'; -import type Proxy from '../../network/Proxy'; -import type { IClientServiceServer } from '../../proto/js/polykey/v1/client_service_grpc_pb'; -import type { FileSystem } from '../../types'; -import Logger from '@matrixai/logger'; -import agentLockAll from './agentLockAll'; -import agentStatus from './agentStatus'; -import agentStop from './agentStop'; -import agentUnlock from './agentUnlock'; -import gestaltsActionsGetByIdentity from './gestaltsActionsGetByIdentity'; -import gestaltsActionsGetByNode from './gestaltsActionsGetByNode'; -import gestaltsActionsSetByIdentity from './gestaltsActionsSetByIdentity'; -import gestaltsActionsSetByNode from './gestaltsActionsSetByNode'; -import gestaltsActionsUnsetByIdentity from './gestaltsActionsUnsetByIdentity'; -import gestaltsActionsUnsetByNode from './gestaltsActionsUnsetByNode'; -import gestaltsDiscoveryByIdentity from './gestaltsDiscoveryByIdentity'; -import gestaltsDiscoveryByNode from './gestaltsDiscoveryByNode'; -import gestaltsGestaltGetByIdentity from './gestaltsGestaltGetByIdentity'; -import gestaltsGestaltGetByNode from './gestaltsGestaltGetByNode'; -import gestaltsGestaltList from './gestaltsGestaltList'; -import gestaltsGestaltTrustByIdentity from './gestaltsGestaltTrustByIdentity'; -import gestaltsGestaltTrustByNode from './gestaltsGestaltTrustByNode'; -import identitiesAuthenticate from './identitiesAuthenticate'; -import identitiesAuthenticatedGet from './identitiesAuthenticatedGet'; -import identitiesClaim from './identitiesClaim'; -import identitiesInfoGet from './identitiesInfoGet'; -import identitiesInfoConnectedGet from './identitiesInfoConnectedGet'; -import identitiesProvidersList from './identitiesProvidersList'; -import identitiesTokenDelete from './identitiesTokenDelete'; -import identitiesTokenGet from './identitiesTokenGet'; -import identitiesTokenPut from './identitiesTokenPut'; -import identitiesInvite from './identitiesInvite'; -import keysCertsChainGet from './keysCertsChainGet'; -import keysCertsGet from './keysCertsGet'; -import keysDecrypt from './keysDecrypt'; -import keysEncrypt from './keysEncrypt'; -import keysKeyPairRenew from './keysKeyPairRenew'; -import keysKeyPairReset from './keysKeyPairReset'; -import keysKeyPair from './keysKeyPair'; -import keysPublicKey from './keysPublicKey'; -import keysPasswordChange from './keysPasswordChange'; -import keysSign from './keysSign'; -import keysVerify from './keysVerify'; -import nodesAdd from './nodesAdd'; -import nodesClaim from './nodesClaim'; -import nodesFind from './nodesFind'; -import nodesPing from './nodesPing'; -import nodesGetAll from './nodesGetAll'; -import nodesListConnections from './nodesListConnections'; -import notificationsClear from './notificationsClear'; -import notificationsRead from './notificationsRead'; -import notificationsSend from './notificationsSend'; -import vaultsClone from './vaultsClone'; -import vaultsCreate from './vaultsCreate'; -import vaultsDelete from './vaultsDelete'; -import vaultsList from './vaultsList'; -import vaultsLog from './vaultsLog'; -import vaultsPermissionGet from './vaultsPermissionGet'; -import vaultsPermissionSet from './vaultsPermissionSet'; -import vaultsPermissionUnset from './vaultsPermissionUnset'; -import vaultsPull from './vaultsPull'; -import vaultsRename from './vaultsRename'; -import vaultsScan from './vaultsScan'; -import vaultsVersion from './vaultsVersion'; -import vaultsSecretsDelete from './vaultsSecretsDelete'; -import vaultsSecretsEdit from './vaultsSecretsEdit'; -import vaultsSecretsGet from './vaultsSecretsGet'; -import vaultsSecretsList from './vaultsSecretsList'; -import vaultsSecretsMkdir from './vaultsSecretsMkdir'; -import vaultsSecretsNew from './vaultsSecretsNew'; -import vaultsSecretsNewDir from './vaultsSecretsNewDir'; -import vaultsSecretsRename from './vaultsSecretsRename'; -import vaultsSecretsStat from './vaultsSecretsStat'; -import * as clientUtils from '../utils'; -import { ClientServiceService } from '../../proto/js/polykey/v1/client_service_grpc_pb'; - -function createService({ - keyRing, - sessionManager, - db, - certManager, - logger = new Logger('GRPCClientClientService'), - fs = require('fs'), - ...containerRest -}: { - pkAgent: PolykeyAgent; - db: DB; - certManager: CertManager; - keyRing: KeyRing; - vaultManager: VaultManager; - nodeGraph: NodeGraph; - nodeConnectionManager: NodeConnectionManager; - nodeManager: NodeManager; - identitiesManager: IdentitiesManager; - gestaltGraph: GestaltGraph; - sessionManager: SessionManager; - notificationsManager: NotificationsManager; - discovery: Discovery; - sigchain: Sigchain; - acl: ACL; - grpcServerClient: GRPCServer; - grpcServerAgent: GRPCServer; - proxy: Proxy; - logger?: Logger; - fs?: FileSystem; -}) { - const authenticate = clientUtils.authenticator(sessionManager, keyRing); - const container = { - ...containerRest, - keyRing, - sessionManager, - db, - certManager, - logger, - fs, - authenticate, - }; - const service: IClientServiceServer = { - agentLockAll: agentLockAll(container), - agentStatus: agentStatus(container), - agentStop: agentStop(container), - agentUnlock: agentUnlock(container), - gestaltsActionsGetByIdentity: gestaltsActionsGetByIdentity(container), - gestaltsActionsGetByNode: gestaltsActionsGetByNode(container), - gestaltsActionsSetByIdentity: gestaltsActionsSetByIdentity(container), - gestaltsActionsSetByNode: gestaltsActionsSetByNode(container), - gestaltsActionsUnsetByIdentity: gestaltsActionsUnsetByIdentity(container), - gestaltsActionsUnsetByNode: gestaltsActionsUnsetByNode(container), - gestaltsDiscoveryByIdentity: gestaltsDiscoveryByIdentity(container), - gestaltsDiscoveryByNode: gestaltsDiscoveryByNode(container), - gestaltsGestaltGetByIdentity: gestaltsGestaltGetByIdentity(container), - gestaltsGestaltGetByNode: gestaltsGestaltGetByNode(container), - gestaltsGestaltList: gestaltsGestaltList(container), - gestaltsGestaltTrustByIdentity: gestaltsGestaltTrustByIdentity(container), - gestaltsGestaltTrustByNode: gestaltsGestaltTrustByNode(container), - identitiesAuthenticate: identitiesAuthenticate(container), - identitiesAuthenticatedGet: identitiesAuthenticatedGet(container), - identitiesClaim: identitiesClaim(container), - identitiesInfoGet: identitiesInfoGet(container), - identitiesInfoConnectedGet: identitiesInfoConnectedGet(container), - identitiesProvidersList: identitiesProvidersList(container), - identitiesTokenDelete: identitiesTokenDelete(container), - identitiesTokenGet: identitiesTokenGet(container), - identitiesTokenPut: identitiesTokenPut(container), - identitiesInvite: identitiesInvite(container), - keysCertsChainGet: keysCertsChainGet(container), - keysCertsGet: keysCertsGet(container), - keysDecrypt: keysDecrypt(container), - keysEncrypt: keysEncrypt(container), - keysKeyPairRenew: keysKeyPairRenew(container), - keysKeyPairReset: keysKeyPairReset(container), - keysKeyPair: keysKeyPair(container), - keysPublicKey: keysPublicKey(container), - keysPasswordChange: keysPasswordChange(container), - keysSign: keysSign(container), - keysVerify: keysVerify(container), - nodesAdd: nodesAdd(container), - nodesClaim: nodesClaim(container), - nodesFind: nodesFind(container), - nodesPing: nodesPing(container), - nodesGetAll: nodesGetAll(container), - nodesListConnections: nodesListConnections(container), - notificationsClear: notificationsClear(container), - notificationsRead: notificationsRead(container), - notificationsSend: notificationsSend(container), - vaultsClone: vaultsClone(container), - vaultsCreate: vaultsCreate(container), - vaultsDelete: vaultsDelete(container), - vaultsList: vaultsList(container), - vaultsLog: vaultsLog(container), - vaultsPermissionSet: vaultsPermissionSet(container), - vaultsPermissionUnset: vaultsPermissionUnset(container), - vaultsPermissionGet: vaultsPermissionGet(container), - vaultsPull: vaultsPull(container), - vaultsRename: vaultsRename(container), - vaultsScan: vaultsScan(container), - vaultsVersion: vaultsVersion(container), - vaultsSecretsDelete: vaultsSecretsDelete(container), - vaultsSecretsEdit: vaultsSecretsEdit(container), - vaultsSecretsGet: vaultsSecretsGet(container), - vaultsSecretsList: vaultsSecretsList(container), - vaultsSecretsMkdir: vaultsSecretsMkdir(container), - vaultsSecretsNew: vaultsSecretsNew(container), - vaultsSecretsNewDir: vaultsSecretsNewDir(container), - vaultsSecretsRename: vaultsSecretsRename(container), - vaultsSecretsStat: vaultsSecretsStat(container), - }; - return service; -} - -export default createService; - -export { ClientServiceService }; diff --git a/src/client/service/keysCertsChainGet.ts b/src/client/service/keysCertsChainGet.ts deleted file mode 100644 index cbb75dcd4..000000000 --- a/src/client/service/keysCertsChainGet.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type CertManager from '../../keys/CertManager'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import * as clientUtils from '../utils'; - -function keysCertsChainGet({ - authenticate, - certManager, - logger, -}: { - authenticate: Authenticate; - certManager: CertManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const certsPEMs: Array = await certManager.getCertPEMsChain(); - let certMessage: keysPB.Certificate; - for (const certPEM of certsPEMs) { - certMessage = new keysPB.Certificate(); - certMessage.setCert(certPEM); - await genWritable.next(certMessage); - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e) && - logger.error(`${keysCertsChainGet.name}:${e}`); - return; - } - }; -} - -export default keysCertsChainGet; diff --git a/src/client/service/keysCertsGet.ts b/src/client/service/keysCertsGet.ts deleted file mode 100644 index 24be53975..000000000 --- a/src/client/service/keysCertsGet.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type CertManager from '../../keys/CertManager'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import * as clientUtils from '../utils'; - -function keysCertsGet({ - authenticate, - certManager, - logger, -}: { - authenticate: Authenticate; - certManager: CertManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new keysPB.Certificate(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const cert = await certManager.getCurrentCertPEM(); - response.setCert(cert); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysCertsGet.name}:${e}`); - return; - } - }; -} - -export default keysCertsGet; diff --git a/src/client/service/keysDecrypt.ts b/src/client/service/keysDecrypt.ts deleted file mode 100644 index 7c3b8dbe0..000000000 --- a/src/client/service/keysDecrypt.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import * as clientUtils from '../utils'; -import { never } from '../../utils/index'; - -function keysDecrypt({ - authenticate, - keyRing, - logger, -}: { - authenticate: Authenticate; - keyRing: KeyRing; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new keysPB.Crypto(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const data = keyRing.decrypt( - Buffer.from(call.request.getData(), 'binary'), - ); - if (data == null) never(); - response.setData(data.toString('binary')); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysDecrypt.name}:${e}`); - return; - } - }; -} - -export default keysDecrypt; diff --git a/src/client/service/keysEncrypt.ts b/src/client/service/keysEncrypt.ts deleted file mode 100644 index 88d96d2d8..000000000 --- a/src/client/service/keysEncrypt.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type Logger from '@matrixai/logger'; -import type { PublicKey, JWK } from '../../keys/types'; -import * as grpcUtils from '../../grpc/utils'; -import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import * as clientUtils from '../utils'; -import * as keysUtils from '../../keys/utils/index'; -import { never } from '../../utils/index'; -import * as keysErrors from '../../keys/errors'; - -function keysEncrypt({ - authenticate, - keyRing, - logger, -}: { - authenticate: Authenticate; - keyRing: KeyRing; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new keysPB.Crypto(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - let publicKey: PublicKey | undefined; - try { - const jwk = JSON.parse(call.request.getPublicKeyJwk()) as JWK; - publicKey = keysUtils.publicKeyFromJWK(jwk); - if (publicKey == null) never(); - } catch (e) { - throw new keysErrors.ErrorPublicKeyParse(undefined, { cause: e }); - } - const data = keyRing.encrypt( - publicKey, - Buffer.from(call.request.getData(), 'binary'), - ); - response.setData(data.toString('binary')); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysEncrypt.name}:${e}`); - return; - } - }; -} - -export default keysEncrypt; diff --git a/src/client/service/keysKeyPair.ts b/src/client/service/keysKeyPair.ts deleted file mode 100644 index 11d398e1b..000000000 --- a/src/client/service/keysKeyPair.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type * as sessionsPB from '../../proto/js/polykey/v1/sessions/sessions_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import * as clientUtils from '../utils'; -import * as keysUtils from '../../keys/utils'; - -function keysKeyPair({ - authenticate, - keyRing, - logger, -}: { - authenticate: Authenticate; - keyRing: KeyRing; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new keysPB.KeyPairJWK(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const privateJWK = keysUtils.privateKeyToJWK(keyRing.keyPair.privateKey); - const privateJWE = keysUtils.wrapWithPassword( - call.request.getPassword(), - privateJWK, - ); - const publicJWK = keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey); - response.setPrivateKeyJwe(JSON.stringify(privateJWE)); - response.setPublicKeyJwk(JSON.stringify(publicJWK)); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysKeyPair.name}:${e}`); - return; - } - }; -} - -export default keysKeyPair; diff --git a/src/client/service/keysKeyPairRenew.ts b/src/client/service/keysKeyPairRenew.ts deleted file mode 100644 index 92b634009..000000000 --- a/src/client/service/keysKeyPairRenew.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type CertManager from '../../keys/CertManager'; -import type * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function keysKeyPairRenew({ - authenticate, - certManager, - logger, -}: { - authenticate: Authenticate; - certManager: CertManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - // Other domains will be updated accordingly via the `EventBus` so we - // only need to modify the KeyManager - await certManager.renewCertWithNewKeyPair(call.request.getName()); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysKeyPairRenew.name}:${e}`); - return; - } - }; -} - -export default keysKeyPairRenew; diff --git a/src/client/service/keysKeyPairReset.ts b/src/client/service/keysKeyPairReset.ts deleted file mode 100644 index 66799f1dd..000000000 --- a/src/client/service/keysKeyPairReset.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type CertManager from '../../keys/CertManager'; -import type * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function keysKeyPairReset({ - authenticate, - certManager, - logger, -}: { - authenticate: Authenticate; - certManager: CertManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - // Other domains will be updated accordingly via the `EventBus` so we - // only need to modify the KeyManager - await certManager.resetCertWithNewKeyPair(call.request.getName()); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysKeyPairReset.name}:${e}`); - return; - } - }; -} - -export default keysKeyPairReset; diff --git a/src/client/service/keysPasswordChange.ts b/src/client/service/keysPasswordChange.ts deleted file mode 100644 index ad32c647b..000000000 --- a/src/client/service/keysPasswordChange.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type * as sessionsPB from '../../proto/js/polykey/v1/sessions/sessions_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function keysPasswordChange({ - authenticate, - keyRing, - logger, -}: { - authenticate: Authenticate; - keyRing: KeyRing; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await keyRing.changePassword(call.request.getPassword()); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysPasswordChange.name}:${e}`); - return; - } - }; -} - -export default keysPasswordChange; diff --git a/src/client/service/keysPublicKey.ts b/src/client/service/keysPublicKey.ts deleted file mode 100644 index e10644c06..000000000 --- a/src/client/service/keysPublicKey.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import * as clientUtils from '../utils'; -import * as keysUtils from '../../keys/utils'; - -function keysPrivateKey({ - authenticate, - keyRing, - logger, -}: { - authenticate: Authenticate; - keyRing: KeyRing; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new keysPB.KeyPairJWK(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const publicJWK = keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey); - response.setPublicKeyJwk(JSON.stringify(publicJWK)); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysPrivateKey.name}:${e}`); - return; - } - }; -} - -export default keysPrivateKey; diff --git a/src/client/service/keysSign.ts b/src/client/service/keysSign.ts deleted file mode 100644 index 50f9f45b4..000000000 --- a/src/client/service/keysSign.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import * as clientUtils from '../utils'; - -function keysSign({ - authenticate, - keyRing, - logger, -}: { - authenticate: Authenticate; - keyRing: KeyRing; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new keysPB.Crypto(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const signature = keyRing.sign( - Buffer.from(call.request.getData(), 'binary'), - ); - response.setData(call.request.getData()); - response.setSignature(signature.toString('binary')); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysSign.name}:${e}`); - return; - } - }; -} - -export default keysSign; diff --git a/src/client/service/keysVerify.ts b/src/client/service/keysVerify.ts deleted file mode 100644 index 1aa78ea6d..000000000 --- a/src/client/service/keysVerify.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type * as keysPB from '../../proto/js/polykey/v1/keys/keys_pb'; -import type Logger from '@matrixai/logger'; -import type { Signature, JWK, PublicKey } from '../../keys/types'; -import * as grpcUtils from '../../grpc/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; -import * as keysUtils from '../../keys/utils'; -import * as keysErrors from '../../keys/errors'; -import { never } from '../../utils/index'; - -function keysVerify({ - authenticate, - keyRing, - logger, -}: { - authenticate: Authenticate; - keyRing: KeyRing; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - let publicKey: PublicKey | undefined; - try { - const jwk = JSON.parse(call.request.getPublicKeyJwk()) as JWK; - publicKey = keysUtils.publicKeyFromJWK(jwk); - if (publicKey == null) never(); - } catch (e) { - throw new keysErrors.ErrorPublicKeyParse(undefined, { cause: e }); - } - const status = keyRing.verify( - publicKey, - Buffer.from(call.request.getData(), 'binary'), - Buffer.from(call.request.getSignature(), 'binary') as Signature, - ); - response.setSuccess(status); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${keysVerify.name}:${e}`); - return; - } - }; -} - -export default keysVerify; diff --git a/src/client/service/nodesAdd.ts b/src/client/service/nodesAdd.ts deleted file mode 100644 index 90ecebb10..000000000 --- a/src/client/service/nodesAdd.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type NodeManager from '../../nodes/NodeManager'; -import type { NodeId, NodeAddress } from '../../nodes/types'; -import type { Host, Hostname, Port } from '../../network/types'; -import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import type Logger from '@matrixai/logger'; -import * as nodeErrors from '../../nodes/errors'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -/** - * Adds a node ID -> node address mapping into the buckets' database. - * This is an unrestricted add: no validity checks are made for the correctness - * of the passed ID or host/port. - */ -function nodesAdd({ - authenticate, - nodeManager, - db, - logger, -}: { - authenticate: Authenticate; - nodeManager: NodeManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - const request = call.request; - call.sendMetadata(metadata); - const { - nodeId, - host, - port, - }: { - nodeId: NodeId; - host: Host | Hostname; - port: Port; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - [['host'], () => validationUtils.parseHostOrHostname(value)], - [['port'], () => validationUtils.parsePort(value)], - () => value, - ); - }, - { - nodeId: request.getNodeId(), - host: request.getAddress()?.getHost(), - port: request.getAddress()?.getPort(), - }, - ); - // Pinging to authenticate the node - if ( - request.getPing() && - !(await nodeManager.pingNode(nodeId, { host, port })) - ) { - throw new nodeErrors.ErrorNodePingFailed( - 'Failed to authenticate target node', - ); - } - - await db.withTransactionF((tran) => - nodeManager.setNode( - nodeId, - { - host, - port, - } as NodeAddress, - true, - request.getForce(), - 1500, - undefined, - tran, - ), - ); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${nodesAdd.name}:${e}`); - return; - } - }; -} - -export default nodesAdd; diff --git a/src/client/service/nodesClaim.ts b/src/client/service/nodesClaim.ts deleted file mode 100644 index e69c90f7b..000000000 --- a/src/client/service/nodesClaim.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type NodeManager from '../../nodes/NodeManager'; -import type { NodeId } from '../../ids/types'; -import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as nodesErrors from '../../nodes/errors'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -/** - * Checks whether there is an existing Gestalt Invitation from the other node. - * If not, send an invitation, if so, create a cryptolink claim between the - * other node and host node. - */ -function nodesClaim({ - authenticate, - nodeManager, - db, - logger, -}: { - authenticate: Authenticate; - nodeManager: NodeManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNodeId(), - }, - ); - await db.withTransactionF(async (tran) => { - // Attempt to claim the node, - // if there is no permission then we get an error - await nodeManager.claimNode(nodeId, tran); - response.setSuccess(true); - }); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(`${nodesClaim.name}:${e}`); - return; - } - }; -} - -export default nodesClaim; diff --git a/src/client/service/nodesFind.ts b/src/client/service/nodesFind.ts deleted file mode 100644 index a4e81ba25..000000000 --- a/src/client/service/nodesFind.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; -import type { NodeId } from '../../ids/types'; -import type Logger from '@matrixai/logger'; -import * as nodesUtils from '../../nodes/utils'; -import * as nodesErrors from '../../nodes/errors'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import { matchSync } from '../../utils'; -import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import * as clientUtils from '../utils'; - -/** - * Attempts to get the node address of a provided node ID (by contacting - * keynodes in the wider Polykey network). - * @throws ErrorNodeGraphNodeNotFound if node address cannot be found - */ -function nodesFind({ - authenticate, - nodeConnectionManager, - logger, -}: { - authenticate: Authenticate; - nodeConnectionManager: NodeConnectionManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new nodesPB.NodeAddress(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNodeId(), - }, - ); - const address = await nodeConnectionManager.findNode(nodeId); - if (address == null) throw new nodesErrors.ErrorNodeGraphNodeIdNotFound(); - response - .setNodeId(nodesUtils.encodeNodeId(nodeId)) - .setAddress( - new nodesPB.Address().setHost(address.host).setPort(address.port), - ); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(`${nodesFind.name}:${e}`); - return; - } - }; -} - -export default nodesFind; diff --git a/src/client/service/nodesGetAll.ts b/src/client/service/nodesGetAll.ts deleted file mode 100644 index b2eb3f36e..000000000 --- a/src/client/service/nodesGetAll.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type Logger from '@matrixai/logger'; -import type { Authenticate } from '../types'; -import type KeyRing from '../../keys/KeyRing'; -import type { NodeId } from '../../ids/types'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type NodeGraph from '../../nodes/NodeGraph'; -import { IdInternal } from '@matrixai/id'; -import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import * as nodesUtils from '../../nodes/utils'; -import * as nodesErrors from '../../nodes/errors'; -import * as grpcUtils from '../../grpc/utils'; -import * as clientUtils from '../utils'; - -/** - * Retrieves all nodes from all buckets in the NodeGraph. - */ -function nodesGetAll({ - authenticate, - nodeGraph, - keyRing, - logger, -}: { - authenticate: Authenticate; - nodeGraph: NodeGraph; - keyRing: KeyRing; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new nodesPB.NodeBuckets(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const buckets = nodeGraph.getBuckets(); - for await (const b of buckets) { - let index; - for (const id of Object.keys(b)) { - const encodedId = nodesUtils.encodeNodeId( - IdInternal.fromString(id), - ); - const address = new nodesPB.Address() - .setHost(b[id].address.host) - .setPort(b[id].address.port); - // For every node in every bucket, add it to our message - if (!index) { - index = nodesUtils.bucketIndex( - keyRing.getNodeId(), - IdInternal.fromString(id), - ); - } - // Need to either add node to an existing bucket, or create a new - // bucket (if it doesn't exist) - const bucket = response.getBucketsMap().get(index); - if (bucket) { - bucket.getNodeTableMap().set(encodedId, address); - } else { - const newBucket = new nodesPB.NodeTable(); - newBucket.getNodeTableMap().set(encodedId, address); - response.getBucketsMap().set(index, newBucket); - } - } - } - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(`${nodesGetAll.name}:${e}`); - return; - } - }; -} - -export default nodesGetAll; diff --git a/src/client/service/nodesListConnections.ts b/src/client/service/nodesListConnections.ts deleted file mode 100644 index aed6023bb..000000000 --- a/src/client/service/nodesListConnections.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import type NodeConnectionManager from '../../nodes/NodeConnectionManager'; -import * as grpcUtils from '../../grpc/utils'; -import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import * as clientUtils from '../utils'; -import * as nodesUtils from '../../nodes/utils'; - -function nodesListConnections({ - authenticate, - nodeConnectionManager, - logger, -}: { - authenticate: Authenticate; - nodeConnectionManager: NodeConnectionManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream< - utilsPB.EmptyMessage, - nodesPB.NodeConnection - >, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const connections = nodeConnectionManager.listConnections(); - for (const connection of connections) { - const connectionMessage = new nodesPB.NodeConnection(); - connectionMessage.setNodeId(nodesUtils.encodeNodeId(connection.nodeId)); - connectionMessage.setHost(connection.address.host); - connectionMessage.setHostname(connection.address.hostname ?? ''); - connectionMessage.setPort(connection.address.port); - connectionMessage.setUsageCount(connection.usageCount); - connectionMessage.setTimeout(connection.timeout ?? -1); - await genWritable.next(connectionMessage); - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e) && - logger.error(`${nodesListConnections.name}:${e}`); - return; - } - }; -} - -export default nodesListConnections; diff --git a/src/client/service/nodesPing.ts b/src/client/service/nodesPing.ts deleted file mode 100644 index 36ae3f6f8..000000000 --- a/src/client/service/nodesPing.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type NodeManager from '../../nodes/NodeManager'; -import type { NodeId } from '../../ids/types'; -import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as nodesErrors from '../../nodes/errors'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -/** - * Checks if a remote node is online. - */ -function nodesPing({ - authenticate, - nodeManager, - logger, -}: { - authenticate: Authenticate; - nodeManager: NodeManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNodeId(), - }, - ); - const status = await nodeManager.pingNode(nodeId); - response.setSuccess(status); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(`${nodesPing.name}:${e}`); - return; - } - }; -} - -export default nodesPing; diff --git a/src/client/service/notificationsClear.ts b/src/client/service/notificationsClear.ts deleted file mode 100644 index e26b24cb4..000000000 --- a/src/client/service/notificationsClear.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type NotificationsManager from '../../notifications/NotificationsManager'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function notificationsClear({ - authenticate, - notificationsManager, - db, - logger, -}: { - authenticate: Authenticate; - notificationsManager: NotificationsManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF((tran) => - notificationsManager.clearNotifications(tran), - ); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${notificationsClear.name}:${e}`); - return; - } - }; -} - -export default notificationsClear; diff --git a/src/client/service/notificationsRead.ts b/src/client/service/notificationsRead.ts deleted file mode 100644 index b80425f1a..000000000 --- a/src/client/service/notificationsRead.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type NotificationsManager from '../../notifications/NotificationsManager'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as notificationsPB from '../../proto/js/polykey/v1/notifications/notifications_pb'; -import * as clientUtils from '../utils'; - -function notificationsRead({ - authenticate, - notificationsManager, - db, - logger, -}: { - authenticate: Authenticate; - notificationsManager: NotificationsManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new notificationsPB.List(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const unread = call.request.getUnread(); - const order = call.request.getOrder() as 'newest' | 'oldest'; - const numberField = call.request.getNumber(); - let number: number | 'all'; - if (numberField === 'all') { - number = numberField; - } else { - number = parseInt(numberField); - } - const notifications = await db.withTransactionF((tran) => - notificationsManager.readNotifications({ - unread, - number, - order, - tran, - }), - ); - const notifMessages: Array = []; - for (const notif of notifications) { - const notificationsMessage = new notificationsPB.AgentNotification(); - notificationsMessage.setContent(JSON.stringify(notif)); - notifMessages.push(notificationsMessage); - } - response.setNotificationList(notifMessages); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e) && - logger.error(`${notificationsRead.name}:${e}`); - return; - } - }; -} - -export default notificationsRead; diff --git a/src/client/service/notificationsSend.ts b/src/client/service/notificationsSend.ts deleted file mode 100644 index 524cac6b1..000000000 --- a/src/client/service/notificationsSend.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type NotificationsManager from '../../notifications/NotificationsManager'; -import type { NodeId } from '../../ids/types'; -import type * as notificationsPB from '../../proto/js/polykey/v1/notifications/notifications_pb'; -import type Logger from '@matrixai/logger'; -import type { General } from '../../notifications/types'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as nodesErrors from '../../nodes/errors'; -import { matchSync } from '../../utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function notificationsSend({ - authenticate, - notificationsManager, - logger, -}: { - authenticate: Authenticate; - notificationsManager: NotificationsManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.EmptyMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getReceiverId(), - }, - ); - const data: General = { - type: 'General', - message: call.request.getData()!.getMessage(), - }; - await notificationsManager.sendNotification(nodeId, data); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(`${notificationsSend.name}:${e}`); - return; - } - }; -} - -export default notificationsSend; diff --git a/src/client/service/vaultsClone.ts b/src/client/service/vaultsClone.ts deleted file mode 100644 index 81d5e7853..000000000 --- a/src/client/service/vaultsClone.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type VaultManager from '../../vaults/VaultManager'; -import type { NodeId } from '../../ids/types'; -import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as grpcUtils from '../../grpc/utils'; -import * as grpcErrors from '../../grpc/errors'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as nodesErrors from '../../nodes/errors'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import { matchSync } from '../../utils'; -import * as clientUtils from '../utils'; - -function vaultsClone({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - // Node id - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNode()?.getNodeId(), - }, - ); - // Vault id - let vaultId; - const vaultNameOrId = call.request.getVault()?.getNameOrId(); - vaultId = vaultsUtils.decodeVaultId(vaultNameOrId); - vaultId = vaultId ?? vaultNameOrId; - await db.withTransactionF(async (tran) => { - await vaultManager.cloneVault(nodeId, vaultId, tran); - }); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - nodesErrors.ErrorNodeGraphNodeIdNotFound, - vaultsErrors.ErrorVaultsNameConflict, - [grpcErrors.ErrorPolykeyRemote, vaultsErrors.ErrorVaultsVaultUndefined], - ]) && logger.error(`${vaultsClone.name}:${e}`); - return; - } - }; -} - -export default vaultsClone; diff --git a/src/client/service/vaultsCreate.ts b/src/client/service/vaultsCreate.ts deleted file mode 100644 index 26617a665..000000000 --- a/src/client/service/vaultsCreate.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Authenticate } from '../types'; -import type { VaultId, VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as clientUtils from '../utils'; - -function vaultsCreate({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - const response = new vaultsPB.Vault(); - let vaultId: VaultId; - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - vaultId = await db.withTransactionF((tran) => - vaultManager.createVault(call.request.getNameOrId() as VaultName, tran), - ); - response.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultDefined, - ]) && logger.error(`${vaultsCreate.name}:${e}`); - return; - } - }; -} - -export default vaultsCreate; diff --git a/src/client/service/vaultsDelete.ts b/src/client/service/vaultsDelete.ts deleted file mode 100644 index 8e04bf0ab..000000000 --- a/src/client/service/vaultsDelete.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as clientUtils from '../utils'; - -function vaultsDelete({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - await vaultManager.destroyVault(vaultId, tran); - }); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - ]) && logger.error(`${vaultsDelete.name}:${e}`); - return; - } - }; -} - -export default vaultsDelete; diff --git a/src/client/service/vaultsList.ts b/src/client/service/vaultsList.ts deleted file mode 100644 index 3fbbdadd5..000000000 --- a/src/client/service/vaultsList.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Authenticate } from '../types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as clientUtils from '../utils'; - -function vaultsList({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const vaults = await db.withTransactionF((tran) => - vaultManager.listVaults(tran), - ); - for await (const [vaultName, vaultId] of vaults) { - const vaultListMessage = new vaultsPB.List(); - vaultListMessage.setVaultName(vaultName); - vaultListMessage.setVaultId(vaultsUtils.encodeVaultId(vaultId)); - await genWritable.next(((_) => vaultListMessage)()); - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e) && - logger.error(`${vaultsList.name}:${e}`); - return; - } - }; -} - -export default vaultsList; diff --git a/src/client/service/vaultsLog.ts b/src/client/service/vaultsLog.ts deleted file mode 100644 index c028006dc..000000000 --- a/src/client/service/vaultsLog.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as clientUtils from '../utils'; - -function vaultsLog({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const log = await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - // Getting the log - const depth = call.request.getLogDepth(); - let commitId: string | undefined = call.request.getCommitId(); - commitId = commitId ? commitId : undefined; - return await vaultManager.withVaults( - [vaultId], - async (vault) => { - return await vault.log(commitId, depth); - }, - tran, - ); - }); - const vaultsLogEntryMessage = new vaultsPB.LogEntry(); - for (const entry of log) { - vaultsLogEntryMessage.setOid(entry.commitId); - vaultsLogEntryMessage.setCommitter(entry.committer.name); - const timestampMessage = new Timestamp(); - timestampMessage.fromDate(entry.committer.timestamp); - vaultsLogEntryMessage.setTimeStamp(timestampMessage); - vaultsLogEntryMessage.setMessage(entry.message); - await genWritable.next(vaultsLogEntryMessage); - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorVaultReferenceInvalid, - ]) && logger.error(`${vaultsLog.name}:${e}`); - return; - } - }; -} - -export default vaultsLog; diff --git a/src/client/service/vaultsPermissionGet.ts b/src/client/service/vaultsPermissionGet.ts deleted file mode 100644 index e89d9b500..000000000 --- a/src/client/service/vaultsPermissionGet.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type VaultManager from '../../vaults/VaultManager'; -import type { VaultName, VaultActions } from '../../vaults/types'; -import type ACL from '../../acl/ACL'; -import type { NodeId, NodeIdEncoded } from 'nodes/types'; -import type Logger from '@matrixai/logger'; -import { IdInternal } from '@matrixai/id'; -import * as grpcUtils from '../../grpc/utils'; -import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as nodesUtils from '../../nodes/utils'; -import * as clientUtils from '../utils'; - -function vaultsPermissionGet({ - authenticate, - vaultManager, - acl, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - acl: ACL; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - // Getting vaultId - const [rawPermissions, vaultId] = await db.withTransactionF( - async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - // Getting permissions - return [await acl.getVaultPerm(vaultId, tran), vaultId]; - }, - ); - const permissionList: Record = {}; - // Getting the relevant information - for (const nodeId in rawPermissions) { - permissionList[nodeId] = rawPermissions[nodeId].vaults[vaultId]; - } - const vaultPermissionsMessage = new vaultsPB.Permissions(); - vaultPermissionsMessage.setVault(call.request); - const nodeMessage = new nodesPB.Node(); - // Constructing the message - for (const nodeIdString in permissionList) { - const nodeId = IdInternal.fromString(nodeIdString); - nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); - vaultPermissionsMessage.setNode(nodeMessage); - const actions = Object.keys(permissionList[nodeIdString]); - vaultPermissionsMessage.setVaultPermissionsList(actions); - await genWritable.next(vaultPermissionsMessage); - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - ]) && logger.error(`${vaultsPermissionGet.name}:${e}`); - return; - } - }; -} - -export default vaultsPermissionGet; diff --git a/src/client/service/vaultsPermissionSet.ts b/src/client/service/vaultsPermissionSet.ts deleted file mode 100644 index e5d7eab2c..000000000 --- a/src/client/service/vaultsPermissionSet.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { NodeId } from '../../ids/types'; -import type { VaultName, VaultAction, VaultActions } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type ACL from '../../acl/ACL'; -import type NotificationsManager from '../../notifications/NotificationsManager'; -import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as aclErrors from '../../acl/errors'; -import * as nodesErrors from '../../nodes/errors'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import { matchSync } from '../../utils'; -import * as clientUtils from '../utils'; - -function vaultsPermissionSet({ - authenticate, - vaultManager, - gestaltGraph, - acl, - notificationsManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - gestaltGraph: GestaltGraph; - acl: ACL; - notificationsManager: NotificationsManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - // Checking session token - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const { - nodeId, - actions, - }: { - nodeId: NodeId; - actions: Array; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - [['actions'], () => value.map(validationUtils.parseVaultAction)], - () => value, - ); - }, - { - nodeId: call.request.getNode()?.getNodeId(), - actions: call.request.getVaultPermissionsList(), - }, - ); - // Checking if vault exists - const vaultMeta = await vaultManager.getVaultMeta(vaultId, tran); - if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); - // Setting permissions - const actionsSet: VaultActions = {}; - await gestaltGraph.setGestaltAction(['node', nodeId], 'scan', tran); - for (const action of actions) { - await acl.setVaultAction(vaultId, nodeId, action, tran); - actionsSet[action] = null; - } - // Sending notification - await notificationsManager.sendNotification(nodeId, { - type: 'VaultShare', - vaultId: vaultsUtils.encodeVaultId(vaultId), - vaultName: vaultMeta.vaultName, - actions: actionsSet, - }); - }); - // Formatting response - const response = new utilsPB.StatusMessage().setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - aclErrors.ErrorACLNodeIdMissing, - nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(`${vaultsPermissionSet.name}:${e}`); - return; - } - }; -} - -export default vaultsPermissionSet; diff --git a/src/client/service/vaultsPermissionUnset.ts b/src/client/service/vaultsPermissionUnset.ts deleted file mode 100644 index ff55c206d..000000000 --- a/src/client/service/vaultsPermissionUnset.ts +++ /dev/null @@ -1,115 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { NodeId } from '../../ids/types'; -import type { VaultName, VaultAction } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type GestaltGraph from '../../gestalts/GestaltGraph'; -import type ACL from '../../acl/ACL'; -import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as gestaltsErrors from '../../gestalts/errors'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import { matchSync } from '../../utils'; -import * as clientUtils from '../utils'; - -function vaultsPermissionUnset({ - authenticate, - vaultManager, - gestaltGraph, - acl, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - gestaltGraph: GestaltGraph; - acl: ACL; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - // Checking session token - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const { - nodeId, - actions, - }: { - nodeId: NodeId; - actions: Array; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - [['actions'], () => value.map(validationUtils.parseVaultAction)], - () => value, - ); - }, - { - nodeId: call.request.getNode()?.getNodeId(), - actions: call.request.getVaultPermissionsList(), - }, - ); - // Checking if vault exists - const vaultMeta = await vaultManager.getVaultMeta(vaultId, tran); - if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); - // Unsetting permissions - await gestaltGraph.setGestaltAction(['node', nodeId], 'scan', tran); - for (const action of actions) { - await acl.unsetVaultAction(vaultId, nodeId, action, tran); - } - // We need to check if there are still shared vaults - const nodePermissions = await acl.getNodePerm(nodeId, tran); - // Remove scan permissions if no more shared vaults - if (nodePermissions != null) { - // Counting total number of permissions - const totalPermissions = Object.keys(nodePermissions.vaults) - .map((key) => Object.keys(nodePermissions.vaults[key]).length) - .reduce((prev, current) => current + prev); - // If no permissions are left then we remove the scan permission - if (totalPermissions === 0) { - await gestaltGraph.unsetGestaltAction( - ['node', nodeId], - 'scan', - tran, - ); - } - } - }); - // Formatting response - const response = new utilsPB.StatusMessage().setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - gestaltsErrors.ErrorGestaltsGraphNodeIdMissing, - ]) && logger.error(`${vaultsPermissionUnset.name}:${e}`); - return; - } - }; -} - -export default vaultsPermissionUnset; diff --git a/src/client/service/vaultsPull.ts b/src/client/service/vaultsPull.ts deleted file mode 100644 index 488ba34a6..000000000 --- a/src/client/service/vaultsPull.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type VaultManager from '../../vaults/VaultManager'; -import type { VaultName } from '../../vaults/types'; -import type { NodeId } from '../../ids/types'; -import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as grpcUtils from '../../grpc/utils'; -import * as grpcErrors from '../../grpc/errors'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as nodesErrors from '../../nodes/errors'; -import { matchSync } from '../../utils'; -import * as clientUtils from '../utils'; - -function vaultsPull({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - let pullVaultId; - const pullVaultMessage = call.request.getPullVault(); - if (pullVaultMessage == null) { - pullVaultId = null; - } else { - pullVaultId = vaultsUtils.decodeVaultId(pullVaultMessage.getNameOrId()); - pullVaultId = pullVaultId ?? pullVaultMessage.getNameOrId(); - if (pullVaultId == null) pullVaultId = pullVaultMessage.getNameOrId(); - } - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const { - nodeId, - }: { - nodeId: NodeId | undefined; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [ - ['nodeId'], - () => (value ? validationUtils.parseNodeId(value) : undefined), - ], - () => value, - ); - }, - { - nodeId: call.request.getNode()?.getNodeId(), - }, - ); - await vaultManager.pullVault({ - vaultId, - pullNodeId: nodeId, - pullVaultNameOrId: pullVaultId, - tran, - }); - }); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - nodesErrors.ErrorNodeGraphNodeIdNotFound, - [grpcErrors.ErrorPolykeyRemote, vaultsErrors.ErrorVaultsVaultUndefined], - ]) && logger.error(`${vaultsPull.name}:${e}`); - return; - } - }; -} - -export default vaultsPull; diff --git a/src/client/service/vaultsRename.ts b/src/client/service/vaultsRename.ts deleted file mode 100644 index 21bcd7626..000000000 --- a/src/client/service/vaultsRename.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as clientUtils from '../utils'; - -function vaultsRename({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new vaultsPB.Vault(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const newName = call.request.getNewName() as VaultName; - await vaultManager.renameVault(vaultId, newName, tran); - response.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - }); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorVaultsVaultDefined, - ]) && logger.error(`${vaultsRename.name}:${e}`); - return; - } - }; -} - -export default vaultsRename; diff --git a/src/client/service/vaultsScan.ts b/src/client/service/vaultsScan.ts deleted file mode 100644 index c72c22720..000000000 --- a/src/client/service/vaultsScan.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { Authenticate } from '../types'; -import type { NodeId } from '../../ids/types'; -import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import type * as grpc from '@grpc/grpc-js'; -import type VaultManager from '../../vaults/VaultManager'; -import type Logger from '@matrixai/logger'; -import * as grpcUtils from '../../grpc/utils'; -import { validateSync } from '../../validation'; -import * as validationUtils from '../../validation/utils'; -import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as nodesErrors from '../../nodes/errors'; -import { matchSync } from '../../utils'; -import * as clientUtils from '../utils'; - -function vaultsScan({ - authenticate, - vaultManager, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - nodeId, - }: { - nodeId: NodeId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['nodeId'], () => validationUtils.parseNodeId(value)], - () => value, - ); - }, - { - nodeId: call.request.getNodeId(), - }, - ); - const vaultListMessage = new vaultsPB.List(); - for await (const { - vaultIdEncoded, - vaultName, - vaultPermissions, - } of vaultManager.scanVaults(nodeId)) { - vaultListMessage.setVaultName(vaultName); - vaultListMessage.setVaultId(vaultIdEncoded); - vaultListMessage.setVaultPermissionsList(vaultPermissions); - await genWritable.next(vaultListMessage); - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e, [ - nodesErrors.ErrorNodeGraphNodeIdNotFound, - ]) && logger.error(`${vaultsScan.name}:${e}`); - return; - } - }; -} - -export default vaultsScan; diff --git a/src/client/service/vaultsSecretsDelete.ts b/src/client/service/vaultsSecretsDelete.ts deleted file mode 100644 index c35ba8ce8..000000000 --- a/src/client/service/vaultsSecretsDelete.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultOps from '../../vaults/VaultOps'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function vaultsSecretsDelete({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const secretName = call.request.getSecretName(); - await vaultManager.withVaults( - [vaultId], - async (vault) => { - await vaultOps.deleteSecret(vault, secretName); - }, - tran, - ); - }); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(`${vaultsSecretsDelete.name}:${e}`); - return; - } - }; -} - -export default vaultsSecretsDelete; diff --git a/src/client/service/vaultsSecretsEdit.ts b/src/client/service/vaultsSecretsEdit.ts deleted file mode 100644 index d114e56d2..000000000 --- a/src/client/service/vaultsSecretsEdit.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultOps from '../../vaults/VaultOps'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function vaultsSecretsEdit({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const secretName = call.request.getSecretName(); - const secretContent = Buffer.from(call.request.getSecretContent()); - await vaultManager.withVaults( - [vaultId], - async (vault) => { - await vaultOps.updateSecret(vault, secretName, secretContent); - }, - tran, - ); - }); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorSecretsSecretUndefined, - vaultsErrors.ErrorVaultRemoteDefined, - ]) && logger.error(`${vaultsSecretsEdit.name}:${e}`); - return; - } - }; -} - -export default vaultsSecretsEdit; diff --git a/src/client/service/vaultsSecretsGet.ts b/src/client/service/vaultsSecretsGet.ts deleted file mode 100644 index 61eafbf16..000000000 --- a/src/client/service/vaultsSecretsGet.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultOps from '../../vaults/VaultOps'; -import * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import * as clientUtils from '../utils'; - -function vaultsSecretsGet({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new secretsPB.Secret(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const secretName = call.request.getSecretName(); - const secretContent = await vaultManager.withVaults( - [vaultId], - async (vault) => { - return await vaultOps.getSecret(vault, secretName); - }, - tran, - ); - response.setSecretContent(secretContent); - }); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(`${vaultsSecretsGet.name}:${e}`); - return; - } - }; -} - -export default vaultsSecretsGet; diff --git a/src/client/service/vaultsSecretsList.ts b/src/client/service/vaultsSecretsList.ts deleted file mode 100644 index c55aa59d0..000000000 --- a/src/client/service/vaultsSecretsList.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as grpc from '@grpc/grpc-js'; -import type { DB } from '@matrixai/db'; -import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import type Logger from '@matrixai/logger'; -import * as vaultsUtils from '../../vaults/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultOps from '../../vaults/VaultOps'; -import * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import * as clientUtils from '../utils'; - -function vaultsSecretsList({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerWritableStream, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call, false); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const secrets = await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - return await vaultManager.withVaults( - [vaultId], - async (vault) => { - return await vaultOps.listSecrets(vault); - }, - tran, - ); - }); - let secretMessage: secretsPB.Secret; - for (const secret of secrets) { - secretMessage = new secretsPB.Secret(); - secretMessage.setSecretName(secret); - await genWritable.next(secretMessage); - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - ]) && logger.error(`${vaultsSecretsList.name}:${e}`); - return; - } - }; -} - -export default vaultsSecretsList; diff --git a/src/client/service/vaultsSecretsMkdir.ts b/src/client/service/vaultsSecretsMkdir.ts deleted file mode 100644 index a9a545704..000000000 --- a/src/client/service/vaultsSecretsMkdir.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultOps from '../../vaults/VaultOps'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function vaultsSecretsMkdir({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - await vaultManager.withVaults( - [vaultId], - async (vault) => { - await vaultOps.mkdir(vault, call.request.getDirName(), { - recursive: call.request.getRecursive(), - }); - }, - tran, - ); - }); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorVaultsRecursive, - ]) && logger.error(`${vaultsSecretsMkdir.name}:${e}`); - return; - } - }; -} - -export default vaultsSecretsMkdir; diff --git a/src/client/service/vaultsSecretsNew.ts b/src/client/service/vaultsSecretsNew.ts deleted file mode 100644 index 2414e6bf6..000000000 --- a/src/client/service/vaultsSecretsNew.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultOps from '../../vaults/VaultOps'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function vaultsSecretsNew({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const secret = call.request.getSecretName(); - const content = Buffer.from(call.request.getSecretContent()); - await vaultManager.withVaults( - [vaultId], - async (vault) => { - await vaultOps.addSecret(vault, secret, content); - }, - tran, - ); - }); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(`${vaultsSecretsNew.name}:${e}`); - return; - } - }; -} - -export default vaultsSecretsNew; diff --git a/src/client/service/vaultsSecretsNewDir.ts b/src/client/service/vaultsSecretsNewDir.ts deleted file mode 100644 index 3ed67ce68..000000000 --- a/src/client/service/vaultsSecretsNewDir.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type { FileSystem } from '../../types'; -import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultOps from '../../vaults/VaultOps'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function vaultsSecretsNewDir({ - authenticate, - vaultManager, - fs, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - fs: FileSystem; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const secretsPath = call.request.getSecretDirectory(); - await vaultManager.withVaults( - [vaultId], - async (vault) => { - await vaultOps.addSecretDirectory(vault, secretsPath, fs); - }, - tran, - ); - }); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - ]) && logger.error(`${vaultsSecretsNewDir.name}:${e}`); - return; - } - }; -} - -export default vaultsSecretsNewDir; diff --git a/src/client/service/vaultsSecretsRename.ts b/src/client/service/vaultsSecretsRename.ts deleted file mode 100644 index d65a87f02..000000000 --- a/src/client/service/vaultsSecretsRename.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultOps from '../../vaults/VaultOps'; -import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '../utils'; - -function vaultsSecretsRename({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new utilsPB.StatusMessage(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getOldSecret()?.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId( - call.request.getOldSecret()?.getVault()?.getNameOrId(), - ); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const oldSecret = call.request.getOldSecret()?.getSecretName(); - if (oldSecret == null) { - throw new vaultsErrors.ErrorSecretsSecretUndefined(); - } - const newSecret = call.request.getNewName(); - await vaultManager.withVaults( - [vaultId], - async (vault) => { - await vaultOps.renameSecret(vault, oldSecret, newSecret); - }, - tran, - ); - }); - response.setSuccess(true); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(`${vaultsSecretsRename.name}:${e}`); - return; - } - }; -} - -export default vaultsSecretsRename; diff --git a/src/client/service/vaultsSecretsStat.ts b/src/client/service/vaultsSecretsStat.ts deleted file mode 100644 index 9d4477443..000000000 --- a/src/client/service/vaultsSecretsStat.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type VaultManager from '../../vaults/VaultManager'; -import type { VaultName } from '../../vaults/types'; -import type { Authenticate } from '../types'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsUtils from '../../vaults/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultOps from '../../vaults/VaultOps'; -import * as secretsPB from '../../proto/js/polykey/v1/secrets/secrets_pb'; -import * as clientUtils from '../utils'; - -function vaultsSecretsStat({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new secretsPB.Stat(); - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - const secretName = call.request.getSecretName(); - const stat = await vaultManager.withVaults( - [vaultId], - async (vault) => { - return await vaultOps.statSecret(vault, secretName); - }, - tran, - ); - response.setJson(JSON.stringify(stat)); - }); - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorSecretsSecretUndefined, - ]) && logger.error(`${vaultsSecretsStat.name}:${e}`); - return; - } - }; -} - -export default vaultsSecretsStat; diff --git a/src/client/service/vaultsVersion.ts b/src/client/service/vaultsVersion.ts deleted file mode 100644 index df1dc3300..000000000 --- a/src/client/service/vaultsVersion.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Authenticate } from '../types'; -import type { VaultName } from '../../vaults/types'; -import type VaultManager from '../../vaults/VaultManager'; -import type Logger from '@matrixai/logger'; -import type * as grpc from '@grpc/grpc-js'; -import * as vaultsUtils from '../../vaults/utils'; -import * as grpcUtils from '../../grpc/utils'; -import * as vaultsErrors from '../../vaults/errors'; -import * as vaultsPB from '../../proto/js/polykey/v1/vaults/vaults_pb'; -import * as clientUtils from '../utils'; - -function vaultsVersion({ - authenticate, - vaultManager, - db, - logger, -}: { - authenticate: Authenticate; - vaultManager: VaultManager; - db: DB; - logger: Logger; -}) { - return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, - ): Promise => { - try { - const response = new vaultsPB.VersionResult(); - // Checking session token - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - await db.withTransactionF(async (tran) => { - const vaultIdFromName = await vaultManager.getVaultId( - call.request.getVault()?.getNameOrId() as VaultName, - tran, - ); - const vaultId = - vaultIdFromName ?? - vaultsUtils.decodeVaultId(call.request.getVault()?.getNameOrId()); - if (vaultId == null) { - throw new vaultsErrors.ErrorVaultsVaultUndefined(); - } - // Doing the deed - const versionId = call.request.getVersionId(); - const [latestOid, currentVersionId] = await vaultManager.withVaults( - [vaultId], - async (vault) => { - const latestOid = (await vault.log())[0].commitId; - await vault.version(versionId); - const currentVersionId = (await vault.log(versionId, 0))[0] - ?.commitId; - return [latestOid, currentVersionId]; - }, - tran, - ); - // Checking if latest version ID - const isLatestVersion = latestOid === currentVersionId; - // Creating message - response.setIsLatestVersion(isLatestVersion); - }); - // Sending message - callback(null, response); - return; - } catch (e) { - callback(grpcUtils.fromError(e)); - !clientUtils.isClientClientError(e, [ - vaultsErrors.ErrorVaultsVaultUndefined, - vaultsErrors.ErrorVaultReferenceInvalid, - vaultsErrors.ErrorVaultReferenceMissing, - ]) && logger.error(`${vaultsVersion.name}:${e}`); - return; - } - }; -} - -export default vaultsVersion; diff --git a/src/client/types.ts b/src/client/types.ts index d06cdb11e..a122b85f2 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -1,14 +1,18 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Class } from '@matrixai/errors'; -import type ErrorPolykey from '../ErrorPolykey'; +import type { JSONValue } from '../types'; -type Authenticate = ( - metadataClient: grpc.Metadata, - metadataServer?: grpc.Metadata, -) => Promise; +// eslint-disable-next-line +type NoData = {}; -type ClientClientErrors = Array< - Class> | Array>> ->; +type ClientRPCRequestParams = NoData> = { + metadata?: { + [Key: string]: JSONValue; + } & Partial<{ authorization: string }>; +} & Omit; -export type { Authenticate, ClientClientErrors }; +type ClientRPCResponseResult = NoData> = { + metadata?: { + [Key: string]: JSONValue; + } & Partial<{ authorization: string }>; +} & Omit; + +export type { ClientRPCRequestParams, ClientRPCResponseResult, NoData }; diff --git a/src/clientRPC/authenticationMiddleware.ts b/src/client/utils/authenticationMiddleware.ts similarity index 55% rename from src/clientRPC/authenticationMiddleware.ts rename to src/client/utils/authenticationMiddleware.ts index ddf89ac4d..6d46adf9d 100644 --- a/src/clientRPC/authenticationMiddleware.ts +++ b/src/client/utils/authenticationMiddleware.ts @@ -1,24 +1,27 @@ import type { - JsonRpcRequest, - JsonRpcResponse, + JSONRPCRequest, + JSONRPCResponse, MiddlewareFactory, -} from '../RPC/types'; -import type { RPCRequestParams, RPCResponseResult } from './types'; -import type { Session } from '../sessions'; -import type SessionManager from '../sessions/SessionManager'; -import type KeyRing from '../keys/KeyRing'; +} from 'rpc/types'; +import type { ClientRPCRequestParams, ClientRPCResponseResult } from '../types'; +import type { Session } from '../../sessions/index'; +import type SessionManager from '../../sessions/SessionManager'; +import type KeyRing from '../../keys/KeyRing'; +import type { JSONRPCError, JSONRPCResponseError } from '../../rpc/types'; import { TransformStream } from 'stream/web'; -import { authenticate, decodeAuth } from './utils'; -import * as utils from '../utils'; +import { authenticate, decodeAuth } from '../utils/utils'; +import { sysexits } from '../../errors'; +import * as utils from '../../utils/index'; +import * as rpcUtils from '../../rpc/utils/utils'; function authenticationMiddlewareServer( sessionManager: SessionManager, keyRing: KeyRing, ): MiddlewareFactory< - JsonRpcRequest, - JsonRpcRequest, - JsonRpcResponse, - JsonRpcResponse + JSONRPCRequest, + JSONRPCRequest, + JSONRPCResponse, + JSONRPCResponse > { return () => { let forwardFirst = true; @@ -26,8 +29,8 @@ function authenticationMiddlewareServer( let outgoingToken: string | null = null; return { forward: new TransformStream< - JsonRpcRequest, - JsonRpcRequest + JSONRPCRequest, + JSONRPCRequest >({ transform: async (chunk, controller) => { if (forwardFirst) { @@ -39,7 +42,18 @@ function authenticationMiddlewareServer( ); } catch (e) { controller.terminate(); - reverseController.terminate(); + const rpcError: JSONRPCError = { + code: e.exitCode ?? sysexits.UNKNOWN, + message: e.description ?? '', + data: rpcUtils.fromError(e, true), + }; + const rpcErrorMessage: JSONRPCResponseError = { + jsonrpc: '2.0', + error: rpcError, + id: null, + }; + reverseController.enqueue(rpcErrorMessage); + reverseController.end(); return; } } @@ -56,10 +70,10 @@ function authenticationMiddlewareServer( if (outgoingToken != null && 'result' in chunk) { if (chunk.result.metadata == null) { chunk.result.metadata = { - Authorization: '', + authorization: '', }; } - chunk.result.metadata.Authorization = outgoingToken; + chunk.result.metadata.authorization = outgoingToken; outgoingToken = null; } controller.enqueue(chunk); @@ -72,30 +86,30 @@ function authenticationMiddlewareServer( function authenticationMiddlewareClient( session: Session, ): MiddlewareFactory< - JsonRpcRequest, - JsonRpcRequest, - JsonRpcResponse, - JsonRpcResponse + JSONRPCRequest, + JSONRPCRequest, + JSONRPCResponse, + JSONRPCResponse > { return () => { let forwardFirst = true; return { forward: new TransformStream< - JsonRpcRequest, - JsonRpcRequest + JSONRPCRequest, + JSONRPCRequest >({ transform: async (chunk, controller) => { if (forwardFirst) { if (chunk.params == null) utils.never(); - if (chunk.params.metadata?.Authorization == null) { + if (chunk.params.metadata?.authorization == null) { const token = await session.readToken(); if (token != null) { if (chunk.params.metadata == null) { chunk.params.metadata = { - Authorization: '', + authorization: '', }; } - chunk.params.metadata.Authorization = `Bearer ${token}`; + chunk.params.metadata.authorization = `Bearer ${token}`; } } } @@ -104,8 +118,8 @@ function authenticationMiddlewareClient( }, }), reverse: new TransformStream< - JsonRpcResponse, - JsonRpcResponse + JSONRPCResponse, + JSONRPCResponse >({ transform: async (chunk, controller) => { controller.enqueue(chunk); diff --git a/src/client/utils/index.ts b/src/client/utils/index.ts index 04bca77e0..5a40ad5dd 100644 --- a/src/client/utils/index.ts +++ b/src/client/utils/index.ts @@ -1 +1,2 @@ +export * from './authenticationMiddleware'; export * from './utils'; diff --git a/src/client/utils/utils.ts b/src/client/utils/utils.ts index 97eb5e04d..f37ee1ce8 100644 --- a/src/client/utils/utils.ts +++ b/src/client/utils/utils.ts @@ -1,131 +1,66 @@ -import type { - NextCall, - Interceptor, - InterceptorOptions, -} from '@grpc/grpc-js/build/src/client-interceptors'; -import type ErrorPolykey from '../../ErrorPolykey'; -import type KeyRing from '../../keys/KeyRing'; -import type Session from '../../sessions/Session'; -import type SessionManager from '../../sessions/SessionManager'; -import type { SessionToken } from '../../sessions/types'; -import type { Authenticate, ClientClientErrors } from '../types'; -import * as grpc from '@grpc/grpc-js'; -import * as validationErrors from '../../validation/errors'; +import type { ClientRPCRequestParams } from '../types'; +import type SessionManager from 'sessions/SessionManager'; +import type KeyRing from 'keys/KeyRing'; +import type { JSONRPCRequest } from 'rpc/types'; +import type { SessionToken } from 'sessions/types'; import * as clientErrors from '../errors'; -/** - * Array of errors that are always considered to be "client errors" - * (4xx errors in HTTP) in the context of the client service - */ -const defaultClientErrors: ClientClientErrors = [ - validationErrors.ErrorValidation, - clientErrors.ErrorClientAuthMissing, - clientErrors.ErrorClientAuthFormat, - clientErrors.ErrorClientAuthDenied, -]; - -/** - * Session interceptor middleware for authenticatio - * Session token is read at the beginning of every call - * Session token is written if the server returns a new token - */ -function sessionInterceptor(session: Session): Interceptor { - const interceptor: Interceptor = ( - options: InterceptorOptions, - nextCall: NextCall, - ) => { - const requester = { - start: async (metadata: grpc.Metadata, _, next) => { - // Outbound interception - // This executes before the call is started - // Set the session token only if the caller hasn't set a Basic token - if (metadata.get('Authorization').length === 0) { - const token = await session.readToken(); - if (token != null) { - encodeAuthFromSession(token, metadata); - } - } - next(metadata, { - // Inbound interception - onReceiveMetadata: async (metadata: grpc.Metadata, next) => { - // This executes when the metadata is received from the server - const token = decodeAuthToSession(metadata); - if (token != null) { - await session.writeToken(token); - } - next(metadata); - }, - }); - }, - }; - return new grpc.InterceptingCall(nextCall(options), requester); - }; - return interceptor; -} - -function authenticator( +async function authenticate( sessionManager: SessionManager, keyRing: KeyRing, -): Authenticate { - return async ( - metadataClient: grpc.Metadata, - metadataServer: grpc.Metadata = new grpc.Metadata(), - ) => { - const auth = metadataClient.get('Authorization')[0] as string | undefined; - if (auth == null) { - throw new clientErrors.ErrorClientAuthMissing(); + message: JSONRPCRequest, +) { + if (message.params == null) throw new clientErrors.ErrorClientAuthMissing(); + if (message.params.metadata == null) { + throw new clientErrors.ErrorClientAuthMissing(); + } + const auth = message.params.metadata.authorization; + if (auth == null) { + throw new clientErrors.ErrorClientAuthMissing(); + } + if (auth.startsWith('Bearer ')) { + const token = auth.substring(7) as SessionToken; + if (!(await sessionManager.verifyToken(token))) { + throw new clientErrors.ErrorClientAuthDenied(); } - if (auth.startsWith('Bearer ')) { - const token = auth.substring(7) as SessionToken; - if (!(await sessionManager.verifyToken(token))) { - throw new clientErrors.ErrorClientAuthDenied(); - } - } else if (auth.startsWith('Basic ')) { - const encoded = auth.substring(6); - const decoded = Buffer.from(encoded, 'base64').toString('utf-8'); - const match = decoded.match(/:(.*)/); - if (match == null) { - throw new clientErrors.ErrorClientAuthFormat(); - } - const password = match[1]; - if (!(await keyRing.checkPassword(password))) { - throw new clientErrors.ErrorClientAuthDenied(); - } - } else { - throw new clientErrors.ErrorClientAuthMissing(); + } else if (auth.startsWith('Basic ')) { + const encoded = auth.substring(6); + const decoded = Buffer.from(encoded, 'base64').toString('utf-8'); + const match = decoded.match(/:(.*)/); + if (match == null) { + throw new clientErrors.ErrorClientAuthFormat(); } - const token = await sessionManager.createToken(); - encodeAuthFromSession(token, metadataServer); - return metadataServer; - }; + const password = match[1]; + if (!(await keyRing.checkPassword(password))) { + throw new clientErrors.ErrorClientAuthDenied(); + } + } else { + throw new clientErrors.ErrorClientAuthMissing(); + } + const token = await sessionManager.createToken(); + return `Bearer ${token}`; } -/** - * Encodes an Authorization header from session token - * Assumes token is already encoded - * Will mutate metadata if it is passed in - */ -function encodeAuthFromSession( - token: SessionToken, - metadata: grpc.Metadata = new grpc.Metadata(), -): grpc.Metadata { - metadata.set('Authorization', `Bearer ${token}`); - return metadata; +function decodeAuth(messageParams: ClientRPCRequestParams) { + const auth = messageParams.metadata?.authorization; + if (auth == null || !auth.startsWith('Bearer ')) { + return; + } + return auth.substring(7) as SessionToken; +} + +function encodeAuthFromPassword(password: string): string { + const encoded = Buffer.from(`:${password}`).toString('base64'); + return `Basic ${encoded}`; } /** - * Encodes an Authorization header from password - * Uses base64 standard format with padding - * Only use this on small data + * Encodes an Authorization header from session token + * Assumes token is already encoded * Will mutate metadata if it is passed in */ -function encodeAuthFromPassword( - password: string, - metadata: grpc.Metadata = new grpc.Metadata(), -): grpc.Metadata { - const encoded = Buffer.from(`:${password}`).toString('base64'); - metadata.set('Authorization', `Basic ${encoded}`); - return metadata; +function encodeAuthFromSession(token: SessionToken): string { + return `Bearer ${token}`; } /** @@ -133,77 +68,19 @@ function encodeAuthFromPassword( * The server is expected to only provide bearer tokens */ function decodeAuthToSession( - metadata: grpc.Metadata, + messageParams: ClientRPCRequestParams, ): SessionToken | undefined { - const auth = metadata.get('Authorization')[0] as string | undefined; + const auth = messageParams.metadata?.authorization; if (auth == null || !auth.startsWith('Bearer ')) { return; } return auth.substring(7) as SessionToken; } -/** - * Checks whether an error is a "client error" (4xx errors in HTTP) - * Used by the service handlers since client errors should not be - * reported on the server side - * Additional errors that are considered to be client errors in the - * context of a given handler can be supplied in the `extraClientErrors` - * argument - */ -function isClientClientError( - thrownError: ErrorPolykey, - extraClientErrors?: ClientClientErrors, -): boolean { - for (const error of defaultClientErrors) { - if (Array.isArray(error)) { - let e = thrownError; - let matches = true; - for (const eType of error) { - if (e == null) { - matches = false; - break; - } - if (!(e instanceof eType)) { - matches = false; - break; - } - e = e.cause; - } - if (matches) return true; - } else if (thrownError instanceof error) { - return true; - } - } - if (extraClientErrors) { - for (const error of extraClientErrors) { - if (Array.isArray(error)) { - let e = thrownError; - let matches = true; - for (const eType of error) { - if (e == null) { - matches = false; - break; - } - if (!(e instanceof eType)) { - matches = false; - break; - } - e = e.cause; - } - if (matches) return true; - } else if (thrownError instanceof error) { - return true; - } - } - } - return false; -} - export { - sessionInterceptor, - authenticator, + authenticate, + decodeAuth, encodeAuthFromPassword, encodeAuthFromSession, decodeAuthToSession, - isClientClientError, }; diff --git a/src/clientRPC/errors.ts b/src/clientRPC/errors.ts deleted file mode 100644 index 030bad8d5..000000000 --- a/src/clientRPC/errors.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ErrorPolykey, sysexits } from '../errors'; - -class ErrorRPC extends ErrorPolykey {} - -class ErrorRPCClient extends ErrorRPC {} - -class ErrorClientAuthMissing extends ErrorRPCClient { - static description = 'Authorisation metadata is required but missing'; - exitCode = sysexits.NOPERM; -} - -class ErrorClientAuthFormat extends ErrorRPCClient { - static description = 'Authorisation metadata has invalid format'; - exitCode = sysexits.USAGE; -} - -class ErrorClientAuthDenied extends ErrorRPCClient { - static description = 'Authorisation metadata is incorrect or expired'; - exitCode = sysexits.NOPERM; -} - -export { - ErrorRPC, - ErrorRPCClient, - ErrorClientAuthMissing, - ErrorClientAuthFormat, - ErrorClientAuthDenied, -}; diff --git a/src/clientRPC/handlers/agentStatus.ts b/src/clientRPC/handlers/agentStatus.ts deleted file mode 100644 index 8e53106f6..000000000 --- a/src/clientRPC/handlers/agentStatus.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type KeyRing from '../../keys/KeyRing'; -import type CertManager from '../../keys/CertManager'; -import type Logger from '@matrixai/logger'; -import type { NodeIdEncoded } from '../../ids'; -import type { RPCRequestParams, RPCResponseResult } from '../types'; -import * as nodesUtils from '../../nodes/utils'; -import * as keysUtils from '../../keys/utils'; -import { UnaryHandler } from '../../RPC/handlers'; -import { UnaryCaller } from '../../RPC/callers'; - -type StatusResult = { - pid: number; - nodeId: NodeIdEncoded; - publicJwk: string; -}; - -const agentStatusCaller = new UnaryCaller< - RPCRequestParams, - RPCResponseResult ->(); - -class AgentStatusHandler extends UnaryHandler< - { - keyRing: KeyRing; - certManager: CertManager; - logger: Logger; - }, - RPCRequestParams, - RPCResponseResult -> { - public async handle(): Promise> { - return { - pid: process.pid, - nodeId: nodesUtils.encodeNodeId(this.container.keyRing.getNodeId()), - publicJwk: JSON.stringify( - keysUtils.publicKeyToJWK(this.container.keyRing.keyPair.publicKey), - ), - }; - } -} - -export { AgentStatusHandler, agentStatusCaller }; diff --git a/src/clientRPC/handlers/agentUnlock.ts b/src/clientRPC/handlers/agentUnlock.ts deleted file mode 100644 index e0c6756df..000000000 --- a/src/clientRPC/handlers/agentUnlock.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type Logger from '@matrixai/logger'; -import type { RPCRequestParams, RPCResponseResult } from '../types'; -import { UnaryHandler } from '../../RPC/handlers'; -import { UnaryCaller } from '../../RPC/callers'; - -const agentUnlockCaller = new UnaryCaller< - RPCRequestParams, - RPCResponseResult ->(); - -class AgentUnlockHandler extends UnaryHandler< - { logger: Logger }, - RPCRequestParams, - RPCResponseResult -> { - public async handle(): Promise { - // This is a NOP handler, - // authentication and unlocking is handled via middleware. - // Failure to authenticate will be an error from the middleware layer. - return {}; - } -} - -export { agentUnlockCaller, AgentUnlockHandler }; diff --git a/src/clientRPC/types.ts b/src/clientRPC/types.ts deleted file mode 100644 index 371511752..000000000 --- a/src/clientRPC/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { JSONValue } from '../types'; - -// eslint-disable-next-line -type NoData = {}; - -type RPCRequestParams = NoData> = { - metadata?: { - [Key: string]: JSONValue; - } & Partial<{ Authorization: string }>; -} & Omit; - -type RPCResponseResult = NoData> = { - metadata?: { - [Key: string]: JSONValue; - } & Partial<{ Authorization: string }>; -} & Omit; - -export type { RPCRequestParams, RPCResponseResult, NoData }; diff --git a/src/clientRPC/utils.ts b/src/clientRPC/utils.ts deleted file mode 100644 index 8fea99443..000000000 --- a/src/clientRPC/utils.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { RPCRequestParams } from './types'; -import type SessionManager from 'sessions/SessionManager'; -import type KeyRing from 'keys/KeyRing'; -import type { JsonRpcRequest } from 'RPC/types'; -import type { SessionToken } from 'sessions/types'; -import * as clientErrors from './errors'; - -async function authenticate( - sessionManager: SessionManager, - keyRing: KeyRing, - message: JsonRpcRequest, -) { - if (message.params == null) throw new clientErrors.ErrorClientAuthMissing(); - if (message.params.metadata == null) { - throw new clientErrors.ErrorClientAuthMissing(); - } - const auth = message.params.metadata.Authorization; - if (auth == null) { - throw new clientErrors.ErrorClientAuthMissing(); - } - if (auth.startsWith('Bearer ')) { - const token = auth.substring(7) as SessionToken; - if (!(await sessionManager.verifyToken(token))) { - throw new clientErrors.ErrorClientAuthDenied(); - } - } else if (auth.startsWith('Basic ')) { - const encoded = auth.substring(6); - const decoded = Buffer.from(encoded, 'base64').toString('utf-8'); - const match = decoded.match(/:(.*)/); - if (match == null) { - throw new clientErrors.ErrorClientAuthFormat(); - } - const password = match[1]; - if (!(await keyRing.checkPassword(password))) { - throw new clientErrors.ErrorClientAuthDenied(); - } - } else { - throw new clientErrors.ErrorClientAuthMissing(); - } - const token = await sessionManager.createToken(); - return `Bearer ${token}`; -} - -function decodeAuth(messageParams: RPCRequestParams) { - const auth = messageParams.metadata?.Authorization; - if (auth == null || !auth.startsWith('Bearer ')) { - return; - } - return auth.substring(7) as SessionToken; -} - -function encodeAuthFromPassword(password: string): string { - const encoded = Buffer.from(`:${password}`).toString('base64'); - return `Basic ${encoded}`; -} - -export { authenticate, decodeAuth, encodeAuthFromPassword }; diff --git a/src/config.ts b/src/config.ts index 6c96a548b..10e0e7621 100644 --- a/src/config.ts +++ b/src/config.ts @@ -101,6 +101,9 @@ const config = { // GRPCServer for client service clientHost: '127.0.0.1' as Host, clientPort: 0 as Port, + maxReadBufferBytes: 1_000_000_000, // About 1 GB + pingInterval: 1_000, // 1 second + pingTimeout: 10_000, // 10 seconds }, proxyConfig: { connConnectTime: 2000, diff --git a/src/errors.ts b/src/errors.ts index 98a9dc13c..ff861a2bd 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -80,4 +80,4 @@ export * from './schema/errors'; export * from './status/errors'; export * from './validation/errors'; export * from './utils/errors'; -export * from './RPC/errors'; +export * from './rpc/errors'; diff --git a/src/grpc/errors.ts b/src/grpc/errors.ts index b3ad1301d..ae753a71b 100644 --- a/src/grpc/errors.ts +++ b/src/grpc/errors.ts @@ -50,7 +50,7 @@ class ErrorGRPCServerVerification extends ErrorGRPC { exitCode = sysexits.UNAVAILABLE; } -class ErrorPolykeyRemote extends ErrorPolykey { +class ErrorPolykeyRemoteOLD extends ErrorPolykey { static description = 'Remote error from RPC call'; exitCode: number = sysexits.UNAVAILABLE; metadata: ClientMetadata; @@ -112,5 +112,5 @@ export { ErrorGRPCServerShutdown, ErrorGRPCServerNotSecured, ErrorGRPCServerVerification, - ErrorPolykeyRemote, + ErrorPolykeyRemoteOLD, }; diff --git a/src/grpc/utils/utils.ts b/src/grpc/utils/utils.ts index 7a87ec9ca..a37a13bdc 100644 --- a/src/grpc/utils/utils.ts +++ b/src/grpc/utils/utils.ts @@ -200,7 +200,7 @@ function toError( if (isNaN(parseInt(key)) && e.code === grpc.status[key]) { if (key === 'UNKNOWN' && errorData != null) { const error: Error = JSON.parse(errorData, reviver); - const remoteError = new grpcErrors.ErrorPolykeyRemote( + const remoteError = new grpcErrors.ErrorPolykeyRemoteOLD( metadata, error.message, { diff --git a/src/nodes/NodeManager.ts b/src/nodes/NodeManager.ts index 005327847..9b5ff8abb 100644 --- a/src/nodes/NodeManager.ts +++ b/src/nodes/NodeManager.ts @@ -1167,6 +1167,10 @@ class NodeManager { logger.info('Syncing nodeGraph'); // Getting the seed node connection information const seedNodes = this.nodeConnectionManager.getSeedNodes(); + if (seedNodes.length === 0) { + logger.debug(`No seed nodes provided, skipping discovery`); + return; + } const addresses = await Promise.all( await this.db.withTransactionF(async (tran) => seedNodes.map( diff --git a/src/RPC/RPCClient.ts b/src/rpc/RPCClient.ts similarity index 66% rename from src/RPC/RPCClient.ts rename to src/rpc/RPCClient.ts index 91f2c729d..6161a1374 100644 --- a/src/RPC/RPCClient.ts +++ b/src/rpc/RPCClient.ts @@ -1,7 +1,7 @@ import type { HandlerType, - JsonRpcRequestMessage, - StreamPairCreateCallback, + JSONRPCRequestMessage, + StreamFactory, ClientManifest, } from './types'; import type { JSONValue } from 'types'; @@ -11,38 +11,48 @@ import type { ReadableStream, } from 'stream/web'; import type { - JsonRpcRequest, - JsonRpcResponse, + JSONRPCRequest, + JSONRPCResponse, MiddlewareFactory, MapCallers, } from './types'; +import type { NodeId } from 'ids/index'; import { CreateDestroy, ready } from '@matrixai/async-init/dist/CreateDestroy'; import Logger from '@matrixai/logger'; -import * as middlewareUtils from './middleware'; +import { IdInternal } from '@matrixai/id'; +import * as middlewareUtils from './utils/middleware'; import * as rpcErrors from './errors'; -import * as rpcUtils from './utils'; -import { - clientInputTransformStream, - clientOutputTransformStream, -} from './utils'; +import * as rpcUtils from './utils/utils'; import { never } from '../utils'; // eslint-disable-next-line interface RPCClient extends CreateDestroy {} @CreateDestroy() class RPCClient { + /** + * @param obj + * @param obj.manifest - Client manifest that defines the types for the rpc + * methods. + * @param obj.streamFactory - An arrow function that when called, creates a + * new stream for each rpc method call. + * @param obj.middlewareFactory - Middleware used to process the rpc messages. + * The middlewareFactory needs to be a function that creates a pair of + * transform streams that convert `JSONRPCRequest` to `Uint8Array` on the forward + * path and `Uint8Array` to `JSONRPCResponse` on the reverse path. + * @param obj.logger + */ static async createRPCClient({ manifest, - streamPairCreateCallback, - middleware = middlewareUtils.defaultClientMiddlewareWrapper(), + streamFactory, + middlewareFactory = middlewareUtils.defaultClientMiddlewareWrapper(), logger = new Logger(this.name), }: { manifest: M; - streamPairCreateCallback: StreamPairCreateCallback; - middleware?: MiddlewareFactory< + streamFactory: StreamFactory; + middlewareFactory?: MiddlewareFactory< Uint8Array, - JsonRpcRequest, - JsonRpcResponse, + JSONRPCRequest, + JSONRPCResponse, Uint8Array >; logger?: Logger; @@ -50,8 +60,8 @@ class RPCClient { logger.info(`Creating ${this.name}`); const rpcClient = new this({ manifest, - streamPairCreateCallback, - middleware, + streamFactory, + middlewareFactory, logger, }); logger.info(`Created ${this.name}`); @@ -59,11 +69,11 @@ class RPCClient { } protected logger: Logger; - protected streamPairCreateCallback: StreamPairCreateCallback; - protected middleware: MiddlewareFactory< + protected streamFactory: StreamFactory; + protected middlewareFactory: MiddlewareFactory< Uint8Array, - JsonRpcRequest, - JsonRpcResponse, + JSONRPCRequest, + JSONRPCResponse, Uint8Array >; protected callerTypes: Record; @@ -93,23 +103,23 @@ class RPCClient { public constructor({ manifest, - streamPairCreateCallback, - middleware, + streamFactory, + middlewareFactory, logger, }: { manifest: M; - streamPairCreateCallback: StreamPairCreateCallback; - middleware: MiddlewareFactory< + streamFactory: StreamFactory; + middlewareFactory: MiddlewareFactory< Uint8Array, - JsonRpcRequest, - JsonRpcResponse, + JSONRPCRequest, + JSONRPCResponse, Uint8Array >; logger: Logger; }) { this.callerTypes = rpcUtils.getHandlerTypes(manifest); - this.streamPairCreateCallback = streamPairCreateCallback; - this.middleware = middleware; + this.streamFactory = streamFactory; + this.middlewareFactory = middlewareFactory; this.logger = logger; } @@ -118,12 +128,12 @@ class RPCClient { this.logger.info(`Destroyed ${this.constructor.name}`); } - @ready(new rpcErrors.ErrorRpcDestroyed()) + @ready(new rpcErrors.ErrorRPCDestroyed()) public get methods(): MapCallers { return this.methodsProxy as MapCallers; } - @ready(new rpcErrors.ErrorRpcDestroyed()) + @ready(new rpcErrors.ErrorRPCDestroyed()) public async unaryCaller( method: string, parameters: I, @@ -134,14 +144,14 @@ class RPCClient { await writer.write(parameters); const output = await reader.read(); if (output.done) { - throw new rpcErrors.ErrorRpcRemoteError('Stream ended before response'); + throw new rpcErrors.ErrorRPCMissingResponse(); } await reader.cancel(); await writer.close(); return output.value; } - @ready(new rpcErrors.ErrorRpcDestroyed()) + @ready(new rpcErrors.ErrorRPCDestroyed()) public async serverStreamCaller( method: string, parameters: I, @@ -154,7 +164,7 @@ class RPCClient { return callerInterface.readable; } - @ready(new rpcErrors.ErrorRpcDestroyed()) + @ready(new rpcErrors.ErrorRPCDestroyed()) public async clientStreamCaller( method: string, ): Promise<{ @@ -165,7 +175,7 @@ class RPCClient { const reader = callerInterface.readable.getReader(); const output = reader.read().then(({ value, done }) => { if (done) { - throw new rpcErrors.ErrorRpcRemoteError('Stream ended before response'); + throw new rpcErrors.ErrorRPCMissingResponse(); } return value; }); @@ -175,15 +185,23 @@ class RPCClient { }; } - @ready(new rpcErrors.ErrorRpcDestroyed()) + @ready(new rpcErrors.ErrorRPCDestroyed()) public async duplexStreamCaller( method: string, ): Promise> { - const outputMessageTransformStream = clientOutputTransformStream(); - const inputMessageTransformStream = clientInputTransformStream(method); - const middleware = this.middleware(); + // Providing empty metadata here. we don't support it yet. + const outputMessageTransformStream = + rpcUtils.clientOutputTransformStream({ + nodeId: IdInternal.fromBuffer(Buffer.alloc(32, 0)), // FIXME + host: '', + port: 0, + command: method, + }); + const inputMessageTransformStream = + rpcUtils.clientInputTransformStream(method); + const middleware = this.middlewareFactory(); // Hooking up agnostic stream side - const streamPair = await this.streamPairCreateCallback(); + const streamPair = await this.streamFactory(); void streamPair.readable .pipeThrough(middleware.reverse) .pipeTo(outputMessageTransformStream.writable) @@ -200,14 +218,14 @@ class RPCClient { }; } - @ready(new rpcErrors.ErrorRpcDestroyed()) + @ready(new rpcErrors.ErrorRPCDestroyed()) public async rawStreamCaller( method: string, headerParams: JSONValue, ): Promise> { - const streamPair = await this.streamPairCreateCallback(); + const streamPair = await this.streamFactory(); const tempWriter = streamPair.writable.getWriter(); - const header: JsonRpcRequestMessage = { + const header: JSONRPCRequestMessage = { jsonrpc: '2.0', method, params: headerParams, diff --git a/src/RPC/RPCServer.ts b/src/rpc/RPCServer.ts similarity index 61% rename from src/RPC/RPCServer.ts rename to src/rpc/RPCServer.ts index 664811c2f..e81413e8b 100644 --- a/src/RPC/RPCServer.ts +++ b/src/rpc/RPCServer.ts @@ -1,11 +1,11 @@ import type { ClientHandlerImplementation, DuplexHandlerImplementation, - JsonRpcError, - JsonRpcRequest, - JsonRpcResponse, - JsonRpcResponseError, - JsonRpcResponseResult, + JSONRPCError, + JSONRPCRequest, + JSONRPCResponse, + JSONRPCResponseError, + JSONRPCResponseResult, ServerManifest, RawHandlerImplementation, ServerHandlerImplementation, @@ -14,8 +14,8 @@ import type { } from './types'; import type { ReadableWritablePair } from 'stream/web'; import type { JSONValue } from '../types'; -import type { RPCErrorEvent } from './utils'; import type { MiddlewareFactory } from './types'; +import { TransformStream } from 'stream/web'; import { ReadableStream } from 'stream/web'; import { CreateDestroy, ready } from '@matrixai/async-init/dist/CreateDestroy'; import Logger from '@matrixai/logger'; @@ -27,27 +27,49 @@ import { ServerHandler, UnaryHandler, } from './handlers'; -import * as rpcUtils from './utils'; +import * as rpcEvents from './events'; +import * as rpcUtils from './utils/utils'; import * as rpcErrors from './errors'; -import * as middlewareUtils from './middleware'; +import * as middlewareUtils from './utils/middleware'; import { never } from '../utils/utils'; import { sysexits } from '../errors'; +/** + * You must provide a error handler `addEventListener('error')`. + * Otherwise errors will just be ignored. + * + * Events: + * - error + */ interface RPCServer extends CreateDestroy {} @CreateDestroy() -class RPCServer { - static async createRPCServer({ +class RPCServer extends EventTarget { + /** + * Creates RPC server. + + * @param obj + * @param obj.manifest - Server manifest used to define the rpc method + * handlers. + * @param obj.middlewareFactory - Middleware used to process the rpc messages. + * The middlewareFactory needs to be a function that creates a pair of + * transform streams that convert `Uint8Array` to `JSONRPCRequest` on the forward + * path and `JSONRPCResponse` to `Uint8Array` on the reverse path. + * @param obj.sensitive - If true, sanitises any rpc error messages of any + * sensitive information. + * @param obj.logger + */ + public static async createRPCServer({ manifest, - middleware = middlewareUtils.defaultServerMiddlewareWrapper(), + middlewareFactory = middlewareUtils.defaultServerMiddlewareWrapper(), sensitive = false, logger = new Logger(this.name), }: { manifest: ServerManifest; - middleware?: MiddlewareFactory< - JsonRpcRequest, + middlewareFactory?: MiddlewareFactory< + JSONRPCRequest, Uint8Array, Uint8Array, - JsonRpcResponseResult + JSONRPCResponse >; sensitive?: boolean; logger?: Logger; @@ -55,7 +77,7 @@ class RPCServer { logger.info(`Creating ${this.name}`); const rpcServer = new this({ manifest, - middleware, + middlewareFactory, sensitive, logger, }); @@ -63,35 +85,34 @@ class RPCServer { return rpcServer; } - // Properties protected logger: Logger; protected handlerMap: Map = new Map(); protected activeStreams: Set> = new Set(); protected sensitive: boolean; - protected events: EventTarget = new EventTarget(); - protected middleware: MiddlewareFactory< - JsonRpcRequest, + protected middlewareFactory: MiddlewareFactory< + JSONRPCRequest, Uint8Array, Uint8Array, - JsonRpcResponseResult + JSONRPCResponseResult >; public constructor({ manifest, - middleware, + middlewareFactory, sensitive, logger, }: { manifest: ServerManifest; - middleware: MiddlewareFactory< - JsonRpcRequest, + middlewareFactory: MiddlewareFactory< + JSONRPCRequest, Uint8Array, Uint8Array, - JsonRpcResponseResult + JSONRPCResponseResult >; sensitive: boolean; logger: Logger; }) { + super(); for (const [key, manifestItem] of Object.entries(manifest)) { if (manifestItem instanceof RawHandler) { this.registerRawStreamHandler( @@ -134,7 +155,7 @@ class RPCServer { } never(); } - this.middleware = middleware; + this.middlewareFactory = middlewareFactory; this.sensitive = sensitive; this.logger = logger; } @@ -143,7 +164,7 @@ class RPCServer { this.logger.info(`Destroying ${this.constructor.name}`); // Stopping any active steams for await (const [activeStream] of this.activeStreams.entries()) { - activeStream.cancel(new rpcErrors.ErrorRpcStopping()); + activeStream.cancel(new rpcErrors.ErrorRPCStopping()); } for await (const [activeStream] of this.activeStreams.entries()) { await activeStream; @@ -158,32 +179,58 @@ class RPCServer { this.handlerMap.set(method, handler); } + /** + * Registers a duplex stream handler. + * This handles all message parsing and conversion from generators + * to raw streams. + * + * @param method - The rpc method name. + * @param handler - The handler takes an input async iterable and returns an output async iterable. + */ protected registerDuplexStreamHandler< I extends JSONValue, O extends JSONValue, - >(method: string, handler: DuplexHandlerImplementation) { - // This needs to handle all the message parsing and conversion from - // generators to the raw streams. - + >(method: string, handler: DuplexHandlerImplementation): void { const rawSteamHandler: RawHandlerImplementation = ( - [input, header], + [header, input], connectionInfo, ctx, ) => { + // Setting up abort controller + const abortController = new AbortController(); + if (ctx.signal.aborted) abortController.abort(ctx.signal.reason); + ctx.signal.addEventListener('abort', () => { + abortController.abort(ctx.signal.reason); + }); + const signal = abortController.signal; // Setting up middleware - const middleware = this.middleware(header); - const forwardStream = input.pipeThrough(middleware.forward); + const middleware = this.middlewareFactory(); + // Forward from the client to the server + const headerStream = new TransformStream({ + start(controller) { + controller.enqueue(Buffer.from(JSON.stringify(header))); + }, + transform(chunk, controller) { + controller.enqueue(chunk); + }, + }); + const forwardStream = input + .pipeThrough(headerStream) + .pipeThrough(middleware.forward); + // Reverse from the server to the client const reverseStream = middleware.reverse.writable; - const events = this.events; - const outputGen = async function* (): AsyncGenerator { - if (ctx.signal.aborted) throw ctx.signal.reason; - const dataGen = async function* (): AsyncIterable { + // Generator derived from handler + const outputGen = async function* (): AsyncGenerator { + if (signal.aborted) throw signal.reason; + // Input generator derived from the forward stream + const inputGen = async function* (): AsyncIterable { for await (const data of forwardStream) { yield data.params as I; } }; - for await (const response of handler(dataGen(), connectionInfo, ctx)) { - const responseMessage: JsonRpcResponseResult = { + const handlerG = handler(inputGen(), connectionInfo, { signal }); + for await (const response of handlerG) { + const responseMessage: JSONRPCResponseResult = { jsonrpc: '2.0', result: response, id: null, @@ -192,7 +239,7 @@ class RPCServer { } }; const outputGenerator = outputGen(); - const reverseMiddlewareStream = new ReadableStream({ + const reverseMiddlewareStream = new ReadableStream({ pull: async (controller) => { try { const { value, done } = await outputGenerator.next(); @@ -202,44 +249,45 @@ class RPCServer { } controller.enqueue(value); } catch (e) { - if (rpcUtils.isReturnableError(e)) { - // We want to convert this error to an error message and pass it along - const rpcError: JsonRpcError = { - code: e.exitCode ?? sysexits.UNKNOWN, - message: e.description ?? '', - data: rpcUtils.fromError(e, this.sensitive), - }; - const rpcErrorMessage: JsonRpcResponseError = { - jsonrpc: '2.0', - error: rpcError, - id: null, - }; - controller.enqueue(rpcErrorMessage); - } else { - // These errors are emitted to the event system - events.dispatchEvent( - new rpcUtils.RPCErrorEvent({ - detail: { - error: e, - }, - }), - ); - } - await forwardStream.cancel( - new rpcErrors.ErrorRpcHandlerFailed('Error clean up'), - ); + const rpcError: JSONRPCError = { + code: e.exitCode ?? sysexits.UNKNOWN, + message: e.description ?? '', + data: rpcUtils.fromError(e, this.sensitive), + }; + const rpcErrorMessage: JSONRPCResponseError = { + jsonrpc: '2.0', + error: rpcError, + id: null, + }; + controller.enqueue(rpcErrorMessage); + // Clean up the input stream here, ignore error if already ended + await forwardStream + .cancel(new rpcErrors.ErrorRPCHandlerFailed('Error clean up')) + .catch(() => {}); controller.close(); } }, cancel: async (reason) => { - await outputGenerator.throw(reason); + this.dispatchEvent( + new rpcEvents.RPCErrorEvent({ + detail: new rpcErrors.ErrorRPCOutputStreamError( + 'Stream has been cancelled', + { + cause: reason, + }, + ), + }), + ); + // Abort with the reason + abortController.abort(reason); + // If the output stream path fails then we need to end the generator + // early. + await outputGenerator.return(undefined); }, }); void reverseMiddlewareStream.pipeTo(reverseStream).catch(() => {}); - return middleware.reverse.readable; }; - this.registerRawStreamHandler(method, rawSteamHandler); } @@ -252,8 +300,11 @@ class RPCServer { connectionInfo, ctx, ) { + // The `input` is expected to be an async iterable with only 1 value. + // Unlike generators, there is no `next()` method. + // So we use `break` after the first iteration. for await (const inputVal of input) { - yield handler(inputVal, connectionInfo, ctx); + yield await handler(inputVal, connectionInfo, ctx); break; } }; @@ -286,12 +337,12 @@ class RPCServer { connectionInfo, ctx, ) { - yield handler(input, connectionInfo, ctx); + yield await handler(input, connectionInfo, ctx); }; this.registerDuplexStreamHandler(method, wrapperDuplex); } - @ready(new rpcErrors.ErrorRpcDestroyed()) + @ready(new rpcErrors.ErrorRPCDestroyed()) public handleStream( streamPair: ReadableWritablePair, connectionInfo: ConnectionInfo, @@ -302,7 +353,7 @@ class RPCServer { const abortController = new AbortController(); const prom = (async () => { const { firstMessageProm, headTransformStream } = - rpcUtils.extractFirstMessageTransform(rpcUtils.parseJsonRpcRequest); + rpcUtils.extractFirstMessageTransform(rpcUtils.parseJSONRPCRequest); const inputStreamEndProm = streamPair.readable .pipeTo(headTransformStream.writable) .catch(() => {}); @@ -315,7 +366,7 @@ class RPCServer { // If the stream ends early then we just stop processing if (leadingMetadataMessage == null) { await inputStream.cancel( - new rpcErrors.ErrorRpcHandlerFailed('Missing header'), + new rpcErrors.ErrorRPCHandlerFailed('Missing header'), ); await streamPair.writable.close(); await inputStreamEndProm; @@ -325,7 +376,7 @@ class RPCServer { const handler = this.handlerMap.get(method); if (handler == null) { await inputStream.cancel( - new rpcErrors.ErrorRpcHandlerFailed('Missing handler'), + new rpcErrors.ErrorRPCHandlerFailed('Missing handler'), ); await streamPair.writable.close(); await inputStreamEndProm; @@ -333,14 +384,14 @@ class RPCServer { } if (abortController.signal.aborted) { await inputStream.cancel( - new rpcErrors.ErrorRpcHandlerFailed('Aborted'), + new rpcErrors.ErrorRPCHandlerFailed('Aborted'), ); await streamPair.writable.close(); await inputStreamEndProm; return; } const outputStream = handler( - [inputStream, leadingMetadataMessage], + [leadingMetadataMessage, inputStream], connectionInfo, { signal: abortController.signal }, ); @@ -356,24 +407,6 @@ class RPCServer { // Putting the PromiseCancellable into the active streams map this.activeStreams.add(handlerProm); } - - @ready(new rpcErrors.ErrorRpcDestroyed()) - public addEventListener( - type: 'error', - callback: (event: RPCErrorEvent) => void, - options?: boolean | AddEventListenerOptions | undefined, - ) { - this.events.addEventListener(type, callback, options); - } - - @ready(new rpcErrors.ErrorRpcDestroyed()) - public removeEventListener( - type: 'error', - callback: (event: RPCErrorEvent) => void, - options?: boolean | AddEventListenerOptions | undefined, - ) { - this.events.removeEventListener(type, callback, options); - } } export default RPCServer; diff --git a/src/RPC/callers.ts b/src/rpc/callers.ts similarity index 100% rename from src/RPC/callers.ts rename to src/rpc/callers.ts diff --git a/src/rpc/errors.ts b/src/rpc/errors.ts new file mode 100644 index 000000000..f62b03f3f --- /dev/null +++ b/src/rpc/errors.ts @@ -0,0 +1,107 @@ +import type { Class } from '@matrixai/errors'; +import type { ClientMetadata } from './types'; +import * as nodesUtils from '../nodes/utils'; +import { ErrorPolykey, sysexits } from '../errors'; + +class ErrorRPC extends ErrorPolykey {} + +class ErrorRPCDestroyed extends ErrorRPC { + static description = 'Rpc is destroyed'; + exitCode = sysexits.USAGE; +} + +class ErrorRPCStopping extends ErrorRPC { + static description = 'Rpc is stopping'; + exitCode = sysexits.USAGE; +} + +class ErrorRPCParse extends ErrorRPC { + static description = 'Failed to parse Buffer stream'; + exitCode = sysexits.SOFTWARE; +} + +/** + * This is an internal error, it should not reach the top level. + */ +class ErrorRPCHandlerFailed extends ErrorRPC { + static description = 'Failed to handle stream'; + exitCode = sysexits.SOFTWARE; +} + +class ErrorRPCMessageLength extends ErrorRPC { + static description = 'RPC Message exceeds maximum size'; + exitCode = sysexits.DATAERR; +} + +class ErrorRPCMissingResponse extends ErrorRPC { + static description = 'Stream ended before response'; + exitCode = sysexits.UNAVAILABLE; +} + +class ErrorRPCOutputStreamError extends ErrorRPC { + static description = 'Output stream failed, unable to send data'; + exitCode = sysexits.UNAVAILABLE; +} + +class ErrorPolykeyRemote extends ErrorPolykey { + static description = 'Remote error from RPC call'; + exitCode: number = sysexits.UNAVAILABLE; + metadata: ClientMetadata; + + constructor(metadata: ClientMetadata, message?: string, options?) { + super(message, options); + this.metadata = metadata; + } + + public static fromJSON>( + this: T, + json: any, + ): InstanceType { + if ( + typeof json !== 'object' || + json.type !== this.name || + typeof json.data !== 'object' || + typeof json.data.message !== 'string' || + isNaN(Date.parse(json.data.timestamp)) || + typeof json.data.metadata !== 'object' || + typeof json.data.data !== 'object' || + typeof json.data.exitCode !== 'number' || + ('stack' in json.data && typeof json.data.stack !== 'string') + ) { + throw new TypeError(`Cannot decode JSON to ${this.name}`); + } + const parsedMetadata: ClientMetadata = { + ...json.data.metadata, + nodeId: nodesUtils.decodeNodeId(json.data.metadata.nodeId), + }; + const e = new this(parsedMetadata, json.data.message, { + timestamp: new Date(json.data.timestamp), + data: json.data.data, + cause: json.data.cause, + }); + e.exitCode = json.data.exitCode; + e.stack = json.data.stack; + return e; + } + + public toJSON(): any { + const json = super.toJSON(); + json.data.metadata = { + ...this.metadata, + nodeId: nodesUtils.encodeNodeId(this.metadata.nodeId), + }; + return json; + } +} + +export { + ErrorRPC, + ErrorRPCDestroyed, + ErrorRPCStopping, + ErrorRPCParse, + ErrorRPCHandlerFailed, + ErrorRPCMessageLength, + ErrorRPCMissingResponse, + ErrorRPCOutputStreamError, + ErrorPolykeyRemote, +}; diff --git a/src/rpc/events.ts b/src/rpc/events.ts new file mode 100644 index 000000000..210d67691 --- /dev/null +++ b/src/rpc/events.ts @@ -0,0 +1,13 @@ +class RPCErrorEvent extends Event { + public detail: Error; + constructor( + options: EventInit & { + detail: Error; + }, + ) { + super('error', options); + this.detail = options.detail; + } +} + +export { RPCErrorEvent }; diff --git a/src/RPC/handlers.ts b/src/rpc/handlers.ts similarity index 84% rename from src/RPC/handlers.ts rename to src/rpc/handlers.ts index 3ca13ce5b..48714b471 100644 --- a/src/RPC/handlers.ts +++ b/src/rpc/handlers.ts @@ -1,7 +1,7 @@ import type { JSONValue } from 'types'; -import type { ContainerType } from 'RPC/types'; +import type { ContainerType } from 'rpc/types'; import type { ReadableStream } from 'stream/web'; -import type { JsonRpcRequest } from 'RPC/types'; +import type { JSONRPCRequest } from 'rpc/types'; import type { ConnectionInfo } from './types'; import type { ContextCancellable } from '../contexts/types'; @@ -20,7 +20,7 @@ abstract class RawHandler< Container extends ContainerType = ContainerType, > extends Handler { abstract handle( - input: [ReadableStream, JsonRpcRequest], + input: [JSONRPCRequest, ReadableStream], connectionInfo: ConnectionInfo, ctx: ContextCancellable, ): ReadableStream; @@ -31,6 +31,11 @@ abstract class DuplexHandler< Input extends JSONValue = JSONValue, Output extends JSONValue = JSONValue, > extends Handler { + /** + * Note that if the output has an error, the handler will not see this as an + * error. If you need to handle any clean up it should be handled in a + * `finally` block and check the abort signal for potential errors. + */ abstract handle( input: AsyncIterable, connectionInfo: ConnectionInfo, diff --git a/src/rpc/index.ts b/src/rpc/index.ts new file mode 100644 index 000000000..79129eb98 --- /dev/null +++ b/src/rpc/index.ts @@ -0,0 +1,6 @@ +export { default as RPCClient } from './RPCClient'; +export { default as RPCServer } from './RPCServer'; +export * as utils from './utils'; +export * as types from './types'; +export * as errors from './errors'; +export * as events from './events'; diff --git a/src/RPC/types.ts b/src/rpc/types.ts similarity index 53% rename from src/RPC/types.ts rename to src/rpc/types.ts index e34d0fabc..771075c87 100644 --- a/src/RPC/types.ts +++ b/src/rpc/types.ts @@ -1,7 +1,7 @@ -import type { JSONValue } from '../types'; -import type { ContextCancellable } from '../contexts/types'; import type { ReadableStream, ReadableWritablePair } from 'stream/web'; import type { Handler } from './handlers'; +import type { ContextCancellable } from '../contexts/types'; +import type { JSONValue } from '../types'; import type { Caller, RawCaller, @@ -12,102 +12,149 @@ import type { } from './callers'; import type { NodeId } from '../nodes/types'; import type { Certificate } from '../keys/types'; +import type { POJO } from '../types'; /** * This is the JSON RPC request object. this is the generic message type used for the RPC. */ -type JsonRpcRequestMessage = { - // A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0" +type JSONRPCRequestMessage = { + /** + * A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0" + */ jsonrpc: '2.0'; - // A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by a - // period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used - // for anything else. + /** + * A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by a + * period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used + * for anything else. + */ method: string; - // A Structured value that holds the parameter values to be used during the invocation of the method. - // This member MAY be omitted. + /** + * A Structured value that holds the parameter values to be used during the invocation of the method. + * This member MAY be omitted. + */ params?: T; - // An identifier established by the Client that MUST contain a String, Number, or NULL value if included. - // If it is not included it is assumed to be a notification. The value SHOULD normally not be Null [1] and Numbers - // SHOULD NOT contain fractional parts [2] + /** + * An identifier established by the Client that MUST contain a String, Number, or NULL value if included. + * If it is not included it is assumed to be a notification. The value SHOULD normally not be Null [1] and Numbers + * SHOULD NOT contain fractional parts [2] + */ id: string | number | null; }; -type JsonRpcRequestNotification = { - // A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0" +/** + * This is the JSON RPC notification object. this is used for a request that + * doesn't expect a response. + */ +type JSONRPCRequestNotification = { + /** + * A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0" + */ jsonrpc: '2.0'; - // A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by a - // period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used - // for anything else. + /** + * A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by a + * period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used + * for anything else. + */ method: string; - // A Structured value that holds the parameter values to be used during the invocation of the method. - // This member MAY be omitted. + /** + * A Structured value that holds the parameter values to be used during the invocation of the method. + * This member MAY be omitted. + */ params?: T; }; -type JsonRpcResponseResult = { - // A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0". +/** + * This is the JSON RPC response result object. It contains the response data for a + * corresponding request. + */ +type JSONRPCResponseResult = { + /** + * A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0". + */ jsonrpc: '2.0'; - // This member is REQUIRED on success. - // This member MUST NOT exist if there was an error invoking the method. - // The value of this member is determined by the method invoked on the Server. + /** + * This member is REQUIRED on success. + * This member MUST NOT exist if there was an error invoking the method. + * The value of this member is determined by the method invoked on the Server. + */ result: T; - // This member is REQUIRED. - // It MUST be the same as the value of the id member in the Request Object. - // If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), - // it MUST be Null. + /** + * This member is REQUIRED. + * It MUST be the same as the value of the id member in the Request Object. + * If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), + * it MUST be Null. + */ id: string | number | null; }; -type JsonRpcResponseError = { - // A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0". +/** + * This is the JSON RPC response Error object. It contains any errors that have + * occurred when responding to a request. + */ +type JSONRPCResponseError = { + /** + * A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0". + */ jsonrpc: '2.0'; - // This member is REQUIRED on error. - // This member MUST NOT exist if there was no error triggered during invocation. - // The value for this member MUST be an Object as defined in section 5.1. - error: JsonRpcError; - // This member is REQUIRED. - // It MUST be the same as the value of the id member in the Request Object. - // If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), - // it MUST be Null. + /** + * This member is REQUIRED on error. + * This member MUST NOT exist if there was no error triggered during invocation. + * The value for this member MUST be an Object as defined in section 5.1. + */ + error: JSONRPCError; + /** + * This member is REQUIRED. + * It MUST be the same as the value of the id member in the Request Object. + * If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), + * it MUST be Null. + */ id: string | number | null; }; -// The error codes from and including -32768 to -32000 are reserved for pre-defined errors. Any code within this range, -// but not defined explicitly below is reserved for future use. The error codes are nearly the same as those suggested -// for XML-RPC at the following url: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php -// -// code message meaning -// -32700 Parse error Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text. -// -32600 Invalid Request The JSON sent is not a valid Request object. -// -32601 Method not found The method does not exist / is not available. -// -32602 Invalid params Invalid method parameter(s). -// -32603 Internal error Internal JSON-RPC error. -// -32000 to -32099 - -type JsonRpcError = { - // A Number that indicates the error type that occurred. - // This MUST be an integer. +/** + * This is a JSON RPC error object, it encodes the error data for the JSONRPCResponseError object. + */ +type JSONRPCError = { + /** + * A Number that indicates the error type that occurred. + * This MUST be an integer. + */ code: number; - // A String providing a short description of the error. - // The message SHOULD be limited to a concise single sentence. + /** + * A String providing a short description of the error. + * The message SHOULD be limited to a concise single sentence. + */ message: string; - // A Primitive or Structured value that contains additional information about the error. - // This may be omitted. - // The value of this member is defined by the Server (e.g. detailed error information, nested errors etc.). + /** + * A Primitive or Structured value that contains additional information about the error. + * This may be omitted. + * The value of this member is defined by the Server (e.g. detailed error information, nested errors etc.). + */ data?: JSONValue; }; -type JsonRpcRequest = - | JsonRpcRequestMessage - | JsonRpcRequestNotification; +/** + * This is the JSON RPC Request object. It can be a request message or + * notification. + */ +type JSONRPCRequest = + | JSONRPCRequestMessage + | JSONRPCRequestNotification; -type JsonRpcResponse = - | JsonRpcResponseResult - | JsonRpcResponseError; +/** + * This is a JSON RPC response object. It can be a response result or error. + */ +type JSONRPCResponse = + | JSONRPCResponseResult + | JSONRPCResponseError; -type JsonRpcMessage = - | JsonRpcRequest - | JsonRpcResponse; +/** + * This is a JSON RPC Message object. This is top level and can be any kind of + * message. + */ +type JSONRPCMessage = + | JSONRPCRequest + | JSONRPCResponse; /** * Proxy connection information @@ -133,22 +180,27 @@ type HandlerImplementation = ( connectionInfo: ConnectionInfo, ctx: ContextCancellable, ) => O; + type RawHandlerImplementation = HandlerImplementation< - [ReadableStream, JsonRpcRequest], + [JSONRPCRequest, ReadableStream], ReadableStream >; + type DuplexHandlerImplementation< I extends JSONValue = JSONValue, O extends JSONValue = JSONValue, > = HandlerImplementation, AsyncIterable>; + type ServerHandlerImplementation< I extends JSONValue = JSONValue, O extends JSONValue = JSONValue, > = HandlerImplementation>; + type ClientHandlerImplementation< I extends JSONValue = JSONValue, O extends JSONValue = JSONValue, > = HandlerImplementation, Promise>; + type UnaryHandlerImplementation< I extends JSONValue = JSONValue, O extends JSONValue = JSONValue, @@ -156,11 +208,22 @@ type UnaryHandlerImplementation< type ContainerType = Record; -type StreamPairCreateCallback = () => Promise< +type StreamFactory = () => Promise< ReadableWritablePair >; -type MiddlewareFactory = (header?: JsonRpcRequest) => { +/** + * Middleware factory creates middlewares. + * Each middleware is a pair of forward and reverse. + * Each forward and reverse is a `ReadableWritablePair`. + * The forward pair is used transform input from client to server. + * The reverse pair is used to transform output from server to client. + * FR, FW is the readable and writable types of the forward pair. + * RR, RW is the readable and writable types of the reverse pair. + * FW -> FR is the direction of data flow from client to server. + * RW -> RR is the direction of data flow from server to client. + */ +type MiddlewareFactory = () => { forward: ReadableWritablePair; reverse: ReadableWritablePair; }; @@ -228,15 +291,22 @@ type MapCallers = { [K in keyof T]: ConvertCaller; }; +type ClientMetadata = { + nodeId: NodeId; + host: string; + port: number; + command: string; +} & POJO; + export type { - JsonRpcRequestMessage, - JsonRpcRequestNotification, - JsonRpcResponseResult, - JsonRpcResponseError, - JsonRpcError, - JsonRpcRequest, - JsonRpcResponse, - JsonRpcMessage, + JSONRPCRequestMessage, + JSONRPCRequestNotification, + JSONRPCResponseResult, + JSONRPCResponseError, + JSONRPCError, + JSONRPCRequest, + JSONRPCResponse, + JSONRPCMessage, ConnectionInfo, HandlerImplementation, RawHandlerImplementation, @@ -245,10 +315,11 @@ export type { ClientHandlerImplementation, UnaryHandlerImplementation, ContainerType, - StreamPairCreateCallback, + StreamFactory, MiddlewareFactory, ServerManifest, ClientManifest, HandlerType, MapCallers, + ClientMetadata, }; diff --git a/src/rpc/utils/index.ts b/src/rpc/utils/index.ts new file mode 100644 index 000000000..d799db43f --- /dev/null +++ b/src/rpc/utils/index.ts @@ -0,0 +1,2 @@ +export * from './middleware'; +export * from './utils'; diff --git a/src/rpc/utils/middleware.ts b/src/rpc/utils/middleware.ts new file mode 100644 index 000000000..6398b2faa --- /dev/null +++ b/src/rpc/utils/middleware.ts @@ -0,0 +1,197 @@ +import type { + JSONRPCMessage, + JSONRPCRequest, + JSONRPCResponse, + JSONRPCResponseResult, + MiddlewareFactory, +} from '../types'; +import { TransformStream } from 'stream/web'; +import * as rpcUtils from './utils'; +import * as rpcErrors from '../errors'; +import { promise } from '../../utils/index'; +const jsonStreamParsers = require('@streamparser/json'); + +/** + * This function is a factory to create a TransformStream that will + * transform a `Uint8Array` stream to a JSONRPC message stream. + * The parsed messages will be validated with the provided messageParser, this + * also infers the type of the stream output. + * @param messageParser - Validates the JSONRPC messages, so you can select for a + * specific type of message + * @param byteLimit - sets the number of bytes buffered before throwing an + * error. This is used to avoid infinitely buffering the input. + */ +function binaryToJsonMessageStream( + messageParser: (message: unknown) => T, + byteLimit: number = 1024 * 1024, +): TransformStream { + const parser = new jsonStreamParsers.JSONParser({ + separator: '', + paths: ['$'], + }); + let bytesWritten: number = 0; + + return new TransformStream({ + flush: async () => { + // Avoid potential race conditions by allowing parser to end first + const waitP = promise(); + parser.onEnd = () => waitP.resolveP(); + parser.end(); + await waitP.p; + }, + start: (controller) => { + parser.onValue = (value) => { + const jsonMessage = messageParser(value.value); + controller.enqueue(jsonMessage); + bytesWritten = 0; + }; + }, + transform: (chunk) => { + try { + bytesWritten += chunk.byteLength; + parser.write(chunk); + } catch (e) { + throw new rpcErrors.ErrorRPCParse(undefined, { cause: e }); + } + if (bytesWritten > byteLimit) { + throw new rpcErrors.ErrorRPCMessageLength(); + } + }, + }); +} + +/** + * This function is a factory for a TransformStream that will transform + * JsonRPCMessages into the `Uint8Array` form. This is used for the stream + * output. + */ +function jsonMessageToBinaryStream(): TransformStream< + JSONRPCMessage, + Uint8Array +> { + return new TransformStream({ + transform: (chunk, controller) => { + controller.enqueue(Buffer.from(JSON.stringify(chunk))); + }, + }); +} + +/** + * This function is a factory for creating a pass-through streamPair. It is used + * as the default middleware for the middleware wrappers. + */ +function defaultMiddleware() { + return { + forward: new TransformStream(), + reverse: new TransformStream(), + }; +} + +/** + * This convenience factory for creating wrapping middleware with the basic + * message processing and parsing for the server middleware. + * In the forward path, it will transform the binary stream into the validated + * JsonRPCMessages and pipe it through the provided middleware. + * The reverse path will pipe the output stream through the provided middleware + * and then transform it back to a binary stream. + * @param middlewareFactory - The provided middleware + */ +function defaultServerMiddlewareWrapper( + middlewareFactory: MiddlewareFactory< + JSONRPCRequest, + JSONRPCRequest, + JSONRPCResponse, + JSONRPCResponse + > = defaultMiddleware, +): MiddlewareFactory { + return () => { + const inputTransformStream = binaryToJsonMessageStream( + rpcUtils.parseJSONRPCRequest, + ); + const outputTransformStream = new TransformStream< + JSONRPCResponseResult, + JSONRPCResponseResult + >(); + + const middleMiddleware = middlewareFactory(); + + const forwardReadable = inputTransformStream.readable.pipeThrough( + middleMiddleware.forward, + ); // Usual middleware here + const reverseReadable = outputTransformStream.readable + .pipeThrough(middleMiddleware.reverse) // Usual middleware here + .pipeThrough(jsonMessageToBinaryStream()); + + return { + forward: { + readable: forwardReadable, + writable: inputTransformStream.writable, + }, + reverse: { + readable: reverseReadable, + writable: outputTransformStream.writable, + }, + }; + }; +} + +/** + * This convenience factory for creating wrapping middleware with the basic + * message processing and parsing for the server middleware. + * The forward path will pipe the input through the provided middleware and then + * transform it to the binary stream. + * The reverse path will parse and validate the output and pipe it through the + * provided middleware. + * @param middleware - the provided middleware + */ +const defaultClientMiddlewareWrapper = ( + middleware: MiddlewareFactory< + JSONRPCRequest, + JSONRPCRequest, + JSONRPCResponse, + JSONRPCResponse + > = defaultMiddleware, +): MiddlewareFactory< + Uint8Array, + JSONRPCRequest, + JSONRPCResponse, + Uint8Array +> => { + return () => { + const outputTransformStream = binaryToJsonMessageStream( + rpcUtils.parseJSONRPCResponse, + // Undefined, + ); + const inputTransformStream = new TransformStream< + JSONRPCRequest, + JSONRPCRequest + >(); + + const middleMiddleware = middleware(); + const forwardReadable = inputTransformStream.readable + .pipeThrough(middleMiddleware.forward) // Usual middleware here + .pipeThrough(jsonMessageToBinaryStream()); + const reverseReadable = outputTransformStream.readable.pipeThrough( + middleMiddleware.reverse, + ); // Usual middleware here + + return { + forward: { + readable: forwardReadable, + writable: inputTransformStream.writable, + }, + reverse: { + readable: reverseReadable, + writable: outputTransformStream.writable, + }, + }; + }; +}; + +export { + binaryToJsonMessageStream, + jsonMessageToBinaryStream, + defaultMiddleware, + defaultServerMiddlewareWrapper, + defaultClientMiddlewareWrapper, +}; diff --git a/src/RPC/utils.ts b/src/rpc/utils/utils.ts similarity index 77% rename from src/RPC/utils.ts rename to src/rpc/utils/utils.ts index 1d5da7f6f..d353d4e12 100644 --- a/src/RPC/utils.ts +++ b/src/rpc/utils/utils.ts @@ -1,28 +1,29 @@ import type { ClientManifest, + ClientMetadata, HandlerType, - JsonRpcError, - JsonRpcMessage, - JsonRpcRequest, - JsonRpcRequestMessage, - JsonRpcRequestNotification, - JsonRpcResponse, - JsonRpcResponseError, - JsonRpcResponseResult, -} from 'RPC/types'; -import type { JSONValue } from '../types'; + JSONRPCError, + JSONRPCMessage, + JSONRPCRequest, + JSONRPCRequestMessage, + JSONRPCRequestNotification, + JSONRPCResponse, + JSONRPCResponseError, + JSONRPCResponseResult, +} from '../types'; +import type { JSONValue } from 'types'; import { TransformStream } from 'stream/web'; import { AbstractError } from '@matrixai/errors'; -import * as rpcErrors from './errors'; -import * as utils from '../utils'; -import { promise } from '../utils'; -import * as validationErrors from '../validation/errors'; -import * as errors from '../errors'; +import * as rpcErrors from '../errors'; +import * as utils from '../../utils/index'; +import { promise } from '../../utils/index'; +import * as validationErrors from '../../validation/errors'; +import * as errors from '../../errors'; const jsonStreamParsers = require('@streamparser/json'); -function parseJsonRpcRequest( +function parseJSONRPCRequest( message: unknown, -): JsonRpcRequest { +): JSONRPCRequest { if (!utils.isObject(message)) { throw new validationErrors.ErrorParse('must be a JSON POJO'); } @@ -35,13 +36,13 @@ function parseJsonRpcRequest( // If ('params' in message && !utils.isObject(message.params)) { // throw new validationErrors.ErrorParse('`params` property must be a POJO'); // } - return message as JsonRpcRequest; + return message as JSONRPCRequest; } -function parseJsonRpcRequestMessage( +function parseJSONRPCRequestMessage( message: unknown, -): JsonRpcRequestMessage { - const jsonRequest = parseJsonRpcRequest(message); +): JSONRPCRequestMessage { + const jsonRequest = parseJSONRPCRequest(message); if (!('id' in jsonRequest)) { throw new validationErrors.ErrorParse('`id` property must be defined'); } @@ -54,22 +55,22 @@ function parseJsonRpcRequestMessage( '`id` property must be a string, number or null', ); } - return jsonRequest as JsonRpcRequestMessage; + return jsonRequest as JSONRPCRequestMessage; } -function parseJsonRpcRequestNotification( +function parseJSONRPCRequestNotification( message: unknown, -): JsonRpcRequestNotification { - const jsonRequest = parseJsonRpcRequest(message); +): JSONRPCRequestNotification { + const jsonRequest = parseJSONRPCRequest(message); if ('id' in jsonRequest) { throw new validationErrors.ErrorParse('`id` property must not be defined'); } - return jsonRequest as JsonRpcRequestNotification; + return jsonRequest as JSONRPCRequestNotification; } -function parseJsonRpcResponseResult( +function parseJSONRPCResponseResult( message: unknown, -): JsonRpcResponseResult { +): JSONRPCResponseResult { if (!utils.isObject(message)) { throw new validationErrors.ErrorParse('must be a JSON POJO'); } @@ -96,10 +97,10 @@ function parseJsonRpcResponseResult( '`id` property must be a string, number or null', ); } - return message as JsonRpcResponseResult; + return message as JSONRPCResponseResult; } -function parseJsonRpcResponseError(message: unknown): JsonRpcResponseError { +function parseJSONRPCResponseError(message: unknown): JSONRPCResponseError { if (!utils.isObject(message)) { throw new validationErrors.ErrorParse('must be a JSON POJO'); } @@ -111,7 +112,7 @@ function parseJsonRpcResponseError(message: unknown): JsonRpcResponseError { if (!('error' in message)) { throw new validationErrors.ErrorParse('`error` property must be defined'); } - parseJsonRpcError(message.error); + parseJSONRPCError(message.error); if (!('id' in message)) { throw new validationErrors.ErrorParse('`id` property must be defined'); } @@ -124,10 +125,10 @@ function parseJsonRpcResponseError(message: unknown): JsonRpcResponseError { '`id` property must be a string, number or null', ); } - return message as JsonRpcResponseError; + return message as JSONRPCResponseError; } -function parseJsonRpcError(message: unknown): JsonRpcError { +function parseJSONRPCError(message: unknown): JSONRPCError { if (!utils.isObject(message)) { throw new validationErrors.ErrorParse('must be a JSON POJO'); } @@ -148,33 +149,33 @@ function parseJsonRpcError(message: unknown): JsonRpcError { // If ('data' in message && !utils.isObject(message.data)) { // throw new validationErrors.ErrorParse('`data` property must be a POJO'); // } - return message as JsonRpcError; + return message as JSONRPCError; } -function parseJsonRpcResponse( +function parseJSONRPCResponse( message: unknown, -): JsonRpcResponse { +): JSONRPCResponse { if (!utils.isObject(message)) { throw new validationErrors.ErrorParse('must be a JSON POJO'); } try { - return parseJsonRpcResponseResult(message); + return parseJSONRPCResponseResult(message); } catch (e) { // Do nothing } try { - return parseJsonRpcResponseError(message); + return parseJSONRPCResponseError(message); } catch (e) { // Do nothing } throw new validationErrors.ErrorParse( - 'structure did not match a `JsonRpcResponse`', + 'structure did not match a `JSONRPCResponse`', ); } -function parseJsonRpcMessage( +function parseJSONRPCMessage( message: unknown, -): JsonRpcMessage { +): JSONRPCMessage { if (!utils.isObject(message)) { throw new validationErrors.ErrorParse('must be a JSON POJO'); } @@ -187,17 +188,17 @@ function parseJsonRpcMessage( ); } try { - return parseJsonRpcRequest(message); + return parseJSONRPCRequest(message); } catch { // Do nothing } try { - return parseJsonRpcResponse(message); + return parseJSONRPCResponse(message); } catch { // Do nothing } throw new validationErrors.ErrorParse( - 'Message structure did not match a `JsonRpcMessage`', + 'Message structure did not match a `JSONRPCMessage`', ); } @@ -349,18 +350,33 @@ function reviver(key: string, value: any): any { } } -function toError(errorData) { - if (errorData == null) return new rpcErrors.ErrorRpcRemoteError(); - const error = JSON.parse(errorData, reviver); - return new rpcErrors.ErrorRpcRemoteError(error.message, { - cause: error, - }); +function toError( + errorData, + metadata: ClientMetadata, +): rpcErrors.ErrorPolykeyRemote { + if (errorData == null) { + return new rpcErrors.ErrorPolykeyRemote(metadata); + } + const error: Error = JSON.parse(errorData, reviver); + const remoteError = new rpcErrors.ErrorPolykeyRemote( + metadata, + error.message, + { + cause: error, + }, + ); + if (error instanceof errors.ErrorPolykey) { + remoteError.exitCode = error.exitCode; + } + return remoteError; } -function clientInputTransformStream(method: string) { - return new TransformStream({ +function clientInputTransformStream( + method: string, +): TransformStream { + return new TransformStream({ transform: (chunk, controller) => { - const message: JsonRpcRequest = { + const message: JSONRPCRequest = { method, jsonrpc: '2.0', id: null, @@ -371,42 +387,27 @@ function clientInputTransformStream(method: string) { }); } -function clientOutputTransformStream() { - return new TransformStream, O>({ +function clientOutputTransformStream( + clientMetadata: ClientMetadata, +): TransformStream, O> { + return new TransformStream, O>({ transform: (chunk, controller) => { + // `error` indicates it's an error message if ('error' in chunk) { - throw toError(chunk.error.data); + throw toError(chunk.error.data, clientMetadata); } controller.enqueue(chunk.result); }, }); } -function isReturnableError(e: Error): boolean { - return !(e instanceof rpcErrors.ErrorRpcNoMessageError); -} - -class RPCErrorEvent extends Event { - public detail: { - error: any; - }; - - constructor( - options: EventInit & { - detail: { - error: any; - }; - }, - ) { - super('error', options); - this.detail = options.detail; - } -} - -function extractFirstMessageTransform( +function extractFirstMessageTransform( messageParser: (message: unknown) => T, byteLimit: number = 1024 * 1024, -) { +): { + headTransformStream: TransformStream; + firstMessageProm: Promise; +} { const parser = new jsonStreamParsers.JSONParser({ separator: '', paths: ['$'], @@ -422,7 +423,7 @@ function extractFirstMessageTransform( try { jsonMessage = messageParser(value.value); } catch (e) { - const error = new rpcErrors.ErrorRpcParse(undefined, { cause: e }); + const error = new rpcErrors.ErrorRPCParse(undefined, { cause: e }); messageProm.rejectP(error); controller.error(error); return; @@ -454,7 +455,7 @@ function extractFirstMessageTransform( // Ignore error } if (bytesWritten > byteLimit) { - messageProm.rejectP(new rpcErrors.ErrorRpcMessageLength()); + messageProm.rejectP(new rpcErrors.ErrorRPCMessageLength()); } }, flush: () => { @@ -475,19 +476,17 @@ function getHandlerTypes( } export { - parseJsonRpcRequest, - parseJsonRpcRequestMessage, - parseJsonRpcRequestNotification, - parseJsonRpcResponseResult, - parseJsonRpcResponseError, - parseJsonRpcResponse, - parseJsonRpcMessage, + parseJSONRPCRequest, + parseJSONRPCRequestMessage, + parseJSONRPCRequestNotification, + parseJSONRPCResponseResult, + parseJSONRPCResponseError, + parseJSONRPCResponse, + parseJSONRPCMessage, fromError, toError, clientInputTransformStream, clientOutputTransformStream, - isReturnableError, - RPCErrorEvent, extractFirstMessageTransform, getHandlerTypes, }; diff --git a/src/websockets/WebSocketClient.ts b/src/websockets/WebSocketClient.ts index cc7be82c6..d8014b22c 100644 --- a/src/websockets/WebSocketClient.ts +++ b/src/websockets/WebSocketClient.ts @@ -7,8 +7,8 @@ import WebSocket from 'ws'; import { Timer } from '@matrixai/timer'; import { Validator } from 'ip-num'; import WebSocketStream from './WebSocketStream'; -import * as clientRpcUtils from './utils'; -import * as clientRPCErrors from './errors'; +import * as webSocketUtils from './utils'; +import * as webSocketErrors from './errors'; import { promise } from '../utils'; const timeoutSymbol = Symbol('TimedOutSymbol'); @@ -68,7 +68,7 @@ class WebSocketClient { } else if (Validator.isValidIPv6String(host)[0]) { this.host = `[${host}]`; } else { - throw new clientRPCErrors.ErrorClientInvalidHost(); + throw new webSocketErrors.ErrorClientInvalidHost(); } } @@ -85,7 +85,17 @@ class WebSocketClient { this.logger.info(`Destroyed ${this.constructor.name}`); } - @createDestroy.ready(new clientRPCErrors.ErrorClientDestroyed()) + @createDestroy.ready(new webSocketErrors.ErrorClientDestroyed()) + public async stopConnections() { + for (const activeConnection of this.activeConnections) { + activeConnection.end(); + } + for (const activeConnection of this.activeConnections) { + await activeConnection.endedProm.catch(() => {}); // Ignore errors here + } + } + + @createDestroy.ready(new webSocketErrors.ErrorClientDestroyed()) public async startConnection({ timeoutTimer, }: { @@ -109,7 +119,7 @@ class WebSocketClient { // Handle connection failure const openErrorHandler = (e) => { connectProm.rejectP( - new clientRPCErrors.ErrorClientConnectionFailed(undefined, { + new webSocketErrors.ErrorClientConnectionFailed(undefined, { cause: e, }), ); @@ -119,10 +129,10 @@ class WebSocketClient { ws.once('upgrade', async (request) => { const tlsSocket = request.socket as TLSSocket; const peerCert = tlsSocket.getPeerCertificate(true); - clientRpcUtils + webSocketUtils .verifyServerCertificateChain( this.expectedNodeIds, - clientRpcUtils.detailedToCertChain(peerCert), + webSocketUtils.detailedToCertChain(peerCert), ) .then(authenticateProm.resolveP, authenticateProm.rejectP); }); @@ -144,7 +154,7 @@ class WebSocketClient { await Promise.all([authenticateProm.p, connectProm.p]), ]); if (result === timeoutSymbol) { - throw new clientRPCErrors.ErrorClientConnectionTimedOut(); + throw new webSocketErrors.ErrorClientConnectionTimedOut(); } } catch (e) { // Clean up @@ -234,7 +244,7 @@ class WebSocketStreamClientInternal extends WebSocketStream { readableLogger.debug( `Closed early, ${code}, ${reason.toString()}`, ); - const e = new clientRPCErrors.ErrorClientConnectionEndedEarly(); + const e = new webSocketErrors.ErrorClientConnectionEndedEarly(); this.signalReadableEnd(e); controller.error(e); } @@ -247,11 +257,12 @@ class WebSocketStreamClientInternal extends WebSocketStream { } }); }, - cancel: () => { + cancel: (reason) => { readableLogger.debug('Cancelled'); + this.signalReadableEnd(reason); if (!this.writableEnded_) { readableLogger.debug('Closing socket'); - this.signalReadableEnd(); + this.signalWritableEnd(reason); ws.close(); } }, @@ -278,7 +289,7 @@ class WebSocketStreamClientInternal extends WebSocketStream { ws.once('close', (code, reason) => { if (!this.writableEnded_) { writableLogger.debug(`Closed early, ${code}, ${reason.toString()}`); - const e = new clientRPCErrors.ErrorClientConnectionEndedEarly(); + const e = new webSocketErrors.ErrorClientConnectionEndedEarly(); this.signalWritableEnd(e); controller.error(e); } @@ -293,9 +304,9 @@ class WebSocketStreamClientInternal extends WebSocketStream { ws.close(); } }, - abort: () => { + abort: (reason) => { writableLogger.debug('Aborted'); - this.signalWritableEnd(Error('TMP ABORTED')); + this.signalWritableEnd(reason); if (this.readableEnded_) { writableLogger.debug('Closing socket'); ws.close(); @@ -310,7 +321,7 @@ class WebSocketStreamClientInternal extends WebSocketStream { // Opting to debug message here and not log an error, sending // failure is common if we send before the close event. writableLogger.debug('failed to send'); - const err = new clientRPCErrors.ErrorClientConnectionEndedEarly( + const err = new webSocketErrors.ErrorClientConnectionEndedEarly( undefined, { cause: e, @@ -345,7 +356,9 @@ class WebSocketStreamClientInternal extends WebSocketStream { logger.debug('WebSocket closed'); const err = code !== 1000 - ? Error(`TMP WebSocket ended with code ${code}, ${reason.toString()}`) + ? new webSocketErrors.ErrorClientConnectionEndedEarly( + `ended with code ${code}, ${reason.toString()}`, + ) : undefined; this.signalWebSocketEnd(err); logger.debug('Cleaning up timers'); @@ -356,7 +369,7 @@ class WebSocketStreamClientInternal extends WebSocketStream { } end(): void { - this.ws.close(4001, 'TMP ENDING CONNECTION'); + this.ws.close(4001, 'Ending connection'); } } diff --git a/src/websockets/WebSocketServer.ts b/src/websockets/WebSocketServer.ts index 5ab3a80c0..13c34c543 100644 --- a/src/websockets/WebSocketServer.ts +++ b/src/websockets/WebSocketServer.ts @@ -4,14 +4,14 @@ import type { WritableStreamDefaultController, } from 'stream/web'; import type { FileSystem, PromiseDeconstructed } from 'types'; -import type { TLSConfig } from 'network/types'; +import type { Host, Port, TLSConfig } from 'network/types'; import type { HttpRequest, HttpResponse, us_socket_context_t, WebSocket, } from 'uWebSockets.js'; -import type { ConnectionInfo } from '../RPC/types'; +import type { ConnectionInfo } from '../rpc/types'; import { WritableStream, ReadableStream } from 'stream/web'; import path from 'path'; import os from 'os'; @@ -19,7 +19,7 @@ import { startStop } from '@matrixai/async-init'; import Logger from '@matrixai/logger'; import uWebsocket from 'uWebSockets.js'; import WebSocketStream from './WebSocketStream'; -import * as clientRPCErrors from './errors'; +import * as webSocketErrors from './errors'; import * as webSocketEvents from './events'; import { promise } from '../utils'; @@ -96,7 +96,8 @@ class WebSocketServer extends EventTarget { protected server: uWebsocket.TemplatedApp; protected listenSocket: uWebsocket.us_listen_socket; - protected host: string; + protected _port: number; + protected _host: string; protected connectionEventHandler: ( event: webSocketEvents.ConnectionEvent, ) => void; @@ -175,7 +176,7 @@ class WebSocketServer extends EventTarget { this.listenSocket = listenSocket; listenProm.resolveP(); } else { - listenProm.rejectP(new clientRPCErrors.ErrorServerPortUnavailable()); + listenProm.rejectP(new webSocketErrors.ErrorServerPortUnavailable()); } }; if (host != null) { @@ -186,15 +187,14 @@ class WebSocketServer extends EventTarget { this.server.listen(port, listenCallback); } await listenProm.p; - this.logger.debug( - `Listening on port ${uWebsocket.us_socket_local_port(this.listenSocket)}`, - ); - this.host = host ?? '127.0.0.1'; + this._port = uWebsocket.us_socket_local_port(this.listenSocket); + this.logger.debug(`Listening on port ${this._port}`); + this._host = host ?? '127.0.0.1'; this.dispatchEvent( new webSocketEvents.StartEvent({ detail: { - host: this.host, - port: this.port, + host: this._host, + port: this._port, }, }), ); @@ -222,8 +222,14 @@ class WebSocketServer extends EventTarget { this.logger.info(`Stopped ${this.constructor.name}`); } - get port() { - return uWebsocket.us_socket_local_port(this.listenSocket); + @startStop.ready(new webSocketErrors.ErrorWebSocketServerNotRunning()) + public getPort(): Port { + return this._port as Port; + } + + @startStop.ready(new webSocketErrors.ErrorWebSocketServerNotRunning()) + public getHost(): Host { + return this._host as Host; } /** @@ -237,9 +243,9 @@ class WebSocketServer extends EventTarget { // TODO: The key file needs to be in the encrypted format const keyFile = path.join(tmpDir, 'keyFile.pem'); const certFile = path.join(tmpDir, 'certFile.pem'); + await this.fs.promises.writeFile(keyFile, tlsConfig.keyPrivatePem); + await this.fs.promises.writeFile(certFile, tlsConfig.certChainPem); try { - await this.fs.promises.writeFile(keyFile, tlsConfig.keyPrivatePem); - await this.fs.promises.writeFile(certFile, tlsConfig.certChainPem); this.server = uWebsocket.SSLApp({ key_file_name: keyFile, cert_file_name: certFile, @@ -247,6 +253,7 @@ class WebSocketServer extends EventTarget { } finally { await this.fs.promises.rm(keyFile); await this.fs.promises.rm(certFile); + await this.fs.promises.rm(tmpDir, { recursive: true, force: true }); } } @@ -295,8 +302,8 @@ class WebSocketServer extends EventTarget { // port from the `uWebsocket` library. const connectionInfo: ConnectionInfo = { remoteHost: Buffer.from(ws.getRemoteAddressAsText()).toString(), - localHost: this.host, - localPort: this.port, + localHost: this._host, + localPort: this._port, }; this.dispatchEvent( new webSocketEvents.ConnectionEvent({ @@ -368,7 +375,7 @@ class WebSocketStreamServerInternal extends WebSocketStream { case 2: // Write failure, emit error writableLogger.error('Send error'); - controller.error(new clientRPCErrors.ErrorServerSendFailed()); + controller.error(new webSocketErrors.ErrorServerSendFailed()); break; case 0: writableLogger.info('Write backpressure'); @@ -395,11 +402,11 @@ class WebSocketStreamServerInternal extends WebSocketStream { ws.end(); } }, - abort: () => { + abort: (reason) => { writableLogger.info('Aborted'); if (this.readableEnded_ && !this.webSocketEnded_) { writableLogger.debug('Ending socket'); - this.signalWebSocketEnd(Error('TMP ERROR ABORTED')); + this.signalWebSocketEnd(reason); ws.end(4001, 'ABORTED'); } }, @@ -429,7 +436,7 @@ class WebSocketStreamServerInternal extends WebSocketStream { controller.enqueue(messageBuffer); if (controller.desiredSize != null && controller.desiredSize < 0) { readableLogger.error('Read stream buffer full'); - const err = new clientRPCErrors.ErrorServerReadableBufferLimit(); + const err = new webSocketErrors.ErrorServerReadableBufferLimit(); if (!this.webSocketEnded_) { this.signalWebSocketEnd(err); ws.end(4001, 'Read stream buffer full'); @@ -438,8 +445,8 @@ class WebSocketStreamServerInternal extends WebSocketStream { } }; }, - cancel: () => { - this.signalReadableEnd(Error('TMP READABLE CANCELLED')); + cancel: (reason) => { + this.signalReadableEnd(reason); if (this.writableEnded_ && !this.webSocketEnded_) { readableLogger.debug('Ending socket'); this.signalWebSocketEnd(); @@ -473,7 +480,7 @@ class WebSocketStreamServerInternal extends WebSocketStream { clearTimeout(pingTimeoutTimer); // Closing streams logger.debug('Cleaning streams'); - const err = new clientRPCErrors.ErrorServerConnectionEndedEarly(); + const err = new webSocketErrors.ErrorServerConnectionEndedEarly(); if (!this.readableEnded_) { readableLogger.debug('Closing'); this.signalReadableEnd(err); @@ -492,7 +499,7 @@ class WebSocketStreamServerInternal extends WebSocketStream { } end(): void { - this.ws.end(4001, 'TMP ENDING CONNECTION'); + this.ws.end(4001, 'Ending connection'); } } diff --git a/src/websockets/WebSocketStream.ts b/src/websockets/WebSocketStream.ts index a91d63a81..604302aa0 100644 --- a/src/websockets/WebSocketStream.ts +++ b/src/websockets/WebSocketStream.ts @@ -36,7 +36,7 @@ abstract class WebSocketStream result[2].status === 'rejected' ) { // Throw a compound error - throw Error('TMP Stream failed', { cause: result }); + throw AggregateError(result, 'stream failed'); } // Otherwise return nothing }); diff --git a/src/websockets/errors.ts b/src/websockets/errors.ts index 002282359..290a7ed64 100644 --- a/src/websockets/errors.ts +++ b/src/websockets/errors.ts @@ -31,6 +31,11 @@ class ErrorClientConnectionEndedEarly extends ErrorWebSocketClient { class ErrorWebSocketServer extends ErrorWebSocket {} +class ErrorWebSocketServerNotRunning extends ErrorWebSocketServer { + static description = 'WebSocketServer is not running'; + exitCode = sysexits.USAGE; +} + class ErrorServerPortUnavailable extends ErrorWebSocketServer { static description = 'Failed to bind a free port'; exitCode = sysexits.UNAVAILABLE; @@ -104,6 +109,7 @@ export { ErrorClientConnectionTimedOut, ErrorClientConnectionEndedEarly, ErrorWebSocketServer, + ErrorWebSocketServerNotRunning, ErrorServerPortUnavailable, ErrorServerSendFailed, ErrorServerReadableBufferLimit, diff --git a/src/websockets/events.ts b/src/websockets/events.ts index aaabb5842..b43b75f33 100644 --- a/src/websockets/events.ts +++ b/src/websockets/events.ts @@ -1,5 +1,5 @@ import type WebSocketStream from 'websockets/WebSocketStream'; -import type { ConnectionInfo } from 'RPC/types'; +import type { ConnectionInfo } from 'rpc/types'; class StartEvent extends Event { public detail: { diff --git a/tests/PolykeyClient.test.ts b/tests/PolykeyClient.test.ts index b796bff08..fae5984e9 100644 --- a/tests/PolykeyClient.test.ts +++ b/tests/PolykeyClient.test.ts @@ -7,6 +7,7 @@ import { PolykeyClient, PolykeyAgent } from '@'; import { Session } from '@/sessions'; import config from '@/config'; import * as keysUtils from '@/keys/utils/index'; +import WebSocketClient from '@/websockets/WebSocketClient'; describe('PolykeyClient', () => { const password = 'password'; @@ -39,23 +40,6 @@ describe('PolykeyClient', () => { recursive: true, }); }); - test('create PolykeyClient and connect to PolykeyAgent', async () => { - const pkClient = await PolykeyClient.createPolykeyClient({ - nodeId: pkAgent.keyRing.getNodeId(), - host: pkAgent.grpcServerClient.getHost(), - port: pkAgent.grpcServerClient.getPort(), - nodePath, - fs, - logger, - }); - expect(pkClient.grpcClient.nodeId).toStrictEqual( - pkAgent.keyRing.getNodeId(), - ); - 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(); - }); test('preserving and destroying session state', async () => { const session = await Session.createSession({ sessionTokenPath: path.join(nodePath, config.defaults.tokenBase), @@ -64,14 +48,19 @@ describe('PolykeyClient', () => { }); await session.writeToken('dummy' as SessionToken); // Using fresh: true means that any token would be destroyed + const webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [pkAgent.keyRing.getNodeId()], + host: pkAgent.webSocketServerClient.getHost(), + port: pkAgent.webSocketServerClient.getPort(), + logger, + }); const pkClient = await PolykeyClient.createPolykeyClient({ - nodeId: pkAgent.keyRing.getNodeId(), - host: pkAgent.grpcServerClient.getHost(), - port: pkAgent.grpcServerClient.getPort(), + streamFactory: () => webSocketClient.startConnection(), nodePath, fs, logger, fresh: true, + manifest: {}, }); expect(await session.readToken()).toBeUndefined(); await session.writeToken('abc' as SessionToken); @@ -79,5 +68,6 @@ describe('PolykeyClient', () => { expect(await session.readToken()).toBeDefined(); await pkClient.destroy(); expect(await session.readToken()).toBeUndefined(); + await webSocketClient.destroy(true); }); }); diff --git a/tests/agent/service/nodesCrossSignClaim.test.ts b/tests/agent/service/nodesCrossSignClaim.test.ts index 676156992..3a2b03642 100644 --- a/tests/agent/service/nodesCrossSignClaim.test.ts +++ b/tests/agent/service/nodesCrossSignClaim.test.ts @@ -179,7 +179,7 @@ describe('nodesCrossSignClaim', () => { const crossSignMessageUndefinedSingly = new nodesPB.AgentClaim(); await genClaims.write(crossSignMessageUndefinedSingly); await expect(() => genClaims.read()).rejects.toThrow( - grpcErrors.ErrorPolykeyRemote, + grpcErrors.ErrorPolykeyRemoteOLD, ); expect(genClaims.stream.destroyed).toBe(true); }); diff --git a/tests/agent/service/notificationsSend.test.ts b/tests/agent/service/notificationsSend.test.ts index daf76a9e8..65698dedb 100644 --- a/tests/agent/service/notificationsSend.test.ts +++ b/tests/agent/service/notificationsSend.test.ts @@ -237,7 +237,7 @@ describe('notificationsSend', () => { const token = Token.fromPayload(notification1); const request1 = new notificationsPB.AgentNotification(); request1.setContent(JSON.stringify(token.toJSON())); - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( grpcClient.notificationsSend(request1), notificationsErrors.ErrorNotificationsVerificationFailed, ); @@ -259,7 +259,7 @@ describe('notificationsSend', () => { ); const request2 = new notificationsPB.AgentNotification(); request2.setContent(signedNotification); - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( grpcClient.notificationsSend(request2), validationErrors.ErrorParse, ); @@ -287,7 +287,7 @@ describe('notificationsSend', () => { ); const request = new notificationsPB.AgentNotification(); request.setContent(signedNotification); - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( grpcClient.notificationsSend(request), notificationsErrors.ErrorNotificationsPermissionsNotFound, ); diff --git a/tests/bin/agent/lockall.test.ts b/tests/bin/agent/lockall.test.ts index 7eaf3356f..2a9519fdd 100644 --- a/tests/bin/agent/lockall.test.ts +++ b/tests/bin/agent/lockall.test.ts @@ -55,7 +55,7 @@ describe('lockall', () => { await session.stop(); }); testUtils.testIf(testUtils.isTestPlatformEmpty)( - 'lockall ensures reauthentication is required', + 'lockall ensures re-authentication is required', async () => { const password = agentPassword; await testUtils.pkStdio(['agent', 'unlock'], { @@ -69,7 +69,7 @@ describe('lockall', () => { env: { PK_NODE_PATH: agentDir }, cwd: agentDir, }); - // Token is deleted, reauthentication is required + // Token is deleted, re-authentication is required mockedPrompts.mockClear(); mockedPrompts.mockImplementation(async (_opts: any) => { return { password }; @@ -83,46 +83,46 @@ describe('lockall', () => { mockedPrompts.mockClear(); }, ); - testUtils.testIf( - testUtils.isTestPlatformEmpty || testUtils.isTestPlatformDocker, - )('lockall causes old session tokens to fail', async () => { - await testUtils.pkExec(['agent', 'unlock'], { - env: { - PK_NODE_PATH: agentDir, - PK_PASSWORD: agentPassword, - }, - cwd: agentDir, - command: globalThis.testCmd, - }); - const session = await Session.createSession({ - sessionTokenPath: path.join(agentDir, config.defaults.tokenBase), - fs, - logger, - }); - const token = await session.readToken(); - await session.stop(); - await testUtils.pkExec(['agent', 'lockall'], { - env: { - PK_NODE_PATH: agentDir, - PK_PASSWORD: agentPassword, - }, - cwd: agentDir, - command: globalThis.testCmd, - }); - // Old token is invalid - const { exitCode, stderr } = await testUtils.pkExec( - ['agent', 'status', '--format', 'json'], - { + testUtils + .testIf(testUtils.isTestPlatformEmpty || testUtils.isTestPlatformDocker) + .only('lockall causes old session tokens to fail', async () => { + await testUtils.pkExec(['agent', 'unlock'], { env: { PK_NODE_PATH: agentDir, - PK_TOKEN: token, + PK_PASSWORD: agentPassword, }, cwd: agentDir, command: globalThis.testCmd, - }, - ); - testUtils.expectProcessError(exitCode, stderr, [ - new errors.ErrorClientAuthDenied(), - ]); - }); + }); + const session = await Session.createSession({ + sessionTokenPath: path.join(agentDir, config.defaults.tokenBase), + fs, + logger, + }); + const token = await session.readToken(); + await session.stop(); + await testUtils.pkExec(['agent', 'lockall'], { + env: { + PK_NODE_PATH: agentDir, + PK_PASSWORD: agentPassword, + }, + cwd: agentDir, + command: globalThis.testCmd, + }); + // Old token is invalid + const { exitCode, stderr } = await testUtils.pkExec( + ['agent', 'status', '--format', 'json'], + { + env: { + PK_NODE_PATH: agentDir, + PK_TOKEN: token, + }, + cwd: agentDir, + command: globalThis.testCmd, + }, + ); + testUtils.expectProcessError(exitCode, stderr, [ + new errors.ErrorClientAuthDenied(), + ]); + }); }); diff --git a/tests/bin/agent/start.test.ts b/tests/bin/agent/start.test.ts index 05b4e9da0..d02dda160 100644 --- a/tests/bin/agent/start.test.ts +++ b/tests/bin/agent/start.test.ts @@ -380,14 +380,18 @@ describe('start', () => { }); const errorStatusLocked = new statusErrors.ErrorStatusLocked(); // It's either the first or second process - if (index === 0) { - testUtils.expectProcessError(exitCode!, stdErrLine1, [ - errorStatusLocked, - ]); + try { + if (index === 0) { + testUtils.expectProcessError(exitCode!, stdErrLine1, [ + errorStatusLocked, + ]); + } else { + testUtils.expectProcessError(exitCode!, stdErrLine2, [ + errorStatusLocked, + ]); + } + } finally { bootstrapProcess.kill('SIGTERM'); - testUtils.expectProcessError(exitCode!, stdErrLine2, [ - errorStatusLocked, - ]); agentProcess.kill('SIGTERM'); } }, diff --git a/tests/bin/agent/status.test.ts b/tests/bin/agent/status.test.ts index 176e5bb06..d0721c3a2 100644 --- a/tests/bin/agent/status.test.ts +++ b/tests/bin/agent/status.test.ts @@ -184,7 +184,7 @@ describe('status', () => { agentPort: expect.any(Number), forwardHost: expect.any(String), forwardPort: expect.any(Number), - publicKeyJWK: expect.any(String), + publicKeyJWK: expect.any(Object), certChainPEM: expect.any(String), }); }); @@ -238,7 +238,7 @@ describe('status', () => { agentPort: expect.any(Number), forwardHost: expect.any(String), forwardPort: expect.any(Number), - publicKeyJWK: expect.any(String), + publicKeyJWK: expect.any(Object), certChainPEM: expect.any(String), }); }); diff --git a/tests/bin/keys/renew.test.ts b/tests/bin/keys/renew.test.ts index 46a12c176..9d96d5d5e 100644 --- a/tests/bin/keys/renew.test.ts +++ b/tests/bin/keys/renew.test.ts @@ -1,9 +1,11 @@ import type { Host } from '@/network/types'; +import type { NodeId } from '@/ids'; 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 * as nodesUtils from '@/nodes/utils'; import * as testUtils from '../../utils'; describe('renew', () => { @@ -12,6 +14,7 @@ describe('renew', () => { let dataDir: string; let nodePath: string; let pkAgent: PolykeyAgent; + let oldNodeId: NodeId; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( path.join(globalThis.tmpDir, 'polykey-test-'), @@ -33,6 +36,7 @@ describe('renew', () => { strictMemoryLock: false, }, }); + oldNodeId = pkAgent.keyRing.getNodeId(); }, globalThis.defaultTimeout * 2); afterEach(async () => { await pkAgent.stop(); @@ -94,6 +98,11 @@ describe('renew', () => { PK_NODE_PATH: nodePath, PK_PASSWORD: 'password-new', PK_PASSWORD_NEW: 'some-password', + // Client server still using old nodeId, this should be removed if + // this is fixed. + PK_NODE_ID: nodesUtils.encodeNodeId(oldNodeId), + PK_CLIENT_HOST: '127.0.0.1', + PK_CLIENT_PORT: `${pkAgent.webSocketServerClient.getPort()}`, }, cwd: dataDir, }, @@ -107,6 +116,11 @@ describe('renew', () => { env: { PK_NODE_PATH: nodePath, PK_PASSWORD: 'password-new', + // Client server still using old nodeId, this should be removed if + // this is fixed. + PK_NODE_ID: nodesUtils.encodeNodeId(oldNodeId), + PK_CLIENT_HOST: '127.0.0.1', + PK_CLIENT_PORT: `${pkAgent.webSocketServerClient.getPort()}`, }, cwd: dataDir, }, diff --git a/tests/bin/keys/reset.test.ts b/tests/bin/keys/reset.test.ts index ce956dde6..977783f0e 100644 --- a/tests/bin/keys/reset.test.ts +++ b/tests/bin/keys/reset.test.ts @@ -1,9 +1,11 @@ import type { Host } from '@/network/types'; +import type { NodeId } from '@/ids'; 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 * as nodesUtils from '@/nodes/utils'; import * as testUtils from '../../utils'; describe('reset', () => { @@ -12,6 +14,7 @@ describe('reset', () => { let dataDir: string; let nodePath: string; let pkAgent: PolykeyAgent; + let oldNodeId: NodeId; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( path.join(globalThis.tmpDir, 'polykey-test-'), @@ -33,6 +36,7 @@ describe('reset', () => { strictMemoryLock: false, }, }); + oldNodeId = pkAgent.keyRing.getNodeId(); }, globalThis.defaultTimeout * 2); afterEach(async () => { await pkAgent.stop(); @@ -87,7 +91,6 @@ describe('reset', () => { )); expect(exitCode).toBe(0); // Get new keypair and nodeId and compare against old - // FIXME, this is still on the old password for some reason ({ exitCode, stdout } = await testUtils.pkStdio( ['keys', 'keypair', '--format', 'json'], { @@ -95,6 +98,11 @@ describe('reset', () => { PK_NODE_PATH: nodePath, PK_PASSWORD: 'password-new', PK_PASSWORD_NEW: 'some-password', + // Client server still using old nodeId, this should be removed if + // this is fixed. + PK_NODE_ID: nodesUtils.encodeNodeId(oldNodeId), + PK_CLIENT_HOST: '127.0.0.1', + PK_CLIENT_PORT: `${pkAgent.webSocketServerClient.getPort()}`, }, cwd: dataDir, }, @@ -108,6 +116,11 @@ describe('reset', () => { env: { PK_NODE_PATH: nodePath, PK_PASSWORD: 'password-new', + // Client server still using old nodeId, this should be removed if + // this is fixed. + PK_NODE_ID: nodesUtils.encodeNodeId(oldNodeId), + PK_CLIENT_HOST: '127.0.0.1', + PK_CLIENT_PORT: `${pkAgent.webSocketServerClient.getPort()}`, }, cwd: dataDir, }, diff --git a/tests/bin/utils.retryAuthentication.test.ts b/tests/bin/utils.retryAuthentication.test.ts index 98c90b57b..03904f272 100644 --- a/tests/bin/utils.retryAuthentication.test.ts +++ b/tests/bin/utils.retryAuthentication.test.ts @@ -1,7 +1,8 @@ import prompts from 'prompts'; import { mocked } from 'jest-mock'; import mockedEnv from 'mocked-env'; -import { utils as clientUtils, errors as clientErrors } from '@/client'; +import * as clientUtils from '@/client/utils'; +import * as clientErrors from '@/client/errors'; import * as binUtils from '@/bin/utils'; import * as testUtils from '../utils'; @@ -108,11 +109,9 @@ describe('bin/utils retryAuthentication', () => { // Prompted for password 1 time expect(mockedPrompts.mock.calls.length).toBe(1); // Authorization metadata was set - const auth = mockCall.mock.calls[1][0].get('Authorization')[0]; + const auth = mockCall.mock.calls[1][0].authorization; expect(auth).toBeDefined(); - expect(auth).toBe( - clientUtils.encodeAuthFromPassword(password).get('Authorization')[0], - ); + expect(auth).toBe(clientUtils.encodeAuthFromPassword(password)); mockedPrompts.mockClear(); }, ); @@ -145,12 +144,10 @@ describe('bin/utils retryAuthentication', () => { // Prompted for password 2 times expect(mockedPrompts.mock.calls.length).toBe(2); // Authorization metadata was set - const auth = mockCall.mock.calls[2][0].get('Authorization')[0]; + const auth = mockCall.mock.calls[2][0].authorization; expect(auth).toBeDefined(); // Second password succeeded - expect(auth).toBe( - clientUtils.encodeAuthFromPassword(password2).get('Authorization')[0], - ); + expect(auth).toBe(clientUtils.encodeAuthFromPassword(password2)); mockedPrompts.mockClear(); }, ); @@ -182,12 +179,10 @@ describe('bin/utils retryAuthentication', () => { envRestore(); expect(mockCall.mock.calls.length).toBe(5); expect(mockedPrompts.mock.calls.length).toBe(4); - const auth = mockCall.mock.calls[4][0].get('Authorization')[0]; + const auth = mockCall.mock.calls[4][0].authorization; expect(auth).toBeDefined(); // Second password was the last used - expect(auth).toBe( - clientUtils.encodeAuthFromPassword(password2).get('Authorization')[0], - ); + expect(auth).toBe(clientUtils.encodeAuthFromPassword(password2)); mockedPrompts.mockClear(); }, ); diff --git a/tests/bin/utils.test.ts b/tests/bin/utils.test.ts index 7093d850f..f3c8bbbc6 100644 --- a/tests/bin/utils.test.ts +++ b/tests/bin/utils.test.ts @@ -97,7 +97,7 @@ describe('bin/utils', () => { timestamp, data, }); - const remoteError = new grpcErrors.ErrorPolykeyRemote( + const remoteError = new grpcErrors.ErrorPolykeyRemoteOLD( { nodeId, host, @@ -107,7 +107,7 @@ describe('bin/utils', () => { 'some remote error', { timestamp, cause: pkError }, ); - const twoRemoteErrors = new grpcErrors.ErrorPolykeyRemote( + const twoRemoteErrors = new grpcErrors.ErrorPolykeyRemoteOLD( { nodeId, host, @@ -117,7 +117,7 @@ describe('bin/utils', () => { 'remote error', { timestamp, - cause: new grpcErrors.ErrorPolykeyRemote( + cause: new grpcErrors.ErrorPolykeyRemoteOLD( { nodeId, host, diff --git a/tests/client/GRPCClientClient.test.ts b/tests/client/GRPCClientClient.test.ts deleted file mode 100644 index 027771691..000000000 --- a/tests/client/GRPCClientClient.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Host, Port } from '@/network/types'; -import type { NodeId } from '@/ids/types'; -import os from 'os'; -import path from 'path'; -import fs from 'fs'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import PolykeyAgent from '@/PolykeyAgent'; -import Session from '@/sessions/Session'; -import * as clientErrors from '@/client/errors'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import { timerStart } from '@/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testClientUtils from './utils'; - -describe(GRPCClientClient.name, () => { - const password = 'password'; - const logger = new Logger(`${GRPCClientClient.name} test`, LogLevel.WARN, [ - new StreamHandler(), - ]); - let client: GRPCClientClient; - let server: grpc.Server; - let port: number; - let pkAgent: PolykeyAgent; - let dataDir: string; - let nodePath: string; - let nodeId: NodeId; - let session: Session; - beforeAll(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - nodePath = path.join(dataDir, 'node'); - pkAgent = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath, - logger: logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, - }); - nodeId = pkAgent.keyRing.getNodeId(); - [server, port] = await testClientUtils.openTestClientServer({ - pkAgent, - }); - const sessionTokenPath = path.join(nodePath, 'sessionToken'); - const session = new Session({ sessionTokenPath, fs, logger }); - const sessionToken = await pkAgent.sessionManager.createToken(); - await session.start({ - sessionToken, - }); - }); - afterAll(async () => { - await client.destroy(); - await testClientUtils.closeTestClientServer(server); - await pkAgent.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('cannot be called when destroyed', 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, - timer: timerStart(10000), - session: session, - }); - await client.destroy(); - await expect(async () => { - await client.agentStatus(new utilsPB.EmptyMessage()); - }).rejects.toThrow(clientErrors.ErrorClientClientDestroyed); - }); -}); diff --git a/tests/clientRPC/authenticationMiddleware.test.ts b/tests/client/authenticationMiddleware.test.ts similarity index 71% rename from tests/clientRPC/authenticationMiddleware.test.ts rename to tests/client/authenticationMiddleware.test.ts index e204dda20..24835c22b 100644 --- a/tests/clientRPC/authenticationMiddleware.test.ts +++ b/tests/client/authenticationMiddleware.test.ts @@ -1,4 +1,7 @@ -import type { RPCRequestParams, RPCResponseResult } from '@/clientRPC/types'; +import type { + ClientRPCRequestParams, + ClientRPCResponseResult, +} from '@/client/types'; import type { TLSConfig } from '../../src/network/types'; import fs from 'fs'; import path from 'path'; @@ -7,21 +10,21 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import KeyRing from '@/keys/KeyRing'; import * as keysUtils from '@/keys/utils'; -import RPCServer from '@/RPC/RPCServer'; +import RPCServer from '@/rpc/RPCServer'; import TaskManager from '@/tasks/TaskManager'; import CertManager from '@/keys/CertManager'; -import RPCClient from '@/RPC/RPCClient'; +import RPCClient from '@/rpc/RPCClient'; import { Session, SessionManager } from '@/sessions'; -import * as clientRPCUtils from '@/clientRPC/utils'; -import * as authMiddleware from '@/clientRPC/authenticationMiddleware'; -import { UnaryCaller } from '@/RPC/callers'; -import { UnaryHandler } from '@/RPC/handlers'; -import * as middlewareUtils from '@/RPC/middleware'; +import * as clientRPCUtils from '@/client/utils'; +import * as authMiddleware from '@/client/utils/authenticationMiddleware'; +import { UnaryCaller } from '@/rpc/callers'; +import { UnaryHandler } from '@/rpc/handlers'; +import * as middlewareUtils from '@/rpc/utils/middleware'; import WebSocketServer from '@/websockets/WebSocketServer'; import WebSocketClient from '@/websockets/WebSocketClient'; import * as testsUtils from '../utils'; -describe('agentUnlock', () => { +describe('authenticationMiddleware', () => { const logger = new Logger('agentUnlock test', LogLevel.WARN, [ new StreamHandler(), ]); @@ -87,22 +90,24 @@ describe('agentUnlock', () => { recursive: true, }); }); - test('get status', async () => { + test('middleware', async () => { // Setup class EchoHandler extends UnaryHandler< { logger: Logger }, - RPCRequestParams, - RPCResponseResult + ClientRPCRequestParams, + ClientRPCResponseResult > { - public async handle(input: RPCRequestParams): Promise { + public async handle( + input: ClientRPCRequestParams, + ): Promise { return input; } } const rpcServer = await RPCServer.createRPCServer({ manifest: { - agentUnlock: new EchoHandler({ logger }), + testHandler: new EchoHandler({ logger }), }, - middleware: middlewareUtils.defaultServerMiddlewareWrapper( + middlewareFactory: middlewareUtils.defaultServerMiddlewareWrapper( authMiddleware.authenticationMiddlewareServer(sessionManager, keyRing), ), logger, @@ -118,35 +123,38 @@ describe('agentUnlock', () => { clientClient = await WebSocketClient.createWebSocketClient({ expectedNodeIds: [keyRing.getNodeId()], host, - port: clientServer.port, + port: clientServer.getPort(), logger, }); const rpcClient = await RPCClient.createRPCClient({ manifest: { - agentUnlock: new UnaryCaller(), + testHandler: new UnaryCaller< + ClientRPCRequestParams, + ClientRPCResponseResult + >(), }, - streamPairCreateCallback: async () => clientClient.startConnection(), - middleware: middlewareUtils.defaultClientMiddlewareWrapper( + streamFactory: async () => clientClient.startConnection(), + middlewareFactory: middlewareUtils.defaultClientMiddlewareWrapper( authMiddleware.authenticationMiddlewareClient(session), ), logger, }); // Doing the test - const result = await rpcClient.methods.agentUnlock({ + const result = await rpcClient.methods.testHandler({ metadata: { - Authorization: clientRPCUtils.encodeAuthFromPassword(password), + authorization: clientRPCUtils.encodeAuthFromPassword(password), }, }); expect(result).toMatchObject({ metadata: { - Authorization: expect.any(String), + authorization: expect.any(String), }, }); - const result2 = await rpcClient.methods.agentUnlock({}); + const result2 = await rpcClient.methods.testHandler({}); expect(result2).toMatchObject({ metadata: { - Authorization: expect.any(String), + authorization: expect.any(String), }, }); }); diff --git a/tests/client/handlers/agentLockAll.test.ts b/tests/client/handlers/agentLockAll.test.ts new file mode 100644 index 000000000..451a422cd --- /dev/null +++ b/tests/client/handlers/agentLockAll.test.ts @@ -0,0 +1,110 @@ +import type { TLSConfig } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import TaskManager from '@/tasks/TaskManager'; +import { AgentLockAllHandler } from '@/client/handlers/agentLockAll'; +import RPCClient from '@/rpc/RPCClient'; +import { SessionManager } from '@/sessions'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import { agentLockAll } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('agentLockAll', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let sessionManager: SessionManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + taskManager = await TaskManager.createTaskManager({ db, logger }); + sessionManager = await SessionManager.createSessionManager({ + db, + keyRing, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await taskManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('Locks all current sessions', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + agentLockAll: new AgentLockAllHandler({ + db, + sessionManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + agentLockAll, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const token = await sessionManager.createToken(); + await rpcClient.methods.agentLockAll({}); + expect(await sessionManager.verifyToken(token)).toBeFalsy(); + }); +}); diff --git a/tests/clientRPC/handlers/agentStatus.test.ts b/tests/client/handlers/agentStatus.test.ts similarity index 55% rename from tests/clientRPC/handlers/agentStatus.test.ts rename to tests/client/handlers/agentStatus.test.ts index b40216b99..9975c3993 100644 --- a/tests/clientRPC/handlers/agentStatus.test.ts +++ b/tests/client/handlers/agentStatus.test.ts @@ -3,20 +3,15 @@ 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 KeyRing from '@/keys/KeyRing'; import * as keysUtils from '@/keys/utils'; -import RPCServer from '@/RPC/RPCServer'; -import TaskManager from '@/tasks/TaskManager'; -import CertManager from '@/keys/CertManager'; -import { - agentStatusCaller, - AgentStatusHandler, -} from '@/clientRPC/handlers/agentStatus'; -import RPCClient from '@/RPC/RPCClient'; +import RPCServer from '@/rpc/RPCServer'; +import { AgentStatusHandler } from '@/client/handlers/agentStatus'; +import RPCClient from '@/rpc/RPCClient'; import * as nodesUtils from '@/nodes/utils'; import WebSocketClient from '@/websockets/WebSocketClient'; import WebSocketServer from '@/websockets/WebSocketServer'; +import PolykeyAgent from '@/PolykeyAgent'; +import { agentStatus } from '@/client/handlers/clientManifest'; import * as testsUtils from '../../utils'; describe('agentStatus', () => { @@ -26,10 +21,7 @@ describe('agentStatus', () => { const password = 'helloworld'; const host = '127.0.0.1'; let dataDir: string; - let db: DB; - let keyRing: KeyRing; - let taskManager: TaskManager; - let certManager: CertManager; + let pkAgent: PolykeyAgent; let clientServer: WebSocketServer; let clientClient: WebSocketClient; let tlsConfig: TLSConfig; @@ -38,36 +30,18 @@ describe('agentStatus', () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); - const keysPath = path.join(dataDir, 'keys'); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - keyRing = await KeyRing.createKeyRing({ + const nodePath = path.join(dataDir, 'node'); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + nodePath, password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - taskManager = await TaskManager.createTaskManager({ db, logger }); - certManager = await CertManager.createCertManager({ - db, - keyRing, - taskManager, logger, }); - tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + tlsConfig = await testsUtils.createTLSConfig(pkAgent.keyRing.keyPair); }); afterEach(async () => { await clientServer?.stop(true); await clientClient?.destroy(true); - await certManager.stop(); - await taskManager.stop(); - await keyRing.stop(); - await db.stop(); + await pkAgent.stop(); await fs.promises.rm(dataDir, { force: true, recursive: true, @@ -78,9 +52,7 @@ describe('agentStatus', () => { const rpcServer = await RPCServer.createRPCServer({ manifest: { agentStatus: new AgentStatusHandler({ - keyRing, - certManager, - logger: logger.getChild('container'), + pkAgentProm: Promise.resolve(pkAgent), }), }, logger: logger.getChild('RPCServer'), @@ -94,26 +66,33 @@ describe('agentStatus', () => { logger, }); clientClient = await WebSocketClient.createWebSocketClient({ - expectedNodeIds: [keyRing.getNodeId()], + expectedNodeIds: [pkAgent.keyRing.getNodeId()], host, - port: clientServer.port, + port: clientServer.getPort(), logger, }); const rpcClient = await RPCClient.createRPCClient({ manifest: { - agentStatus: agentStatusCaller, + agentStatus, }, - streamPairCreateCallback: async () => clientClient.startConnection(), + streamFactory: async () => clientClient.startConnection(), logger: logger.getChild('RPCClient'), }); // Doing the test const result = await rpcClient.methods.agentStatus({}); expect(result).toStrictEqual({ pid: process.pid, - nodeId: nodesUtils.encodeNodeId(keyRing.getNodeId()), - publicJwk: JSON.stringify( - keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey), - ), + nodeIdEncoded: nodesUtils.encodeNodeId(pkAgent.keyRing.getNodeId()), + clientHost: pkAgent.webSocketServerClient.getHost(), + clientPort: pkAgent.webSocketServerClient.getPort(), + proxyHost: pkAgent.proxy.getProxyHost(), + proxyPort: pkAgent.proxy.getProxyPort(), + agentHost: pkAgent.grpcServerAgent.getHost(), + agentPort: pkAgent.grpcServerAgent.getPort(), + forwardHost: pkAgent.proxy.getForwardHost(), + forwardPort: pkAgent.proxy.getForwardPort(), + publicKeyJwk: keysUtils.publicKeyToJWK(pkAgent.keyRing.keyPair.publicKey), + certChainPEM: await pkAgent.certManager.getCertPEMsChainPEM(), }); }); }); diff --git a/tests/client/handlers/agentStop.test.ts b/tests/client/handlers/agentStop.test.ts new file mode 100644 index 000000000..274dadb80 --- /dev/null +++ b/tests/client/handlers/agentStop.test.ts @@ -0,0 +1,131 @@ +import type { TLSConfig } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import { running } from '@matrixai/async-init'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import TaskManager from '@/tasks/TaskManager'; +import { AgentStopHandler } from '@/client/handlers/agentStop'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import config from '@/config'; +import PolykeyAgent from '@/PolykeyAgent'; +import { agentStop } from '@/client'; +import * as testsUtils from '../../utils'; +import Status from '../../../src/status/Status'; + +describe('agentStop', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let nodePath: string; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let pkAgent: PolykeyAgent; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + nodePath = path.join(dataDir, 'polykey'); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + taskManager = await TaskManager.createTaskManager({ db, logger }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + logger, + keyRingConfig: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await taskManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('Stops the agent', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + agentStop: new AgentStopHandler({ + pkAgentProm: Promise.resolve(pkAgent), + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + agentStop, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const statusPath = path.join(nodePath, config.defaults.statusBase); + const statusLockPath = path.join(nodePath, config.defaults.statusLockBase); + const status = new Status({ + statusPath, + statusLockPath, + fs, + logger, + }); + await rpcClient.methods.agentStop({}); + // It may already be stopping + expect(await status.readStatus()).toMatchObject({ + status: expect.stringMatching(/LIVE|STOPPING|DEAD/), + }); + await status.waitFor('DEAD'); + expect(pkAgent[running]).toBe(false); + }); +}); diff --git a/tests/clientRPC/handlers/agentUnlock.test.ts b/tests/client/handlers/agentUnlock.test.ts similarity index 80% rename from tests/clientRPC/handlers/agentUnlock.test.ts rename to tests/client/handlers/agentUnlock.test.ts index 41d8c9b44..ca43f6cda 100644 --- a/tests/clientRPC/handlers/agentUnlock.test.ts +++ b/tests/client/handlers/agentUnlock.test.ts @@ -6,20 +6,18 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; import KeyRing from '@/keys/KeyRing'; import * as keysUtils from '@/keys/utils'; -import RPCServer from '@/RPC/RPCServer'; +import RPCServer from '@/rpc/RPCServer'; import TaskManager from '@/tasks/TaskManager'; import CertManager from '@/keys/CertManager'; -import { - agentUnlockCaller, - AgentUnlockHandler, -} from '@/clientRPC/handlers/agentUnlock'; -import RPCClient from '@/RPC/RPCClient'; +import { AgentUnlockHandler } from '@/client/handlers/agentUnlock'; +import RPCClient from '@/rpc/RPCClient'; import { Session, SessionManager } from '@/sessions'; -import * as clientRPCUtils from '@/clientRPC/utils'; -import * as authMiddleware from '@/clientRPC/authenticationMiddleware'; -import * as middlewareUtils from '@/RPC/middleware'; +import * as clientUtils from '@/client/utils'; +import * as authMiddleware from '@/client/utils/authenticationMiddleware'; +import * as middlewareUtils from '@/rpc/utils/middleware'; import WebSocketServer from '@/websockets/WebSocketServer'; import WebSocketClient from '@/websockets/WebSocketClient'; +import { agentUnlock } from '@/client'; import * as testsUtils from '../../utils'; describe('agentUnlock', () => { @@ -92,9 +90,9 @@ describe('agentUnlock', () => { // Setup const rpcServer = await RPCServer.createRPCServer({ manifest: { - agentUnlock: new AgentUnlockHandler({ logger }), + agentUnlock: new AgentUnlockHandler({}), }, - middleware: middlewareUtils.defaultServerMiddlewareWrapper( + middlewareFactory: middlewareUtils.defaultServerMiddlewareWrapper( authMiddleware.authenticationMiddlewareServer(sessionManager, keyRing), ), logger, @@ -110,14 +108,14 @@ describe('agentUnlock', () => { expectedNodeIds: [keyRing.getNodeId()], host, logger, - port: clientServer.port, + port: clientServer.getPort(), }); const rpcClient = await RPCClient.createRPCClient({ manifest: { - agentUnlock: agentUnlockCaller, + agentUnlock, }, - streamPairCreateCallback: async () => clientClient.startConnection(), - middleware: middlewareUtils.defaultClientMiddlewareWrapper( + streamFactory: async () => clientClient.startConnection(), + middlewareFactory: middlewareUtils.defaultClientMiddlewareWrapper( authMiddleware.authenticationMiddlewareClient(session), ), logger, @@ -126,18 +124,18 @@ describe('agentUnlock', () => { // Doing the test const result = await rpcClient.methods.agentUnlock({ metadata: { - Authorization: clientRPCUtils.encodeAuthFromPassword(password), + authorization: clientUtils.encodeAuthFromPassword(password), }, }); expect(result).toMatchObject({ metadata: { - Authorization: expect.any(String), + authorization: expect.any(String), }, }); const result2 = await rpcClient.methods.agentUnlock({}); expect(result2).toMatchObject({ metadata: { - Authorization: expect.any(String), + authorization: expect.any(String), }, }); }); diff --git a/tests/client/handlers/gestaltsActionsSetUnsetGetByIdentity.test.ts b/tests/client/handlers/gestaltsActionsSetUnsetGetByIdentity.test.ts new file mode 100644 index 000000000..64a51e5ff --- /dev/null +++ b/tests/client/handlers/gestaltsActionsSetUnsetGetByIdentity.test.ts @@ -0,0 +1,193 @@ +import type { TLSConfig } from '@/network/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { + ClaimIdEncoded, + IdentityId, + ProviderId, + ProviderIdentityClaimId, +} from '@/ids/index'; +import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import TaskManager from '@/tasks/TaskManager'; +import { GestaltsActionsGetByIdentityHandler } from '@/client/handlers/gestaltsActionsGetByIdentity'; +import { GestaltsActionsSetByIdentityHandler } from '@/client/handlers/gestaltsActionsSetByIdentity'; +import { GestaltsActionsUnsetByIdentityHandler } from '@/client/handlers/gestaltsActionsUnsetByIdentity'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import * as nodesUtils from '@/nodes/utils'; +import { encodeProviderIdentityId } from '@/ids/index'; +import Token from '@/tokens/Token'; +import { + gestaltsActionsGetByIdentity, + gestaltsActionsSetByIdentity, + gestaltsActionsUnsetByIdentity, +} from '@/client'; +import * as testsUtils from '../../utils'; + +describe('gestaltsActionsByIdentity', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let acl: ACL; + let gestaltGraph: GestaltGraph; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + taskManager = await TaskManager.createTaskManager({ db, logger }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + acl, + db, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await acl.stop(); + await gestaltGraph.stop(); + await taskManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('sets/unsets/gets actions by identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsActionsGetByIdentity: new GestaltsActionsGetByIdentityHandler({ + db, + gestaltGraph, + }), + gestaltsActionsSetByIdentity: new GestaltsActionsSetByIdentityHandler({ + db, + gestaltGraph, + }), + gestaltsActionsUnsetByIdentity: + new GestaltsActionsUnsetByIdentityHandler({ + db, + gestaltGraph, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsActionsGetByIdentity, + gestaltsActionsSetByIdentity, + gestaltsActionsUnsetByIdentity, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const nodeId = keyRing.getNodeId(); + const node: GestaltNodeInfo = { + nodeId: nodeId, + }; + const identity: GestaltIdentityInfo = { + identityId: 'identityId' as IdentityId, + providerId: 'providerId' as ProviderId, + }; + const dummyClaim: ClaimLinkIdentity = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(nodeId), + sub: encodeProviderIdentityId([identity.providerId, identity.identityId]), + jti: '' as ClaimIdEncoded, + iat: 0, + nbf: 0, + exp: 0, + aud: '', + seq: 0, + prevClaimId: null, + prevDigest: null, + }; + const token = Token.fromPayload(dummyClaim); + token.signWithPrivateKey(keyRing.keyPair); + const signedClaim = token.toSigned(); + await gestaltGraph.linkNodeAndIdentity(node, identity, { + claim: signedClaim, + meta: { providerIdentityClaimId: '' as ProviderIdentityClaimId }, + }); + + const action = 'notify' as const; + const providerMessage = { + providerId: identity.providerId, + identityId: identity.identityId, + }; + const setActionMessage = { + ...providerMessage, + action, + }; + await rpcClient.methods.gestaltsActionsSetByIdentity(setActionMessage); + // Check for permission + const getSetResponse = await rpcClient.methods.gestaltsActionsGetByIdentity( + providerMessage, + ); + expect(getSetResponse.actionsList).toContainEqual(action); + // Unset permission + await rpcClient.methods.gestaltsActionsUnsetByIdentity(setActionMessage); + // Check permission was removed + const getUnsetResponse = + await rpcClient.methods.gestaltsActionsGetByIdentity(providerMessage); + expect(getUnsetResponse.actionsList).toHaveLength(0); + }); +}); diff --git a/tests/client/handlers/gestaltsActionsSetUnsetGetByNode.test.ts b/tests/client/handlers/gestaltsActionsSetUnsetGetByNode.test.ts new file mode 100644 index 000000000..1ba9e7cdc --- /dev/null +++ b/tests/client/handlers/gestaltsActionsSetUnsetGetByNode.test.ts @@ -0,0 +1,160 @@ +import type { TLSConfig } from '@/network/types'; +import type { GestaltNodeInfo } from '@/gestalts/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import TaskManager from '@/tasks/TaskManager'; +import { GestaltsActionsGetByNodeHandler } from '@/client/handlers/gestaltsActionsGetByNode'; +import { GestaltsActionsSetByNodeHandler } from '@/client/handlers/gestaltsActionsSetByNode'; +import { GestaltsActionsUnsetByNodeHandler } from '@/client/handlers/gestaltsActionsUnsetByNode'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import * as nodesUtils from '@/nodes/utils'; +import { + gestaltsActionsGetByNode, + gestaltsActionsSetByNode, + gestaltsActionsUnsetByNode, +} from '@/client'; +import * as testsUtils from '../../utils'; + +describe('gestaltsActionsByNode', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let acl: ACL; + let gestaltGraph: GestaltGraph; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + taskManager = await TaskManager.createTaskManager({ db, logger }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + acl, + db, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await acl.stop(); + await gestaltGraph.stop(); + await taskManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('sets/unsets/gets actions by node', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsActionsGetByNode: new GestaltsActionsGetByNodeHandler({ + db, + gestaltGraph, + }), + gestaltsActionsSetByNode: new GestaltsActionsSetByNodeHandler({ + db, + gestaltGraph, + }), + gestaltsActionsUnsetByNode: new GestaltsActionsUnsetByNodeHandler({ + db, + gestaltGraph, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsActionsGetByNode, + gestaltsActionsSetByNode, + gestaltsActionsUnsetByNode, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const nodeId = keyRing.getNodeId(); + const node: GestaltNodeInfo = { + nodeId: nodeId, + }; + await gestaltGraph.setNode(node); + // Set permission + const nodeMessage = { + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + }; + const action = 'notify' as const; + const requestMessage = { + ...nodeMessage, + action, + }; + await rpcClient.methods.gestaltsActionsSetByNode(requestMessage); + // Check for permission + const getSetResponse = await rpcClient.methods.gestaltsActionsGetByNode( + nodeMessage, + ); + expect(getSetResponse.actionsList).toContainEqual(action); + // Unset permission + await rpcClient.methods.gestaltsActionsUnsetByNode(requestMessage); + // Check permission was removed + const getUnsetResponse = await rpcClient.methods.gestaltsActionsGetByNode( + nodeMessage, + ); + expect(getUnsetResponse.actionsList).toHaveLength(0); + }); +}); diff --git a/tests/client/service/gestaltsDiscoveryByIdentity.test.ts b/tests/client/handlers/gestaltsDiscoveryByIdentity.test.ts similarity index 56% rename from tests/client/service/gestaltsDiscoveryByIdentity.test.ts rename to tests/client/handlers/gestaltsDiscoveryByIdentity.test.ts index f27a24cda..827c59c28 100644 --- a/tests/client/service/gestaltsDiscoveryByIdentity.test.ts +++ b/tests/client/handlers/gestaltsDiscoveryByIdentity.test.ts @@ -1,67 +1,68 @@ -import type { IdentityId, ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type { Key } from '@/keys/types'; +import type { TLSConfig } from '@/network/types'; +import type { IdentityId, ProviderId } from '@/ids/index'; import type { GestaltIdentityInfo } from '@/gestalts/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 Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; import TaskManager from '@/tasks/TaskManager'; +import { GestaltsDiscoveryByIdentityHandler } from '@/client/handlers/gestaltsDiscoveryByIdentity'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; import GestaltGraph from '@/gestalts/GestaltGraph'; import ACL from '@/acl/ACL'; -import KeyRing from '@/keys/KeyRing'; -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 Proxy from '@/network/Proxy'; -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 utils from '@/utils'; -import * as clientUtils from '@/client/utils/utils'; -import * as keysUtils from '@/keys/utils'; -import * as testsUtils from '../../utils/index'; +import { gestaltsDiscoveryByIdentity } from '@/client'; +import * as testsUtils from '../../utils'; +import Discovery from '../../../src/discovery/Discovery'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import NodeManager from '../../../src/nodes/NodeManager'; +import IdentitiesManager from '../../../src/identities/IdentitiesManager'; +import Proxy from '../../../src/network/Proxy'; +import Sigchain from '../../../src/sigchain/Sigchain'; +import NodeGraph from '../../../src/nodes/NodeGraph'; -describe('gestaltsDiscoveryByIdentity', () => { - const logger = new Logger('gestaltsDiscoveryByIdentity test', LogLevel.WARN, [ - new StreamHandler(), +describe('gestaltsDiscoverByIdentity', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const identity: GestaltIdentityInfo = { - identityId: 'identityId' as IdentityId, - providerId: 'providerId' as ProviderId, - }; + const password = 'helloWorld'; + const host = '127.0.0.1'; const authToken = 'abc123'; let dataDir: string; - let discovery: Discovery; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let acl: ACL; let gestaltGraph: GestaltGraph; let identitiesManager: IdentitiesManager; let nodeGraph: NodeGraph; - let taskManager: TaskManager; - let nodeConnectionManager: NodeConnectionManager; - let nodeManager: NodeManager; let sigchain: Sigchain; let proxy: Proxy; - let acl: ACL; - let db: DB; - let keyRing: KeyRing; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; + let nodeManager: NodeManager; + let nodeConnectionManager: NodeConnectionManager; + let discovery: Discovery; + beforeEach(async () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); keyRing = await KeyRing.createKeyRing({ password, keysPath, @@ -70,35 +71,19 @@ describe('gestaltsDiscoveryByIdentity', () => { passwordMemLimit: keysUtils.passwordMemLimits.min, strictMemoryLock: false, }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, + taskManager = await TaskManager.createTaskManager({ + db, logger, - crypto: { - key: keyRing.dbKey, - ops: { - encrypt: async (key, plainText) => { - return keysUtils.encryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(plainText), - ); - }, - decrypt: async (key, cipherText) => { - return keysUtils.decryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(cipherText), - ); - }, - }, - }, + lazy: true, }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); acl = await ACL.createACL({ db, logger, }); gestaltGraph = await GestaltGraph.createGestaltGraph({ - db, acl, + db, logger, }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ @@ -127,11 +112,6 @@ describe('gestaltsDiscoveryByIdentity', () => { keyRing, logger: logger.getChild('NodeGraph'), }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, @@ -155,39 +135,18 @@ describe('gestaltsDiscoveryByIdentity', () => { await nodeConnectionManager.start({ nodeManager }); discovery = await Discovery.createDiscovery({ db, - keyRing, gestaltGraph, identitiesManager, + keyRing, + logger, nodeManager, taskManager, - logger, }); await taskManager.startProcessing(); - const clientService = { - gestaltsDiscoveryByIdentity: gestaltsDiscoveryByIdentity({ - authenticate, - discovery, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); }); afterEach(async () => { await taskManager.stopProcessing(); await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); await discovery.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); @@ -195,28 +154,62 @@ describe('gestaltsDiscoveryByIdentity', () => { await sigchain.stop(); await proxy.stop(); await identitiesManager.stop(); - await gestaltGraph.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); await acl.stop(); - await db.stop(); - await keyRing.stop(); + await gestaltGraph.stop(); await taskManager.stop(); + await keyRing.stop(); + await db.stop(); await fs.promises.rm(dataDir, { force: true, recursive: true, }); }); test('discovers by identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsDiscoveryByIdentity: new GestaltsDiscoveryByIdentityHandler({ + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsDiscoveryByIdentity, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const identity: GestaltIdentityInfo = { + identityId: 'identityId' as IdentityId, + providerId: 'providerId' as ProviderId, + }; 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); + const request = { + providerId: identity.providerId, + identityId: identity.identityId, + }; + await rpcClient.methods.gestaltsDiscoveryByIdentity(request); expect(mockDiscoveryByIdentity).toHaveBeenCalled(); mockDiscoveryByIdentity.mockRestore(); }); diff --git a/tests/client/service/gestaltsDiscoveryByNode.test.ts b/tests/client/handlers/gestaltsDiscoveryByNode.test.ts similarity index 57% rename from tests/client/service/gestaltsDiscoveryByNode.test.ts rename to tests/client/handlers/gestaltsDiscoveryByNode.test.ts index d85591b9f..79112d19f 100644 --- a/tests/client/service/gestaltsDiscoveryByNode.test.ts +++ b/tests/client/handlers/gestaltsDiscoveryByNode.test.ts @@ -1,67 +1,68 @@ +import type { TLSConfig } from '@/network/types'; import type { Host, Port } from '@/network/types'; -import type { Key } from '@/keys/types'; -import type { GestaltNodeInfo } from '@/gestalts/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; import TaskManager from '@/tasks/TaskManager'; +import { GestaltsDiscoveryByNodeHandler } from '@/client/handlers/gestaltsDiscoveryByNode'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; import GestaltGraph from '@/gestalts/GestaltGraph'; import ACL from '@/acl/ACL'; -import KeyRing from '@/keys/KeyRing'; -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 Proxy from '@/network/Proxy'; -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 utils from '@/utils'; -import * as clientUtils from '@/client/utils/utils'; -import * as keysUtils from '@/keys/utils'; import * as nodesUtils from '@/nodes/utils'; +import { gestaltsDiscoveryByNode } from '@/client'; +import * as testsUtils from '../../utils'; +import Discovery from '../../../src/discovery/Discovery'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import NodeManager from '../../../src/nodes/NodeManager'; +import IdentitiesManager from '../../../src/identities/IdentitiesManager'; +import Proxy from '../../../src/network/Proxy'; +import Sigchain from '../../../src/sigchain/Sigchain'; +import NodeGraph from '../../../src/nodes/NodeGraph'; import * as testNodesUtils from '../../nodes/utils'; -import * as testsUtils from '../../utils/index'; -describe('gestaltsDiscoveryByNode', () => { - const logger = new Logger('gestaltsDiscoveryByNode test', LogLevel.WARN, [ - new StreamHandler(), +describe('gestaltsDiscoverByNode', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const node: GestaltNodeInfo = { - nodeId: testNodesUtils.generateRandomNodeId(), - }; + const password = 'helloWorld'; + const host = '127.0.0.1'; const authToken = 'abc123'; let dataDir: string; - let discovery: Discovery; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let acl: ACL; let gestaltGraph: GestaltGraph; let identitiesManager: IdentitiesManager; let nodeGraph: NodeGraph; - let taskManager: TaskManager; - let nodeConnectionManager: NodeConnectionManager; - let nodeManager: NodeManager; let sigchain: Sigchain; let proxy: Proxy; - let acl: ACL; - let db: DB; - let keyRing: KeyRing; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; + let nodeManager: NodeManager; + let nodeConnectionManager: NodeConnectionManager; + let discovery: Discovery; + beforeEach(async () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); keyRing = await KeyRing.createKeyRing({ password, keysPath, @@ -70,35 +71,19 @@ describe('gestaltsDiscoveryByNode', () => { passwordMemLimit: keysUtils.passwordMemLimits.min, strictMemoryLock: false, }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, + taskManager = await TaskManager.createTaskManager({ + db, logger, - crypto: { - key: keyRing.dbKey, - ops: { - encrypt: async (key, plainText) => { - return keysUtils.encryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(plainText), - ); - }, - decrypt: async (key, cipherText) => { - return keysUtils.decryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(cipherText), - ); - }, - }, - }, + lazy: true, }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); acl = await ACL.createACL({ db, logger, }); gestaltGraph = await GestaltGraph.createGestaltGraph({ - db, acl, + db, logger, }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ @@ -127,11 +112,6 @@ describe('gestaltsDiscoveryByNode', () => { keyRing, logger: logger.getChild('NodeGraph'), }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, @@ -155,39 +135,18 @@ describe('gestaltsDiscoveryByNode', () => { await nodeConnectionManager.start({ nodeManager }); discovery = await Discovery.createDiscovery({ db, - keyRing, gestaltGraph, identitiesManager, + keyRing, + logger, nodeManager, taskManager, - logger, }); await taskManager.startProcessing(); - const clientService = { - gestaltsDiscoveryByNode: gestaltsDiscoveryByNode({ - authenticate, - discovery, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); }); afterEach(async () => { await taskManager.stopProcessing(); await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); await discovery.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); @@ -195,27 +154,59 @@ describe('gestaltsDiscoveryByNode', () => { await sigchain.stop(); await proxy.stop(); await identitiesManager.stop(); - await gestaltGraph.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); await acl.stop(); - await db.stop(); - await keyRing.stop(); + await gestaltGraph.stop(); await taskManager.stop(); + await keyRing.stop(); + await db.stop(); await fs.promises.rm(dataDir, { force: true, recursive: true, }); }); test('discovers by node', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsDiscoveryByNode: new GestaltsDiscoveryByNodeHandler({ + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsDiscoveryByNode, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test const mockDiscoveryByNode = jest .spyOn(Discovery.prototype, 'queueDiscoveryByNode') .mockResolvedValue(); - const request = new nodesPB.Node(); - request.setNodeId(nodesUtils.encodeNodeId(node.nodeId)); - const response = await grpcClient.gestaltsDiscoveryByNode( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + const request = { + nodeIdEncoded: nodesUtils.encodeNodeId( + testNodesUtils.generateRandomNodeId(), + ), + }; + await rpcClient.methods.gestaltsDiscoveryByNode(request); expect(mockDiscoveryByNode).toHaveBeenCalled(); mockDiscoveryByNode.mockRestore(); }); diff --git a/tests/client/handlers/gestaltsGestaltGetByIdentity.test.ts b/tests/client/handlers/gestaltsGestaltGetByIdentity.test.ts new file mode 100644 index 000000000..0996addda --- /dev/null +++ b/tests/client/handlers/gestaltsGestaltGetByIdentity.test.ts @@ -0,0 +1,180 @@ +import type { TLSConfig } from '@/network/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { + ClaimIdEncoded, + IdentityId, + ProviderId, + ProviderIdentityClaimId, +} from '@/ids/index'; +import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import TaskManager from '@/tasks/TaskManager'; +import { GestaltsGestaltGetByIdentityHandler } from '@/client/handlers/gestaltsGestaltGetByIdentity'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import * as nodesUtils from '@/nodes/utils'; +import { encodeProviderIdentityId } from '@/ids/index'; +import Token from '@/tokens/Token'; +import * as gestaltUtils from '@/gestalts/utils'; +import { gestaltsGestaltGetByIdentity } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('gestaltsGestaltGetByIdentity', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let acl: ACL; + let gestaltGraph: GestaltGraph; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + taskManager = await TaskManager.createTaskManager({ db, logger }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + acl, + db, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await acl.stop(); + await gestaltGraph.stop(); + await taskManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('getys gestalt by identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltGetByIdentity: new GestaltsGestaltGetByIdentityHandler({ + db, + gestaltGraph, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltGetByIdentity, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const nodeId = keyRing.getNodeId(); + const node: GestaltNodeInfo = { + nodeId: nodeId, + }; + const identity: GestaltIdentityInfo = { + identityId: 'identityId' as IdentityId, + providerId: 'providerId' as ProviderId, + }; + const dummyClaim: ClaimLinkIdentity = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(nodeId), + sub: encodeProviderIdentityId([identity.providerId, identity.identityId]), + jti: '' as ClaimIdEncoded, + iat: 0, + nbf: 0, + exp: 0, + aud: '', + seq: 0, + prevClaimId: null, + prevDigest: null, + }; + const token = Token.fromPayload(dummyClaim); + token.signWithPrivateKey(keyRing.keyPair); + const signedClaim = token.toSigned(); + await gestaltGraph.linkNodeAndIdentity(node, identity, { + claim: signedClaim, + meta: { providerIdentityClaimId: '' as ProviderIdentityClaimId }, + }); + const nodeKey = gestaltUtils.encodeGestaltId(['node', nodeId]); + const identityKey = gestaltUtils.encodeGestaltId([ + 'identity', + [identity.providerId, identity.identityId], + ]); + const expectedGestalt = { + matrix: {}, + nodes: {}, + identities: {}, + }; + expectedGestalt.matrix[identityKey] = {}; + expectedGestalt.matrix[nodeKey] = {}; + expectedGestalt.matrix[identityKey][nodeKey] = null; + expectedGestalt.matrix[nodeKey][identityKey] = null; + expectedGestalt.nodes[nodeKey] = expect.anything(); + expectedGestalt.identities[identityKey] = expect.anything(); + + const request = { + providerId: identity.providerId, + identityId: identity.identityId, + }; + const response = await rpcClient.methods.gestaltsGestaltGetByIdentity( + request, + ); + expect(response.gestalt).toEqual(expectedGestalt); + }); +}); diff --git a/tests/client/handlers/gestaltsGestaltGetByNode.test.ts b/tests/client/handlers/gestaltsGestaltGetByNode.test.ts new file mode 100644 index 000000000..e587aba9b --- /dev/null +++ b/tests/client/handlers/gestaltsGestaltGetByNode.test.ts @@ -0,0 +1,177 @@ +import type { TLSConfig } from '@/network/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { + ClaimIdEncoded, + IdentityId, + ProviderId, + ProviderIdentityClaimId, +} from '@/ids/index'; +import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import TaskManager from '@/tasks/TaskManager'; +import { GestaltsGestaltGetByNodeHandler } from '@/client/handlers/gestaltsGestaltGetByNode'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import * as nodesUtils from '@/nodes/utils'; +import { encodeProviderIdentityId } from '@/ids/index'; +import Token from '@/tokens/Token'; +import * as gestaltUtils from '@/gestalts/utils'; +import { gestaltsGestaltGetByNode } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('gestaltsGestaltGetByNode', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let acl: ACL; + let gestaltGraph: GestaltGraph; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + taskManager = await TaskManager.createTaskManager({ db, logger }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + acl, + db, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await acl.stop(); + await gestaltGraph.stop(); + await taskManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets gestalt by node', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltGetByNode: new GestaltsGestaltGetByNodeHandler({ + db, + gestaltGraph, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltGetByNode, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const nodeId = keyRing.getNodeId(); + const node: GestaltNodeInfo = { + nodeId: nodeId, + }; + const identity: GestaltIdentityInfo = { + identityId: 'identityId' as IdentityId, + providerId: 'providerId' as ProviderId, + }; + const dummyClaim: ClaimLinkIdentity = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(nodeId), + sub: encodeProviderIdentityId([identity.providerId, identity.identityId]), + jti: '' as ClaimIdEncoded, + iat: 0, + nbf: 0, + exp: 0, + aud: '', + seq: 0, + prevClaimId: null, + prevDigest: null, + }; + const token = Token.fromPayload(dummyClaim); + token.signWithPrivateKey(keyRing.keyPair); + const signedClaim = token.toSigned(); + await gestaltGraph.linkNodeAndIdentity(node, identity, { + claim: signedClaim, + meta: { providerIdentityClaimId: '' as ProviderIdentityClaimId }, + }); + const nodeKey = gestaltUtils.encodeGestaltId(['node', nodeId]); + const identityKey = gestaltUtils.encodeGestaltId([ + 'identity', + [identity.providerId, identity.identityId], + ]); + const expectedGestalt = { + matrix: {}, + nodes: {}, + identities: {}, + }; + expectedGestalt.matrix[identityKey] = {}; + expectedGestalt.matrix[nodeKey] = {}; + expectedGestalt.matrix[identityKey][nodeKey] = null; + expectedGestalt.matrix[nodeKey][identityKey] = null; + expectedGestalt.nodes[nodeKey] = expect.anything(); + expectedGestalt.identities[identityKey] = expect.anything(); + + const request = { + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + }; + const response = await rpcClient.methods.gestaltsGestaltGetByNode(request); + expect(response.gestalt).toEqual(expectedGestalt); + }); +}); diff --git a/tests/client/handlers/gestaltsGestaltList.test.ts b/tests/client/handlers/gestaltsGestaltList.test.ts new file mode 100644 index 000000000..02f035071 --- /dev/null +++ b/tests/client/handlers/gestaltsGestaltList.test.ts @@ -0,0 +1,160 @@ +import type { TLSConfig } from '@/network/types'; +import type { IdentityId, ProviderId } from '@/ids/index'; +import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; +import type { Gestalt } from '@/gestalts/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import TaskManager from '@/tasks/TaskManager'; +import { GestaltsGestaltListHandler } from '@/client/handlers/gestaltsGestaltList'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import * as gestaltUtils from '@/gestalts/utils'; +import { gestaltsGestaltList } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('gestaltsGestaltList', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let acl: ACL; + let gestaltGraph: GestaltGraph; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + taskManager = await TaskManager.createTaskManager({ db, logger }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + acl, + db, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await acl.stop(); + await gestaltGraph.stop(); + await taskManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('lists gestalts', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltList: new GestaltsGestaltListHandler({ + db, + gestaltGraph, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltList, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const nodeId = keyRing.getNodeId(); + const node: GestaltNodeInfo = { + nodeId: nodeId, + }; + const identity: GestaltIdentityInfo = { + identityId: 'identityId' as IdentityId, + providerId: 'providerId' as ProviderId, + }; + await gestaltGraph.setNode(node); + await gestaltGraph.setIdentity(identity); + const nodeKey = gestaltUtils.encodeGestaltId(['node', nodeId]); + const identityKey = gestaltUtils.encodeGestaltId([ + 'identity', + [identity.providerId, identity.identityId], + ]); + const expectedNodeGestalt: Gestalt = { + matrix: {}, + nodes: {}, + identities: {}, + }; + expectedNodeGestalt.matrix[nodeKey] = {}; + expectedNodeGestalt.nodes[nodeKey] = expect.any(Object); + const expectedIdentityGestalt: Gestalt = { + matrix: {}, + nodes: {}, + identities: {}, + }; + expectedIdentityGestalt.matrix[identityKey] = {}; + expectedIdentityGestalt.identities[identityKey] = expect.any(Object); + + for await (const response of await rpcClient.methods.gestaltsGestaltList( + {}, + )) { + if (Object.keys(response.gestalt.identities).length !== 0) { + // Should be the identity response + expect(response.gestalt).toEqual(expectedIdentityGestalt); + } else { + // Otherwise the node gestalt + expect(response.gestalt).toEqual(expectedNodeGestalt); + } + } + }); +}); diff --git a/tests/client/handlers/gestaltsGestaltTrustByIdentity.test.ts b/tests/client/handlers/gestaltsGestaltTrustByIdentity.test.ts new file mode 100644 index 000000000..f78617af1 --- /dev/null +++ b/tests/client/handlers/gestaltsGestaltTrustByIdentity.test.ts @@ -0,0 +1,512 @@ +import type { TLSConfig } from '@/network/types'; +import type { IdentityId, ClaimId, ProviderIdentityClaimId } from '@/ids/index'; +import type { SignedClaim } from '@/claims/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads'; +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import TaskManager from '@/tasks/TaskManager'; +import { GestaltsGestaltTrustByIdentityHandler } from '@/client/handlers/gestaltsGestaltTrustByIdentity'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import ACL from '@/acl/ACL'; +import { encodeProviderIdentityId } from '@/ids/index'; +import * as nodesUtils from '@/nodes/utils'; +import * as gestaltsErrors from '@/gestalts/errors'; +import { sleep } from '@/utils'; +import { gestaltsGestaltTrustByIdentity } from '@/client'; +import * as testsUtils from '../../utils'; +import Discovery from '../../../src/discovery/Discovery'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import NodeManager from '../../../src/nodes/NodeManager'; +import IdentitiesManager from '../../../src/identities/IdentitiesManager'; +import Proxy from '../../../src/network/Proxy'; +import Sigchain from '../../../src/sigchain/Sigchain'; +import NodeGraph from '../../../src/nodes/NodeGraph'; +import TestProvider from '../../identities/TestProvider'; +import * as testUtils from '../../utils'; + +describe('gestaltsGestaltTrustByIdentity', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const authToken = 'abc123'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let acl: ACL; + let gestaltGraph: GestaltGraph; + let identitiesManager: IdentitiesManager; + let nodeGraph: NodeGraph; + let sigchain: Sigchain; + let proxy: Proxy; + let nodeManager: NodeManager; + let nodeConnectionManager: NodeConnectionManager; + let discovery: Discovery; + let testProvider: TestProvider; + const connectedIdentity = 'trusted-node' as IdentityId; + let claimId: ClaimId; + const nodeChainData: Record = {}; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + taskManager = await TaskManager.createTaskManager({ + db, + logger, + lazy: true, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + acl, + db, + logger, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + keyRing, + sigchain, + db, + gestaltGraph, + logger, + }); + proxy = new Proxy({ + authToken, + logger, + }); + await proxy.start({ + serverHost: '127.0.0.1' as Host, + serverPort: 0 as Port, + tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), + }); + sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyRing, + logger: logger.getChild('NodeGraph'), + }); + nodeConnectionManager = new NodeConnectionManager({ + keyRing, + nodeGraph, + proxy, + taskManager, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + nodeManager = new NodeManager({ + db, + keyRing, + nodeConnectionManager, + nodeGraph, + sigchain, + taskManager, + gestaltGraph, + logger, + }); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + discovery = await Discovery.createDiscovery({ + db, + gestaltGraph, + identitiesManager, + keyRing, + logger, + nodeManager, + taskManager, + }); + await taskManager.startProcessing(); + + testProvider = new TestProvider(); + identitiesManager.registerProvider(testProvider); + await identitiesManager.putToken(testProvider.id, connectedIdentity, { + accessToken: 'abc123', + }); + testProvider.users['trusted-node'] = {}; + const identityClaim = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(keyRing.getNodeId()), + sub: encodeProviderIdentityId([testProvider.id, connectedIdentity]), + }; + const [claimId_, claim] = await sigchain.addClaim(identityClaim); + claimId = claimId_; + nodeChainData[claimId_] = claim; + await testProvider.publishClaim( + connectedIdentity, + claim as SignedClaim, + ); + }); + afterEach(async () => { + await taskManager.stopProcessing(); + await taskManager.stopTasks(); + await discovery.stop(); + await nodeGraph.stop(); + await nodeConnectionManager.stop(); + await nodeManager.stop(); + await sigchain.stop(); + await proxy.stop(); + await identitiesManager.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await acl.stop(); + await gestaltGraph.stop(); + await taskManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('trusts an identity (already set in gestalt graph)', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltTrustByIdentity: + new GestaltsGestaltTrustByIdentityHandler({ + db, + gestaltGraph, + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltTrustByIdentity, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + testProvider.users['disconnected-user'] = {}; + await gestaltGraph.linkNodeAndIdentity( + { nodeId: keyRing.getNodeId() }, + { + providerId: testProvider.id, + identityId: connectedIdentity, + }, + { + claim: nodeChainData[claimId] as SignedClaim, + meta: { + providerIdentityClaimId: '' as ProviderIdentityClaimId, + }, + }, + ); + const request = { + providerId: testProvider.id, + identityId: connectedIdentity, + }; + await rpcClient.methods.gestaltsGestaltTrustByIdentity(request); + expect( + await gestaltGraph.getGestaltActions([ + 'identity', + [testProvider.id, connectedIdentity], + ]), + ).toEqual({ + notify: null, + }); + }); + test('trusts an identity (new identity)', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltTrustByIdentity: + new GestaltsGestaltTrustByIdentityHandler({ + db, + gestaltGraph, + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltTrustByIdentity, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const request = { + providerId: testProvider.id, + identityId: connectedIdentity, + }; + // Should fail on first attempt - need to allow time for the identity to be + // linked to a node via discovery + await testUtils.expectRemoteError( + rpcClient.methods.gestaltsGestaltTrustByIdentity(request), + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + ); + // Wait for both identity and node to be set in GG + let existingTasks: number = 0; + do { + existingTasks = await discovery.waitForDiscoveryTasks(); + } while (existingTasks > 0); + await rpcClient.methods.gestaltsGestaltTrustByIdentity(request); + expect( + await gestaltGraph.getGestaltActions([ + 'identity', + [testProvider.id, connectedIdentity], + ]), + ).toEqual({ + notify: null, + }); + }); + test('cannot trust a disconnected identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltTrustByIdentity: + new GestaltsGestaltTrustByIdentityHandler({ + db, + gestaltGraph, + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltTrustByIdentity, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + testProvider.users['disconnected-user'] = {}; + const request = { + providerId: testProvider.id, + identityId: connectedIdentity, + }; + // Should fail on first attempt - attempt to find a connected node + await testUtils.expectRemoteError( + rpcClient.methods.gestaltsGestaltTrustByIdentity(request), + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + ); + // Wait and try again - should fail again because the identity has no + // linked nodes we can trust + await testUtils.expectRemoteError( + rpcClient.methods.gestaltsGestaltTrustByIdentity(request), + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + ); + }); + test('trust extends to entire gestalt', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltTrustByIdentity: + new GestaltsGestaltTrustByIdentityHandler({ + db, + gestaltGraph, + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltTrustByIdentity, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + await gestaltGraph.linkNodeAndIdentity( + { nodeId: keyRing.getNodeId() }, + { + providerId: testProvider.id, + identityId: connectedIdentity, + }, + { + claim: nodeChainData[claimId] as SignedClaim, + meta: { + providerIdentityClaimId: '' as ProviderIdentityClaimId, + }, + }, + ); + const request = { + providerId: testProvider.id, + identityId: connectedIdentity, + }; + await rpcClient.methods.gestaltsGestaltTrustByIdentity(request); + expect( + await gestaltGraph.getGestaltActions([ + 'identity', + [testProvider.id, connectedIdentity], + ]), + ).toEqual({ + notify: null, + }); + expect( + await gestaltGraph.getGestaltActions(['node', keyRing.getNodeId()]), + ).toEqual({ + notify: null, + }); + }); + test('links trusted identity to an existing node', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltTrustByIdentity: + new GestaltsGestaltTrustByIdentityHandler({ + db, + gestaltGraph, + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltTrustByIdentity, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + await gestaltGraph.setNode({ + nodeId: keyRing.getNodeId(), + }); + const request = { + providerId: testProvider.id, + identityId: connectedIdentity, + }; + // Should fail on first attempt - need to allow time for the identity to be + // linked to a node via discovery + await testUtils.expectRemoteError( + rpcClient.methods.gestaltsGestaltTrustByIdentity(request), + gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, + ); + // Wait and try again - should succeed second time + await sleep(2000); + await rpcClient.methods.gestaltsGestaltTrustByIdentity(request); + // Wait for both identity and node to be set in GG + let existingTasks: number = 0; + do { + existingTasks = await discovery.waitForDiscoveryTasks(); + } while (existingTasks > 0); + await rpcClient.methods.gestaltsGestaltTrustByIdentity(request); + expect( + await gestaltGraph.getGestaltActions([ + 'identity', + [testProvider.id, connectedIdentity], + ]), + ).toEqual({ + notify: null, + }); + expect( + await gestaltGraph.getGestaltActions(['node', keyRing.getNodeId()]), + ).toEqual({ + notify: null, + }); + }); +}); diff --git a/tests/client/service/gestaltsGestaltTrustByNode.test.ts b/tests/client/handlers/gestaltsGestaltTrustByNode.test.ts similarity index 52% rename from tests/client/service/gestaltsGestaltTrustByNode.test.ts rename to tests/client/handlers/gestaltsGestaltTrustByNode.test.ts index 76670cca0..3b0e17466 100644 --- a/tests/client/service/gestaltsGestaltTrustByNode.test.ts +++ b/tests/client/handlers/gestaltsGestaltTrustByNode.test.ts @@ -1,59 +1,76 @@ -import type { NodeId, NodeIdEncoded } from '@/ids/types'; -import type { IdentityId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type { Key } from '@/keys/types'; -import type { ClaimId } from '@/ids/types'; +import type { TLSConfig } from '@/network/types'; +import type { IdentityId, NodeId, NodeIdEncoded, ClaimId } from '@/ids/index'; import type { SignedClaim } from '@/claims/types'; -import type { ClaimLinkIdentity } from '@/claims/payloads/index'; +import type { ClaimLinkIdentity } from '@/claims/payloads'; +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 Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; -import TaskManager from '@/tasks/TaskManager'; -import PolykeyAgent from '@/PolykeyAgent'; import KeyRing from '@/keys/KeyRing'; -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 Proxy from '@/network/Proxy'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import TaskManager from '@/tasks/TaskManager'; +import { GestaltsGestaltTrustByNodeHandler } from '@/client/handlers/gestaltsGestaltTrustByNode'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; 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 * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; -import * as keysUtils from '@/keys/utils'; -import * as clientUtils from '@/client/utils/utils'; +import { encodeProviderIdentityId } from '@/ids/index'; import * as nodesUtils from '@/nodes/utils'; -import * as utils from '@/utils/index'; -import { encodeProviderIdentityId } from '@/identities/utils'; -import * as testsUtils from '../../utils/index'; +import PolykeyAgent from '@/PolykeyAgent'; +import { gestaltsGestaltTrustByNode } from '@/client'; +import * as testsUtils from '../../utils'; +import Discovery from '../../../src/discovery/Discovery'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import NodeManager from '../../../src/nodes/NodeManager'; +import IdentitiesManager from '../../../src/identities/IdentitiesManager'; +import Proxy from '../../../src/network/Proxy'; +import Sigchain from '../../../src/sigchain/Sigchain'; +import NodeGraph from '../../../src/nodes/NodeGraph'; import TestProvider from '../../identities/TestProvider'; describe('gestaltsGestaltTrustByNode', () => { - const logger = new Logger('gestaltsGestaltTrustByNode test', LogLevel.WARN, [ - new StreamHandler(), + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const testProvider = new TestProvider(); - // Create node to trust + const password = 'helloWorld'; + const host = '127.0.0.1'; + const authToken = 'abc123'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let taskManager: TaskManager; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let acl: ACL; + let gestaltGraph: GestaltGraph; + let identitiesManager: IdentitiesManager; + let nodeGraph: NodeGraph; + let sigchain: Sigchain; + let proxy: Proxy; + let nodeManager: NodeManager; + let nodeConnectionManager: NodeConnectionManager; + let discovery: Discovery; + let testProvider: TestProvider; const connectedIdentity = 'trusted-node' as IdentityId; - let nodeDataDir: string; - let node: PolykeyAgent; - let nodeId: NodeId; - let nodeIdEncoded: NodeIdEncoded; const nodeChainData: Record = {}; + let nodeIdRemote: NodeId; + let nodeIdEncodedRemote: NodeIdEncoded; + let node: PolykeyAgent; let mockedRequestChainData: jest.SpyInstance; - beforeAll(async () => { + let nodeDataDir: string; + + beforeEach(async () => { + testProvider = new TestProvider(); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); mockedRequestChainData = jest .spyOn(NodeManager.prototype, 'requestChainData') .mockResolvedValue(nodeChainData); @@ -77,8 +94,8 @@ describe('gestaltsGestaltTrustByNode', () => { strictMemoryLock: false, }, }); - nodeId = node.keyRing.getNodeId(); - nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); + nodeIdRemote = node.keyRing.getNodeId(); + nodeIdEncodedRemote = nodesUtils.encodeNodeId(nodeIdRemote); node.identitiesManager.registerProvider(testProvider); await node.identitiesManager.putToken(testProvider.id, connectedIdentity, { accessToken: 'abc123', @@ -86,7 +103,7 @@ describe('gestaltsGestaltTrustByNode', () => { testProvider.users['trusted-node'] = {}; const identityClaim = { typ: 'ClaimLinkIdentity', - iss: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), + iss: nodeIdEncodedRemote, sub: encodeProviderIdentityId([testProvider.id, connectedIdentity]), }; const [claimId, claim] = await node.sigchain.addClaim(identityClaim); @@ -95,37 +112,13 @@ describe('gestaltsGestaltTrustByNode', () => { connectedIdentity, claim as SignedClaim, ); - }, globalThis.maxTimeout); - afterAll(async () => { - await node.stop(); - await fs.promises.rm(nodeDataDir, { - force: true, - recursive: true, - }); - mockedRequestChainData.mockRestore(); - }); - const authToken = 'abc123'; - let dataDir: string; - let discovery: Discovery; - let gestaltGraph: GestaltGraph; - let identitiesManager: IdentitiesManager; - let taskManager: TaskManager; - let nodeManager: NodeManager; - let nodeConnectionManager: NodeConnectionManager; - let nodeGraph: NodeGraph; - let sigchain: Sigchain; - let proxy: Proxy; - let acl: ACL; - let db: DB; - let keyRing: KeyRing; - 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'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); keyRing = await KeyRing.createKeyRing({ password, keysPath, @@ -134,35 +127,19 @@ describe('gestaltsGestaltTrustByNode', () => { passwordMemLimit: keysUtils.passwordMemLimits.min, strictMemoryLock: false, }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, + taskManager = await TaskManager.createTaskManager({ + db, logger, - crypto: { - key: keyRing.dbKey, - ops: { - encrypt: async (key, plainText) => { - return keysUtils.encryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(plainText), - ); - }, - decrypt: async (key, cipherText) => { - return keysUtils.decryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(cipherText), - ); - }, - }, - }, + lazy: true, }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); acl = await ACL.createACL({ db, logger, }); gestaltGraph = await GestaltGraph.createGestaltGraph({ - db, acl, + db, logger, }); identitiesManager = await IdentitiesManager.createIdentitiesManager({ @@ -173,13 +150,9 @@ describe('gestaltsGestaltTrustByNode', () => { logger, }); identitiesManager.registerProvider(testProvider); - await identitiesManager.putToken( - testProvider.id, - 'test-user' as IdentityId, - { - accessToken: 'def456', - }, - ); + await identitiesManager.putToken(testProvider.id, connectedIdentity, { + accessToken: 'abc123', + }); proxy = new Proxy({ authToken, logger, @@ -199,11 +172,6 @@ describe('gestaltsGestaltTrustByNode', () => { keyRing, logger: logger.getChild('NodeGraph'), }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); nodeConnectionManager = new NodeConnectionManager({ keyRing, nodeGraph, @@ -225,118 +193,178 @@ describe('gestaltsGestaltTrustByNode', () => { }); await nodeManager.start(); await nodeConnectionManager.start({ nodeManager }); - await nodeManager.setNode(nodesUtils.decodeNodeId(nodeIdEncoded)!, { + await nodeManager.setNode(nodeIdRemote, { host: node.proxy.getProxyHost(), port: node.proxy.getProxyPort(), }); discovery = await Discovery.createDiscovery({ db, - keyRing, gestaltGraph, identitiesManager, + keyRing, + logger, nodeManager, taskManager, - logger, }); await taskManager.startProcessing(); - const clientService = { - gestaltsGestaltTrustByNode: gestaltsGestaltTrustByNode({ - authenticate, - gestaltGraph, - discovery, - logger, - db, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); }); afterEach(async () => { await taskManager.stopProcessing(); await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); await discovery.stop(); + await nodeGraph.stop(); await nodeConnectionManager.stop(); await nodeManager.stop(); - await nodeGraph.stop(); - await proxy.stop(); await sigchain.stop(); + await proxy.stop(); await identitiesManager.stop(); - await gestaltGraph.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); await acl.stop(); - await db.stop(); - await keyRing.stop(); + await gestaltGraph.stop(); await taskManager.stop(); + await keyRing.stop(); + await db.stop(); await fs.promises.rm(dataDir, { force: true, recursive: true, }); + await node.stop(); + await fs.promises.rm(nodeDataDir, { + force: true, + recursive: true, + }); + mockedRequestChainData.mockRestore(); }); test('trusts a node (already set in gestalt graph)', async () => { - await gestaltGraph.setNode({ nodeId: nodeId }); - const request = new nodesPB.Node(); - request.setNodeId(nodeIdEncoded); - const response = await grpcClient.gestaltsGestaltTrustByNode( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltTrustByNode: new GestaltsGestaltTrustByNodeHandler({ + db, + gestaltGraph, + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltTrustByNode, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + await gestaltGraph.setNode({ nodeId: nodeIdRemote }); + const request = { + nodeIdEncoded: nodeIdEncodedRemote, + }; + await rpcClient.methods.gestaltsGestaltTrustByNode(request); expect( - await gestaltGraph.getGestaltActions([ - 'node', - nodesUtils.decodeNodeId(nodeIdEncoded)!, - ]), + await gestaltGraph.getGestaltActions(['node', nodeIdRemote!]), ).toEqual({ notify: null, }); - // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeIdEncoded)!); - await gestaltGraph.unsetIdentity([testProvider.id, connectedIdentity]); }); test('trusts a node (new node)', async () => { - const request = new nodesPB.Node(); - request.setNodeId(nodeIdEncoded); - const response = await grpcClient.gestaltsGestaltTrustByNode( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltTrustByNode: new GestaltsGestaltTrustByNodeHandler({ + db, + gestaltGraph, + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltTrustByNode, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const request = { + nodeIdEncoded: nodeIdEncodedRemote, + }; + await rpcClient.methods.gestaltsGestaltTrustByNode(request); expect( - await gestaltGraph.getGestaltActions([ - 'node', - nodesUtils.decodeNodeId(nodeIdEncoded)!, - ]), + await gestaltGraph.getGestaltActions(['node', nodeIdRemote]), ).toEqual({ notify: null, }); - // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeIdEncoded)!); - await gestaltGraph.unsetIdentity([testProvider.id, connectedIdentity]); }); test('trust extends to entire gestalt', async () => { - const request = new nodesPB.Node(); - request.setNodeId(nodeIdEncoded); - const response = await grpcClient.gestaltsGestaltTrustByNode( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + gestaltsGestaltTrustByNode: new GestaltsGestaltTrustByNodeHandler({ + db, + gestaltGraph, + discovery, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + gestaltsGestaltTrustByNode, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const request = { + nodeIdEncoded: nodeIdEncodedRemote, + }; + await rpcClient.methods.gestaltsGestaltTrustByNode(request); expect( - await gestaltGraph.getGestaltActions([ - 'node', - nodesUtils.decodeNodeId(nodeIdEncoded)!, - ]), + await gestaltGraph.getGestaltActions(['node', nodeIdRemote]), ).toEqual({ notify: null, }); @@ -353,8 +381,5 @@ describe('gestaltsGestaltTrustByNode', () => { ).toEqual({ notify: null, }); - // Reverse side effects - await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeIdEncoded)!); - await gestaltGraph.unsetIdentity([testProvider.id, connectedIdentity]); }); }); diff --git a/tests/client/handlers/identitiesAuthenticate.test.ts b/tests/client/handlers/identitiesAuthenticate.test.ts new file mode 100644 index 000000000..70e3fccbf --- /dev/null +++ b/tests/client/handlers/identitiesAuthenticate.test.ts @@ -0,0 +1,195 @@ +import type { TLSConfig } from '@/network/types'; +import type { IdentityId, ProviderId } from '@/ids/index'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { IdentitiesAuthenticateHandler } from '@/client/handlers/identitiesAuthenticate'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import * as validationErrors from '@/validation/errors'; +import { identitiesAuthenticate } from '@/client'; +import * as testUtils from '../../utils'; +import * as testsUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('identitiesAuthenticate', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + let testProvider: TestProvider; + const testToken = { + providerId: 'test-provider' as ProviderId, + identityId: 'test_user' as IdentityId, + providerToken: { + accessToken: 'abc123', + }, + }; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + testProvider = new TestProvider(); + identitiesManager.registerProvider(testProvider); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('authenticates identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesAuthenticate: new IdentitiesAuthenticateHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesAuthenticate, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const request = { + providerId: testToken.providerId, + }; + const response = await rpcClient.methods.identitiesAuthenticate(request); + let step = 0; + for await (const message of response) { + if (step === 0) { + expect(message.request).toBeDefined(); + expect(message.response).toBeUndefined(); + const authRequest = message.request!; + expect(authRequest.url).toBe('test.com'); + expect(authRequest.dataMap['userCode']).toBe('randomtestcode'); + } + if (step === 1) { + expect(message.request).toBeUndefined(); + expect(message.response).toBeDefined(); + const authResponse = message.response!; + expect(authResponse.identityId).toBe(testToken.identityId); + } + if (step > 1) fail('Too many steps'); + step++; + } + expect( + await identitiesManager.getToken( + testToken.providerId, + testToken.identityId, + ), + ).toEqual(testToken.providerToken); + await identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + }); + test('cannot authenticate invalid provider', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesAuthenticate: new IdentitiesAuthenticateHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesAuthenticate, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const request = { + providerId: '', + }; + const response = await rpcClient.methods.identitiesAuthenticate(request); + const reader = response.getReader(); + await testUtils.expectRemoteError( + reader.read(), + validationErrors.ErrorValidation, + ); + }); +}); diff --git a/tests/client/handlers/identitiesAuthenticatedGet.test.ts b/tests/client/handlers/identitiesAuthenticatedGet.test.ts new file mode 100644 index 000000000..65f11b905 --- /dev/null +++ b/tests/client/handlers/identitiesAuthenticatedGet.test.ts @@ -0,0 +1,340 @@ +import type { TLSConfig } from '@/network/types'; +import type { IdentityId, ProviderId } from '@/ids/index'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { IdentitiesAuthenticatedGetHandler } from '@/client/handlers/identitiesAuthenticatedGet'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import { identitiesAuthenticatedGet } from '@/client'; +import * as testsUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('identitiesClaim', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + const providerToken = { + accessToken: 'abc123', + }; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets an authenticated identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesAuthenticatedGet: new IdentitiesAuthenticatedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesAuthenticatedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + }; + provider.users['user1'] = user1; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + user1.providerId, + user1.identityId, + providerToken, + ); + const response = await rpcClient.methods.identitiesAuthenticatedGet({}); + const output = Array(); + for await (const providerMessage of response) { + output.push(providerMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual(user1); + }); + test('does not get an unauthenticated identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesAuthenticatedGet: new IdentitiesAuthenticatedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesAuthenticatedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + }; + provider.users['user1'] = user1; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + user1.providerId, + user1.identityId, + providerToken, + ); + await identitiesManager.delToken(user1.providerId, user1.identityId); + const response = await rpcClient.methods.identitiesAuthenticatedGet({}); + const output = Array(); + for await (const providerMessage of response) { + output.push(providerMessage); + } + expect(output).toHaveLength(0); + }); + test('gets authenticated identities from multiple providers', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesAuthenticatedGet: new IdentitiesAuthenticatedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesAuthenticatedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + }; + const user2 = { + providerId: provider1.id, + identityId: 'user2' as IdentityId, + }; + const user3 = { + providerId: provider2.id, + identityId: 'user3' as IdentityId, + }; + provider1.users['user1'] = user1; + provider1.users['user2'] = user2; + provider2.users['user3'] = user3; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + user1.providerId, + user1.identityId, + providerToken, + ); + await identitiesManager.putToken( + user2.providerId, + user2.identityId, + providerToken, + ); + await identitiesManager.putToken( + user3.providerId, + user3.identityId, + providerToken, + ); + const response = await rpcClient.methods.identitiesAuthenticatedGet({}); + const output = Array(); + for await (const providerMessage of response) { + output.push(providerMessage); + } + expect(output).toHaveLength(3); + expect(output[0]).toEqual(user1); + expect(output[1]).toEqual(user2); + expect(output[2]).toEqual(user3); + }); + test('gets authenticated identities a specific provider', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesAuthenticatedGet: new IdentitiesAuthenticatedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesAuthenticatedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + }; + const user2 = { + providerId: provider1.id, + identityId: 'user2' as IdentityId, + }; + const user3 = { + providerId: provider2.id, + identityId: 'user3' as IdentityId, + }; + provider1.users['user1'] = user1; + provider1.users['user2'] = user2; + provider2.users['user3'] = user3; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + user1.providerId, + user1.identityId, + providerToken, + ); + await identitiesManager.putToken( + user2.providerId, + user2.identityId, + providerToken, + ); + await identitiesManager.putToken( + user3.providerId, + user3.identityId, + providerToken, + ); + const response = await rpcClient.methods.identitiesAuthenticatedGet({ + providerId: provider2.id, + }); + const output = Array(); + for await (const providerMessage of response) { + output.push(providerMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual(user3); + }); +}); diff --git a/tests/client/handlers/identitiesClaim.test.ts b/tests/client/handlers/identitiesClaim.test.ts new file mode 100644 index 000000000..df601dc3a --- /dev/null +++ b/tests/client/handlers/identitiesClaim.test.ts @@ -0,0 +1,235 @@ +import type { TLSConfig } from '@/network/types'; +import type { IdentityId, ProviderId } from '@/ids/index'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type { Claim } from '@/claims/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { IdentitiesClaimHandler } from '@/client/handlers/identitiesClaim'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import * as nodesUtils from '@/nodes/utils'; +import { encodeProviderIdentityId } from '@/ids/index'; +import * as claimsUtils from '@/claims/utils'; +import * as validationErrors from '@/validation/errors'; +import { identitiesClaim } from '@/client'; +import * as testsUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; +import Token from '../../../src/tokens/Token'; +import * as testUtils from '../../utils'; +import Sigchain from '../../../src/sigchain/Sigchain'; + +describe('identitiesClaim', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + let mockedAddClaim: jest.SpyInstance; + let testProvider: TestProvider; + let sigchain: Sigchain; + const testToken = { + providerId: 'test-provider' as ProviderId, + identityId: 'test_user' as IdentityId, + providerToken: { + accessToken: 'abc123', + }, + }; + const issNodeKeypair = keysUtils.generateKeyPair(); + const issNodeId = keysUtils.publicKeyToNodeId(issNodeKeypair.publicKey); + const claimId = claimsUtils.createClaimIdGenerator(issNodeId)(); + const dummyClaim: ClaimLinkIdentity = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(issNodeId), + sub: encodeProviderIdentityId([testToken.providerId, testToken.identityId]), + jti: claimsUtils.encodeClaimId(claimId), + iat: 0, + nbf: 0, + exp: 0, + aud: '', + seq: 0, + prevClaimId: null, + prevDigest: null, + }; + const token = Token.fromPayload(dummyClaim); + token.signWithPrivateKey(issNodeKeypair); + const signedClaim = token.toSigned(); + + beforeEach(async () => { + mockedAddClaim = jest + .spyOn(Sigchain.prototype, 'addClaim') + .mockImplementation(async (payload, _, func) => { + const token = Token.fromPayload(payload); + // We need to call the function to resolve a promise in the code + func != null && (await func(token as unknown as Token)); + return [claimId, signedClaim]; + }); + + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: { + linkNodeAndIdentity: jest.fn(), + } as unknown as GestaltGraph, + keyRing, + sigchain, + logger, + }); + testProvider = new TestProvider(); + identitiesManager.registerProvider(testProvider); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await sigchain.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedAddClaim.mockRestore(); + }); + test('claims identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesClaim: new IdentitiesClaimHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesClaim, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup provider + // Need an authenticated identity + await identitiesManager.putToken( + testToken.providerId, + testToken.identityId, + testToken.providerToken, + ); + const request = { + providerId: testToken.providerId, + identityId: testToken.identityId, + }; + const response = await rpcClient.methods.identitiesClaim(request); + expect(response.claimId).toBe('0'); + expect(response.url).toBe('test.com'); + }); + test('cannot claim invalid identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesClaim: new IdentitiesClaimHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesClaim, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup provider + await testUtils.expectRemoteError( + rpcClient.methods.identitiesClaim({ + providerId: testToken.providerId, + identityId: '', + }), + validationErrors.ErrorValidation, + ); + await testUtils.expectRemoteError( + rpcClient.methods.identitiesClaim({ + providerId: '', + identityId: testToken.identityId, + }), + validationErrors.ErrorValidation, + ); + await testUtils.expectRemoteError( + rpcClient.methods.identitiesClaim({ + providerId: '', + identityId: '', + }), + validationErrors.ErrorValidation, + ); + }); +}); diff --git a/tests/client/handlers/identitiesInfoConnectedGet.test.ts b/tests/client/handlers/identitiesInfoConnectedGet.test.ts new file mode 100644 index 000000000..e545a6fdf --- /dev/null +++ b/tests/client/handlers/identitiesInfoConnectedGet.test.ts @@ -0,0 +1,1071 @@ +import type { TLSConfig } from '@/network/types'; +import type { IdentityId } from '@/ids/index'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type { ClientRPCResponseResult } from '@/client/types'; +import type { IdentityInfoMessage } from '@/client/handlers/types'; +import type { ProviderId } from '@/ids/index'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { IdentitiesInfoConnectedGetHandler } from '@/client/handlers/identitiesInfoConnectedGet'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import * as identitiesErrors from '@/identities/errors'; +import { identitiesInfoConnectedGet } from '@/client'; +import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; +import * as testsUtils from '../../utils'; + +describe('identitiesInfoConnectedGet', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + const testToken = { + identityId: 'test_user' as IdentityId, + providerToken: { + accessToken: 'abc123', + }, + }; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets connected identities from a single provider', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.providerToken, + ); + provider.users[testToken.identityId].connected = [ + user1.identityId, + user2.identityId, + ]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + providerIdList: [provider.id], + identityId: '', + disconnected: false, + searchTermList: [], + }); + const output = Array>(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + identityId: user2.identityId, + providerId: user2.providerId, + url: user2.url, + }); + }); + test('gets connected identities to a particular identity id', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.providerToken, + ); + provider.users[testToken.identityId].connected = [user1.identityId]; + await identitiesManager.putToken( + provider.id, + 'otherAuthenticatedId' as IdentityId, + testToken.providerToken, + ); + provider.users['otherAuthenticatedId'] = { connected: [user2.identityId] }; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + authIdentityId: 'otherAuthenticatedId', + providerIdList: [provider.id], + identityId: '', + disconnected: false, + searchTermList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user2.email, + name: user2.name, + identityId: user2.identityId, + providerId: user2.providerId, + url: user2.url, + }); + }); + test('gets connected identities from multiple providers', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.providerToken, + ); + provider2.users[testToken.identityId].connected = [user2.identityId]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + identityId: '', + disconnected: false, + searchTermList: [], + providerIdList: [provider1.id, provider2.id], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + identityId: user2.identityId, + providerId: user2.providerId, + url: user2.url, + }); + }); + test('gets connected identities from all providers', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.providerToken, + ); + provider2.users[testToken.identityId].connected = [user2.identityId]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + providerIdList: [], + identityId: '', + disconnected: false, + searchTermList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + identityId: user2.identityId, + providerId: user2.providerId, + url: user2.url, + }); + }); + test('searches for identities matching a search term', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.providerToken, + ); + provider.users[testToken.identityId].connected = [ + user1.identityId, + user2.identityId, + ]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + searchTermList: ['1'], + identityId: '', + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + }); + test('searches for identities matching multiple search terms', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup providers + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.providerToken, + ); + provider.users[testToken.identityId].connected = [ + user1.identityId, + user2.identityId, + ]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + searchTermList: ['1', '2'], + identityId: '', + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + identityId: user2.identityId, + providerId: user2.providerId, + url: user2.url, + }); + }); + test('searches for identities matching a search term across multiple providers', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.providerToken, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + provider2.users[testToken.identityId].connected = [user2.identityId]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + searchTermList: ['1'], + identityId: '', + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + identityId: user2.identityId, + providerId: user2.providerId, + url: user2.url, + }); + }); + test('gets no connected identities', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.providerToken, + ); + provider.users[testToken.identityId].connected = [ + user1.identityId, + user2.identityId, + ]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + limit: 0, + identityId: '', + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(0); + }); + test('gets one connected identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.providerToken, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + provider2.users[testToken.identityId].connected = [user2.identityId]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + limit: 1, + identityId: '', + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + }); + test('cannot get more identities than available', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.providerToken, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + provider2.users[testToken.identityId].connected = [user2.identityId]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + limit: 3, + identityId: '', + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + identityId: user2.identityId, + providerId: user2.providerId, + url: user2.url, + }); + }); + test('can only get from authenticated providers', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + provider2.users[testToken.identityId].connected = [user2.identityId]; + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + identityId: '', + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + }); + test('gets disconnected identities', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoConnectedGet: new IdentitiesInfoConnectedGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoConnectedGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // This feature is not implemented yet - should throw error + const response = await rpcClient.methods.identitiesInfoConnectedGet({ + disconnected: true, + identityId: '', + providerIdList: [], + }); + const reader = response.getReader(); + await testUtils.expectRemoteError( + reader.read(), + identitiesErrors.ErrorProviderUnimplemented, + ); + }); +}); diff --git a/tests/client/handlers/identitiesInfoGet.test.ts b/tests/client/handlers/identitiesInfoGet.test.ts new file mode 100644 index 000000000..e9ef50369 --- /dev/null +++ b/tests/client/handlers/identitiesInfoGet.test.ts @@ -0,0 +1,640 @@ +import type { TLSConfig } from '@/network/types'; +import type { IdentityId } from '@/ids/index'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import type { IdentityInfoMessage } from '@/client/handlers/types'; +import type { ProviderId } from '@/ids/index'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { IdentitiesInfoGetHandler } from '@/client/handlers/identitiesInfoGet'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import { identitiesInfoGet } from '@/client'; +import TestProvider from '../../identities/TestProvider'; +import * as testsUtils from '../../utils'; + +describe('identitiesInfoConnectedGet', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + const testToken = { + identityId: 'test_user' as IdentityId, + providerToken: { + accessToken: 'abc123', + }, + }; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets an identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoGet: new IdentitiesInfoGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.providerToken, + ); + const response = await rpcClient.methods.identitiesInfoGet({ + identityId: user1.identityId, + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + }); + test('searches for a handle across providers', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoGet: new IdentitiesInfoGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.providerToken, + ); + const response = await rpcClient.methods.identitiesInfoGet({ + identityId: 'user1', + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + identityId: user2.identityId, + providerId: user2.providerId, + url: user2.url, + }); + }); + test('searches for identities matching a search term', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoGet: new IdentitiesInfoGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'abc', + email: 'abc@test.com', + url: 'provider1.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'def', + email: 'def@test.com', + url: 'provider2.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.providerToken, + ); + const response = await rpcClient.methods.identitiesInfoGet({ + identityId: 'user1', + searchTermList: ['abc'], + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + }); + test('gets no connected identities', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoGet: new IdentitiesInfoGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.providerToken, + ); + const response = await rpcClient.methods.identitiesInfoGet({ + identityId: 'user1', + limit: 0, + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(0); + }); + test('gets one connected identity', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoGet: new IdentitiesInfoGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.providerToken, + ); + const response = await rpcClient.methods.identitiesInfoGet({ + identityId: 'user1', + limit: 1, + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + }); + test('cannot get more identities than available', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoGet: new IdentitiesInfoGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.providerToken, + ); + const response = await rpcClient.methods.identitiesInfoGet({ + identityId: 'user1', + limit: 3, + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + identityId: user2.identityId, + providerId: user2.providerId, + url: user2.url, + }); + }); + test('can only get from authenticated providers', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInfoGet: new IdentitiesInfoGetHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInfoGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.providerToken, + ); + const response = await rpcClient.methods.identitiesInfoGet({ + identityId: 'user1', + disconnected: false, + providerIdList: [], + }); + const output = Array(); + for await (const identityInfoMessage of response) { + output.push(identityInfoMessage); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + identityId: user1.identityId, + providerId: user1.providerId, + url: user1.url, + }); + }); +}); diff --git a/tests/client/handlers/identitiesInvite.test.ts b/tests/client/handlers/identitiesInvite.test.ts new file mode 100644 index 000000000..15abf1d77 --- /dev/null +++ b/tests/client/handlers/identitiesInvite.test.ts @@ -0,0 +1,183 @@ +import type { TLSConfig } from '@/network/types'; +import type { IdentityId, ProviderId } from '@/ids/index'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type { Claim } from '@/claims/types'; +import type { ClaimLinkIdentity } from '@/claims/payloads'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { IdentitiesInviteHandler } from '@/client/handlers/identitiesInvite'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import * as nodesUtils from '@/nodes/utils'; +import { encodeProviderIdentityId } from '@/ids/index'; +import * as claimsUtils from '@/claims/utils'; +import { identitiesInvite } from '@/client'; +import * as testsUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; +import Token from '../../../src/tokens/Token'; +import Sigchain from '../../../src/sigchain/Sigchain'; + +describe('identitiesClaim', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + let mockedAddClaim: jest.SpyInstance; + let testProvider: TestProvider; + let sigchain: Sigchain; + const testToken = { + providerId: 'test-provider' as ProviderId, + identityId: 'test_user' as IdentityId, + providerToken: { + accessToken: 'abc123', + }, + }; + const issNodeKeypair = keysUtils.generateKeyPair(); + const issNodeId = keysUtils.publicKeyToNodeId(issNodeKeypair.publicKey); + const claimId = claimsUtils.createClaimIdGenerator(issNodeId)(); + const dummyClaim: ClaimLinkIdentity = { + typ: 'ClaimLinkIdentity', + iss: nodesUtils.encodeNodeId(issNodeId), + sub: encodeProviderIdentityId([testToken.providerId, testToken.identityId]), + jti: claimsUtils.encodeClaimId(claimId), + iat: 0, + nbf: 0, + exp: 0, + aud: '', + seq: 0, + prevClaimId: null, + prevDigest: null, + }; + const token = Token.fromPayload(dummyClaim); + token.signWithPrivateKey(issNodeKeypair); + const signedClaim = token.toSigned(); + + beforeEach(async () => { + mockedAddClaim = jest + .spyOn(Sigchain.prototype, 'addClaim') + .mockImplementation(async (payload, _, func) => { + const token = Token.fromPayload(payload); + // We need to call the function to resolve a promise in the code + func != null && (await func(token as unknown as Token)); + return [claimId, signedClaim]; + }); + + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: { + linkNodeAndIdentity: jest.fn(), + } as unknown as GestaltGraph, + keyRing, + sigchain, + logger, + }); + testProvider = new TestProvider(); + identitiesManager.registerProvider(testProvider); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await sigchain.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedAddClaim.mockRestore(); + }); + test('Invite a node', async () => { + // Setup + const acl = { + setNodeAction: jest.fn(), + }; + const notificationsManager = { + sendNotification: jest.fn(), + }; + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesInvite: new IdentitiesInviteHandler({ + acl: acl as unknown as ACL, + notificationsManager: + notificationsManager as unknown as NotificationsManager, + logger, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesInvite, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const nodeId = testsUtils.generateRandomNodeId(); + await rpcClient.methods.identitiesInvite({ + nodeIdEncoded: nodesUtils.encodeNodeId(nodeId), + }); + expect(acl.setNodeAction).toHaveBeenCalledWith(nodeId, 'claim'); + expect(notificationsManager.sendNotification).toHaveBeenCalledWith(nodeId, { + type: 'GestaltInvite', + }); + }); +}); diff --git a/tests/client/handlers/identitiesProvidersList.test.ts b/tests/client/handlers/identitiesProvidersList.test.ts new file mode 100644 index 000000000..28c87ff7b --- /dev/null +++ b/tests/client/handlers/identitiesProvidersList.test.ts @@ -0,0 +1,122 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import type { ProviderId } from '@/ids/index'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { IdentitiesProvidersListHandler } from '@/client/handlers/identitiesProvidersList'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import { identitiesProvidersList } from '@/client'; +import * as testsUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('identitiesInfoConnectedGet', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + const id1 = 'provider1' as ProviderId; + const id2 = 'provider2' as ProviderId; + const providers = {}; + providers[id1] = new TestProvider(); + providers[id2] = new TestProvider(); + let mockedGetProviders: jest.SpyInstance; + + beforeEach(async () => { + mockedGetProviders = jest + .spyOn(IdentitiesManager.prototype, 'getProviders') + .mockReturnValue(providers); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + mockedGetProviders.mockRestore(); + }); + test('identitiesProvidersList', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesProvidersList: new IdentitiesProvidersListHandler({ + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesProvidersList, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const response = await rpcClient.methods.identitiesProvidersList({}); + expect(response.providerIds).toContain('provider1'); + expect(response.providerIds).toContain('provider2'); + }); +}); diff --git a/tests/client/handlers/identitiesTokenPutDeleteGet.test.ts b/tests/client/handlers/identitiesTokenPutDeleteGet.test.ts new file mode 100644 index 000000000..73d45fe19 --- /dev/null +++ b/tests/client/handlers/identitiesTokenPutDeleteGet.test.ts @@ -0,0 +1,154 @@ +import type { TLSConfig } from '@/network/types'; +import type { IdentityId } from '@/ids/index'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import type { ProviderId } from '@/ids/index'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { IdentitiesTokenPutHandler } from '@/client/handlers/identitiesTokenPut'; +import { IdentitiesTokenDeleteHandler } from '@/client/handlers/identitiesTokenDelete'; +import { IdentitiesTokenGetHandler } from '@/client/handlers/identitiesTokenGet'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import { + identitiesTokenDelete, + identitiesTokenGet, + identitiesTokenPut, +} from '@/client'; +import * as testsUtils from '../../utils'; + +describe('identitiesTokenPutDeleteGet', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + const testToken = { + providerId: 'test-provider' as ProviderId, + identityId: 'test_user' as IdentityId, + providerToken: { + accessToken: 'abc123', + }, + }; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('puts/deletes/gets tokens', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + identitiesTokenPut: new IdentitiesTokenPutHandler({ + identitiesManager, + db, + }), + identitiesTokenDelete: new IdentitiesTokenDeleteHandler({ + db, + identitiesManager, + }), + identitiesTokenGet: new IdentitiesTokenGetHandler({ + db, + identitiesManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + identitiesTokenPut, + identitiesTokenDelete, + identitiesTokenGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Put token + const providerMessage = { + providerId: testToken.providerId, + identityId: testToken.identityId, + }; + await rpcClient.methods.identitiesTokenPut({ + ...providerMessage, + token: testToken.providerToken, + }); + // Get token + const getPutResponse = await rpcClient.methods.identitiesTokenGet( + providerMessage, + ); + expect(getPutResponse.token).toStrictEqual(testToken.providerToken); + // Delete token + await rpcClient.methods.identitiesTokenDelete(providerMessage); + // Check token was deleted + const getDeleteResponse = await rpcClient.methods.identitiesTokenGet( + providerMessage, + ); + expect(getDeleteResponse.token).toBeUndefined(); + }); +}); diff --git a/tests/client/handlers/keysCertsChainGet.test.ts b/tests/client/handlers/keysCertsChainGet.test.ts new file mode 100644 index 000000000..fa9737e81 --- /dev/null +++ b/tests/client/handlers/keysCertsChainGet.test.ts @@ -0,0 +1,136 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import type { CertificatePEM } from '@/keys/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { KeysCertsChainGetHandler } from '@/client/handlers/keysCertsChainGet'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import CertManager from '@/keys/CertManager'; +import TaskManager from '@/tasks/TaskManager'; +import { keysCertsChainGet } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('keysCertsChainGet', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const certs = ['cert1', 'cert2', 'cert3'] as Array; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + let taskManager: TaskManager; + let certManager: CertManager; + let mockedGetRootCertChainPems: jest.SpyInstance; + + beforeEach(async () => { + mockedGetRootCertChainPems = jest + .spyOn(CertManager.prototype, 'getCertPEMsChain') + .mockResolvedValue(certs); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + taskManager = await TaskManager.createTaskManager({ + db, + logger, + }); + certManager = await CertManager.createCertManager({ + db, + keyRing, + taskManager, + logger, + }); + }); + afterEach(async () => { + mockedGetRootCertChainPems.mockRestore(); + await certManager.stop(); + await taskManager.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets the root certchain', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + keysCertsChainGet: new KeysCertsChainGetHandler({ + certManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + keysCertsChainGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const response = await rpcClient.methods.keysCertsChainGet({}); + const output = Array(); + for await (const cert of response) { + output.push(cert.cert); + } + expect(output).toEqual(certs); + }); +}); diff --git a/tests/client/handlers/keysCertsGet.test.ts b/tests/client/handlers/keysCertsGet.test.ts new file mode 100644 index 000000000..06f67ba28 --- /dev/null +++ b/tests/client/handlers/keysCertsGet.test.ts @@ -0,0 +1,131 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import type { CertificatePEM } from '@/keys/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { KeysCertsGetHandler } from '@/client/handlers/keysCertsGet'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import CertManager from '@/keys/CertManager'; +import TaskManager from '@/tasks/TaskManager'; +import { keysCertsGet } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('keysCertsGet', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + let taskManager: TaskManager; + let certManager: CertManager; + let mockedGetRootCertPem: jest.SpyInstance; + + beforeEach(async () => { + mockedGetRootCertPem = jest + .spyOn(CertManager.prototype, 'getCurrentCertPEM') + .mockResolvedValue('rootCertPem' as CertificatePEM); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + taskManager = await TaskManager.createTaskManager({ + db, + logger, + }); + certManager = await CertManager.createCertManager({ + db, + keyRing, + taskManager, + logger, + }); + }); + afterEach(async () => { + mockedGetRootCertPem.mockRestore(); + await certManager.stop(); + await taskManager.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets the root certificate', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + keysCertsGet: new KeysCertsGetHandler({ + certManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + keysCertsGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const response = await rpcClient.methods.keysCertsGet({}); + expect(response.cert).toBe('rootCertPem'); + }); +}); diff --git a/tests/client/handlers/keysEncryptDecrypt.test.ts b/tests/client/handlers/keysEncryptDecrypt.test.ts new file mode 100644 index 000000000..a062802a3 --- /dev/null +++ b/tests/client/handlers/keysEncryptDecrypt.test.ts @@ -0,0 +1,120 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { KeysEncryptHandler } from '@/client/handlers/keysEncrypt'; +import { KeysDecryptHandler } from '@/client/handlers/keysDecrypt'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import { keysDecrypt, keysEncrypt } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('keysEncryptDecrypt', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('encrypts and decrypts data', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + keysEncrypt: new KeysEncryptHandler({ + keyRing, + }), + keysDecrypt: new KeysDecryptHandler({ + keyRing, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + keysEncrypt, + keysDecrypt, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const plainText = Buffer.from('abc'); + const publicKeyJWK = keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey); + const encrypted = await rpcClient.methods.keysEncrypt({ + data: plainText.toString('binary'), + publicKeyJwk: publicKeyJWK, + }); + const response = await rpcClient.methods.keysDecrypt(encrypted); + expect(response.data).toBe('abc'); + }); +}); diff --git a/tests/client/handlers/keysKeyPair.test.ts b/tests/client/handlers/keysKeyPair.test.ts new file mode 100644 index 000000000..5a7307e86 --- /dev/null +++ b/tests/client/handlers/keysKeyPair.test.ts @@ -0,0 +1,124 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { KeysKeyPairHandler } from '@/client/handlers/keysKeyPair'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import { keysKeyPair } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('keysKeyPair', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets the keypair', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + keysKeyPair: new KeysKeyPairHandler({ + keyRing, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + keysKeyPair, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const response = await rpcClient.methods.keysKeyPair({ + password: 'password', + }); + expect(response.privateKeyJwe).toEqual({ + ciphertext: expect.any(String), + iv: expect.any(String), + protected: expect.any(String), + tag: expect.any(String), + }); + expect(response.publicKeyJwk).toEqual({ + alg: expect.any(String), + crv: expect.any(String), + ext: expect.any(Boolean), + key_ops: expect.any(Array), + kty: expect.any(String), + x: expect.any(String), + }); + }); +}); diff --git a/tests/client/handlers/keysKeyPairRenew.test.ts b/tests/client/handlers/keysKeyPairRenew.test.ts new file mode 100644 index 000000000..cb70d369e --- /dev/null +++ b/tests/client/handlers/keysKeyPairRenew.test.ts @@ -0,0 +1,125 @@ +import type { TLSConfig } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { KeysKeyPairRenewHandler } from '@/client/handlers/keysKeyPairRenew'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import { NodeManager } from '@/nodes'; +import PolykeyAgent from '@/PolykeyAgent'; +import { keysKeyPairRenew } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('keysKeyPairRenew', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let pkAgent: PolykeyAgent; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let mockedRefreshBuckets: jest.SpyInstance; + + beforeEach(async () => { + mockedRefreshBuckets = jest.spyOn(NodeManager.prototype, 'resetBuckets'); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const nodePath = path.join(dataDir, 'polykey'); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + logger, + keyRingConfig: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, + }); + tlsConfig = await testsUtils.createTLSConfig(pkAgent.keyRing.keyPair); + }); + afterEach(async () => { + mockedRefreshBuckets.mockRestore(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await pkAgent.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('renews the root key pair', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + keysKeyPairRenew: new KeysKeyPairRenewHandler({ + certManager: pkAgent.certManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [pkAgent.keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + keysKeyPairRenew, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const rootKeyPair1 = pkAgent.keyRing.keyPair; + const nodeId1 = pkAgent.keyRing.getNodeId(); + // @ts-ignore - get protected property + const fwdTLSConfig1 = pkAgent.proxy.tlsConfig; + const expectedTLSConfig1: TLSConfig = { + keyPrivatePem: keysUtils.privateKeyToPEM(rootKeyPair1.privateKey), + certChainPem: await pkAgent.certManager.getCertPEMsChainPEM(), + }; + const nodeIdStatus1 = pkAgent.keyRing.getNodeId(); + expect(mockedRefreshBuckets).toHaveBeenCalledTimes(0); + expect(fwdTLSConfig1).toEqual(expectedTLSConfig1); + expect(nodeId1.equals(nodeIdStatus1)).toBe(true); + // Run command + await rpcClient.methods.keysKeyPairRenew({ + password: 'somepassphrase', + }); + const rootKeyPair2 = pkAgent.keyRing.keyPair; + const nodeId2 = pkAgent.keyRing.getNodeId(); + // @ts-ignore - get protected property + const fwdTLSConfig2 = pkAgent.proxy.tlsConfig; + const expectedTLSConfig2: TLSConfig = { + keyPrivatePem: keysUtils.privateKeyToPEM(rootKeyPair2.privateKey), + certChainPem: await pkAgent.certManager.getCertPEMsChainPEM(), + }; + const nodeIdStatus2 = pkAgent.keyRing.getNodeId(); + expect(mockedRefreshBuckets).toHaveBeenCalled(); + expect(fwdTLSConfig2).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); + }); +}); diff --git a/tests/client/handlers/keysKeyPairReset.test.ts b/tests/client/handlers/keysKeyPairReset.test.ts new file mode 100644 index 000000000..52fbca0c2 --- /dev/null +++ b/tests/client/handlers/keysKeyPairReset.test.ts @@ -0,0 +1,125 @@ +import type { TLSConfig } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { KeysKeyPairResethandler } from '@/client/handlers/keysKeyPairReset'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import PolykeyAgent from '@/PolykeyAgent'; +import { NodeManager } from '@/nodes'; +import { keysKeyPairReset } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('keysKeyPairReset', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let pkAgent: PolykeyAgent; + let tlsConfig: TLSConfig; + let mockedRefreshBuckets: jest.SpyInstance; + + beforeEach(async () => { + mockedRefreshBuckets = jest.spyOn(NodeManager.prototype, 'resetBuckets'); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const nodePath = path.join(dataDir, 'polykey'); + pkAgent = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + logger, + keyRingConfig: { + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }, + }); + tlsConfig = await testsUtils.createTLSConfig(pkAgent.keyRing.keyPair); + }); + afterEach(async () => { + mockedRefreshBuckets.mockRestore(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await pkAgent.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('resets the root key pair', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + keysKeyPairReset: new KeysKeyPairResethandler({ + certManager: pkAgent.certManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [pkAgent.keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + keysKeyPairReset, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const rootKeyPair1 = pkAgent.keyRing.keyPair; + const nodeId1 = pkAgent.keyRing.getNodeId(); + // @ts-ignore - get protected property + const fwdTLSConfig1 = pkAgent.proxy.tlsConfig; + const expectedTLSConfig1: TLSConfig = { + keyPrivatePem: keysUtils.privateKeyToPEM(rootKeyPair1.privateKey), + certChainPem: await pkAgent.certManager.getCertPEMsChainPEM(), + }; + const nodeIdStatus1 = (await pkAgent.status.readStatus())!.data.nodeId; + expect(mockedRefreshBuckets).not.toHaveBeenCalled(); + expect(fwdTLSConfig1).toEqual(expectedTLSConfig1); + expect(nodeId1.equals(nodeIdStatus1)).toBe(true); + // Run command + await rpcClient.methods.keysKeyPairReset({ + password: 'somepassphrase', + }); + const rootKeyPair2 = pkAgent.keyRing.keyPair; + const nodeId2 = pkAgent.keyRing.getNodeId(); + // @ts-ignore - get protected property + const fwdTLSConfig2 = pkAgent.proxy.tlsConfig; + const expectedTLSConfig2: TLSConfig = { + keyPrivatePem: keysUtils.privateKeyToPEM(rootKeyPair2.privateKey), + certChainPem: await pkAgent.certManager.getCertPEMsChainPEM(), + }; + const nodeIdStatus2 = (await pkAgent.status.readStatus())!.data.nodeId; + expect(mockedRefreshBuckets).toHaveBeenCalled(); + expect(fwdTLSConfig2).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); + }); +}); diff --git a/tests/client/handlers/keysPasswordChange.test.ts b/tests/client/handlers/keysPasswordChange.test.ts new file mode 100644 index 000000000..6efbe24ac --- /dev/null +++ b/tests/client/handlers/keysPasswordChange.test.ts @@ -0,0 +1,114 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { KeysPasswordChangeHandler } from '@/client/handlers/keysPasswordChange'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import { keysPasswordChange } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('keysPasswordChange', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('changes the password', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + keysPasswordChange: new KeysPasswordChangeHandler({ + keyRing, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + keysPasswordChange, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + await rpcClient.methods.keysPasswordChange({ + password: 'newpassword', + }); + await keyRing.stop(); + await keyRing.start({ + password: 'newpassword', + }); + }); +}); diff --git a/tests/client/handlers/keysPublicKey.test.ts b/tests/client/handlers/keysPublicKey.test.ts new file mode 100644 index 000000000..f90c2828c --- /dev/null +++ b/tests/client/handlers/keysPublicKey.test.ts @@ -0,0 +1,116 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { KeysPublicKeyHandler } from '@/client/handlers/keysPublicKey'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import { keysPublicKey } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('keysPublicKey', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets the public key', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + keysPublicKey: new KeysPublicKeyHandler({ + keyRing, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + keysPublicKey, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const response = await rpcClient.methods.keysPublicKey({}); + expect(response.publicKeyJwk).toEqual({ + alg: expect.any(String), + crv: expect.any(String), + ext: expect.any(Boolean), + key_ops: expect.any(Array), + kty: expect.any(String), + x: expect.any(String), + }); + }); +}); diff --git a/tests/client/handlers/keysSignVerify.test.ts b/tests/client/handlers/keysSignVerify.test.ts new file mode 100644 index 000000000..1396d7141 --- /dev/null +++ b/tests/client/handlers/keysSignVerify.test.ts @@ -0,0 +1,124 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type Sigchain from '../../../src/sigchain/Sigchain'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { KeysSignHandler } from '@/client/handlers/keysSign'; +import { KeysVerifyHandler } from '@/client/handlers/keysVerify'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import IdentitiesManager from '@/identities/IdentitiesManager'; +import { publicKeyToJWK } from '@/keys/utils'; +import { keysSign, keysVerify } from '@/client'; +import * as testsUtils from '../../utils'; + +describe('keysSignVerify', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let identitiesManager: IdentitiesManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + gestaltGraph: {} as GestaltGraph, + keyRing: {} as KeyRing, + sigchain: {} as Sigchain, + logger, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await identitiesManager.stop(); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('signs and verifies with root keypair', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + keysSign: new KeysSignHandler({ + keyRing, + }), + keysVerify: new KeysVerifyHandler({ + keyRing, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + keysSign, + keysVerify, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const data = Buffer.from('abc'); + const signed = await rpcClient.methods.keysSign({ + data: data.toString('binary'), + }); + const publicKeyJWK = publicKeyToJWK(keyRing.keyPair.publicKey); + const response = await rpcClient.methods.keysVerify({ + data: data.toString('binary'), + signature: signed.signature, + publicKeyJwk: publicKeyJWK, + }); + expect(response.success).toBeTruthy(); + }); +}); diff --git a/tests/client/handlers/nodesAdd.test.ts b/tests/client/handlers/nodesAdd.test.ts new file mode 100644 index 000000000..f98b06878 --- /dev/null +++ b/tests/client/handlers/nodesAdd.test.ts @@ -0,0 +1,254 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type { NodeIdEncoded } from '@/ids'; +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { NodesAddHandler } from '@/client/handlers/nodesAdd'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import * as nodesUtils from '@/nodes/utils'; +import NodeManager from '@/nodes/NodeManager'; +import NodeGraph from '@/nodes/NodeGraph'; +import NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import * as validationErrors from '@/validation/errors'; +import { nodesAdd } from '@/client'; +import Proxy from '../../../src/network/Proxy'; +import TaskManager from '../../../src/tasks/TaskManager'; +import * as testsUtils from '../../utils'; +import Sigchain from '../../../src/sigchain/Sigchain'; + +describe('nodesAdd', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const authToken = 'abc123'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let nodeGraph: NodeGraph; + let taskManager: TaskManager; + let nodeConnectionManager: NodeConnectionManager; + let nodeManager: NodeManager; + let sigchain: Sigchain; + let proxy: Proxy; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + proxy = new Proxy({ + authToken, + logger, + }); + await proxy.start({ + tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), + serverHost: '127.0.0.1' as Host, + serverPort: 0 as Port, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyRing, + logger: logger.getChild('NodeGraph'), + }); + taskManager = await TaskManager.createTaskManager({ + db, + logger, + lazy: true, + }); + nodeConnectionManager = new NodeConnectionManager({ + keyRing, + nodeGraph, + proxy, + taskManager, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + nodeManager = new NodeManager({ + db, + keyRing, + nodeConnectionManager, + nodeGraph, + sigchain, + taskManager, + gestaltGraph: {} as GestaltGraph, + logger, + }); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + await taskManager.startProcessing(); + }); + afterEach(async () => { + await taskManager.stopProcessing(); + await taskManager.stopTasks(); + await nodeGraph.stop(); + await nodeConnectionManager.stop(); + await nodeManager.stop(); + await sigchain.stop(); + await proxy.stop(); + await db.stop(); + await keyRing.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await keyRing.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('adds a node', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + nodesAdd: new NodesAddHandler({ + db, + nodeManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + nodesAdd, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + await rpcClient.methods.nodesAdd({ + nodeIdEncoded: + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded, + host: '127.0.0.1', + port: 11111, + ping: false, + force: false, + }); + const result = await nodeGraph.getNode( + nodesUtils.decodeNodeId( + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', + )!, + ); + expect(result).toBeDefined(); + expect(result!.address).toEqual({ host: '127.0.0.1', port: 11111 }); + }); + test('cannot add invalid node', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + nodesAdd: new NodesAddHandler({ + db, + nodeManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + nodesAdd, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + + // Invalid host + await testsUtils.expectRemoteError( + rpcClient.methods.nodesAdd({ + nodeIdEncoded: + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded, + host: '', + port: 11111, + ping: false, + force: false, + }), + validationErrors.ErrorValidation, + ); + // Invalid port + await testsUtils.expectRemoteError( + rpcClient.methods.nodesAdd({ + nodeIdEncoded: + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded, + host: '127.0.0.1', + port: 111111, + ping: false, + force: false, + }), + validationErrors.ErrorValidation, + ); + // Invalid nodeid + await testsUtils.expectRemoteError( + rpcClient.methods.nodesAdd({ + nodeIdEncoded: 'nodeId' as NodeIdEncoded, + host: '127.0.0.1', + port: 11111, + ping: false, + force: false, + }), + validationErrors.ErrorValidation, + ); + }); +}); diff --git a/tests/client/service/nodesClaim.test.ts b/tests/client/handlers/nodesClaim.test.ts similarity index 53% rename from tests/client/service/nodesClaim.test.ts rename to tests/client/handlers/nodesClaim.test.ts index ec08afbc0..1bc4a0408 100644 --- a/tests/client/service/nodesClaim.test.ts +++ b/tests/client/handlers/nodesClaim.test.ts @@ -1,41 +1,42 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type { NodeIdEncoded } from '@/ids'; import type { Notification } from '@/notifications/types'; -import type { NodeIdEncoded } from '@/ids/types'; import type { Host, Port } from '@/network/types'; -import type GestaltGraph from 'gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; -import TaskManager from '@/tasks/TaskManager'; import KeyRing from '@/keys/KeyRing'; -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 Proxy from '@/network/Proxy'; -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 RPCServer from '@/rpc/RPCServer'; +import { NodesClaimHandler } from '@/client/handlers/nodesClaim'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; import * as validationErrors from '@/validation/errors'; -import * as keysUtils from '@/keys/utils/index'; +import { nodesClaim } from '@/client'; +import * as testsUtils from '../../utils'; +import ACL from '../../../src/acl/ACL'; +import Proxy from '../../../src/network/Proxy'; +import NodeGraph from '../../../src/nodes/NodeGraph'; +import TaskManager from '../../../src/tasks/TaskManager'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import NodeManager from '../../../src/nodes/NodeManager'; +import NotificationsManager from '../../../src/notifications/NotificationsManager'; import * as testUtils from '../../utils'; -import * as testsUtils from '../../utils/index'; +import Sigchain from '../../../src/sigchain/Sigchain'; describe('nodesClaim', () => { - const logger = new Logger('nodesClaim test', LogLevel.WARN, [ - new StreamHandler(), + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; + const password = 'helloWorld'; + const host = '127.0.0.1'; + const authToken = 'abc123'; const dummyNotification: Notification = { typ: 'notification', data: { @@ -45,10 +46,25 @@ describe('nodesClaim', () => { sub: 'test' as NodeIdEncoded, isRead: false, }; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let nodeGraph: NodeGraph; + let taskManager: TaskManager; + let nodeConnectionManager: NodeConnectionManager; + let nodeManager: NodeManager; + let notificationsManager: NotificationsManager; + let acl: ACL; + let sigchain: Sigchain; + let proxy: Proxy; let mockedFindGestaltInvite: jest.SpyInstance; let mockedSendNotification: jest.SpyInstance; let mockedClaimNode: jest.SpyInstance; - beforeAll(async () => { + + beforeEach(async () => { mockedFindGestaltInvite = jest .spyOn(NotificationsManager.prototype, 'findGestaltInvite') .mockResolvedValueOnce(undefined) @@ -59,31 +75,15 @@ describe('nodesClaim', () => { mockedClaimNode = jest .spyOn(NodeManager.prototype, 'claimNode') .mockResolvedValue(undefined); - }); - afterAll(async () => { - mockedFindGestaltInvite.mockRestore(); - mockedSendNotification.mockRestore(); - mockedClaimNode.mockRestore(); - }); - const authToken = 'abc123'; - let dataDir: string; - let nodeGraph: NodeGraph; - let taskManager: TaskManager; - let nodeConnectionManager: NodeConnectionManager; - let nodeManager: NodeManager; - let notificationsManager: NotificationsManager; - let acl: ACL; - let sigchain: Sigchain; - let proxy: Proxy; - let db: DB; - let keyRing: KeyRing; - 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'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); keyRing = await KeyRing.createKeyRing({ password, keysPath, @@ -92,11 +92,8 @@ describe('nodesClaim', () => { passwordMemLimit: keysUtils.passwordMemLimits.min, strictMemoryLock: false, }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + acl = await ACL.createACL({ db, logger, @@ -156,32 +153,15 @@ describe('nodesClaim', () => { keyRing, logger, }); - const clientService = { - nodesClaim: nodesClaim({ - authenticate, - nodeManager, - logger, - db, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); }); afterEach(async () => { + mockedFindGestaltInvite.mockRestore(); + mockedSendNotification.mockRestore(); + mockedClaimNode.mockRestore(); await taskManager.stopProcessing(); await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); await nodeConnectionManager.stop(); await nodeManager.stop(); await nodeGraph.stop(); @@ -198,24 +178,82 @@ describe('nodesClaim', () => { }); }); test('claims a node', async () => { - const request = new nodesPB.Claim(); - request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); - request.setForceInvite(false); - const response = await grpcClient.nodesClaim( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.StatusMessage); - expect(response.getSuccess()).toBeTruthy(); + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + nodesClaim: new NodesClaimHandler({ + db, + nodeManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + nodesClaim, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const response = await rpcClient.methods.nodesClaim({ + nodeIdEncoded: + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded, + forceInvite: false, + }); + expect(response.success).toBeTruthy(); }); test('cannot claim an invalid node', async () => { - const request = new nodesPB.Claim(); - request.setNodeId('nodeId'); + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + nodesClaim: new NodesClaimHandler({ + db, + nodeManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + nodesClaim, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test await testUtils.expectRemoteError( - grpcClient.nodesClaim( - request, - clientUtils.encodeAuthFromPassword(password), - ), + rpcClient.methods.nodesClaim({ + nodeIdEncoded: 'nodeId' as NodeIdEncoded, + }), validationErrors.ErrorValidation, ); }); diff --git a/tests/client/handlers/nodesFind.test.ts b/tests/client/handlers/nodesFind.test.ts new file mode 100644 index 000000000..fdaa8e336 --- /dev/null +++ b/tests/client/handlers/nodesFind.test.ts @@ -0,0 +1,209 @@ +import type { TLSConfig } from '@/network/types'; +import type { NodeIdEncoded } from '@/ids/index'; +import type NodeManager from '../../../src/nodes/NodeManager'; +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { NodesFindHandler } from '@/client/handlers/nodesFind'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; +import * as validationErrors from '@/validation/errors'; +import { nodesFind } from '@/client'; +import * as testsUtils from '../../utils'; +import * as testUtils from '../../utils'; +import Proxy from '../../../src/network/Proxy'; +import Sigchain from '../../../src/sigchain/Sigchain'; +import NodeGraph from '../../../src/nodes/NodeGraph'; +import TaskManager from '../../../src/tasks/TaskManager'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; + +describe('nodesFind', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const authToken = 'abc123'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let nodeGraph: NodeGraph; + let taskManager: TaskManager; + let nodeConnectionManager: NodeConnectionManager; + let sigchain: Sigchain; + let proxy: Proxy; + let mockedFindNode: jest.SpyInstance; + + beforeEach(async () => { + mockedFindNode = jest + .spyOn(NodeConnectionManager.prototype, 'findNode') + .mockResolvedValue({ + host: '127.0.0.1' as Host, + port: 11111 as Port, + }); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + proxy = new Proxy({ + authToken, + logger, + }); + await proxy.start({ + tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), + serverHost: '127.0.0.1' as Host, + serverPort: 0 as Port, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyRing, + logger: logger.getChild('NodeGraph'), + }); + taskManager = await TaskManager.createTaskManager({ + db, + logger, + lazy: true, + }); + nodeConnectionManager = new NodeConnectionManager({ + keyRing, + nodeGraph, + proxy, + taskManager, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + await nodeConnectionManager.start({ nodeManager: {} as NodeManager }); + await taskManager.startProcessing(); + }); + afterEach(async () => { + mockedFindNode.mockRestore(); + await taskManager.stopProcessing(); + await taskManager.stopTasks(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await sigchain.stop(); + await nodeGraph.stop(); + await nodeConnectionManager.stop(); + await proxy.stop(); + await db.stop(); + await keyRing.stop(); + await taskManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('finds a node', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + nodesFind: new NodesFindHandler({ + nodeConnectionManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + nodesFind, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const response = await rpcClient.methods.nodesFind({ + nodeIdEncoded: + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded, + }); + expect(response.host).toBe('127.0.0.1'); + expect(response.port).toBe(11111); + }); + test('cannot find an invalid node', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + nodesFind: new NodesFindHandler({ + nodeConnectionManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + nodesFind, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const request = new nodesPB.Node(); + request.setNodeId('nodeId'); + await testUtils.expectRemoteError( + rpcClient.methods.nodesFind({ + nodeIdEncoded: 'nodeId' as NodeIdEncoded, + }), + validationErrors.ErrorValidation, + ); + }); +}); diff --git a/tests/client/handlers/nodesPing.test.ts b/tests/client/handlers/nodesPing.test.ts new file mode 100644 index 000000000..4b9aa744e --- /dev/null +++ b/tests/client/handlers/nodesPing.test.ts @@ -0,0 +1,252 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '../../../src/gestalts/GestaltGraph'; +import type { NodeIdEncoded } from '@/ids'; +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { NodesPingHandler } from '@/client/handlers/nodesPing'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import * as validationErrors from '@/validation/errors'; +import { nodesPing } from '@/client'; +import * as testsUtils from '../../utils'; +import Proxy from '../../../src/network/Proxy'; +import Sigchain from '../../../src/sigchain/Sigchain'; +import NodeGraph from '../../../src/nodes/NodeGraph'; +import TaskManager from '../../../src/tasks/TaskManager'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import NodeManager from '../../../src/nodes/NodeManager'; +import * as testUtils from '../../utils'; + +describe('nodesPing', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const authToken = 'abc123'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let nodeGraph: NodeGraph; + let taskManager: TaskManager; + let nodeConnectionManager: NodeConnectionManager; + let nodeManager: NodeManager; + let sigchain: Sigchain; + let proxy: Proxy; + let mockedPingNode: jest.SpyInstance; + + beforeEach(async () => { + mockedPingNode = jest.spyOn(NodeManager.prototype, 'pingNode'); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + proxy = new Proxy({ + authToken, + logger, + }); + await proxy.start({ + tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), + serverHost: '127.0.0.1' as Host, + serverPort: 0 as Port, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyRing, + logger: logger.getChild('NodeGraph'), + }); + taskManager = await TaskManager.createTaskManager({ + db, + logger, + lazy: true, + }); + nodeConnectionManager = new NodeConnectionManager({ + keyRing, + nodeGraph, + proxy, + taskManager, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + nodeManager = new NodeManager({ + db, + keyRing, + nodeConnectionManager, + nodeGraph, + sigchain, + taskManager, + gestaltGraph: {} as GestaltGraph, + logger, + }); + await nodeConnectionManager.start({ nodeManager }); + await taskManager.startProcessing(); + }); + afterEach(async () => { + mockedPingNode.mockRestore(); + await taskManager.stopProcessing(); + await taskManager.stopTasks(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await sigchain.stop(); + await nodeGraph.stop(); + await nodeConnectionManager.stop(); + await proxy.stop(); + await db.stop(); + await keyRing.stop(); + await taskManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('pings a node (offline)', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + nodesPing: new NodesPingHandler({ + nodeManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + nodesPing, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + mockedPingNode.mockResolvedValue(false); + const response = await rpcClient.methods.nodesPing({ + nodeIdEncoded: + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded, + }); + expect(response.success).toBeFalsy(); + }); + test('pings a node (online)', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + nodesPing: new NodesPingHandler({ + nodeManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + nodesPing, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + mockedPingNode.mockResolvedValue(true); + const response = await rpcClient.methods.nodesPing({ + nodeIdEncoded: + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded, + }); + expect(response.success).toBeTruthy(); + }); + test('cannot ping an invalid node', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + nodesPing: new NodesPingHandler({ + nodeManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + nodesPing, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + await testUtils.expectRemoteError( + rpcClient.methods.nodesPing({ + nodeIdEncoded: 'nodeId' as NodeIdEncoded, + }), + validationErrors.ErrorValidation, + ); + }); +}); diff --git a/tests/client/service/notificationsClear.test.ts b/tests/client/handlers/notificationsClear.test.ts similarity index 53% rename from tests/client/service/notificationsClear.test.ts rename to tests/client/handlers/notificationsClear.test.ts index 34e443891..f41f9cd91 100644 --- a/tests/client/service/notificationsClear.test.ts +++ b/tests/client/handlers/notificationsClear.test.ts @@ -1,47 +1,44 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '../../../src/gestalts/GestaltGraph'; import type { Host, Port } from '@/network/types'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Metadata } from '@grpc/grpc-js'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import TaskManager from '@/tasks/TaskManager'; import KeyRing from '@/keys/KeyRing'; -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 Proxy from '@/network/Proxy'; -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 clientUtils from '@/client/utils/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testsUtils from '../../utils/index'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { NotificationsClearHandler } from '@/client/handlers/notificationsClear'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import { notificationsClear } from '@/client'; +import * as testsUtils from '../../utils'; +import ACL from '../../../src/acl/ACL'; +import Proxy from '../../../src/network/Proxy'; +import Sigchain from '../../../src/sigchain/Sigchain'; +import NodeGraph from '../../../src/nodes/NodeGraph'; +import TaskManager from '../../../src/tasks/TaskManager'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import NodeManager from '../../../src/nodes/NodeManager'; +import NotificationsManager from '../../../src/notifications/NotificationsManager'; describe('notificationsClear', () => { - const logger = new Logger('notificationsClear test', LogLevel.WARN, [ - new StreamHandler(), + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedClearNotifications: jest.SpyInstance; - beforeAll(async () => { - mockedClearNotifications = jest - .spyOn(NotificationsManager.prototype, 'clearNotifications') - .mockResolvedValue(); - }); - afterAll(async () => { - mockedClearNotifications.mockRestore(); - }); + const password = 'helloWorld'; + const host = '127.0.0.1'; const authToken = 'abc123'; let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; let nodeGraph: NodeGraph; let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; @@ -50,12 +47,12 @@ describe('notificationsClear', () => { let acl: ACL; let sigchain: Sigchain; let proxy: Proxy; + let mockedClearNotifications: jest.SpyInstance; - let db: DB; - let keyRing: KeyRing; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; beforeEach(async () => { + mockedClearNotifications = jest + .spyOn(NotificationsManager.prototype, 'clearNotifications') + .mockResolvedValue(); dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); @@ -68,6 +65,7 @@ describe('notificationsClear', () => { passwordMemLimit: keysUtils.passwordMemLimits.min, strictMemoryLock: false, }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); const dbPath = path.join(dataDir, 'db'); db = await DB.createDB({ dbPath, @@ -132,54 +130,51 @@ describe('notificationsClear', () => { keyRing, logger, }); - const clientService = { - notificationsClear: notificationsClear({ - authenticate, - notificationsManager, - logger, - db, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); }); afterEach(async () => { - await taskManager.stopProcessing(); - await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); - await notificationsManager.stop(); - await nodeGraph.stop(); - await nodeConnectionManager.stop(); - await nodeManager.stop(); - await sigchain.stop(); - await proxy.stop(); - await acl.stop(); - await db.stop(); + mockedClearNotifications.mockRestore(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); await keyRing.stop(); - await taskManager.stop(); await fs.promises.rm(dataDir, { force: true, recursive: true, }); }); - test('clears notifications', async () => { - const request = new utilsPB.EmptyMessage(); - const response = await grpcClient.notificationsClear( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + test('puts/deletes/gets tokens', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + notificationsClear: new NotificationsClearHandler({ + db, + notificationsManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + notificationsClear, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + await rpcClient.methods.notificationsClear({}); expect(mockedClearNotifications.mock.calls.length).toBe(1); }); }); diff --git a/tests/client/handlers/notificationsRead.test.ts b/tests/client/handlers/notificationsRead.test.ts new file mode 100644 index 000000000..3e28dde42 --- /dev/null +++ b/tests/client/handlers/notificationsRead.test.ts @@ -0,0 +1,589 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '../../../src/gestalts/GestaltGraph'; +import type { General, Notification, VaultShare } from '@/notifications/types'; +import type { VaultIdEncoded } from '@/ids'; +import type { VaultName } from '@/vaults/types'; +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { NotificationsReadHandler } from '@/client/handlers/notificationsRead'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import * as nodesUtils from '@/nodes/utils'; +import { notificationsRead } from '@/client'; +import * as testsUtils from '../../utils'; +import NodeGraph from '../../../src/nodes/NodeGraph'; +import TaskManager from '../../../src/tasks/TaskManager'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import NodeManager from '../../../src/nodes/NodeManager'; +import NotificationsManager from '../../../src/notifications/NotificationsManager'; +import ACL from '../../../src/acl/ACL'; +import Sigchain from '../../../src/sigchain/Sigchain'; +import Proxy from '../../../src/network/Proxy'; +import * as testNodesUtils from '../../nodes/utils'; + +describe('identitiesTokenPutDeleteGet', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const authToken = 'abc123'; + const nodeIdSender = testNodesUtils.generateRandomNodeId(); + const nodeIdSenderEncoded = nodesUtils.encodeNodeId(nodeIdSender); + const nodeIdReceiverEncoded = 'test'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let nodeGraph: NodeGraph; + let taskManager: TaskManager; + let nodeConnectionManager: NodeConnectionManager; + let nodeManager: NodeManager; + let notificationsManager: NotificationsManager; + let acl: ACL; + let sigchain: Sigchain; + let proxy: Proxy; + let mockedReadNotifications: jest.SpyInstance; + + beforeEach(async () => { + mockedReadNotifications = jest.spyOn( + NotificationsManager.prototype, + 'readNotifications', + ); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + acl = await ACL.createACL({ + db, + logger, + }); + proxy = new Proxy({ + authToken, + logger, + }); + await proxy.start({ + tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), + serverHost: '127.0.0.1' as Host, + serverPort: 0 as Port, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyRing, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyRing, + logger: logger.getChild('NodeGraph'), + }); + taskManager = await TaskManager.createTaskManager({ + db, + logger, + lazy: true, + }); + nodeConnectionManager = new NodeConnectionManager({ + keyRing, + nodeGraph, + proxy, + taskManager, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + nodeManager = new NodeManager({ + db, + keyRing, + nodeConnectionManager, + nodeGraph, + gestaltGraph: {} as GestaltGraph, + sigchain, + taskManager, + logger, + }); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); + await taskManager.start(); + notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager, + nodeManager, + keyRing, + logger, + }); + }); + afterEach(async () => { + mockedReadNotifications.mockRestore(); + await taskManager.stopProcessing(); + await taskManager.stopTasks(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await notificationsManager.stop(); + await sigchain.stop(); + await nodeGraph.stop(); + await nodeConnectionManager.stop(); + await nodeManager.stop(); + await proxy.stop(); + await acl.stop(); + await db.stop(); + await keyRing.stop(); + await taskManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('reads a single notification', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + notificationsRead: new NotificationsReadHandler({ + db, + notificationsManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + notificationsRead, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + mockedReadNotifications.mockResolvedValueOnce([ + { + typ: 'notification', + data: { + type: 'General', + message: 'test', + }, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, + isRead: true, + }, + ]); + const response = await rpcClient.methods.notificationsRead({ + order: 'newest', + number: 1, + unread: false, + }); + const notificationList: Array = []; + for await (const notificationMessage of response) { + notificationList.push(notificationMessage.notification); + } + expect(notificationList).toHaveLength(1); + const notification = notificationList[0]; + expect(notification.data.type).toBe('General'); + const messageData = notification.data as General; + expect(messageData.message).toBe('test'); + expect(notification.iss).toBe(nodeIdSenderEncoded); + expect(notification.sub).toBe(nodeIdReceiverEncoded); + expect(notification.isRead).toBeTruthy(); + // Check request was parsed correctly + expect(mockedReadNotifications.mock.calls[0][0].unread).toBeFalsy(); + expect(mockedReadNotifications.mock.calls[0][0].number).toBe(1); + expect(mockedReadNotifications.mock.calls[0][0].order).toBe('newest'); + }); + test('reads unread notifications', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + notificationsRead: new NotificationsReadHandler({ + db, + notificationsManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + notificationsRead, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + mockedReadNotifications.mockResolvedValueOnce([ + { + typ: 'notification', + data: { + type: 'General', + message: 'test1', + }, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, + isRead: true, + }, + { + typ: 'notification', + data: { + type: 'General', + message: 'test2', + }, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, + isRead: true, + }, + ]); + const response = await rpcClient.methods.notificationsRead({ + unread: true, + number: 'all', + order: 'newest', + }); + const notificationList: Array = []; + for await (const notificationMessage of response) { + notificationList.push(notificationMessage.notification); + } + expect(notificationList).toHaveLength(2); + const notification1 = notificationList[0]; + const notification2 = notificationList[1]; + expect(notification1.data.type).toBe('General'); + const messageData1 = notification1.data as General; + expect(messageData1.message).toBe('test1'); + expect(notification1.iss).toBe(nodeIdSenderEncoded); + expect(notification1.sub).toBe(nodeIdReceiverEncoded); + expect(notification1.isRead).toBeTruthy(); + expect(notification2.data.type).toBe('General'); + const messageData2 = notification2.data as General; + expect(messageData2.message).toBe('test2'); + expect(notification2.iss).toBe(nodeIdSenderEncoded); + expect(notification2.sub).toBe(nodeIdReceiverEncoded); + expect(notification2.isRead).toBeTruthy(); + // Check request was parsed correctly + expect(mockedReadNotifications.mock.calls[0][0].unread).toBeTruthy(); + expect(mockedReadNotifications.mock.calls[0][0].number).toBe('all'); + expect(mockedReadNotifications.mock.calls[0][0].order).toBe('newest'); + }); + test('reads notifications in reverse order', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + notificationsRead: new NotificationsReadHandler({ + db, + notificationsManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + notificationsRead, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + mockedReadNotifications.mockResolvedValueOnce([ + { + typ: 'notification', + data: { + type: 'General', + message: 'test2', + }, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, + isRead: true, + }, + { + typ: 'notification', + data: { + type: 'General', + message: 'test1', + }, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, + isRead: true, + }, + ]); + const response = await rpcClient.methods.notificationsRead({ + unread: false, + number: 'all', + order: 'oldest', + }); + const notificationList: Array = []; + for await (const notificationMessage of response) { + notificationList.push(notificationMessage.notification); + } + expect(notificationList).toHaveLength(2); + const notification1 = notificationList[0]; + const notification2 = notificationList[1]; + expect(notification1.data.type).toBe('General'); + const messageData1 = notification1.data as General; + expect(messageData1.message).toBe('test2'); + expect(notification1.iss).toBe(nodeIdSenderEncoded); + expect(notification1.sub).toBe(nodeIdReceiverEncoded); + expect(notification1.isRead).toBeTruthy(); + expect(notification2.data.type).toBe('General'); + const messageData2 = notification2.data as General; + expect(messageData2.message).toBe('test1'); + expect(notification2.iss).toBe(nodeIdSenderEncoded); + expect(notification2.sub).toBe(nodeIdReceiverEncoded); + expect(notification2.isRead).toBeTruthy(); + // Check request was parsed correctly + expect(mockedReadNotifications.mock.calls[0][0].unread).toBeFalsy(); + expect(mockedReadNotifications.mock.calls[0][0].number).toBe('all'); + expect(mockedReadNotifications.mock.calls[0][0].order).toBe('oldest'); + }); + test('reads gestalt invite notifications', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + notificationsRead: new NotificationsReadHandler({ + db, + notificationsManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + notificationsRead, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + mockedReadNotifications.mockResolvedValueOnce([ + { + typ: 'notification', + data: { + type: 'GestaltInvite', + }, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, + isRead: true, + }, + ]); + const response = await rpcClient.methods.notificationsRead({ + unread: false, + number: 'all', + order: 'newest', + }); + const notificationList: Array = []; + for await (const notificationMessage of response) { + notificationList.push(notificationMessage.notification); + } + expect(notificationList).toHaveLength(1); + const notification = notificationList[0]; + expect(notification.data.type).toBe('GestaltInvite'); + expect(notification.iss).toBe(nodeIdSenderEncoded); + expect(notification.sub).toBe(nodeIdReceiverEncoded); + expect(notification.isRead).toBeTruthy(); + // Check request was parsed correctly + expect(mockedReadNotifications.mock.calls[0][0].unread).toBeFalsy(); + expect(mockedReadNotifications.mock.calls[0][0].number).toBe('all'); + expect(mockedReadNotifications.mock.calls[0][0].order).toBe('newest'); + }); + test('reads vault share notifications', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + notificationsRead: new NotificationsReadHandler({ + db, + notificationsManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + notificationsRead, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + mockedReadNotifications.mockResolvedValueOnce([ + { + typ: 'notification', + data: { + type: 'VaultShare', + vaultId: 'vault' as VaultIdEncoded, + vaultName: 'vault' as VaultName, + actions: { + clone: null, + pull: null, + }, + }, + iss: nodeIdSenderEncoded, + sub: nodeIdReceiverEncoded, + isRead: true, + }, + ]); + const response = await rpcClient.methods.notificationsRead({ + unread: false, + number: 'all', + order: 'newest', + }); + const notificationList: Array = []; + for await (const notificationMessage of response) { + notificationList.push(notificationMessage.notification); + } + expect(notificationList).toHaveLength(1); + const notification = notificationList[0]; + expect(notification.data.type).toBe('VaultShare'); + const notificationData = notification.data as VaultShare; + expect(notificationData.vaultId).toBe('vault'); + expect(notificationData.vaultName).toBe('vault'); + expect(notificationData.actions).toStrictEqual({ + clone: null, + pull: null, + }); + expect(notification.iss).toBe(nodeIdSenderEncoded); + expect(notification.sub).toBe(nodeIdReceiverEncoded); + expect(notification.isRead).toBeTruthy(); + // Check request was parsed correctly + expect(mockedReadNotifications.mock.calls[0][0].unread).toBeFalsy(); + expect(mockedReadNotifications.mock.calls[0][0].number).toBe('all'); + expect(mockedReadNotifications.mock.calls[0][0].order).toBe('newest'); + }); + test('reads no notifications', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + notificationsRead: new NotificationsReadHandler({ + db, + notificationsManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + notificationsRead, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + mockedReadNotifications.mockResolvedValueOnce([]); + const response = await rpcClient.methods.notificationsRead({ + unread: false, + number: 'all', + order: 'newest', + }); + const notificationList: Array = []; + for await (const notificationMessage of response) { + notificationList.push(notificationMessage.notification); + } + expect(notificationList).toHaveLength(0); + // Check request was parsed correctly + expect(mockedReadNotifications.mock.calls[0][0].unread).toBeFalsy(); + expect(mockedReadNotifications.mock.calls[0][0].number).toBe('all'); + expect(mockedReadNotifications.mock.calls[0][0].order).toBe('newest'); + }); +}); diff --git a/tests/client/service/notificationsSend.test.ts b/tests/client/handlers/notificationsSend.test.ts similarity index 56% rename from tests/client/service/notificationsSend.test.ts rename to tests/client/handlers/notificationsSend.test.ts index e66d9ff6e..12624419f 100644 --- a/tests/client/service/notificationsSend.test.ts +++ b/tests/client/handlers/notificationsSend.test.ts @@ -1,58 +1,48 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '../../../src/gestalts/GestaltGraph'; import type { Host, Port } from '@/network/types'; import type { SignedNotification } from '@/notifications/types'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type { NodeIdEncoded } from '@/ids/index'; 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 Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import TaskManager from '@/tasks/TaskManager'; import KeyRing from '@/keys/KeyRing'; -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 Proxy from '@/network/Proxy'; -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 * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { NotificationsSendHandler } from '@/client/handlers/notificationsSend'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; import * as nodesUtils from '@/nodes/utils'; import * as notificationsUtils from '@/notifications/utils'; -import * as clientUtils from '@/client/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testsUtils from '../../utils/index'; +import { notificationsSend } from '@/client'; +import * as testsUtils from '../../utils'; +import NodeGraph from '../../../src/nodes/NodeGraph'; +import TaskManager from '../../../src/tasks/TaskManager'; +import NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import NodeManager from '../../../src/nodes/NodeManager'; +import NotificationsManager from '../../../src/notifications/NotificationsManager'; +import ACL from '../../../src/acl/ACL'; +import Sigchain from '../../../src/sigchain/Sigchain'; +import Proxy from '../../../src/network/Proxy'; describe('notificationsSend', () => { - const logger = new Logger('notificationsSend test', LogLevel.WARN, [ - new StreamHandler(), + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedSignNotification: jest.SpyInstance; - let mockedSendNotification: jest.SpyInstance; - beforeAll(async () => { - mockedSignNotification = jest - .spyOn(notificationsUtils, 'generateNotification') - .mockImplementation(async () => { - return 'signedNotification' as SignedNotification; - }); - mockedSendNotification = jest - .spyOn(NodeConnectionManager.prototype, 'withConnF') - .mockImplementation(); - }); - afterAll(async () => { - mockedSignNotification.mockRestore(); - mockedSendNotification.mockRestore(); - }); + const password = 'helloWorld'; + const host = '127.0.0.1'; const authToken = 'abc123'; let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; let nodeGraph: NodeGraph; let taskManager: TaskManager; let nodeConnectionManager: NodeConnectionManager; @@ -61,11 +51,18 @@ describe('notificationsSend', () => { let acl: ACL; let sigchain: Sigchain; let proxy: Proxy; - let db: DB; - let keyRing: KeyRing; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; + let mockedSignNotification: jest.SpyInstance; + let mockedSendNotification: jest.SpyInstance; + beforeEach(async () => { + mockedSignNotification = jest.spyOn( + notificationsUtils, + 'generateNotification', + ); + mockedSendNotification = jest.spyOn( + NodeConnectionManager.prototype, + 'withConnF', + ); dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); @@ -78,6 +75,7 @@ describe('notificationsSend', () => { passwordMemLimit: keysUtils.passwordMemLimits.min, strictMemoryLock: false, }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); const dbPath = path.join(dataDir, 'db'); db = await DB.createDB({ dbPath, @@ -132,7 +130,7 @@ describe('notificationsSend', () => { }); await nodeManager.start(); await nodeConnectionManager.start({ nodeManager }); - await taskManager.startProcessing(); + await taskManager.start(); notificationsManager = await NotificationsManager.createNotificationsManager({ acl, @@ -142,36 +140,19 @@ describe('notificationsSend', () => { keyRing, logger, }); - const clientService = { - notificationsSend: notificationsSend({ - authenticate, - notificationsManager, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); }); afterEach(async () => { + mockedSignNotification.mockRestore(); + mockedSendNotification.mockRestore(); await taskManager.stopProcessing(); await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); await notificationsManager.stop(); + await sigchain.stop(); await nodeGraph.stop(); await nodeConnectionManager.stop(); await nodeManager.stop(); - await sigchain.stop(); await proxy.stop(); await acl.stop(); await db.stop(); @@ -183,18 +164,47 @@ describe('notificationsSend', () => { }); }); test('sends a notification', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + notificationsSend: new NotificationsSendHandler({ + notificationsManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + notificationsSend, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + mockedSignNotification.mockImplementation(async () => { + return 'signedNotification' as SignedNotification; + }); + mockedSendNotification.mockImplementation(); const receiverNodeIdEncoded = - 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'; - const generalMessage = new notificationsPB.General(); - generalMessage.setMessage('test'); - const request = new notificationsPB.Send(); - request.setData(generalMessage); - request.setReceiverId(receiverNodeIdEncoded); - const response = await grpcClient.notificationsSend( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded; + await rpcClient.methods.notificationsSend({ + nodeIdEncoded: receiverNodeIdEncoded, + message: 'test', + }); // Check we signed and sent the notification expect(mockedSignNotification.mock.calls.length).toBe(1); expect(mockedSendNotification.mock.calls.length).toBe(1); diff --git a/tests/client/handlers/vaultsClone.test.ts b/tests/client/handlers/vaultsClone.test.ts new file mode 100644 index 000000000..e5972a386 --- /dev/null +++ b/tests/client/handlers/vaultsClone.test.ts @@ -0,0 +1,108 @@ +import type GestaltGraph from '../../../src/gestalts/GestaltGraph'; +import type WebSocketServer from '@/websockets/WebSocketServer'; +import type WebSocketClient from '@/websockets/WebSocketClient'; +import type NodeConnectionManager from '../../../src/nodes/NodeConnectionManager'; +import type NotificationsManager from '../../../src/notifications/NotificationsManager'; +import type ACL from '../../../src/acl/ACL'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import VaultManager from '@/vaults/VaultManager'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; + +describe('notificationsSend', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + // Const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + // Let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + // TlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing: {} as KeyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test.todo('sends a notification'); // , async () => { + // // Setup + // const rpcServer = await RPCServer.createRPCServer({ + // manifest: { + // notificationsSend: new NotificationsSendHandler({ + // notificationsManager, + // }), + // }, + // logger, + // }); + // webSocketServer = await WebSocketServer.createWebSocketServer({ + // connectionCallback: (streamPair, connectionInfo) => + // rpcServer.handleStream(streamPair, connectionInfo), + // host, + // tlsConfig, + // logger: logger.getChild('server'), + // }); + // webSocketClient = await WebSocketClient.createWebSocketClient({ + // expectedNodeIds: [keyRing.getNodeId()], + // host, + // logger: logger.getChild('client'), + // port: webSocketServer.port, + // }); + // const rpcClient = await RPCClient.createRPCClient({ + // manifest: { + // notificationsSend, + // }, + // streamFactory: async () => webSocketClient.startConnection(), + // logger: logger.getChild('clientRPC'), + // }); + // + // // Doing the test + // + // }); +}); diff --git a/tests/client/handlers/vaultsCreateDeleteList.test.ts b/tests/client/handlers/vaultsCreateDeleteList.test.ts new file mode 100644 index 000000000..b4e518088 --- /dev/null +++ b/tests/client/handlers/vaultsCreateDeleteList.test.ts @@ -0,0 +1,156 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import type { VaultListMessage } from '@/client/handlers/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsCreatehandler } from '@/client/handlers/vaultsCreate'; +import { VaultsDeleteHandler } from '@/client/handlers/vaultsDelete'; +import { VaultsListHandler } from '@/client/handlers/vaultsList'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import VaultManager from '@/vaults/VaultManager'; +import { + vaultsCreate, + vaultsDelete, + vaultsList, +} from '@/client/handlers/clientManifest'; +import * as testsUtils from '../../utils'; + +describe('vaultsCreateDeleteList', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('creates, lists, and deletes vaults', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsCreate: new VaultsCreatehandler({ + vaultManager, + db, + }), + vaultsDelete: new VaultsDeleteHandler({ + vaultManager, + db, + }), + vaultsList: new VaultsListHandler({ + vaultManager, + db, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsCreate, + vaultsDelete, + vaultsList, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Create vault + const createResponse = await rpcClient.methods.vaultsCreate({ + vaultName: 'test-vault', + }); + // List vault + const listResponse1 = await rpcClient.methods.vaultsList({}); + const vaults1: Array = []; + for await (const vault of listResponse1) { + vaults1.push(vault); + } + expect(vaults1).toHaveLength(1); + expect(vaults1[0].vaultName).toBe('test-vault'); + expect(vaults1[0].vaultIdEncoded).toBe(createResponse.vaultIdEncoded); + // Delete vault + const deleteResponse = await rpcClient.methods.vaultsDelete({ + nameOrId: createResponse.vaultIdEncoded, + }); + expect(deleteResponse.success).toBeTruthy(); + // Check vault was deleted + const listResponse2 = await rpcClient.methods.vaultsList({}); + const vaults2: Array = []; + for await (const vault of listResponse2) { + vaults2.push(vault); + } + expect(vaults2).toHaveLength(0); + }); +}); diff --git a/tests/client/handlers/vaultsLog.test.ts b/tests/client/handlers/vaultsLog.test.ts new file mode 100644 index 000000000..de329e405 --- /dev/null +++ b/tests/client/handlers/vaultsLog.test.ts @@ -0,0 +1,239 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import type { VaultId } from '@/ids/index'; +import type { LogEntryMessage } from '@/client/handlers/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsLogHandler } from '@/client/handlers/vaultsLog'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import VaultManager from '@/vaults/VaultManager'; +import { vaultsLog } from '@/client/handlers/clientManifest'; +import * as testsUtils from '../../utils'; + +describe('vaultsLog', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + const vaultName = 'test-vault'; + const secret1 = { name: 'secret1', content: 'Secret-1-content' }; + const secret2 = { name: 'secret2', content: 'Secret-2-content' }; + let vaultId: VaultId; + let commit1Oid: string; + let commit2Oid: string; + let commit3Oid: string; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + vaultId = await vaultManager.createVault(vaultName); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile(secret1.name, secret1.content); + }); + commit1Oid = (await vault.log(undefined, 0))[0].commitId; + await vault.writeF(async (efs) => { + await efs.writeFile(secret2.name, secret2.content); + }); + commit2Oid = (await vault.log(undefined, 0))[0].commitId; + await vault.writeF(async (efs) => { + await efs.unlink(secret2.name); + }); + commit3Oid = (await vault.log(undefined, 0))[0].commitId; + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('should get the full log', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsLog: new VaultsLogHandler({ + vaultManager, + db, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsLog, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const logStream = await rpcClient.methods.vaultsLog({ + nameOrId: vaultName, + }); + const logMessages: Array = []; + for await (const log of logStream) { + logMessages.push(log); + } + // Checking commits exist in order. + expect(logMessages[2].commitId).toEqual(commit1Oid); + expect(logMessages[1].commitId).toEqual(commit2Oid); + expect(logMessages[0].commitId).toEqual(commit3Oid); + }); + test('should get a part of the log', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsLog: new VaultsLogHandler({ + vaultManager, + db, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsLog, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const logStream = await rpcClient.methods.vaultsLog({ + nameOrId: vaultName, + depth: 2, + }); + const logMessages: Array = []; + for await (const log of logStream) { + logMessages.push(log); + } + // Checking commits exist in order. + expect(logMessages[1].commitId).toEqual(commit2Oid); + expect(logMessages[0].commitId).toEqual(commit3Oid); + }); + test('should get a specific commit', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsLog: new VaultsLogHandler({ + vaultManager, + db, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsLog, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const logStream = await rpcClient.methods.vaultsLog({ + nameOrId: vaultName, + commitId: commit2Oid, + }); + const logMessages: Array = []; + for await (const log of logStream) { + logMessages.push(log); + } + // Checking commits exist in order. + expect(logMessages[0].commitId).toEqual(commit2Oid); + }); +}); diff --git a/tests/client/handlers/vaultsPermissionSetUnsetGet.test.ts b/tests/client/handlers/vaultsPermissionSetUnsetGet.test.ts new file mode 100644 index 000000000..38c7851db --- /dev/null +++ b/tests/client/handlers/vaultsPermissionSetUnsetGet.test.ts @@ -0,0 +1,216 @@ +import type { TLSConfig } from '@/network/types'; +import type { VaultPermissionMessage } from '@/client/handlers/types'; +import type NodeManager from 'nodes/NodeManager'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsPermissionSetHandler } from '@/client/handlers/vaultsPermissionSet'; +import { VaultsPermissionGetHandler } from '@/client/handlers/vaultsPermissionGet'; +import { VaultsPermissionUnsetHandler } from '@/client/handlers/vaultsPermissionUnset'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import NotificationsManager from '@/notifications/NotificationsManager'; +import ACL from '@/acl/ACL'; +import VaultManager from '@/vaults/VaultManager'; +import * as nodesUtils from '@/nodes/utils'; +import { + vaultsPermissionGet, + vaultsPermissionSet, + vaultsPermissionUnset, +} from '@/client/handlers/clientManifest'; +import * as testsUtils from '../../utils'; +import * as testUtils from '../../utils/index'; + +describe('vaultsPermissionSetUnsetGet', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const nodeId = testUtils.generateRandomNodeId(); + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + let acl: ACL; + let gestaltGraph: GestaltGraph; + let notificationsManager: NotificationsManager; + let mockedSendNotification: jest.SpyInstance; + + beforeEach(async () => { + mockedSendNotification = jest + .spyOn(NotificationsManager.prototype, 'sendNotification') + .mockImplementation(); + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + 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({ + nodeId: nodeId, + }); + notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager: {} as NodeConnectionManager, + nodeManager: {} as NodeManager, + keyRing, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph, + notificationsManager, + logger, + }); + }); + afterEach(async () => { + mockedSendNotification.mockRestore(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await notificationsManager.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('sets, gets, and unsets vault permissions', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsPermissionSet: new VaultsPermissionSetHandler({ + acl, + db, + gestaltGraph, + notificationsManager, + vaultManager, + }), + vaultsPermissionGet: new VaultsPermissionGetHandler({ + acl, + db, + vaultManager, + }), + vaultsPermissionUnset: new VaultsPermissionUnsetHandler({ + acl, + db, + gestaltGraph, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsPermissionSet, + vaultsPermissionGet, + vaultsPermissionUnset, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const nodeIdEncoded = nodesUtils.encodeNodeId(nodeId); + const vaultName = 'test-vault'; + await vaultManager.createVault(vaultName); + // Set permissions + const setResponse = await rpcClient.methods.vaultsPermissionSet({ + nameOrId: vaultName, + nodeIdEncoded: nodeIdEncoded, + vaultPermissionList: ['clone', 'pull'], + }); + expect(setResponse.success).toBeTruthy(); + // Get permissions + const getResponse1 = await rpcClient.methods.vaultsPermissionGet({ + nameOrId: vaultName, + }); + const list1: Array = []; + for await (const permission of getResponse1) { + const permissionsList = permission.vaultPermissionList; + expect(permissionsList).toContain('pull'); + expect(permissionsList).toContain('clone'); + const receivedNodeId = permission.nodeIdEncoded; + expect(receivedNodeId).toEqual(nodeIdEncoded); + list1.push(permission); + } + expect(list1).toHaveLength(1); + // Unset permissions + const deleteResponse = await rpcClient.methods.vaultsPermissionUnset({ + nameOrId: vaultName, + nodeIdEncoded: nodeIdEncoded, + vaultPermissionList: ['pull', 'clone'], + }); + expect(deleteResponse.success).toBeTruthy(); + // Check permissions were unset + const getResponse2 = await rpcClient.methods.vaultsPermissionGet({ + nameOrId: vaultName, + }); + const list2: Array = []; + for await (const permission of getResponse2) { + const permissionsList = permission.vaultPermissionList; + expect(permissionsList).toEqual([]); + expect(permission.nodeIdEncoded).toEqual(nodeIdEncoded); + list2.push(permission); + } + expect(list2).toHaveLength(1); + }); +}); diff --git a/tests/client/handlers/vaultsPull.test.ts b/tests/client/handlers/vaultsPull.test.ts new file mode 100644 index 000000000..28b77ed49 --- /dev/null +++ b/tests/client/handlers/vaultsPull.test.ts @@ -0,0 +1,155 @@ +import type WebSocketServer from '@/websockets/WebSocketServer'; +import type WebSocketClient from '@/websockets/WebSocketClient'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NodeManager from 'nodes/NodeManager'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import GestaltGraph from '@/gestalts/GestaltGraph'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import NotificationsManager from '@/notifications/NotificationsManager'; +import ACL from '@/acl/ACL'; +import VaultManager from '@/vaults/VaultManager'; +import * as testUtils from '../../utils/index'; + +describe('vaultsPull', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + // Const host = '127.0.0.1'; + const nodeId = testUtils.generateRandomNodeId(); + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + // Let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + let acl: ACL; + let gestaltGraph: GestaltGraph; + let notificationsManager: NotificationsManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + // TlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + 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({ + nodeId: nodeId, + }); + notificationsManager = + await NotificationsManager.createNotificationsManager({ + acl, + db, + nodeConnectionManager: {} as NodeConnectionManager, + nodeManager: {} as NodeManager, + keyRing, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph, + notificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await notificationsManager.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test.todo('pulls from a vault'); // , async () => { + // // Setup + // const rpcServer = await RPCServer.createRPCServer({ + // manifest: { + // vaultsPermissionSet: new VaultsPermissionSetHandler({ + // acl, + // db, + // gestaltGraph, + // notificationsManager, + // vaultManager + // }), + // vaultsPermissionGet: new VaultsPermissionGetHandler({ + // acl, + // db, + // vaultManager + // }), + // vaultsPermissionUnset: new VaultsPermissionUnsetHandler({ + // acl, + // db, + // gestaltGraph, + // vaultManager + // }), + // }, + // logger, + // }); + // webSocketServer = await WebSocketServer.createWebSocketServer({ + // connectionCallback: (streamPair, connectionInfo) => + // rpcServer.handleStream(streamPair, connectionInfo), + // host, + // tlsConfig, + // logger: logger.getChild('server'), + // }); + // webSocketClient = await WebSocketClient.createWebSocketClient({ + // expectedNodeIds: [keyRing.getNodeId()], + // host, + // logger: logger.getChild('client'), + // port: webSocketServer.port, + // }); + // const rpcClient = await RPCClient.createRPCClient({ + // manifest: { + // vaultsPermissionSet, + // vaultsPermissionGet, + // vaultsPermissionUnset, + // }, + // streamFactory: async () => webSocketClient.startConnection(), + // logger: logger.getChild('clientRPC'), + // }); + // + // // Doing the test + // + // }); +}); diff --git a/tests/client/handlers/vaultsRename.test.ts b/tests/client/handlers/vaultsRename.test.ts new file mode 100644 index 000000000..28c5ba98a --- /dev/null +++ b/tests/client/handlers/vaultsRename.test.ts @@ -0,0 +1,124 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsRenameHandler } from '@/client/handlers/vaultsRename'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import VaultManager from '@/vaults/VaultManager'; +import * as vaultsUtils from '@/vaults/utils'; +import { vaultsRename } from '@/client/handlers/clientManifest'; +import * as testsUtils from '../../utils'; + +describe('vaultsRename', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('should rename vault', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsRename: new VaultsRenameHandler({ + db, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsRename, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const vaultId1 = await vaultManager.createVault('test-vault1'); + const vaultId1Encoded = vaultsUtils.encodeVaultId(vaultId1); + const vaultId2 = await rpcClient.methods.vaultsRename({ + nameOrId: vaultId1Encoded, + newName: 'test-vault2', + }); + expect(vaultId2.vaultIdEncoded).toEqual(vaultId1Encoded); + const renamedVaultId = await vaultManager.getVaultId('test-vault2'); + expect(renamedVaultId).toStrictEqual(vaultId1); + }); +}); diff --git a/tests/client/handlers/vaultsScan.test.ts b/tests/client/handlers/vaultsScan.test.ts new file mode 100644 index 000000000..cce3d5314 --- /dev/null +++ b/tests/client/handlers/vaultsScan.test.ts @@ -0,0 +1,120 @@ +import type WebSocketServer from '@/websockets/WebSocketServer'; +import type WebSocketClient from '@/websockets/WebSocketClient'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import VaultManager from '@/vaults/VaultManager'; + +describe('vaultsScan', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + // Const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + // Let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + // TlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test.todo('scans a vault'); // , async () => { + // // Setup + // const rpcServer = await RPCServer.createRPCServer({ + // manifest: { + // vaultsRename: new VaultsRenameHandler({ + // db, + // vaultManager, + // }), + // }, + // logger, + // }); + // webSocketServer = await WebSocketServer.createWebSocketServer({ + // connectionCallback: (streamPair, connectionInfo) => + // rpcServer.handleStream(streamPair, connectionInfo), + // host, + // tlsConfig, + // logger: logger.getChild('server'), + // }); + // webSocketClient = await WebSocketClient.createWebSocketClient({ + // expectedNodeIds: [keyRing.getNodeId()], + // host, + // logger: logger.getChild('client'), + // port: webSocketServer.port, + // }); + // const rpcClient = await RPCClient.createRPCClient({ + // manifest: { + // vaultsRename, + // }, + // streamFactory: async () => webSocketClient.startConnection(), + // logger: logger.getChild('clientRPC'), + // }); + // + // // Doing the test + // const vaultId1 = await vaultManager.createVault('test-vault1'); + // const vaultId1Encoded = vaultsUtils.encodeVaultId(vaultId1); + // const vaultId2 = await rpcClient.methods.vaultsRename({ + // nameOrId: vaultId1Encoded, + // newName: 'test-vault2', + // }, + // ); + // expect(vaultId2.vaultIdEncoded).toEqual( + // vaultId1Encoded, + // ); + // const renamedVaultId = await vaultManager.getVaultId('test-vault2'); + // expect(renamedVaultId).toStrictEqual(vaultId1); + // }); +}); diff --git a/tests/client/service/vaultsSecretsEdit.test.ts b/tests/client/handlers/vaultsSecretsEdit.test.ts similarity index 50% rename from tests/client/service/vaultsSecretsEdit.test.ts rename to tests/client/handlers/vaultsSecretsEdit.test.ts index 54aa6f801..ecb454065 100644 --- a/tests/client/service/vaultsSecretsEdit.test.ts +++ b/tests/client/handlers/vaultsSecretsEdit.test.ts @@ -1,41 +1,41 @@ -import type { Host, Port } from '@/network/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; +import type { TLSConfig } from '@/network/types'; import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsSecretsEditHandler } from '@/client/handlers/vaultsSecretsEdit'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsSecretsEdit from '@/client/service/vaultsSecretsEdit'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; -import * as clientUtils from '@/client/utils/utils'; import * as vaultsUtils from '@/vaults/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; +import { vaultsSecretsEdit } from '@/client/handlers/clientManifest'; +import * as testsUtils from '../../utils'; describe('vaultsSecretsEdit', () => { - const logger = new Logger('vaultsSecretsEdit test', LogLevel.WARN, [ - new StreamHandler(), + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; + const password = 'helloWorld'; + const host = '127.0.0.1'; let dataDir: string; - let keyRing: KeyRing; let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; let vaultManager: VaultManager; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; + beforeEach(async () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), @@ -49,6 +49,7 @@ describe('vaultsSecretsEdit', () => { passwordMemLimit: keysUtils.passwordMemLimits.min, strictMemoryLock: false, }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); const dbPath = path.join(dataDir, 'db'); db = await DB.createDB({ dbPath, @@ -65,30 +66,10 @@ describe('vaultsSecretsEdit', () => { notificationsManager: {} as NotificationsManager, logger, }); - const clientService = { - vaultsSecretsEdit: vaultsSecretsEdit({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); }); afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); + await webSocketServer.stop(true); + await webSocketClient.destroy(true); await vaultManager.stop(); await db.stop(); await keyRing.stop(); @@ -98,6 +79,38 @@ describe('vaultsSecretsEdit', () => { }); }); test('edits secrets', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsSecretsEdit: new VaultsSecretsEditHandler({ + db, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsSecretsEdit, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test const vaultName = 'test-vault'; const secretName = 'test-secret'; const vaultId = await vaultManager.createVault(vaultName); @@ -106,18 +119,12 @@ describe('vaultsSecretsEdit', () => { await efs.writeFile(secretName, secretName); }); }); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretName); - secretMessage.setSecretContent(Buffer.from('content-change')); - const response = await grpcClient.vaultsSecretsEdit( - secretMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.StatusMessage); - expect(response.getSuccess()).toBeTruthy(); + const response = await rpcClient.methods.vaultsSecretsEdit({ + nameOrId: vaultsUtils.encodeVaultId(vaultId), + secretName: secretName, + secretContent: Buffer.from('content-change').toString('binary'), + }); + expect(response.success).toBeTruthy(); await vaultManager.withVaults([vaultId], async (vault) => { await vault.readF(async (efs) => { expect((await efs.readFile(secretName)).toString()).toStrictEqual( diff --git a/tests/client/handlers/vaultsSecretsMkdir.test.ts b/tests/client/handlers/vaultsSecretsMkdir.test.ts new file mode 100644 index 000000000..21a4f22b0 --- /dev/null +++ b/tests/client/handlers/vaultsSecretsMkdir.test.ts @@ -0,0 +1,129 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsSecretsMkdirHandler } from '@/client/handlers/vaultsSecretsMkdir'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import VaultManager from '@/vaults/VaultManager'; +import * as vaultsUtils from '@/vaults/utils'; +import { vaultsSecretsMkdir } from '@/client/handlers/clientManifest'; +import * as testsUtils from '../../utils'; + +describe('vaultsSecretsMkdir', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('makes a directory', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsSecretsMkdir: new VaultsSecretsMkdirHandler({ + db, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsSecretsMkdir, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const vaultName = 'test-vault'; + const vaultId = await vaultManager.createVault(vaultName); + const dirPath = 'dir/dir1/dir2'; + const response = await rpcClient.methods.vaultsSecretsMkdir({ + recursive: true, + nameOrId: vaultsUtils.encodeVaultId(vaultId), + dirName: dirPath, + }); + expect(response.success).toBeTruthy(); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.readF(async (efs) => { + expect(await efs.exists(dirPath)).toBeTruthy(); + }); + }); + }); +}); diff --git a/tests/client/handlers/vaultsSecretsNewDeleteGet.test.ts b/tests/client/handlers/vaultsSecretsNewDeleteGet.test.ts new file mode 100644 index 000000000..f16e059ac --- /dev/null +++ b/tests/client/handlers/vaultsSecretsNewDeleteGet.test.ts @@ -0,0 +1,164 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsSecretsNewHandler } from '@/client/handlers/vaultsSecretsNew'; +import { VaultsSecretsDeleteHandler } from '@/client/handlers/vaultsSecretsDelete'; +import { VaultsSecretsGetHandler } from '@/client/handlers/vaultsSecretsGet'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import VaultManager from '@/vaults/VaultManager'; +import * as vaultsUtils from '@/vaults/utils'; +import * as vaultsErrors from '@/vaults/errors'; +import { + vaultsSecretsDelete, + vaultsSecretsGet, + vaultsSecretsNew, +} from '@/client/handlers/clientManifest'; +import * as testUtils from '../../utils/index'; +import * as testsUtils from '../../utils'; + +describe('vaultsSecretsNewDeleteGet', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('creates, gets, and deletes secrets', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsSecretsNew: new VaultsSecretsNewHandler({ + db, + vaultManager, + }), + vaultsSecretsDelete: new VaultsSecretsDeleteHandler({ + db, + vaultManager, + }), + vaultsSecretsGet: new VaultsSecretsGetHandler({ + db, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsSecretsNew, + vaultsSecretsDelete, + vaultsSecretsGet, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Create secret + const secret = 'test-secret'; + const vaultId = await vaultManager.createVault('test-vault'); + const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId); + const createResponse = await rpcClient.methods.vaultsSecretsNew({ + nameOrId: vaultIdEncoded, + secretName: secret, + secretContent: Buffer.from(secret).toString('binary'), + }); + expect(createResponse.success).toBeTruthy(); + // Get secret + const getResponse1 = await rpcClient.methods.vaultsSecretsGet({ + nameOrId: vaultIdEncoded, + secretName: secret, + }); + const secretContent = getResponse1.secretContent; + expect(secretContent).toStrictEqual(secret); + // Delete secret + const deleteResponse = await rpcClient.methods.vaultsSecretsDelete({ + nameOrId: vaultIdEncoded, + secretName: secret, + }); + expect(deleteResponse.success).toBeTruthy(); + // Check secret was deleted + await testUtils.expectRemoteError( + rpcClient.methods.vaultsSecretsGet({ + nameOrId: vaultIdEncoded, + secretName: secret, + }), + vaultsErrors.ErrorSecretsSecretUndefined, + ); + }); +}); diff --git a/tests/client/handlers/vaultsSecretsNewDirList.test.ts b/tests/client/handlers/vaultsSecretsNewDirList.test.ts new file mode 100644 index 000000000..bce12b9f1 --- /dev/null +++ b/tests/client/handlers/vaultsSecretsNewDirList.test.ts @@ -0,0 +1,154 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import type { FileSystem } from '@/types'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsSecretsNewDirHandler } from '@/client/handlers/vaultsSecretsNewDir'; +import { VaultsSecretsListHandler } from '@/client/handlers/vaultsSecretsList'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import VaultManager from '@/vaults/VaultManager'; +import * as vaultsUtils from '@/vaults/utils'; +import { + vaultsSecretsList, + vaultsSecretsNewDir, +} from '@/client/handlers/clientManifest'; +import * as testsUtils from '../../utils'; + +describe('vaultsSecretsNewDirList', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const fs: FileSystem = require('fs'); + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('adds and lists a directory of secrets', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsSecretsNewDir: new VaultsSecretsNewDirHandler({ + db, + fs, + vaultManager, + }), + vaultsSecretsList: new VaultsSecretsListHandler({ + db, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsSecretsNewDir, + vaultsSecretsList, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Add directory of secrets + const vaultName = 'test-vault'; + const secretList = ['test-secret1', 'test-secret2', 'test-secret3']; + const secretDir = path.join(dataDir, 'secretDir'); + await fs.promises.mkdir(secretDir); + for (const secret of secretList) { + const secretFile = path.join(secretDir, secret); + // Write secret to file + await fs.promises.writeFile(secretFile, secret); + } + const vaultId = await vaultManager.createVault(vaultName); + const vaultsIdEncoded = vaultsUtils.encodeVaultId(vaultId); + const addResponse = await rpcClient.methods.vaultsSecretsNewDir({ + nameOrId: vaultsIdEncoded, + dirName: secretDir, + }); + expect(addResponse.success).toBeTruthy(); + // List secrets + const listResponse = await rpcClient.methods.vaultsSecretsList({ + nameOrId: vaultsIdEncoded, + }); + const secrets: Array = []; + for await (const secret of listResponse) { + secrets.push(secret.secretName); + } + expect(secrets.sort()).toStrictEqual( + secretList.map((secret) => path.join('secretDir', secret)).sort(), + ); + }); +}); diff --git a/tests/client/handlers/vaultsSecretsRename.test.ts b/tests/client/handlers/vaultsSecretsRename.test.ts new file mode 100644 index 000000000..12890bb6a --- /dev/null +++ b/tests/client/handlers/vaultsSecretsRename.test.ts @@ -0,0 +1,137 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import type { FileSystem } from '@/types'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsSecretsRenameHandler } from '@/client/handlers/vaultsSecretsRename'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import VaultManager from '@/vaults/VaultManager'; +import * as vaultsUtils from '@/vaults/utils'; +import { vaultsSecretsRename } from '@/client/handlers/clientManifest'; +import * as testsUtils from '../../utils'; + +describe('vaultsSecretsRename', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const fs: FileSystem = require('fs'); + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('renames a secret', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsSecretsRename: new VaultsSecretsRenameHandler({ + db, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsSecretsRename, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const vaultName = 'test-vault'; + const secretName = 'test-secret'; + const vaultId = await vaultManager.createVault(vaultName); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile(secretName, secretName); + }); + }); + const response = await rpcClient.methods.vaultsSecretsRename({ + nameOrId: vaultsUtils.encodeVaultId(vaultId), + secretName: secretName, + newSecretName: 'name-change', + }); + expect(response.success).toBeTruthy(); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.readF(async (efs) => { + expect((await efs.readFile('name-change')).toString()).toStrictEqual( + secretName, + ); + }); + }); + }); +}); diff --git a/tests/client/handlers/vaultsSecretsStat.test.ts b/tests/client/handlers/vaultsSecretsStat.test.ts new file mode 100644 index 000000000..15fcf8231 --- /dev/null +++ b/tests/client/handlers/vaultsSecretsStat.test.ts @@ -0,0 +1,132 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import type { FileSystem } from '@/types'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsSecretsStatHandler } from '@/client/handlers/vaultsSecretsStat'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import VaultManager from '@/vaults/VaultManager'; +import * as vaultsUtils from '@/vaults/utils'; +import { vaultsSecretsStat } from '@/client/handlers/clientManifest'; +import * as testsUtils from '../../utils'; + +describe('vaultsSecretsStat', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const fs: FileSystem = require('fs'); + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('stats a file', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsSecretsStat: new VaultsSecretsStatHandler({ + db, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsSecretsStat, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + const vaultName = 'test-vault'; + const secretName = 'test-secret'; + const vaultId = await vaultManager.createVault(vaultName); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile(secretName, secretName); + }); + }); + const response = await rpcClient.methods.vaultsSecretsStat({ + nameOrId: vaultsUtils.encodeVaultId(vaultId), + secretName: secretName, + }); + const stat = response.stat; + expect(stat.size).toBe(secretName.length); + expect(stat.blksize).toBe(4096); + expect(stat.blocks).toBe(1); + }); +}); diff --git a/tests/client/handlers/vaultsVersion.test.ts b/tests/client/handlers/vaultsVersion.test.ts new file mode 100644 index 000000000..e0bdaa8e5 --- /dev/null +++ b/tests/client/handlers/vaultsVersion.test.ts @@ -0,0 +1,213 @@ +import type { TLSConfig } from '@/network/types'; +import type GestaltGraph from '@/gestalts/GestaltGraph'; +import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; +import type NotificationsManager from '@/notifications/NotificationsManager'; +import type ACL from '@/acl/ACL'; +import type { FileSystem } from '@/types'; +import type { VaultId } from '@/ids/index'; +import path from 'path'; +import os from 'os'; +import Logger, { formatting, LogLevel, StreamHandler } from '@matrixai/logger'; +import { DB } from '@matrixai/db'; +import KeyRing from '@/keys/KeyRing'; +import * as keysUtils from '@/keys/utils'; +import RPCServer from '@/rpc/RPCServer'; +import { VaultsVersionHandler } from '@/client/handlers/vaultsVersion'; +import RPCClient from '@/rpc/RPCClient'; +import WebSocketServer from '@/websockets/WebSocketServer'; +import WebSocketClient from '@/websockets/WebSocketClient'; +import VaultManager from '@/vaults/VaultManager'; +import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; +import * as vaultsUtils from '@/vaults/utils'; +import * as vaultsErrors from '@/vaults/errors'; +import { vaultsVersion } from '@/client/handlers/clientManifest'; +import * as testUtils from '../../utils/index'; +import * as testsUtils from '../../utils'; + +describe('vaultsVersion', () => { + const logger = new Logger('agentUnlock test', LogLevel.WARN, [ + new StreamHandler( + formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, + ), + ]); + const password = 'helloWorld'; + const host = '127.0.0.1'; + const fs: FileSystem = require('fs'); + let dataDir: string; + let db: DB; + let keyRing: KeyRing; + let webSocketClient: WebSocketClient; + let webSocketServer: WebSocketServer; + let tlsConfig: TLSConfig; + let vaultManager: VaultManager; + let vaultId: VaultId; + const secretVer1 = { + name: 'secret1v1', + content: 'Secret-1-content-ver1', + }; + const secretVer2 = { + name: 'secret1v2', + content: 'Secret-1-content-ver2', + }; + const vaultName = 'test-vault'; + + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyRing = await KeyRing.createKeyRing({ + password, + keysPath, + logger, + passwordOpsLimit: keysUtils.passwordOpsLimits.min, + passwordMemLimit: keysUtils.passwordMemLimits.min, + strictMemoryLock: false, + }); + tlsConfig = await testsUtils.createTLSConfig(keyRing.keyPair); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + const vaultsPath = path.join(dataDir, 'vaults'); + vaultManager = await VaultManager.createVaultManager({ + vaultsPath, + db, + acl: {} as ACL, + keyRing, + nodeConnectionManager: {} as NodeConnectionManager, + gestaltGraph: {} as GestaltGraph, + notificationsManager: {} as NotificationsManager, + logger, + }); + vaultId = await vaultManager.createVault(vaultName); + }); + afterEach(async () => { + await webSocketServer.stop(true); + await webSocketClient.destroy(true); + await vaultManager.stop(); + await db.stop(); + await keyRing.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('should switch a vault to a version', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsVersion: new VaultsVersionHandler({ + db, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsVersion, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Commit some history + const ver1Oid = await vaultManager.withVaults([vaultId], async (vault) => { + await vault.writeF(async (efs) => { + await efs.writeFile(secretVer1.name, secretVer1.content); + }); + const ver1Oid = (await vault.log())[0].commitId; + await vault.writeF(async (efs) => { + await efs.writeFile(secretVer2.name, secretVer2.content); + }); + return ver1Oid; + }); + // Revert the version + const vaultMessage = new vaultsPB.Vault(); + vaultMessage.setNameOrId(vaultName); + const vaultVersionMessage = new vaultsPB.Version(); + vaultVersionMessage.setVault(vaultMessage); + vaultVersionMessage.setVersionId(ver1Oid); + const version = await rpcClient.methods.vaultsVersion({ + nameOrId: vaultName, + versionId: ver1Oid, + }); + expect(version.latestVersion).toBeFalsy(); + // Read old history + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.readF(async (efs) => { + expect((await efs.readFile(secretVer1.name)).toString()).toStrictEqual( + secretVer1.content, + ); + }); + }); + }); + test('should fail to find a non existent version', async () => { + // Setup + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + vaultsVersion: new VaultsVersionHandler({ + db, + vaultManager, + }), + }, + logger, + }); + webSocketServer = await WebSocketServer.createWebSocketServer({ + connectionCallback: (streamPair, connectionInfo) => + rpcServer.handleStream(streamPair, connectionInfo), + host, + tlsConfig, + logger: logger.getChild('server'), + }); + webSocketClient = await WebSocketClient.createWebSocketClient({ + expectedNodeIds: [keyRing.getNodeId()], + host, + logger: logger.getChild('client'), + port: webSocketServer.getPort(), + }); + const rpcClient = await RPCClient.createRPCClient({ + manifest: { + vaultsVersion, + }, + streamFactory: async () => webSocketClient.startConnection(), + logger: logger.getChild('clientRPC'), + }); + + // Doing the test + // Revert the version + const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId); + const version = rpcClient.methods.vaultsVersion({ + nameOrId: vaultIdEncoded, + versionId: 'invalidOid', + }); + await testUtils.expectRemoteError( + version, + vaultsErrors.ErrorVaultReferenceInvalid, + ); + const version2 = rpcClient.methods.vaultsVersion({ + nameOrId: vaultIdEncoded, + versionId: '7660aa9a2fee90e875c2d19e5deefe882ca1d4d9', + }); + await testUtils.expectRemoteError( + version2, + vaultsErrors.ErrorVaultReferenceMissing, + ); + }); +}); diff --git a/tests/client/service/agentLockAll.test.ts b/tests/client/service/agentLockAll.test.ts deleted file mode 100644 index f7a3e2102..000000000 --- a/tests/client/service/agentLockAll.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type { Key } from '@/keys/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 SessionManager from '@/sessions/SessionManager'; -import KeyRing from '@/keys/KeyRing'; -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 { timerStart } from '@/utils/index'; -import * as utils from '@/utils/index'; - -describe('agentLockall', () => { - const logger = new Logger('agentLockall test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let sessionManager: SessionManager; - let db: DB; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - crypto: { - key: keyRing.dbKey, - ops: { - encrypt: async (key, plainText) => { - return keysUtils.encryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(plainText), - ); - }, - decrypt: async (key, cipherText) => { - return keysUtils.decryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(cipherText), - ); - }, - }, - }, - }); - sessionManager = await SessionManager.createSessionManager({ - db, - keyRing, - logger, - }); - const clientService = { - agentLockAll: agentLockAll({ - authenticate, - sessionManager, - logger, - db, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - timer: timerStart(5000), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await sessionManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('locks all sessions', async () => { - const token = await sessionManager.createToken(); - const request = new utilsPB.EmptyMessage(); - const response = await grpcClient.agentLockAll( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - expect(await sessionManager.verifyToken(token)).toBeFalsy(); - }); -}); diff --git a/tests/client/service/agentStatus.test.ts b/tests/client/service/agentStatus.test.ts deleted file mode 100644 index 4f271cedc..000000000 --- a/tests/client/service/agentStatus.test.ts +++ /dev/null @@ -1,143 +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 { DB } from '@matrixai/db'; -import KeyRing from '@/keys/KeyRing'; -import TaskManager from '@/tasks/TaskManager'; -import CertManager from '@/keys/CertManager'; -import Proxy from '@/network/Proxy'; -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 clientUtils from '@/client/utils/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testsUtils from '../../utils'; - -describe('agentStatus', () => { - const logger = new Logger('agentStatus test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const authToken = 'abc123'; - let dataDir: string; - let db: DB; - let keyRing: KeyRing; - let taskManager: TaskManager; - let certManager: CertManager; - let grpcServerClient: GRPCServer; - let grpcServerAgent: GRPCServer; - let proxy: Proxy; - 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'); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - taskManager = await TaskManager.createTaskManager({ db, logger }); - certManager = await CertManager.createCertManager({ - db, - keyRing, - taskManager, - logger, - }); - grpcServerClient = new GRPCServer({ logger }); - await grpcServerClient.start({ - services: [], - }); - grpcServerAgent = new GRPCServer({ logger }); - await grpcServerAgent.start({ - services: [], - }); - proxy = new Proxy({ - authToken, - logger, - }); - await proxy.start({ - serverHost: '127.0.0.1' as Host, - serverPort: 0 as Port, - tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), - }); - const clientService = { - agentStatus: agentStatus({ - authenticate, - keyRing, - certManager, - grpcServerClient, - grpcServerAgent, - proxy, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await proxy.stop(); - await grpcServerAgent.stop(); - await grpcServerClient.stop(); - await certManager.stop(); - await taskManager.stop(); - await keyRing.stop(); - await db.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('gets status', async () => { - const request = new utilsPB.EmptyMessage(); - const response = await grpcClient.agentStatus( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(agentPB.InfoMessage); - expect(response.toObject()).toMatchObject({ - pid: expect.any(Number), - nodeId: expect.any(String), - clientHost: expect.any(String), - clientPort: expect.any(Number), - agentHost: expect.any(String), - agentPort: expect.any(Number), - forwardHost: expect.any(String), - forwardPort: expect.any(Number), - proxyHost: expect.any(String), - proxyPort: expect.any(Number), - publicKeyJwk: expect.any(String), - certChainPem: expect.any(String), - }); - }); -}); diff --git a/tests/client/service/agentStop.test.ts b/tests/client/service/agentStop.test.ts deleted file mode 100644 index 95c8df36d..000000000 --- a/tests/client/service/agentStop.test.ts +++ /dev/null @@ -1,98 +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 { running } from '@matrixai/async-init'; -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 clientUtils from '@/client/utils/utils'; -import * as keysUtils from '@/keys/utils/index'; - -describe('agentStop', () => { - const logger = new Logger('agentStop test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let nodePath: string; - let pkAgent: PolykeyAgent; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - nodePath = path.join(dataDir, 'polykey'); - // Note that by doing this, the agent the call is stopping is a separate agent - pkAgent = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, - }); - const clientService = { - agentStop: agentStop({ - authenticate, - pkAgent: pkAgent as unknown as PolykeyAgent, - logger, - }), - }; - 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: pkAgent.keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await pkAgent.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('stops the agent', async () => { - const statusPath = path.join(nodePath, config.defaults.statusBase); - const statusLockPath = path.join(nodePath, config.defaults.statusLockBase); - const status = new Status({ - statusPath, - statusLockPath, - fs, - logger, - }); - const request = new utilsPB.EmptyMessage(); - const response = await grpcClient.agentStop( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - // It may already be stopping - expect(await status.readStatus()).toMatchObject({ - status: expect.stringMatching(/LIVE|STOPPING|DEAD/), - }); - await status.waitFor('DEAD'); - expect(pkAgent[running]).toBe(false); - }); -}); diff --git a/tests/client/service/agentUnlock.test.ts b/tests/client/service/agentUnlock.test.ts deleted file mode 100644 index 4a64076ad..000000000 --- a/tests/client/service/agentUnlock.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Metadata } from '@grpc/grpc-js'; -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 * as nodesUtils from '@/nodes/utils'; -import * as clientUtils from '@/client/utils/utils'; - -describe('agentUnlock', () => { - const logger = new Logger('agentUnlock test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - beforeEach(async () => { - const clientService = { - agentUnlock: agentUnlock({ - authenticate, - logger, - }), - }; - 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.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - }); - test('requests a session', async () => { - const request = new utilsPB.EmptyMessage(); - const response = await grpcClient.agentUnlock( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - }); -}); diff --git a/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts b/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts deleted file mode 100644 index 91411b6fb..000000000 --- a/tests/client/service/gestaltsActionsSetUnsetGetByIdentity.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import type { IdentityId, ProviderId } from '@/identities/types'; -import type { NodeId } from '@/nodes/types'; -import type { Host, Port } from '@/network/types'; -import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; -import type { ClaimLinkIdentity } from '@/claims/payloads/index'; -import type { ClaimIdEncoded } from '@/ids/index'; -import type { ProviderIdentityClaimId } from '@/identities/types'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -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'; -import { encodeProviderIdentityId } from '@/ids/index'; -import Token from '@/tokens/Token'; -import * as keysUtils from '@/keys/utils'; - -describe('gestaltsActionsByIdentity', () => { - const logger = new Logger('gestaltsActionsByIdentity test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const keyPair = keysUtils.generateKeyPair(); - const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); - const node: GestaltNodeInfo = { - nodeId: nodeId, - }; - const identity: GestaltIdentityInfo = { - identityId: 'identityId' as IdentityId, - providerId: 'providerId' as ProviderId, - }; - 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 - // Constructing the claim - const dummyClaim: ClaimLinkIdentity = { - typ: 'ClaimLinkIdentity', - iss: nodesUtils.encodeNodeId(nodeId), - sub: encodeProviderIdentityId([identity.providerId, identity.identityId]), - jti: '' as ClaimIdEncoded, - iat: 0, - nbf: 0, - exp: 0, - aud: '', - seq: 0, - prevClaimId: null, - prevDigest: null, - }; - const token = Token.fromPayload(dummyClaim); - token.signWithPrivateKey(keyPair); - const signedClaim = token.toSigned(); - await gestaltGraph.linkNodeAndIdentity(node, identity, { - claim: signedClaim, - meta: { providerIdentityClaimId: '' as ProviderIdentityClaimId }, - }); - const clientService = { - gestaltsActionsSetByIdentity: gestaltsActionsSetByIdentity({ - authenticate, - gestaltGraph, - logger, - db, - }), - gestaltsActionsGetByIdentity: gestaltsActionsGetByIdentity({ - authenticate, - gestaltGraph, - logger, - db, - }), - gestaltsActionsUnsetByIdentity: gestaltsActionsUnsetByIdentity({ - authenticate, - gestaltGraph, - logger, - db, - }), - }; - 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 deleted file mode 100644 index 4c67b74e8..000000000 --- a/tests/client/service/gestaltsActionsSetUnsetGetByNode.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import type { NodeId } from '@/nodes/types'; -import type { Host, Port } from '@/network/types'; -import type { GestaltNodeInfo } from '@/gestalts/types'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -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: GestaltNodeInfo = { - nodeId: nodeId, - }; - 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, - logger, - db, - }), - gestaltsActionsGetByNode: gestaltsActionsGetByNode({ - authenticate, - gestaltGraph, - logger, - db, - }), - gestaltsActionsUnsetByNode: gestaltsActionsUnsetByNode({ - authenticate, - gestaltGraph, - logger, - db, - }), - }; - 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(nodesUtils.encodeNodeId(node.nodeId)); - 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/gestaltsGestaltGetByIdentity.test.ts b/tests/client/service/gestaltsGestaltGetByIdentity.test.ts deleted file mode 100644 index 8d2cae4d2..000000000 --- a/tests/client/service/gestaltsGestaltGetByIdentity.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; -import type { NodeId } from '@/ids/types'; -import type { IdentityId, ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type { ClaimLinkIdentity } from '@/claims/payloads/index'; -import type { ClaimIdEncoded, ProviderIdentityClaimId } from '@/ids/types'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -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'; -import { encodeProviderIdentityId } from '@/ids/index'; -import Token from '@/tokens/Token'; -import * as keysUtils from '@/keys/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 keyPair = keysUtils.generateKeyPair(); - const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); - const node: GestaltNodeInfo = { - nodeId: nodeId, - }; - const identity: GestaltIdentityInfo = { - identityId: 'identityId' as IdentityId, - providerId: 'providerId' as ProviderId, - }; - const nodeKey = gestaltUtils.encodeGestaltId(['node', nodeId]); - const identityKey = gestaltUtils.encodeGestaltId([ - 'identity', - [identity.providerId, identity.identityId], - ]); - const expectedGestalt = { - matrix: {}, - nodes: {}, - identities: {}, - }; - expectedGestalt.matrix[identityKey] = {}; - expectedGestalt.matrix[nodeKey] = {}; - expectedGestalt.matrix[identityKey][nodeKey] = null; - expectedGestalt.matrix[nodeKey][identityKey] = null; - expectedGestalt.nodes[nodeKey] = expect.anything(); - expectedGestalt.identities[identityKey] = expect.anything(); - 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, - }); - // Constructing the claim - const dummyClaim: ClaimLinkIdentity = { - typ: 'ClaimLinkIdentity', - iss: nodesUtils.encodeNodeId(nodeId), - sub: encodeProviderIdentityId([identity.providerId, identity.identityId]), - jti: '' as ClaimIdEncoded, - iat: 0, - nbf: 0, - exp: 0, - aud: '', - seq: 0, - prevClaimId: null, - prevDigest: null, - }; - const token = Token.fromPayload(dummyClaim); - token.signWithPrivateKey(keyPair); - const signedClaim = token.toSigned(); - await gestaltGraph.linkNodeAndIdentity(node, identity, { - claim: signedClaim, - meta: { providerIdentityClaimId: '' as ProviderIdentityClaimId }, - }); - const clientService = { - gestaltsGestaltGetByIdentity: gestaltsGestaltGetByIdentity({ - authenticate, - gestaltGraph, - logger, - db, - }), - }; - 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 deleted file mode 100644 index 3cb31a380..000000000 --- a/tests/client/service/gestaltsGestaltGetByNode.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type { GestaltIdentityInfo, GestaltNodeInfo } from '@/gestalts/types'; -import type { NodeId } from '@/nodes/types'; -import type { - IdentityId, - ProviderId, - ProviderIdentityClaimId, -} from '@/identities/types'; -import type { ClaimLinkIdentity } from '@/claims/payloads/index'; -import type { ClaimIdEncoded } from '@/ids/index'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -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'; -import { encodeProviderIdentityId } from '@/ids/index'; -import Token from '@/tokens/Token'; -import * as keysUtils from '@/keys/utils'; - -describe('gestaltsGestaltGetByNode', () => { - const logger = new Logger('gestaltsGestaltGetByNode test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const keyPair = keysUtils.generateKeyPair(); - const nodeId = keysUtils.publicKeyToNodeId(keyPair.publicKey); - const node: GestaltNodeInfo = { - nodeId: nodeId, - }; - const identity: GestaltIdentityInfo = { - identityId: 'identityId' as IdentityId, - providerId: 'providerId' as ProviderId, - }; - const nodeKey = gestaltUtils.encodeGestaltId(['node', nodeId]); - const identityKey = gestaltUtils.encodeGestaltId([ - 'identity', - [identity.providerId, identity.identityId], - ]); - const expectedGestalt = { - matrix: {}, - nodes: {}, - identities: {}, - }; - expectedGestalt.matrix[identityKey] = {}; - expectedGestalt.matrix[nodeKey] = {}; - expectedGestalt.matrix[identityKey][nodeKey] = null; - expectedGestalt.matrix[nodeKey][identityKey] = null; - expectedGestalt.nodes[nodeKey] = expect.anything(); - expectedGestalt.identities[identityKey] = expect.anything(); - 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, - }); - // Constructing the claim - const dummyClaim: ClaimLinkIdentity = { - typ: 'ClaimLinkIdentity', - iss: nodesUtils.encodeNodeId(nodeId), - sub: encodeProviderIdentityId([identity.providerId, identity.identityId]), - jti: '' as ClaimIdEncoded, - iat: 0, - nbf: 0, - exp: 0, - aud: '', - seq: 0, - prevClaimId: null, - prevDigest: null, - }; - const token = Token.fromPayload(dummyClaim); - token.signWithPrivateKey(keyPair); - const signedClaim = token.toSigned(); - await gestaltGraph.linkNodeAndIdentity(node, identity, { - claim: signedClaim, - meta: { providerIdentityClaimId: '' as ProviderIdentityClaimId }, - }); - const clientService = { - gestaltsGestaltGetByNode: gestaltsGestaltGetByNode({ - authenticate, - gestaltGraph, - logger, - db, - }), - }; - 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(nodesUtils.encodeNodeId(node.nodeId)); - const response = await grpcClient.gestaltsGestaltGetByNode( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(gestaltsPB.Graph); - expect(JSON.parse(response.getGestaltGraph())).toMatchObject( - expectedGestalt, - ); - }); -}); diff --git a/tests/client/service/gestaltsGestaltList.test.ts b/tests/client/service/gestaltsGestaltList.test.ts deleted file mode 100644 index 240ba713a..000000000 --- a/tests/client/service/gestaltsGestaltList.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import type { - Gestalt, - GestaltIdentityInfo, - GestaltNodeInfo, -} from '@/gestalts/types'; -import type { NodeId } from '@/ids/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 { 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'; - -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: GestaltNodeInfo = { - nodeId: nodeId, - }; - const identity: GestaltIdentityInfo = { - identityId: 'identityId' as IdentityId, - providerId: 'providerId' as ProviderId, - }; - const nodeKey = gestaltUtils.encodeGestaltId(['node', nodeId]); - const identityKey = gestaltUtils.encodeGestaltId([ - 'identity', - [identity.providerId, identity.identityId], - ]); - const gestalt1: Gestalt = { - matrix: {}, - nodes: {}, - identities: {}, - }; - gestalt1.matrix[nodeKey] = {}; - gestalt1.nodes[nodeKey] = expect.any(Object); - const gestalt2: Gestalt = { - matrix: {}, - nodes: {}, - identities: {}, - }; - gestalt2.matrix[identityKey] = {}; - gestalt2.identities[identityKey] = expect.any(Object); - 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, - logger, - db, - }), - }; - 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).toMatchObject([gestalt2, gestalt1]); - }); -}); diff --git a/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts b/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts deleted file mode 100644 index eff70ffbd..000000000 --- a/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts +++ /dev/null @@ -1,454 +0,0 @@ -import type { NodeId, ProviderIdentityClaimId } from '@/ids/types'; -import type { IdentityId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type { Key } from '@/keys/types'; -import type { ClaimId } from '@/ids/types'; -import type { SignedClaim } from '@/claims/types'; -import type { ClaimLinkIdentity } from '@/claims/payloads/index'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; -import TaskManager from '@/tasks/TaskManager'; -import PolykeyAgent from '@/PolykeyAgent'; -import KeyRing from '@/keys/KeyRing'; -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 Proxy from '@/network/Proxy'; -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 * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; -import * as gestaltsErrors from '@/gestalts/errors'; -import * as keysUtils from '@/keys/utils'; -import * as clientUtils from '@/client/utils/utils'; -import * as nodesUtils from '@/nodes/utils'; -import * as utils from '@/utils/index'; -import { encodeProviderIdentityId } from '@/ids/index'; -import { sleep } from '@/utils/index'; -import * as testsUtils from '../../utils/index'; -import TestProvider from '../../identities/TestProvider'; -import * as testUtils from '../../utils'; - -describe('gestaltsGestaltTrustByIdentity', () => { - const logger = new Logger( - 'gestaltsGestaltTrustByIdentity test', - LogLevel.WARN, - [new StreamHandler()], - ); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let testProvider: TestProvider; - // Create node to trust - const connectedIdentity = 'trusted-node' as IdentityId; - let nodeDataDir: string; - let node: PolykeyAgent; - let nodeId: NodeId; - let claimId: ClaimId; - const nodeChainData: Record = {}; - let mockedRequestChainData: jest.SpyInstance; - const authToken = 'abc123'; - let dataDir: string; - let discovery: Discovery; - let gestaltGraph: GestaltGraph; - let identitiesManager: IdentitiesManager; - let taskManager: TaskManager; - let nodeManager: NodeManager; - let nodeConnectionManager: NodeConnectionManager; - let nodeGraph: NodeGraph; - let sigchain: Sigchain; - let proxy: Proxy; - let acl: ACL; - let db: DB; - let keyRing: KeyRing; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - beforeEach(async () => { - testProvider = new TestProvider(); - mockedRequestChainData = jest - .spyOn(NodeManager.prototype, 'requestChainData') - .mockResolvedValue(nodeChainData); - nodeDataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'trusted-node-'), - ); - const nodePath = path.join(nodeDataDir, 'polykey'); - node = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath, - networkConfig: { - proxyHost: '127.0.0.1' as Host, - forwardHost: '127.0.0.1' as Host, - agentHost: '127.0.0.1' as Host, - clientHost: '127.0.0.1' as Host, - }, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, - }); - nodeId = node.keyRing.getNodeId(); - node.identitiesManager.registerProvider(testProvider); - await node.identitiesManager.putToken(testProvider.id, connectedIdentity, { - accessToken: 'abc123', - }); - testProvider.users['trusted-node'] = {}; - const identityClaim = { - typ: 'ClaimLinkIdentity', - iss: nodesUtils.encodeNodeId(node.keyRing.getNodeId()), - sub: encodeProviderIdentityId([testProvider.id, connectedIdentity]), - }; - const [claimId_, claim] = await node.sigchain.addClaim(identityClaim); - claimId = claimId_; - nodeChainData[claimId_] = claim; - await testProvider.publishClaim( - connectedIdentity, - claim as SignedClaim, - ); - - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const keysPath = path.join(dataDir, 'keys'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - crypto: { - key: keyRing.dbKey, - ops: { - encrypt: async (key, plainText) => { - return keysUtils.encryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(plainText), - ); - }, - decrypt: async (key, cipherText) => { - return keysUtils.decryptWithKey( - utils.bufferWrap(key) as Key, - utils.bufferWrap(cipherText), - ); - }, - }, - }, - }); - acl = await ACL.createACL({ - db, - logger, - }); - gestaltGraph = await GestaltGraph.createGestaltGraph({ - db, - acl, - logger, - }); - identitiesManager = await IdentitiesManager.createIdentitiesManager({ - keyRing, - sigchain, - db, - gestaltGraph, - logger, - }); - identitiesManager.registerProvider(testProvider); - await identitiesManager.putToken( - testProvider.id, - 'test-user' as IdentityId, - { - accessToken: 'def456', - }, - ); - proxy = new Proxy({ - authToken, - logger, - }); - await proxy.start({ - serverHost: '127.0.0.1' as Host, - serverPort: 0 as Port, - tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), - }); - sigchain = await Sigchain.createSigchain({ - db, - keyRing, - logger, - }); - nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing, - logger: logger.getChild('NodeGraph'), - }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); - nodeConnectionManager = new NodeConnectionManager({ - keyRing, - nodeGraph, - proxy, - taskManager, - connConnectTime: 2000, - connTimeoutTime: 2000, - logger: logger.getChild('NodeConnectionManager'), - }); - nodeManager = new NodeManager({ - db, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - gestaltGraph, - logger, - }); - await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager }); - await nodeManager.setNode(nodeId, { - host: node.proxy.getProxyHost(), - port: node.proxy.getProxyPort(), - }); - discovery = await Discovery.createDiscovery({ - db, - keyRing, - gestaltGraph, - identitiesManager, - nodeManager, - taskManager, - logger, - }); - await taskManager.startProcessing(); - const clientService = { - gestaltsGestaltTrustByIdentity: gestaltsGestaltTrustByIdentity({ - authenticate, - gestaltGraph, - discovery, - logger, - db, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await taskManager.stopProcessing(); - await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); - await discovery.stop(); - await nodeConnectionManager.stop(); - await nodeManager.stop(); - await nodeGraph.stop(); - await proxy.stop(); - await sigchain.stop(); - await identitiesManager.stop(); - await gestaltGraph.stop(); - await acl.stop(); - await db.stop(); - await keyRing.stop(); - await taskManager.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - - await node.stop(); - await fs.promises.rm(nodeDataDir, { - force: true, - recursive: true, - }); - mockedRequestChainData.mockRestore(); - }); - test('trusts an identity (already set in gestalt graph)', async () => { - testProvider.users['disconnected-user'] = {}; - await gestaltGraph.linkNodeAndIdentity( - { nodeId: nodeId }, - { - providerId: testProvider.id, - identityId: connectedIdentity, - }, - { - claim: nodeChainData[claimId] as SignedClaim, - meta: { - providerIdentityClaimId: '' as ProviderIdentityClaimId, - }, - }, - ); - const request = new identitiesPB.Provider(); - request.setProviderId(testProvider.id); - request.setIdentityId(connectedIdentity); - const response = await grpcClient.gestaltsGestaltTrustByIdentity( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - expect( - await gestaltGraph.getGestaltActions([ - 'identity', - [testProvider.id, connectedIdentity], - ]), - ).toEqual({ - notify: null, - }); - }); - test('trusts an identity (new identity)', async () => { - const request = new identitiesPB.Provider(); - request.setProviderId(testProvider.id); - request.setIdentityId(connectedIdentity); - // Should fail on first attempt - need to allow time for the identity to be - // linked to a node via discovery - await testUtils.expectRemoteError( - grpcClient.gestaltsGestaltTrustByIdentity( - request, - clientUtils.encodeAuthFromPassword(password), - ), - gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, - ); - // Wait for both identity and node to be set in GG - let existingTasks: number = 0; - do { - existingTasks = await discovery.waitForDiscoveryTasks(); - } while (existingTasks > 0); - const response = await grpcClient.gestaltsGestaltTrustByIdentity( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - expect( - await gestaltGraph.getGestaltActions([ - 'identity', - [testProvider.id, connectedIdentity], - ]), - ).toEqual({ - notify: null, - }); - }); - test('cannot trust a disconnected identity', async () => { - testProvider.users['disconnected-user'] = {}; - const request = new identitiesPB.Provider(); - request.setProviderId(testProvider.id); - request.setIdentityId('disconnected-user'); - // Should fail on first attempt - attempt to find a connected node - await testUtils.expectRemoteError( - grpcClient.gestaltsGestaltTrustByIdentity( - request, - clientUtils.encodeAuthFromPassword(password), - ), - gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, - ); - // Wait and try again - should fail again because the identity has no - // linked nodes we can trust - await testUtils.expectRemoteError( - grpcClient.gestaltsGestaltTrustByIdentity( - request, - clientUtils.encodeAuthFromPassword(password), - ), - gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, - ); - }); - test('trust extends to entire gestalt', async () => { - await gestaltGraph.linkNodeAndIdentity( - { nodeId: nodeId }, - { - providerId: testProvider.id, - identityId: connectedIdentity, - }, - { - claim: nodeChainData[claimId] as SignedClaim, - meta: { - providerIdentityClaimId: '' as ProviderIdentityClaimId, - }, - }, - ); - const request = new identitiesPB.Provider(); - request.setProviderId(testProvider.id); - request.setIdentityId(connectedIdentity); - const response = await grpcClient.gestaltsGestaltTrustByIdentity( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - expect( - await gestaltGraph.getGestaltActions([ - 'identity', - [testProvider.id, connectedIdentity], - ]), - ).toEqual({ - notify: null, - }); - expect(await gestaltGraph.getGestaltActions(['node', nodeId])).toEqual({ - notify: null, - }); - }); - test('links trusted identity to an existing node', async () => { - await gestaltGraph.setNode({ - nodeId: nodeId, - }); - const request = new identitiesPB.Provider(); - request.setProviderId(testProvider.id); - request.setIdentityId(connectedIdentity); - // Should fail on first attempt - need to allow time for the identity to be - // linked to a node via discovery - await testUtils.expectRemoteError( - grpcClient.gestaltsGestaltTrustByIdentity( - request, - clientUtils.encodeAuthFromPassword(password), - ), - gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing, - ); - // Wait and try again - should succeed second time - await sleep(2000); - await grpcClient.gestaltsGestaltTrustByIdentity( - request, - clientUtils.encodeAuthFromPassword(password), - ); - // Wait for both identity and node to be set in GG - let existingTasks: number = 0; - do { - existingTasks = await discovery.waitForDiscoveryTasks(); - } while (existingTasks > 0); - const response = await grpcClient.gestaltsGestaltTrustByIdentity( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - expect( - await gestaltGraph.getGestaltActions([ - 'identity', - [testProvider.id, connectedIdentity], - ]), - ).toEqual({ - notify: null, - }); - expect(await gestaltGraph.getGestaltActions(['node', nodeId])).toEqual({ - notify: null, - }); - }); -}); diff --git a/tests/client/service/identitiesAuthenticate.test.ts b/tests/client/service/identitiesAuthenticate.test.ts deleted file mode 100644 index f126e16c1..000000000 --- a/tests/client/service/identitiesAuthenticate.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import type { IdentityId, ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type KeyRing from 'keys/KeyRing'; -import type Sigchain from 'sigchain/Sigchain'; -import type GestaltGraph from 'gestalts/GestaltGraph'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -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 * as validationErrors from '@/validation/errors'; -import * as clientUtils from '@/client/utils/utils'; -import * as nodesUtils from '@/nodes/utils'; -import TestProvider from '../../identities/TestProvider'; -import * as testUtils from '../../utils'; - -describe('identitiesAuthenticate', () => { - const logger = new Logger('identitiesAuthenticate 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, - providerToken: { - accessToken: 'abc123', - }, - }; - let dataDir: string; - let testProvider: TestProvider; - 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, - gestaltGraph: {} as GestaltGraph, - keyRing: {} as KeyRing, - sigchain: {} as Sigchain, - logger, - }); - testProvider = new TestProvider(); - identitiesManager.registerProvider(testProvider); - const clientService = { - identitiesAuthenticate: identitiesAuthenticate({ - authenticate, - identitiesManager, - logger, - }), - }; - 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.getPort(), - 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 identity', async () => { - const request = new identitiesPB.Provider(); - request.setProviderId(testToken.providerId); - const response = grpcClient.identitiesAuthenticate( - request, - clientUtils.encodeAuthFromPassword(password), - ); - let step = 0; - for await (const message of response) { - if (step === 0) { - expect(message.getStepCase()).toBe( - identitiesPB.AuthenticationProcess.StepCase.REQUEST, - ); - const authRequest = message.getRequest()!; - expect(authRequest.getUrl()).toBe('test.com'); - expect(authRequest.getDataMap().get('userCode')).toBe('randomtestcode'); - } - if (step === 1) { - expect(message.getStepCase()).toBe( - identitiesPB.AuthenticationProcess.StepCase.RESPONSE, - ); - const authResponse = message.getResponse()!; - expect(authResponse.getIdentityId()).toBe(testToken.identityId); - } - step++; - } - expect( - await identitiesManager.getToken( - testToken.providerId, - testToken.identityId, - ), - ).toEqual(testToken.providerToken); - expect(response.stream.destroyed).toBeTruthy(); - await identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - }); - test('cannot authenticate invalid provider', async () => { - const request = new identitiesPB.Provider(); - request.setProviderId(''); - await testUtils.expectRemoteError( - grpcClient - .identitiesAuthenticate( - request, - clientUtils.encodeAuthFromPassword(password), - ) - .next(), - validationErrors.ErrorValidation, - ); - }); -}); diff --git a/tests/client/service/identitiesAuthenticatedGet.test.ts b/tests/client/service/identitiesAuthenticatedGet.test.ts deleted file mode 100644 index 68cbc9094..000000000 --- a/tests/client/service/identitiesAuthenticatedGet.test.ts +++ /dev/null @@ -1,244 +0,0 @@ -import type { IdentityId, ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type KeyRing from '@/keys/KeyRing'; -import type Sigchain from '@/sigchain/Sigchain'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -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 TestProvider from '../../identities/TestProvider'; - -describe('identitiesAuthenticatedGet', () => { - const logger = new Logger('identitiesAuthenticatedGet test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const providerToken = { - 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, - gestaltGraph: {} as GestaltGraph, - keyRing: {} as KeyRing, - sigchain: {} as Sigchain, - logger, - }); - const clientService = { - identitiesAuthenticatedGet: identitiesAuthenticatedGet({ - authenticate, - identitiesManager, - logger, - }), - }; - 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.getPort(), - 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 an authenticated identity', async () => { - // Setup provider - const provider = new TestProvider(); - const user1 = { - providerId: provider.id, - identityId: 'user1' as IdentityId, - }; - provider.users['user1'] = user1; - identitiesManager.registerProvider(provider); - await identitiesManager.putToken( - user1.providerId, - user1.identityId, - providerToken, - ); - const request = new identitiesPB.OptionalProvider(); - const response = grpcClient.identitiesAuthenticatedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const providerMessage of response) { - expect(providerMessage).toBeInstanceOf(identitiesPB.Provider); - const obj = providerMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual(user1); - }); - test('does not get an unauthenticated identity', async () => { - // Setup provider - const provider = new TestProvider(); - const user1 = { - providerId: provider.id, - identityId: 'user1' as IdentityId, - }; - provider.users['user1'] = user1; - identitiesManager.registerProvider(provider); - await identitiesManager.putToken( - user1.providerId, - user1.identityId, - providerToken, - ); - await identitiesManager.delToken(user1.providerId, user1.identityId); - const request = new identitiesPB.OptionalProvider(); - const response = grpcClient.identitiesAuthenticatedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const providerMessage of response) { - expect(providerMessage).toBeInstanceOf(identitiesPB.Provider); - const obj = providerMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(0); - }); - test('gets authenticated identities from multiple providers', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - }; - const user2 = { - providerId: provider1.id, - identityId: 'user2' as IdentityId, - }; - const user3 = { - providerId: provider2.id, - identityId: 'user3' as IdentityId, - }; - provider1.users['user1'] = user1; - provider1.users['user2'] = user2; - provider2.users['user3'] = user3; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - user1.providerId, - user1.identityId, - providerToken, - ); - await identitiesManager.putToken( - user2.providerId, - user2.identityId, - providerToken, - ); - await identitiesManager.putToken( - user3.providerId, - user3.identityId, - providerToken, - ); - const request = new identitiesPB.OptionalProvider(); - const response = grpcClient.identitiesAuthenticatedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const providerMessage of response) { - expect(providerMessage).toBeInstanceOf(identitiesPB.Provider); - const obj = providerMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(3); - expect(output[0]).toEqual(user1); - expect(output[1]).toEqual(user2); - expect(output[2]).toEqual(user3); - }); - test('gets authenticated identities a specific provider', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - }; - const user2 = { - providerId: provider1.id, - identityId: 'user2' as IdentityId, - }; - const user3 = { - providerId: provider2.id, - identityId: 'user3' as IdentityId, - }; - provider1.users['user1'] = user1; - provider1.users['user2'] = user2; - provider2.users['user3'] = user3; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - user1.providerId, - user1.identityId, - providerToken, - ); - await identitiesManager.putToken( - user2.providerId, - user2.identityId, - providerToken, - ); - await identitiesManager.putToken( - user3.providerId, - user3.identityId, - providerToken, - ); - const request = new identitiesPB.OptionalProvider(); - request.setProviderId(provider2.id); - const response = grpcClient.identitiesAuthenticatedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const providerMessage of response) { - expect(providerMessage).toBeInstanceOf(identitiesPB.Provider); - const obj = providerMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual(user3); - }); -}); diff --git a/tests/client/service/identitiesClaim.test.ts b/tests/client/service/identitiesClaim.test.ts deleted file mode 100644 index 03695e71b..000000000 --- a/tests/client/service/identitiesClaim.test.ts +++ /dev/null @@ -1,257 +0,0 @@ -import type { IdentityId, ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type NodeManager from '@/nodes/NodeManager'; -import type { ClaimLinkIdentity } from '@/claims/payloads/index'; -import type { Claim } from '@/claims/types'; -import type GestaltGraph from 'gestalts/GestaltGraph'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; -import TaskManager from '@/tasks/TaskManager'; -import KeyRing from '@/keys/KeyRing'; -import IdentitiesManager from '@/identities/IdentitiesManager'; -import NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import NodeGraph from '@/nodes/NodeGraph'; -import Sigchain from '@/sigchain/Sigchain'; -import Proxy from '@/network/Proxy'; -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 claimsUtils from '@/claims/utils'; -import * as nodesUtils from '@/nodes/utils'; -import * as validationErrors from '@/validation/errors'; -import * as keysUtils from '@/keys/utils/index'; -import Token from '@/tokens/Token'; -import { encodeProviderIdentityId } from '@/identities/utils'; -import TestProvider from '../../identities/TestProvider'; -import * as testUtils from '../../utils'; -import * as testsUtils from '../../utils/index'; - -describe('identitiesClaim', () => { - const logger = new Logger('identitiesClaim 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, - providerToken: { - accessToken: 'abc123', - }, - }; - const issNodeKeypair = keysUtils.generateKeyPair(); - const issNodeId = keysUtils.publicKeyToNodeId(issNodeKeypair.publicKey); - const claimId = claimsUtils.createClaimIdGenerator(issNodeId)(); - let mockedAddClaim: jest.SpyInstance; - const dummyNodeManager = { setNode: jest.fn() } as unknown as NodeManager; - beforeAll(async () => { - const dummyClaim: ClaimLinkIdentity = { - typ: 'ClaimLinkIdentity', - iss: nodesUtils.encodeNodeId(issNodeId), - sub: encodeProviderIdentityId([ - testToken.providerId, - testToken.identityId, - ]), - jti: claimsUtils.encodeClaimId(claimId), - iat: 0, - nbf: 0, - exp: 0, - aud: '', - seq: 0, - prevClaimId: null, - prevDigest: null, - }; - const token = Token.fromPayload(dummyClaim); - token.signWithPrivateKey(issNodeKeypair); - const signedClaim = token.toSigned(); - mockedAddClaim = jest - .spyOn(Sigchain.prototype, 'addClaim') - .mockImplementation(async (payload, _, func) => { - const token = Token.fromPayload(payload); - // We need to call the function to resolve a promise in the code - func != null && (await func(token as unknown as Token)); - return [claimId, signedClaim]; - }); - }); - afterAll(async () => { - mockedAddClaim.mockRestore(); - }); - const authToken = 'abc123'; - let dataDir: string; - let testProvider: TestProvider; - let identitiesManager: IdentitiesManager; - let nodeGraph: NodeGraph; - let taskManager: TaskManager; - let nodeConnectionManager: NodeConnectionManager; - let sigchain: Sigchain; - let proxy: Proxy; - - let db: DB; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - sigchain = await Sigchain.createSigchain({ - db, - keyRing, - logger, - }); - identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - gestaltGraph: { - linkNodeAndIdentity: jest.fn(), - } as unknown as GestaltGraph, - keyRing: keyRing, - sigchain: sigchain, - logger, - }); - testProvider = new TestProvider(); - identitiesManager.registerProvider(testProvider); - proxy = new Proxy({ - authToken, - logger, - }); - await proxy.start({ - serverHost: '127.0.0.1' as Host, - serverPort: 0 as Port, - tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), - }); - nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing, - logger: logger.getChild('NodeGraph'), - }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); - nodeConnectionManager = new NodeConnectionManager({ - connConnectTime: 2000, - proxy, - keyRing, - nodeGraph, - taskManager, - logger: logger.getChild('NodeConnectionManager'), - }); - await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); - await taskManager.startProcessing(); - const clientService = { - identitiesClaim: identitiesClaim({ - authenticate, - identitiesManager, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await taskManager.stopProcessing(); - await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); - await nodeConnectionManager.stop(); - await nodeGraph.stop(); - await sigchain.stop(); - await proxy.stop(); - await identitiesManager.stop(); - await db.stop(); - await keyRing.stop(); - await taskManager.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('claims identity', async () => { - // Need an authenticated identity - await identitiesManager.putToken( - testToken.providerId, - testToken.identityId, - testToken.providerToken, - ); - const request = new identitiesPB.Provider(); - request.setIdentityId(testToken.identityId); - request.setProviderId(testToken.providerId); - const response = await grpcClient.identitiesClaim( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(identitiesPB.Claim); - expect(response.getClaimId()).toBe('0'); - expect(response.getUrl()).toBe('test.com'); - // Reverse side-effects - testProvider.linkIdCounter = 0; - testProvider.links = {}; - await identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - }); - test('cannot claim invalid identity', async () => { - const request = new identitiesPB.Provider(); - request.setIdentityId(''); - request.setProviderId(testToken.providerId); - await testUtils.expectRemoteError( - grpcClient.identitiesClaim( - request, - clientUtils.encodeAuthFromPassword(password), - ), - validationErrors.ErrorValidation, - ); - request.setIdentityId(testToken.identityId); - request.setProviderId(''); - await testUtils.expectRemoteError( - grpcClient.identitiesClaim( - request, - clientUtils.encodeAuthFromPassword(password), - ), - validationErrors.ErrorValidation, - ); - request.setIdentityId(''); - request.setProviderId(''); - await testUtils.expectRemoteError( - grpcClient.identitiesClaim( - request, - clientUtils.encodeAuthFromPassword(password), - ), - validationErrors.ErrorValidation, - ); - }); -}); diff --git a/tests/client/service/identitiesInfoConnectedGet.test.ts b/tests/client/service/identitiesInfoConnectedGet.test.ts deleted file mode 100644 index 221422420..000000000 --- a/tests/client/service/identitiesInfoConnectedGet.test.ts +++ /dev/null @@ -1,749 +0,0 @@ -import type { IdentityId, ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type KeyRing from 'keys/KeyRing'; -import type Sigchain from 'sigchain/Sigchain'; -import type GestaltGraph from 'gestalts/GestaltGraph'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -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 TestProvider from '../../identities/TestProvider'; -import * as testUtils from '../../utils'; - -describe('identitiesInfoConnectedGet', () => { - const logger = new Logger('identitiesInfoConnectedGet test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const testToken = { - identityId: 'test_user' as IdentityId, - providerToken: { - 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, - gestaltGraph: {} as GestaltGraph, - keyRing: {} as KeyRing, - sigchain: {} as Sigchain, - logger, - }); - const clientService = { - identitiesInfoConnectedGet: identitiesInfoConnectedGet({ - authenticate, - identitiesManager, - logger, - }), - }; - 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.getPort(), - 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 connected identities from a single provider', async () => { - // Setup provider - const provider = new TestProvider(); - const user1 = { - providerId: provider.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider.users['user1'] = user1; - const user2 = { - providerId: provider.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider.users['user2'] = user2; - identitiesManager.registerProvider(provider); - await identitiesManager.putToken( - provider.id, - testToken.identityId, - testToken.providerToken, - ); - provider.users[testToken.identityId].connected = [ - user1.identityId, - user2.identityId, - ]; - const request = new identitiesPB.ProviderSearch(); - request.setProviderIdList([provider.id]); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(2); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - expect(output[1]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - }); - test('gets connected identities to a particular identity id', async () => { - // Setup provider - const provider = new TestProvider(); - const user1 = { - providerId: provider.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider.users['user1'] = user1; - const user2 = { - providerId: provider.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider.users['user2'] = user2; - identitiesManager.registerProvider(provider); - await identitiesManager.putToken( - provider.id, - testToken.identityId, - testToken.providerToken, - ); - provider.users[testToken.identityId].connected = [user1.identityId]; - await identitiesManager.putToken( - provider.id, - 'otherAuthenticatedId' as IdentityId, - testToken.providerToken, - ); - provider.users['otherAuthenticatedId'] = { connected: [user2.identityId] }; - const request = new identitiesPB.ProviderSearch(); - request.setProviderIdList([provider.id]); - request.setAuthIdentityId('otherAuthenticatedId'); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - }); - test('gets connected identities from multiple providers', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider2.users['user2'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - provider1.users[testToken.identityId].connected = [user1.identityId]; - await identitiesManager.putToken( - provider2.id, - testToken.identityId, - testToken.providerToken, - ); - provider2.users[testToken.identityId].connected = [user2.identityId]; - const request = new identitiesPB.ProviderSearch(); - request.setProviderIdList([provider1.id, provider2.id]); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(2); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - expect(output[1]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - }); - test('gets connected identities from all providers', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider2.users['user2'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - provider1.users[testToken.identityId].connected = [user1.identityId]; - await identitiesManager.putToken( - provider2.id, - testToken.identityId, - testToken.providerToken, - ); - provider2.users[testToken.identityId].connected = [user2.identityId]; - const request = new identitiesPB.ProviderSearch(); - request.setProviderIdList([]); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(2); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - expect(output[1]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - }); - test('searches for identities matching a search term', async () => { - // Setup provider - const provider = new TestProvider(); - const user1 = { - providerId: provider.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider.users['user1'] = user1; - const user2 = { - providerId: provider.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider.users['user2'] = user2; - identitiesManager.registerProvider(provider); - await identitiesManager.putToken( - provider.id, - testToken.identityId, - testToken.providerToken, - ); - provider.users[testToken.identityId].connected = [ - user1.identityId, - user2.identityId, - ]; - const request = new identitiesPB.ProviderSearch(); - request.setSearchTermList(['1']); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - }); - test('searches for identities matching multiple search terms', async () => { - // Setup providers - const provider = new TestProvider(); - const user1 = { - providerId: provider.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider.users['user1'] = user1; - const user2 = { - providerId: provider.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider.users['user2'] = user2; - identitiesManager.registerProvider(provider); - await identitiesManager.putToken( - provider.id, - testToken.identityId, - testToken.providerToken, - ); - provider.users[testToken.identityId].connected = [ - user1.identityId, - user2.identityId, - ]; - const request = new identitiesPB.ProviderSearch(); - request.setSearchTermList(['1', '2']); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(2); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - expect(output[1]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - }); - test('searches for identities matching a search term across multiple providers', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider2.users['user1'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - await identitiesManager.putToken( - provider2.id, - testToken.identityId, - testToken.providerToken, - ); - provider1.users[testToken.identityId].connected = [user1.identityId]; - provider2.users[testToken.identityId].connected = [user2.identityId]; - const request = new identitiesPB.ProviderSearch(); - request.setSearchTermList(['1']); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(2); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - expect(output[1]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - }); - test('gets no connected identities', async () => { - // Setup provider - const provider = new TestProvider(); - const user1 = { - providerId: provider.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider.users['user1'] = user1; - const user2 = { - providerId: provider.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider.users['user2'] = user2; - identitiesManager.registerProvider(provider); - await identitiesManager.putToken( - provider.id, - testToken.identityId, - testToken.providerToken, - ); - provider.users[testToken.identityId].connected = [ - user1.identityId, - user2.identityId, - ]; - const request = new identitiesPB.ProviderSearch(); - request.setLimit('0'); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(0); - }); - test('gets one connected identity', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider2.users['user2'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - await identitiesManager.putToken( - provider2.id, - testToken.identityId, - testToken.providerToken, - ); - provider1.users[testToken.identityId].connected = [user1.identityId]; - provider2.users[testToken.identityId].connected = [user2.identityId]; - const request = new identitiesPB.ProviderSearch(); - request.setLimit('1'); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - }); - test('cannot get more identities than available', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider2.users['user2'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - await identitiesManager.putToken( - provider2.id, - testToken.identityId, - testToken.providerToken, - ); - provider1.users[testToken.identityId].connected = [user1.identityId]; - provider2.users[testToken.identityId].connected = [user2.identityId]; - const request = new identitiesPB.ProviderSearch(); - request.setLimit('3'); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(2); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - expect(output[1]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - }); - test('can only get from authenticated providers', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider2.users['user2'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - provider1.users[testToken.identityId].connected = [user1.identityId]; - provider2.users[testToken.identityId].connected = [user2.identityId]; - const request = new identitiesPB.ProviderSearch(); - const response = grpcClient.identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - }); - test('gets disconnected identities', async () => { - // This feature is not implemented yet - should throw error - const request = new identitiesPB.ProviderSearch(); - request.setDisconnected(true); - await testUtils.expectRemoteError( - grpcClient - .identitiesInfoConnectedGet( - request, - clientUtils.encodeAuthFromPassword(password), - ) - .next(), - identitiesErrors.ErrorProviderUnimplemented, - ); - }); -}); diff --git a/tests/client/service/identitiesInfoGet.test.ts b/tests/client/service/identitiesInfoGet.test.ts deleted file mode 100644 index 330f6c8dd..000000000 --- a/tests/client/service/identitiesInfoGet.test.ts +++ /dev/null @@ -1,459 +0,0 @@ -import type { IdentityId, ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type KeyRing from 'keys/KeyRing'; -import type Sigchain from 'sigchain/Sigchain'; -import type GestaltGraph from 'gestalts/GestaltGraph'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -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 TestProvider from '../../identities/TestProvider'; - -describe('identitiesInfoGet', () => { - const logger = new Logger('identitiesInfoGet test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const testToken = { - identityId: 'test_user' as IdentityId, - providerToken: { - 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, - gestaltGraph: {} as GestaltGraph, - keyRing: {} as KeyRing, - sigchain: {} as Sigchain, - logger, - }); - const clientService = { - identitiesInfoGet: identitiesInfoGet({ - authenticate, - identitiesManager, - logger, - }), - }; - 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.getPort(), - 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 an identity', async () => { - // Setup provider - const provider = new TestProvider(); - const user1 = { - providerId: provider.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider.users['user1'] = user1; - identitiesManager.registerProvider(provider); - await identitiesManager.putToken( - provider.id, - testToken.identityId, - testToken.providerToken, - ); - const request = new identitiesPB.ProviderSearch(); - request.setIdentityId(user1.identityId); - const response = grpcClient.identitiesInfoGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - }); - test('searches for a handle across providers', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider2.users['user1'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - await identitiesManager.putToken( - provider2.id, - testToken.identityId, - testToken.providerToken, - ); - const request = new identitiesPB.ProviderSearch(); - request.setIdentityId('user1'); - const response = grpcClient.identitiesInfoGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(2); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - expect(output[1]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - }); - test('searches for identities matching a search term', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'abc', - email: 'abc@test.com', - url: 'provider1.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user1' as IdentityId, - name: 'def', - email: 'def@test.com', - url: 'provider2.com/user1', - }; - provider2.users['user1'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - await identitiesManager.putToken( - provider2.id, - testToken.identityId, - testToken.providerToken, - ); - const request = new identitiesPB.ProviderSearch(); - request.setIdentityId('user1'); - request.setSearchTermList(['abc']); - const response = grpcClient.identitiesInfoGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - }); - test('gets no connected identities', async () => { - // Setup provider - const provider = new TestProvider(); - const user1 = { - providerId: provider.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider.users['user1'] = user1; - const user2 = { - providerId: provider.id, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider.users['user2'] = user2; - identitiesManager.registerProvider(provider); - await identitiesManager.putToken( - provider.id, - testToken.identityId, - testToken.providerToken, - ); - const request = new identitiesPB.ProviderSearch(); - request.setIdentityId('user1'); - request.setLimit('0'); - const response = grpcClient.identitiesInfoGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(0); - }); - test('gets one connected identity', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider2.users['user1'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - await identitiesManager.putToken( - provider2.id, - testToken.identityId, - testToken.providerToken, - ); - const request = new identitiesPB.ProviderSearch(); - request.setIdentityId('user1'); - request.setLimit('1'); - const response = grpcClient.identitiesInfoGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - }); - test('cannot get more identities than available', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider2.users['user1'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - await identitiesManager.putToken( - provider2.id, - testToken.identityId, - testToken.providerToken, - ); - const request = new identitiesPB.ProviderSearch(); - request.setIdentityId('user1'); - request.setLimit('3'); - const response = grpcClient.identitiesInfoGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(2); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - expect(output[1]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - }); - test('can only get from authenticated providers', async () => { - // Setup providers - const provider1 = new TestProvider('provider1' as ProviderId); - const provider2 = new TestProvider('provider2' as ProviderId); - const user1 = { - providerId: provider1.id, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - provider1.users['user1'] = user1; - const user2 = { - providerId: provider2.id, - identityId: 'user1' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - provider2.users['user1'] = user2; - identitiesManager.registerProvider(provider1); - identitiesManager.registerProvider(provider2); - await identitiesManager.putToken( - provider1.id, - testToken.identityId, - testToken.providerToken, - ); - const request = new identitiesPB.ProviderSearch(); - request.setIdentityId('user1'); - const response = grpcClient.identitiesInfoGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output).toHaveLength(1); - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - }); -}); diff --git a/tests/client/service/identitiesProvidersList.test.ts b/tests/client/service/identitiesProvidersList.test.ts deleted file mode 100644 index 426ee7b25..000000000 --- a/tests/client/service/identitiesProvidersList.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type { ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type KeyRing from 'keys/KeyRing'; -import type Sigchain from 'sigchain/Sigchain'; -import type GestaltGraph from 'gestalts/GestaltGraph'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -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 { 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 nodesUtils from '@/nodes/utils'; -import TestProvider from '../../identities/TestProvider'; - -describe('identitiesProvidersList', () => { - const logger = new Logger('identitiesProvidersList test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const id1 = 'provider1' as ProviderId; - const id2 = 'provider2' as ProviderId; - const providers = {}; - providers[id1] = new TestProvider(); - providers[id2] = new TestProvider(); - let mockedGetProviders: jest.SpyInstance; - beforeAll(async () => { - mockedGetProviders = jest - .spyOn(IdentitiesManager.prototype, 'getProviders') - .mockReturnValue(providers); - }); - afterAll(async () => { - mockedGetProviders.mockRestore(); - }); - 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, - gestaltGraph: {} as GestaltGraph, - keyRing: {} as KeyRing, - sigchain: {} as Sigchain, - logger, - }); - const clientService = { - identitiesProvidersList: identitiesProvidersList({ - authenticate, - identitiesManager, - logger, - }), - }; - 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.getPort(), - 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('lists providers', async () => { - const request = new utilsPB.EmptyMessage(); - const response = await grpcClient.identitiesProvidersList( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(identitiesPB.Provider); - expect(response.getProviderId()).toContain('provider1'); - expect(response.getProviderId()).toContain('provider2'); - }); -}); diff --git a/tests/client/service/identitiesTokenPutDeleteGet.test.ts b/tests/client/service/identitiesTokenPutDeleteGet.test.ts deleted file mode 100644 index 8211c0aad..000000000 --- a/tests/client/service/identitiesTokenPutDeleteGet.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import type { IdentityId, ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type KeyRing from '@/keys/KeyRing'; -import type Sigchain from '@/sigchain/Sigchain'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -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 { 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 nodesUtils from '@/nodes/utils'; -import TestProvider from '../../identities/TestProvider'; - -describe('identitiesTokenPutDeleteGet', () => { - const logger = new Logger('identitiesTokenPutDeleteGet 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, - providerToken: { - 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, - gestaltGraph: {} as GestaltGraph, - keyRing: {} as KeyRing, - sigchain: {} as Sigchain, - logger, - }); - identitiesManager.registerProvider(new TestProvider()); - const clientService = { - identitiesTokenPut: identitiesTokenPut({ - authenticate, - identitiesManager, - logger, - db, - }), - identitiesTokenGet: identitiesTokenGet({ - authenticate, - identitiesManager, - logger, - db, - }), - identitiesTokenDelete: identitiesTokenDelete({ - authenticate, - identitiesManager, - logger, - db, - }), - }; - 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.getPort(), - 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('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.providerToken.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.providerToken, - ); - // Delete token - const deleteResponse = await grpcClient.identitiesTokenDelete( - providerMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(deleteResponse).toBeInstanceOf(utilsPB.EmptyMessage); - // Check token was deleted - const getDeleteResponse = await grpcClient.identitiesTokenGet( - providerMessage, - clientUtils.encodeAuthFromPassword(password), - ); - 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 deleted file mode 100644 index 10c4fbfc0..000000000 --- a/tests/client/service/keysCertsChainGet.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type { CertificatePEM } from '../../../src/keys/types'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; -import CertManager from '@/keys/CertManager'; -import KeyRing from '@/keys/KeyRing'; -import TaskManager from '@/tasks/TaskManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import keysCertsChainGet from '@/client/service/keysCertsChainGet'; -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/index'; - -describe('keysCertsChainGet', () => { - const logger = new Logger('keysCertsChainGet test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const certs = ['cert1', 'cert2', 'cert3'] as Array; - let mockedGetRootCertChainPems: jest.SpyInstance; - beforeAll(async () => { - mockedGetRootCertChainPems = jest - .spyOn(CertManager.prototype, 'getCertPEMsChain') - .mockResolvedValue(certs); - }); - afterAll(async () => { - mockedGetRootCertChainPems.mockRestore(); - }); - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let taskManager: TaskManager; - let certManager: CertManager; - 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'); - const dbPath = path.join(dataDir, 'db'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - db = await DB.createDB({ - dbPath, - logger, - }); - taskManager = await TaskManager.createTaskManager({ db, logger }); - certManager = await CertManager.createCertManager({ - db, - keyRing, - taskManager, - logger, - }); - const clientService = { - keysCertsChainGet: keysCertsChainGet({ - authenticate, - certManager, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await certManager.stop(); - await taskManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('gets the root certchain', async () => { - const request = new utilsPB.EmptyMessage(); - const response = grpcClient.keysCertsChainGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const cert of response) { - expect(cert).toBeInstanceOf(keysPB.Certificate); - output.push(cert.getCert()); - } - expect(output).toEqual(certs); - }); -}); diff --git a/tests/client/service/keysCertsGet.test.ts b/tests/client/service/keysCertsGet.test.ts deleted file mode 100644 index c6f71a74f..000000000 --- a/tests/client/service/keysCertsGet.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type { CertificatePEM } from '@/keys/types'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; -import KeyRing from '@/keys/KeyRing'; -import TaskManager from '@/tasks/TaskManager'; -import CertManager from '@/keys/CertManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import keysCertsGet from '@/client/service/keysCertsGet'; -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/index'; - -describe('keysCertsGet', () => { - const logger = new Logger('keysCertsGet test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedGetRootCertPem: jest.SpyInstance; - beforeAll(async () => { - mockedGetRootCertPem = jest - .spyOn(CertManager.prototype, 'getCurrentCertPEM') - .mockResolvedValue('rootCertPem' as CertificatePEM); - }); - afterAll(async () => { - mockedGetRootCertPem.mockRestore(); - }); - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let taskManager: TaskManager; - let certManager: CertManager; - 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'); - const dbPath = path.join(dataDir, 'db'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - db = await DB.createDB({ - dbPath, - logger, - }); - taskManager = await TaskManager.createTaskManager({ db, logger }); - certManager = await CertManager.createCertManager({ - db, - keyRing, - taskManager, - logger, - }); - const clientService = { - keysCertsGet: keysCertsGet({ - authenticate, - certManager, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await certManager.stop(); - await taskManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('gets the root certificate', async () => { - const request = new utilsPB.EmptyMessage(); - const response = await grpcClient.keysCertsGet( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(keysPB.Certificate); - expect(response.getCert()).toBe('rootCertPem'); - }); -}); diff --git a/tests/client/service/keysEncryptDecrypt.test.ts b/tests/client/service/keysEncryptDecrypt.test.ts deleted file mode 100644 index 9b91456b3..000000000 --- a/tests/client/service/keysEncryptDecrypt.test.ts +++ /dev/null @@ -1,93 +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 KeyRing from '@/keys/KeyRing'; -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/index'; - -describe('keysEncryptDecrypt', () => { - const logger = new Logger('keysEncryptDecrypt test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const clientService = { - keysEncrypt: keysEncrypt({ - authenticate, - keyRing, - logger, - }), - keysDecrypt: keysDecrypt({ - authenticate, - keyRing, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('encrypts and decrypts data', async () => { - const plainText = Buffer.from('abc'); - const request = new keysPB.Crypto(); - request.setData(plainText.toString('binary')); - const publicKeyJWK = keysUtils.publicKeyToJWK(keyRing.keyPair.publicKey); - request.setPublicKeyJwk(JSON.stringify(publicKeyJWK)); - const encrypted = await grpcClient.keysEncrypt( - request, - clientUtils.encodeAuthFromPassword(password), - ); - 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/keysKeyPair.test.ts b/tests/client/service/keysKeyPair.test.ts deleted file mode 100644 index fcad3f87f..000000000 --- a/tests/client/service/keysKeyPair.test.ts +++ /dev/null @@ -1,93 +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 KeyRing from '@/keys/KeyRing'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; -import * as sessionsPB from '@/proto/js/polykey/v1/sessions/sessions_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as keysUtils from '@/keys/utils'; -import keysKeyPair from '../../../src/client/service/keysKeyPair'; - -describe('keysKeyPair', () => { - const logger = new Logger('keysKeyPair test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const clientService = { - keysKeyPair: keysKeyPair({ - authenticate, - keyRing, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('gets the keypair', async () => { - const request = new sessionsPB.Password(); - request.setPassword('password'); - const response = await grpcClient.keysKeyPair( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(keysPB.KeyPairJWK); - expect(JSON.parse(response.getPrivateKeyJwe())).toEqual({ - ciphertext: expect.any(String), - iv: expect.any(String), - protected: expect.any(String), - tag: expect.any(String), - }); - expect(JSON.parse(response.getPublicKeyJwk())).toEqual({ - alg: expect.any(String), - crv: expect.any(String), - ext: expect.any(Boolean), - key_ops: expect.any(Array), - kty: expect.any(String), - x: expect.any(String), - }); - }); -}); diff --git a/tests/client/service/keysKeyPairRenew.test.ts b/tests/client/service/keysKeyPairRenew.test.ts deleted file mode 100644 index 3b7cfebe2..000000000 --- a/tests/client/service/keysKeyPairRenew.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { Host, Port, TLSConfig } from '@/network/types'; -import type Proxy from '@/network/Proxy'; -import type Status from '@/status/Status'; -import type KeyRing from '@/keys/KeyRing'; -import type CertManager from '@/keys/CertManager'; -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 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 { NodeManager } from '@/nodes'; - -describe('keysKeyPairRenew', () => { - const logger = new Logger('keysKeyPairRenew test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedRefreshBuckets: jest.SpyInstance; - beforeAll(async () => { - mockedRefreshBuckets = jest.spyOn(NodeManager.prototype, 'resetBuckets'); - }); - afterAll(async () => { - mockedRefreshBuckets.mockRestore(); - }); - let dataDir: string; - let keyRing: KeyRing; - let certManager: CertManager; - let grpcServerClient: GRPCServer; - let proxy: Proxy; - - let status: Status; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - let pkAgent: PolykeyAgent; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const nodePath = path.join(dataDir, 'polykey'); - pkAgent = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, - }); - keyRing = pkAgent.keyRing; - certManager = pkAgent.certManager; - grpcServerClient = pkAgent.grpcServerClient; - proxy = pkAgent.proxy; - status = pkAgent.status; - const clientService = { - keysKeyPairRenew: keysKeyPairRenew({ - authenticate, - certManager, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await pkAgent.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('renews the root key pair', async () => { - const rootKeyPair1 = keyRing.keyPair; - const nodeId1 = keyRing.getNodeId(); - // @ts-ignore - get protected property - const fwdTLSConfig1 = proxy.tlsConfig; - // @ts-ignore - get protected property - const serverTLSConfig1 = grpcServerClient.tlsConfig; - const expectedTLSConfig1: TLSConfig = { - keyPrivatePem: keysUtils.privateKeyToPEM(rootKeyPair1.privateKey), - certChainPem: await certManager.getCertPEMsChainPEM(), - }; - const nodeIdStatus1 = (await status.readStatus())!.data.nodeId; - expect(mockedRefreshBuckets).toHaveBeenCalledTimes(0); - expect(fwdTLSConfig1).toEqual(expectedTLSConfig1); - expect(serverTLSConfig1).toEqual(expectedTLSConfig1); - expect(nodeId1.equals(nodeIdStatus1)).toBe(true); - // Run command - const request = new keysPB.Key(); - request.setName('somepassphrase'); - const response = await grpcClient.keysKeyPairRenew( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - const rootKeyPair2 = keyRing.keyPair; - const nodeId2 = keyRing.getNodeId(); - // @ts-ignore - get protected property - const fwdTLSConfig2 = proxy.tlsConfig; - // @ts-ignore - get protected property - const serverTLSConfig2 = grpcServerClient.tlsConfig; - const expectedTLSConfig2: TLSConfig = { - keyPrivatePem: keysUtils.privateKeyToPEM(rootKeyPair2.privateKey), - certChainPem: await certManager.getCertPEMsChainPEM(), - }; - const nodeIdStatus2 = (await status.readStatus())!.data.nodeId; - expect(mockedRefreshBuckets).toHaveBeenCalled(); - expect(fwdTLSConfig2).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); - }); -}); diff --git a/tests/client/service/keysKeyPairReset.test.ts b/tests/client/service/keysKeyPairReset.test.ts deleted file mode 100644 index c86f967b0..000000000 --- a/tests/client/service/keysKeyPairReset.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { Host, Port, TLSConfig } from '@/network/types'; -import type Proxy from '@/network/Proxy'; -import type Status from '@/status/Status'; -import type KeyRing from '@/keys/KeyRing'; -import type CertManager from '@/keys/CertManager'; -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 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 { NodeManager } from '@/nodes'; - -describe('keysKeyPairReset', () => { - const logger = new Logger('keysKeyPairReset test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedRefreshBuckets: jest.SpyInstance; - beforeAll(async () => { - mockedRefreshBuckets = jest.spyOn(NodeManager.prototype, 'resetBuckets'); - }); - afterAll(async () => { - mockedRefreshBuckets.mockRestore(); - }); - let dataDir: string; - let keyRing: KeyRing; - let certManager: CertManager; - let grpcServerClient: GRPCServer; - let proxy: Proxy; - - let status: Status; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - let pkAgent: PolykeyAgent; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const nodePath = path.join(dataDir, 'polykey'); - pkAgent = await PolykeyAgent.createPolykeyAgent({ - password, - nodePath, - logger, - keyRingConfig: { - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }, - }); - keyRing = pkAgent.keyRing; - certManager = pkAgent.certManager; - grpcServerClient = pkAgent.grpcServerClient; - proxy = pkAgent.proxy; - status = pkAgent.status; - const clientService = { - keysKeyPairReset: keysKeyPairReset({ - authenticate, - certManager, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await pkAgent.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('resets the root key pair', async () => { - const rootKeyPair1 = keyRing.keyPair; - const nodeId1 = keyRing.getNodeId(); - // @ts-ignore - get protected property - const fwdTLSConfig1 = proxy.tlsConfig; - // @ts-ignore - get protected property - const serverTLSConfig1 = grpcServerClient.tlsConfig; - const expectedTLSConfig1: TLSConfig = { - keyPrivatePem: keysUtils.privateKeyToPEM(rootKeyPair1.privateKey), - certChainPem: await certManager.getCertPEMsChainPEM(), - }; - const nodeIdStatus1 = (await status.readStatus())!.data.nodeId; - expect(mockedRefreshBuckets).not.toHaveBeenCalled(); - expect(fwdTLSConfig1).toEqual(expectedTLSConfig1); - expect(serverTLSConfig1).toEqual(expectedTLSConfig1); - expect(nodeId1.equals(nodeIdStatus1)).toBe(true); - // Run command - const request = new keysPB.Key(); - request.setName('somepassphrase'); - const response = await grpcClient.keysKeyPairReset( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - const rootKeyPair2 = keyRing.keyPair; - const nodeId2 = keyRing.getNodeId(); - // @ts-ignore - get protected property - const fwdTLSConfig2 = proxy.tlsConfig; - // @ts-ignore - get protected property - const serverTLSConfig2 = grpcServerClient.tlsConfig; - const expectedTLSConfig2: TLSConfig = { - keyPrivatePem: keysUtils.privateKeyToPEM(rootKeyPair2.privateKey), - certChainPem: await certManager.getCertPEMsChainPEM(), - }; - const nodeIdStatus2 = (await status.readStatus())!.data.nodeId; - expect(mockedRefreshBuckets).toHaveBeenCalled(); - expect(fwdTLSConfig2).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); - }); -}); diff --git a/tests/client/service/keysPasswordChange.test.ts b/tests/client/service/keysPasswordChange.test.ts deleted file mode 100644 index 31cda71e7..000000000 --- a/tests/client/service/keysPasswordChange.test.ts +++ /dev/null @@ -1,92 +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 KeyRing from '@/keys/KeyRing'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import keysPasswordChange from '@/client/service/keysPasswordChange'; -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/index'; - -describe('keysPasswordChange', () => { - const logger = new Logger('keysPasswordChange test', LogLevel.WARN, [ - new StreamHandler(), - ]); - let password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedChangePassword: jest.SpyInstance; - beforeAll(async () => { - mockedChangePassword = jest - .spyOn(KeyRing.prototype, 'changePassword') - .mockImplementation(async () => { - password = 'newpassword'; - }); - }); - afterAll(async () => { - mockedChangePassword.mockRestore(); - }); - let dataDir: string; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const clientService = { - keysPasswordChange: keysPasswordChange({ - authenticate, - keyRing, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('changes the password', async () => { - expect(password).toBe('helloworld'); - const request = new sessionsPB.Password(); - request.setPassword('newpassword'); - const response = await grpcClient.keysPasswordChange( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - expect(password).toBe('newpassword'); - }); -}); diff --git a/tests/client/service/keysPublicKey.test.ts b/tests/client/service/keysPublicKey.test.ts deleted file mode 100644 index 266b0affd..000000000 --- a/tests/client/service/keysPublicKey.test.ts +++ /dev/null @@ -1,86 +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 KeyRing from '@/keys/KeyRing'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as keysPB from '@/proto/js/polykey/v1/keys/keys_pb'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as keysUtils from '@/keys/utils'; -import keysPublicKey from '../../../src/client/service/keysPublicKey'; - -describe('keysPublicKey', () => { - const logger = new Logger('keysPublicKey test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const clientService = { - keysPublicKey: keysPublicKey({ - authenticate, - keyRing, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('gets the public key', async () => { - const request = new utilsPB.EmptyMessage(); - const response = await grpcClient.keysPublicKey( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(keysPB.KeyPairJWK); - expect(JSON.parse(response.getPublicKeyJwk())).toEqual({ - alg: expect.any(String), - crv: expect.any(String), - ext: expect.any(Boolean), - key_ops: expect.any(Array), - kty: expect.any(String), - x: expect.any(String), - }); - }); -}); diff --git a/tests/client/service/keysSignVerify.test.ts b/tests/client/service/keysSignVerify.test.ts deleted file mode 100644 index 1da726a99..000000000 --- a/tests/client/service/keysSignVerify.test.ts +++ /dev/null @@ -1,95 +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 KeyRing from '@/keys/KeyRing'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import keysSign from '@/client/service/keysSign'; -import keysVerify from '@/client/service/keysVerify'; -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/index'; -import { publicKeyToJWK } from '@/keys/utils/index'; - -describe('keysSignVerify', () => { - const logger = new Logger('keysSignVerify test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const clientService = { - keysSign: keysSign({ - authenticate, - keyRing, - logger, - }), - keysVerify: keysVerify({ - authenticate, - keyRing, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('signs and verifies with root keypair', async () => { - const data = Buffer.from('abc'); - const request = new keysPB.Crypto(); - request.setData(data.toString('binary')); - const signed = await grpcClient.keysSign( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(signed).toBeInstanceOf(keysPB.Crypto); - const publicKeyJWK = publicKeyToJWK(keyRing.keyPair.publicKey); - signed.setPublicKeyJwk(JSON.stringify(publicKeyJWK)); - const response = await grpcClient.keysVerify( - signed, - clientUtils.encodeAuthFromPassword(password), - ); - 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 deleted file mode 100644 index 996636c82..000000000 --- a/tests/client/service/nodesAdd.test.ts +++ /dev/null @@ -1,211 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; -import TaskManager from '@/tasks/TaskManager'; -import KeyRing from '@/keys/KeyRing'; -import NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import NodeGraph from '@/nodes/NodeGraph'; -import NodeManager from '@/nodes/NodeManager'; -import Sigchain from '@/sigchain/Sigchain'; -import Proxy from '@/network/Proxy'; -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 * as nodesUtils from '@/nodes/utils'; -import * as clientUtils from '@/client/utils/utils'; -import * as validationErrors from '@/validation/errors'; -import * as keysUtils from '@/keys/utils/index'; -import * as testsUtils from '../../utils'; - -describe('nodesAdd', () => { - const logger = new Logger('nodesAdd test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const authToken = 'abc123'; - let dataDir: string; - let nodeGraph: NodeGraph; - let taskManager: TaskManager; - let nodeConnectionManager: NodeConnectionManager; - let nodeManager: NodeManager; - let sigchain: Sigchain; - let proxy: Proxy; - - let db: DB; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - proxy = new Proxy({ - authToken, - logger, - }); - await proxy.start({ - tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), - serverHost: '127.0.0.1' as Host, - serverPort: 0 as Port, - }); - sigchain = await Sigchain.createSigchain({ - db, - keyRing, - logger, - }); - nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing, - logger: logger.getChild('NodeGraph'), - }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); - nodeConnectionManager = new NodeConnectionManager({ - keyRing, - nodeGraph, - proxy, - taskManager, - connConnectTime: 2000, - connTimeoutTime: 2000, - logger: logger.getChild('NodeConnectionManager'), - }); - nodeManager = new NodeManager({ - db, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - gestaltGraph: {} as GestaltGraph, - logger, - }); - await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager }); - await taskManager.startProcessing(); - const clientService = { - nodesAdd: nodesAdd({ - authenticate, - nodeManager, - logger, - db, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await taskManager.stopProcessing(); - await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); - await nodeGraph.stop(); - await nodeConnectionManager.stop(); - await nodeManager.stop(); - await sigchain.stop(); - await proxy.stop(); - await db.stop(); - await keyRing.stop(); - await taskManager.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('adds a node', async () => { - const addressMessage = new nodesPB.Address(); - addressMessage.setHost('127.0.0.1'); - addressMessage.setPort(11111); - const request = new nodesPB.NodeAdd(); - request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); - request.setAddress(addressMessage); - const response = await grpcClient.nodesAdd( - request, - clientUtils.encodeAuthFromPassword(password), - ); - request.setPing(false); - request.setForce(false); - expect(response).toBeInstanceOf(utilsPB.EmptyMessage); - const result = await nodeGraph.getNode( - nodesUtils.decodeNodeId( - 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', - )!, - ); - expect(result).toBeDefined(); - expect(result!.address).toEqual({ host: '127.0.0.1', port: 11111 }); - }); - test('cannot add invalid node', async () => { - // Invalid host - const addressMessage = new nodesPB.Address(); - addressMessage.setHost(''); - addressMessage.setPort(11111); - const request = new nodesPB.NodeAdd(); - request.setPing(false); - request.setForce(false); - request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); - request.setAddress(addressMessage); - await testsUtils.expectRemoteError( - grpcClient.nodesAdd( - request, - clientUtils.encodeAuthFromPassword(password), - ), - validationErrors.ErrorValidation, - ); - // Invalid port - addressMessage.setHost('127.0.0.1'); - addressMessage.setPort(111111); - await testsUtils.expectRemoteError( - grpcClient.nodesAdd( - request, - clientUtils.encodeAuthFromPassword(password), - ), - validationErrors.ErrorValidation, - ); - // Invalid nodeid - addressMessage.setPort(11111); - request.setNodeId('nodeId'); - await testsUtils.expectRemoteError( - grpcClient.nodesAdd( - request, - clientUtils.encodeAuthFromPassword(password), - ), - validationErrors.ErrorValidation, - ); - }); -}); diff --git a/tests/client/service/nodesFind.test.ts b/tests/client/service/nodesFind.test.ts deleted file mode 100644 index bd141d7e3..000000000 --- a/tests/client/service/nodesFind.test.ts +++ /dev/null @@ -1,172 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type NodeManager from '@/nodes/NodeManager'; -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 TaskManager from '@/tasks/TaskManager'; -import KeyRing from '@/keys/KeyRing'; -import NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import NodeGraph from '@/nodes/NodeGraph'; -import Sigchain from '@/sigchain/Sigchain'; -import Proxy from '@/network/Proxy'; -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 validationErrors from '@/validation/errors'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; -import * as testsUtils from '../../utils/index'; - -describe('nodesFind', () => { - const logger = new Logger('nodesFind test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedFindNode: jest.SpyInstance; - beforeAll(async () => { - mockedFindNode = jest - .spyOn(NodeConnectionManager.prototype, 'findNode') - .mockResolvedValue({ - host: '127.0.0.1' as Host, - port: 11111 as Port, - }); - }); - afterAll(async () => { - mockedFindNode.mockRestore(); - }); - const authToken = 'abc123'; - let dataDir: string; - let nodeGraph: NodeGraph; - let taskManager: TaskManager; - let nodeConnectionManager: NodeConnectionManager; - let sigchain: Sigchain; - let proxy: Proxy; - - let db: DB; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - proxy = new Proxy({ - authToken, - logger, - }); - await proxy.start({ - tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), - serverHost: '127.0.0.1' as Host, - serverPort: 0 as Port, - }); - sigchain = await Sigchain.createSigchain({ - db, - keyRing, - logger, - }); - nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing, - logger: logger.getChild('NodeGraph'), - }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); - nodeConnectionManager = new NodeConnectionManager({ - keyRing, - nodeGraph, - proxy, - taskManager, - connConnectTime: 2000, - connTimeoutTime: 2000, - logger: logger.getChild('NodeConnectionManager'), - }); - await nodeConnectionManager.start({ nodeManager: {} as NodeManager }); - await taskManager.startProcessing(); - const clientService = { - nodesFind: nodesFind({ - authenticate, - nodeConnectionManager, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await taskManager.stopProcessing(); - await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); - await sigchain.stop(); - await nodeGraph.stop(); - await nodeConnectionManager.stop(); - await proxy.stop(); - await db.stop(); - await keyRing.stop(); - await taskManager.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('finds a node', async () => { - const request = new nodesPB.Node(); - request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); - const response = await grpcClient.nodesFind( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(nodesPB.NodeAddress); - expect(response.getNodeId()).toBe( - 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', - ); - expect(response.getAddress()!.getHost()).toBe('127.0.0.1'); - expect(response.getAddress()!.getPort()).toBe(11111); - }); - test('cannot find an invalid node', async () => { - const request = new nodesPB.Node(); - request.setNodeId('nodeId'); - await testUtils.expectRemoteError( - grpcClient.nodesFind( - request, - clientUtils.encodeAuthFromPassword(password), - ), - validationErrors.ErrorValidation, - ); - }); -}); diff --git a/tests/client/service/nodesPing.test.ts b/tests/client/service/nodesPing.test.ts deleted file mode 100644 index 9accd6e77..000000000 --- a/tests/client/service/nodesPing.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { DB } from '@matrixai/db'; -import { Metadata } from '@grpc/grpc-js'; -import TaskManager from '@/tasks/TaskManager'; -import KeyRing from '@/keys/KeyRing'; -import NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import NodeGraph from '@/nodes/NodeGraph'; -import NodeManager from '@/nodes/NodeManager'; -import Sigchain from '@/sigchain/Sigchain'; -import Proxy from '@/network/Proxy'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import nodesPing from '@/client/service/nodesPing'; -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 validationErrors from '@/validation/errors'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; -import * as testsUtils from '../../utils/index'; - -describe('nodesPing', () => { - const logger = new Logger('nodesPing test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedPingNode: jest.SpyInstance; - beforeAll(async () => { - mockedPingNode = jest - .spyOn(NodeManager.prototype, 'pingNode') - .mockResolvedValueOnce(false) - .mockResolvedValue(true); - }); - afterAll(async () => { - mockedPingNode.mockRestore(); - }); - const authToken = 'abc123'; - let dataDir: string; - let nodeGraph: NodeGraph; - let taskManager: TaskManager; - let nodeConnectionManager: NodeConnectionManager; - let nodeManager: NodeManager; - let sigchain: Sigchain; - let proxy: Proxy; - - let db: DB; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - proxy = new Proxy({ - authToken, - logger, - }); - await proxy.start({ - tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), - serverHost: '127.0.0.1' as Host, - serverPort: 0 as Port, - }); - sigchain = await Sigchain.createSigchain({ - db, - keyRing, - logger, - }); - nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing, - logger: logger.getChild('NodeGraph'), - }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); - nodeConnectionManager = new NodeConnectionManager({ - keyRing, - nodeGraph, - proxy, - taskManager, - connConnectTime: 2000, - connTimeoutTime: 2000, - logger: logger.getChild('NodeConnectionManager'), - }); - nodeManager = new NodeManager({ - db, - keyRing, - nodeConnectionManager, - nodeGraph, - sigchain, - taskManager, - gestaltGraph: {} as GestaltGraph, - logger, - }); - await nodeConnectionManager.start({ nodeManager }); - await taskManager.startProcessing(); - const clientService = { - nodesPing: nodesPing({ - authenticate, - nodeManager, - logger, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await taskManager.stopProcessing(); - await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); - await sigchain.stop(); - await nodeGraph.stop(); - await nodeConnectionManager.stop(); - await proxy.stop(); - await db.stop(); - await keyRing.stop(); - await taskManager.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('pings a node (offline)', async () => { - const request = new nodesPB.Node(); - request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); - const response = await grpcClient.nodesPing( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.StatusMessage); - expect(response.getSuccess()).toBeFalsy(); - }); - test('pings a node (online)', async () => { - const request = new nodesPB.Node(); - request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); - const response = await grpcClient.nodesPing( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.StatusMessage); - expect(response.getSuccess()).toBeTruthy(); - }); - test('cannot ping an invalid node', async () => { - const request = new nodesPB.Node(); - request.setNodeId('nodeId'); - await testUtils.expectRemoteError( - grpcClient.nodesPing( - request, - clientUtils.encodeAuthFromPassword(password), - ), - validationErrors.ErrorValidation, - ); - }); -}); diff --git a/tests/client/service/notificationsRead.test.ts b/tests/client/service/notificationsRead.test.ts deleted file mode 100644 index 18e60f5e9..000000000 --- a/tests/client/service/notificationsRead.test.ts +++ /dev/null @@ -1,414 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type { VaultIdEncoded, VaultName } from '@/vaults/types'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Metadata } from '@grpc/grpc-js'; -import { DB } from '@matrixai/db'; -import TaskManager from '@/tasks/TaskManager'; -import KeyRing from '@/keys/KeyRing'; -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 Proxy from '@/network/Proxy'; -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 nodesUtils from '@/nodes/utils'; -import * as clientUtils from '@/client/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testNodesUtils from '../../nodes/utils'; -import * as testsUtils from '../../utils/index'; - -describe('notificationsRead', () => { - const logger = new Logger('notificationsRead test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const nodeIdSender = testNodesUtils.generateRandomNodeId(); - const nodeIdSenderEncoded = nodesUtils.encodeNodeId(nodeIdSender); - const nodeIdReceiverEncoded = 'test'; - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedReadNotifications: jest.SpyInstance; - beforeAll(async () => { - mockedReadNotifications = jest - .spyOn(NotificationsManager.prototype, 'readNotifications') - .mockResolvedValueOnce([ - { - typ: 'notification', - data: { - type: 'General', - message: 'test', - }, - iss: nodeIdSenderEncoded, - sub: nodeIdReceiverEncoded, - isRead: true, - }, - ]) - .mockResolvedValueOnce([ - { - typ: 'notification', - data: { - type: 'General', - message: 'test1', - }, - iss: nodeIdSenderEncoded, - sub: nodeIdReceiverEncoded, - isRead: true, - }, - { - typ: 'notification', - data: { - type: 'General', - message: 'test2', - }, - iss: nodeIdSenderEncoded, - sub: nodeIdReceiverEncoded, - isRead: true, - }, - ]) - .mockResolvedValueOnce([ - { - typ: 'notification', - data: { - type: 'General', - message: 'test2', - }, - iss: nodeIdSenderEncoded, - sub: nodeIdReceiverEncoded, - isRead: true, - }, - { - typ: 'notification', - data: { - type: 'General', - message: 'test1', - }, - iss: nodeIdSenderEncoded, - sub: nodeIdReceiverEncoded, - isRead: true, - }, - ]) - .mockResolvedValueOnce([ - { - typ: 'notification', - data: { - type: 'GestaltInvite', - }, - iss: nodeIdSenderEncoded, - sub: nodeIdReceiverEncoded, - isRead: true, - }, - ]) - .mockResolvedValueOnce([ - { - typ: 'notification', - data: { - type: 'VaultShare', - vaultId: 'vault' as VaultIdEncoded, - vaultName: 'vault' as VaultName, - actions: { - clone: null, - pull: null, - }, - }, - iss: nodeIdSenderEncoded, - sub: nodeIdReceiverEncoded, - isRead: true, - }, - ]) - .mockResolvedValueOnce([]); - }); - afterAll(async () => { - mockedReadNotifications.mockRestore(); - }); - const authToken = 'abc123'; - let dataDir: string; - let nodeGraph: NodeGraph; - let taskManager: TaskManager; - let nodeConnectionManager: NodeConnectionManager; - let nodeManager: NodeManager; - let notificationsManager: NotificationsManager; - let acl: ACL; - let sigchain: Sigchain; - let proxy: Proxy; - - let db: DB; - let keyRing: KeyRing; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - acl = await ACL.createACL({ - db, - logger, - }); - proxy = new Proxy({ - authToken, - logger, - }); - await proxy.start({ - tlsConfig: await testsUtils.createTLSConfig(keyRing.keyPair), - serverHost: '127.0.0.1' as Host, - serverPort: 0 as Port, - }); - sigchain = await Sigchain.createSigchain({ - db, - keyRing, - logger, - }); - nodeGraph = await NodeGraph.createNodeGraph({ - db, - keyRing, - logger: logger.getChild('NodeGraph'), - }); - taskManager = await TaskManager.createTaskManager({ - db, - logger, - lazy: true, - }); - nodeConnectionManager = new NodeConnectionManager({ - keyRing, - nodeGraph, - proxy, - taskManager, - connConnectTime: 2000, - connTimeoutTime: 2000, - logger: logger.getChild('NodeConnectionManager'), - }); - nodeManager = new NodeManager({ - db, - keyRing, - nodeConnectionManager, - nodeGraph, - gestaltGraph: {} as GestaltGraph, - sigchain, - taskManager, - logger, - }); - await nodeManager.start(); - await nodeConnectionManager.start({ nodeManager }); - await taskManager.start(); - notificationsManager = - await NotificationsManager.createNotificationsManager({ - acl, - db, - nodeConnectionManager, - nodeManager, - keyRing, - logger, - }); - const clientService = { - notificationsRead: notificationsRead({ - authenticate, - notificationsManager, - logger, - db, - }), - }; - 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: keyRing.getNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await taskManager.stopProcessing(); - await taskManager.stopTasks(); - await grpcClient.destroy(); - await grpcServer.stop(); - await notificationsManager.stop(); - await sigchain.stop(); - await nodeGraph.stop(); - await nodeConnectionManager.stop(); - await nodeManager.stop(); - await proxy.stop(); - await acl.stop(); - await db.stop(); - await keyRing.stop(); - await taskManager.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('reads a single notification', async () => { - const request = new notificationsPB.Read(); - request.setUnread(false); - request.setNumber('1'); - request.setOrder('newest'); - const response = await grpcClient.notificationsRead( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(notificationsPB.List); - const output = response.getNotificationList(); - expect(output).toHaveLength(1); - const notification = JSON.parse(output[0].getContent()); - expect(notification.data.type).toBe('General'); - expect(notification.data.message).toBe('test'); - expect(notification.iss).toBe(nodeIdSenderEncoded); - expect(notification.sub).toBe(nodeIdReceiverEncoded); - expect(notification.isRead).toBeTruthy(); - // Check request was parsed correctly - expect(mockedReadNotifications.mock.calls[0][0].unread).toBeFalsy(); - expect(mockedReadNotifications.mock.calls[0][0].number).toBe(1); - expect(mockedReadNotifications.mock.calls[0][0].order).toBe('newest'); - }); - test('reads unread notifications', async () => { - const request = new notificationsPB.Read(); - request.setUnread(true); - request.setNumber('all'); - request.setOrder('newest'); - const response = await grpcClient.notificationsRead( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(notificationsPB.List); - const output = response.getNotificationList(); - expect(output).toHaveLength(2); - const notification1 = JSON.parse(output[0].getContent()); - const notification2 = JSON.parse(output[1].getContent()); - expect(notification1.data.type).toBe('General'); - expect(notification1.data.message).toBe('test1'); - expect(notification1.iss).toBe(nodeIdSenderEncoded); - expect(notification1.sub).toBe(nodeIdReceiverEncoded); - expect(notification1.isRead).toBeTruthy(); - expect(notification2.data.type).toBe('General'); - expect(notification2.data.message).toBe('test2'); - expect(notification2.iss).toBe(nodeIdSenderEncoded); - expect(notification2.sub).toBe(nodeIdReceiverEncoded); - expect(notification2.isRead).toBeTruthy(); - // Check request was parsed correctly - expect(mockedReadNotifications.mock.calls[1][0].unread).toBeTruthy(); - expect(mockedReadNotifications.mock.calls[1][0].number).toBe('all'); - expect(mockedReadNotifications.mock.calls[1][0].order).toBe('newest'); - }); - test('reads notifications in reverse order', async () => { - const request = new notificationsPB.Read(); - request.setUnread(false); - request.setNumber('all'); - request.setOrder('oldest'); - const response = await grpcClient.notificationsRead( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(notificationsPB.List); - const output = response.getNotificationList(); - expect(output).toHaveLength(2); - const notification1 = JSON.parse(output[0].getContent()); - const notification2 = JSON.parse(output[1].getContent()); - expect(notification1.data.type).toBe('General'); - expect(notification1.data.message).toBe('test2'); - expect(notification1.iss).toBe(nodeIdSenderEncoded); - expect(notification1.sub).toBe(nodeIdReceiverEncoded); - expect(notification1.isRead).toBeTruthy(); - expect(notification2.data.type).toBe('General'); - expect(notification2.data.message).toBe('test1'); - expect(notification2.iss).toBe(nodeIdSenderEncoded); - expect(notification2.sub).toBe(nodeIdReceiverEncoded); - expect(notification2.isRead).toBeTruthy(); - // Check request was parsed correctly - expect(mockedReadNotifications.mock.calls[2][0].unread).toBeFalsy(); - expect(mockedReadNotifications.mock.calls[2][0].number).toBe('all'); - expect(mockedReadNotifications.mock.calls[2][0].order).toBe('oldest'); - }); - test('reads gestalt invite notifications', async () => { - const request = new notificationsPB.Read(); - request.setUnread(false); - request.setNumber('all'); - request.setOrder('newest'); - const response = await grpcClient.notificationsRead( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(notificationsPB.List); - const output = response.getNotificationList(); - expect(output).toHaveLength(1); - const notification = JSON.parse(output[0].getContent()); - expect(notification.data.type).toBe('GestaltInvite'); - expect(notification.iss).toBe(nodeIdSenderEncoded); - expect(notification.sub).toBe(nodeIdReceiverEncoded); - expect(notification.isRead).toBeTruthy(); - // Check request was parsed correctly - expect(mockedReadNotifications.mock.calls[3][0].unread).toBeFalsy(); - expect(mockedReadNotifications.mock.calls[3][0].number).toBe('all'); - expect(mockedReadNotifications.mock.calls[3][0].order).toBe('newest'); - }); - test('reads vault share notifications', async () => { - const request = new notificationsPB.Read(); - request.setUnread(false); - request.setNumber('all'); - request.setOrder('newest'); - const response = await grpcClient.notificationsRead( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(notificationsPB.List); - const output = response.getNotificationList(); - expect(output).toHaveLength(1); - const notification = JSON.parse(output[0].getContent()); - expect(notification.data.type).toBe('VaultShare'); - expect(notification.data.vaultId).toBe('vault'); - expect(notification.data.vaultName).toBe('vault'); - expect(notification.data.actions).toStrictEqual({ - clone: null, - pull: null, - }); - expect(notification.iss).toBe(nodeIdSenderEncoded); - expect(notification.sub).toBe(nodeIdReceiverEncoded); - expect(notification.isRead).toBeTruthy(); - // Check request was parsed correctly - expect(mockedReadNotifications.mock.calls[4][0].unread).toBeFalsy(); - expect(mockedReadNotifications.mock.calls[4][0].number).toBe('all'); - expect(mockedReadNotifications.mock.calls[4][0].order).toBe('newest'); - }); - test('reads no notifications', async () => { - const request = new notificationsPB.Read(); - request.setUnread(false); - request.setNumber('all'); - request.setOrder('newest'); - const response = await grpcClient.notificationsRead( - request, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(notificationsPB.List); - const output = response.getNotificationList(); - expect(output).toHaveLength(0); - // Check request was parsed correctly - expect(mockedReadNotifications.mock.calls[5][0].unread).toBeFalsy(); - expect(mockedReadNotifications.mock.calls[5][0].number).toBe('all'); - expect(mockedReadNotifications.mock.calls[5][0].order).toBe('newest'); - }); -}); diff --git a/tests/client/service/vaultsClone.test.ts b/tests/client/service/vaultsClone.test.ts deleted file mode 100644 index 17cc0d3db..000000000 --- a/tests/client/service/vaultsClone.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type KeyRing from '@/keys/KeyRing'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsClone from '@/client/service/vaultsClone'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as testUtils from '../../utils'; - -describe('vaultsClone', () => { - const logger = new Logger('vaultsClone test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let db: DB; - let vaultManager: VaultManager; - 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, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing: {} as KeyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsClone: vaultsClone({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test.todo('clones a vault'); -}); diff --git a/tests/client/service/vaultsCreateDeleteList.test.ts b/tests/client/service/vaultsCreateDeleteList.test.ts deleted file mode 100644 index 4b456f163..000000000 --- a/tests/client/service/vaultsCreateDeleteList.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 KeyRing from '@/keys/KeyRing'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsCreate from '@/client/service/vaultsCreate'; -import vaultsDelete from '@/client/service/vaultsDelete'; -import vaultsList from '@/client/service/vaultsList'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsCreateDeleteList', () => { - const logger = new Logger('vaultsCreateDeleteList test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsCreate: vaultsCreate({ - authenticate, - vaultManager, - db, - logger, - }), - vaultsDelete: vaultsDelete({ - authenticate, - vaultManager, - db, - logger, - }), - vaultsList: vaultsList({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('creates, lists, and deletes vaults', async () => { - // Create vault - const createRequest = new vaultsPB.Vault(); - createRequest.setNameOrId('test-vault'); - const createResponse = await grpcClient.vaultsCreate( - createRequest, - clientUtils.encodeAuthFromPassword(password), - ); - expect(createResponse).toBeInstanceOf(vaultsPB.Vault); - const vaultId = createResponse.getNameOrId(); - // List vault - const emptyMessage = new utilsPB.EmptyMessage(); - const listResponse1 = grpcClient.vaultsList( - emptyMessage, - clientUtils.encodeAuthFromPassword(password), - ); - const vaults1: Array<{ name: string; id: string }> = []; - for await (const vault of listResponse1) { - expect(vault).toBeInstanceOf(vaultsPB.List); - vaults1.push({ name: vault.getVaultName(), id: vault.getVaultId() }); - } - expect(vaults1).toHaveLength(1); - expect(vaults1[0].name).toBe('test-vault'); - expect(vaults1[0].id).toBe(vaultId); - // Delete vault - const deleteRequest = createRequest; - const deleteResponse = await grpcClient.vaultsDelete( - deleteRequest, - clientUtils.encodeAuthFromPassword(password), - ); - expect(deleteResponse).toBeInstanceOf(utilsPB.StatusMessage); - expect(deleteResponse.getSuccess()).toBeTruthy(); - // Check vault was deleted - const listResponse2 = grpcClient.vaultsList( - emptyMessage, - clientUtils.encodeAuthFromPassword(password), - ); - const vaults2: Array<{ name: string; id: string }> = []; - for await (const vault of listResponse2) { - expect(vault).toBeInstanceOf(vaultsPB.List); - vaults2.push({ name: vault.getVaultName(), id: vault.getVaultId() }); - } - expect(vaults2).toHaveLength(0); - }); -}); diff --git a/tests/client/service/vaultsLog.test.ts b/tests/client/service/vaultsLog.test.ts deleted file mode 100644 index bd5254179..000000000 --- a/tests/client/service/vaultsLog.test.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type { VaultId } from '@/vaults/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 KeyRing from '@/keys/KeyRing'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsLog from '@/client/service/vaultsLog'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsLog', () => { - const logger = new Logger('vaultsLog test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const vaultName = 'test-vault'; - const secret1 = { name: 'secret1', content: 'Secret-1-content' }; - const secret2 = { name: 'secret2', content: 'Secret-2-content' }; - let dataDir: string; - let vaultId: VaultId; - let commit1Oid: string; - let commit2Oid: string; - let commit3Oid: string; - let keyRing: KeyRing; - let db: DB; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - vaultId = await vaultManager.createVault(vaultName); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secret1.name, secret1.content); - }); - commit1Oid = (await vault.log(undefined, 0))[0].commitId; - await vault.writeF(async (efs) => { - await efs.writeFile(secret2.name, secret2.content); - }); - commit2Oid = (await vault.log(undefined, 0))[0].commitId; - await vault.writeF(async (efs) => { - await efs.unlink(secret2.name); - }); - commit3Oid = (await vault.log(undefined, 0))[0].commitId; - }); - const clientService = { - vaultsLog: vaultsLog({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }, globalThis.defaultTimeout * 2); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('should get the full log', async () => { - const vaultsLogMessage = new vaultsPB.Log(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); - vaultsLogMessage.setVault(vaultMessage); - const logStream = grpcClient.vaultsLog( - vaultsLogMessage, - clientUtils.encodeAuthFromPassword(password), - ); - const logMessages: vaultsPB.LogEntry[] = []; - for await (const log of logStream) { - expect(log).toBeInstanceOf(vaultsPB.LogEntry); - logMessages.push(log); - } - // Checking commits exist in order. - expect(logMessages[2].getOid()).toEqual(commit1Oid); - expect(logMessages[1].getOid()).toEqual(commit2Oid); - expect(logMessages[0].getOid()).toEqual(commit3Oid); - }); - test('should get a part of the log', async () => { - const vaultsLogMessage = new vaultsPB.Log(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); - vaultsLogMessage.setVault(vaultMessage); - vaultsLogMessage.setLogDepth(2); - const logStream = grpcClient.vaultsLog( - vaultsLogMessage, - clientUtils.encodeAuthFromPassword(password), - ); - const logMessages: vaultsPB.LogEntry[] = []; - for await (const log of logStream) { - expect(log).toBeInstanceOf(vaultsPB.LogEntry); - logMessages.push(log); - } - // Checking commits exist in order. - expect(logMessages[1].getOid()).toEqual(commit2Oid); - expect(logMessages[0].getOid()).toEqual(commit3Oid); - }); - test('should get a specific commit', async () => { - const vaultsLogMessage = new vaultsPB.Log(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); - vaultsLogMessage.setVault(vaultMessage); - vaultsLogMessage.setCommitId(commit2Oid); - const logStream = grpcClient.vaultsLog( - vaultsLogMessage, - clientUtils.encodeAuthFromPassword(password), - ); - const logMessages: vaultsPB.LogEntry[] = []; - for await (const log of logStream) { - expect(log).toBeInstanceOf(vaultsPB.LogEntry); - logMessages.push(log); - } - // Checking commits exist in order. - expect(logMessages[0].getOid()).toEqual(commit2Oid); - }); -}); diff --git a/tests/client/service/vaultsPermissionSetUnsetGet.test.ts b/tests/client/service/vaultsPermissionSetUnsetGet.test.ts deleted file mode 100644 index 606795bfe..000000000 --- a/tests/client/service/vaultsPermissionSetUnsetGet.test.ts +++ /dev/null @@ -1,217 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type NodeManager from '@/nodes/NodeManager'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -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 ACL from '@/acl/ACL'; -import GestaltGraph from '@/gestalts/GestaltGraph'; -import KeyRing from '@/keys/KeyRing'; -import NotificationsManager from '@/notifications/NotificationsManager'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsPermissionGet from '@/client/service/vaultsPermissionGet'; -import vaultsPermissionSet from '@/client/service/vaultsPermissionSet'; -import vaultsPermissionUnset from '@/client/service/vaultsPermissionUnset'; -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 vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as nodesUtils from '@/nodes/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsPermissionSetUnsetGet', () => { - const logger = new Logger('vaultsPermissionSetUnsetGet test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let mockedSendNotification: jest.SpyInstance; - beforeAll(async () => { - mockedSendNotification = jest - .spyOn(NotificationsManager.prototype, 'sendNotification') - .mockImplementation(); - }); - afterAll(async () => { - mockedSendNotification.mockRestore(); - }); - const nodeId = testUtils.generateRandomNodeId(); - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let acl: ACL; - let gestaltGraph: GestaltGraph; - let notificationsManager: NotificationsManager; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - 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({ - nodeId: nodeId, - }); - notificationsManager = - await NotificationsManager.createNotificationsManager({ - acl, - db, - nodeConnectionManager: {} as NodeConnectionManager, - nodeManager: {} as NodeManager, - keyRing, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph, - notificationsManager: notificationsManager, - logger, - }); - const clientService = { - vaultsPermissionSet: vaultsPermissionSet({ - authenticate, - vaultManager, - gestaltGraph, - acl, - notificationsManager, - db, - logger, - }), - vaultsPermissionUnset: vaultsPermissionUnset({ - authenticate, - vaultManager, - gestaltGraph, - acl, - db, - logger, - }), - vaultsPermissionGet: vaultsPermissionGet({ - authenticate, - vaultManager, - acl, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await notificationsManager.stop(); - await gestaltGraph.stop(); - await acl.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('sets, gets, and unsets vault permissions', async () => { - const vaultName = 'test-vault'; - await vaultManager.createVault(vaultName); - // Set permissions - const vault = new vaultsPB.Vault(); - vault.setNameOrId(vaultName); - const node = new nodesPB.Node(); - node.setNodeId(nodesUtils.encodeNodeId(nodeId)); - const permissions = new vaultsPB.Permissions(); - permissions.setVault(vault); - permissions.setNode(node); - permissions.setVaultPermissionsList(['clone', 'pull']); - const setResponse = await grpcClient.vaultsPermissionSet( - permissions, - clientUtils.encodeAuthFromPassword(password), - ); - expect(setResponse).toBeInstanceOf(utilsPB.StatusMessage); - expect(setResponse.getSuccess()).toBeTruthy(); - // Get permissions - const getResponse1 = grpcClient.vaultsPermissionGet( - vault, - clientUtils.encodeAuthFromPassword(password), - ); - const list1: Record[] = []; - for await (const permission of getResponse1) { - expect(permission).toBeInstanceOf(vaultsPB.Permissions); - const permissionsList = permission.getVaultPermissionsList(); - expect(permissionsList).toContain('pull'); - expect(permissionsList).toContain('clone'); - const node = permission.getNode(); - const receivedNodeId = node?.getNodeId(); - expect(receivedNodeId).toEqual(nodesUtils.encodeNodeId(nodeId)); - list1.push(permission.toObject()); - } - expect(list1).toHaveLength(1); - // Unset permissions - const deleteResponse = await grpcClient.vaultsPermissionUnset( - permissions, - clientUtils.encodeAuthFromPassword(password), - ); - expect(deleteResponse).toBeInstanceOf(utilsPB.StatusMessage); - expect(deleteResponse.getSuccess()).toBeTruthy(); - // Check permissions were unset - const getResponse2 = grpcClient.vaultsPermissionGet( - vault, - clientUtils.encodeAuthFromPassword(password), - ); - const list2: Record[] = []; - for await (const permission of getResponse2) { - expect(permission).toBeInstanceOf(vaultsPB.Permissions); - const permissionsList = permission.getVaultPermissionsList(); - expect(permissionsList).toEqual([]); - const node = permission.getNode(); - const receivedNodeId = node?.getNodeId(); - expect(receivedNodeId).toEqual(nodesUtils.encodeNodeId(nodeId)); - list2.push(permission.toObject()); - } - expect(list2).toHaveLength(1); - }); -}); diff --git a/tests/client/service/vaultsPull.test.ts b/tests/client/service/vaultsPull.test.ts deleted file mode 100644 index 52e801741..000000000 --- a/tests/client/service/vaultsPull.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type KeyRing from '@/keys/KeyRing'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsPull from '@/client/service/vaultsPull'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as testUtils from '../../utils'; - -describe('vaultsPull', () => { - const logger = new Logger('vaultsPull test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let db: DB; - let vaultManager: VaultManager; - 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, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing: {} as KeyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsPull: vaultsPull({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test.todo('pulls from a vault'); -}); diff --git a/tests/client/service/vaultsRename.test.ts b/tests/client/service/vaultsRename.test.ts deleted file mode 100644 index 469946d8f..000000000 --- a/tests/client/service/vaultsRename.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 KeyRing from '@/keys/KeyRing'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsRename from '@/client/service/vaultsRename'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as vaultsUtils from '@/vaults/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsRename', () => { - const logger = new Logger('vaultsRename test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsRename: vaultsRename({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('should rename vault', async () => { - const vaultId1 = await vaultManager.createVault('test-vault1'); - const vaultRenameMessage = new vaultsPB.Rename(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId1)); - vaultRenameMessage.setVault(vaultMessage); - vaultRenameMessage.setNewName('test-vault2'); - const vaultId2 = await grpcClient.vaultsRename( - vaultRenameMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(vaultId2).toBeInstanceOf(vaultsPB.Vault); - expect(vaultsUtils.decodeVaultId(vaultId2.getNameOrId())).toStrictEqual( - vaultId1, - ); - const renamedVaultId = await vaultManager.getVaultId('test-vault2'); - expect(renamedVaultId).toEqual(vaultId1); - }); -}); diff --git a/tests/client/service/vaultsScan.test.ts b/tests/client/service/vaultsScan.test.ts deleted file mode 100644 index f3e85d451..000000000 --- a/tests/client/service/vaultsScan.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { DB } from '@matrixai/db'; -import type { Host, Port } from '@/network/types'; -import type KeyRing from '@/keys/KeyRing'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsScan from '@/client/service/vaultsScan'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as testUtils from '../../utils'; - -describe('vaultsScan', () => { - const logger = new Logger('vaultsScan test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let vaultManager: VaultManager; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db: {} as DB, - acl: {} as ACL, - keyRing: {} as KeyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsScan: vaultsScan({ - authenticate, - vaultManager, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test.todo('scans a vault'); -}); diff --git a/tests/client/service/vaultsSecretsMkdir.test.ts b/tests/client/service/vaultsSecretsMkdir.test.ts deleted file mode 100644 index 19bad9e50..000000000 --- a/tests/client/service/vaultsSecretsMkdir.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 KeyRing from '@/keys/KeyRing'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsSecretsMkdir from '@/client/service/vaultsSecretsMkdir'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as vaultsUtils from '@/vaults/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsSecretsMkdir', () => { - const logger = new Logger('vaultsSecretsMkdir test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsSecretsMkdir: vaultsSecretsMkdir({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('makes a directory', async () => { - const vaultName = 'test-vault'; - const vaultId = await vaultManager.createVault(vaultName); - const dirPath = 'dir/dir1/dir2'; - const vaultMkdirMessage = new vaultsPB.Mkdir(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - vaultMkdirMessage.setVault(vaultMessage); - vaultMkdirMessage.setDirName(dirPath); - vaultMkdirMessage.setRecursive(true); - const response = await grpcClient.vaultsSecretsMkdir( - vaultMkdirMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.StatusMessage); - expect(response.getSuccess()).toBeTruthy(); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.readF(async (efs) => { - expect(await efs.exists(dirPath)).toBeTruthy(); - }); - }); - }); -}); diff --git a/tests/client/service/vaultsSecretsNewDeleteGet.test.ts b/tests/client/service/vaultsSecretsNewDeleteGet.test.ts deleted file mode 100644 index a35c35173..000000000 --- a/tests/client/service/vaultsSecretsNewDeleteGet.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 KeyRing from '@/keys/KeyRing'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsSecretsNew from '@/client/service/vaultsSecretsNew'; -import vaultsSecretsDelete from '@/client/service/vaultsSecretsDelete'; -import vaultsSecretsGet from '@/client/service/vaultsSecretsGet'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as vaultsUtils from '@/vaults/utils'; -import * as vaultsErrors from '@/vaults/errors'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsSecretsNewDeleteGet', () => { - const logger = new Logger('vaultsSecretsNewDeleteGet test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsSecretsNew: vaultsSecretsNew({ - authenticate, - vaultManager, - db, - logger, - }), - vaultsSecretsDelete: vaultsSecretsDelete({ - authenticate, - vaultManager, - db, - logger, - }), - vaultsSecretsGet: vaultsSecretsGet({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('creates, gets, and deletes secrets', async () => { - // Create secret - const secret = 'test-secret'; - const vaultId = await vaultManager.createVault('test-vault'); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secret); - secretMessage.setSecretContent(Buffer.from(secret)); - const createResponse = await grpcClient.vaultsSecretsNew( - secretMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(createResponse).toBeInstanceOf(utilsPB.StatusMessage); - expect(createResponse.getSuccess()).toBeTruthy(); - // Get secret - const getResponse1 = await grpcClient.vaultsSecretsGet( - secretMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(getResponse1).toBeInstanceOf(secretsPB.Secret); - const secretContent = Buffer.from( - getResponse1.getSecretContent(), - ).toString(); - expect(secretContent).toStrictEqual(secret); - // Delete secret - const deleteResponse = await grpcClient.vaultsSecretsDelete( - secretMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(deleteResponse).toBeInstanceOf(utilsPB.StatusMessage); - expect(deleteResponse.getSuccess()).toBeTruthy(); - // Check secret was deleted - await testUtils.expectRemoteError( - grpcClient.vaultsSecretsGet( - secretMessage, - clientUtils.encodeAuthFromPassword(password), - ), - vaultsErrors.ErrorSecretsSecretUndefined, - ); - }); -}); diff --git a/tests/client/service/vaultsSecretsNewDirList.test.ts b/tests/client/service/vaultsSecretsNewDirList.test.ts deleted file mode 100644 index 35714929b..000000000 --- a/tests/client/service/vaultsSecretsNewDirList.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 KeyRing from '@/keys/KeyRing'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsSecretsNewDir from '@/client/service/vaultsSecretsNewDir'; -import vaultsSecretsList from '@/client/service/vaultsSecretsList'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as vaultsUtils from '@/vaults/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsSecretsNewDirList', () => { - const logger = new Logger('vaultsSecretsNewDirList test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsSecretsNewDir: vaultsSecretsNewDir({ - authenticate, - vaultManager, - fs, - db, - logger, - }), - vaultsSecretsList: vaultsSecretsList({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('adds and lists a directory of secrets', async () => { - // Add directory of secrets - const vaultName = 'test-vault'; - const secretList = ['test-secret1', 'test-secret2', 'test-secret3']; - const secretDir = path.join(dataDir, 'secretDir'); - await fs.promises.mkdir(secretDir); - for (const secret of secretList) { - const secretFile = path.join(secretDir, secret); - // Write secret to file - await fs.promises.writeFile(secretFile, secret); - } - const vaultId = await vaultManager.createVault(vaultName); - const secretDirectoryMessage = new secretsPB.Directory(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretDirectoryMessage.setVault(vaultMessage); - secretDirectoryMessage.setSecretDirectory(secretDir); - const addResponse = await grpcClient.vaultsSecretsNewDir( - secretDirectoryMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(addResponse).toBeInstanceOf(utilsPB.StatusMessage); - expect(addResponse.getSuccess()).toBeTruthy(); - // List secrets - const listResponse = grpcClient.vaultsSecretsList( - vaultMessage, - clientUtils.encodeAuthFromPassword(password), - ); - const secrets: Array = []; - for await (const secret of listResponse) { - expect(secret).toBeInstanceOf(secretsPB.Secret); - secrets.push(secret.getSecretName()); - } - expect(secrets.sort()).toStrictEqual( - secretList.map((secret) => path.join('secretDir', secret)).sort(), - ); - }); -}); diff --git a/tests/client/service/vaultsSecretsRename.test.ts b/tests/client/service/vaultsSecretsRename.test.ts deleted file mode 100644 index dfe268a79..000000000 --- a/tests/client/service/vaultsSecretsRename.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 KeyRing from '@/keys/KeyRing'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsSecretsRename from '@/client/service/vaultsSecretsRename'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; -import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as vaultsUtils from '@/vaults/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsSecretsRename', () => { - const logger = new Logger('vaultsSecretsRename test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsSecretsRename: vaultsSecretsRename({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('renames a secret', async () => { - const vaultName = 'test-vault'; - const secretName = 'test-secret'; - const vaultId = await vaultManager.createVault(vaultName); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secretName, secretName); - }); - }); - const secretRenameMessage = new secretsPB.Rename(); - const vaultMessage = new vaultsPB.Vault(); - const secretMessage = new secretsPB.Secret(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setSecretName(secretName); - secretMessage.setVault(vaultMessage); - secretRenameMessage.setNewName('name-change'); - secretRenameMessage.setOldSecret(secretMessage); - const response = await grpcClient.vaultsSecretsRename( - secretRenameMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(utilsPB.StatusMessage); - expect(response.getSuccess()).toBeTruthy(); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.readF(async (efs) => { - expect((await efs.readFile('name-change')).toString()).toStrictEqual( - secretName, - ); - }); - }); - }); -}); diff --git a/tests/client/service/vaultsSecretsStat.test.ts b/tests/client/service/vaultsSecretsStat.test.ts deleted file mode 100644 index 9208ed36d..000000000 --- a/tests/client/service/vaultsSecretsStat.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type { Stat } from 'encryptedfs'; -import type { Host, Port } from '@/network/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 KeyRing from '@/keys/KeyRing'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsSecretsStat from '@/client/service/vaultsSecretsStat'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as secretsPB from '@/proto/js/polykey/v1/secrets/secrets_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as vaultsUtils from '@/vaults/utils'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsSecretsStat', () => { - const logger = new Logger('vaultsSecretsStat test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - const clientService = { - vaultsSecretsStat: vaultsSecretsStat({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('stats a file', async () => { - const vaultName = 'test-vault'; - const secretName = 'test-secret'; - const vaultId = await vaultManager.createVault(vaultName); - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secretName, secretName); - }); - }); - const secretMessage = new secretsPB.Secret(); - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - secretMessage.setVault(vaultMessage); - secretMessage.setSecretName(secretName); - const response = await grpcClient.vaultsSecretsStat( - secretMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(response).toBeInstanceOf(secretsPB.Stat); - const stat: Stat = JSON.parse(response.getJson()); - expect(stat.size).toBe(secretName.length); - expect(stat.blksize).toBe(4096); - expect(stat.blocks).toBe(1); - expect(stat.nlink).toBe(1); - }); -}); diff --git a/tests/client/service/vaultsVersion.test.ts b/tests/client/service/vaultsVersion.test.ts deleted file mode 100644 index 93e49e55c..000000000 --- a/tests/client/service/vaultsVersion.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -import type { Host, Port } from '@/network/types'; -import type { VaultId } from '@/vaults/types'; -import type NodeConnectionManager from '@/nodes/NodeConnectionManager'; -import type ACL from '@/acl/ACL'; -import type GestaltGraph from '@/gestalts/GestaltGraph'; -import type NotificationsManager from '@/notifications/NotificationsManager'; -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 KeyRing from '@/keys/KeyRing'; -import VaultManager from '@/vaults/VaultManager'; -import GRPCServer from '@/grpc/GRPCServer'; -import GRPCClientClient from '@/client/GRPCClientClient'; -import vaultsVersion from '@/client/service/vaultsVersion'; -import { ClientServiceService } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import * as vaultsPB from '@/proto/js/polykey/v1/vaults/vaults_pb'; -import * as clientUtils from '@/client/utils/utils'; -import * as vaultsUtils from '@/vaults/utils'; -import * as vaultsErrors from '@/vaults/errors'; -import * as keysUtils from '@/keys/utils/index'; -import * as testUtils from '../../utils'; - -describe('vaultsVersion', () => { - const logger = new Logger('vaultsVersion test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const secretVer1 = { - name: 'secret1v1', - content: 'Secret-1-content-ver1', - }; - const secretVer2 = { - name: 'secret1v2', - content: 'Secret-1-content-ver2', - }; - const vaultName = 'test-vault'; - let vaultId: VaultId; - let dataDir: string; - let keyRing: KeyRing; - let db: DB; - let vaultManager: VaultManager; - 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'); - keyRing = await KeyRing.createKeyRing({ - password, - keysPath, - logger, - passwordOpsLimit: keysUtils.passwordOpsLimits.min, - passwordMemLimit: keysUtils.passwordMemLimits.min, - strictMemoryLock: false, - }); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - const vaultsPath = path.join(dataDir, 'vaults'); - vaultManager = await VaultManager.createVaultManager({ - vaultsPath, - db, - acl: {} as ACL, - keyRing, - nodeConnectionManager: {} as NodeConnectionManager, - gestaltGraph: {} as GestaltGraph, - notificationsManager: {} as NotificationsManager, - logger, - }); - vaultId = await vaultManager.createVault(vaultName); - const clientService = { - vaultsVersion: vaultsVersion({ - authenticate, - vaultManager, - db, - logger, - }), - }; - 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: testUtils.generateRandomNodeId(), - host: '127.0.0.1' as Host, - port: grpcServer.getPort(), - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await vaultManager.stop(); - await db.stop(); - await keyRing.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('should switch a vault to a version', async () => { - // Commit some history - const ver1Oid = await vaultManager.withVaults([vaultId], async (vault) => { - await vault.writeF(async (efs) => { - await efs.writeFile(secretVer1.name, secretVer1.content); - }); - const ver1Oid = (await vault.log())[0].commitId; - await vault.writeF(async (efs) => { - await efs.writeFile(secretVer2.name, secretVer2.content); - }); - return ver1Oid; - }); - // Revert the version - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultName); - const vaultVersionMessage = new vaultsPB.Version(); - vaultVersionMessage.setVault(vaultMessage); - vaultVersionMessage.setVersionId(ver1Oid); - const version = await grpcClient.vaultsVersion( - vaultVersionMessage, - clientUtils.encodeAuthFromPassword(password), - ); - expect(version.getIsLatestVersion()).toBeFalsy(); - // Read old history - await vaultManager.withVaults([vaultId], async (vault) => { - await vault.readF(async (efs) => { - expect((await efs.readFile(secretVer1.name)).toString()).toStrictEqual( - secretVer1.content, - ); - }); - }); - }); - test('should fail to find a non existent version', async () => { - // Revert the version - const vaultMessage = new vaultsPB.Vault(); - vaultMessage.setNameOrId(vaultsUtils.encodeVaultId(vaultId)); - const vaultVersionMessage = new vaultsPB.Version(); - vaultVersionMessage.setVault(vaultMessage); - vaultVersionMessage.setVersionId('invalidOid'); - const version = grpcClient.vaultsVersion( - vaultVersionMessage, - clientUtils.encodeAuthFromPassword(password), - ); - await testUtils.expectRemoteError( - version, - vaultsErrors.ErrorVaultReferenceInvalid, - ); - vaultVersionMessage.setVersionId( - '7660aa9a2fee90e875c2d19e5deefe882ca1d4d9', - ); - const version2 = grpcClient.vaultsVersion( - vaultVersionMessage, - clientUtils.encodeAuthFromPassword(password), - ); - await testUtils.expectRemoteError( - version2, - vaultsErrors.ErrorVaultReferenceMissing, - ); - }); -}); diff --git a/tests/client/utils.ts b/tests/client/utils.ts deleted file mode 100644 index d55dc4664..000000000 --- a/tests/client/utils.ts +++ /dev/null @@ -1,127 +0,0 @@ -import type { IClientServiceServer } from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import type { SessionToken } from '@/sessions/types'; -import type PolykeyAgent from '@/PolykeyAgent'; -import type { NodeId } from '@/ids/types'; -import type { Host, Port } from '@/network/types'; -import * as grpc from '@grpc/grpc-js'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { - ClientServiceService, - ClientServiceClient, -} from '@/proto/js/polykey/v1/client_service_grpc_pb'; -import createClientService from '@/client/service'; -import PolykeyClient from '@/PolykeyClient'; -import { promisify, timerStart } from '@/utils'; -import * as grpcUtils from '@/grpc/utils'; -import * as keysUtils from '@/keys/utils'; - -async function openTestClientServer({ - pkAgent, - secure, -}: { - pkAgent: PolykeyAgent; - secure?: boolean; -}) { - const _secure = secure ?? true; - const clientService: IClientServiceServer = createClientService({ - pkAgent, - keyRing: pkAgent.keyRing, - certManager: pkAgent.certManager, - vaultManager: pkAgent.vaultManager, - nodeGraph: pkAgent.nodeGraph, - nodeConnectionManager: pkAgent.nodeConnectionManager, - nodeManager: pkAgent.nodeManager, - identitiesManager: pkAgent.identitiesManager, - gestaltGraph: pkAgent.gestaltGraph, - sessionManager: pkAgent.sessionManager, - notificationsManager: pkAgent.notificationsManager, - discovery: pkAgent.discovery, - sigchain: pkAgent.sigchain, - acl: pkAgent.acl, - proxy: pkAgent.proxy, - grpcServerClient: pkAgent.grpcServerClient, - grpcServerAgent: pkAgent.grpcServerAgent, - fs: pkAgent.fs, - db: pkAgent.db, - logger: pkAgent.logger, - }); - - const callCredentials = _secure - ? grpcUtils.serverSecureCredentials( - keysUtils.privateKeyToPEM(pkAgent.keyRing.keyPair.privateKey), - await pkAgent.certManager.getCertPEMsChainPEM(), - ) - : grpcUtils.serverInsecureCredentials(); - - const server = new grpc.Server(); - server.addService(ClientServiceService, clientService); - const bindAsync = promisify(server.bindAsync).bind(server); - const port = await bindAsync(`127.0.0.1:0`, callCredentials); - server.start(); - return [server, port]; -} - -const closeTestClientServer = async (server) => { - const tryShutdown = promisify(server.tryShutdown).bind(server); - await tryShutdown(); -}; - -async function openTestClientClient( - nodeId: NodeId, - host: Host, - port: Port, - clientPath: string, -) { - const logger = new Logger('ClientClientTest', LogLevel.WARN, [ - new StreamHandler(), - ]); - const fs = require('fs/promises'); - - const pkc: PolykeyClient = await PolykeyClient.createPolykeyClient({ - nodePath: clientPath, - host: host, - nodeId, - port: port, - fs, - logger, - timer: timerStart(30000), - }); - - return pkc; -} - -async function closeTestClientClient(client: PolykeyClient) { - await client.stop(); -} - -async function openSimpleClientClient( - port: number, -): Promise { - const client = new ClientServiceClient( - `127.0.0.1:${port}`, - grpc.ChannelCredentials.createInsecure(), - ); - const waitForReady = promisify(client.waitForReady).bind(client); - await waitForReady(Infinity); - return client; -} - -function closeSimpleClientClient(client: ClientServiceClient): void { - client.close(); -} - -function createCallCredentials(token: SessionToken): grpc.Metadata { - const meta = new grpc.Metadata(); - meta.set('Authorization', `Bearer ${token}`); - return meta; -} - -export { - openTestClientServer, - closeTestClientServer, - openTestClientClient, - closeTestClientClient, - openSimpleClientClient, - closeSimpleClientClient, - createCallCredentials, -}; diff --git a/tests/grpc/GRPCClient.test.ts b/tests/grpc/GRPCClient.test.ts index cc3263287..d6a87784d 100644 --- a/tests/grpc/GRPCClient.test.ts +++ b/tests/grpc/GRPCClient.test.ts @@ -201,7 +201,7 @@ describe('GRPCClient', () => { const m2 = new utilsPB.EchoMessage(); m2.setChallenge('error'); pCall = client.unary(m2); - await testUtils.expectRemoteError(pCall, grpcErrors.ErrorGRPC); + await testUtils.expectRemoteErrorOLD(pCall, grpcErrors.ErrorGRPC); meta = await pCall.meta; // Expect reflected reflected session token expect(clientUtils.decodeAuthToSession(meta)).toBe( diff --git a/tests/grpc/utils.test.ts b/tests/grpc/utils.test.ts index bb5cd9f7a..2e67b5674 100644 --- a/tests/grpc/utils.test.ts +++ b/tests/grpc/utils.test.ts @@ -74,7 +74,7 @@ describe('GRPC utils', () => { const messageTo = new utilsPB.EchoMessage(); messageTo.setChallenge('error'); const pCall = unary(messageTo); - await expect(pCall).rejects.toThrow(grpcErrors.ErrorPolykeyRemote); + await expect(pCall).rejects.toThrow(grpcErrors.ErrorPolykeyRemoteOLD); try { await pCall; } catch (e) { @@ -138,7 +138,7 @@ describe('GRPC utils', () => { messageTo.setChallenge(challenge); const stream = serverStream(messageTo); await expect(() => stream.next()).rejects.toThrow( - grpcErrors.ErrorPolykeyRemote, + grpcErrors.ErrorPolykeyRemoteOLD, ); // The generator will have ended // the internal stream will be automatically destroyed @@ -386,7 +386,7 @@ describe('GRPC utils', () => { messageTo.setChallenge('error'); await genDuplex.write(messageTo); await expect(() => genDuplex.read()).rejects.toThrow( - grpcErrors.ErrorPolykeyRemote, + grpcErrors.ErrorPolykeyRemoteOLD, ); expect(genDuplex.stream.destroyed).toBe(true); expect(genDuplex.stream.getPeer()).toBe(`127.0.0.1:${port}`); @@ -409,7 +409,7 @@ describe('GRPC utils', () => { const messageTo = new utilsPB.EchoMessage(); messageTo.setChallenge('error'); await expect(() => genDuplex.next(messageTo)).rejects.toThrow( - grpcErrors.ErrorPolykeyRemote, + grpcErrors.ErrorPolykeyRemoteOLD, ); expect(genDuplex.stream.destroyed).toBe(true); expect(genDuplex.stream.getPeer()).toBe(`127.0.0.1:${port}`); @@ -446,7 +446,7 @@ describe('GRPC utils', () => { command: 'testCall', }, ); - expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemote); + expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemoteOLD); expect(deserialisedError.message).toBe('test error'); // @ts-ignore - already checked above that error is ErrorPolykeyRemote const metadata = deserialisedError.metadata; @@ -485,7 +485,7 @@ describe('GRPC utils', () => { command: 'testCall', }, ); - expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemote); + expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemoteOLD); expect(deserialisedError.message).toBe('test error'); // @ts-ignore - already checked above that error is ErrorPolykeyRemote const metadata = deserialisedError.metadata; @@ -518,7 +518,7 @@ describe('GRPC utils', () => { command: 'testCall', }, ); - expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemote); + expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemoteOLD); // @ts-ignore - already checked above that error is ErrorPolykeyRemote const metadata = deserialisedError.metadata; expect(metadata.nodeId).toBe(nodeId); @@ -569,7 +569,7 @@ describe('GRPC utils', () => { command: 'testCall', }, ); - expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemote); + expect(deserialisedError).toBeInstanceOf(grpcErrors.ErrorPolykeyRemoteOLD); expect(deserialisedError.message).toBe('test error'); // @ts-ignore - already checked above that error is ErrorPolykeyRemote const metadata = deserialisedError.metadata; diff --git a/tests/notifications/NotificationsManager.test.ts b/tests/notifications/NotificationsManager.test.ts index ebf9e0879..d4ae0f5bc 100644 --- a/tests/notifications/NotificationsManager.test.ts +++ b/tests/notifications/NotificationsManager.test.ts @@ -295,21 +295,21 @@ describe('NotificationsManager', () => { } as VaultActions, }; - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( notificationsManager.sendNotification( receiver.keyRing.getNodeId(), generalNotification, ), notificationsErrors.ErrorNotificationsPermissionsNotFound, ); - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( notificationsManager.sendNotification( receiver.keyRing.getNodeId(), gestaltNotification, ), notificationsErrors.ErrorNotificationsPermissionsNotFound, ); - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( notificationsManager.sendNotification( receiver.keyRing.getNodeId(), vaultNotification, diff --git a/tests/RPC/RPC.test.ts b/tests/rpc/RPC.test.ts similarity index 90% rename from tests/RPC/RPC.test.ts rename to tests/rpc/RPC.test.ts index 883b4e4f1..6d30bcbef 100644 --- a/tests/RPC/RPC.test.ts +++ b/tests/rpc/RPC.test.ts @@ -1,26 +1,26 @@ -import type { ContainerType, JsonRpcRequest } from '@/RPC/types'; +import type { ContainerType, JSONRPCRequest } from '@/rpc/types'; import type { ConnectionInfo } from '@/network/types'; import type { ReadableStream } from 'stream/web'; import type { JSONValue } from '@/types'; import { fc, testProp } from '@fast-check/jest'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import RPCServer from '@/RPC/RPCServer'; -import RPCClient from '@/RPC/RPCClient'; +import RPCServer from '@/rpc/RPCServer'; +import RPCClient from '@/rpc/RPCClient'; import { ClientHandler, DuplexHandler, RawHandler, ServerHandler, UnaryHandler, -} from '@/RPC/handlers'; +} from '@/rpc/handlers'; import { ClientCaller, DuplexCaller, RawCaller, ServerCaller, UnaryCaller, -} from '@/RPC/callers'; -import * as rpcErrors from '@/RPC/errors'; +} from '@/rpc/callers'; +import * as rpcErrors from '@/rpc/errors'; import * as rpcTestUtils from './utils'; describe('RPC', () => { @@ -37,12 +37,12 @@ describe('RPC', () => { Uint8Array >(); - let header: JsonRpcRequest | undefined; + let header: JSONRPCRequest | undefined; class TestMethod extends RawHandler { public handle( - input: [ReadableStream, JsonRpcRequest], + input: [JSONRPCRequest, ReadableStream], ): ReadableStream { - const [stream, header_] = input; + const [header_, stream] = input; header = header_; return stream; } @@ -59,7 +59,7 @@ describe('RPC', () => { manifest: { testMethod: new RawCaller(), }, - streamPairCreateCallback: async () => clientPair, + streamFactory: async () => clientPair, logger, }); @@ -72,7 +72,7 @@ describe('RPC', () => { await writer.write(value); } await writer.close(); - const expectedHeader: JsonRpcRequest = { + const expectedHeader: JSONRPCRequest = { jsonrpc: '2.0', method: 'testMethod', params: { hello: 'world' }, @@ -112,7 +112,7 @@ describe('RPC', () => { manifest: { testMethod: new DuplexCaller(), }, - streamPairCreateCallback: async () => clientPair, + streamFactory: async () => clientPair, logger, }); @@ -160,7 +160,7 @@ describe('RPC', () => { manifest: { testMethod: new ServerCaller(), }, - streamPairCreateCallback: async () => clientPair, + streamFactory: async () => clientPair, logger, }); @@ -205,7 +205,7 @@ describe('RPC', () => { manifest: { testMethod: new ClientCaller(), }, - streamPairCreateCallback: async () => clientPair, + streamFactory: async () => clientPair, logger, }); @@ -247,7 +247,7 @@ describe('RPC', () => { manifest: { testMethod: new UnaryCaller(), }, - streamPairCreateCallback: async () => clientPair, + streamFactory: async () => clientPair, logger, }); @@ -286,12 +286,12 @@ describe('RPC', () => { manifest: { testMethod: new UnaryCaller(), }, - streamPairCreateCallback: async () => clientPair, + streamFactory: async () => clientPair, logger, }); const callProm = rpcClient.methods.testMethod(value); - await expect(callProm).rejects.toThrow(rpcErrors.ErrorRpcRemoteError); + await expect(callProm).rejects.toThrow(rpcErrors.ErrorPolykeyRemote); await expect( callProm.catch((e) => { throw e.cause; @@ -334,12 +334,12 @@ describe('RPC', () => { manifest: { testMethod: new UnaryCaller(), }, - streamPairCreateCallback: async () => clientPair, + streamFactory: async () => clientPair, logger, }); const callProm = rpcClient.methods.testMethod(value); - await expect(callProm).rejects.toThrow(rpcErrors.ErrorRpcRemoteError); + await expect(callProm).rejects.toThrow(rpcErrors.ErrorPolykeyRemote); await expect( callProm.catch((e) => { throw e.cause; diff --git a/tests/RPC/RPCClient.test.ts b/tests/rpc/RPCClient.test.ts similarity index 87% rename from tests/RPC/RPCClient.test.ts rename to tests/rpc/RPCClient.test.ts index 672f10626..ccea25143 100644 --- a/tests/RPC/RPCClient.test.ts +++ b/tests/rpc/RPCClient.test.ts @@ -1,24 +1,24 @@ import type { ReadableWritablePair } from 'stream/web'; import type { JSONValue } from '@/types'; import type { - JsonRpcRequest, - JsonRpcRequestMessage, - JsonRpcResponse, -} from '@/RPC/types'; + JSONRPCRequest, + JSONRPCRequestMessage, + JSONRPCResponse, +} from '@/rpc/types'; import { TransformStream, ReadableStream } from 'stream/web'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { testProp, fc } from '@fast-check/jest'; -import RPCClient from '@/RPC/RPCClient'; -import RPCServer from '@/RPC/RPCServer'; -import * as rpcErrors from '@/RPC/errors'; +import RPCClient from '@/rpc/RPCClient'; +import RPCServer from '@/rpc/RPCServer'; +import * as rpcErrors from '@/rpc/errors'; import { ClientCaller, DuplexCaller, RawCaller, ServerCaller, UnaryCaller, -} from '@/RPC/callers'; -import * as middlewareUtils from '@/RPC/middleware'; +} from '@/rpc/callers'; +import * as middlewareUtils from '@/rpc/utils/middleware'; import * as rpcTestUtils from './utils'; describe(`${RPCClient.name}`, () => { @@ -58,7 +58,7 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const callerInterface = await rpcClient.rawStreamCaller( @@ -72,7 +72,7 @@ describe(`${RPCClient.name}`, () => { } await writer.close(); - const expectedHeader: JsonRpcRequest = { + const expectedHeader: JSONRPCRequest = { jsonrpc: '2.0', method: methodName, params: headerParams, @@ -95,7 +95,7 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const callerInterface = await rpcClient.duplexStreamCaller< @@ -108,8 +108,8 @@ describe(`${RPCClient.name}`, () => { } await writable.close(); - const expectedMessages: Array = messages.map((v) => { - const request: JsonRpcRequestMessage = { + const expectedMessages: Array = messages.map((v) => { + const request: JSONRPCRequestMessage = { jsonrpc: '2.0', method: methodName, id: null, @@ -135,7 +135,7 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const callerInterface = await rpcClient.serverStreamCaller< @@ -175,7 +175,7 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const { output, writable } = await rpcClient.clientStreamCaller< @@ -214,7 +214,7 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const result = await rpcClient.unaryCaller( @@ -252,7 +252,7 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const callerInterface = await rpcClient.duplexStreamCaller< @@ -265,7 +265,7 @@ describe(`${RPCClient.name}`, () => { // Only consume } })(); - await expect(callProm).rejects.toThrow(rpcErrors.ErrorRpcRemoteError); + await expect(callProm).rejects.toThrow(rpcErrors.ErrorPolykeyRemote); await outputResult; await rpcClient.destroy(); }, @@ -289,7 +289,7 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const callerInterface = await rpcClient.duplexStreamCaller< @@ -302,7 +302,7 @@ describe(`${RPCClient.name}`, () => { // Only consume } })(); - await expect(callProm).rejects.toThrow(rpcErrors.ErrorRpcRemoteError); + await expect(callProm).rejects.toThrow(rpcErrors.ErrorPolykeyRemote); await outputResult; await rpcClient.destroy(); }, @@ -329,7 +329,7 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const callerInterface = await rpcClient.duplexStreamCaller< @@ -342,7 +342,7 @@ describe(`${RPCClient.name}`, () => { // Only consume } })(); - await expect(callProm).rejects.toThrow(rpcErrors.ErrorRpcRemoteError); + await expect(callProm).rejects.toThrow(rpcErrors.ErrorPolykeyRemote); await outputResult; await rpcClient.destroy(); }, @@ -360,20 +360,22 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, - middleware: middlewareUtils.defaultClientMiddlewareWrapper(() => { - return { - forward: new TransformStream({ - transform: (chunk, controller) => { - controller.enqueue({ - ...chunk, - params: 'one', - }); - }, - }), - reverse: new TransformStream(), - }; - }), + streamFactory: async () => streamPair, + middlewareFactory: middlewareUtils.defaultClientMiddlewareWrapper( + () => { + return { + forward: new TransformStream({ + transform: (chunk, controller) => { + controller.enqueue({ + ...chunk, + params: 'one', + }); + }, + }), + reverse: new TransformStream(), + }; + }, + ), logger, }); @@ -393,9 +395,9 @@ describe(`${RPCClient.name}`, () => { await writer.write(value); } - const expectedMessages: Array = messages.map( + const expectedMessages: Array = messages.map( () => { - const request: JsonRpcRequestMessage = { + const request: JSONRPCRequestMessage = { jsonrpc: '2.0', method: methodName, id: null, @@ -424,20 +426,22 @@ describe(`${RPCClient.name}`, () => { }; const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => streamPair, - middleware: middlewareUtils.defaultClientMiddlewareWrapper(() => { - return { - forward: new TransformStream(), - reverse: new TransformStream({ - transform: (chunk, controller) => { - controller.enqueue({ - ...chunk, - result: 'one', - }); - }, - }), - }; - }), + streamFactory: async () => streamPair, + middlewareFactory: middlewareUtils.defaultClientMiddlewareWrapper( + () => { + return { + forward: new TransformStream(), + reverse: new TransformStream({ + transform: (chunk, controller) => { + controller.enqueue({ + ...chunk, + result: 'one', + }); + }, + }), + }; + }, + ), logger, }); @@ -475,7 +479,7 @@ describe(`${RPCClient.name}`, () => { manifest: { server: new ServerCaller(), }, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const callerInterface = await rpcClient.methods.server(params); @@ -514,7 +518,7 @@ describe(`${RPCClient.name}`, () => { manifest: { client: new ClientCaller(), }, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const { output, writable } = await rpcClient.methods.client(); @@ -552,7 +556,7 @@ describe(`${RPCClient.name}`, () => { manifest: { unary: new UnaryCaller(), }, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const result = await rpcClient.methods.unary(params); @@ -595,7 +599,7 @@ describe(`${RPCClient.name}`, () => { manifest: { raw: new RawCaller(), }, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); const callerInterface = await rpcClient.methods.raw(headerParams); @@ -606,7 +610,7 @@ describe(`${RPCClient.name}`, () => { } await writer.close(); - const expectedHeader: JsonRpcRequest = { + const expectedHeader: JSONRPCRequest = { jsonrpc: '2.0', method: 'raw', params: headerParams, @@ -638,7 +642,7 @@ describe(`${RPCClient.name}`, () => { manifest: { duplex: new DuplexCaller(), }, - streamPairCreateCallback: async () => streamPair, + streamFactory: async () => streamPair, logger, }); let count = 0; @@ -659,7 +663,7 @@ describe(`${RPCClient.name}`, () => { test('manifest without handler errors', async () => { const rpcClient = await RPCClient.createRPCClient({ manifest: {}, - streamPairCreateCallback: async () => { + streamFactory: async () => { return {} as ReadableWritablePair; }, logger, diff --git a/tests/RPC/RPCServer.test.ts b/tests/rpc/RPCServer.test.ts similarity index 78% rename from tests/RPC/RPCServer.test.ts rename to tests/rpc/RPCServer.test.ts index 8ad825725..083f94759 100644 --- a/tests/RPC/RPCServer.test.ts +++ b/tests/rpc/RPCServer.test.ts @@ -1,28 +1,30 @@ import type { ContainerType, - JsonRpcRequest, - JsonRpcResponse, - JsonRpcResponseError, -} from '@/RPC/types'; + JSONRPCRequest, + JSONRPCResponse, + JSONRPCResponseError, +} from '@/rpc/types'; import type { JSONValue } from '@/types'; import type { ConnectionInfo, Host, Port } from '@/network/types'; import type { NodeId } from '@/ids'; import type { ReadableWritablePair } from 'stream/web'; import type { ContextCancellable } from '@/contexts/types'; +import type { RPCErrorEvent } from '@/rpc/events'; import { TransformStream, ReadableStream } from 'stream/web'; import { fc, testProp } from '@fast-check/jest'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import RPCServer from '@/RPC/RPCServer'; -import * as rpcErrors from '@/RPC/errors'; -import * as rpcUtils from '@/RPC/utils'; +import RPCServer from '@/rpc/RPCServer'; +import * as rpcErrors from '@/rpc/errors'; +import * as rpcUtils from '@/rpc/utils'; import { ClientHandler, DuplexHandler, RawHandler, ServerHandler, UnaryHandler, -} from '@/RPC/handlers'; -import * as middlewareUtils from '@/RPC/middleware'; +} from '@/rpc/handlers'; +import * as middlewareUtils from '@/rpc/utils/middleware'; +import { promise } from '@/utils'; import * as rpcTestUtils from './utils'; describe(`${RPCServer.name}`, () => { @@ -66,7 +68,7 @@ describe(`${RPCServer.name}`, () => { rpcTestUtils.binaryStreamToSnippedStream([4, 7, 13, 2, 6]), ); class TestHandler extends RawHandler { - public handle([input, _header]): ReadableStream { + public handle([_header, input]): ReadableStream { void (async () => { for await (const _ of input) { // No touch, only consume @@ -324,7 +326,7 @@ describe(`${RPCServer.name}`, () => { // @ts-ignore: kidnap private property for (const activeStream of activeStreams) { thing = activeStream; - activeStream.cancel(new rpcErrors.ErrorRpcStopping()); + activeStream.cancel(new rpcErrors.ErrorRPCStopping()); } } }, @@ -340,7 +342,7 @@ describe(`${RPCServer.name}`, () => { await expect(thing).toResolve(); expect(lastMessage).toBeDefined(); expect(() => - rpcUtils.parseJsonRpcResponseError(JSON.parse(lastMessage.toString())), + rpcUtils.parseJSONRPCResponseError(JSON.parse(lastMessage.toString())), ).not.toThrow(); await rpcServer.destroy(); }); @@ -388,11 +390,11 @@ describe(`${RPCServer.name}`, () => { logger, }); let resolve, reject; - const errorProm = new Promise((resolve_, reject_) => { - resolve = resolve_; - reject = reject_; + const errorProm = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; }); - rpcServer.addEventListener('error', (thing) => { + rpcServer.addEventListener('error', (thing: RPCErrorEvent) => { resolve(thing); }); const [outputResult, outputStream] = rpcTestUtils.streamToArray(); @@ -429,11 +431,11 @@ describe(`${RPCServer.name}`, () => { logger, }); let resolve, reject; - const errorProm = new Promise((resolve_, reject_) => { - resolve = resolve_; - reject = reject_; + const errorProm = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; }); - rpcServer.addEventListener('error', (thing) => { + rpcServer.addEventListener('error', (thing: RPCErrorEvent) => { resolve(thing); }); const [outputResult, outputStream] = rpcTestUtils.streamToArray(); @@ -453,13 +455,19 @@ describe(`${RPCServer.name}`, () => { }, ); testProp( - 'should emit stream error', + 'should emit stream error if input stream fails', [specificMessageArb], async (messages) => { - const stream = rpcTestUtils.messagesToReadableStream(messages); + const handlerEndedProm = promise(); class TestMethod extends DuplexHandler { - public async *handle(): AsyncIterable { - throw new rpcErrors.ErrorRpcPlaceholderConnectionError(); + public async *handle(input): AsyncIterable { + try { + for await (const _ of input) { + // Consume but don't yield anything + } + } finally { + handlerEndedProm.resolveP(); + } } } const rpcServer = await RPCServer.createRPCServer({ @@ -468,26 +476,103 @@ describe(`${RPCServer.name}`, () => { }, logger, }); - let resolve, reject; - const errorProm = new Promise((resolve_, reject_) => { - resolve = resolve_; - reject = reject_; - }); - rpcServer.addEventListener('error', (thing) => { + let resolve; + rpcServer.addEventListener('error', (thing: RPCErrorEvent) => { resolve(thing); }); - const [outputResult, outputStream] = rpcTestUtils.streamToArray(); - const readWriteStream: ReadableWritablePair = { - readable: stream, + const passThroughStreamIn = new TransformStream(); + const [outputResult, outputStream] = rpcTestUtils.streamToArray(); + const readWriteStream: ReadableWritablePair = { + readable: passThroughStreamIn.readable, writable: outputStream, }; rpcServer.handleStream(readWriteStream, {} as ConnectionInfo); - await outputResult; - + const writer = passThroughStreamIn.writable.getWriter(); + // Write messages + for (const message of messages) { + await writer.write(Buffer.from(JSON.stringify(message))); + } + // Abort stream + const writerReason = Symbol('writerAbort'); + await writer.abort(writerReason); + // We should get an error RPC message + await expect(outputResult).toResolve(); + const errorMessage = JSON.parse((await outputResult)[0].toString()); + // Parse without error + rpcUtils.parseJSONRPCResponseError(errorMessage); + // Check that the handler was cleaned up. + await expect(handlerEndedProm.p).toResolve(); + await rpcServer.destroy(); + }, + { numRuns: 1 }, + ); + testProp( + 'should emit stream error if output stream fails', + [specificMessageArb], + async (messages) => { + const handlerEndedProm = promise(); + let ctx: ContextCancellable | undefined; + class TestMethod extends DuplexHandler { + public async *handle(input, _, _ctx): AsyncIterable { + ctx = _ctx; + // Echo input + try { + yield* input; + } finally { + handlerEndedProm.resolveP(); + } + } + } + const rpcServer = await RPCServer.createRPCServer({ + manifest: { + testMethod: new TestMethod({}), + }, + logger, + }); + let resolve; + const errorProm = new Promise((_resolve) => { + resolve = _resolve; + }); + rpcServer.addEventListener('error', (thing: RPCErrorEvent) => { + resolve(thing); + }); + const passThroughStreamIn = new TransformStream(); + const passThroughStreamOut = new TransformStream< + Uint8Array, + Uint8Array + >(); + const readWriteStream: ReadableWritablePair = { + readable: passThroughStreamIn.readable, + writable: passThroughStreamOut.writable, + }; + rpcServer.handleStream(readWriteStream, {} as ConnectionInfo); + const writer = passThroughStreamIn.writable.getWriter(); + const reader = passThroughStreamOut.readable.getReader(); + // Write messages + for (const message of messages) { + await writer.write(Buffer.from(JSON.stringify(message))); + await reader.read(); + } + // Abort stream + // const writerReason = Symbol('writerAbort'); + const readerReason = Symbol('readerAbort'); + // Await writer.abort(writerReason); + await reader.cancel(readerReason); + // We should get an error event + const event = await errorProm; + await writer.close(); + // Expect(event.detail.cause).toContain(writerReason); + expect(event.detail).toBeInstanceOf(rpcErrors.ErrorRPCOutputStreamError); + expect(event.detail.cause).toBe(readerReason); + // Check that the handler was cleaned up. + await expect(handlerEndedProm.p).toResolve(); + // Check that an abort signal happened + expect(ctx).toBeDefined(); + expect(ctx?.signal.aborted).toBeTrue(); + expect(ctx?.signal.reason).toBe(readerReason); await rpcServer.destroy(); - reject(); - await expect(errorProm).toResolve(); }, + { numRuns: 1 }, ); testProp('forward middlewares', [specificMessageArb], async (messages) => { const stream = rpcTestUtils.messagesToReadableStream(messages); @@ -498,22 +583,24 @@ describe(`${RPCServer.name}`, () => { yield* input; } } - const middleware = middlewareUtils.defaultServerMiddlewareWrapper(() => { - return { - forward: new TransformStream({ - transform: (chunk, controller) => { - chunk.params = 1; - controller.enqueue(chunk); - }, - }), - reverse: new TransformStream(), - }; - }); + const middlewareFactory = middlewareUtils.defaultServerMiddlewareWrapper( + () => { + return { + forward: new TransformStream({ + transform: (chunk, controller) => { + chunk.params = 1; + controller.enqueue(chunk); + }, + }), + reverse: new TransformStream(), + }; + }, + ); const rpcServer = await RPCServer.createRPCServer({ manifest: { testMethod: new TestMethod({}), }, - middleware, + middlewareFactory: middlewareFactory, logger, }); const [outputResult, outputStream] = rpcTestUtils.streamToArray(); @@ -558,7 +645,7 @@ describe(`${RPCServer.name}`, () => { manifest: { testMethod: new TestMethod({}), }, - middleware, + middlewareFactory: middleware, logger, }); const [outputResult, outputStream] = rpcTestUtils.streamToArray(); @@ -593,11 +680,11 @@ describe(`${RPCServer.name}`, () => { } const middleware = middlewareUtils.defaultServerMiddlewareWrapper(() => { let first = true; - let reverseController: TransformStreamDefaultController; + let reverseController: TransformStreamDefaultController; return { forward: new TransformStream< - JsonRpcRequest, - JsonRpcRequest + JSONRPCRequest, + JSONRPCRequest >({ transform: (chunk, controller) => { if (first && chunk.params?.metadata.token !== validToken) { @@ -625,7 +712,7 @@ describe(`${RPCServer.name}`, () => { manifest: { testMethod: new TestMethod({}), }, - middleware, + middlewareFactory: middleware, logger, }); const [outputResult, outputStream] = rpcTestUtils.streamToArray(); @@ -639,7 +726,7 @@ describe(`${RPCServer.name}`, () => { }; data: JSONValue; }; - const failureMessage: JsonRpcResponseError = { + const failureMessage: JSONRPCResponseError = { jsonrpc: '2.0', id: null, error: { diff --git a/tests/RPC/utils.ts b/tests/rpc/utils.ts similarity index 83% rename from tests/RPC/utils.ts rename to tests/rpc/utils.ts index 70ef328f2..3744306ca 100644 --- a/tests/RPC/utils.ts +++ b/tests/rpc/utils.ts @@ -1,20 +1,22 @@ import type { ReadableWritablePair } from 'stream/web'; import type { JSONValue } from '@/types'; import type { - JsonRpcError, - JsonRpcMessage, - JsonRpcRequestNotification, - JsonRpcRequestMessage, - JsonRpcResponseError, - JsonRpcResponseResult, - JsonRpcResponse, - JsonRpcRequest, -} from '@/RPC/types'; + JSONRPCError, + JSONRPCMessage, + JSONRPCRequestNotification, + JSONRPCRequestMessage, + JSONRPCResponseError, + JSONRPCResponseResult, + JSONRPCResponse, + JSONRPCRequest, +} from '@/rpc/types'; +import type { NodeId } from '@/ids'; import { ReadableStream, WritableStream, TransformStream } from 'stream/web'; import { fc } from '@fast-check/jest'; +import { IdInternal } from '@matrixai/id'; import * as utils from '@/utils'; -import { fromError } from '@/RPC/utils'; -import * as rpcErrors from '@/RPC/errors'; +import { fromError } from '@/rpc/utils'; +import * as rpcErrors from '@/rpc/errors'; /** * This is used to convert regular chunks into randomly sized chunks based on @@ -60,14 +62,13 @@ function binaryStreamToNoisyStream(noise: Array) { } /** - * This takes an array of JsonRpcMessages and converts it to a readable stream. + * This takes an array of JSONRPCMessages and converts it to a readable stream. * Used to seed input for handlers and output for callers. */ -const messagesToReadableStream = (messages: Array) => { +const messagesToReadableStream = (messages: Array) => { return new ReadableStream({ async start(controller) { for (const arrayElement of messages) { - // Controller.enqueue(arrayElement) controller.enqueue(Buffer.from(JSON.stringify(arrayElement), 'utf-8')); } controller.close(); @@ -102,7 +103,7 @@ const jsonRpcRequestMessageArb = ( requiredKeys: ['jsonrpc', 'method', 'id'], }, ) - .noShrink() as fc.Arbitrary; + .noShrink() as fc.Arbitrary; const jsonRpcRequestNotificationArb = ( method: fc.Arbitrary = fc.string(), @@ -119,7 +120,7 @@ const jsonRpcRequestNotificationArb = ( requiredKeys: ['jsonrpc', 'method'], }, ) - .noShrink() as fc.Arbitrary; + .noShrink() as fc.Arbitrary; const jsonRpcRequestArb = ( method: fc.Arbitrary = fc.string(), @@ -130,7 +131,7 @@ const jsonRpcRequestArb = ( jsonRpcRequestMessageArb(method, params), jsonRpcRequestNotificationArb(method, params), ) - .noShrink() as fc.Arbitrary; + .noShrink() as fc.Arbitrary; const jsonRpcResponseResultArb = ( result: fc.Arbitrary = safeJsonValueArb, @@ -141,7 +142,7 @@ const jsonRpcResponseResultArb = ( result: result, id: idArb, }) - .noShrink() as fc.Arbitrary; + .noShrink() as fc.Arbitrary; const jsonRpcErrorArb = ( error: fc.Arbitrary = fc.constant(new Error('test error')), sensitive: boolean = false, @@ -157,7 +158,7 @@ const jsonRpcErrorArb = ( requiredKeys: ['code', 'message'], }, ) - .noShrink() as fc.Arbitrary; + .noShrink() as fc.Arbitrary; const jsonRpcResponseErrorArb = ( error?: fc.Arbitrary, @@ -169,14 +170,14 @@ const jsonRpcResponseErrorArb = ( error: jsonRpcErrorArb(error, sensitive), id: idArb, }) - .noShrink() as fc.Arbitrary; + .noShrink() as fc.Arbitrary; const jsonRpcResponseArb = ( result: fc.Arbitrary = safeJsonValueArb, ) => fc .oneof(jsonRpcResponseResultArb(result), jsonRpcResponseErrorArb()) - .noShrink() as fc.Arbitrary; + .noShrink() as fc.Arbitrary; const jsonRpcMessageArb = ( method: fc.Arbitrary = fc.string(), @@ -185,7 +186,7 @@ const jsonRpcMessageArb = ( ) => fc .oneof(jsonRpcRequestArb(method, params), jsonRpcResponseArb(result)) - .noShrink() as fc.Arbitrary; + .noShrink() as fc.Arbitrary; const snippingPatternArb = fc .array(fc.integer({ min: 1, max: 32 }), { minLength: 100, size: 'medium' }) @@ -261,9 +262,22 @@ const errorArb = ( ) => cause.chain((cause) => fc.oneof( - fc.constant(new rpcErrors.ErrorRpcParse(undefined, { cause })), - fc.constant(new rpcErrors.ErrorRpcMessageLength(undefined, { cause })), - fc.constant(new rpcErrors.ErrorRpcRemoteError(undefined, { cause })), + fc.constant(new rpcErrors.ErrorRPCParse(undefined, { cause })), + fc.constant(new rpcErrors.ErrorRPCMessageLength(undefined, { cause })), + fc.constant( + new rpcErrors.ErrorPolykeyRemote( + { + command: 'someCommand', + host: `someHost`, + nodeId: IdInternal.fromBuffer(Buffer.alloc(32, 0)), + port: 0, + }, + undefined, + { + cause, + }, + ), + ), ), ); diff --git a/tests/RPC/middleware.test.ts b/tests/rpc/utils/middleware.test.ts similarity index 82% rename from tests/RPC/middleware.test.ts rename to tests/rpc/utils/middleware.test.ts index 754725d71..f3a2a427b 100644 --- a/tests/RPC/middleware.test.ts +++ b/tests/rpc/utils/middleware.test.ts @@ -1,10 +1,10 @@ import { fc, testProp } from '@fast-check/jest'; import { AsyncIterableX as AsyncIterable } from 'ix/asynciterable'; -import * as rpcUtils from '@/RPC/utils'; +import * as rpcUtils from '@/rpc/utils'; import 'ix/add/asynciterable-operators/toarray'; -import * as rpcErrors from '@/RPC/errors'; -import * as middleware from '@/RPC/middleware'; -import * as rpcTestUtils from './utils'; +import * as rpcErrors from '@/rpc/errors'; +import * as middleware from '@/rpc/utils/middleware'; +import * as rpcTestUtils from '../utils'; describe('Middleware tests', () => { const noiseArb = fc @@ -21,7 +21,7 @@ describe('Middleware tests', () => { const parsedStream = rpcTestUtils .messagesToReadableStream(messages) .pipeThrough( - middleware.binaryToJsonMessageStream(rpcUtils.parseJsonRpcMessage), + middleware.binaryToJsonMessageStream(rpcUtils.parseJSONRPCMessage), ); // Converting back. const asd = await AsyncIterable.as(parsedStream).toArray(); @@ -45,7 +45,7 @@ describe('Middleware tests', () => { .pipeThrough(rpcTestUtils.binaryStreamToSnippedStream([10])) .pipeThrough( middleware.binaryToJsonMessageStream( - rpcUtils.parseJsonRpcMessage, + rpcUtils.parseJSONRPCMessage, 50, ), ); @@ -55,7 +55,7 @@ describe('Middleware tests', () => { // No touch, only consume } }; - await expect(doThing()).rejects.toThrow(rpcErrors.ErrorRpcMessageLength); + await expect(doThing()).rejects.toThrow(rpcErrors.ErrorRPCMessageLength); }, { numRuns: 1000 }, ); @@ -67,7 +67,7 @@ describe('Middleware tests', () => { .messagesToReadableStream(messages) .pipeThrough(rpcTestUtils.binaryStreamToSnippedStream(snippattern)) // Imaginary internet here .pipeThrough( - middleware.binaryToJsonMessageStream(rpcUtils.parseJsonRpcMessage), + middleware.binaryToJsonMessageStream(rpcUtils.parseJSONRPCMessage), ); // Converting back. const asd = await AsyncIterable.as(parsedStream).toArray(); @@ -84,11 +84,11 @@ describe('Middleware tests', () => { .pipeThrough(rpcTestUtils.binaryStreamToSnippedStream(snippattern)) // Imaginary internet here .pipeThrough(rpcTestUtils.binaryStreamToNoisyStream(noise)) // Adding bad data to the stream .pipeThrough( - middleware.binaryToJsonMessageStream(rpcUtils.parseJsonRpcMessage), + middleware.binaryToJsonMessageStream(rpcUtils.parseJSONRPCMessage), ); // Converting back. await expect(AsyncIterable.as(parsedStream).toArray()).rejects.toThrow( - rpcErrors.ErrorRpcParse, + rpcErrors.ErrorRPCParse, ); }, { numRuns: 1000 }, diff --git a/tests/RPC/utils.test.ts b/tests/rpc/utils/utils.test.ts similarity index 75% rename from tests/RPC/utils.test.ts rename to tests/rpc/utils/utils.test.ts index 75b09415e..33dfe805f 100644 --- a/tests/RPC/utils.test.ts +++ b/tests/rpc/utils/utils.test.ts @@ -1,16 +1,16 @@ import { testProp } from '@fast-check/jest'; import { AsyncIterableX as AsyncIterable } from 'ix/asynciterable'; -import * as rpcUtils from '@/RPC/utils'; +import * as rpcUtils from '@/rpc/utils'; import 'ix/add/asynciterable-operators/toarray'; -import * as middleware from '@/RPC/middleware'; -import * as rpcTestUtils from './utils'; +import * as middleware from '@/rpc/utils/middleware'; +import * as rpcTestUtils from '../utils'; describe('utils tests', () => { testProp( 'can parse messages', [rpcTestUtils.jsonRpcMessageArb()], async (message) => { - rpcUtils.parseJsonRpcMessage(message); + rpcUtils.parseJSONRPCMessage(message); }, { numRuns: 1000 }, ); @@ -20,13 +20,13 @@ describe('utils tests', () => { [rpcTestUtils.jsonMessagesArb], async (messages) => { const { firstMessageProm, headTransformStream } = - rpcUtils.extractFirstMessageTransform(rpcUtils.parseJsonRpcRequest); + rpcUtils.extractFirstMessageTransform(rpcUtils.parseJSONRPCRequest); const parsedStream = rpcTestUtils .messagesToReadableStream(messages) .pipeThrough(rpcTestUtils.binaryStreamToSnippedStream([7])) .pipeThrough(headTransformStream) .pipeThrough( - middleware.binaryToJsonMessageStream(rpcUtils.parseJsonRpcMessage), + middleware.binaryToJsonMessageStream(rpcUtils.parseJSONRPCMessage), ); // Converting back. expect(await firstMessageProm).toStrictEqual(messages[0]); diff --git a/tests/utils/utils.ts b/tests/utils/utils.ts index a3d7e8ac0..4ebb6844d 100644 --- a/tests/utils/utils.ts +++ b/tests/utils/utils.ts @@ -10,6 +10,7 @@ import readline from 'readline'; import { IdInternal } from '@matrixai/id'; import * as keysUtils from '@/keys/utils'; import * as grpcErrors from '@/grpc/errors'; +import * as rpcErrors from '@/rpc/errors'; import * as validationUtils from '@/validation/utils'; import * as utils from '@/utils'; import * as execUtils from './exec'; @@ -85,11 +86,23 @@ function generateRandomNodeId(): NodeId { return IdInternal.fromString(random); } +const expectRemoteErrorOLD = async ( + promise: Promise, + error, +): Promise => { + await expect(promise).rejects.toThrow(grpcErrors.ErrorPolykeyRemoteOLD); + try { + return await promise; + } catch (e) { + expect(e.cause).toBeInstanceOf(error); + } +}; + const expectRemoteError = async ( promise: Promise, error, ): Promise => { - await expect(promise).rejects.toThrow(grpcErrors.ErrorPolykeyRemote); + await expect(promise).rejects.toThrow(rpcErrors.ErrorPolykeyRemote); try { return await promise; } catch (e) { @@ -162,6 +175,7 @@ async function createTLSConfigWithChain( export { setupTestAgent, generateRandomNodeId, + expectRemoteErrorOLD, expectRemoteError, testIf, describeIf, diff --git a/tests/vaults/VaultManager.test.ts b/tests/vaults/VaultManager.test.ts index 539357ca2..7f0d36cf7 100644 --- a/tests/vaults/VaultManager.test.ts +++ b/tests/vaults/VaultManager.test.ts @@ -748,7 +748,7 @@ describe('VaultManager', () => { 'pull', ); - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( vaultManager.cloneVault( remoteKeynode1Id, 'not-existing' as VaultName, @@ -836,7 +836,7 @@ describe('VaultManager', () => { }); try { // Should reject with no permissions set - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( vaultManager.cloneVault(remoteKeynode1Id, remoteVaultId), vaultsErrors.ErrorVaultsPermissionDenied, ); @@ -878,7 +878,7 @@ describe('VaultManager', () => { remoteVaultId, ); - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( vaultManager.pullVault({ vaultId: clonedVaultId }), vaultsErrors.ErrorVaultsPermissionDenied, ); @@ -1565,7 +1565,7 @@ describe('VaultManager', () => { // Should throw } }; - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( testFun(), vaultsErrors.ErrorVaultsPermissionDenied, ); @@ -1574,7 +1574,7 @@ describe('VaultManager', () => { ['node', nodeId1], 'notify', ); - await testUtils.expectRemoteError( + await testUtils.expectRemoteErrorOLD( testFun(), vaultsErrors.ErrorVaultsPermissionDenied, ); diff --git a/tests/websockets/WebSocket.test.ts b/tests/websockets/WebSocket.test.ts index d098d60e8..a43f3d3f2 100644 --- a/tests/websockets/WebSocket.test.ts +++ b/tests/websockets/WebSocket.test.ts @@ -94,10 +94,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -129,10 +129,10 @@ describe('WebSocket', () => { host: '::1', logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host: '::1', - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -164,10 +164,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -195,10 +195,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -273,10 +273,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -326,10 +326,10 @@ describe('WebSocket', () => { maxReadBufferBytes: 1500, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -365,7 +365,7 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); const testProcess = await testsUtils.spawn( 'ts-node', @@ -377,7 +377,7 @@ describe('WebSocket', () => { { env: { PK_TEST_HOST: host, - PK_TEST_PORT: `${webSocketServer.port}`, + PK_TEST_PORT: `${webSocketServer.getPort()}`, PK_TEST_NODE_ID: nodesUtils.encodeNodeId(keyRing.getNodeId()), }, }, @@ -488,10 +488,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -527,10 +527,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -564,10 +564,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -592,10 +592,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -633,10 +633,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); const getResProm = promise(); https.get( - `https://${host}:${webSocketServer.port}/`, + `https://${host}:${webSocketServer.getPort()}/`, { rejectUnauthorized: false }, getResProm.resolveP, ); @@ -664,10 +664,10 @@ describe('WebSocket', () => { pingTimeout: 100, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -690,10 +690,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], logger: logger.getChild('clientClient'), }); @@ -734,10 +734,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [invalidNodeId], logger: logger.getChild('clientClient'), }); @@ -773,10 +773,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [nodeId], logger: logger.getChild('clientClient'), }); @@ -803,10 +803,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId(), alternativeNodeId], logger: logger.getChild('clientClient'), }); @@ -843,10 +843,10 @@ describe('WebSocket', () => { host, logger: logger.getChild('server'), }); - logger.info(`Server started on port ${webSocketServer.port}`); + logger.info(`Server started on port ${webSocketServer.getPort()}`); webSocketClient = await WebSocketClient.createWebSocketClient({ host, - port: webSocketServer.port, + port: webSocketServer.getPort(), expectedNodeIds: [keyRing.getNodeId()], pingTimeout: 100, logger: logger.getChild('clientClient'), diff --git a/tests/websockets/testServer.ts b/tests/websockets/testServer.ts index 0a7aac880..3cad3b284 100644 --- a/tests/websockets/testServer.ts +++ b/tests/websockets/testServer.ts @@ -26,7 +26,7 @@ async function main() { tlsConfig, logger, }); - process.stdout.write(`${clientServer.port}`); + process.stdout.write(`${clientServer.getPort()}`); } if (require.main === module) {