From 1d2af00699c1c43556b2f7930b7cabb276d8f2d3 Mon Sep 17 00:00:00 2001 From: Emma Casolin Date: Wed, 27 Apr 2022 11:28:59 +1000 Subject: [PATCH] feat: using `cause` property to establish error chain When any error is thrown as the result of another error occurring, the original error is now contained within the `cause` property of the new error. #304 --- src/bin/keys/CommandDecrypt.ts | 1 + src/bin/keys/CommandEncrypt.ts | 1 + src/bin/keys/CommandSign.ts | 1 + src/bin/keys/CommandVerify.ts | 1 + src/bin/nodes/CommandPing.ts | 2 +- src/bin/secrets/CommandCreate.ts | 1 + src/bin/secrets/CommandEdit.ts | 1 + src/bin/secrets/CommandUpdate.ts | 1 + src/bin/utils/processors.ts | 4 ++++ src/grpc/GRPCClient.ts | 8 +++++--- src/grpc/GRPCServer.ts | 10 ++++++---- .../providers/github/GitHubProvider.ts | 8 +++++++- src/keys/KeyManager.ts | 19 +++++++++++++++++-- src/network/ConnectionForward.ts | 1 + src/network/ConnectionReverse.ts | 2 ++ src/network/utils.ts | 2 +- src/nodes/NodeConnection.ts | 2 +- src/notifications/utils.ts | 2 +- src/schema/Schema.ts | 5 +++++ src/status/Status.ts | 12 +++++++++--- src/validation/utils.ts | 2 ++ src/vaults/VaultInternal.ts | 4 ++-- src/vaults/VaultManager.ts | 4 +++- src/vaults/VaultOps.ts | 3 +++ 24 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/bin/keys/CommandDecrypt.ts b/src/bin/keys/CommandDecrypt.ts index c45691596..aeb4a5191 100644 --- a/src/bin/keys/CommandDecrypt.ts +++ b/src/bin/keys/CommandDecrypt.ts @@ -58,6 +58,7 @@ class CommandDecrypt extends CommandPolykey { code: e.code, path: e.path, }, + cause: e, }); } cryptoMessage.setData(cipherText); diff --git a/src/bin/keys/CommandEncrypt.ts b/src/bin/keys/CommandEncrypt.ts index 3d8ddc601..2edef5b08 100644 --- a/src/bin/keys/CommandEncrypt.ts +++ b/src/bin/keys/CommandEncrypt.ts @@ -58,6 +58,7 @@ class CommandEncypt extends CommandPolykey { code: e.code, path: e.path, }, + cause: e, }); } cryptoMessage.setData(plainText); diff --git a/src/bin/keys/CommandSign.ts b/src/bin/keys/CommandSign.ts index e69007eb6..4d31dee5f 100644 --- a/src/bin/keys/CommandSign.ts +++ b/src/bin/keys/CommandSign.ts @@ -58,6 +58,7 @@ class CommandSign extends CommandPolykey { code: e.code, path: e.path, }, + cause: e, }); } cryptoMessage.setData(data); diff --git a/src/bin/keys/CommandVerify.ts b/src/bin/keys/CommandVerify.ts index c8c094193..7c0a5de49 100644 --- a/src/bin/keys/CommandVerify.ts +++ b/src/bin/keys/CommandVerify.ts @@ -66,6 +66,7 @@ class CommandVerify extends CommandPolykey { code: e.code, path: e.path, }, + cause: e, }); } cryptoMessage.setData(data); diff --git a/src/bin/nodes/CommandPing.ts b/src/bin/nodes/CommandPing.ts index b22e0d19d..11913011a 100644 --- a/src/bin/nodes/CommandPing.ts +++ b/src/bin/nodes/CommandPing.ts @@ -59,7 +59,7 @@ class CommandPing extends CommandPolykey { error = new binErrors.ErrorNodePingFailed( `Failed to resolve node ID ${nodesUtils.encodeNodeId( nodeId, - )} to an address.`, + )} to an address.`, { cause: err }, ); } else { throw err; diff --git a/src/bin/secrets/CommandCreate.ts b/src/bin/secrets/CommandCreate.ts index f2e2f1c3e..26f22cbbe 100644 --- a/src/bin/secrets/CommandCreate.ts +++ b/src/bin/secrets/CommandCreate.ts @@ -71,6 +71,7 @@ class CommandCreate extends CommandPolykey { code: e.code, path: e.path, }, + cause: e, }); } secretMessage.setSecretContent(content); diff --git a/src/bin/secrets/CommandEdit.ts b/src/bin/secrets/CommandEdit.ts index d1ac4e36f..f86b37499 100644 --- a/src/bin/secrets/CommandEdit.ts +++ b/src/bin/secrets/CommandEdit.ts @@ -80,6 +80,7 @@ class CommandEdit extends CommandPolykey { code: e.code, path: e.path, }, + cause: e, }); } secretMessage.setVault(vaultMessage); diff --git a/src/bin/secrets/CommandUpdate.ts b/src/bin/secrets/CommandUpdate.ts index c7fb3a6fb..c7b9e696d 100644 --- a/src/bin/secrets/CommandUpdate.ts +++ b/src/bin/secrets/CommandUpdate.ts @@ -71,6 +71,7 @@ class CommandUpdate extends CommandPolykey { code: e.code, path: e.path, }, + cause: e, }); } secretMessage.setSecretContent(content); diff --git a/src/bin/utils/processors.ts b/src/bin/utils/processors.ts index d41487173..df43437d0 100644 --- a/src/bin/utils/processors.ts +++ b/src/bin/utils/processors.ts @@ -95,6 +95,7 @@ async function processPassword( code: e.code, path: e.path, }, + cause: e, }); } } else if (typeof process.env['PK_PASSWORD'] === 'string') { @@ -139,6 +140,7 @@ async function processNewPassword( code: e.code, path: e.path, }, + cause: e, }); } } else if (!existing && typeof process.env['PK_PASSWORD'] === 'string') { @@ -177,6 +179,7 @@ async function processRecoveryCode( code: e.code, path: e.path, }, + cause: e, }); } } else if (typeof process.env['PK_RECOVERY_CODE'] === 'string') { @@ -384,6 +387,7 @@ async function processAuthentication( code: e.code, path: e.path, }, + cause: e, }); } meta = clientUtils.encodeAuthFromPassword(password); diff --git a/src/grpc/GRPCClient.ts b/src/grpc/GRPCClient.ts index b55d3a275..b705e9aa5 100644 --- a/src/grpc/GRPCClient.ts +++ b/src/grpc/GRPCClient.ts @@ -129,7 +129,7 @@ abstract class GRPCClient { } catch (e) { // If we fail here then we leak the client object... client.close(); - throw new grpcErrors.ErrorGRPCClientTimeout(); + throw new grpcErrors.ErrorGRPCClientTimeout(e.message, { cause: e }); } let serverCertChain: Array | undefined; if (channelCredentials._isSecure()) { @@ -151,8 +151,10 @@ abstract class GRPCClient { `Failed GRPC server certificate verification connecting to ${address}`, ); const e_ = new grpcErrors.ErrorGRPCClientVerification( - `${e.name}: ${e.message}`, - e.data, + `${e.name}: ${e.message}`,{ + data: e.data, + cause: e, + }, ); session.destroy(e_, http2.constants.NGHTTP2_PROTOCOL_ERROR); } diff --git a/src/grpc/GRPCServer.ts b/src/grpc/GRPCServer.ts index 7d6ab3d35..2c7edf11b 100644 --- a/src/grpc/GRPCServer.ts +++ b/src/grpc/GRPCServer.ts @@ -70,7 +70,7 @@ class GRPCServer { try { this.port = await bindAsync(address, serverCredentials); } catch (e) { - throw new grpcErrors.ErrorGRPCServerBind(e.message); + throw new grpcErrors.ErrorGRPCServerBind(e.message, { cause: e }); } if (serverCredentials._isSecure()) { // @ts-ignore hack for private property @@ -100,8 +100,10 @@ class GRPCServer { `Failed GRPC client certificate verification connecting from ${address}`, ); const e_ = new grpcErrors.ErrorGRPCServerVerification( - `${e.name}: ${e.message}`, - e.data, + `${e.name}: ${e.message}`, { + data: e.data, + cause: e, + }, ); session.destroy(e_, http2.constants.NGHTTP2_PROTOCOL_ERROR); } else { @@ -140,7 +142,7 @@ class GRPCServer { ...(timer != null ? [timer.timerP] : []), ]); } catch (e) { - throw new grpcErrors.ErrorGRPCServerShutdown(e.message); + throw new grpcErrors.ErrorGRPCServerShutdown(e.message, { cause: e }); } finally { if (timer != null) timerStop(timer); } diff --git a/src/identities/providers/github/GitHubProvider.ts b/src/identities/providers/github/GitHubProvider.ts index 4dc939999..122122931 100644 --- a/src/identities/providers/github/GitHubProvider.ts +++ b/src/identities/providers/github/GitHubProvider.ts @@ -120,7 +120,7 @@ class GitHubProvider extends Provider { data = await response.json(); } catch (e) { throw new identitiesErrors.ErrorProviderAuthentication( - 'Provider access token response is not valid JSON', + 'Provider access token response is not valid JSON', { cause: e }, ); } if (data.error) { @@ -200,6 +200,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } return data.login; @@ -246,6 +247,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } return { @@ -297,6 +299,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } for (const item of data) { @@ -343,6 +346,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } for (const item of data) { @@ -414,6 +418,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } return { @@ -466,6 +471,7 @@ class GitHubProvider extends Provider { } catch (e) { throw new identitiesErrors.ErrorProviderCall( `Provider response body is not valid JSON`, + { cause: e }, ); } const linkClaimData = data.files[this.gistFilename]?.content; diff --git a/src/keys/KeyManager.ts b/src/keys/KeyManager.ts index 22d1a4329..937c80d98 100644 --- a/src/keys/KeyManager.ts +++ b/src/keys/KeyManager.ts @@ -275,6 +275,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } try { @@ -422,6 +423,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } await this.garbageCollectRootCerts(); @@ -545,6 +547,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } } @@ -642,6 +645,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } return true; @@ -663,6 +667,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } let keyPair; @@ -675,7 +680,7 @@ class KeyManager { password, ); } catch (e) { - throw new keysErrors.ErrorRootKeysParse(e.message); + throw new keysErrors.ErrorRootKeysParse(e.message, { cause: e }); } return keyPair; } @@ -709,6 +714,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } } @@ -735,6 +741,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } const rootKeyPairBits = keysUtils.publicKeyBitSize( @@ -784,6 +791,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } return true; @@ -802,6 +810,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } let keysDbKeyPlain; @@ -811,7 +820,7 @@ class KeyManager { keysDbKeyCipher, ); } catch (e) { - throw new keysErrors.ErrorDBKeyParse(e.message); + throw new keysErrors.ErrorDBKeyParse(e.message, { cause: e }); } return keysDbKeyPlain; } @@ -838,6 +847,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } } @@ -881,6 +891,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } return true; @@ -901,6 +912,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } const rootCert = keysUtils.certFromPem(rootCertPem); @@ -924,6 +936,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } } @@ -945,6 +958,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } rootCertsNames.sort((a, b) => { @@ -984,6 +998,7 @@ class KeyManager { code: e.code, path: e.path, }, + cause: e, }); } return rootCertsPems; diff --git a/src/network/ConnectionForward.ts b/src/network/ConnectionForward.ts index b28d95f2f..5e8a829d4 100644 --- a/src/network/ConnectionForward.ts +++ b/src/network/ConnectionForward.ts @@ -170,6 +170,7 @@ class ConnectionForward extends Connection { errno: e.errno, syscall: e.syscall, }, + cause: e, }); } finally { clearInterval(punchInterval); diff --git a/src/network/ConnectionReverse.ts b/src/network/ConnectionReverse.ts index 265bdaa4a..ada434649 100644 --- a/src/network/ConnectionReverse.ts +++ b/src/network/ConnectionReverse.ts @@ -162,6 +162,7 @@ class ConnectionReverse extends Connection { errno: e.errno, syscall: e.syscall, }, + cause: e, }); } finally { clearInterval(punchInterval); @@ -254,6 +255,7 @@ class ConnectionReverse extends Connection { errno: e.errno, syscall: e.syscall, }, + cause: e, }); } tlsSocket.on('error', async (e) => { diff --git a/src/network/utils.ts b/src/network/utils.ts index 08dab06ff..f97f71ef9 100644 --- a/src/network/utils.ts +++ b/src/network/utils.ts @@ -101,7 +101,7 @@ async function resolveHost(host: Host | Hostname): Promise { // Resolve the hostname and get the IPv4 address resolvedHost = await lookup(host, 4); } catch (e) { - throw new networkErrors.ErrorHostnameResolutionFailed(e.message); + throw new networkErrors.ErrorHostnameResolutionFailed(e.message, { cause: e }); } // Returns an array of [ resolved address, family (4 or 6) ] return resolvedHost[0] as Host; diff --git a/src/nodes/NodeConnection.ts b/src/nodes/NodeConnection.ts index 6788c20fe..d81ac9721 100644 --- a/src/nodes/NodeConnection.ts +++ b/src/nodes/NodeConnection.ts @@ -135,7 +135,7 @@ class NodeConnection { await nodeConnection.destroy(); // If the connection times out, re-throw this with a higher level nodes exception if (e instanceof grpcErrors.ErrorGRPCClientTimeout) { - throw new nodesErrors.ErrorNodeConnectionTimeout(); + throw new nodesErrors.ErrorNodeConnectionTimeout(e.message, { cause: e }); } throw e; } diff --git a/src/notifications/utils.ts b/src/notifications/utils.ts index 08532f524..0f55a6620 100644 --- a/src/notifications/utils.ts +++ b/src/notifications/utils.ts @@ -78,7 +78,7 @@ async function verifyAndDecodeNotif(notifJWT: string): Promise { throw err; } else { // Error came from jose - throw new notificationsErrors.ErrorNotificationsParse(); + throw new notificationsErrors.ErrorNotificationsParse(err.message, { cause: err }); } } } diff --git a/src/schema/Schema.ts b/src/schema/Schema.ts index 9c107acff..b7c66be4c 100644 --- a/src/schema/Schema.ts +++ b/src/schema/Schema.ts @@ -88,6 +88,7 @@ class Schema { code: e.code, path: e.path, }, + cause: e, }); } } @@ -101,6 +102,7 @@ class Schema { code: e.code, path: e.path, }, + cause: e, }); } const stateVersion = await this.readVersion(); @@ -136,6 +138,7 @@ class Schema { code: e.code, path: e.path, }, + cause: e, }); } this.logger.info(`Destroyed ${this.constructor.name}`); @@ -160,6 +163,7 @@ class Schema { code: e.code, path: e.path, }, + cause: e, }); } const stateVersion = parseInt(stateVersionData.trim()); @@ -186,6 +190,7 @@ class Schema { code: e.code, path: e.path, }, + cause: e, }); } }); diff --git a/src/status/Status.ts b/src/status/Status.ts index 5a248ba9f..fbee3edce 100644 --- a/src/status/Status.ts +++ b/src/status/Status.ts @@ -118,6 +118,7 @@ class Status { code: e.code, path: e.path, }, + cause: e, }); } while (!lock(statusFile.fd)) { @@ -134,6 +135,7 @@ class Status { code: e.code, path: e.path, }, + cause: e, }); } if (statusData === '') { @@ -143,7 +145,7 @@ class Status { try { statusInfo = JSON.parse(statusData, this.statusReviver); } catch (e) { - throw new statusErrors.ErrorStatusParse('JSON parsing failed'); + throw new statusErrors.ErrorStatusParse('JSON parsing failed', {cause: e}); } if (!statusUtils.statusValidate(statusInfo)) { throw new statusErrors.ErrorStatusParse( @@ -192,6 +194,7 @@ class Status { code: e.code, path: e.path, }, + cause: e, }); } } finally { @@ -219,6 +222,7 @@ class Status { code: e.code, path: e.path, }, + cause: e, }); } while (!lock(statusFile.fd)) { @@ -235,13 +239,14 @@ class Status { code: e.code, path: e.path, }, + cause: e, }); } let statusInfo; try { statusInfo = JSON.parse(statusData, this.statusReviver); } catch (e) { - throw new statusErrors.ErrorStatusParse('JSON parsing failed'); + throw new statusErrors.ErrorStatusParse('JSON parsing failed', { cause: e }); } if (!statusUtils.statusValidate(statusInfo)) { throw new statusErrors.ErrorStatusParse( @@ -272,6 +277,7 @@ class Status { code: e.code, path: e.path, }, + cause: e, }); } return statusInfo; @@ -311,7 +317,7 @@ class Status { ); } catch (e) { if (e instanceof errors.ErrorUtilsPollTimeout) { - throw new errors.ErrorStatusTimeout(); + throw new errors.ErrorStatusTimeout(e.message, { cause: e }); } throw e; } diff --git a/src/validation/utils.ts b/src/validation/utils.ts index 3ce13f258..4221e1508 100644 --- a/src/validation/utils.ts +++ b/src/validation/utils.ts @@ -227,6 +227,7 @@ function parseSeedNodes(data: any): [SeedNodes, boolean] { if (e instanceof TypeError) { throw new validationErrors.ErrorParse( 'Seed nodes must be of format `nodeId@host:port;...`', + { cause: e }, ); } throw e; @@ -245,6 +246,7 @@ function parseSeedNodes(data: any): [SeedNodes, boolean] { if (e instanceof validationErrors.ErrorParse) { throw new validationErrors.ErrorParse( 'Seed nodes must be of format `nodeId@host:port;...`', + { cause: e }, ); } throw e; diff --git a/src/vaults/VaultInternal.ts b/src/vaults/VaultInternal.ts index 76f87e13b..c7f60829b 100644 --- a/src/vaults/VaultInternal.ts +++ b/src/vaults/VaultInternal.ts @@ -376,7 +376,7 @@ class VaultInternal { e instanceof git.Errors.NotFoundError || e instanceof git.Errors.CommitNotFetchedError ) { - throw new vaultsErrors.ErrorVaultReferenceMissing(); + throw new vaultsErrors.ErrorVaultReferenceMissing(e.message, { cause: e }); } throw e; } @@ -551,7 +551,7 @@ class VaultInternal { if (err instanceof git.Errors.SmartHttpError && error) { throw error; } else if (err instanceof git.Errors.MergeNotSupportedError) { - throw new vaultsErrors.ErrorVaultsMergeConflict(); + throw new vaultsErrors.ErrorVaultsMergeConflict(err.message, { cause: err }); } throw err; } diff --git a/src/vaults/VaultManager.ts b/src/vaults/VaultManager.ts index 13f57b5e4..571b8879d 100644 --- a/src/vaults/VaultManager.ts +++ b/src/vaults/VaultManager.ts @@ -196,7 +196,7 @@ class VaultManager { }); } catch (e) { if (e instanceof encryptedFsErrors.ErrorEncryptedFSKey) { - throw new vaultsErrors.ErrorVaultManagerKey(); + throw new vaultsErrors.ErrorVaultManagerKey(e.message, { cause: e }); } throw new vaultsErrors.ErrorVaultManagerEFS(e.message, { data: { @@ -205,6 +205,7 @@ class VaultManager { code: e.code, path: e.path, }, + cause: e, }); } this.vaultsDb = vaultsDb; @@ -942,6 +943,7 @@ class VaultManager { code: e.code, path: e.path, }, + cause: e, }); } } diff --git a/src/vaults/VaultOps.ts b/src/vaults/VaultOps.ts index 203bb4a4a..3de1edd1c 100644 --- a/src/vaults/VaultOps.ts +++ b/src/vaults/VaultOps.ts @@ -109,6 +109,7 @@ async function getSecret(vault: Vault, secretName: string): Promise { if (err.code === 'ENOENT') { throw new vaultsErrors.ErrorSecretsSecretUndefined( `Secret with name: ${secretName} does not exist`, + { cause: err }, ); } throw err; @@ -124,6 +125,7 @@ async function statSecret(vault: Vault, secretName: string): Promise { if (err.code === 'ENOENT') { throw new vaultsErrors.ErrorSecretsSecretUndefined( `Secret with name: ${secretName} does not exist`, + { cause: err }, ); } throw err; @@ -178,6 +180,7 @@ async function mkdir( if (err.code === 'ENOENT' && !recursive) { throw new vaultsErrors.ErrorVaultsRecursive( `Could not create directory '${dirPath}' without recursive option`, + { cause: err }, ); } }