From 90aeb6a37c579838533ce7afb15e30c8d6e80647 Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Tue, 24 Sep 2024 14:23:02 +1000 Subject: [PATCH] chore: added option to continue on error --- src/client/handlers/VaultsSecretsGet.ts | 35 +++++++++++++++++++------ src/client/types.ts | 5 ++++ tests/client/handlers/vaults.test.ts | 34 ++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/client/handlers/VaultsSecretsGet.ts b/src/client/handlers/VaultsSecretsGet.ts index ead41f0e9..b32618cc1 100644 --- a/src/client/handlers/VaultsSecretsGet.ts +++ b/src/client/handlers/VaultsSecretsGet.ts @@ -2,7 +2,7 @@ import type { DB } from '@matrixai/db'; import type { ClientRPCRequestParams, ClientRPCResponseResult, - ContentMessage, + ContentWithErrorMessage, SecretIdentifierMessage, } from '../types'; import type VaultManager from '../../vaults/VaultManager'; @@ -17,36 +17,55 @@ class VaultsSecretsGet extends DuplexHandler< db: DB; }, ClientRPCRequestParams, - ClientRPCResponseResult + ClientRPCResponseResult > { public handle = async function* ( input: AsyncIterable>, _cancel, _meta, ctx, - ): AsyncGenerator> { + ): AsyncGenerator> { if (ctx.signal.aborted) throw ctx.signal.reason; const { vaultManager, db } = this.container; yield* db.withTransactionG(async function* (tran): AsyncGenerator< - ClientRPCResponseResult + ClientRPCResponseResult > { if (ctx.signal.aborted) throw ctx.signal.reason; // As we need to preserve the order of parameters, we need to loop over // them individually, as grouping them would make them go out of order. + let metadata: any = undefined; for await (const secretIdentiferMessage of input) { + if (ctx.signal.aborted) throw ctx.signal.reason; + if (metadata == null) metadata = secretIdentiferMessage.metadata ?? {}; const { nameOrId, secretName } = secretIdentiferMessage; const vaultIdFromName = await vaultManager.getVaultId(nameOrId, tran); const vaultId = vaultIdFromName ?? vaultsUtils.decodeVaultId(nameOrId); if (vaultId == null) throw new vaultsErrors.ErrorVaultsVaultUndefined(); - const content: Buffer = await vaultManager.withVaults( + yield await vaultManager.withVaults( [vaultId], async (vault) => { - return await vaultOps.getSecret(vault, secretName); + try { + const content = await vaultOps.getSecret(vault, secretName); + return { secretContent: content.toString('binary') }; + } catch (e) { + if (metadata?.options?.continueOnError === true) { + if (e instanceof vaultsErrors.ErrorSecretsSecretUndefined) { + return { + secretContent: '', + error: `${e.name}: ${secretName}: No such secret or directory\n`, + }; + } else if (e instanceof vaultsErrors.ErrorSecretsIsDirectory) { + return { + secretContent: '', + error: `${e.name}: ${secretName}: Is a directory\n`, + }; + } + } + throw e; + } }, tran, ); - - yield { secretContent: content.toString('binary') }; } }); }; diff --git a/src/client/types.ts b/src/client/types.ts index 0b125b18d..faca7089e 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -311,6 +311,10 @@ type ContentMessage = { secretContent: string; }; +type ContentWithErrorMessage = ContentMessage & { + error?: string; +}; + type SecretContentMessage = SecretIdentifierMessage & ContentMessage; type SecretMkdirMessage = VaultIdentifierMessage & { @@ -417,6 +421,7 @@ export type { SecretPathMessage, SecretIdentifierMessage, ContentMessage, + ContentWithErrorMessage, SecretContentMessage, SecretMkdirMessage, SecretDirMessage, diff --git a/tests/client/handlers/vaults.test.ts b/tests/client/handlers/vaults.test.ts index 03f544cf5..d7a5124b9 100644 --- a/tests/client/handlers/vaults.test.ts +++ b/tests/client/handlers/vaults.test.ts @@ -1562,6 +1562,40 @@ describe('vaultsSecretsNew and vaultsSecretsDelete, vaultsSecretsGet', () => { `${secretName1}${secretName2}${secretName3}`, ); }); + test('should not fail to get secrets on error when continueOnError is set', async () => { + // Create secrets + const secretName1 = 'test-secret1'; + const secretName2 = 'test-secret2'; + const vaultId = await vaultManager.createVault('test-vault'); + const vaultIdEncoded = vaultsUtils.encodeVaultId(vaultId); + await createVaultSecret(vaultIdEncoded, secretName1); + await createVaultSecret(vaultIdEncoded, secretName2); + // Get secrets + const getStream = await rpcClient.methods.vaultsSecretsGet(); + await (async () => { + const writer = getStream.writable.getWriter(); + await writer.write({ + nameOrId: vaultIdEncoded, + secretName: secretName1, + metadata: { options: { continueOnError: true } }, + }); + await writer.write({ nameOrId: vaultIdEncoded, secretName: 'invalid' }); + await writer.write({ nameOrId: vaultIdEncoded, secretName: secretName2 }); + await writer.close(); + })(); + let secretContent: string = ''; + let errorContent: string = ''; + await expect( + (async () => { + for await (const data of getStream.readable) { + if (data.error) errorContent += data.error; + else secretContent += data.secretContent; + } + })(), + ).toResolve(); + expect(secretContent).toStrictEqual(`${secretName1}${secretName2}`); + expect(errorContent.length).not.toBe(0); + }); test('deletes multiple secrets from the same vault', async () => { // Create secrets const secretName1 = 'test-secret1';