diff --git a/src/agent/service/vaultsGitInfoGet.ts b/src/agent/service/vaultsGitInfoGet.ts index f5a9d8f41f..bc956d0172 100644 --- a/src/agent/service/vaultsGitInfoGet.ts +++ b/src/agent/service/vaultsGitInfoGet.ts @@ -37,7 +37,7 @@ function vaultsGitInfoGet({ if (!vaultId) { try { vaultId = validationUtils.parseVaultId(vaultNameOrId); - vaultName = (await vaultManager.getVaultMeta(vaultId)).name; + vaultName = (await vaultManager.getVaultMeta(vaultId))?.vaultName; } catch (err) { await genWritable.throw(new vaultsErrors.ErrorVaultsVaultUndefined()); return; diff --git a/src/client/service/vaultsClone.ts b/src/client/service/vaultsClone.ts index c7338b9a28..5b4e516f70 100644 --- a/src/client/service/vaultsClone.ts +++ b/src/client/service/vaultsClone.ts @@ -37,7 +37,7 @@ function vaultsClone({ // Vault id let vaultId; const vaultNameOrId = vaultMessage.getNameOrId(); - vaultId = vaultManager.getVaultId(vaultNameOrId) + vaultId = vaultManager.getVaultId(vaultNameOrId); vaultId = vaultId ?? vaultsUtils.decodeVaultId(vaultNameOrId); if (vaultId == null) throw new vaultsErrors.ErrorVaultsVaultUndefined(); // Node id diff --git a/src/vaults/VaultInternal.ts b/src/vaults/VaultInternal.ts index cb44f30eaa..8147807da4 100644 --- a/src/vaults/VaultInternal.ts +++ b/src/vaults/VaultInternal.ts @@ -2,14 +2,14 @@ import type { ReadCommitResult } from 'isomorphic-git'; import type { EncryptedFS } from 'encryptedfs'; import type { DB, DBDomain, DBLevel } from '@matrixai/db'; import type { - VaultId, - VaultName, - VaultRef, CommitId, CommitLog, FileSystemReadable, FileSystemWritable, + VaultId, VaultIdEncoded, + VaultName, + VaultRef, } from './types'; import type { KeyManager } from '../keys'; import type { NodeId, NodeIdEncoded } from '../nodes/types'; @@ -23,7 +23,6 @@ import { CreateDestroyStartStop, ready, } from '@matrixai/async-init/dist/CreateDestroyStartStop'; -import { IdInternal, utils as idUtils } from '@matrixai/id'; import * as vaultsUtils from './utils'; import * as vaultsErrors from './errors'; import { withF, withG } from '../utils'; @@ -242,13 +241,16 @@ class VaultInternal { this.logger.info( `Starting ${this.constructor.name} - ${this.vaultIdEncoded}`, ); - const vaultDbDomain = [...this.vaultsDbDomain, this.vaultIdEncoded]; - const vaultsNamesDomain = [...this.vaultsDbDomain, 'names']; - const vaultDb = await this.db.level(this.vaultIdEncoded, this.vaultsDb); + this.vaultMetadataDbDomain = [...this.vaultsDbDomain, this.vaultIdEncoded]; + this.vaultsNamesDomain = [...this.vaultsDbDomain, 'names']; + this.vaultMetadataDb = await this.db.level( + this.vaultIdEncoded, + this.vaultsDb, + ); // Let's backup any metadata. if (fresh) { - await vaultDb.clear(); + await this.vaultMetadataDb.clear(); try { await this.efs.rmdir(this.vaultIdEncoded, { recursive: true, @@ -259,21 +261,27 @@ class VaultInternal { } } } - await this.efs.mkdir(this.vaultIdEncoded, { recursive: true }); - await this.efs.mkdir(this.vaultDataDir, { recursive: true }); - await this.efs.mkdir(this.vaultGitDir, { recursive: true }); + await this.mkdirExists(this.vaultIdEncoded); + await this.mkdirExists(this.vaultDataDir); + await this.mkdirExists(this.vaultGitDir); await this.setupMeta({ vaultName }); await this.setupGit(); - const efsVault = await this.efs.chroot(this.vaultDataDir); - this.vaultMetadataDbDomain = vaultDbDomain; - this.vaultsNamesDomain = vaultsNamesDomain; - this.vaultMetadataDb = vaultDb; - this.efsVault = efsVault; + this.efsVault = await this.efs.chroot(this.vaultDataDir); this.logger.info( `Started ${this.constructor.name} - ${this.vaultIdEncoded}`, ); } + private async mkdirExists(directory: string) { + try { + await this.efs.mkdir(directory, { recursive: true }); + } catch (e) { + if (e.code !== 'EEXIST') { + throw e; + } + } + } + public async stop(): Promise { this.logger.info( `Stopping ${this.constructor.name} - ${this.vaultIdEncoded}`, @@ -714,7 +722,11 @@ class VaultInternal { VaultInternal.dirtyKey, )) == null ) { - await this.db.put(this.vaultsDbDomain, VaultInternal.dirtyKey, true); + await this.db.put( + this.vaultMetadataDbDomain, + VaultInternal.dirtyKey, + true, + ); } // Set up vault Name @@ -725,7 +737,11 @@ class VaultInternal { )) == null && vaultName != null ) { - await this.db.put(this.vaultsDbDomain, VaultInternal.nameKey, vaultName); + await this.db.put( + this.vaultMetadataDbDomain, + VaultInternal.nameKey, + vaultName, + ); } // Remote: [NodeId, VaultId] | undefined diff --git a/src/vaults/VaultManager.ts b/src/vaults/VaultManager.ts index d67382b8f7..ab05d18deb 100644 --- a/src/vaults/VaultManager.ts +++ b/src/vaults/VaultManager.ts @@ -347,10 +347,12 @@ class VaultManager { this.vaultMap.set(vaultId, { lock }); return await this.transact(async () => { // Adding vault to name map + console.log(vaultId.toBuffer()); await this.db.put( this.vaultsNamesDbDomain, vaultName, vaultId.toBuffer(), + true, ); // Creating vault const vault = await VaultInternal.createVaultInternal({ @@ -450,8 +452,11 @@ class VaultManager { // Stream of vaultName VaultId key value pairs for await (const o of this.vaultsNamesDb.createReadStream()) { const obj = o as any; - const vaultId = IdInternal.fromBuffer(obj.value); - vaults.set(obj.key as VaultName, vaultId); + console.log(obj.key.toString()); + console.log(obj.value as Buffer); + const vaultId = IdInternal.fromBuffer(obj.value as Buffer); + console.log(vaultId); + vaults.set(obj.key.toString() as VaultName, vaultId); } return vaults; } @@ -540,6 +545,7 @@ class VaultManager { @ready(new vaultsErrors.ErrorVaultManagerNotRunning()) public async shareVault(vaultId: VaultId, nodeId: NodeId): Promise { const vaultMeta = await this.getVaultMeta(vaultId); + console.log(vaultId, vaultMeta); if (!vaultMeta) throw new vaultsErrors.ErrorVaultsVaultUndefined(); await this.transact(async () => { await this.gestaltGraph._transaction(async () => { diff --git a/src/vaults/VaultOps.ts b/src/vaults/VaultOps.ts index 9059471435..f9bef210a3 100644 --- a/src/vaults/VaultOps.ts +++ b/src/vaults/VaultOps.ts @@ -7,6 +7,11 @@ import path from 'path'; import * as vaultsErrors from './errors'; import * as vaultsUtils from './utils'; +// TODO: remove? +type FileOptions = { + recursive?: boolean; +}; + // TODO: tests // - add succeeded // - secret exists diff --git a/src/vaults/utils.ts b/src/vaults/utils.ts index ef014f4b0a..3e4e0d3c1c 100644 --- a/src/vaults/utils.ts +++ b/src/vaults/utils.ts @@ -80,18 +80,20 @@ function commitAuthor(nodeId: NodeId): { name: string; email: string } { // return true; // } -// async function* readdirRecursively(fs, dir = '.') { -// const dirents = await fs.promises.readdir(dir); -// for (const dirent of dirents) { -// const res = path.join(dir, dirent.toString()); -// const stat = await fs.promises.stat(res); -// if (stat.isDirectory()) { -// yield* readdirRecursively(fs, res); -// } else if (stat.isFile()) { -// yield res; -// } -// } -// } +// TODO: remove or move? +async function* readdirRecursively(fs, dir = '.') { + throw Error('Not Implemented'); + // Const dirents = await fs.promises.readdir(dir); + // for (const dirent of dirents) { + // const res = path.join(dir, dirent.toString()); + // const stat = await fs.promises.stat(res); + // if (stat.isDirectory()) { + // yield* readdirRecursively(fs, res); + // } else if (stat.isFile()) { + // yield res; + // } + // } +} // TODO: Is this being removed or moved? async function request( @@ -206,4 +208,5 @@ export { commitAuthor, isVaultAction, request, + readdirRecursively, }; diff --git a/tests/vaults/VaultInternal.test.ts b/tests/vaults/VaultInternal.test.ts index cb721f3482..7989350d75 100644 --- a/tests/vaults/VaultInternal.test.ts +++ b/tests/vaults/VaultInternal.test.ts @@ -1,16 +1,19 @@ import type { VaultId } from '@/vaults/types'; import type { Vault } from '@/vaults/Vault'; import type { KeyManager } from '@/keys'; +import type { DBDomain, DBLevel } from '@matrixai/db'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { EncryptedFS } from 'encryptedfs'; +import { DB } from '@matrixai/db'; import { VaultInternal } from '@/vaults'; import { generateVaultId } from '@/vaults/utils'; import * as vaultsErrors from '@/vaults/errors'; import { sleep } from '@/utils'; import { utils as keysUtils } from '@/keys'; +import * as vaultsUtils from '@/vaults/utils'; import * as testsUtils from '../utils'; jest.mock('@/keys/utils', () => ({ @@ -20,14 +23,19 @@ jest.mock('@/keys/utils', () => ({ })); describe('VaultInternal', () => { + const logger = new Logger('Vault', LogLevel.WARN, [new StreamHandler()]); + let dataDir: string; - let dbPath: string; + let efsDbPath: string; let vault: VaultInternal; let dbKey: Buffer; let vaultId: VaultId; let efs: EncryptedFS; - const logger = new Logger('Vault', LogLevel.WARN, [new StreamHandler()]); + + let db: DB; + let vaultsDb: DBLevel; + let vaultsDbDomain: DBDomain; const fakeKeyManager = { getNodeId: () => { @@ -37,33 +45,56 @@ describe('VaultInternal', () => { const secret1 = { name: 'secret-1', content: 'secret-content-1' }; const secret2 = { name: 'secret-2', content: 'secret-content-2' }; - beforeAll(async () => { + beforeAll(async () => {}); + + beforeEach(async () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); dbKey = await keysUtils.generateKey(); - dbPath = path.join(dataDir, 'db'); - await fs.promises.mkdir(dbPath); + efsDbPath = path.join(dataDir, 'efsDb'); + await fs.promises.mkdir(efsDbPath); efs = await EncryptedFS.createEncryptedFS({ - dbPath, + dbPath: efsDbPath, dbKey, logger, }); await efs.start(); - }); - beforeEach(async () => { + db = await DB.createDB({ + crypto: { + key: await keysUtils.generateKey(), + ops: { + encrypt: keysUtils.encryptWithKey, + decrypt: keysUtils.decryptWithKey, + }, + }, + dbPath: path.join(dataDir, 'db'), + fs: fs, + logger: logger, + }); + vaultsDbDomain = ['vaults']; + vaultsDb = await db.level(vaultsDbDomain[0]); + vaultId = generateVaultId(); - vault = await VaultInternal.create({ + vault = await VaultInternal.createVaultInternal({ vaultId, keyManager: fakeKeyManager, efs, logger, fresh: true, + db, + vaultsDb, + vaultsDbDomain, + vaultName: 'testVault', }); }); - afterAll(async () => { + afterEach(async () => { + await vault.stop(); + await vault.destroy(); + await db.stop(); + await db.destroy(); await efs.stop(); await efs.destroy(); await fs.promises.rm(dataDir, { @@ -73,9 +104,13 @@ describe('VaultInternal', () => { }); test('VaultInternal readiness', async () => { - await vault.destroy(); + await vault.stop(); await expect(async () => { await vault.log(); + }).rejects.toThrow(vaultsErrors.ErrorVaultNotRunning); + await vault.destroy(); + await expect(async () => { + await vault.start(); }).rejects.toThrow(vaultsErrors.ErrorVaultDestroyed); }); test('is type correct', async () => { @@ -99,13 +134,17 @@ describe('VaultInternal', () => { await vault.writeF(async (efs) => { await efs.writeFile('secret-1', 'secret-content'); }); - await vault.destroy(); - vault = await VaultInternal.create({ + await vault.stop(); + vault = await VaultInternal.createVaultInternal({ vaultId, keyManager: fakeKeyManager, efs, logger, fresh: false, + db, + vaultName: 'testVault2', + vaultsDb, + vaultsDbDomain, }); await vault.readF(async (efs) => { expect((await efs.readFile('secret-1')).toString()).toStrictEqual( @@ -155,7 +194,7 @@ describe('VaultInternal', () => { }); test('does not allow changing to an unrecognised commit', async () => { await expect(() => vault.version('unrecognisedcommit')).rejects.toThrow( - vaultsErrors.ErrorVaultReferenceMissing, + vaultsErrors.ErrorVaultReferenceInvalid, ); await vault.writeF(async (efs) => { await efs.writeFile('test1', 'testdata1'); @@ -598,15 +637,12 @@ describe('VaultInternal', () => { expect(vaultNormal.destroy).toBeTruthy(); // This exists again. }); test('cannot commit when the remote field is set', async () => { - await vault.destroy(); - vault = await VaultInternal.create({ - vaultId, - keyManager: fakeKeyManager, - efs, - logger, - remote: true, - fresh: true, - }); + // Write remote metadata + await db.put( + [...vaultsDbDomain, vaultsUtils.encodeVaultId(vaultId)], + VaultInternal.remoteKey, + { remoteNode: '', remoteVault: '' }, + ); const commit = (await vault.log(undefined, 1))[0]; await vault.version(commit.commitId); const files = await vault.readF(async (efs) => { diff --git a/tests/vaults/VaultManager.test.ts b/tests/vaults/VaultManager.test.ts index ca0b5f89fb..38f1ea6c10 100644 --- a/tests/vaults/VaultManager.test.ts +++ b/tests/vaults/VaultManager.test.ts @@ -258,6 +258,7 @@ describe('VaultManager', () => { ).rejects.toThrow(vaultErrors.ErrorVaultsVaultUndefined); }); test('can delete a vault', async () => { + console.log(await vaultManager.listVaults()); const secondVaultId = await vaultManager.createVault(secondVaultName); await vaultManager.destroyVault(secondVaultId); }); @@ -319,7 +320,7 @@ describe('VaultManager', () => { vaultManager.renameVault(vaultId, secondVaultName), vaultManager.renameVault(vaultId, thirdVaultName), ]); - const vaultNameTest = (await vaultManager.getVaultMeta(vaultId)).name; + const vaultNameTest = (await vaultManager.getVaultMeta(vaultId))?.vaultName; expect(vaultNameTest).toBe(thirdVaultName); }); test('can concurrently open and rename the same vault', async () => { @@ -328,7 +329,7 @@ describe('VaultManager', () => { vaultManager.renameVault(vaultId, secondVaultName), vaultManager.withVaults([vaultId], async (vault) => vault.vaultId), ]); - const vaultNameTest = (await vaultManager.getVaultMeta(vaultId)).name; + const vaultNameTest = (await vaultManager.getVaultMeta(vaultId))?.vaultName; expect(vaultNameTest).toBe(secondVaultName); }); test('can save the commit state of a vault', async () => { diff --git a/tests/vaults/VaultOps.test.ts b/tests/vaults/VaultOps.test.ts index 8c6907bf98..962b3de919 100644 --- a/tests/vaults/VaultOps.test.ts +++ b/tests/vaults/VaultOps.test.ts @@ -1,12 +1,14 @@ import type { VaultId } from '@/vaults/types'; import type { Vault } from '@/vaults/Vault'; import type { KeyManager } from '@/keys'; +import type { DBDomain, DBLevel } from '@matrixai/db'; import fs from 'fs'; import path from 'path'; import os from 'os'; import { EncryptedFS } from 'encryptedfs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { utils as idUtils } from '@matrixai/id'; +import { DB } from '@matrixai/db'; import * as errors from '@/vaults/errors'; import { VaultInternal, vaultOps } from '@/vaults'; import * as vaultsUtils from '@/vaults/utils'; @@ -21,6 +23,9 @@ describe('VaultOps', () => { let vaultId: VaultId; let vaultInternal: VaultInternal; let vault: Vault; + let db: DB; + let vaultsDb: DBLevel; + let vaultsDbDomain: DBDomain; const dummyKeyManager = { getNodeId: () => { return testUtils.generateRandomNodeId(); @@ -30,7 +35,7 @@ describe('VaultOps', () => { let mockedGenerateKeyPair: jest.SpyInstance; let mockedGenerateDeterministicKeyPair: jest.SpyInstance; - beforeAll(async () => { + beforeEach(async () => { const globalKeyPair = await testUtils.setupGlobalKeypair(); mockedGenerateKeyPair = jest .spyOn(keysUtils, 'generateKeyPair') @@ -42,7 +47,7 @@ describe('VaultOps', () => { dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), ); - const dbPath = path.join(dataDir, 'db'); + const dbPath = path.join(dataDir, 'efsDb'); const dbKey = await keysUtils.generateKey(); baseEfs = await EncryptedFS.createEncryptedFS({ dbKey, @@ -50,34 +55,43 @@ describe('VaultOps', () => { logger, }); await baseEfs.start(); - }); - - afterAll(async () => { - mockedGenerateKeyPair.mockRestore(); - mockedGenerateDeterministicKeyPair.mockRestore(); - await baseEfs.stop(); - await baseEfs.destroy(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - beforeEach(async () => { vaultId = vaultsUtils.generateVaultId(); await baseEfs.mkdir(path.join(idUtils.toString(vaultId), 'contents'), { recursive: true, }); - vaultInternal = await VaultInternal.create({ + db = await DB.createDB({ dbPath: path.join(dataDir, 'db') }); + vaultsDbDomain = ['vaults']; + vaultsDb = await db.level(vaultsDbDomain[0]); + vaultInternal = await VaultInternal.createVaultInternal({ keyManager: dummyKeyManager, vaultId, efs: baseEfs, logger: logger.getChild(VaultInternal.name), fresh: true, + db, + vaultsDbDomain, + vaultsDb, + vaultName: 'VaultName', }); vault = vaultInternal as Vault; }); + afterEach(async () => { + await vaultInternal.stop(); + await vaultInternal.destroy(); + await db.stop(); + await db.destroy(); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + await baseEfs.stop(); + await baseEfs.destroy(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('adding a secret', async () => { await vaultOps.addSecret(vault, 'secret-1', 'secret-content'); const dir = await vault.readF(async (efs) => { diff --git a/tests/vaults/utils.test.ts b/tests/vaults/utils.test.ts index d41cec6c8e..85a866f88a 100644 --- a/tests/vaults/utils.test.ts +++ b/tests/vaults/utils.test.ts @@ -27,10 +27,6 @@ describe('Vaults utils', () => { }); }); - test('VaultId type guard works', async () => { - const vaultId = vaultsUtils.generateVaultId(); - expect(vaultsUtils.decodeVaultId(vaultId)).toBeTruthy(); - }); test('EFS can be read recursively', async () => { const key = await keysUtils.generateKey(256); const efs = await EncryptedFS.createEncryptedFS({