diff --git a/src/acl/ACL.ts b/src/acl/ACL.ts index 3f82eefb4..f1f551059 100644 --- a/src/acl/ACL.ts +++ b/src/acl/ACL.ts @@ -1,20 +1,23 @@ -import type { Permission, VaultActions, PermissionIdString } from './types'; +import type { + PermissionId, + PermissionIdString, + Permission, + VaultActions, +} from './types'; import type { DB, DBLevel, DBOp } from '@matrixai/db'; import type { NodeId } from '../nodes/types'; import type { GestaltAction } from '../gestalts/types'; import type { VaultAction, VaultId } from '../vaults/types'; import type { Ref } from '../types'; - import { Mutex } from 'async-mutex'; import Logger from '@matrixai/logger'; -import { IdInternal, utils as idUtils } from '@matrixai/id'; +import { IdInternal } from '@matrixai/id'; import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import * as aclUtils from './utils'; import * as aclErrors from './errors'; -import { makePermissionId } from './utils'; interface ACL extends CreateDestroyStartStop {} @CreateDestroyStartStop( @@ -49,6 +52,7 @@ class ACL { protected aclNodesDb: DBLevel; protected aclVaultsDb: DBLevel; protected lock: Mutex = new Mutex(); + protected generatePermId: () => PermissionId; constructor({ db, logger }: { db: DB; logger: Logger }) { this.logger = logger; @@ -81,6 +85,7 @@ class ACL { this.aclPermsDb = aclPermsDb; this.aclNodesDb = aclNodesDb; this.aclVaultsDb = aclVaultsDb; + this.generatePermId = aclUtils.createPermIdGenerator(); this.logger.info(`Started ${this.constructor.name}`); } @@ -155,9 +160,9 @@ class ACL { Record > = {}; for await (const o of this.aclNodesDb.createReadStream()) { - const nodeId = IdInternal.create((o as any).key); + const nodeId = IdInternal.fromBuffer((o as any).key); const data = (o as any).value as Buffer; - const permId = makePermissionId( + const permId = IdInternal.fromBuffer( await this.db.deserializeDecrypt(data, true), ); let nodePerm: Record; @@ -174,7 +179,7 @@ class ACL { } else { const permRef = (await this.db.get( this.aclPermsDbDomain, - idUtils.toBuffer(permId), + permId.toBuffer(), )) as Ref; nodePerm = { [nodeId]: permRef.object }; permIds[permId] = nodePerm; @@ -235,7 +240,7 @@ class ACL { ops.push({ type: 'put', domain: this.aclVaultsDbDomain, - key: idUtils.toBuffer(vaultId), + key: vaultId.toBuffer(), value: nodeIds, }); } @@ -281,7 +286,7 @@ class ACL { return await this._transaction(async () => { const nodeIds = await this.db.get>( this.aclVaultsDbDomain, - idUtils.toBuffer(vaultId), + vaultId.toBuffer(), ); if (nodeIds == null) { return {}; @@ -316,11 +321,7 @@ class ACL { for (const nodeId of nodeIdsGc) { delete nodeIds[nodeId]; } - await this.db.put( - this.aclVaultsDbDomain, - idUtils.toBuffer(vaultId), - nodeIds, - ); + await this.db.put(this.aclVaultsDbDomain, vaultId.toBuffer(), nodeIds); } return perms; }); @@ -339,7 +340,7 @@ class ACL { ); const ops: Array = []; if (permId == null) { - const permId = await aclUtils.generatePermId(); + const permId = await this.generatePermId(); const permRef = { count: 1, object: { @@ -353,14 +354,14 @@ class ACL { { type: 'put', domain: this.aclPermsDbDomain, - key: idUtils.toBuffer(permId), + key: permId.toBuffer(), value: permRef, }, { type: 'put', domain: this.aclNodesDbDomain, key: nodeId.toBuffer(), - value: idUtils.toBuffer(permId), + value: permId.toBuffer(), raw: true, }, ); @@ -414,7 +415,7 @@ class ACL { const nodeIds = (await this.db.get>( this.aclVaultsDbDomain, - idUtils.toBuffer(vaultId), + vaultId.toBuffer(), )) ?? {}; const permId = await this.db.get( this.aclNodesDbDomain, @@ -452,7 +453,7 @@ class ACL { { type: 'put', domain: this.aclVaultsDbDomain, - key: idUtils.toBuffer(vaultId), + key: vaultId.toBuffer(), value: nodeIds, }, ]; @@ -469,7 +470,7 @@ class ACL { await this._transaction(async () => { const nodeIds = await this.db.get>( this.aclVaultsDbDomain, - idUtils.toBuffer(vaultId), + vaultId.toBuffer(), ); if (nodeIds == null || !(nodeId in nodeIds)) { return; @@ -527,32 +528,32 @@ class ACL { if (permIdBuffer == null) { continue; } - const permId = makePermissionId(permIdBuffer); + const permId = IdInternal.fromBuffer(permIdBuffer); permIdCounts[permId] = (permIdCounts[permId] ?? 0) + 1; } for (const permIdString in permIdCounts) { - const permId = makePermissionId(idUtils.fromString(permIdString)); + const permId = IdInternal.fromString(permIdString); const permRef = (await this.db.get( this.aclPermsDbDomain, - idUtils.toBuffer(permId), + permId.toBuffer(), )) as Ref; permRef.count = permRef.count - permIdCounts[permId]; if (permRef.count === 0) { ops.push({ type: 'del', domain: this.aclPermsDbDomain, - key: idUtils.toBuffer(permId), + key: permId.toBuffer(), }); } else { ops.push({ type: 'put', domain: this.aclPermsDbDomain, - key: idUtils.toBuffer(permId), + key: permId.toBuffer(), value: permRef, }); } } - const permId = await aclUtils.generatePermId(); + const permId = await this.generatePermId(); const permRef = { count: nodeIds.length, object: perm, @@ -560,7 +561,7 @@ class ACL { ops.push({ domain: this.aclPermsDbDomain, type: 'put', - key: idUtils.toBuffer(permId), + key: permId.toBuffer(), value: permRef, }); for (const nodeId of nodeIds) { @@ -568,7 +569,7 @@ class ACL { domain: this.aclNodesDbDomain, type: 'put', key: nodeId.toBuffer(), - value: idUtils.toBuffer(permId), + value: permId.toBuffer(), raw: true, }); } @@ -595,7 +596,7 @@ class ACL { ); const ops: Array = []; if (permId == null) { - const permId = await aclUtils.generatePermId(); + const permId = await this.generatePermId(); const permRef = { count: 1, object: perm, @@ -604,14 +605,14 @@ class ACL { { type: 'put', domain: this.aclPermsDbDomain, - key: idUtils.toBuffer(permId), + key: permId.toBuffer(), value: permRef, }, { type: 'put', domain: this.aclNodesDbDomain, key: nodeId.toBuffer(), - value: idUtils.toBuffer(permId), + value: permId.toBuffer(), raw: true, }, ); @@ -685,7 +686,7 @@ class ACL { await this._transaction(async () => { const nodeIds = await this.db.get>( this.aclVaultsDbDomain, - idUtils.toBuffer(vaultId), + vaultId.toBuffer(), ); if (nodeIds == null) { return; @@ -718,7 +719,7 @@ class ACL { ops.push({ type: 'del', domain: this.aclVaultsDbDomain, - key: idUtils.toBuffer(vaultId), + key: vaultId.toBuffer(), }); await this.db.batch(ops); }); @@ -825,7 +826,7 @@ class ACL { ): Promise> { const nodeIds = await this.db.get>( this.aclVaultsDbDomain, - idUtils.toBuffer(vaultId), + vaultId.toBuffer(), ); if (nodeIds == null) { throw new aclErrors.ErrorACLVaultIdMissing(); @@ -869,7 +870,7 @@ class ACL { ops.push({ type: 'put', domain: this.aclVaultsDbDomain, - key: idUtils.toBuffer(vaultIdJoin), + key: vaultIdJoin.toBuffer(), value: nodeIds, }); } @@ -881,7 +882,7 @@ class ACL { ops.push({ type: 'put', domain: this.aclVaultsDbDomain, - key: idUtils.toBuffer(vaultId), + key: vaultId.toBuffer(), value: nodeIds, }); } diff --git a/src/acl/types.ts b/src/acl/types.ts index 14df5f868..92ae07d13 100644 --- a/src/acl/types.ts +++ b/src/acl/types.ts @@ -1,11 +1,10 @@ import type { Opaque } from '../types'; import type { GestaltAction } from '../gestalts/types'; import type { VaultActions, VaultId } from '../vaults/types'; -import type { Id, IdString } from '../GenericIdTypes'; +import type { Id } from '@matrixai/id'; type PermissionId = Opaque<'PermissionId', Id>; - -type PermissionIdString = Opaque<'PermissionIdString', IdString>; +type PermissionIdString = Opaque<'PermissionIdString', string>; type Permission = { gestalt: GestaltActions; diff --git a/src/acl/utils.ts b/src/acl/utils.ts index 0dc3f32f6..f5f81e0a0 100644 --- a/src/acl/utils.ts +++ b/src/acl/utils.ts @@ -1,27 +1,9 @@ -import type { Permission, PermissionId, PermissionIdString } from './types'; - +import type { Permission, PermissionId } from './types'; import { IdRandom } from '@matrixai/id'; -import { isIdString, isId, makeIdString, makeId } from '../GenericIdTypes'; - -function isPermissionId(arg: any): arg is PermissionId { - return isId(arg); -} - -function makePermissionId(arg: any) { - return makeId(arg); -} - -function isPermissionIdString(arg: any): arg is PermissionIdString { - return isIdString(arg); -} - -function makePermissionIdString(arg: any) { - return makeIdString(arg); -} -const randomIdGenerator = new IdRandom(); -async function generatePermId(): Promise { - return makePermissionId(randomIdGenerator.get()); +function createPermIdGenerator() { + const generator = new IdRandom(); + return () => generator.get(); } function permUnion(perm1: Permission, perm2: Permission): Permission { @@ -44,11 +26,4 @@ function permUnion(perm1: Permission, perm2: Permission): Permission { return perm; } -export { - generatePermId, - permUnion, - isPermissionId, - makePermissionId, - isPermissionIdString, - makePermissionIdString, -}; +export { createPermIdGenerator, permUnion }; diff --git a/src/agent/service/nodesChainDataGet.ts b/src/agent/service/nodesChainDataGet.ts index 0f4c201c7..0c471e399 100644 --- a/src/agent/service/nodesChainDataGet.ts +++ b/src/agent/service/nodesChainDataGet.ts @@ -1,5 +1,5 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ClaimIdString } from '../../claims/types'; +import type { ClaimIdEncoded } from '../../claims/types'; import type { NodeManager } from '../../nodes'; import type * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; import { utils as grpcUtils } from '../../grpc'; @@ -13,12 +13,12 @@ function nodesChainDataGet({ nodeManager }: { nodeManager: NodeManager }) { call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new nodesPB.ChainData(); try { + const response = new nodesPB.ChainData(); const chainData = await nodeManager.getChainData(); // Iterate through each claim in the chain, and serialize for transport for (const c in chainData) { - const claimId = c as ClaimIdString; + const claimId = c as ClaimIdEncoded; const claim = chainData[claimId]; const claimMessage = new nodesPB.AgentClaim(); // Will always have a payload (never undefined) so cast as string @@ -34,10 +34,12 @@ function nodesChainDataGet({ nodeManager }: { nodeManager: NodeManager }) { // Add the serialized claim response.getChainDataMap().set(claimId, claimMessage); } - } catch (err) { - callback(grpcUtils.fromError(err), response); + callback(null, response); + return; + } catch (e) { + callback(grpcUtils.fromError(e)); + return; } - callback(null, response); }; } diff --git a/src/agent/service/nodesClosestLocalNodesGet.ts b/src/agent/service/nodesClosestLocalNodesGet.ts index 5c643bbe0..534d3e011 100644 --- a/src/agent/service/nodesClosestLocalNodesGet.ts +++ b/src/agent/service/nodesClosestLocalNodesGet.ts @@ -1,7 +1,10 @@ import type * as grpc from '@grpc/grpc-js'; import type { NodeManager } from '../../nodes'; +import type { NodeId } from '../../nodes/types'; import { utils as grpcUtils } from '../../grpc'; import { utils as nodesUtils } from '../../nodes'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; /** @@ -17,11 +20,25 @@ function nodesClosestLocalNodesGet({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new nodesPB.NodeTable(); try { - const targetNodeId = nodesUtils.decodeNodeId(call.request.getNodeId()); + const response = new nodesPB.NodeTable(); + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: call.request.getNodeId(), + }, + ); // Get all local nodes that are closest to the target node from the request - const closestNodes = await nodeManager.getClosestLocalNodes(targetNodeId); + const closestNodes = await nodeManager.getClosestLocalNodes(nodeId); for (const node of closestNodes) { const addressMessage = new nodesPB.Address(); addressMessage.setHost(node.address.host); @@ -31,10 +48,12 @@ function nodesClosestLocalNodesGet({ .getNodeTableMap() .set(nodesUtils.encodeNodeId(node.id), addressMessage); } - } catch (err) { - callback(grpcUtils.fromError(err), response); + callback(null, response); + return; + } catch (e) { + callback(grpcUtils.fromError(e)); + return; } - callback(null, response); }; } diff --git a/src/agent/service/nodesCrossSignClaim.ts b/src/agent/service/nodesCrossSignClaim.ts index 729bd6064..64529c551 100644 --- a/src/agent/service/nodesCrossSignClaim.ts +++ b/src/agent/service/nodesCrossSignClaim.ts @@ -1,12 +1,15 @@ import type * as grpc from '@grpc/grpc-js'; import type { ClaimEncoded, ClaimIntermediary } from '../../claims/types'; import type { NodeManager } from '../../nodes'; +import type { NodeId } from '../../nodes/types'; import type { Sigchain } from '../../sigchain'; import type { KeyManager } from '../../keys'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import { utils as grpcUtils } from '../../grpc'; import { utils as claimsUtils, errors as claimsErrors } from '../../claims'; import { utils as nodesUtils } from '../../nodes'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; function nodesCrossSignClaim({ keyManager, @@ -65,10 +68,23 @@ function nodesCrossSignClaim({ if (payloadData.type !== 'node') { throw new claimsErrors.ErrorNodesClaimType(); } - // Verify the claim - const senderPublicKey = await nodeManager.getPublicKey( - nodesUtils.decodeNodeId(payloadData.node1), + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: payloadData.node1, + }, ); + // Verify the claim + const senderPublicKey = await nodeManager.getPublicKey(nodeId); const verified = await claimsUtils.verifyClaimSignature( constructedEncodedClaim, senderPublicKey, @@ -141,11 +157,13 @@ function nodesCrossSignClaim({ await sigchain.addExistingClaim(constructedDoublySignedClaim); // Close the stream await genClaims.next(null); + return; }); } catch (e) { await genClaims.throw(e); // TODO: Handle the exception on this server - throw e? // throw e; + return; } }; } diff --git a/src/agent/service/nodesHolePunchMessageSend.ts b/src/agent/service/nodesHolePunchMessageSend.ts index 2668bfa41..afeea0af8 100644 --- a/src/agent/service/nodesHolePunchMessageSend.ts +++ b/src/agent/service/nodesHolePunchMessageSend.ts @@ -1,9 +1,11 @@ import type * as grpc from '@grpc/grpc-js'; import type { NodeManager } from '../../nodes'; +import type { NodeId } from '../../nodes/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import { utils as networkUtils } from '../../network'; import { utils as grpcUtils } from '../../grpc'; -import { utils as nodesUtils } from '../../nodes'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function nodesHolePunchMessageSend({ @@ -15,32 +17,49 @@ function nodesHolePunchMessageSend({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); + const { + targetId, + sourceId, + }: { + targetId: NodeId; + sourceId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [ + ['targetId'], + ['sourceId'], + () => validationUtils.parseNodeId(value), + ], + () => value, + ); + }, + { + targetId: call.request.getTargetId(), + sourceId: call.request.getSrcId(), + }, + ); // Firstly, check if this node is the desired node // If so, then we want to make this node start sending hole punching packets // back to the source node. - if ( - nodeManager.getNodeId() === - nodesUtils.decodeNodeId(call.request.getTargetId()) - ) { + if (nodeManager.getNodeId() === targetId) { const [host, port] = networkUtils.parseAddress( call.request.getEgressAddress(), ); await nodeManager.openConnection(host, port); // Otherwise, find if node in table // If so, ask the nodeManager to relay to the node - } else if ( - await nodeManager.knowsNode( - nodesUtils.decodeNodeId(call.request.getSrcId()), - ) - ) { + } else if (await nodeManager.knowsNode(sourceId)) { await nodeManager.relayHolePunchMessage(call.request); } - } catch (err) { - callback(grpcUtils.fromError(err), response); + callback(null, response); + return; + } catch (e) { + callback(grpcUtils.fromError(e)); + return; } - callback(null, response); }; } diff --git a/src/agent/service/notificationsSend.ts b/src/agent/service/notificationsSend.ts index 3631d8af9..cf2b589ea 100644 --- a/src/agent/service/notificationsSend.ts +++ b/src/agent/service/notificationsSend.ts @@ -2,10 +2,7 @@ import type * as grpc from '@grpc/grpc-js'; import type { NotificationsManager } from '../../notifications'; import type * as notificationsPB from '../../proto/js/polykey/v1/notifications/notifications_pb'; import { utils as grpcUtils } from '../../grpc'; -import { - utils as notificationsUtils, - errors as notificationsErrors, -} from '../../notifications'; +import { utils as notificationsUtils } from '../../notifications'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function notificationsSend({ @@ -20,19 +17,17 @@ function notificationsSend({ >, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const jwt = call.request.getContent(); const notification = await notificationsUtils.verifyAndDecodeNotif(jwt); await notificationsManager.receiveNotification(notification); - } catch (err) { - if (err instanceof notificationsErrors.ErrorNotifications) { - callback(grpcUtils.fromError(err), response); - } else { - throw err; - } + callback(null, response); + return; + } catch (e) { + callback(grpcUtils.fromError(e)); + return; } - callback(null, response); }; } diff --git a/src/agent/service/vaultsPermissionsCheck.ts b/src/agent/service/vaultsPermissionsCheck.ts index 8b3046f06..47c61d77e 100644 --- a/src/agent/service/vaultsPermissionsCheck.ts +++ b/src/agent/service/vaultsPermissionsCheck.ts @@ -26,8 +26,8 @@ function vaultsPermissionsCheck(_) { // response.setPermission(true); // } // callback(null, response); - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); } }; } diff --git a/src/bin/identities/CommandAllow.ts b/src/bin/identities/CommandAllow.ts index dc1fada57..ca6dfc5b7 100644 --- a/src/bin/identities/CommandAllow.ts +++ b/src/bin/identities/CommandAllow.ts @@ -1,8 +1,9 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; -import * as parsers from '../utils/parsers'; +import * as binParsers from '../utils/parsers'; import * as binProcessors from '../utils/processors'; class CommandAllow extends CommandPolykey { @@ -12,14 +13,14 @@ class CommandAllow extends CommandPolykey { this.description('Allow Permission for Identity'); this.argument( '', - 'Node ID or `Provider Id:Identity Id`', - parsers.parseGestaltId, + 'Node ID or `Provider ID:Identity ID`', + binParsers.parseGestaltId, ); this.argument('', 'permission to set'); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (gestaltId, permissions, options) => { + this.action(async (gestaltId: GestaltId, permissions, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const identitiesPB = await import( '../../proto/js/polykey/v1/identities/identities_pb' @@ -54,7 +55,7 @@ class CommandAllow extends CommandPolykey { }); const setActionMessage = new permissionsPB.ActionSet(); setActionMessage.setAction(permissions); - if (gestaltId.nodeId) { + if (gestaltId.type === 'node') { // Setting by Node const nodeMessage = new nodesPB.Node(); nodeMessage.setNodeId(gestaltId.nodeId); diff --git a/src/bin/identities/CommandDisallow.ts b/src/bin/identities/CommandDisallow.ts index 6f2889bde..d1115c30b 100644 --- a/src/bin/identities/CommandDisallow.ts +++ b/src/bin/identities/CommandDisallow.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -19,7 +20,7 @@ class CommandDisallow extends CommandPolykey { this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (gestaltId, permissions, options) => { + this.action(async (gestaltId: GestaltId, permissions, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const identitiesPB = await import( '../../proto/js/polykey/v1/identities/identities_pb' @@ -54,8 +55,8 @@ class CommandDisallow extends CommandPolykey { }); const setActionMessage = new permissionsPB.ActionSet(); setActionMessage.setAction(permissions); - if (gestaltId.nodeId) { - // Setting by Node. + if (gestaltId.type === 'node') { + // Setting by Node const nodeMessage = new nodesPB.Node(); nodeMessage.setNodeId(gestaltId.nodeId); setActionMessage.setNode(nodeMessage); diff --git a/src/bin/identities/CommandDiscover.ts b/src/bin/identities/CommandDiscover.ts index dea63c9cb..9d08ca105 100644 --- a/src/bin/identities/CommandDiscover.ts +++ b/src/bin/identities/CommandDiscover.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -14,13 +15,13 @@ class CommandDiscover extends CommandPolykey { ); this.argument( '', - 'Node ID or `Provider Id:Identity Id`', + 'Node ID or `Provider ID:Identity ID`', parsers.parseGestaltId, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (gestaltId, options) => { + this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const identitiesPB = await import( '../../proto/js/polykey/v1/identities/identities_pb' @@ -50,8 +51,8 @@ class CommandDiscover extends CommandPolykey { port: clientOptions.clientPort, logger: this.logger.getChild(PolykeyClient.name), }); - if (gestaltId.nodeId != null) { - // Discovery by Node. + if (gestaltId.type === 'node') { + // Discovery by Node const nodeMessage = new nodesPB.Node(); nodeMessage.setNodeId(gestaltId.nodeId); await binUtils.retryAuthentication( diff --git a/src/bin/identities/CommandGet.ts b/src/bin/identities/CommandGet.ts index 1b82616ad..38fca462f 100644 --- a/src/bin/identities/CommandGet.ts +++ b/src/bin/identities/CommandGet.ts @@ -1,5 +1,6 @@ -import type gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; import type PolykeyClient from '../../PolykeyClient'; +import type { GestaltId } from '../../gestalts/types'; +import type gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -15,13 +16,13 @@ class CommandGet extends CommandPolykey { ); this.argument( '', - 'Node ID or `Provider Id:Identity Id`', + 'Node ID or `Provider ID:Identity ID`', parsers.parseGestaltId, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (gestaltId, options) => { + this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const identitiesPB = await import( '../../proto/js/polykey/v1/identities/identities_pb' @@ -52,8 +53,8 @@ class CommandGet extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); let res: gestaltsPB.Graph; - if (gestaltId.nodeId) { - // Getting from node. + if (gestaltId.type === 'node') { + // Getting from node const nodeMessage = new nodesPB.Node(); nodeMessage.setNodeId(gestaltId.nodeId); res = await binUtils.retryAuthentication( diff --git a/src/bin/identities/CommandPermissions.ts b/src/bin/identities/CommandPermissions.ts index 96a1839e3..365b5f2ef 100644 --- a/src/bin/identities/CommandPermissions.ts +++ b/src/bin/identities/CommandPermissions.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -12,13 +13,13 @@ class CommandPermissions extends CommandPolykey { this.description('Gets the Permissions for a Node or Identity'); this.argument( '', - 'Node ID or `Provider Id:Identity Id`', + 'Node ID or `Provider ID:Identity ID`', parsers.parseGestaltId, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (gestaltId, options) => { + this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const identitiesPB = await import( '../../proto/js/polykey/v1/identities/identities_pb' @@ -49,8 +50,8 @@ class CommandPermissions extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); let actions: string[] = []; - if (gestaltId.nodeId) { - // Getting by Node. + if (gestaltId.type === 'node') { + // Getting by Node const nodeMessage = new nodesPB.Node(); nodeMessage.setNodeId(gestaltId.nodeId); const res = await binUtils.retryAuthentication( diff --git a/src/bin/identities/CommandTrust.ts b/src/bin/identities/CommandTrust.ts index 9499e5924..284f32de7 100644 --- a/src/bin/identities/CommandTrust.ts +++ b/src/bin/identities/CommandTrust.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -12,13 +13,13 @@ class CommandTrust extends CommandPolykey { this.description('Trust a Keynode or Identity'); this.argument( '', - 'Node ID or `Provider Id:Identity Id`', + 'Node ID or `Provider ID:Identity ID`', parsers.parseGestaltId, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (gestaltId, options) => { + this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const identitiesPB = await import( '../../proto/js/polykey/v1/identities/identities_pb' @@ -54,8 +55,8 @@ class CommandTrust extends CommandPolykey { const action = 'notify'; const setActionMessage = new permissionsPB.ActionSet(); setActionMessage.setAction(action); - if (gestaltId.nodeId) { - // Setting by Node. + if (gestaltId.type === 'node') { + // Setting by Node const nodeMessage = new nodesPB.Node(); nodeMessage.setNodeId(gestaltId.nodeId); setActionMessage.setNode(nodeMessage); diff --git a/src/bin/identities/CommandUntrust.ts b/src/bin/identities/CommandUntrust.ts index 53e45e997..ac7ad3a73 100644 --- a/src/bin/identities/CommandUntrust.ts +++ b/src/bin/identities/CommandUntrust.ts @@ -1,4 +1,5 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { GestaltId } from '../../gestalts/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; @@ -12,13 +13,13 @@ class CommandUntrust extends CommandPolykey { this.description('Untrust a Keynode or Identity'); this.argument( '', - 'Node ID or `Provider Id:Identity Id`', + 'Node ID or `Provider ID:Identity ID`', parsers.parseGestaltId, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (gestaltId, options) => { + this.action(async (gestaltId: GestaltId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const identitiesPB = await import( '../../proto/js/polykey/v1/identities/identities_pb' @@ -54,7 +55,7 @@ class CommandUntrust extends CommandPolykey { const action = 'notify'; const setActionMessage = new permissionsPB.ActionSet(); setActionMessage.setAction(action); - if (gestaltId.nodeId) { + if (gestaltId.type === 'node') { // Setting by Node. const nodeMessage = new nodesPB.Node(); nodeMessage.setNodeId(gestaltId.nodeId); diff --git a/src/bin/nodes/CommandAdd.ts b/src/bin/nodes/CommandAdd.ts index 6cbdc0115..fdf49f48e 100644 --- a/src/bin/nodes/CommandAdd.ts +++ b/src/bin/nodes/CommandAdd.ts @@ -1,22 +1,26 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { NodeId } from '../../nodes/types'; +import type { Host, Port } from '../../network/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils/utils'; import * as binProcessors from '../utils/processors'; import * as binOptions from '../utils/options'; +import * as binParsers from '../utils/parsers'; class CommandAdd extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('add'); this.description('Add a Node to the Node Graph'); - this.argument('', 'Id of the node to add'); - this.argument('', 'Address of the node'); - this.argument('', 'Port of the node'); + this.argument('', 'Id of the node to add', binParsers.parseNodeId); + this.argument('', 'Address of the node', binParsers.parseHost); + this.argument('', 'Port of the node', binParsers.parsePort); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (nodeId, host, port, options) => { + this.action(async (nodeId: NodeId, host: Host, port: Port, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); + const nodesUtils = await import('../../nodes/utils'); const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -43,7 +47,7 @@ class CommandAdd extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); const nodeAddressMessage = new nodesPB.NodeAddress(); - nodeAddressMessage.setNodeId(nodeId); + nodeAddressMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); nodeAddressMessage.setAddress( new nodesPB.Address().setHost(host).setPort(port), ); diff --git a/src/bin/nodes/CommandClaim.ts b/src/bin/nodes/CommandClaim.ts index ca7408aee..06bb2fc45 100644 --- a/src/bin/nodes/CommandClaim.ts +++ b/src/bin/nodes/CommandClaim.ts @@ -1,15 +1,21 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { NodeId } from '../../nodes/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandClaim extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('claim'); this.description('Claim another Keynode'); - this.argument('', 'Id of the node to claim'); + this.argument( + '', + 'Id of the node to claim', + binParsers.parseNodeId, + ); this.option( '-f, --force-invite', '(optional) Flag to force a Gestalt Invitation to be sent rather than a node claim.', @@ -17,8 +23,9 @@ class CommandClaim extends CommandPolykey { this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (nodeId, options) => { + this.action(async (nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); + const nodesUtils = await import('../../nodes/utils'); const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -45,7 +52,7 @@ class CommandClaim extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); const nodeClaimMessage = new nodesPB.Claim(); - nodeClaimMessage.setNodeId(nodeId); + nodeClaimMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); if (options.forceInvite) { nodeClaimMessage.setForceInvite(true); } else { @@ -61,7 +68,9 @@ class CommandClaim extends CommandPolykey { binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', data: [ - `Successfully generated a cryptolink claim on Keynode with ID ${nodeId}`, + `Successfully generated a cryptolink claim on Keynode with ID ${nodesUtils.encodeNodeId( + nodeId, + )}`, ], }), ); @@ -70,7 +79,9 @@ class CommandClaim extends CommandPolykey { binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', data: [ - `Successfully sent Gestalt Invite notification to Keynode with ID ${nodeId}`, + `Successfully sent Gestalt Invite notification to Keynode with ID ${nodesUtils.encodeNodeId( + nodeId, + )}`, ], }), ); diff --git a/src/bin/nodes/CommandFind.ts b/src/bin/nodes/CommandFind.ts index 61d7c3cd5..09150b5dd 100644 --- a/src/bin/nodes/CommandFind.ts +++ b/src/bin/nodes/CommandFind.ts @@ -1,26 +1,29 @@ -import type { Host, Port } from '../../network/types'; - import type PolykeyClient from '../../PolykeyClient'; +import type { NodeId } from '../../nodes/types'; +import type { Host, Port } from '../../network/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; +import * as binErrors from '../errors'; class CommandFind extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('find'); this.description('Attempt to Find a Node'); - this.argument('', 'Id of the node to find'); + this.argument('', 'Id of the node to find', binParsers.parseNodeId); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (nodeId, options) => { + this.action(async (nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); + const nodesUtils = await import('../../nodes/utils'); const networkUtils = await import('../../network/utils'); - const CLIErrors = await import('../errors'); const nodesErrors = await import('../../nodes/errors'); + const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -46,7 +49,7 @@ class CommandFind extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodeId); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); const result = { success: false, message: '', @@ -72,7 +75,7 @@ class CommandFind extends CommandPolykey { throw err; // Else failed to find the node. result.success = false; - result.id = nodeId; + result.id = nodesUtils.encodeNodeId(nodeId); result.host = ''; result.port = 0; result.message = `Failed to find node ${result.id}`; @@ -87,7 +90,7 @@ class CommandFind extends CommandPolykey { ); // Like ping it should error when failing to find node for automation reasons. if (!result.success) - throw new CLIErrors.ErrorNodeFindFailed(result.message); + throw new binErrors.ErrorNodeFindFailed(result.message); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/bin/nodes/CommandPing.ts b/src/bin/nodes/CommandPing.ts index 387abaca4..997ddaeb7 100644 --- a/src/bin/nodes/CommandPing.ts +++ b/src/bin/nodes/CommandPing.ts @@ -1,23 +1,26 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { NodeId } from '../../nodes/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; +import * as binErrors from '../errors'; class CommandPing extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('ping'); this.description("Ping a Node to check if it's Online"); - this.argument('', 'Id of the node to ping'); + this.argument('', 'Id of the node to ping', binParsers.parseNodeId); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (nodeId, options) => { + this.action(async (nodeId: NodeId, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); - const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); - const CLIErrors = await import('../errors'); + const nodesUtils = await import('../../nodes/utils'); const nodesErrors = await import('../../nodes/errors'); + const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -43,7 +46,7 @@ class CommandPing extends CommandPolykey { logger: this.logger.getChild(PolykeyClient.name), }); const nodeMessage = new nodesPB.Node(); - nodeMessage.setNodeId(nodeId); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); let statusMessage; let error; try { @@ -53,8 +56,10 @@ class CommandPing extends CommandPolykey { ); } catch (err) { if (err instanceof nodesErrors.ErrorNodeGraphNodeNotFound) { - error = new CLIErrors.ErrorNodePingFailed( - `Failed to resolve node ID ${nodeId} to an address.`, + error = new binErrors.ErrorNodePingFailed( + `Failed to resolve node ID ${nodesUtils.encodeNodeId( + nodeId, + )} to an address.`, ); } else { throw err; @@ -63,7 +68,7 @@ class CommandPing extends CommandPolykey { const status = { success: false, message: '' }; status.success = statusMessage ? statusMessage.getSuccess() : false; if (!status.success && !error) - error = new CLIErrors.ErrorNodePingFailed('No response received'); + error = new binErrors.ErrorNodePingFailed('No response received'); if (status.success) status.message = 'Node is Active.'; else status.message = error.message; const output: any = diff --git a/src/bin/notifications/CommandSend.ts b/src/bin/notifications/CommandSend.ts index 40bc72104..d40d72947 100644 --- a/src/bin/notifications/CommandSend.ts +++ b/src/bin/notifications/CommandSend.ts @@ -1,21 +1,28 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { NodeId } from '../../nodes/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandSend extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('send'); this.description('Send a Notification with a Message to another Node'); - this.argument('', 'Id of the node to send a message to'); + this.argument( + '', + 'Id of the node to send a message to', + binParsers.parseNodeId, + ); this.argument('', 'Message to send'); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (node, message, options) => { + 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' ); @@ -46,7 +53,7 @@ class CommandSend extends CommandPolykey { const notificationsSendMessage = new notificationsPB.Send(); const generalMessage = new notificationsPB.General(); generalMessage.setMessage(message); - notificationsSendMessage.setReceiverId(node); + notificationsSendMessage.setReceiverId(nodesUtils.encodeNodeId(nodeId)); notificationsSendMessage.setData(generalMessage); await binUtils.retryAuthentication( (auth) => diff --git a/src/bin/utils/index.ts b/src/bin/utils/index.ts index e875e06bb..4555c33f8 100644 --- a/src/bin/utils/index.ts +++ b/src/bin/utils/index.ts @@ -1,4 +1,5 @@ export * from './utils'; export * as options from './options'; export * as parsers from './parsers'; +export * as processors from './processors'; export { default as ExitHandlers } from './ExitHandlers'; diff --git a/src/bin/utils/options.ts b/src/bin/utils/options.ts index 2fffd0ecb..e7832b67c 100644 --- a/src/bin/utils/options.ts +++ b/src/bin/utils/options.ts @@ -1,6 +1,8 @@ /** * Options and Arguments used by commands - * Use PolykeyCommand.addOption or PolykeyCommand.addArgument + * Use `PolykeyCommand.addOption` + * The option parsers will parse parameters and environment variables + * but not the default value * @module */ import commander from 'commander'; @@ -48,7 +50,9 @@ const fresh = new commander.Option( /** * Node ID used for connecting to a remote agent */ -const nodeId = new commander.Option('-ni, --node-id ').env('PK_NODE_ID'); +const nodeId = new commander.Option('-ni, --node-id ') + .env('PK_NODE_ID') + .argParser(binParsers.parseNodeId); /** * Client host used for connecting to remote agent @@ -56,7 +60,9 @@ const nodeId = new commander.Option('-ni, --node-id ').env('PK_NODE_ID'); const clientHost = new commander.Option( '-ch, --client-host ', 'Client Host Address', -).env('PK_CLIENT_HOST'); +) + .env('PK_CLIENT_HOST') + .argParser(binParsers.parseHost); /** * Client port used for connecting to remote agent @@ -65,29 +71,30 @@ const clientPort = new commander.Option( '-cp, --client-port ', 'Client Port', ) - .argParser(binParsers.parseNumber) - .env('PK_CLIENT_PORT'); + .env('PK_CLIENT_PORT') + .argParser(binParsers.parsePort); const ingressHost = new commander.Option( '-ih, --ingress-host ', 'Ingress host', ) .env('PK_INGRESS_HOST') + .argParser(binParsers.parseHost) .default(config.defaults.networkConfig.ingressHost); const ingressPort = new commander.Option( '-ip, --ingress-port ', 'Ingress Port', ) - .argParser(binParsers.parseNumber) .env('PK_INGRESS_PORT') + .argParser(binParsers.parsePort) .default(config.defaults.networkConfig.ingressPort); const connTimeoutTime = new commander.Option( '--connection-timeout ', 'Timeout value for connection establishment between nodes', ) - .argParser(binParsers.parseNumber) + .argParser(binParsers.parseInteger) .default(config.defaults.forwardProxyConfig.connTimeoutTime); const passwordFile = new commander.Option( @@ -123,7 +130,7 @@ const backgroundErrFile = new commander.Option( const rootKeyPairBits = new commander.Option( '-rkpb --root-key-pair-bits ', 'Bit size of root key pair', -).argParser(binParsers.parseNumber); +).argParser(binParsers.parseInteger); const seedNodes = new commander.Option( '-sn, --seed-nodes [nodeId1@host:port;nodeId2@host:port;...]', diff --git a/src/bin/utils/parsers.ts b/src/bin/utils/parsers.ts index 8595eb688..132dbf62a 100644 --- a/src/bin/utils/parsers.ts +++ b/src/bin/utils/parsers.ts @@ -1,33 +1,56 @@ -import type { IdentityId, ProviderId } from '../../identities/types'; -import type { Host, Hostname, Port } from '../../network/types'; import type { NodeId, NodeMapping } from '../../nodes/types'; +import type { Host, Hostname, Port } from '../../network/types'; import commander from 'commander'; import { IdInternal } from '@matrixai/id'; import * as nodesUtils from '../../nodes/utils'; import * as networkUtils from '../../network/utils'; +import * as validationUtils from '../../validation/utils'; +import * as validationErrors from '../../validation/errors'; import config from '../../config'; import { never } from '../../utils'; -function parseNumber(v: string): number { - const num = parseInt(v); - if (isNaN(num)) { - throw new commander.InvalidArgumentError(`${v} is not a number`); - } - return num; +/** + * Converts a validation parser to commander argument parser + */ +function validateParserToArgParser( + validate: (data: string) => T, +): (data: string) => T { + return (data: string) => { + try { + return validate(data); + } catch (e) { + if (e instanceof validationErrors.ErrorParse) { + throw new commander.InvalidArgumentError(e.message); + } else { + throw e; + } + } + }; } +const parseInteger = validateParserToArgParser(validationUtils.parseInteger); +const parseNumber = validateParserToArgParser(validationUtils.parseNumber); +const parseNodeId = validateParserToArgParser(validationUtils.parseNodeId); +const parseGestaltId = validateParserToArgParser( + validationUtils.parseGestaltId, +); +const parseHost = validateParserToArgParser(validationUtils.parseHost); +const parseHostname = validateParserToArgParser(validationUtils.parseHostname); +const parseHostOrHostname = validateParserToArgParser( + validationUtils.parseHostOrHostname, +); +const parsePort = validateParserToArgParser(validationUtils.parsePort); + function parseCoreCount(v: string): number | undefined { if (v === 'all') { return undefined; } - return parseNumber(v); + return parseInt(v); } -function parseSecretPath( - secretPath: string, -): [string, string, string | undefined] { +function parseSecretPath(secretPath: string): [string, string, string?] { // E.g. If 'vault1:a/b/c', ['vault1', 'a/b/c'] is returned - // If 'vault1:a/b/c=VARIABLE', ['vault1, 'a/b/c', 'VAIRABLE'] is returned + // If 'vault1:a/b/c=VARIABLE', ['vault1, 'a/b/c', 'VARIABLE'] is returned const secretPathRegex = /^([\w-]+)(?::)([\w\-\\\/\.\$]+)(?:=)?([a-zA-Z_][\w]+)?$/; if (!secretPathRegex.test(secretPath)) { @@ -39,36 +62,6 @@ function parseSecretPath( return [vaultName, directoryPath, undefined]; } -function parseGestaltId(gestaltId: string) { - let providerId: string | null = null; - let identityId: string | null = null; - let nodeId: string | null = null; - - const identityStringRegex = /^(.+):(.+)$/; - if (identityStringRegex.test(gestaltId)) { - const parsed = parseIdentityString(gestaltId); - providerId = parsed.providerId; - identityId = parsed.identityId; - } else if (gestaltId != null) { - nodeId = gestaltId; - } else { - throw new commander.InvalidArgumentError( - `${gestaltId} is not a valid Node ID or Identity String`, - ); - } - return { providerId, identityId, nodeId }; -} - -function parseIdentityString(identityString: string): { - providerId: ProviderId; - identityId: IdentityId; -} { - const split = identityString.split(':'); - const providerId = split[0] as ProviderId; - const identityId = split[1] as IdentityId; - return { providerId, identityId }; -} - /** * Acquires the default seed nodes from src/config.ts. */ @@ -128,15 +121,15 @@ function parseSeedNodes(rawSeedNodes: string): [NodeMapping, boolean] { `${idHostPort[0]} is not a valid node ID`, ); } - if (!networkUtils.isValidHostname(idHostPort[1])) { + if (!networkUtils.isHostname(idHostPort[1])) { throw new commander.InvalidOptionArgumentError( `${idHostPort[1]} is not a valid hostname`, ); } - const port = parseNumber(idHostPort[2]); + const port = parsePort(idHostPort[2]); seedNodeMappings[seedNodeId] = { host: idHostPort[1] as Host | Hostname, - port: port as Port, + port: port, }; } return [seedNodeMappings, defaults]; @@ -153,10 +146,16 @@ function parseNetwork(network: string): NodeMapping { } export { + parseInteger, parseNumber, parseCoreCount, parseSecretPath, + parseNodeId, parseGestaltId, + parseHost, + parseHostname, + parseHostOrHostname, + parsePort, getDefaultSeedNodes, parseSeedNodes, parseNetwork, diff --git a/src/bin/utils/processors.ts b/src/bin/utils/processors.ts index 78b37713f..20ac20a07 100644 --- a/src/bin/utils/processors.ts +++ b/src/bin/utils/processors.ts @@ -1,6 +1,6 @@ import type { FileSystem } from '../../types'; import type { RecoveryCode } from '../../keys/types'; -import type { NodeId, NodeIdEncoded } from '../../nodes/types'; +import type { NodeId } from '../../nodes/types'; import type { Host, Port } from '../../network/types'; import type { StatusStarting, @@ -17,7 +17,6 @@ import * as binErrors from '../errors'; import * as clientUtils from '../../client/utils'; import { Status } from '../../status'; import config from '../../config'; -import { utils as nodesUtils } from '../../nodes'; /** * Prompts for existing password @@ -192,7 +191,7 @@ async function processRecoveryCode( */ async function processClientOptions( nodePath: string, - nodeId?: NodeIdEncoded, + nodeId?: NodeId, clientHost?: Host, clientPort?: Port, fs = require('fs'), @@ -215,15 +214,14 @@ async function processClientOptions( if (statusInfo === undefined || statusInfo.status !== 'LIVE') { throw new binErrors.ErrorCLIStatusNotLive(); } - if (nodeId == null) - nodeId = nodesUtils.encodeNodeId(statusInfo.data.nodeId); + if (nodeId == null) nodeId = statusInfo.data.nodeId; if (clientHost == null) clientHost = statusInfo.data.clientHost; if (clientPort == null) clientPort = statusInfo.data.clientPort; } return { - nodeId: nodesUtils.decodeNodeId(nodeId), - clientHost: clientHost, - clientPort: clientPort, + nodeId, + clientHost, + clientPort, }; } @@ -235,7 +233,7 @@ async function processClientOptions( */ async function processClientStatus( nodePath: string, - nodeId?: NodeIdEncoded, + nodeId?: NodeId, clientHost?: Host, clientPort?: Port, fs = require('fs'), @@ -263,14 +261,12 @@ async function processClientStatus( clientPort: Port; } > { - let nodeIdDecoded: NodeId | undefined = - nodeId != null ? nodesUtils.decodeNodeId(nodeId) : undefined; // If all parameters are set, no status and no statusInfo is used - if (nodeIdDecoded != null && clientHost != null && clientPort != null) { + if (nodeId != null && clientHost != null && clientPort != null) { return { statusInfo: undefined, status: undefined, - nodeId: nodeIdDecoded, + nodeId, clientHost, clientPort, }; @@ -290,13 +286,13 @@ async function processClientStatus( throw new binErrors.ErrorCLIStatusMissing(); } if (statusInfo.status === 'LIVE') { - if (nodeIdDecoded == null) nodeIdDecoded = statusInfo.data.nodeId; + if (nodeId == null) nodeId = statusInfo.data.nodeId; if (clientHost == null) clientHost = statusInfo.data.clientHost; if (clientPort == null) clientPort = statusInfo.data.clientPort; return { statusInfo, status, - nodeId: nodeIdDecoded, + nodeId, clientHost, clientPort, }; @@ -304,7 +300,7 @@ async function processClientStatus( return { statusInfo, status, - nodeId: nodeIdDecoded, + nodeId, clientHost, clientPort, }; diff --git a/src/bin/vaults/CommandClone.ts b/src/bin/vaults/CommandClone.ts index a0af7d2ad..55853a796 100644 --- a/src/bin/vaults/CommandClone.ts +++ b/src/bin/vaults/CommandClone.ts @@ -1,8 +1,10 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { NodeId } from '../../nodes/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandClone extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -10,12 +12,17 @@ class CommandClone extends CommandPolykey { this.name('clone'); this.description('Clone a Vault from Another Node'); this.argument('', 'Id of the vault to be cloned'); - this.argument('', 'Id of the node to clone the vault from'); + this.argument( + '', + 'Id of the node to clone the vault from', + binParsers.parseNodeId, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (vaultNameOrId, nodeId, options) => { + 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' ); @@ -49,7 +56,7 @@ class CommandClone extends CommandPolykey { const vaultCloneMessage = new vaultsPB.Clone(); vaultCloneMessage.setVault(vaultMessage); vaultCloneMessage.setNode(nodeMessage); - nodeMessage.setNodeId(nodeId); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); vaultMessage.setNameOrId(vaultNameOrId); await binUtils.retryAuthentication( (auth) => pkClient.grpcClient.vaultsClone(vaultCloneMessage, auth), diff --git a/src/bin/vaults/CommandPull.ts b/src/bin/vaults/CommandPull.ts index dc549deca..b7aacd3a8 100644 --- a/src/bin/vaults/CommandPull.ts +++ b/src/bin/vaults/CommandPull.ts @@ -1,21 +1,28 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { NodeId } from '../../nodes/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandPull extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('pull'); this.description('Pull a Vault from Another Node'); - this.argument('', 'Id of the node to pull the vault from'); + this.argument( + '', + 'Id of the node to pull the vault from', + binParsers.parseNodeId, + ); this.argument('', 'Name of the vault to be pulled'); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (nodeId, vaultName, options) => { + this.action(async (nodeId: NodeId, vaultName, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); + const nodesUtils = await import('../../nodes/utils'); const vaultsPB = await import( '../../proto/js/polykey/v1/vaults/vaults_pb' ); @@ -49,7 +56,7 @@ class CommandPull extends CommandPolykey { const vaultPullMessage = new vaultsPB.Pull(); vaultPullMessage.setVault(vaultMessage); vaultPullMessage.setNode(nodeMessage); - nodeMessage.setNodeId(nodeId); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); vaultMessage.setNameOrId(vaultName); await binUtils.retryAuthentication( (auth) => pkClient.grpcClient.vaultsPull(vaultPullMessage, auth), diff --git a/src/bin/vaults/CommandShare.ts b/src/bin/vaults/CommandShare.ts index 12a53a202..e71e8de20 100644 --- a/src/bin/vaults/CommandShare.ts +++ b/src/bin/vaults/CommandShare.ts @@ -1,8 +1,10 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { NodeId } from '../../nodes/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandShare extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -10,12 +12,17 @@ class CommandShare extends CommandPolykey { this.name('share'); this.description('Set the Permissions of a Vault for a Node'); this.argument('', 'Name of the vault to be shared'); - this.argument('', 'Id of the node to share to'); + this.argument( + '', + 'Id of the node to share to', + binParsers.parseNodeId, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (vaultName, nodeId, options) => { + 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' ); @@ -50,7 +57,7 @@ class CommandShare extends CommandPolykey { setVaultPermsMessage.setVault(vaultMessage); setVaultPermsMessage.setNode(nodeMessage); vaultMessage.setNameOrId(vaultName); - nodeMessage.setNodeId(nodeId); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); await binUtils.retryAuthentication( (auth) => pkClient.grpcClient.vaultsPermissionsSet( diff --git a/src/bin/vaults/CommandUnshare.ts b/src/bin/vaults/CommandUnshare.ts index 8a4cabbd4..097af67a5 100644 --- a/src/bin/vaults/CommandUnshare.ts +++ b/src/bin/vaults/CommandUnshare.ts @@ -1,8 +1,10 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { NodeId } from '../../nodes/types'; import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as binParsers from '../utils/parsers'; class CommandUnshare extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -10,12 +12,17 @@ class CommandUnshare extends CommandPolykey { this.name('unshare'); this.description('Unset the Permissions of a Vault for a Node'); this.argument('', 'Name of the vault to be unshared'); - this.argument('', 'Id of the node to unshare with'); + this.argument( + '', + 'Id of the node to unshare with', + binParsers.parseNodeId, + ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (vaultName, nodeId, options) => { + 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' ); @@ -50,7 +57,7 @@ class CommandUnshare extends CommandPolykey { unsetVaultPermsMessage.setVault(vaultMessage); unsetVaultPermsMessage.setNode(nodeMessage); vaultMessage.setNameOrId(vaultName); - nodeMessage.setNodeId(nodeId); + nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); await binUtils.retryAuthentication( (auth) => pkClient.grpcClient.vaultsPermissionsUnset( diff --git a/src/claims/types.ts b/src/claims/types.ts index d9a97ddc8..3d559f9ab 100644 --- a/src/claims/types.ts +++ b/src/claims/types.ts @@ -1,8 +1,8 @@ +import type { Id } from '@matrixai/id'; +import type { GeneralJWS, FlattenedJWSInput } from 'jose'; import type { Opaque } from '../types'; import type { NodeIdEncoded } from '../nodes/types'; import type { ProviderId, IdentityId } from '../identities/types'; -import type { GeneralJWS, FlattenedJWSInput } from 'jose'; -import type { Id, IdString } from '../GenericIdTypes'; /** * A JSON-ified, decoded version of the ClaimEncoded type. @@ -43,13 +43,13 @@ type SignatureData = { }; /** - * An arbitraty string serving as a unique identitifer for a particular claim. + * An arbitrary string serving as a unique identitifer for a particular claim. * Depending on the domain the claim is used in, its implementation detail will * differ. For example, the sigchain domain uses a lexicographic-integer as the * claim ID (representing the sequence number key of the claim). */ type ClaimId = Opaque<'ClaimId', Id>; -type ClaimIdString = Opaque<'ClaimIdString', IdString>; +type ClaimIdEncoded = Opaque<'ClaimIdEncoded', string>; type ClaimIdGenerator = () => ClaimId; @@ -104,7 +104,7 @@ export type { ClaimIntermediary, SignatureData, ClaimId, - ClaimIdString, + ClaimIdEncoded, ClaimIdGenerator, ClaimEncoded, ClaimData, diff --git a/src/claims/utils.ts b/src/claims/utils.ts index 024892477..46303f5da 100644 --- a/src/claims/utils.ts +++ b/src/claims/utils.ts @@ -1,4 +1,6 @@ import type { + ClaimId, + ClaimIdEncoded, Claim, ClaimEncoded, ClaimData, @@ -8,20 +10,19 @@ import type { import type { NodeIdEncoded } from '../nodes/types'; import type { PublicKeyPem, PrivateKeyPem } from '../keys/types'; import type { POJO } from '../types'; - import type { GeneralJWSInput } from 'jose'; import type { DefinedError } from 'ajv'; import { createPublicKey, createPrivateKey } from 'crypto'; -import { GeneralSign, generalVerify, generateKeyPair, base64url } from 'jose'; import { md } from 'node-forge'; import canonicalize from 'canonicalize'; +import { GeneralSign, generalVerify, generateKeyPair, base64url } from 'jose'; +import { IdInternal, IdSortable } from '@matrixai/id'; import { claimIdentityValidate, claimNodeSinglySignedValidate, claimNodeDoublySignedValidate, } from './schema'; import * as claimsErrors from './errors'; - import * as nodesPB from '../proto/js/polykey/v1/nodes/nodes_pb'; /** @@ -479,6 +480,26 @@ function reconstructClaimEncoded(claimMsg: nodesPB.AgentClaim): ClaimEncoded { return claim; } +function encodeClaimId(claimId: ClaimId): ClaimIdEncoded { + return claimId.toMultibase('base32hex') as ClaimIdEncoded; +} + +function decodeClaimId(claimIdEncoded: string): ClaimId | undefined { + const claimId = IdInternal.fromMultibase(claimIdEncoded); + if (claimId == null) { + return; + } + return claimId; +} + +function createClaimIdGenerator(nodeId: NodeIdEncoded, lastClaimId?: ClaimId) { + const generator = new IdSortable({ + lastId: lastClaimId, + nodeId: IdInternal.fromString(nodeId).toBuffer(), + }); + return () => generator.get(); +} + export { createClaim, signExistingClaim, @@ -496,4 +517,7 @@ export { createCrossSignMessage, reconstructClaimIntermediary, reconstructClaimEncoded, + encodeClaimId, + decodeClaimId, + createClaimIdGenerator, }; diff --git a/src/client/errors.ts b/src/client/errors.ts index c6a0c39fb..246ddb084 100644 --- a/src/client/errors.ts +++ b/src/client/errors.ts @@ -1,30 +1,25 @@ -import { ErrorPolykey } from '../errors'; +import { ErrorPolykey, sysexits } from '../errors'; class ErrorClient extends ErrorPolykey {} class ErrorClientClientDestroyed extends ErrorClient { description = 'GRPCClientClient has been destroyed'; - exitCode = 64; + exitCode = sysexits.USAGE; } class ErrorClientAuthMissing extends ErrorClient { description = 'Authorisation metadata is required but missing'; - exitCode = 77; + exitCode = sysexits.NOPERM; } class ErrorClientAuthFormat extends ErrorClient { description = 'Authorisation metadata has invalid format'; - exitCode = 64; + exitCode = sysexits.USAGE; } class ErrorClientAuthDenied extends ErrorClient { description = 'Authorisation metadata is incorrect or expired'; - exitCode = 77; -} - -class ErrorClientInvalidProvider extends ErrorClient { - description = 'Provider Id is invalid or does not exist'; - exitCode = 70; + exitCode = sysexits.NOPERM; } export { @@ -33,5 +28,4 @@ export { ErrorClientAuthMissing, ErrorClientAuthFormat, ErrorClientAuthDenied, - ErrorClientInvalidProvider, }; diff --git a/src/client/service/agentLockAll.ts b/src/client/service/agentLockAll.ts index 3641e1f71..dd80ebf62 100644 --- a/src/client/service/agentLockAll.ts +++ b/src/client/service/agentLockAll.ts @@ -15,15 +15,15 @@ function agentLockAll({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); await sessionManager.resetKey(); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/agentStatus.ts b/src/client/service/agentStatus.ts index aad6e832b..8d28dbf64 100644 --- a/src/client/service/agentStatus.ts +++ b/src/client/service/agentStatus.ts @@ -28,8 +28,8 @@ function agentStatus({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new agentPB.InfoMessage(); try { + const response = new agentPB.InfoMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); response.setPid(process.pid); @@ -49,8 +49,8 @@ function agentStatus({ response.setRootCertChainPem(await keyManager.getRootCertChainPem()); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/agentStop.ts b/src/client/service/agentStop.ts index 94f3e0ff3..6f0c83bdc 100644 --- a/src/client/service/agentStop.ts +++ b/src/client/service/agentStop.ts @@ -27,8 +27,8 @@ function agentStop({ call.sendMetadata(metadata); // Respond first to close the GRPC connection callback(null, response); - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } // Stop is called after GRPC resources are cleared diff --git a/src/client/service/agentUnlock.ts b/src/client/service/agentUnlock.ts index 1ddef6f11..a3ff33ddd 100644 --- a/src/client/service/agentUnlock.ts +++ b/src/client/service/agentUnlock.ts @@ -8,14 +8,14 @@ function agentUnlock({ authenticate }: { authenticate: Authenticate }) { call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsActionsGetByIdentity.ts b/src/client/service/gestaltsActionsGetByIdentity.ts index 6c039cdec..1ee46b1fd 100644 --- a/src/client/service/gestaltsActionsGetByIdentity.ts +++ b/src/client/service/gestaltsActionsGetByIdentity.ts @@ -4,6 +4,8 @@ import type { GestaltGraph } from '../../gestalts'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; function gestaltsActionsGetByIdentity({ @@ -17,13 +19,26 @@ function gestaltsActionsGetByIdentity({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const info = call.request; - const response = new permissionsPB.Actions(); try { + const response = new permissionsPB.Actions(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const providerId = info.getProviderId() as ProviderId; - const identityId = info.getIdentityId() as IdentityId; + 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 gestaltGraph.getGestaltActionsByIdentity( providerId, identityId, @@ -38,8 +53,8 @@ function gestaltsActionsGetByIdentity({ } callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsActionsGetByNode.ts b/src/client/service/gestaltsActionsGetByNode.ts index 879697f39..3d1f6b1c5 100644 --- a/src/client/service/gestaltsActionsGetByNode.ts +++ b/src/client/service/gestaltsActionsGetByNode.ts @@ -1,9 +1,11 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; +import type { NodeId } from '../../nodes/types'; import type { GestaltGraph } from '../../gestalts'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import { utils as grpcUtils } from '../../grpc'; -import { utils as nodesUtils } from '../../nodes'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; function gestaltsActionsGetByNode({ @@ -17,12 +19,21 @@ function gestaltsActionsGetByNode({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const info = call.request; - const response = new permissionsPB.Actions(); try { + const response = new permissionsPB.Actions(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const nodeId = nodesUtils.decodeNodeId(info.getNodeId()); + const { nodeId }: { nodeId: NodeId } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: call.request.getNodeId(), + }, + ); const result = await gestaltGraph.getGestaltActionsByNode(nodeId); if (result == null) { // Node doesn't exist, so no permissions. might throw error instead TBD. @@ -34,8 +45,8 @@ function gestaltsActionsGetByNode({ } callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsActionsSetByIdentity.ts b/src/client/service/gestaltsActionsSetByIdentity.ts index 976e2450f..0a7637876 100644 --- a/src/client/service/gestaltsActionsSetByIdentity.ts +++ b/src/client/service/gestaltsActionsSetByIdentity.ts @@ -1,10 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { GestaltGraph } from '../../gestalts'; +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 { utils as grpcUtils } from '../../grpc'; -import { utils as gestaltsUtils } from '../../gestalts'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function gestaltsActionsSetByIdentity({ @@ -18,15 +20,33 @@ function gestaltsActionsSetByIdentity({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const info = call.request; - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Setting the action. - const action = gestaltsUtils.makeGestaltAction(info.getAction()); - const providerId = info.getIdentity()?.getProviderId() as ProviderId; - const identityId = info.getIdentity()?.getIdentityId() as IdentityId; + 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 gestaltGraph.setGestaltActionByIdentity( providerId, identityId, @@ -34,8 +54,8 @@ function gestaltsActionsSetByIdentity({ ); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsActionsSetByNode.ts b/src/client/service/gestaltsActionsSetByNode.ts index 2dda1e227..f7e2e22fc 100644 --- a/src/client/service/gestaltsActionsSetByNode.ts +++ b/src/client/service/gestaltsActionsSetByNode.ts @@ -1,10 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { GestaltGraph } from '../../gestalts'; +import type { GestaltAction } from '../../gestalts/types'; +import type { NodeId } from '../../nodes/types'; import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; import { utils as grpcUtils } from '../../grpc'; -import { utils as nodesUtils } from '../../nodes'; -import { utils as gestaltsUtils } from '../../gestalts'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function gestaltsActionsSetByNode({ @@ -18,19 +20,29 @@ function gestaltsActionsSetByNode({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const info = call.request; - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Setting the action. - const action = gestaltsUtils.makeGestaltAction(info.getAction()); - const nodeId = nodesUtils.decodeNodeId(info.getNode()!.getNodeId()); + 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 gestaltGraph.setGestaltActionByNode(nodeId, action); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsActionsUnsetByIdentity.ts b/src/client/service/gestaltsActionsUnsetByIdentity.ts index 7d6bedd4e..a247babd8 100644 --- a/src/client/service/gestaltsActionsUnsetByIdentity.ts +++ b/src/client/service/gestaltsActionsUnsetByIdentity.ts @@ -1,10 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { GestaltGraph } from '../../gestalts'; +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 { utils as grpcUtils } from '../../grpc'; -import { utils as gestaltsUtils } from '../../gestalts'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function gestaltsActionsUnsetByIdentity({ @@ -18,15 +20,33 @@ function gestaltsActionsUnsetByIdentity({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const info = call.request; - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Setting the action. - const action = gestaltsUtils.makeGestaltAction(info.getAction()); - const providerId = info.getIdentity()?.getProviderId() as ProviderId; - const identityId = info.getIdentity()?.getIdentityId() as IdentityId; + 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 gestaltGraph.unsetGestaltActionByIdentity( providerId, identityId, @@ -34,8 +54,8 @@ function gestaltsActionsUnsetByIdentity({ ); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsActionsUnsetByNode.ts b/src/client/service/gestaltsActionsUnsetByNode.ts index 720d6f7f5..0add07203 100644 --- a/src/client/service/gestaltsActionsUnsetByNode.ts +++ b/src/client/service/gestaltsActionsUnsetByNode.ts @@ -1,10 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { GestaltGraph } from '../../gestalts'; +import type { GestaltAction } from '../../gestalts/types'; +import type { NodeId } from '../../nodes/types'; import type * as permissionsPB from '../../proto/js/polykey/v1/permissions/permissions_pb'; import { utils as grpcUtils } from '../../grpc'; -import { utils as nodesUtils } from '../../nodes'; -import { utils as gestaltsUtils } from '../../gestalts'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function gestaltsActionsUnsetByNode({ @@ -18,19 +20,29 @@ function gestaltsActionsUnsetByNode({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const info = call.request; - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Setting the action. - const action = gestaltsUtils.makeGestaltAction(info.getAction()); - const nodeId = nodesUtils.decodeNodeId(info.getNode()!.getNodeId()); + 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 gestaltGraph.unsetGestaltActionByNode(nodeId, action); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsDiscoveryByIdentity.ts b/src/client/service/gestaltsDiscoveryByIdentity.ts index 11c54fb89..75199dfb7 100644 --- a/src/client/service/gestaltsDiscoveryByIdentity.ts +++ b/src/client/service/gestaltsDiscoveryByIdentity.ts @@ -4,6 +4,8 @@ import type { Discovery } from '../../discovery'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function gestaltsDiscoveryByIdentity({ @@ -17,23 +19,37 @@ function gestaltsDiscoveryByIdentity({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const info = call.request; - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Constructing identity info. - const gen = discovery.discoverGestaltByIdentity( - info.getProviderId() as ProviderId, - info.getIdentityId() as IdentityId, + 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 gen = discovery.discoverGestaltByIdentity(providerId, identityId); for await (const _ of gen) { // Empty } callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsDiscoveryByNode.ts b/src/client/service/gestaltsDiscoveryByNode.ts index 19575bb54..8cb8910ae 100644 --- a/src/client/service/gestaltsDiscoveryByNode.ts +++ b/src/client/service/gestaltsDiscoveryByNode.ts @@ -1,9 +1,11 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { Discovery } from '../../discovery'; +import type { NodeId } from '../../nodes/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import { utils as grpcUtils } from '../../grpc'; -import { utils as nodesUtils } from '../../nodes'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function gestaltsDiscoveryByNode({ @@ -17,21 +19,33 @@ function gestaltsDiscoveryByNode({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const info = call.request; - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Constructing identity info. - const nodeId = nodesUtils.decodeNodeId(info.getNodeId()!); + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: call.request.getNodeId(), + }, + ); const gen = discovery.discoverGestaltByNode(nodeId); for await (const _ of gen) { // Empty } callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsGestaltGetByIdentity.ts b/src/client/service/gestaltsGestaltGetByIdentity.ts index eaf2be7cb..357761c11 100644 --- a/src/client/service/gestaltsGestaltGetByIdentity.ts +++ b/src/client/service/gestaltsGestaltGetByIdentity.ts @@ -4,6 +4,8 @@ import type { GestaltGraph } from '../../gestalts'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; function gestaltsGestaltGetByIdentity({ @@ -17,21 +19,40 @@ function gestaltsGestaltGetByIdentity({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new gestaltsPB.Graph(); 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 gestaltGraph.getGestaltByIdentity( - call.request.getProviderId() as ProviderId, - call.request.getIdentityId() as IdentityId, + providerId, + identityId, ); if (gestalt != null) { response.setGestaltGraph(JSON.stringify(gestalt)); } callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsGestaltGetByNode.ts b/src/client/service/gestaltsGestaltGetByNode.ts index 83f8d5c2e..424ad0f65 100644 --- a/src/client/service/gestaltsGestaltGetByNode.ts +++ b/src/client/service/gestaltsGestaltGetByNode.ts @@ -1,9 +1,11 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; +import type { NodeId } from '../../nodes/types'; import type { GestaltGraph } from '../../gestalts'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import { utils as grpcUtils } from '../../grpc'; -import { utils as nodesUtils } from '../../nodes'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as gestaltsPB from '../../proto/js/polykey/v1/gestalts/gestalts_pb'; function gestaltsGestaltGetByNode({ @@ -17,19 +19,33 @@ function gestaltsGestaltGetByNode({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new gestaltsPB.Graph(); try { + const response = new gestaltsPB.Graph(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const nodeId = nodesUtils.decodeNodeId(call.request.getNodeId()!); + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: call.request.getNodeId(), + }, + ); const gestalt = await gestaltGraph.getGestaltByNode(nodeId); if (gestalt != null) { response.setGestaltGraph(JSON.stringify(gestalt)); } callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/gestaltsGestaltList.ts b/src/client/service/gestaltsGestaltList.ts index e4daf338f..458b5cf32 100644 --- a/src/client/service/gestaltsGestaltList.ts +++ b/src/client/service/gestaltsGestaltList.ts @@ -29,8 +29,8 @@ function gestaltsGestaltList({ } await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); return; } }; diff --git a/src/client/service/identitiesAuthenticate.ts b/src/client/service/identitiesAuthenticate.ts index 6cb12f941..24ccf2f7e 100644 --- a/src/client/service/identitiesAuthenticate.ts +++ b/src/client/service/identitiesAuthenticate.ts @@ -2,10 +2,11 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { IdentitiesManager } from '../../identities'; import type { ProviderId } from '../../identities/types'; -import * as clientErrors from '../errors'; import { utils as grpcUtils } from '../../grpc'; +import { errors as identitiesErrors } from '../../identities'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync, never } from '../../utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import { never } from '../../utils'; function identitiesAuthenticate({ identitiesManager, @@ -24,11 +25,24 @@ function identitiesAuthenticate({ try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const provider = identitiesManager.getProvider( - call.request.getProviderId() as ProviderId, + 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 clientErrors.ErrorClientInvalidProvider(); + throw new identitiesErrors.ErrorProviderMissing(); } const authFlow = provider.authenticate(); let authFlowResult = await authFlow.next(); diff --git a/src/client/service/identitiesClaim.ts b/src/client/service/identitiesClaim.ts index 44d71fb94..a87d36776 100644 --- a/src/client/service/identitiesClaim.ts +++ b/src/client/service/identitiesClaim.ts @@ -4,12 +4,13 @@ import type { NodeManager } from '../../nodes'; import type { Sigchain } from '../../sigchain'; import type { IdentitiesManager } from '../../identities'; import type { IdentityId, ProviderId } from '../../identities/types'; -import * as clientErrors from '../errors'; import { utils as grpcUtils } from '../../grpc'; import { utils as claimsUtils } from '../../claims'; +import { utils as nodesUtils } from '../../nodes'; import { errors as identitiesErrors } from '../../identities'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import { utils as nodeUtils } from '../../nodes'; /** * Augments the keynode with a new identity. @@ -29,15 +30,34 @@ function identitiesClaim({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new identitiesPB.Claim(); 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(), + }, + ); // Check provider is authenticated - const providerId = call.request.getProviderId() as ProviderId; const provider = identitiesManager.getProvider(providerId); - if (provider == null) throw new clientErrors.ErrorClientInvalidProvider(); - const identityId = call.request.getIdentityId() as IdentityId; + if (provider == null) { + throw new identitiesErrors.ErrorProviderMissing(); + } const identities = await provider.getAuthIdentityIds(); if (!identities.includes(identityId)) { throw new identitiesErrors.ErrorProviderUnauthenticated(); @@ -45,7 +65,7 @@ function identitiesClaim({ // Create identity claim on our node const [, claim] = await sigchain.addClaim({ type: 'identity', - node: nodeUtils.encodeNodeId(nodeManager.getNodeId()), + node: nodesUtils.encodeNodeId(nodeManager.getNodeId()), provider: providerId, identity: identityId, }); @@ -58,8 +78,8 @@ function identitiesClaim({ } callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/identitiesInfoGet.ts b/src/client/service/identitiesInfoGet.ts index 248a60e94..dcbaac80d 100644 --- a/src/client/service/identitiesInfoGet.ts +++ b/src/client/service/identitiesInfoGet.ts @@ -3,6 +3,8 @@ import type { Authenticate } from '../types'; import type { IdentitiesManager } from '../../identities'; import type { ProviderId } from '../../identities/types'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; /** @@ -19,12 +21,25 @@ function identitiesInfoGet({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new identitiesPB.Provider(); try { + const response = new identitiesPB.Provider(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Get's an identity out of all identities. - const providerId = call.request.getProviderId() as ProviderId; + 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 !== undefined) { const identities = await provider.getAuthIdentityIds(); @@ -35,8 +50,8 @@ function identitiesInfoGet({ } callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/identitiesInfoGetConnected.ts b/src/client/service/identitiesInfoGetConnected.ts index 683f0702d..22ab3124a 100644 --- a/src/client/service/identitiesInfoGetConnected.ts +++ b/src/client/service/identitiesInfoGetConnected.ts @@ -2,8 +2,10 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { IdentitiesManager } from '../../identities'; import type { IdentityId, ProviderId } from '../../identities/types'; -import * as clientErrors from '../errors'; import { utils as grpcUtils } from '../../grpc'; +import { errors as identitiesErrors } from '../../identities'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; function identitiesInfoGetConnected({ @@ -23,20 +25,33 @@ function identitiesInfoGetConnected({ try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const providerId = call.request - .getProvider() - ?.getProviderId() as ProviderId; - const identityId = call.request - .getProvider() - ?.getIdentityId() as IdentityId; + 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(), + }, + ); const provider = identitiesManager.getProvider(providerId); - if (provider == null) throw new clientErrors.ErrorClientInvalidProvider(); - + if (provider == null) { + throw new identitiesErrors.ErrorProviderMissing(); + } const identities = provider.getConnectedIdentityDatas( identityId, call.request.getSearchTermList(), ); - for await (const identity of identities) { const identityInfoMessage = new identitiesPB.Info(); const providerMessage = new identitiesPB.Provider(); @@ -50,8 +65,8 @@ function identitiesInfoGetConnected({ } await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); return; } }; diff --git a/src/client/service/identitiesProvidersList.ts b/src/client/service/identitiesProvidersList.ts index 62f883d65..a7ce51051 100644 --- a/src/client/service/identitiesProvidersList.ts +++ b/src/client/service/identitiesProvidersList.ts @@ -16,17 +16,16 @@ function identitiesProvidersList({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new identitiesPB.Provider(); 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 (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/identitiesTokenDelete.ts b/src/client/service/identitiesTokenDelete.ts index 383f0d51d..835fee428 100644 --- a/src/client/service/identitiesTokenDelete.ts +++ b/src/client/service/identitiesTokenDelete.ts @@ -4,6 +4,8 @@ import type { IdentitiesManager } from '../../identities'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function identitiesTokenDelete({ @@ -17,18 +19,34 @@ function identitiesTokenDelete({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - await identitiesManager.delToken( - call.request.getProviderId() as ProviderId, - call.request.getIdentityId() as IdentityId, + 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 identitiesManager.delToken(providerId, identityId); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/identitiesTokenGet.ts b/src/client/service/identitiesTokenGet.ts index bb1f614d0..102ae3a07 100644 --- a/src/client/service/identitiesTokenGet.ts +++ b/src/client/service/identitiesTokenGet.ts @@ -3,6 +3,8 @@ import type { Authenticate } from '../types'; import type { IdentitiesManager } from '../../identities'; import type { IdentityId, ProviderId } from '../../identities/types'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; function identitiesTokenGet({ @@ -16,19 +18,35 @@ function identitiesTokenGet({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new identitiesPB.Token(); try { + const response = new identitiesPB.Token(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const tokens = await identitiesManager.getToken( - call.request.getProviderId() as ProviderId, - call.request.getIdentityId() as IdentityId, + 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 identitiesManager.getToken(providerId, identityId); response.setToken(JSON.stringify(tokens)); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/identitiesTokenPut.ts b/src/client/service/identitiesTokenPut.ts index b447368b5..1a371cb95 100644 --- a/src/client/service/identitiesTokenPut.ts +++ b/src/client/service/identitiesTokenPut.ts @@ -4,6 +4,8 @@ import type { IdentitiesManager } from '../../identities'; import type { IdentityId, ProviderId, TokenData } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function identitiesTokenPut({ @@ -20,20 +22,36 @@ function identitiesTokenPut({ >, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const provider = call.request.getProvider(); - await identitiesManager.putToken( - provider?.getProviderId() as ProviderId, - provider?.getIdentityId() as IdentityId, - { accessToken: call.request.getToken() } as TokenData, + 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 identitiesManager.putToken(providerId, identityId, { + accessToken: call.request.getToken(), + } as TokenData); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/keysCertsChainGet.ts b/src/client/service/keysCertsChainGet.ts index 830381136..8355474fd 100644 --- a/src/client/service/keysCertsChainGet.ts +++ b/src/client/service/keysCertsChainGet.ts @@ -19,7 +19,6 @@ function keysCertsChainGet({ try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const certs: Array = await keyManager.getRootCertChainPems(); let certMessage: keysPB.Certificate; for (const cert of certs) { @@ -29,8 +28,8 @@ function keysCertsChainGet({ } await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); return; } }; diff --git a/src/client/service/keysCertsGet.ts b/src/client/service/keysCertsGet.ts index d70bff9b4..198a67884 100644 --- a/src/client/service/keysCertsGet.ts +++ b/src/client/service/keysCertsGet.ts @@ -16,16 +16,16 @@ function keysCertsGet({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new keysPB.Certificate(); try { + const response = new keysPB.Certificate(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const cert = keyManager.getRootCertPem(); response.setCert(cert); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/keysDecrypt.ts b/src/client/service/keysDecrypt.ts index 2e5e601ee..0aa84d004 100644 --- a/src/client/service/keysDecrypt.ts +++ b/src/client/service/keysDecrypt.ts @@ -15,8 +15,8 @@ function keysDecrypt({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new keysPB.Crypto(); try { + const response = new keysPB.Crypto(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const data = await keyManager.decryptWithRootKeyPair( @@ -25,8 +25,8 @@ function keysDecrypt({ response.setData(data.toString('binary')); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/keysEncrypt.ts b/src/client/service/keysEncrypt.ts index c092458b5..8c048e94c 100644 --- a/src/client/service/keysEncrypt.ts +++ b/src/client/service/keysEncrypt.ts @@ -15,8 +15,8 @@ function keysEncrypt({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new keysPB.Crypto(); try { + const response = new keysPB.Crypto(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const data = await keyManager.encryptWithRootKeyPair( @@ -25,8 +25,8 @@ function keysEncrypt({ response.setData(data.toString('binary')); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/keysKeyPairRenew.ts b/src/client/service/keysKeyPairRenew.ts index 4e8b09c36..ffcd9b5b4 100644 --- a/src/client/service/keysKeyPairRenew.ts +++ b/src/client/service/keysKeyPairRenew.ts @@ -16,8 +16,8 @@ function keysKeyPairRenew({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); 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 @@ -25,8 +25,8 @@ function keysKeyPairRenew({ await keyManager.renewRootKeyPair(call.request.getName()); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/keysKeyPairReset.ts b/src/client/service/keysKeyPairReset.ts index 5f561507f..a5fee6145 100644 --- a/src/client/service/keysKeyPairReset.ts +++ b/src/client/service/keysKeyPairReset.ts @@ -16,8 +16,8 @@ function keysKeyPairReset({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); 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 @@ -25,8 +25,8 @@ function keysKeyPairReset({ await keyManager.resetRootKeyPair(call.request.getName()); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/keysKeyPairRoot.ts b/src/client/service/keysKeyPairRoot.ts index 793c08e3c..4a5858347 100644 --- a/src/client/service/keysKeyPairRoot.ts +++ b/src/client/service/keysKeyPairRoot.ts @@ -16,8 +16,8 @@ function keysKeyPairRoot({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new keysPB.KeyPair(); try { + const response = new keysPB.KeyPair(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const keyPair = keyManager.getRootKeyPairPem(); @@ -25,8 +25,8 @@ function keysKeyPairRoot({ response.setPrivate(keyPair.privateKey); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/keysPasswordChange.ts b/src/client/service/keysPasswordChange.ts index 80b128d3e..2d5074abf 100644 --- a/src/client/service/keysPasswordChange.ts +++ b/src/client/service/keysPasswordChange.ts @@ -16,16 +16,15 @@ function keysPasswordChange({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - await keyManager.changePassword(call.request.getPassword()); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/keysSign.ts b/src/client/service/keysSign.ts index b48702e5a..b7c94923d 100644 --- a/src/client/service/keysSign.ts +++ b/src/client/service/keysSign.ts @@ -15,19 +15,18 @@ function keysSign({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new keysPB.Crypto(); try { + const response = new keysPB.Crypto(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const signature = await keyManager.signWithRootKeyPair( Buffer.from(call.request.getData(), 'binary'), ); response.setSignature(signature.toString('binary')); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/keysVerify.ts b/src/client/service/keysVerify.ts index 007ab1681..1090642c5 100644 --- a/src/client/service/keysVerify.ts +++ b/src/client/service/keysVerify.ts @@ -16,8 +16,8 @@ function keysVerify({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const status = await keyManager.verifyWithRootKeyPair( @@ -27,8 +27,8 @@ function keysVerify({ response.setSuccess(status); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/nodesAdd.ts b/src/client/service/nodesAdd.ts index edd68a6bf..57924cd5e 100644 --- a/src/client/service/nodesAdd.ts +++ b/src/client/service/nodesAdd.ts @@ -1,11 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { NodeManager } from '../../nodes'; -import type { NodeAddress } from '../../nodes/types'; +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 { utils as nodesUtils, errors as nodesErrors } from '../../nodes'; import { utils as grpcUtils } from '../../grpc'; -import { utils as networkUtils } from '../../network'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; /** @@ -24,26 +25,41 @@ function nodesAdd({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Validate the passed node ID and host - const nodeId = nodesUtils.decodeNodeId(call.request.getNodeId()); - const validHost = networkUtils.isValidHost( - call.request.getAddress()!.getHost(), + 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: call.request.getNodeId(), + host: call.request.getAddress()?.getHost(), + port: call.request.getAddress()?.getPort(), + }, ); - if (!validHost) { - throw new nodesErrors.ErrorInvalidHost(); - } await nodeManager.setNode(nodeId, { - host: call.request.getAddress()!.getHost(), - port: call.request.getAddress()!.getPort(), + host, + port, } as NodeAddress); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/nodesClaim.ts b/src/client/service/nodesClaim.ts index 8b929ffaa..280dedb24 100644 --- a/src/client/service/nodesClaim.ts +++ b/src/client/service/nodesClaim.ts @@ -1,11 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { NodeManager } from '../../nodes'; -import type { NotificationData } from '../../notifications/types'; +import type { NodeId } from '../../nodes/types'; import type { NotificationsManager } from '../../notifications'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as nodesUtils } from '../../nodes'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; /** @@ -26,31 +27,44 @@ function nodesClaim({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const remoteNodeId = nodesUtils.decodeNodeId(call.request.getNodeId()); + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: call.request.getNodeId(), + }, + ); const gestaltInvite = await notificationsManager.findGestaltInvite( - remoteNodeId, + nodeId, ); // Check first whether there is an existing gestalt invite from the remote node // or if we want to force an invitation rather than a claim if (gestaltInvite === undefined || call.request.getForceInvite()) { - const data = { + await notificationsManager.sendNotification(nodeId, { type: 'GestaltInvite', - } as NotificationData; - await notificationsManager.sendNotification(remoteNodeId, data); + }); response.setSuccess(false); } else { // There is an existing invitation, and we want to claim the node - await nodeManager.claimNode(remoteNodeId); + await nodeManager.claimNode(nodeId); response.setSuccess(true); } callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/nodesFind.ts b/src/client/service/nodesFind.ts index bb4d0dcd7..b283b58e9 100644 --- a/src/client/service/nodesFind.ts +++ b/src/client/service/nodesFind.ts @@ -1,8 +1,11 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { NodeManager } from '../../nodes'; +import type { NodeId } from '../../nodes/types'; import { utils as nodesUtils } from '../../nodes'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; /** @@ -21,22 +24,35 @@ function nodesFind({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new nodesPB.NodeAddress(); try { + const response = new nodesPB.NodeAddress(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const nodeIdEncoded = call.request.getNodeId(); - const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded); + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: call.request.getNodeId(), + }, + ); const address = await nodeManager.findNode(nodeId); response - .setNodeId(nodeIdEncoded) + .setNodeId(nodesUtils.encodeNodeId(nodeId)) .setAddress( new nodesPB.Address().setHost(address.host).setPort(address.port), ); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/nodesPing.ts b/src/client/service/nodesPing.ts index 0653f2976..7dfd89939 100644 --- a/src/client/service/nodesPing.ts +++ b/src/client/service/nodesPing.ts @@ -1,9 +1,11 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { NodeManager } from '../../nodes'; +import type { NodeId } from '../../nodes/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as nodesUtils } from '../../nodes'; import { utils as grpcUtils } from '../../grpc'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; /** @@ -20,17 +22,31 @@ function nodesPing({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const nodeId = nodesUtils.decodeNodeId(call.request.getNodeId()); + 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 (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/notificationsClear.ts b/src/client/service/notificationsClear.ts index adb139294..322f64cf6 100644 --- a/src/client/service/notificationsClear.ts +++ b/src/client/service/notificationsClear.ts @@ -15,16 +15,15 @@ function notificationsClear({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - await notificationsManager.clearNotifications(); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/notificationsRead.ts b/src/client/service/notificationsRead.ts index 953e1e9a1..e890ce6c0 100644 --- a/src/client/service/notificationsRead.ts +++ b/src/client/service/notificationsRead.ts @@ -15,8 +15,8 @@ function notificationsRead({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new notificationsPB.List(); try { + const response = new notificationsPB.List(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const unread = call.request.getUnread(); @@ -63,8 +63,8 @@ function notificationsRead({ response.setNotificationList(notifMessages); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/notificationsSend.ts b/src/client/service/notificationsSend.ts index 6476f7ff2..ed3b26e05 100644 --- a/src/client/service/notificationsSend.ts +++ b/src/client/service/notificationsSend.ts @@ -1,10 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { NotificationsManager } from '../../notifications'; +import type { NodeId } from '../../nodes/types'; import type * as notificationsPB from '../../proto/js/polykey/v1/notifications/notifications_pb'; import { utils as grpcUtils } from '../../grpc'; -import { utils as nodesUtils } from '../../nodes'; import { utils as notificationsUtils } from '../../notifications'; +import { validateSync, utils as validationUtils } from '../../validation'; +import { matchSync } from '../../utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function notificationsSend({ @@ -18,22 +20,36 @@ function notificationsSend({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.EmptyMessage(); try { + const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const receivingId = nodesUtils.decodeNodeId(call.request.getReceiverId()); + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: call.request.getReceiverId(), + }, + ); const data = { type: 'General', message: call.request.getData()?.getMessage(), }; const validatedData = notificationsUtils.validateGeneralNotification(data); - await notificationsManager.sendNotification(receivingId, validatedData); + await notificationsManager.sendNotification(nodeId, validatedData); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsClone.ts b/src/client/service/vaultsClone.ts index 2a5e579d7..d83cf771f 100644 --- a/src/client/service/vaultsClone.ts +++ b/src/client/service/vaultsClone.ts @@ -9,8 +9,8 @@ function vaultsClone({ authenticate }: { authenticate: Authenticate }) { call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -35,8 +35,8 @@ function vaultsClone({ authenticate }: { authenticate: Authenticate }) { response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsCreate.ts b/src/client/service/vaultsCreate.ts index 6539c7083..bf1ddf33b 100644 --- a/src/client/service/vaultsCreate.ts +++ b/src/client/service/vaultsCreate.ts @@ -29,8 +29,8 @@ function vaultsCreate({ response.setNameOrId(vaultsUtils.makeVaultIdPretty(vault.vaultId)); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsDelete.ts b/src/client/service/vaultsDelete.ts index 34a6c692f..e8c80bb84 100644 --- a/src/client/service/vaultsDelete.ts +++ b/src/client/service/vaultsDelete.ts @@ -26,8 +26,8 @@ function vaultsDelete({ callback: grpc.sendUnaryData, ): Promise => { const vaultMessage = call.request; - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const nameOrId = vaultMessage.getNameOrId(); @@ -38,8 +38,8 @@ function vaultsDelete({ response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsList.ts b/src/client/service/vaultsList.ts index 237aaedf1..b22723fa0 100644 --- a/src/client/service/vaultsList.ts +++ b/src/client/service/vaultsList.ts @@ -33,8 +33,8 @@ function vaultsList({ } await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); return; } }; diff --git a/src/client/service/vaultsLog.ts b/src/client/service/vaultsLog.ts index 6678cd52b..3c06e172d 100644 --- a/src/client/service/vaultsLog.ts +++ b/src/client/service/vaultsLog.ts @@ -56,8 +56,8 @@ function vaultsLog({ } await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); return; } }; diff --git a/src/client/service/vaultsPermissions.ts b/src/client/service/vaultsPermissions.ts index 2a43ade68..8fba60112 100644 --- a/src/client/service/vaultsPermissions.ts +++ b/src/client/service/vaultsPermissions.ts @@ -43,8 +43,8 @@ function vaultsPermissions({ authenticate }: { authenticate: Authenticate }) { // } await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); return; } }; diff --git a/src/client/service/vaultsPermissionsSet.ts b/src/client/service/vaultsPermissionsSet.ts index adb31381d..05ddb055f 100644 --- a/src/client/service/vaultsPermissionsSet.ts +++ b/src/client/service/vaultsPermissionsSet.ts @@ -35,8 +35,8 @@ function vaultsPermissionsSet({ response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsPermissionsUnset.ts b/src/client/service/vaultsPermissionsUnset.ts index 4840176d3..fd2e8429f 100644 --- a/src/client/service/vaultsPermissionsUnset.ts +++ b/src/client/service/vaultsPermissionsUnset.ts @@ -35,8 +35,8 @@ function vaultsPermissionsUnset({ response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsPull.ts b/src/client/service/vaultsPull.ts index 1e61964f9..a9dcf663a 100644 --- a/src/client/service/vaultsPull.ts +++ b/src/client/service/vaultsPull.ts @@ -9,8 +9,8 @@ function vaultsPull({ authenticate }: { authenticate: Authenticate }) { call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -33,8 +33,8 @@ function vaultsPull({ authenticate }: { authenticate: Authenticate }) { response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsRename.ts b/src/client/service/vaultsRename.ts index 3c2bfd078..42e1aee97 100644 --- a/src/client/service/vaultsRename.ts +++ b/src/client/service/vaultsRename.ts @@ -24,8 +24,8 @@ function vaultsRename({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new vaultsPB.Vault(); try { + const response = new vaultsPB.Vault(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const vaultMessage = call.request.getVault(); @@ -42,8 +42,8 @@ function vaultsRename({ response.setNameOrId(vaultsUtils.makeVaultIdPretty(vaultId)); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsScan.ts b/src/client/service/vaultsScan.ts index 75235e770..0845809ed 100644 --- a/src/client/service/vaultsScan.ts +++ b/src/client/service/vaultsScan.ts @@ -17,12 +17,9 @@ function vaultsScan({ call: grpc.ServerWritableStream, ): Promise => { const genWritable = grpcUtils.generatorWritable(call); - // Const possibleNodeId = nodesUtils.decodeNodeId(call.request.getNodeId()); - // const nodeId = nodesUtils.validateNodeId(possibleNodeId); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const vaults = await vaultManager.listVaults(); vaults.forEach(async (vaultId, vaultName) => { const vaultListMessage = new vaultsPB.List(); @@ -32,8 +29,8 @@ function vaultsScan({ }); await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); return; } }; diff --git a/src/client/service/vaultsSecretsDelete.ts b/src/client/service/vaultsSecretsDelete.ts index 4a3a58fd1..8c042e845 100644 --- a/src/client/service/vaultsSecretsDelete.ts +++ b/src/client/service/vaultsSecretsDelete.ts @@ -25,8 +25,8 @@ function vaultsSecretsDelete({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -45,8 +45,8 @@ function vaultsSecretsDelete({ response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsSecretsEdit.ts b/src/client/service/vaultsSecretsEdit.ts index 830232677..2142d5014 100644 --- a/src/client/service/vaultsSecretsEdit.ts +++ b/src/client/service/vaultsSecretsEdit.ts @@ -25,8 +25,8 @@ function vaultsSecretsEdit({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -51,8 +51,8 @@ function vaultsSecretsEdit({ response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsSecretsGet.ts b/src/client/service/vaultsSecretsGet.ts index f3c6b2094..c3fd06cbc 100644 --- a/src/client/service/vaultsSecretsGet.ts +++ b/src/client/service/vaultsSecretsGet.ts @@ -25,8 +25,8 @@ function vaultsSecretsGet({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new secretsPB.Secret(); try { + const response = new secretsPB.Secret(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -45,8 +45,8 @@ function vaultsSecretsGet({ response.setSecretContent(secretContent); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsSecretsList.ts b/src/client/service/vaultsSecretsList.ts index 778947b8d..42eaea942 100644 --- a/src/client/service/vaultsSecretsList.ts +++ b/src/client/service/vaultsSecretsList.ts @@ -43,8 +43,8 @@ function vaultsSecretsList({ } await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); return; } }; diff --git a/src/client/service/vaultsSecretsMkdir.ts b/src/client/service/vaultsSecretsMkdir.ts index 594640a66..5c51f0673 100644 --- a/src/client/service/vaultsSecretsMkdir.ts +++ b/src/client/service/vaultsSecretsMkdir.ts @@ -25,8 +25,8 @@ function vaultsSecretsMkdir({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -47,8 +47,8 @@ function vaultsSecretsMkdir({ response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsSecretsNew.ts b/src/client/service/vaultsSecretsNew.ts index 0352e0241..2e3e7d18b 100644 --- a/src/client/service/vaultsSecretsNew.ts +++ b/src/client/service/vaultsSecretsNew.ts @@ -25,8 +25,8 @@ function vaultsSecretsNew({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -46,8 +46,8 @@ function vaultsSecretsNew({ response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsSecretsNewDir.ts b/src/client/service/vaultsSecretsNewDir.ts index 9b3804ddf..33d9b6968 100644 --- a/src/client/service/vaultsSecretsNewDir.ts +++ b/src/client/service/vaultsSecretsNewDir.ts @@ -28,8 +28,8 @@ function vaultsSecretsNewDir({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -48,8 +48,8 @@ function vaultsSecretsNewDir({ response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsSecretsRename.ts b/src/client/service/vaultsSecretsRename.ts index dc3f98f2b..2fe81c7b5 100644 --- a/src/client/service/vaultsSecretsRename.ts +++ b/src/client/service/vaultsSecretsRename.ts @@ -25,8 +25,8 @@ function vaultsSecretsRename({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new utilsPB.StatusMessage(); try { + const response = new utilsPB.StatusMessage(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const secretMessage = call.request.getOldSecret(); @@ -50,8 +50,8 @@ function vaultsSecretsRename({ response.setSuccess(true); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsSecretsStat.ts b/src/client/service/vaultsSecretsStat.ts index 20b7308f3..f7250cdad 100644 --- a/src/client/service/vaultsSecretsStat.ts +++ b/src/client/service/vaultsSecretsStat.ts @@ -8,8 +8,8 @@ function vaultsSecretsStat({ authenticate }: { authenticate: Authenticate }) { call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new vaultsPB.Stat(); try { + const response = new vaultsPB.Stat(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -22,8 +22,8 @@ function vaultsSecretsStat({ authenticate }: { authenticate: Authenticate }) { // response.setStats(JSON.stringify(stats));); callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/client/service/vaultsVersion.ts b/src/client/service/vaultsVersion.ts index c05f0ac1f..4533dca9e 100644 --- a/src/client/service/vaultsVersion.ts +++ b/src/client/service/vaultsVersion.ts @@ -24,8 +24,8 @@ function vaultsVersion({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - const response = new vaultsPB.VersionResult(); try { + const response = new vaultsPB.VersionResult(); // Checking session token const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); @@ -60,8 +60,8 @@ function vaultsVersion({ // Sending message callback(null, response); return; - } catch (err) { - callback(grpcUtils.fromError(err), null); + } catch (e) { + callback(grpcUtils.fromError(e)); return; } }; diff --git a/src/discovery/Discovery.ts b/src/discovery/Discovery.ts index 757faf0a2..16985e8b2 100644 --- a/src/discovery/Discovery.ts +++ b/src/discovery/Discovery.ts @@ -10,7 +10,7 @@ import type { } from '../identities/types'; import type { NodeManager } from '../nodes'; import type { Provider, IdentitiesManager } from '../identities'; -import type { Claim, ClaimIdString, ClaimLinkIdentity } from '../claims/types'; +import type { Claim, ClaimIdEncoded, ClaimLinkIdentity } from '../claims/types'; import type { ChainData } from '../sigchain/types'; import Logger from '@matrixai/logger'; @@ -113,12 +113,12 @@ class Discovery { // The sigchain data of the vertex (containing all cryptolinks) let vertexChainData: ChainData = {}; // If the vertex we've found is our own node, we simply get our own chain - const nodeId = nodesUtils.decodeNodeId(vertexGId.nodeId); + const nodeId = nodesUtils.decodeNodeId(vertexGId.nodeId)!; if (nodeId.equals(this.nodeManager.getNodeId())) { const vertexChainDataEncoded = await this.nodeManager.getChainData(); // Decode all our claims - no need to verify (on our own sigchain) for (const c in vertexChainDataEncoded) { - const claimId = c as ClaimIdString; + const claimId = c as ClaimIdEncoded; vertexChainData[claimId] = claimsUtils.decodeClaim( vertexChainDataEncoded[claimId], ); @@ -145,14 +145,14 @@ class Discovery { // that this will iterate in lexicographical order of keys. For now, // this doesn't matter though (because of the previous comment). for (const claimId in vertexChainData) { - const claim: Claim = vertexChainData[claimId as ClaimIdString]; + const claim: Claim = vertexChainData[claimId as ClaimIdEncoded]; // If the claim is to a node if (claim.payload.data.type === 'node') { // Get the chain data of the linked node const linkedVertexNodeId = nodesUtils.decodeNodeId( claim.payload.data.node2, - ); + )!; const linkedVertexChainData = await this.nodeManager.requestChainData(linkedVertexNodeId); // With this verified chain, we can link @@ -224,7 +224,7 @@ class Discovery { // Claims on an identity provider will always be node -> identity // So just cast payload data as such const data = claim.payload.data as ClaimLinkIdentity; - const linkedVertexNodeId = nodesUtils.decodeNodeId(data.node); + const linkedVertexNodeId = nodesUtils.decodeNodeId(data.node)!; // Get the chain data of this claimed node (so that we can link in GG) const linkedVertexChainData = await this.nodeManager.requestChainData( linkedVertexNodeId, @@ -316,7 +316,9 @@ class Discovery { // Verify the claim with the public key of the node const verified = await claimsUtils.verifyClaimSignature( encoded, - await this.nodeManager.getPublicKey(nodesUtils.decodeNodeId(data.node)), + await this.nodeManager.getPublicKey( + nodesUtils.decodeNodeId(data.node)!, + ), ); // If verified, add to the record if (verified) { diff --git a/src/errors.ts b/src/errors.ts index dfa44bb1a..1d224575e 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -60,4 +60,5 @@ export * from './bootstrap/errors'; export * from './notifications/errors'; export * from './schema/errors'; export * from './status/errors'; +export * from './validation/errors'; export * from './utils/errors'; diff --git a/src/gestalts/GestaltGraph.ts b/src/gestalts/GestaltGraph.ts index 0d3d6c7df..e9ff881be 100644 --- a/src/gestalts/GestaltGraph.ts +++ b/src/gestalts/GestaltGraph.ts @@ -283,7 +283,7 @@ class GestaltGraph { if (gId.type === 'node') { ops.push( ...(await this.unlinkNodeAndIdentityOps( - nodesUtils.decodeNodeId(gId.nodeId), + nodesUtils.decodeNodeId(gId.nodeId)!, providerId, identityId, )), @@ -318,7 +318,7 @@ class GestaltGraph { @ready(new gestaltsErrors.ErrorGestaltsGraphNotRunning()) public async setNodeOps(nodeInfo: NodeInfo): Promise> { const nodeKey = gestaltsUtils.keyFromNode( - nodesUtils.decodeNodeId(nodeInfo.id), + nodesUtils.decodeNodeId(nodeInfo.id)!, ); const ops: Array = []; let nodeKeyKeys = await this.db.get( @@ -330,7 +330,7 @@ class GestaltGraph { // Sets the gestalt in the acl ops.push( ...(await this.acl.setNodePermOps( - nodesUtils.decodeNodeId(nodeInfo.id), + nodesUtils.decodeNodeId(nodeInfo.id)!, { gestalt: {}, vaults: {}, @@ -392,7 +392,7 @@ class GestaltGraph { ops.push( ...(await this.unlinkNodeAndNodeOps( nodeId, - nodesUtils.decodeNodeId(gId.nodeId), + nodesUtils.decodeNodeId(gId.nodeId)!, )), ); } else if (gId.type === 'identity') { @@ -437,7 +437,7 @@ class GestaltGraph { ): Promise> { const ops: Array = []; const nodeKey = gestaltsUtils.keyFromNode( - nodesUtils.decodeNodeId(nodeInfo.id), + nodesUtils.decodeNodeId(nodeInfo.id)!, ); const identityKey = gestaltsUtils.keyFromIdentity( identityInfo.providerId, @@ -489,7 +489,7 @@ class GestaltGraph { if (nodeNew && identityNew) { ops.push( ...(await this.acl.setNodePermOps( - nodesUtils.decodeNodeId(nodeInfo.id), + nodesUtils.decodeNodeId(nodeInfo.id)!, { gestalt: {}, vaults: {}, @@ -510,7 +510,7 @@ class GestaltGraph { ); // These must exist const nodePerm = (await this.acl.getNodePerm( - nodesUtils.decodeNodeId(nodeInfo.id), + nodesUtils.decodeNodeId(nodeInfo.id)!, )) as Permission; const identityPerm = (await this.acl.getNodePerm( identityNodeIds[0], @@ -522,7 +522,7 @@ class GestaltGraph { // and the perm record update ops.push( ...(await this.acl.joinNodePermOps( - nodesUtils.decodeNodeId(nodeInfo.id), + nodesUtils.decodeNodeId(nodeInfo.id)!, identityNodeIds, permNew, )), @@ -531,7 +531,7 @@ class GestaltGraph { if (utils.isEmptyObject(identityKeyKeys)) { ops.push( ...(await this.acl.setNodePermOps( - nodesUtils.decodeNodeId(nodeInfo.id), + nodesUtils.decodeNodeId(nodeInfo.id)!, { gestalt: {}, vaults: {}, @@ -547,7 +547,7 @@ class GestaltGraph { const identityNodeId = gestaltsUtils.nodeFromKey(identityNodeKey!); ops.push( ...(await this.acl.joinNodePermOps(identityNodeId, [ - nodesUtils.decodeNodeId(nodeInfo.id), + nodesUtils.decodeNodeId(nodeInfo.id)!, ])), ); } @@ -602,8 +602,8 @@ class GestaltGraph { nodeInfo2: NodeInfo, ): Promise> { const ops: Array = []; - const nodeIdEncoded1 = nodesUtils.decodeNodeId(nodeInfo1.id); - const nodeIdEncoded2 = nodesUtils.decodeNodeId(nodeInfo2.id); + const nodeIdEncoded1 = nodesUtils.decodeNodeId(nodeInfo1.id)!; + const nodeIdEncoded2 = nodesUtils.decodeNodeId(nodeInfo2.id)!; const nodeKey1 = gestaltsUtils.keyFromNode(nodeIdEncoded1); const nodeKey2 = gestaltsUtils.keyFromNode(nodeIdEncoded2); let nodeKeyKeys1 = await this.db.get( diff --git a/src/gestalts/errors.ts b/src/gestalts/errors.ts index 486685f4d..b44eb0af6 100644 --- a/src/gestalts/errors.ts +++ b/src/gestalts/errors.ts @@ -12,8 +12,6 @@ class ErrorGestaltsGraphNodeIdMissing extends ErrorGestalts {} class ErrorGestaltsGraphIdentityIdMissing extends ErrorGestalts {} -class ErrorGestaltsInvalidAction extends ErrorGestalts {} - export { ErrorGestalts, ErrorGestaltsGraphRunning, @@ -21,5 +19,4 @@ export { ErrorGestaltsGraphDestroyed, ErrorGestaltsGraphNodeIdMissing, ErrorGestaltsGraphIdentityIdMissing, - ErrorGestaltsInvalidAction, }; diff --git a/src/gestalts/types.ts b/src/gestalts/types.ts index 19e8adb54..b7bb84171 100644 --- a/src/gestalts/types.ts +++ b/src/gestalts/types.ts @@ -2,7 +2,9 @@ import type { Opaque } from '../types'; import type { NodeIdEncoded, NodeInfo } from '../nodes/types'; import type { IdentityId, ProviderId, IdentityInfo } from '../identities/types'; -type GestaltAction = 'notify' | 'scan'; +const gestaltActions = ['notify', 'scan'] as const; + +type GestaltAction = typeof gestaltActions[number]; type GestaltActions = Partial>; type GestaltId = GestaltNodeId | GestaltIdentityId; @@ -30,6 +32,8 @@ type Gestalt = { identities: GestaltIdentities; }; +export { gestaltActions }; + export type { GestaltAction, GestaltActions, diff --git a/src/gestalts/utils.ts b/src/gestalts/utils.ts index 47bda4ece..1f1df121f 100644 --- a/src/gestalts/utils.ts +++ b/src/gestalts/utils.ts @@ -9,9 +9,8 @@ import type { } from './types'; import type { NodeId } from '../nodes/types'; import type { IdentityId, ProviderId } from '../identities/types'; - import canonicalize from 'canonicalize'; -import { ErrorGestaltsInvalidAction } from './errors'; +import { gestaltActions } from './types'; import { utils as nodesUtils } from '../nodes'; /** @@ -60,16 +59,14 @@ function keyFromIdentity( /** * Deconstruct GestaltKey to NodeId - * This is a partial function. */ function nodeFromKey(nodeKey: GestaltNodeKey): NodeId { const node = ungestaltKey(nodeKey) as GestaltNodeId; - return nodesUtils.decodeNodeId(node.nodeId); + return nodesUtils.decodeNodeId(node.nodeId)!; } /** * Deconstruct GestaltKey to IdentityId and ProviderId - * This is a partial function. */ function identityFromKey( identityKey: GestaltIdentityKey, @@ -78,15 +75,9 @@ function identityFromKey( return [identity.providerId, identity.identityId]; } -const validGestaltAction = ['notify', 'scan']; -function isGestaltAction(arg: any): arg is GestaltAction { - if (typeof arg !== 'string') return false; - return validGestaltAction.includes(arg); -} - -function makeGestaltAction(value: string): GestaltAction { - if (isGestaltAction(value)) return value; - throw new ErrorGestaltsInvalidAction(`${value} is not a valid GestaltAction`); +function isGestaltAction(action: any): action is GestaltAction { + if (typeof action !== 'string') return false; + return (gestaltActions as Readonly>).includes(action); } export { @@ -97,5 +88,4 @@ export { nodeFromKey, identityFromKey, isGestaltAction, - makeGestaltAction, }; diff --git a/src/grpc/utils/utils.ts b/src/grpc/utils/utils.ts index 1dbb4f6bf..51e68516c 100644 --- a/src/grpc/utils/utils.ts +++ b/src/grpc/utils/utils.ts @@ -144,15 +144,20 @@ function getClientSession( } /** - * Serializes ErrorPolykey instances into GRPC errors + * Serializes Error instances into GRPC errors * Use this on the sending side to send exceptions * Do not send exceptions to clients you do not trust */ -function fromError(error: errors.ErrorPolykey): ServerStatusResponse { +function fromError(error: Error): ServerStatusResponse { const metadata = new grpc.Metadata(); + // If the error is not ErrorPolykey, wrap it up so it can be serialised + // TODO: add additional metadata regarding the network location of the error + if (!(error instanceof errors.ErrorPolykey)) { + error = new errors.ErrorPolykey(error.message); + } metadata.set('name', error.name); metadata.set('message', error.message); - metadata.set('data', JSON.stringify(error.data)); + metadata.set('data', JSON.stringify((error as errors.ErrorPolykey).data)); return { metadata, }; diff --git a/src/identities/Provider.ts b/src/identities/Provider.ts index 7d6364a21..df63cb884 100644 --- a/src/identities/Provider.ts +++ b/src/identities/Provider.ts @@ -95,6 +95,7 @@ abstract class Provider { } catch (e) { return; } + // TODO: Add node ID validation here? if (!schema.claimIdentityValidate(claim)) { return; } diff --git a/src/identities/errors.ts b/src/identities/errors.ts index 40a69e124..0effd2306 100644 --- a/src/identities/errors.ts +++ b/src/identities/errors.ts @@ -3,7 +3,9 @@ import { ErrorPolykey } from '../errors'; class ErrorIdentities extends ErrorPolykey {} class ErrorIdentitiesManagerRunning extends ErrorIdentities {} + class ErrorIdentitiesManagerNotRunning extends ErrorIdentities {} + class ErrorIdentitiesManagerDestroyed extends ErrorIdentities {} class ErrorProviderDuplicate extends ErrorIdentities {} @@ -16,6 +18,8 @@ class ErrorProviderUnauthenticated extends ErrorIdentities {} class ErrorProviderUnimplemented extends ErrorIdentities {} +class ErrorProviderMissing extends ErrorIdentities {} + export { ErrorIdentities, ErrorIdentitiesManagerRunning, @@ -26,4 +30,5 @@ export { ErrorProviderAuthentication, ErrorProviderUnauthenticated, ErrorProviderUnimplemented, + ErrorProviderMissing, }; diff --git a/src/keys/utils.ts b/src/keys/utils.ts index 0d990ef81..f96d26f3e 100644 --- a/src/keys/utils.ts +++ b/src/keys/utils.ts @@ -30,10 +30,9 @@ import { import * as bip39 from 'bip39'; import { IdInternal } from '@matrixai/id'; import * as keysErrors from './errors'; -import config from '../config'; -import * as utils from '../utils'; -import { never } from '../utils'; import { utils as nodesUtils } from '../nodes'; +import config from '../config'; +import { promisify, getUnixtime, never } from '../utils'; bip39.setDefaultWordlist('english'); @@ -43,9 +42,7 @@ const authTagSize = 16; const eventRootKeyPairChange = Symbol('rootKeyPairChange'); async function generateKeyPair(bits: number): Promise { - const generateKeyPair = utils - .promisify(pki.rsa.generateKeyPair) - .bind(pki.rsa); + const generateKeyPair = promisify(pki.rsa.generateKeyPair).bind(pki.rsa); return await generateKeyPair({ bits }); } @@ -65,9 +62,7 @@ async function generateDeterministicKeyPair( md.sha512.create(), ); }; - const generateKeyPair = utils - .promisify(pki.rsa.generateKeyPair) - .bind(pki.rsa); + const generateKeyPair = promisify(pki.rsa.generateKeyPair).bind(pki.rsa); return await generateKeyPair({ bits, prng }); } @@ -221,7 +216,7 @@ function generateCertificate( const now = new Date(); const cert = pki.createCertificate(); cert.publicKey = subjectPublicKey; - cert.serialNumber = utils.getUnixtime(now).toString(); + cert.serialNumber = getUnixtime(now).toString(); const notBeforeDate = new Date(now.getTime()); const notAfterDate = new Date(now.getTime()); notAfterDate.setSeconds(notAfterDate.getSeconds() + duration); @@ -407,7 +402,6 @@ function certVerifiedNode(cert: Certificate): boolean { }); // Calculate the certificate digest const certDigest = md.sha256.create(); - let verified; try { cert.setExtensions(extensionsFiltered); @@ -431,6 +425,17 @@ function certVerifiedNode(cert: Certificate): boolean { return verified; } +/** + * Acquires the NodeId from a certificate + */ +function certNodeId(cert: Certificate): NodeId | undefined { + const commonName = cert.subject.getField({ type: '2.5.4.3' }); + if (commonName == null) { + return; + } + return nodesUtils.decodeNodeId(commonName.value); +} + function encryptWithPublicKey(publicKey: PublicKey, plainText: Buffer): Buffer { // Sha256 is 256 bits or 32 bytes const maxSize = maxEncryptSize(publicKeyBitSize(publicKey) / 8, 32); @@ -597,6 +602,7 @@ export { certIssued, certVerified, certVerifiedNode, + certNodeId, encryptWithPublicKey, decryptWithPrivateKey, signWithPrivateKey, diff --git a/src/network/ConnectionForward.ts b/src/network/ConnectionForward.ts index 66d2a6969..1ca254e7d 100644 --- a/src/network/ConnectionForward.ts +++ b/src/network/ConnectionForward.ts @@ -295,7 +295,7 @@ class ConnectionForward extends Connection { @ready(new networkErrors.ErrorConnectionNotRunning()) public getServerNodeIds(): Array { - return this.serverCertChain.map((c) => networkUtils.certNodeId(c)); + return this.serverCertChain.map((c) => keysUtils.certNodeId(c)!); } protected async startKeepAliveInterval(): Promise { diff --git a/src/network/ConnectionReverse.ts b/src/network/ConnectionReverse.ts index fb138983b..506f47d53 100644 --- a/src/network/ConnectionReverse.ts +++ b/src/network/ConnectionReverse.ts @@ -328,7 +328,7 @@ class ConnectionReverse extends Connection { if (!this._composed) { throw new networkErrors.ErrorConnectionNotComposed(); } - return this.clientCertChain.map((c) => networkUtils.certNodeId(c)); + return this.clientCertChain.map((c) => keysUtils.certNodeId(c)!); } protected startKeepAliveTimeout() { diff --git a/src/network/types.ts b/src/network/types.ts index c4273b539..ec29a005f 100644 --- a/src/network/types.ts +++ b/src/network/types.ts @@ -6,11 +6,24 @@ import type { } from '../keys/types'; import type { Opaque } from '../types'; -// Host is always an IP address +/** + * Host is always an IP address + */ type Host = Opaque<'Host', string>; -// Specifically for hostname domain names (i.e. to be resolved to an IP address) + +/** + * Hostnames are resolved to IP addresses + */ type Hostname = Opaque<'Hostname', string>; + +/** + * Ports are numbers from 0 to 65535 + */ type Port = Opaque<'Port', number>; + +/** + * Combination of `:` + */ type Address = Opaque<'Address', string>; type TLSConfig = { diff --git a/src/network/utils.ts b/src/network/utils.ts index 4058177e6..4cfb5a54e 100644 --- a/src/network/utils.ts +++ b/src/network/utils.ts @@ -8,9 +8,9 @@ import { Buffer } from 'buffer'; import dns from 'dns'; import { IPv4, IPv6, Validator } from 'ip-num'; import * as networkErrors from './errors'; -import { isEmptyObject, promisify } from '../utils'; import { utils as keysUtils } from '../keys'; import { utils as nodesUtils } from '../nodes'; +import { isEmptyObject, promisify } from '../utils'; const pingBuffer = serializeNetworkMessage({ type: 'ping', @@ -20,6 +20,35 @@ const pongBuffer = serializeNetworkMessage({ type: 'pong', }); +/** + * Validates that a provided host address is a valid IPv4 or IPv6 address. + */ +function isHost(host: any): host is Host { + if (typeof host !== 'string') return false; + const [isIPv4] = Validator.isValidIPv4String(host); + const [isIPv6] = Validator.isValidIPv6String(host); + return isIPv4 || isIPv6; +} + +/** + * Validates hostname as per RFC 1123 + */ +function isHostname(hostname: any): hostname is Hostname { + if (typeof hostname !== 'string') return false; + const regex = + /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; + return regex.test(hostname); +} + +/** + * Ports must be numbers between 0 and 65535 inclusive + */ +function isPort(port: any): port is Port { + if (typeof port !== 'number') return false; + if (port < 0 || port > 65535) return false; + return true; +} + /** * Given a bearer token, return a authentication token string. */ @@ -56,31 +85,11 @@ function parseAddress(address: string): [Host, Port] { } /** - * Validates that a provided host address is a valid IPv4 or IPv6 address. - */ -function isValidHost(host: string): boolean { - const [isIPv4] = Validator.isValidIPv4String(host); - const [isIPv6] = Validator.isValidIPv6String(host); - return isIPv4 || isIPv6; -} - -/** - * Validates that a provided hostname is valid, as per RFC 1123. - */ -function isValidHostname(hostname: string): boolean { - return hostname.match( - /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/, - ) - ? true - : false; -} - -/** - * Resolves a provided hostname to its respective IP address (type Host). + * Resolves a provided hostname to its respective IP address (type Host) */ async function resolveHost(host: Host | Hostname): Promise { // If already IPv4/IPv6 address, return it - if (isValidHost(host)) { + if (isHost(host)) { return host as Host; } const lookup = promisify(dns.lookup).bind(dns); @@ -164,14 +173,6 @@ function isTLSSocket(socket: Socket | TLSSocket): socket is TLSSocket { return (socket as TLSSocket).encrypted; } -/** - * Acquires the NodeId from a certificate - */ -function certNodeId(cert: Certificate): NodeId { - const commonName = cert.subject.getField({ type: '2.5.4.3' }); - return nodesUtils.decodeNodeId(commonName.value); -} - /** * Verify the server certificate chain when connecting to it from a client * This is a custom verification intended to verify that the server owned @@ -352,17 +353,17 @@ function verifyClientCertificateChain(certChain: Array): void { export { pingBuffer, pongBuffer, + isHost, + isHostname, + isPort, toAuthToken, buildAddress, parseAddress, - isValidHost, - isValidHostname, resolveHost, resolvesZeroIP, serializeNetworkMessage, unserializeNetworkMessage, isTLSSocket, - certNodeId, getCertificateChain, verifyServerCertificateChain, verifyClientCertificateChain, diff --git a/src/nodes/NodeConnection.ts b/src/nodes/NodeConnection.ts index b844fa4c2..99f1c026f 100644 --- a/src/nodes/NodeConnection.ts +++ b/src/nodes/NodeConnection.ts @@ -7,7 +7,7 @@ import type { Certificate, PublicKey, PublicKeyPem } from '../keys/types'; import type { ClaimEncoded, ClaimIntermediary, - ClaimIdString, + ClaimIdEncoded, } from '../claims/types'; import type { ForwardProxy } from '../network'; @@ -246,7 +246,7 @@ class NodeConnection { const certificates = this.getRootCertChain(); let publicKey: PublicKeyPem | null = null; for (const cert of certificates) { - if (networkUtils.certNodeId(cert).equals(expectedNodeId)) { + if (keysUtils.certNodeId(cert)!.equals(expectedNodeId)) { publicKey = keysUtils.publicKeyToPem( cert.publicKey as PublicKey, ) as PublicKeyPem; @@ -271,7 +271,7 @@ class NodeConnection { const nodes: Array = []; // Loop over each map element (from the returned response) and populate nodes response.getNodeTableMap().forEach((address, nodeIdEncoded: string) => { - const nodeId: NodeId = nodesUtils.decodeNodeId(nodeIdEncoded); + const nodeId: NodeId = nodesUtils.decodeNodeId(nodeIdEncoded)!; nodes.push({ id: nodeId, address: { @@ -332,7 +332,7 @@ class NodeConnection { const response = await this.client.nodesChainDataGet(emptyMsg); // Reconstruct each claim from the returned ChainDataMessage response.getChainDataMap().forEach((claimMsg, id: string) => { - const claimId = id as ClaimIdString; + const claimId = id as ClaimIdEncoded; // Reconstruct the signatures array const signatures: Array<{ signature: string; protected: string }> = []; for (const signatureData of claimMsg.getSignaturesList()) { diff --git a/src/nodes/NodeManager.ts b/src/nodes/NodeManager.ts index 809a85a26..6da36686a 100644 --- a/src/nodes/NodeManager.ts +++ b/src/nodes/NodeManager.ts @@ -2,7 +2,7 @@ import type { KeyManager } from '../keys'; import type { PublicKeyPem } from '../keys/types'; import type { Sigchain } from '../sigchain'; import type { ChainData, ChainDataEncoded } from '../sigchain/types'; -import type { ClaimIdString } from '../claims/types'; +import type { ClaimIdEncoded } from '../claims/types'; import type { NodeId, NodeAddress, @@ -312,10 +312,11 @@ class NodeManager { // node on the other end of the claim // e.g. a node claim from A -> B, verify with B's public key for (const c in verifiedChainData) { - const claimId = c as ClaimIdString; + const claimId = c as ClaimIdEncoded; const payload = verifiedChainData[claimId].payload; if (payload.data.type === 'node') { - const endNodeId = nodesUtils.decodeNodeId(payload.data.node2); + // TODO: remove ! assertion and perform exception handling in #310 + const endNodeId = nodesUtils.decodeNodeId(payload.data.node2)!; let endPublicKey: PublicKeyPem; // If the claim points back to our own node, don't attempt to connect if (endNodeId.equals(this.getNodeId())) { @@ -402,11 +403,11 @@ class NodeManager { @ready(new nodesErrors.ErrorNodeManagerNotRunning()) public async relayHolePunchMessage(message: nodesPB.Relay): Promise { const conn = await this.getConnectionToNode( - nodesUtils.decodeNodeId(message.getTargetId()), + nodesUtils.decodeNodeId(message.getTargetId())!, ); await conn.sendHolePunchMessage( - nodesUtils.decodeNodeId(message.getSrcId()), - nodesUtils.decodeNodeId(message.getTargetId()), + nodesUtils.decodeNodeId(message.getSrcId())!, + nodesUtils.decodeNodeId(message.getTargetId())!, message.getEgressAddress(), Buffer.from(message.getSignature()), ); @@ -484,7 +485,7 @@ class NodeManager { const targetAddress = await this.findNode(targetNodeId); // If the stored host is not a valid host (IP address), then we assume it to // be a hostname - const targetHostname = !(await networkUtils.isValidHost(targetAddress.host)) + const targetHostname = !(await networkUtils.isHost(targetAddress.host)) ? (targetAddress.host as Hostname) : undefined; const connection = await NodeConnection.createNodeConnection({ diff --git a/src/nodes/errors.ts b/src/nodes/errors.ts index 04bf0db25..e9bb83fa7 100644 --- a/src/nodes/errors.ts +++ b/src/nodes/errors.ts @@ -45,16 +45,6 @@ class ErrorNodeConnectionInfoNotExist extends ErrorNodes {} class ErrorNodeConnectionPublicKeyNotFound extends ErrorNodes {} -class ErrorInvalidNodeId extends ErrorNodes { - description: string = 'Invalid node ID.'; - exitCode: number = 64; -} - -class ErrorInvalidHost extends ErrorNodes { - description: string = 'Invalid IP address.'; - exitCode: number = 64; -} - export { ErrorNodes, ErrorNodeManagerRunning, @@ -76,6 +66,4 @@ export { ErrorNodeConnectionNotExist, ErrorNodeConnectionInfoNotExist, ErrorNodeConnectionPublicKeyNotFound, - ErrorInvalidNodeId, - ErrorInvalidHost, }; diff --git a/src/nodes/utils.ts b/src/nodes/utils.ts index 67e4a9bd1..e49ea9b41 100644 --- a/src/nodes/utils.ts +++ b/src/nodes/utils.ts @@ -1,7 +1,5 @@ import type { NodeData, NodeId, NodeIdEncoded } from './types'; - -import { IdInternal, utils as idUtils } from '@matrixai/id'; -import * as nodesErrors from './errors'; +import { IdInternal } from '@matrixai/id'; /** * Compute the distance between two nodes. @@ -68,19 +66,22 @@ function sortByDistance(a: NodeData, b: NodeData) { } function encodeNodeId(nodeId: NodeId): NodeIdEncoded { - return idUtils.toMultibase(nodeId, 'base32hex') as NodeIdEncoded; + return nodeId.toMultibase('base32hex') as NodeIdEncoded; } -function decodeNodeId(nodeIdEncoded: NodeIdEncoded | string): NodeId { +function decodeNodeId(nodeIdEncoded: any): NodeId | undefined { + if (typeof nodeIdEncoded !== 'string') { + return; + } const nodeId = IdInternal.fromMultibase(nodeIdEncoded); - if (nodeId == null) - throw new nodesErrors.ErrorInvalidNodeId( - `Was not a valid multibase: ${nodeIdEncoded}`, - ); - if (nodeId.length !== 32) - throw new nodesErrors.ErrorInvalidNodeId( - `Was not 32 bytes long: ${nodeIdEncoded}`, - ); + if (nodeId == null) { + return; + } + // All NodeIds are 32 bytes long + // The NodeGraph requires a fixed size for Node Ids + if (nodeId.length !== 32) { + return; + } return nodeId; } diff --git a/src/notifications/NotificationsManager.ts b/src/notifications/NotificationsManager.ts index 1726ebdf6..cb0fa470e 100644 --- a/src/notifications/NotificationsManager.ts +++ b/src/notifications/NotificationsManager.ts @@ -9,8 +9,8 @@ import type { DB, DBLevel } from '@matrixai/db'; import type { KeyManager } from '../keys'; import type { NodeManager } from '../nodes'; import type { NodeId } from '../nodes/types'; - import Logger from '@matrixai/logger'; +import { IdInternal } from '@matrixai/id'; import { Mutex } from 'async-mutex'; import { CreateDestroyStartStop, @@ -134,7 +134,7 @@ class NotificationsManager { reverse: true, }); for await (const o of keyStream) { - latestId = o as any as NotificationId; + latestId = IdInternal.fromBuffer(o); } this.notificationIdGenerator = createNotificationIdGenerator(latestId); this.logger.info(`Started ${this.constructor.name}`); @@ -207,7 +207,7 @@ class NotificationsManager { public async receiveNotification(notification: Notification) { await this._transaction(async () => { const nodePerms = await this.acl.getNodePerm( - nodesUtils.decodeNodeId(notification.senderId), + nodesUtils.decodeNodeId(notification.senderId)!, ); if (nodePerms === undefined) { throw new notificationsErrors.ErrorNotificationsPermissionsNotFound(); @@ -296,7 +296,7 @@ class NotificationsManager { for (const notification of notifications) { if ( notification.data.type === 'GestaltInvite' && - nodesUtils.decodeNodeId(notification.senderId).equals(fromNode) + nodesUtils.decodeNodeId(notification.senderId)!.equals(fromNode) ) { return notification; } @@ -349,7 +349,9 @@ class NotificationsManager { return await this._transaction(async () => { const notificationIds: Array = []; for await (const o of this.notificationsMessagesDb.createReadStream()) { - const notificationId = (o as any).key as NotificationId; + const notificationId = IdInternal.fromBuffer( + (o as any).key, + ); const data = (o as any).value as Buffer; const notification = await this.db.deserializeDecrypt( data, diff --git a/src/notifications/utils.ts b/src/notifications/utils.ts index 2895a7bfa..08532f524 100644 --- a/src/notifications/utils.ts +++ b/src/notifications/utils.ts @@ -10,11 +10,9 @@ import type { import type { KeyPairPem } from '../keys/types'; import type { NodeId } from '../nodes/types'; import type { VaultId } from '../vaults/types'; - import type { DefinedError } from 'ajv'; import { createPublicKey, createPrivateKey } from 'crypto'; import { SignJWT, exportJWK, jwtVerify, EmbeddedJWK } from 'jose'; - import { IdSortable } from '@matrixai/id'; import { notificationValidate, @@ -23,23 +21,15 @@ import { vaultShareNotificationValidate, } from './schema'; import * as notificationsErrors from './errors'; -import { isId, makeId } from '../GenericIdTypes'; - -function isNotificationId(arg: any): arg is NotificationId { - return isId(arg); -} - -function makeNotificationId(arg: any) { - return makeId(arg); -} +import * as nodesUtils from '../nodes/utils'; function createNotificationIdGenerator( lastId?: NotificationId, ): NotificationIdGenerator { - const idSortableGenerator = new IdSortable({ + const generator = new IdSortable({ lastId, }); - return (): NotificationId => makeNotificationId(idSortableGenerator.get()); + return () => generator.get(); } function constructGestaltInviteMessage(nodeId: NodeId): string { @@ -99,7 +89,11 @@ async function verifyAndDecodeNotif(notifJWT: string): Promise { function validateNotification( notification: Record, ): Notification { - if (notificationValidate(notification)) { + // Also ensure the sender's node ID is valid + if ( + notificationValidate(notification) && + nodesUtils.decodeNodeId(notification['senderId']) + ) { return notification as Notification; } else { for (const err of notificationValidate.errors as DefinedError[]) { @@ -149,8 +143,6 @@ function validateVaultShareNotification( } export { - isNotificationId, - makeNotificationId, createNotificationIdGenerator, signNotification, verifyAndDecodeNotif, diff --git a/src/sigchain/Sigchain.ts b/src/sigchain/Sigchain.ts index 6414902ab..57f85ca06 100644 --- a/src/sigchain/Sigchain.ts +++ b/src/sigchain/Sigchain.ts @@ -1,3 +1,4 @@ +import type { DB, DBLevel, DBOp } from '@matrixai/db'; import type { ChainDataEncoded } from './types'; import type { ClaimData, @@ -8,19 +9,16 @@ import type { ClaimType, } from '../claims/types'; import type { KeyManager } from '../keys'; -import type { DB, DBLevel, DBOp } from '@matrixai/db'; import type { NodeIdEncoded } from '../nodes/types'; - import Logger from '@matrixai/logger'; +import { IdInternal } from '@matrixai/id'; import { Mutex } from 'async-mutex'; import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; -import { utils as idUtils } from '@matrixai/id'; import * as sigchainErrors from './errors'; import * as claimsUtils from '../claims/utils'; -import { createClaimIdGenerator, makeClaimIdString } from '../sigchain/utils'; import { utils as nodesUtils } from '../nodes'; interface Sigchain extends CreateDestroyStartStop {} @@ -139,7 +137,7 @@ class Sigchain { // Creating the ID generator const latestId = await this.getLatestClaimId(); - this.generateClaimId = createClaimIdGenerator( + this.generateClaimId = claimsUtils.createClaimIdGenerator( nodesUtils.encodeNodeId(this.keyManager.getNodeId()), latestId, ); @@ -240,7 +238,7 @@ class Sigchain { { type: 'put', domain: this.sigchainClaimsDbDomain, - key: idUtils.toBuffer(claimId), + key: claimId.toBuffer(), value: claim, }, { @@ -280,7 +278,7 @@ class Sigchain { { type: 'put', domain: this.sigchainClaimsDbDomain, - key: idUtils.toBuffer(this.generateClaimId()), + key: this.generateClaimId().toBuffer(), value: claim, }, { @@ -327,13 +325,13 @@ class Sigchain { return await this._transaction(async () => { const chainData: ChainDataEncoded = {}; for await (const o of this.sigchainClaimsDb.createReadStream()) { - const claimId = (o as any).key as ClaimId; + const claimId = IdInternal.fromBuffer((o as any).key); const encryptedClaim = (o as any).value; const claim = await this.db.deserializeDecrypt( encryptedClaim, false, ); - chainData[makeClaimIdString(claimId)] = claim; + chainData[claimsUtils.encodeClaimId(claimId)] = claim; } return chainData; }); @@ -418,7 +416,7 @@ class Sigchain { return await this._transaction(async () => { const claim = await this.db.get( this.sigchainClaimsDbDomain, - idUtils.toBuffer(claimId), + claimId.toBuffer(), ); if (claim == null) { throw new sigchainErrors.ErrorSigchainClaimUndefined(); @@ -433,7 +431,7 @@ class Sigchain { const claimStream = this.sigchainClaimsDb.createKeyStream(); let seq = 1; for await (const o of claimStream) { - map[seq] = o as any as ClaimId; + map[seq] = IdInternal.fromBuffer(o); seq++; } return map; @@ -460,7 +458,7 @@ class Sigchain { reverse: true, }); for await (const o of keyStream) { - latestId = o as any as ClaimId; + latestId = IdInternal.fromBuffer(o); } return latestId; }); diff --git a/src/sigchain/types.ts b/src/sigchain/types.ts index 377ec109b..c55dc3379 100644 --- a/src/sigchain/types.ts +++ b/src/sigchain/types.ts @@ -1,16 +1,16 @@ -import type { Claim, ClaimEncoded, ClaimIdString } from '../claims/types'; +import type { Claim, ClaimEncoded, ClaimIdEncoded } from '../claims/types'; /** * Serialized version of a node's sigchain. * Currently used for storage in the gestalt graph. */ -type ChainData = Record; +type ChainData = Record; /** * Serialized version of a node's sigchain, but with the claims as * Should be used when needing to transport ChainData, such that the claims can * be verified without having to be re-encoded as ClaimEncoded types. */ -type ChainDataEncoded = Record; +type ChainDataEncoded = Record; export type { ChainData, ChainDataEncoded }; diff --git a/src/sigchain/utils.ts b/src/sigchain/utils.ts index 2d9276697..7f40dd6a3 100644 --- a/src/sigchain/utils.ts +++ b/src/sigchain/utils.ts @@ -1,11 +1,6 @@ import type { PublicKeyPem } from '../keys/types'; import type { ChainData, ChainDataEncoded } from './types'; -import type { ClaimId, ClaimIdString } from '../claims/types'; -import type { NodeIdEncoded } from '../nodes/types'; - -import { IdInternal, IdSortable } from '@matrixai/id'; import * as claimsUtils from '../claims/utils'; -import { isIdString, isId, makeIdString, makeId } from '../GenericIdTypes'; /** * Verifies each claim in a ChainDataEncoded record, and returns a ChainData @@ -29,35 +24,4 @@ async function verifyChainData( return decodedChain; } -function isClaimId(arg): arg is ClaimId { - return isId(arg); -} - -function makeClaimId(arg) { - return makeId(arg); -} - -function isClaimIdString(arg): arg is ClaimIdString { - return isIdString(arg); -} - -function makeClaimIdString(arg) { - return makeIdString(arg); -} - -function createClaimIdGenerator(nodeId: NodeIdEncoded, lastClaimId?: ClaimId) { - const generator = new IdSortable({ - lastId: lastClaimId, - nodeId: IdInternal.fromString(nodeId).toBuffer(), - }); - return () => makeClaimId(Buffer.from(generator.get())); -} - -export { - verifyChainData, - isClaimId, - makeClaimId, - isClaimIdString, - makeClaimIdString, - createClaimIdGenerator, -}; +export { verifyChainData }; diff --git a/src/status/Status.ts b/src/status/Status.ts index b1f5e4e32..4569f1ed9 100644 --- a/src/status/Status.ts +++ b/src/status/Status.ts @@ -6,11 +6,9 @@ import type { StatusDead, } from './types'; import type { FileSystem, FileHandle } from '../types'; -import type { NodeId } from '../nodes/types'; import Logger from '@matrixai/logger'; import lock from 'fd-lock'; import { StartStop, ready } from '@matrixai/async-init/dist/StartStop'; -import { IdInternal } from '@matrixai/id'; import * as statusErrors from './errors'; import * as statusUtils from './utils'; import { sleep, poll } from '../utils'; @@ -139,7 +137,7 @@ class Status { } let statusInfo; try { - statusInfo = JSON.parse(statusData, this.nodeIdReviver); + statusInfo = JSON.parse(statusData, this.statusReviver); } catch (e) { throw new statusErrors.ErrorStatusParse('JSON parsing failed'); } @@ -178,7 +176,7 @@ class Status { try { await statusFile.truncate(); await statusFile.write( - JSON.stringify(statusInfo, this.nodeIdReplacer, 2) + '\n', + JSON.stringify(statusInfo, this.statusReplacer, 2) + '\n', 0, 'utf-8', ); @@ -231,7 +229,7 @@ class Status { } let statusInfo; try { - statusInfo = JSON.parse(statusData, this.nodeIdReviver); + statusInfo = JSON.parse(statusData, this.statusReviver); } catch (e) { throw new statusErrors.ErrorStatusParse('JSON parsing failed'); } @@ -252,7 +250,7 @@ class Status { try { await statusFile.truncate(); await statusFile.write( - JSON.stringify(statusInfo, this.nodeIdReplacer, 2) + '\n', + JSON.stringify(statusInfo, this.statusReplacer, 2) + '\n', 0, 'utf-8', ); @@ -314,14 +312,29 @@ class Status { return statusInfo; } - private nodeIdReplacer = (key, value) => { - if (key === 'nodeId') - return nodesUtils.encodeNodeId(IdInternal.create(value.data)); + /** + * Replacer used during encoding to JSON + * This is a function expression and not an arrow function expression + * because it needs to access the `this` inside the JSON.stringify + * in order to encode the `NodeId` before the `toJSON` of IdInternal is called + */ + protected statusReplacer = function (key: string, value: any): any { + if (key === 'nodeId') { + return nodesUtils.encodeNodeId(this[key]); + } return value; }; - private nodeIdReviver = (key, value) => { - if (key === 'nodeId') return nodesUtils.decodeNodeId(value); + /** + * Reviver used during decoding from JSON + */ + protected statusReviver = function (key: string, value: any): any { + if (key === 'nodeId') { + value = nodesUtils.decodeNodeId(value); + if (value == null) { + throw new statusErrors.ErrorStatusParse('Invalid nodeId'); + } + } return value; }; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 55c296086..17dd51405 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,4 +2,5 @@ export { default as sysexits } from './sysexits'; export * from './locks'; export * from './context'; export * from './utils'; +export * from './matchers'; export * as errors from './errors'; diff --git a/src/utils/matchers.ts b/src/utils/matchers.ts new file mode 100644 index 000000000..89d8a4482 --- /dev/null +++ b/src/utils/matchers.ts @@ -0,0 +1,70 @@ +import { isDeepStrictEqual } from 'util'; + +type Proc = (data: any) => Promise | T; +type Case = [...patterns: [any, ...Array], proc: Proc]; + +type ProcSync = (data: any) => T; +type CaseSync = [...patterns: [any, ...Array], proc: ProcSync]; + +/** + * Poor man's pattern matching in JS + * Use it like a switch-case that is capable of doing deep equality + * Unlike switch-case, it cannot assert the data type in the procedure + * Without a default case, the returned value is undefined + */ +function match( + data: any, +): ( + ...cases: readonly [...Array>, Proc] | readonly [...Array>] +) => Promise { + return async (...cases) => { + for (const case_ of cases) { + // Default case + if (typeof case_ === 'function') { + return await case_(data); + } + // Last item has to be the procedure + const [proc, ...patterns] = [case_.pop(), ...case_]; + for (const p of patterns) { + if (isDeepStrictEqual(data, p)) { + return await proc(data); + } + } + } + return; + }; +} + +/** + * Poor man's pattern matching in JS + * Use it like a switch-case that is capable of doing deep equality + * Unlike switch-case, it cannot assert the data type in the procedure + * Without a default case, the returned value is undefined + * Synchronous version + */ +function matchSync( + data: any, +): ( + ...cases: + | readonly [...Array>, ProcSync] + | readonly [...Array>] +) => T { + return (...cases) => { + for (const case_ of cases) { + // Default case + if (typeof case_ === 'function') { + return case_(data); + } + // Last item has to be the procedure + const [proc, ...patterns] = [case_.pop(), ...case_]; + for (const p of patterns) { + if (isDeepStrictEqual(data, p)) { + return proc(data); + } + } + } + return; + }; +} + +export { match, matchSync }; diff --git a/src/validation/errors.ts b/src/validation/errors.ts new file mode 100644 index 000000000..2322ef90c --- /dev/null +++ b/src/validation/errors.ts @@ -0,0 +1,42 @@ +import { CustomError } from 'ts-custom-error'; +import { ErrorPolykey, sysexits } from '../errors'; + +/** + * This packages the `ErrorParse` array into the `data` property + * This is to allow encoding to and decoding from GRPC errors + */ +class ErrorValidation extends ErrorPolykey { + public readonly errors: Array; + constructor(errors: Array) { + const message = errors.map((e) => e.message).join('; '); + const data = { + errors: errors.map((e) => ({ + message: e.message, + keyPath: e.keyPath, + value: e.value.valueOf(), + })), + }; + super(message, data); + this.description = 'Input data failed validation'; + this.exitCode = sysexits.DATAERR; + this.errors = errors; + } +} + +/** + * Exception to be thrown during parsing failure + * This is not part of the Polykey exception hierarchy + * This is because it plain exception wrapper parsing error data + * While JS allows us to throw POJOs directly, having a nominal type + * is easier to check against + */ +class ErrorParse extends CustomError { + public keyPath: Array; + public value: any; + public context: object; + constructor(message?: string) { + super(message); + } +} + +export { ErrorValidation, ErrorParse }; diff --git a/src/validation/index.ts b/src/validation/index.ts new file mode 100644 index 000000000..295e63c89 --- /dev/null +++ b/src/validation/index.ts @@ -0,0 +1,107 @@ +import * as validationErrors from './errors'; +import * as validationUtils from './utils'; + +async function validate( + parser: (keyPath: Array, value: any) => Promise, + data: any, + options: { mode: 'greedy' | 'lazy' } = { mode: 'lazy' }, +): Promise { + const errors: Array = []; + const parse_ = async ( + keyPath: Array, + value: any, + context: object, + ) => { + if (typeof value === 'object' && value != null) { + for (const key in value) { + value[key] = await parse_([...keyPath, key], value[key], value); + } + } + try { + value = await parser.bind(context)(keyPath, value); + } catch (e) { + if (e instanceof validationErrors.ErrorParse) { + e.keyPath = keyPath; + e.value = value; + e.context = context; + errors.push(e); + // If lazy mode, short circuit evaluation + // And throw the error up + if (options.mode === 'lazy') { + throw e; + } + } else { + throw e; + } + } + return value; + }; + try { + // The root context is an object containing the root data but keyed with undefined + data = await parse_([], data, { undefined: data }); + } catch (e) { + if (e instanceof validationErrors.ErrorParse) { + throw new validationErrors.ErrorValidation(errors); + } else { + throw e; + } + } + if (errors.length > 0) { + throw new validationErrors.ErrorValidation(errors); + } + return data; +} + +function validateSync( + parser: (keyPath: Array, value: any) => any, + data: any, + options: { mode: 'greedy' | 'lazy' } = { mode: 'lazy' }, +): any { + const errors: Array = []; + const parse_ = (keyPath: Array, value: any, context: object) => { + if (typeof value === 'object' && value != null) { + for (const key in value) { + value[key] = parse_([...keyPath, key], value[key], value); + } + } + try { + value = parser.bind(context)(keyPath, value); + } catch (e) { + if (e instanceof validationErrors.ErrorParse) { + e.keyPath = keyPath; + e.value = value; + e.context = context; + errors.push(e); + // If lazy mode, short circuit evaluation + // And throw the error up + if (options.mode === 'lazy') { + throw e; + } + } else { + throw e; + } + } + return value; + }; + try { + // The root context is an object containing the root data but keyed with undefined + data = parse_([], data, { undefined: data }); + } catch (e) { + if (e instanceof validationErrors.ErrorParse) { + throw new validationErrors.ErrorValidation(errors); + } else { + throw e; + } + } + if (errors.length > 0) { + throw new validationErrors.ErrorValidation(errors); + } + return data; +} + +export { + validate, + validateSync, + validationErrors as errors, + validationUtils as utils, +}; diff --git a/src/validation/utils.ts b/src/validation/utils.ts new file mode 100644 index 000000000..5659fb878 --- /dev/null +++ b/src/validation/utils.ts @@ -0,0 +1,190 @@ +/** + * Parsing utilities for validating all input data + * This pulls in domain types and performs procedural-validation + * All functions here must marshal `data: any` into their respective domain type + * Failing to do so, they must throw the `validationErrors.ErrorParse` + * The parse error message must focus on why the validation failed + * @module + */ +import type { NodeId } from '../nodes/types'; +import type { ProviderId, IdentityId } from '../identities/types'; +import type { GestaltAction, GestaltId } from '../gestalts/types'; +import type { VaultAction } from '../vaults/types'; +import type { Host, Hostname, Port } from '../network/types'; +import type { ClaimId } from '../claims/types'; +import * as validationErrors from './errors'; +import * as nodesUtils from '../nodes/utils'; +import * as gestaltsUtils from '../gestalts/utils'; +import * as vaultsUtils from '../vaults/utils'; +import * as networkUtils from '../network/utils'; +import * as claimsUtils from '../claims/utils'; + +function parseInteger(data: any): number { + data = parseInt(data); + if (isNaN(data)) { + throw new validationErrors.ErrorParse('Number is invalid'); + } + return data; +} + +function parseNumber(data: any): number { + data = parseFloat(data); + if (isNaN(data)) { + throw new validationErrors.ErrorParse('Number is invalid'); + } + return data; +} + +function parseNodeId(data: any): NodeId { + data = nodesUtils.decodeNodeId(data); + if (data == null) { + throw new validationErrors.ErrorParse( + 'Node ID must be multibase base32hex encoded public-keys', + ); + } + return data; +} + +function parseGestaltId(data: any): GestaltId { + if (typeof data !== 'string') { + throw new validationErrors.ErrorParse('Gestalt ID must be string'); + } + data = nodesUtils.decodeNodeId(data); + if (data != null) { + return { + type: 'node', + nodeId: data, + }; + } + const match = (data as string).match(/^(.+):(.+)$/); + if (match == null) { + throw new validationErrors.ErrorParse( + 'Gestalt ID must be either a Node ID or `Provider ID:Identity ID`', + ); + } + const providerId = parseProviderId(match[1]); + const identityId = parseIdentityId(match[2]); + return { + type: 'identity', + providerId, + identityId, + }; +} + +function parseClaimId(data: any): ClaimId { + data = claimsUtils.decodeClaimId(data); + if (data == null) { + throw new validationErrors.ErrorParse( + 'Claim ID must be multibase base32hex encoded strings', + ); + } + return data; +} + +function parseGestaltAction(data: any): GestaltAction { + if (!gestaltsUtils.isGestaltAction(data)) { + throw new validationErrors.ErrorParse( + 'Gestalt action must be `notify` or `scan`', + ); + } + return data; +} + +function parseVaultAction(data: any): VaultAction { + if (!vaultsUtils.isVaultAction(data)) { + throw new validationErrors.ErrorParse( + 'Vault action must be `clone` or `pull`', + ); + } + return data; +} + +function parseProviderId(data: any): ProviderId { + if (typeof data !== 'string') { + throw new validationErrors.ErrorParse('Provider ID must be a string'); + } + if (data.length < 1) { + throw new validationErrors.ErrorParse( + 'Provider ID length must be greater than 0', + ); + } + return data as ProviderId; +} + +function parseIdentityId(data: any): IdentityId { + if (typeof data !== 'string') { + throw new validationErrors.ErrorParse('Provider ID must be a string'); + } + if (data.length < 1) { + throw new validationErrors.ErrorParse( + 'Identity ID length must be greater than 0', + ); + } + return data as IdentityId; +} + +function parseHost(data: any): Host { + if (!networkUtils.isHost(data)) { + throw new validationErrors.ErrorParse( + 'Host must be an IPv4 or IPv6 address', + ); + } + return data; +} + +function parseHostname(data: any): Hostname { + if (!networkUtils.isHostname(data)) { + throw new validationErrors.ErrorParse( + 'Hostname must follow RFC 1123 standard', + ); + } + return data; +} + +function parseHostOrHostname(data: any): Host | Hostname { + if (!networkUtils.isHost(data) && !networkUtils.isHostname(data)) { + throw new validationErrors.ErrorParse( + 'Host must be IPv4 or IPv6 address or hostname', + ); + } + return data; +} + +/** + * Parses number into a Port + * Data can be a string-number + */ +function parsePort(data: any): Port { + if (typeof data === 'string') { + try { + data = parseInteger(data); + } catch (e) { + if (e instanceof validationErrors.ErrorParse) { + e.message = 'Port must be a number'; + } + throw e; + } + } + if (!networkUtils.isPort(data)) { + throw new validationErrors.ErrorParse( + 'Port must be a number between 0 and 65535 inclusive', + ); + } + return data; +} + +export { + parseInteger, + parseNumber, + parseNodeId, + parseGestaltId, + parseClaimId, + parseGestaltAction, + parseVaultAction, + parseProviderId, + parseIdentityId, + parseHost, + parseHostname, + parseHostOrHostname, + parsePort, +}; diff --git a/src/vaults/VaultManager.ts b/src/vaults/VaultManager.ts index 61ed6eb2f..e17d90d7b 100644 --- a/src/vaults/VaultManager.ts +++ b/src/vaults/VaultManager.ts @@ -357,12 +357,12 @@ class VaultManager { await this.acl.setNodeAction(nodeId, 'scan'); await this.acl.setVaultAction( vaultId, - nodesUtils.decodeNodeId(nodes[node].id), + nodesUtils.decodeNodeId(nodes[node].id)!, 'pull', ); await this.acl.setVaultAction( vaultId, - nodesUtils.decodeNodeId(nodes[node].id), + nodesUtils.decodeNodeId(nodes[node].id)!, 'clone', ); } diff --git a/src/vaults/types.ts b/src/vaults/types.ts index 2a483f43c..559b89bd8 100644 --- a/src/vaults/types.ts +++ b/src/vaults/types.ts @@ -7,6 +7,8 @@ import type { FdIndex } from 'encryptedfs/dist/fd/types'; import type { EncryptedFS } from 'encryptedfs'; import type { Id, IdString } from '../GenericIdTypes'; +const vaultActions = ['clone', 'pull'] as const; + /** * Randomly generated vault ID for each new vault */ @@ -21,7 +23,7 @@ type VaultKey = Opaque<'VaultKey', Buffer>; /** * Actions relating to what is possible with vaults */ -type VaultAction = 'clone' | 'pull'; +type VaultAction = typeof vaultActions[number]; type VaultList = Map; @@ -148,6 +150,8 @@ type CommitLog = { message: string; }; +export { vaultActions }; + export type { VaultId, VaultIdPretty, diff --git a/src/vaults/utils.ts b/src/vaults/utils.ts index ab7e33568..b9987c04f 100644 --- a/src/vaults/utils.ts +++ b/src/vaults/utils.ts @@ -4,16 +4,17 @@ import type { VaultKey, VaultList, VaultName, + VaultAction, FileSystemReadable, VaultIdPretty, } from './types'; import type { FileSystem } from '../types'; import type { NodeId } from '../nodes/types'; - import type { GRPCClientAgent } from '../agent'; import path from 'path'; import { IdRandom } from '@matrixai/id'; import * as grpc from '@grpc/grpc-js'; +import { vaultActions } from './types'; import * as vaultsErrors from './errors'; import { GitRequest } from '../git'; import { promisify } from '../utils'; @@ -244,6 +245,11 @@ async function requestVaultNames( return data; } +function isVaultAction(action: any): action is VaultAction { + if (typeof action !== 'string') return false; + return (vaultActions as Readonly>).includes(action); +} + export { isVaultId, isVaultIdPretty, @@ -257,4 +263,5 @@ export { readdirRecursivelyEFS2, constructGitHandler, searchVaultName, + isVaultAction, }; diff --git a/tests/agent/GRPCClientAgent.test.ts b/tests/agent/GRPCClientAgent.test.ts index a39ab0c4f..30710e335 100644 --- a/tests/agent/GRPCClientAgent.test.ts +++ b/tests/agent/GRPCClientAgent.test.ts @@ -1,6 +1,6 @@ import type * as grpc from '@grpc/grpc-js'; import type { NodeAddress, NodeIdEncoded, NodeInfo } from '@/nodes/types'; -import type { ClaimIdString, ClaimIntermediary } from '@/claims/types'; +import type { ClaimIdEncoded, ClaimIntermediary } from '@/claims/types'; import type { Host, Port, TLSConfig } from '@/network/types'; import fs from 'fs'; import os from 'os'; @@ -37,7 +37,7 @@ describe(GRPCClientAgent.name, () => { id: 'v359vgrgmqf1r5g4fvisiddjknjko6bmm4qv7646jr7fi9enbfuug' as NodeIdEncoded, chain: {}, }; - const nodeId1 = nodesUtils.decodeNodeId(node1.id); + const nodeId1 = nodesUtils.decodeNodeId(node1.id)!; let mockedGenerateKeyPair: jest.SpyInstance; let mockedGenerateDeterministicKeyPair: jest.SpyInstance; beforeAll(async () => { @@ -255,11 +255,11 @@ describe(GRPCClientAgent.name, () => { const nodeIdX = nodesUtils.decodeNodeId( 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', - ); + )!; const nodeIdXEncoded = nodesUtils.encodeNodeId(nodeIdX); const nodeIdY = nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ); + )!; const nodeIdYEncoded = nodesUtils.encodeNodeId(nodeIdY); beforeEach(async () => { @@ -389,7 +389,7 @@ describe(GRPCClientAgent.name, () => { expect(Object.keys(chain).length).toBe(1); // Iterate just to be safe, but expected to only have this single claim for (const c of Object.keys(chain)) { - const claimId = c as ClaimIdString; + const claimId = c as ClaimIdEncoded; expect(chain[claimId]).toStrictEqual(doublyResponse); } }, diff --git a/tests/bin/identities/identities.test.ts b/tests/bin/identities/identities.test.ts index 2e6818b57..ab9ab0ffa 100644 --- a/tests/bin/identities/identities.test.ts +++ b/tests/bin/identities/identities.test.ts @@ -39,10 +39,10 @@ describe('CLI Identities', () => { // Defining constants const nodeId1Encoded = 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded; - const nodeId1 = nodesUtils.decodeNodeId(nodeId1Encoded); + const nodeId1 = nodesUtils.decodeNodeId(nodeId1Encoded)!; const nodeId2Encoded = 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg' as NodeIdEncoded; - const nodeId2 = nodesUtils.decodeNodeId(nodeId2Encoded); + const nodeId2 = nodesUtils.decodeNodeId(nodeId2Encoded)!; const nodeId3Encoded = 'v359vgrgmqf1r5g4fvisiddjknjko6bmm4qv7646jr7fi9enbfuug' as NodeIdEncoded; // Const nodeId3 = nodesUtils.decodeNodeId(nodeId3Encoded); diff --git a/tests/bin/nodes/add.test.ts b/tests/bin/nodes/add.test.ts index e1feba991..b478447c5 100644 --- a/tests/bin/nodes/add.test.ts +++ b/tests/bin/nodes/add.test.ts @@ -6,6 +6,7 @@ import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { IdInternal } from '@matrixai/id'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; +import { sysexits } from '@/utils'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; @@ -92,8 +93,7 @@ describe('add', () => { port.toString(), ]); const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toContain('Invalid node ID.'); + expect(result.exitCode).toBe(sysexits.USAGE); }, global.failedConnectionTimeout, ); @@ -107,8 +107,7 @@ describe('add', () => { port.toString(), ]); const result = await testBinUtils.pkStdio(commands, {}, dataDir); - expect(result.exitCode).not.toBe(0); - expect(result.stderr).toContain('Invalid IP address.'); + expect(result.exitCode).toBe(sysexits.USAGE); // Checking if node was added. const res = await polykeyAgent.nodeManager.getNode(validNodeId); diff --git a/tests/bin/nodes/find.test.ts b/tests/bin/nodes/find.test.ts index 039cd7342..cb9a09bed 100644 --- a/tests/bin/nodes/find.test.ts +++ b/tests/bin/nodes/find.test.ts @@ -177,7 +177,7 @@ describe('find', () => { async () => { const unknownNodeId = nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ); + )!; const commands = genCommands([ 'find', nodesUtils.encodeNodeId(unknownNodeId), diff --git a/tests/bin/nodes/ping.test.ts b/tests/bin/nodes/ping.test.ts index 00a1262bf..c86424fa9 100644 --- a/tests/bin/nodes/ping.test.ts +++ b/tests/bin/nodes/ping.test.ts @@ -124,7 +124,7 @@ describe('ping', () => { async () => { const fakeNodeId = nodesUtils.decodeNodeId( 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', - ); + )!; const commands = genCommands([ 'ping', nodesUtils.encodeNodeId(fakeNodeId), diff --git a/tests/bin/vaults/vaults.test.ts b/tests/bin/vaults/vaults.test.ts index fed187a05..da1c0ce78 100644 --- a/tests/bin/vaults/vaults.test.ts +++ b/tests/bin/vaults/vaults.test.ts @@ -41,7 +41,7 @@ describe('CLI vaults', () => { // Constants const nodeId1Encoded = 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0' as NodeIdEncoded; - const nodeId1 = nodesUtils.decodeNodeId(nodeId1Encoded); + const nodeId1 = nodesUtils.decodeNodeId(nodeId1Encoded)!; const nodeId2Encoded = 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg' as NodeIdEncoded; // Const nodeId2 = nodesUtils.decodeNodeId(nodeId2Encoded); diff --git a/tests/client/rpcGestalts.test.ts b/tests/client/rpcGestalts.test.ts index 88019f823..b314d1632 100644 --- a/tests/client/rpcGestalts.test.ts +++ b/tests/client/rpcGestalts.test.ts @@ -49,7 +49,7 @@ describe('Client service', () => { const nodeId2Encoded = 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg' as NodeIdEncoded; - const nodeId2 = nodesUtils.decodeNodeId(nodeId2Encoded); + const nodeId2 = nodesUtils.decodeNodeId(nodeId2Encoded)!; const testToken = { providerId: 'test-provider' as ProviderId, diff --git a/tests/client/service/agentUnlock.test.ts b/tests/client/service/agentUnlock.test.ts index 952259e47..a2fd589db 100644 --- a/tests/client/service/agentUnlock.test.ts +++ b/tests/client/service/agentUnlock.test.ts @@ -35,7 +35,7 @@ describe('agentUnlock', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ), + )!, host: '127.0.0.1' as Host, port: grpcServer.port, logger, diff --git a/tests/client/service/identitiesAuthenticate.test.ts b/tests/client/service/identitiesAuthenticate.test.ts index 123171969..cd273de32 100644 --- a/tests/client/service/identitiesAuthenticate.test.ts +++ b/tests/client/service/identitiesAuthenticate.test.ts @@ -68,7 +68,7 @@ describe('identitiesAuthenticate', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ), + )!, host: '127.0.0.1' as Host, port: grpcServer.port, logger, diff --git a/tests/client/service/identitiesClaim.test.ts b/tests/client/service/identitiesClaim.test.ts index ed2092261..88b00e205 100644 --- a/tests/client/service/identitiesClaim.test.ts +++ b/tests/client/service/identitiesClaim.test.ts @@ -22,7 +22,6 @@ import { import identitiesClaim from '@/client/service/identitiesClaim'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; import * as claimsUtils from '@/claims/utils'; -import { createClaimIdGenerator } from '@/sigchain/utils'; import TestProvider from '../../identities/TestProvider'; import * as testUtils from '../../utils'; @@ -46,7 +45,7 @@ describe('identitiesClaim', () => { provider: testToken.providerId, identity: testToken.identityId, }; - const claimId = createClaimIdGenerator(claimData.node)(); + const claimId = claimsUtils.createClaimIdGenerator(claimData.node)(); let mockedGenerateKeyPair: jest.SpyInstance; let mockedGenerateDeterministicKeyPair: jest.SpyInstance; let mockedAddClaim: jest.SpyInstance; diff --git a/tests/client/service/identitiesInfoGet.test.ts b/tests/client/service/identitiesInfoGet.test.ts index 2370a0164..344444210 100644 --- a/tests/client/service/identitiesInfoGet.test.ts +++ b/tests/client/service/identitiesInfoGet.test.ts @@ -77,7 +77,7 @@ describe('identitiesInfoGet', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ), + )!, host: '127.0.0.1' as Host, port: grpcServer.port, logger, diff --git a/tests/client/service/identitiesInfoGetConnected.test.ts b/tests/client/service/identitiesInfoGetConnected.test.ts index e4094ad13..7a40b97a4 100644 --- a/tests/client/service/identitiesInfoGetConnected.test.ts +++ b/tests/client/service/identitiesInfoGetConnected.test.ts @@ -95,7 +95,7 @@ describe('identitiesInfoGetConnected', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ), + )!, host: '127.0.0.1' as Host, port: grpcServer.port, logger, diff --git a/tests/client/service/identitiesProvidersList.test.ts b/tests/client/service/identitiesProvidersList.test.ts index 35d15c67d..5d6fc579c 100644 --- a/tests/client/service/identitiesProvidersList.test.ts +++ b/tests/client/service/identitiesProvidersList.test.ts @@ -73,7 +73,7 @@ describe('identitiesProvidersList', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ), + )!, host: '127.0.0.1' as Host, port: grpcServer.port, logger, diff --git a/tests/client/service/identitiesTokenDelete.test.ts b/tests/client/service/identitiesTokenDelete.test.ts index 805c60bd1..91ac6da26 100644 --- a/tests/client/service/identitiesTokenDelete.test.ts +++ b/tests/client/service/identitiesTokenDelete.test.ts @@ -67,7 +67,7 @@ describe('identitiesTokenDelete', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ), + )!, host: '127.0.0.1' as Host, port: grpcServer.port, logger, diff --git a/tests/client/service/identitiesTokenGet.test.ts b/tests/client/service/identitiesTokenGet.test.ts index ecda3a347..6d8a2873c 100644 --- a/tests/client/service/identitiesTokenGet.test.ts +++ b/tests/client/service/identitiesTokenGet.test.ts @@ -66,7 +66,7 @@ describe('identitiesTokenGet', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ), + )!, host: '127.0.0.1' as Host, port: grpcServer.port, logger, diff --git a/tests/client/service/identitiesTokenPut.test.ts b/tests/client/service/identitiesTokenPut.test.ts index 6f1bf7a11..d5e3a7192 100644 --- a/tests/client/service/identitiesTokenPut.test.ts +++ b/tests/client/service/identitiesTokenPut.test.ts @@ -67,7 +67,7 @@ describe('identitiesTokenPut', () => { grpcClient = await GRPCClientClient.createGRPCClientClient({ nodeId: nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ), + )!, host: '127.0.0.1' as Host, port: grpcServer.port, logger, diff --git a/tests/client/service/nodesAdd.test.ts b/tests/client/service/nodesAdd.test.ts index 472f2e36a..740cd5f4f 100644 --- a/tests/client/service/nodesAdd.test.ts +++ b/tests/client/service/nodesAdd.test.ts @@ -149,7 +149,7 @@ describe('nodesAdd', () => { const result = await nodeManager.getNode( nodesUtils.decodeNodeId( 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', - ), + )!, ); expect(result).toBeDefined(); expect(result!.host).toBe('127.0.0.1'); diff --git a/tests/grpc/GRPCClient.test.ts b/tests/grpc/GRPCClient.test.ts index 194f79294..a4f83a1e0 100644 --- a/tests/grpc/GRPCClient.test.ts +++ b/tests/grpc/GRPCClient.test.ts @@ -12,7 +12,6 @@ import { DB } from '@matrixai/db'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { utils as keysUtils } from '@/keys'; import { Session, SessionManager } from '@/sessions'; -import { utils as networkUtils } from '@/network'; import { errors as grpcErrors } from '@/grpc'; import * as clientUtils from '@/client/utils'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; @@ -47,7 +46,7 @@ describe('GRPCClient', () => { serverKeyPair.privateKey, 31536000, ); - nodeIdServer = networkUtils.certNodeId(serverCert); + nodeIdServer = keysUtils.certNodeId(serverCert)!; const dbPath = path.join(dataDir, 'db'); db = await DB.createDB({ dbPath, diff --git a/tests/grpc/GRPCServer.test.ts b/tests/grpc/GRPCServer.test.ts index 49ba78a28..780ebdea5 100644 --- a/tests/grpc/GRPCServer.test.ts +++ b/tests/grpc/GRPCServer.test.ts @@ -8,7 +8,6 @@ import { DB } from '@matrixai/db'; import { GRPCServer, utils as grpcUtils } from '@/grpc'; import { KeyManager, utils as keysUtils } from '@/keys'; import { SessionManager } from '@/sessions'; -import * as networkUtils from '@/network/utils'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as grpcErrors from '@/grpc/errors'; import * as clientUtils from '@/client/utils'; @@ -143,7 +142,7 @@ describe('GRPCServer', () => { certChainPem: keysUtils.certToPem(serverCert), }, }); - const nodeIdServer = networkUtils.certNodeId(serverCert); + const nodeIdServer = keysUtils.certNodeId(serverCert)!; const clientKeyPair = await keysUtils.generateKeyPair(4096); const clientCert = keysUtils.generateCertificate( clientKeyPair.publicKey, @@ -203,7 +202,7 @@ describe('GRPCServer', () => { 31536000, ); // First client connection - const nodeIdServer1 = networkUtils.certNodeId(serverCert1); + const nodeIdServer1 = keysUtils.certNodeId(serverCert1)!; const client1 = await testGrpcUtils.openTestClientSecure( nodeIdServer1, server.port, @@ -240,7 +239,7 @@ describe('GRPCServer', () => { const m2_ = await pCall2; expect(m2_.getChallenge()).toBe(m2.getChallenge()); // Second client connection - const nodeIdServer2 = networkUtils.certNodeId(serverCert2); + const nodeIdServer2 = keysUtils.certNodeId(serverCert2)!; const client2 = await testGrpcUtils.openTestClientSecure( nodeIdServer2, server.port, @@ -286,7 +285,7 @@ describe('GRPCServer', () => { certChainPem: keysUtils.certToPem(serverCert), }, }); - const nodeIdServer = networkUtils.certNodeId(serverCert); + const nodeIdServer = keysUtils.certNodeId(serverCert)!; const clientKeyPair = await keysUtils.generateKeyPair(4096); const clientCert = keysUtils.generateCertificate( clientKeyPair.publicKey, diff --git a/tests/network/ForwardProxy.test.ts b/tests/network/ForwardProxy.test.ts index a54c9558c..a1f9e2377 100644 --- a/tests/network/ForwardProxy.test.ts +++ b/tests/network/ForwardProxy.test.ts @@ -762,7 +762,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem = keysUtils.certToPem(serverCert); - const serverNodeId = networkUtils.certNodeId(serverCert); + const serverNodeId = keysUtils.certNodeId(serverCert)!; const fwdProxy = new ForwardProxy({ authToken, logger: logger.getChild( @@ -886,7 +886,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem = keysUtils.certToPem(serverCert); - const serverNodeId = networkUtils.certNodeId(serverCert); + const serverNodeId = keysUtils.certNodeId(serverCert)!; const fwdProxy = new ForwardProxy({ authToken, connEndTime: 5000, @@ -1034,7 +1034,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem = keysUtils.certToPem(serverCert); - const serverNodeId = networkUtils.certNodeId(serverCert); + const serverNodeId = keysUtils.certNodeId(serverCert)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const fwdProxy = new ForwardProxy({ authToken, @@ -1181,7 +1181,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem = keysUtils.certToPem(serverCert); - const serverNodeId = networkUtils.certNodeId(serverCert); + const serverNodeId = keysUtils.certNodeId(serverCert)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const fwdProxy = new ForwardProxy({ authToken, @@ -1351,7 +1351,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem = keysUtils.certToPem(serverCert); - const serverNodeId = networkUtils.certNodeId(serverCert); + const serverNodeId = keysUtils.certNodeId(serverCert)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const fwdProxy = new ForwardProxy({ authToken, @@ -1508,7 +1508,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem = keysUtils.certToPem(serverCert); - const serverNodeId = networkUtils.certNodeId(serverCert); + const serverNodeId = keysUtils.certNodeId(serverCert)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const fwdProxy = new ForwardProxy({ authToken, @@ -1637,7 +1637,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem = keysUtils.certToPem(serverCert); - const serverNodeId = networkUtils.certNodeId(serverCert); + const serverNodeId = keysUtils.certNodeId(serverCert)!; const fwdProxy = new ForwardProxy({ authToken, connKeepAliveTimeoutTime: 1000, @@ -1752,7 +1752,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem = keysUtils.certToPem(serverCert); - const serverNodeId = networkUtils.certNodeId(serverCert); + const serverNodeId = keysUtils.certNodeId(serverCert)!; const serverNodeIdEncoded = nodesUtils.encodeNodeId(serverNodeId); const fwdProxy = new ForwardProxy({ authToken, @@ -1892,7 +1892,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem = keysUtils.certToPem(serverCert); - const serverNodeId = networkUtils.certNodeId(serverCert); + const serverNodeId = keysUtils.certNodeId(serverCert)!; const fwdProxy = new ForwardProxy({ authToken, logger, @@ -1986,7 +1986,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem1 = keysUtils.certToPem(serverCert1); - const serverNodeId1 = networkUtils.certNodeId(serverCert1); + const serverNodeId1 = keysUtils.certNodeId(serverCert1)!; // Second server keys const serverKeyPair2 = await keysUtils.generateKeyPair(1024); const serverKeyPairPem2 = keysUtils.keyPairToPem(serverKeyPair2); @@ -1997,7 +1997,7 @@ describe(ForwardProxy.name, () => { 86400, ); const serverCertPem2 = keysUtils.certToPem(serverCert2); - const serverNodeId2 = networkUtils.certNodeId(serverCert2); + const serverNodeId2 = keysUtils.certNodeId(serverCert2)!; const fwdProxy = new ForwardProxy({ authToken, logger, diff --git a/tests/network/index.test.ts b/tests/network/index.test.ts index 913e045a0..17759511a 100644 --- a/tests/network/index.test.ts +++ b/tests/network/index.test.ts @@ -2,7 +2,7 @@ import type { Host, Port } from '@/network/types'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import grpc from '@grpc/grpc-js'; import { utils as keysUtils } from '@/keys'; -import { ForwardProxy, ReverseProxy, utils as networkUtils } from '@/network'; +import { ForwardProxy, ReverseProxy } from '@/network'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import { sleep } from '@/utils'; import { openTestServer, closeTestServer, GRPCClientTest } from '../grpc/utils'; @@ -30,7 +30,7 @@ describe('network index', () => { 12332432423, ); clientCertPem = keysUtils.certToPem(clientCert); - clientNodeId = networkUtils.certNodeId(clientCert); + clientNodeId = keysUtils.certNodeId(clientCert)!; // Server keys const serverKeyPair = await keysUtils.generateKeyPair(1024); serverKeyPairPem = keysUtils.keyPairToPem(serverKeyPair); @@ -41,7 +41,7 @@ describe('network index', () => { 12332432423, ); serverCertPem = keysUtils.certToPem(serverCert); - serverNodeId = networkUtils.certNodeId(serverCert); + serverNodeId = keysUtils.certNodeId(serverCert)!; }); let server; let revProxy; diff --git a/tests/network/utils.test.ts b/tests/network/utils.test.ts index 3a73d9cb1..4ef705fd8 100644 --- a/tests/network/utils.test.ts +++ b/tests/network/utils.test.ts @@ -28,7 +28,7 @@ describe('utils', () => { networkUtils.resolveHost('www.google.com' as Host), ).resolves.toBeDefined(); const host = await networkUtils.resolveHost('www.google.com' as Host); - expect(networkUtils.isValidHost(host)).toBeTruthy(); + expect(networkUtils.isHost(host)).toBeTruthy(); await expect( networkUtils.resolveHost('invalidHostname' as Host), ).rejects.toThrow(networkErrors.ErrorHostnameResolutionFailed); diff --git a/tests/nodes/NodeGraph.test.ts b/tests/nodes/NodeGraph.test.ts index d36e8cbbb..cdcd59bbb 100644 --- a/tests/nodes/NodeGraph.test.ts +++ b/tests/nodes/NodeGraph.test.ts @@ -43,7 +43,7 @@ describe('NodeGraph', () => { // const nodeId3 = makeNodeId('v359vgrgmqf1r5g4fvisiddjknjko6bmm4qv7646jr7fi9enbfuug'); const dummyNode = nodesUtils.decodeNodeId( 'vi3et1hrpv2m2lrplcm7cu913kr45v51cak54vm68anlbvuf83ra0', - ); + )!; const logger = new Logger('NodeGraph Test', LogLevel.WARN, [ new StreamHandler(), diff --git a/tests/nodes/NodeManager.test.ts b/tests/nodes/NodeManager.test.ts index 6f70c3e07..71c11ac46 100644 --- a/tests/nodes/NodeManager.test.ts +++ b/tests/nodes/NodeManager.test.ts @@ -1,4 +1,4 @@ -import type { ClaimIdString } from '@/claims/types'; +import type { ClaimIdEncoded } from '@/claims/types'; import type { CertificatePem, KeyPairPem, PublicKeyPem } from '@/keys/types'; import type { Host, Port } from '@/network/types'; import type { NodeId, NodeAddress } from '@/nodes/types'; @@ -45,13 +45,13 @@ describe('NodeManager', () => { const nodeId1 = nodesUtils.decodeNodeId( 'vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0', - ); + )!; const nodeId2 = nodesUtils.decodeNodeId( 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ); + )!; const dummyNode = nodesUtils.decodeNodeId( 'vi3et1hrpv2m2lrplcm7cu913kr45v51cak54vm68anlbvuf83ra0', - ); + )!; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -505,7 +505,7 @@ describe('NodeManager', () => { expect(Object.keys(xChain).length).toBe(1); // Iterate just to be safe, but expected to only have this single claim for (const c of Object.keys(xChain)) { - const claimId = c as ClaimIdString; + const claimId = c as ClaimIdEncoded; const claim = xChain[claimId]; const decoded = claimsUtils.decodeClaim(claim); expect(decoded).toStrictEqual({ @@ -541,7 +541,7 @@ describe('NodeManager', () => { expect(Object.keys(yChain).length).toBe(1); // Iterate just to be safe, but expected to only have this single claim for (const c of Object.keys(yChain)) { - const claimId = c as ClaimIdString; + const claimId = c as ClaimIdEncoded; const claim = yChain[claimId]; const decoded = claimsUtils.decodeClaim(claim); expect(decoded).toStrictEqual({ diff --git a/tests/notifications/NotificationsManager.test.ts b/tests/notifications/NotificationsManager.test.ts index b31b1e2f6..8952e9270 100644 --- a/tests/notifications/NotificationsManager.test.ts +++ b/tests/notifications/NotificationsManager.test.ts @@ -19,8 +19,6 @@ import { NodeManager } from '@/nodes'; import { NotificationsManager } from '@/notifications'; import { ForwardProxy, ReverseProxy } from '@/network'; import { AgentServiceService, createAgentService } from '@/agent'; - -import * as networkUtils from '@/network/utils'; import { generateVaultId } from '@/vaults/utils'; import { utils as nodesUtils } from '@/nodes'; import * as testUtils from '../utils'; @@ -95,7 +93,7 @@ describe('NotificationsManager', () => { }); senderKeyPairPem = senderKeyManager.getRootKeyPairPem(); senderCertPem = senderKeyManager.getRootCertPem(); - senderNodeId = networkUtils.certNodeId(senderKeyManager.getRootCert()); + senderNodeId = keysUtils.certNodeId(senderKeyManager.getRootCert())!; fwdTLSConfig = { keyPrivatePem: senderKeyPairPem.privateKey, certChainPem: senderCertPem, @@ -186,7 +184,7 @@ describe('NotificationsManager', () => { messageCap: 5, logger: logger, }); - receiverNodeId = networkUtils.certNodeId(receiverKeyManager.getRootCert()); + receiverNodeId = keysUtils.certNodeId(receiverKeyManager.getRootCert())!; await receiverGestaltGraph.setNode(node); await receiverNodeManager.start(); diff --git a/tests/validation/index.test.ts b/tests/validation/index.test.ts new file mode 100644 index 000000000..9cb633342 --- /dev/null +++ b/tests/validation/index.test.ts @@ -0,0 +1,412 @@ +import { + validate, + validateSync, + errors as validationErrors, +} from '@/validation'; + +describe('validation/index', () => { + test('validate primitives', async () => { + expect( + await validate((keyPath, value) => { + expect(keyPath).toEqual([]); + expect(value).toBe(123); + return value; + }, 123), + ).toBe(123); + expect( + await validate(async (keyPath, value) => { + expect(keyPath).toEqual([]); + expect(value).toBe('hello world'); + return value; + }, 'hello world'), + ).toBe('hello world'); + expect( + await validate((keyPath, value) => { + expect(keyPath).toEqual([]); + expect(value).toBe(null); + return value; + }, null), + ).toBe(null); + expect( + await validate((keyPath, value) => { + expect(keyPath).toEqual([]); + expect(value).toBe(undefined); + return value; + }, undefined), + ).toBeUndefined(); + }); + test('validate primitives - sync', () => { + expect( + validateSync((keyPath, value) => { + expect(keyPath).toEqual([]); + expect(value).toBe(123); + return value; + }, 123), + ).toBe(123); + expect( + validateSync((keyPath, value) => { + expect(keyPath).toEqual([]); + expect(value).toBe('hello world'); + return value; + }, 'hello world'), + ).toBe('hello world'); + expect( + validateSync((keyPath, value) => { + expect(keyPath).toEqual([]); + expect(value).toBe(null); + return value; + }, null), + ).toBe(null); + expect( + validateSync((keyPath, value) => { + expect(keyPath).toEqual([]); + expect(value).toBe(undefined); + return value; + }, undefined), + ).toBeUndefined(); + }); + test('validate objects', async () => { + // POJO + expect( + await validate( + (keyPath, value) => { + switch (keyPath.join('.')) { + case 'a': + value = value + 1; + break; + case 'b': + value = value + '!'; + break; + } + return value; + }, + { + a: 123, + b: 'hello world', + }, + ), + ).toStrictEqual({ + a: 124, + b: 'hello world!', + }); + // Array + expect( + await validate( + async (keyPath, value) => { + switch (keyPath.join('.')) { + case '0': + value = value + 1; + break; + case '1': + value = value + '!'; + break; + } + return value; + }, + [123, 'hello world'], + ), + ).toStrictEqual([124, 'hello world!']); + // Nested POJO and Array + expect( + await validate( + (keyPath, value) => { + switch (keyPath.join('.')) { + case 'arr.0.a': + case 'obj.arr.0': + if (value !== 123) { + throw new validationErrors.ErrorParse('should be 123'); + } + break; + case 'arr.0.b': + case 'obj.arr.1': + if (value !== 'hello world') { + throw new validationErrors.ErrorParse('should be hello world'); + } + break; + case 'arr': + expect(value).toStrictEqual([ + { + a: 123, + b: 'hello world', + }, + ]); + break; + case 'obj': + expect(value).toStrictEqual({ + arr: [123, 'hello world'], + }); + break; + } + return value; + }, + { + arr: [ + { + a: 123, + b: 'hello world', + }, + ], + obj: { + arr: [123, 'hello world'], + }, + }, + ), + ).toStrictEqual({ + arr: [ + { + a: 123, + b: 'hello world', + }, + ], + obj: { + arr: [123, 'hello world'], + }, + }); + }); + test('validate objects - sync', () => { + // POJO + expect( + validateSync( + (keyPath, value) => { + switch (keyPath.join('.')) { + case 'a': + value = value + 1; + break; + case 'b': + value = value + '!'; + break; + } + return value; + }, + { + a: 123, + b: 'hello world', + }, + ), + ).toStrictEqual({ + a: 124, + b: 'hello world!', + }); + // Array + expect( + validateSync( + (keyPath, value) => { + switch (keyPath.join('.')) { + case '0': + value = value + 1; + break; + case '1': + value = value + '!'; + break; + } + return value; + }, + [123, 'hello world'], + ), + ).toStrictEqual([124, 'hello world!']); + // Nested POJO and Array + expect( + validateSync( + (keyPath, value) => { + switch (keyPath.join('.')) { + case 'arr.0.a': + case 'obj.arr.0': + if (value !== 123) { + throw new validationErrors.ErrorParse('should be 123'); + } + break; + case 'arr.0.b': + case 'obj.arr.1': + if (value !== 'hello world') { + throw new validationErrors.ErrorParse('should be hello world'); + } + break; + case 'arr': + expect(value).toStrictEqual([ + { + a: 123, + b: 'hello world', + }, + ]); + break; + case 'obj': + expect(value).toStrictEqual({ + arr: [123, 'hello world'], + }); + break; + } + return value; + }, + { + arr: [ + { + a: 123, + b: 'hello world', + }, + ], + obj: { + arr: [123, 'hello world'], + }, + }, + ), + ).toStrictEqual({ + arr: [ + { + a: 123, + b: 'hello world', + }, + ], + obj: { + arr: [123, 'hello world'], + }, + }); + }); + test('validation error contains parse errors', async () => { + // Lazy mode + const f = () => + validateSync( + (keyPath, value) => { + switch (keyPath.join('.')) { + case 'arr.0.a': + case 'obj.arr.0': + if (value !== 123) { + throw new validationErrors.ErrorParse('should be 123'); + } + break; + case 'arr.0.b': + case 'obj.arr.1': + if (value !== 'hello world') { + throw new validationErrors.ErrorParse('should be hello world'); + } + break; + } + return value; + }, + { + arr: [ + { + a: '123', + b: 'foo bar', + }, + ], + obj: { + arr: ['123', 'foo bar'], + }, + }, + ); + expect(f).toThrow(validationErrors.ErrorValidation); + try { + f(); + } catch (e) { + expect(e).toBeInstanceOf(validationErrors.ErrorValidation); + const e_ = e as validationErrors.ErrorValidation; + // Lazy mode should only return 1 parse error + expect(e_.errors.length).toBe(1); + expect(e_.errors[0]).toBeInstanceOf(validationErrors.ErrorParse); + expect(e_.errors[0].keyPath).toStrictEqual(['arr', '0', 'a']); + expect(e_.errors[0].value).toBe('123'); + expect(e_.errors[0].context).toStrictEqual({ a: '123', b: 'foo bar' }); + } + // Greedy mode + const g = async () => + validate( + (keyPath, value) => { + switch (keyPath.join('.')) { + case 'arr.0.a': + case 'obj.arr.0': + if (value !== 123) { + throw new validationErrors.ErrorParse('should be 123'); + } + break; + case 'arr.0.b': + case 'obj.arr.1': + if (value !== 'hello world') { + throw new validationErrors.ErrorParse('should be hello world'); + } + break; + } + return value; + }, + { + arr: [ + { + a: '123', + b: 'foo bar', + }, + ], + obj: { + arr: ['123', 'foo bar'], + }, + }, + { + mode: 'greedy', + }, + ); + await expect(g).rejects.toThrow(validationErrors.ErrorValidation); + try { + await g(); + } catch (e) { + expect(e).toBeInstanceOf(validationErrors.ErrorValidation); + const e_ = e as validationErrors.ErrorValidation; + // Greedy mode should only return 4 parse errors + expect(e_.errors.length).toBe(4); + expect(e_.errors[0]).toBeInstanceOf(validationErrors.ErrorParse); + expect(e_.errors[0].keyPath).toStrictEqual(['arr', '0', 'a']); + expect(e_.errors[0].value).toBe('123'); + expect(e_.errors[0].context).toStrictEqual({ a: '123', b: 'foo bar' }); + expect(e_.errors[1]).toBeInstanceOf(validationErrors.ErrorParse); + expect(e_.errors[1].keyPath).toStrictEqual(['arr', '0', 'b']); + expect(e_.errors[1].value).toBe('foo bar'); + expect(e_.errors[1].context).toStrictEqual({ a: '123', b: 'foo bar' }); + expect(e_.errors[2]).toBeInstanceOf(validationErrors.ErrorParse); + expect(e_.errors[2].keyPath).toStrictEqual(['obj', 'arr', '0']); + expect(e_.errors[2].value).toBe('123'); + expect(e_.errors[2].context).toStrictEqual(['123', 'foo bar']); + expect(e_.errors[3]).toBeInstanceOf(validationErrors.ErrorParse); + expect(e_.errors[3].keyPath).toStrictEqual(['obj', 'arr', '1']); + expect(e_.errors[3].value).toBe('foo bar'); + expect(e_.errors[3].context).toStrictEqual(['123', 'foo bar']); + } + }); + test('manipulate `this` when using function expressions', async () => { + await validate((_, value) => { + expect(this).toEqual({}); + return value; + }, 'hello world'); + await validate(function (_, value) { + expect(this[undefined!]).toBe(value); + return value; + }, 'hello world'); + // Mutating the root context + expect( + await validate(function (_, value) { + // This has no effect because the same value is still returned + this[undefined!] = 'foo bar'; + return value; + }, 'hello world'), + ).toBe('hello world'); + // Mutating nested context + expect( + await validate( + async function (keyPath, value) { + if (keyPath[0] === 'a' && keyPath[1] === 'b') { + expect(this).toEqual({ b: 'c' }); + this['d'] = 'e'; + return 'c2'; + } + return value; + }, + { + a: { + b: 'c', + }, + }, + ), + ).toEqual({ + a: { + b: 'c2', + d: 'e', + }, + }); + }); +});