From 8fb019abeee39c66b6fbf942b0d103eb5f0b62cc Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Fri, 27 Sep 2024 15:42:55 +1000 Subject: [PATCH] feat: added 'VaultsSecretsWriteFile' feat: removed 'VaultsSecretsEdit' chore: updated tests chore: added tests for vaultOps --- src/client/callers/index.ts | 6 +- src/client/callers/vaultsSecretsEdit.ts | 12 -- src/client/callers/vaultsSecretsWriteFile.ts | 12 ++ ...cretsEdit.ts => VaultsSecretsWriteFile.ts} | 6 +- src/client/handlers/index.ts | 6 +- src/vaults/VaultOps.ts | 16 ++ tests/client/handlers/vaults.test.ts | 196 ++++++++++-------- tests/vaults/VaultOps.test.ts | 18 ++ 8 files changed, 163 insertions(+), 109 deletions(-) delete mode 100644 src/client/callers/vaultsSecretsEdit.ts create mode 100644 src/client/callers/vaultsSecretsWriteFile.ts rename src/client/handlers/{VaultsSecretsEdit.ts => VaultsSecretsWriteFile.ts} (88%) diff --git a/src/client/callers/index.ts b/src/client/callers/index.ts index c38418c2c..4f4f805ba 100644 --- a/src/client/callers/index.ts +++ b/src/client/callers/index.ts @@ -63,7 +63,6 @@ import vaultsPermissionUnset from './vaultsPermissionUnset'; import vaultsPull from './vaultsPull'; import vaultsRename from './vaultsRename'; import vaultsScan from './vaultsScan'; -import vaultsSecretsEdit from './vaultsSecretsEdit'; import vaultsSecretsEnv from './vaultsSecretsEnv'; import vaultsSecretsGet from './vaultsSecretsGet'; import vaultsSecretsList from './vaultsSecretsList'; @@ -73,6 +72,7 @@ import vaultsSecretsNewDir from './vaultsSecretsNewDir'; import vaultsSecretsRename from './vaultsSecretsRename'; import vaultsSecretsRemove from './vaultsSecretsRemove'; import vaultsSecretsStat from './vaultsSecretsStat'; +import vaultsSecretsWriteFile from './vaultsSecretsWriteFile'; import vaultsVersion from './vaultsVersion'; /** @@ -144,7 +144,6 @@ const clientManifest = { vaultsPull, vaultsRename, vaultsScan, - vaultsSecretsEdit, vaultsSecretsEnv, vaultsSecretsGet, vaultsSecretsList, @@ -154,6 +153,7 @@ const clientManifest = { vaultsSecretsRename, vaultsSecretsRemove, vaultsSecretsStat, + vaultsSecretsWriteFile, vaultsVersion, }; @@ -224,7 +224,6 @@ export { vaultsPull, vaultsRename, vaultsScan, - vaultsSecretsEdit, vaultsSecretsEnv, vaultsSecretsGet, vaultsSecretsList, @@ -234,5 +233,6 @@ export { vaultsSecretsRename, vaultsSecretsRemove, vaultsSecretsStat, + vaultsSecretsWriteFile, vaultsVersion, }; diff --git a/src/client/callers/vaultsSecretsEdit.ts b/src/client/callers/vaultsSecretsEdit.ts deleted file mode 100644 index 01815a555..000000000 --- a/src/client/callers/vaultsSecretsEdit.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { HandlerTypes } from '@matrixai/rpc'; -import type VaultsSecretsEdit from '../handlers/VaultsSecretsEdit'; -import { UnaryCaller } from '@matrixai/rpc'; - -type CallerTypes = HandlerTypes; - -const vaultsSecretsEdit = new UnaryCaller< - CallerTypes['input'], - CallerTypes['output'] ->(); - -export default vaultsSecretsEdit; diff --git a/src/client/callers/vaultsSecretsWriteFile.ts b/src/client/callers/vaultsSecretsWriteFile.ts new file mode 100644 index 000000000..1a71aceb3 --- /dev/null +++ b/src/client/callers/vaultsSecretsWriteFile.ts @@ -0,0 +1,12 @@ +import type { HandlerTypes } from '@matrixai/rpc'; +import type VaultsSecretsWriteFile from '../handlers/VaultsSecretsWriteFile'; +import { UnaryCaller } from '@matrixai/rpc'; + +type CallerTypes = HandlerTypes; + +const vaultsSecretsWriteFile = new UnaryCaller< + CallerTypes['input'], + CallerTypes['output'] +>(); + +export default vaultsSecretsWriteFile; diff --git a/src/client/handlers/VaultsSecretsEdit.ts b/src/client/handlers/VaultsSecretsWriteFile.ts similarity index 88% rename from src/client/handlers/VaultsSecretsEdit.ts rename to src/client/handlers/VaultsSecretsWriteFile.ts index a0e896934..da11f5e68 100644 --- a/src/client/handlers/VaultsSecretsEdit.ts +++ b/src/client/handlers/VaultsSecretsWriteFile.ts @@ -11,7 +11,7 @@ import * as vaultsUtils from '../../vaults/utils'; import * as vaultsErrors from '../../vaults/errors'; import * as vaultOps from '../../vaults/VaultOps'; -class VaultsSecretsEdit extends UnaryHandler< +class VaultsSecretsWriteFile extends UnaryHandler< { vaultManager: VaultManager; db: DB; @@ -37,7 +37,7 @@ class VaultsSecretsEdit extends UnaryHandler< await vaultManager.withVaults( [vaultId], async (vault) => { - await vaultOps.updateSecret(vault, input.secretName, secretContent); + await vaultOps.writeSecret(vault, input.secretName, secretContent); }, tran, ); @@ -48,4 +48,4 @@ class VaultsSecretsEdit extends UnaryHandler< }; } -export default VaultsSecretsEdit; +export default VaultsSecretsWriteFile; diff --git a/src/client/handlers/index.ts b/src/client/handlers/index.ts index 6253465c8..76b1d4450 100644 --- a/src/client/handlers/index.ts +++ b/src/client/handlers/index.ts @@ -80,7 +80,6 @@ import VaultsPermissionUnset from './VaultsPermissionUnset'; import VaultsPull from './VaultsPull'; import VaultsRename from './VaultsRename'; import VaultsScan from './VaultsScan'; -import VaultsSecretsEdit from './VaultsSecretsEdit'; import VaultsSecretsEnv from './VaultsSecretsEnv'; import VaultsSecretsGet from './VaultsSecretsGet'; import VaultsSecretsList from './VaultsSecretsList'; @@ -90,6 +89,7 @@ import VaultsSecretsNewDir from './VaultsSecretsNewDir'; import VaultsSecretsRename from './VaultsSecretsRename'; import VaultsSecretsRemove from './VaultsSecretsRemove'; import VaultsSecretsStat from './VaultsSecretsStat'; +import VaultsSecretsWriteFile from './VaultsSecretsWriteFile'; import VaultsVersion from './VaultsVersion'; /** @@ -184,7 +184,6 @@ const serverManifest = (container: { vaultsPull: new VaultsPull(container), vaultsRename: new VaultsRename(container), vaultsScan: new VaultsScan(container), - vaultsSecretsEdit: new VaultsSecretsEdit(container), vaultsSecretsEnv: new VaultsSecretsEnv(container), vaultsSecretsGet: new VaultsSecretsGet(container), vaultsSecretsList: new VaultsSecretsList(container), @@ -194,6 +193,7 @@ const serverManifest = (container: { vaultsSecretsRename: new VaultsSecretsRename(container), vaultsSecretsRemove: new VaultsSecretsRemove(container), vaultsSecretsStat: new VaultsSecretsStat(container), + vaultsSecretsWriteFile: new VaultsSecretsWriteFile(container), vaultsVersion: new VaultsVersion(container), }; }; @@ -266,7 +266,6 @@ export { VaultsPull, VaultsRename, VaultsScan, - VaultsSecretsEdit, VaultsSecretsEnv, VaultsSecretsGet, VaultsSecretsList, @@ -276,5 +275,6 @@ export { VaultsSecretsRename, VaultsSecretsRemove, VaultsSecretsStat, + VaultsSecretsWriteFile, VaultsVersion, }; diff --git a/src/vaults/VaultOps.ts b/src/vaults/VaultOps.ts index 05cdaca8a..6cffeab9a 100644 --- a/src/vaults/VaultOps.ts +++ b/src/vaults/VaultOps.ts @@ -263,6 +263,21 @@ async function listSecrets(vault: Vault): Promise { }); } +/** + * Changes the contents of a secret. Creates a new file if it doesn't exist. + */ +async function writeSecret( + vault: Vault, + secretName: string, + content: Buffer | string, + logger?: Logger, +): Promise { + await vault.writeF(async (efs) => { + await efs.writeFile(secretName, content); + }); + logger?.info(`Wrote secret ${secretName} in vault ${vault.vaultId}`); +} + export { addSecret, updateSecret, @@ -273,4 +288,5 @@ export { mkdir, addSecretDirectory, listSecrets, + writeSecret, }; diff --git a/tests/client/handlers/vaults.test.ts b/tests/client/handlers/vaults.test.ts index d7a5124b9..df44c11ce 100644 --- a/tests/client/handlers/vaults.test.ts +++ b/tests/client/handlers/vaults.test.ts @@ -32,7 +32,7 @@ import { VaultsPermissionUnset, VaultsRename, VaultsSecretsRemove, - VaultsSecretsEdit, + VaultsSecretsWriteFile, VaultsSecretsEnv, VaultsSecretsGet, VaultsSecretsList, @@ -53,7 +53,7 @@ import { vaultsPermissionUnset, vaultsRename, vaultsSecretsRemove, - vaultsSecretsEdit, + vaultsSecretsWriteFile, vaultsSecretsEnv, vaultsSecretsGet, vaultsSecretsList, @@ -854,7 +854,7 @@ describe('vaultsScan', () => { }); test.todo('scans a vault'); }); -describe('vaultsSecretsEdit', () => { +describe('vaultsSecretsWriteFile', () => { const logger = new Logger('vaultsSecretsEdit test', LogLevel.WARN, [ new StreamHandler( formatting.format`${formatting.level}:${formatting.keys}:${formatting.msg}`, @@ -869,7 +869,7 @@ describe('vaultsSecretsEdit', () => { let clientService: ClientService; let webSocketClient: WebSocketClient; let rpcClient: RPCClient<{ - vaultsSecretsEdit: typeof vaultsSecretsEdit; + vaultsSecretsWriteFile: typeof vaultsSecretsWriteFile; }>; let vaultManager: VaultManager; beforeEach(async () => { @@ -908,7 +908,7 @@ describe('vaultsSecretsEdit', () => { }); await clientService.start({ manifest: { - vaultsSecretsEdit: new VaultsSecretsEdit({ + vaultsSecretsWriteFile: new VaultsSecretsWriteFile({ db, vaultManager, }), @@ -925,7 +925,7 @@ describe('vaultsSecretsEdit', () => { }); rpcClient = new RPCClient({ manifest: { - vaultsSecretsEdit, + vaultsSecretsWriteFile, }, streamFactory: () => webSocketClient.connection.newStream(), toError: networkUtils.toError, @@ -943,7 +943,7 @@ describe('vaultsSecretsEdit', () => { recursive: true, }); }); - test('edits secrets', async () => { + test('should edit a secret', async () => { const vaultName = 'test-vault'; const secretName = 'test-secret'; const vaultId = await vaultManager.createVault(vaultName); @@ -952,7 +952,25 @@ describe('vaultsSecretsEdit', () => { await efs.writeFile(secretName, secretName); }); }); - const response = await rpcClient.methods.vaultsSecretsEdit({ + const response = await rpcClient.methods.vaultsSecretsWriteFile({ + nameOrId: vaultsUtils.encodeVaultId(vaultId), + secretName: secretName, + secretContent: Buffer.from('content-change').toString('binary'), + }); + expect(response.success).toBeTruthy(); + await vaultManager.withVaults([vaultId], async (vault) => { + await vault.readF(async (efs) => { + expect((await efs.readFile(secretName)).toString()).toStrictEqual( + 'content-change', + ); + }); + }); + }); + test('should create target file with contents if it does not exist', async () => { + const vaultName = 'test-vault'; + const secretName = 'test-secret'; + const vaultId = await vaultManager.createVault(vaultName); + const response = await rpcClient.methods.vaultsSecretsWriteFile({ nameOrId: vaultsUtils.encodeVaultId(vaultId), secretName: secretName, secretContent: Buffer.from('content-change').toString('binary'), @@ -1493,14 +1511,12 @@ describe('vaultsSecretsNew and vaultsSecretsDelete, vaultsSecretsGet', () => { expect(createResponse.success).toBeTruthy(); // Get secret const getStream = await rpcClient.methods.vaultsSecretsGet(); - await (async () => { - const writer = getStream.writable.getWriter(); - await writer.write({ - nameOrId: vaultIdEncoded, - secretName: secretName, - }); - await writer.close(); - })(); + const getWriter = getStream.writable.getWriter(); + await getWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName, + }); + await getWriter.close(); const secretContent: Array = []; for await (const data of getStream.readable) { secretContent.push(data.secretContent); @@ -1509,25 +1525,21 @@ describe('vaultsSecretsNew and vaultsSecretsDelete, vaultsSecretsGet', () => { expect(concatenatedContent).toStrictEqual(secretName); // Delete secret const deleteStream = await rpcClient.methods.vaultsSecretsRemove(); - await (async () => { - const writer = deleteStream.writable.getWriter(); - await writer.write({ - nameOrId: vaultIdEncoded, - secretName: secretName, - }); - await writer.close(); - })(); + const deleteWriter = deleteStream.writable.getWriter(); + await deleteWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName, + }); + await deleteWriter.close(); expect((await deleteStream.output).success).toBeTruthy(); // Check secret was deleted const deleteGetStream = await rpcClient.methods.vaultsSecretsGet(); - await (async () => { - const writer = deleteGetStream.writable.getWriter(); - await writer.write({ - nameOrId: vaultIdEncoded, - secretName: secretName, - }); - await writer.close(); - })(); + const deleteGetWriter = deleteGetStream.writable.getWriter(); + await deleteGetWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName, + }); + await deleteGetWriter.close(); await testsUtils.expectRemoteError( (async () => { for await (const _ of deleteGetStream.readable); @@ -1547,13 +1559,20 @@ describe('vaultsSecretsNew and vaultsSecretsDelete, vaultsSecretsGet', () => { await createVaultSecret(vaultIdEncoded, secretName3); // Get secrets const getStream = await rpcClient.methods.vaultsSecretsGet(); - await (async () => { - const writer = getStream.writable.getWriter(); - await writer.write({ nameOrId: vaultIdEncoded, secretName: secretName1 }); - await writer.write({ nameOrId: vaultIdEncoded, secretName: secretName2 }); - await writer.write({ nameOrId: vaultIdEncoded, secretName: secretName3 }); - await writer.close(); - })(); + const getWriter = getStream.writable.getWriter(); + await getWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName1, + }); + await getWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName2, + }); + await getWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName3, + }); + await getWriter.close(); let secretContent: string = ''; for await (const data of getStream.readable) { secretContent += data.secretContent; @@ -1572,17 +1591,18 @@ describe('vaultsSecretsNew and vaultsSecretsDelete, vaultsSecretsGet', () => { 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(); - })(); + const getWriter = getStream.writable.getWriter(); + await getWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName1, + metadata: { options: { continueOnError: true } }, + }); + await getWriter.write({ nameOrId: vaultIdEncoded, secretName: 'invalid' }); + await getWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName2, + }); + await getWriter.close(); let secretContent: string = ''; let errorContent: string = ''; await expect( @@ -1606,12 +1626,16 @@ describe('vaultsSecretsNew and vaultsSecretsDelete, vaultsSecretsGet', () => { await createVaultSecret(vaultIdEncoded, secretName2); // Delete secrets const deleteStream = await rpcClient.methods.vaultsSecretsRemove(); - await (async () => { - const writer = deleteStream.writable.getWriter(); - await writer.write({ nameOrId: vaultIdEncoded, secretName: secretName1 }); - await writer.write({ nameOrId: vaultIdEncoded, secretName: secretName2 }); - await writer.close(); - })(); + const deleteWriter = deleteStream.writable.getWriter(); + await deleteWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName1, + }); + await deleteWriter.write({ + nameOrId: vaultIdEncoded, + secretName: secretName2, + }); + await deleteWriter.close(); expect((await deleteStream.output).success).toBeTruthy(); // Check each secret was deleted await checkSecretIsDeleted(vaultIdEncoded, secretName1); @@ -1631,22 +1655,20 @@ describe('vaultsSecretsNew and vaultsSecretsDelete, vaultsSecretsGet', () => { await createVaultSecret(vaultIdEncoded2, secretName3); // Get secret const getStream = await rpcClient.methods.vaultsSecretsGet(); - await (async () => { - const writer = getStream.writable.getWriter(); - await writer.write({ - nameOrId: vaultIdEncoded1, - secretName: secretName1, - }); - await writer.write({ - nameOrId: vaultIdEncoded1, - secretName: secretName2, - }); - await writer.write({ - nameOrId: vaultIdEncoded2, - secretName: secretName3, - }); - await writer.close(); - })(); + const getWriter = getStream.writable.getWriter(); + await getWriter.write({ + nameOrId: vaultIdEncoded1, + secretName: secretName1, + }); + await getWriter.write({ + nameOrId: vaultIdEncoded1, + secretName: secretName2, + }); + await getWriter.write({ + nameOrId: vaultIdEncoded2, + secretName: secretName3, + }); + await getWriter.close(); let secretContent: string = ''; for await (const data of getStream.readable) { secretContent += data.secretContent; @@ -1679,22 +1701,20 @@ describe('vaultsSecretsNew and vaultsSecretsDelete, vaultsSecretsGet', () => { ); // Delete secret const deleteStream = await rpcClient.methods.vaultsSecretsRemove(); - await (async () => { - const writer = deleteStream.writable.getWriter(); - await writer.write({ - nameOrId: vaultIdEncoded1, - secretName: secretName1, - }); - await writer.write({ - nameOrId: vaultIdEncoded1, - secretName: secretName2, - }); - await writer.write({ - nameOrId: vaultIdEncoded2, - secretName: secretName3, - }); - await writer.close(); - })(); + const deleteWriter = deleteStream.writable.getWriter(); + await deleteWriter.write({ + nameOrId: vaultIdEncoded1, + secretName: secretName1, + }); + await deleteWriter.write({ + nameOrId: vaultIdEncoded1, + secretName: secretName2, + }); + await deleteWriter.write({ + nameOrId: vaultIdEncoded2, + secretName: secretName3, + }); + await deleteWriter.close(); expect((await deleteStream.output).success).toBeTruthy(); // Ensure single log message for deleting the secrets await vaultManager.withVaults( diff --git a/tests/vaults/VaultOps.test.ts b/tests/vaults/VaultOps.test.ts index 8c1f5da32..bd329d464 100644 --- a/tests/vaults/VaultOps.test.ts +++ b/tests/vaults/VaultOps.test.ts @@ -500,6 +500,24 @@ describe('VaultOps', () => { expect(secretList).toHaveLength(0); }); }); + describe('writeSecret', () => { + const secretName = 'secret'; + const secretContent = 'secret-content'; + const newSecretContent = 'updated-secret-content'; + + test('updates existing secret', async () => { + await vaultOps.addSecret(vault, secretName, secretContent); + await vaultOps.writeSecret(vault, secretName, newSecretContent); + const result = await vaultOps.getSecret(vault, secretName); + expect(result.toString()).toStrictEqual(newSecretContent); + }); + + test('creates new secret if it does not exist', async () => { + await vaultOps.writeSecret(vault, secretName, newSecretContent); + const result = await vaultOps.getSecret(vault, secretName); + expect(result.toString()).toStrictEqual(newSecretContent); + }); + }); test('adding hidden files and directories', async () => { await vaultOps.addSecret(vault, '.hiddenSecret', 'hidden_contents'); await vaultOps.mkdir(vault, '.hiddenDir', { recursive: true });