OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / unit / secure-storage.test.js

100 lines · javascript ref:
import test from 'brittle'
import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
import { SecureStorage } from '../../lib/identity/secure-storage.js'

const tmpDir = path.join(os.tmpdir(), 'pear-git-test-vault-' + Date.now())

function vaultPath (name) {
  return path.join(tmpDir, name + '.enc')
}

test('setup: create temp dir', async (t) => {
  await fs.promises.mkdir(tmpDir, { recursive: true })
  t.pass('temp dir created')
})

test('secure-storage: encrypt and decrypt roundtrip', async (t) => {
  const vault = new SecureStorage(vaultPath('roundtrip'))
  const plaintext = Buffer.from('abandon ability able about above absent')
  const password = Buffer.from('test-password-123')

  await vault.encrypt(plaintext, password)
  t.ok(vault.exists(), 'vault file exists after encrypt')

  const decrypted = await vault.decrypt(password)
  t.alike(decrypted, plaintext, 'decrypted matches original plaintext')
})

test('secure-storage: wrong password throws', async (t) => {
  const vault = new SecureStorage(vaultPath('wrongpw'))
  const plaintext = Buffer.from('secret data here')
  const password = Buffer.from('correct-password')
  const wrongPassword = Buffer.from('wrong-password')

  await vault.encrypt(plaintext, password)

  try {
    await vault.decrypt(wrongPassword)
    t.fail('should have thrown')
  } catch (err) {
    t.ok(err.message.includes('wrong password'), 'throws on wrong password')
  }
})

test('secure-storage: change password', async (t) => {
  const vault = new SecureStorage(vaultPath('changepw'))
  const plaintext = Buffer.from('mnemonic words here')
  const oldPw = Buffer.from('old-password')
  const newPw = Buffer.from('new-password')

  await vault.encrypt(plaintext, oldPw)
  await vault.changePassword(oldPw, newPw)

  // Old password should fail
  try {
    await vault.decrypt(oldPw)
    t.fail('old password should not work')
  } catch {
    t.pass('old password rejected after change')
  }

  // New password should work
  const decrypted = await vault.decrypt(newPw)
  t.alike(decrypted, plaintext, 'new password decrypts correctly')
})

test('secure-storage: exists() returns false for missing file', (t) => {
  const vault = new SecureStorage(vaultPath('nonexistent'))
  t.is(vault.exists(), false, 'exists() is false for missing file')
})

test('secure-storage: vault file has correct version', async (t) => {
  const vault = new SecureStorage(vaultPath('version'))
  await vault.encrypt(Buffer.from('data'), Buffer.from('pw'))

  const raw = JSON.parse(await fs.promises.readFile(vaultPath('version'), 'utf-8'))
  t.is(raw.version, 1, 'vault version is 1')
  t.ok(raw.salt, 'salt present')
  t.ok(raw.nonce, 'nonce present')
  t.ok(raw.ciphertext, 'ciphertext present')
  t.ok(raw.opslimit, 'opslimit present')
  t.ok(raw.memlimit, 'memlimit present')
})

test('secure-storage: vault file has mode 0600', async (t) => {
  const fp = vaultPath('perms')
  const vault = new SecureStorage(fp)
  await vault.encrypt(Buffer.from('data'), Buffer.from('pw'))

  const stat = await fs.promises.stat(fp)
  const mode = stat.mode & 0o777
  t.is(mode, 0o600, 'vault file mode is 0600')
})

test('cleanup: remove temp dir', async (t) => {
  await fs.promises.rm(tmpDir, { recursive: true, force: true })
  t.pass('cleaned up')
})