355 lines
14 KiB
JavaScript
355 lines
14 KiB
JavaScript
/**
|
|
* Unit tests for nedb.js and sqlite.js enc/dec support.
|
|
* Uses Node's built-in test runner (node:test).
|
|
*
|
|
* Run with:
|
|
* node --test test/unit/db-enc.spec.js
|
|
*/
|
|
|
|
const { test, describe, before, after } = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
const os = require('os')
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Simple reversible enc/dec for tests (XOR-rotate + base64)
|
|
// ---------------------------------------------------------------------------
|
|
// const TEST_ENC_PREFIX = 'enc:'
|
|
|
|
function simpleEnc (str) {
|
|
return Buffer.from(str).toString('base64')
|
|
}
|
|
|
|
function simpleDec (str) {
|
|
return Buffer.from(str, 'base64').toString('utf8')
|
|
}
|
|
|
|
const encOpts = { enc: simpleEnc, dec: simpleDec }
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
function makeTmpDir () {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'electerm-test-'))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SQLite tests (Node >= 22)
|
|
// ---------------------------------------------------------------------------
|
|
describe('sqlite createDb', () => {
|
|
const { createDb } = require('../../src/app/lib/sqlite')
|
|
|
|
// --- without enc/dec ---
|
|
describe('without enc/dec', () => {
|
|
let db
|
|
let tmpDir
|
|
|
|
before(() => {
|
|
tmpDir = makeTmpDir()
|
|
db = createDb(tmpDir, 'testuser')
|
|
})
|
|
|
|
after(() => {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('insert and find returns original data', async () => {
|
|
const doc = { host: 'example.com', port: 22 }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
assert.ok(inserted._id, 'inserted document should have _id')
|
|
assert.equal(inserted.host, 'example.com')
|
|
|
|
const results = await db.dbAction('bookmarks', 'find')
|
|
assert.equal(results.length, 1)
|
|
assert.equal(results[0].host, 'example.com')
|
|
assert.equal(results[0].port, 22)
|
|
})
|
|
|
|
test('findOne returns correct document', async () => {
|
|
const doc = { host: 'other.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found.host, 'other.com')
|
|
})
|
|
|
|
test('update modifies data', async () => {
|
|
const doc = { host: 'update-me.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
await db.dbAction('bookmarks', 'update', { _id: inserted._id }, { $set: { host: 'updated.com' } })
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found.host, 'updated.com')
|
|
})
|
|
|
|
test('remove deletes document', async () => {
|
|
const doc = { host: 'remove-me.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
const changes = await db.dbAction('bookmarks', 'remove', { _id: inserted._id })
|
|
assert.ok(changes >= 1)
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found, null)
|
|
})
|
|
|
|
test('non-enc table (lastStates) stores data normally', async () => {
|
|
const doc = { key: 'value' }
|
|
const inserted = await db.dbAction('lastStates', 'insert', doc)
|
|
const found = await db.dbAction('lastStates', 'findOne', { _id: inserted._id })
|
|
assert.equal(found.key, 'value')
|
|
})
|
|
})
|
|
|
|
// --- with enc/dec ---
|
|
describe('with enc/dec', () => {
|
|
let db
|
|
let tmpDir
|
|
let dbFolder
|
|
|
|
before(() => {
|
|
tmpDir = makeTmpDir()
|
|
dbFolder = path.join(tmpDir, 'electerm', 'users', 'testuser')
|
|
db = createDb(tmpDir, 'testuser', encOpts)
|
|
})
|
|
|
|
after(() => {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('inserted bookmarks data is encrypted on disk', async () => {
|
|
const doc = { host: 'secret.com', password: 'hunter2' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
assert.ok(inserted._id)
|
|
|
|
// Read raw sqlite file to confirm the value is not plain text
|
|
const dbPath = path.join(dbFolder, 'electerm.db')
|
|
const raw = fs.readFileSync(dbPath, 'latin1')
|
|
assert.ok(!raw.includes('hunter2'), 'raw db should NOT contain plaintext password')
|
|
assert.ok(!raw.includes('secret.com'), 'raw db should NOT contain plaintext host')
|
|
})
|
|
|
|
test('find returns decrypted data', async () => {
|
|
const results = await db.dbAction('bookmarks', 'find')
|
|
const found = results.find(r => r.host === 'secret.com')
|
|
assert.ok(found, 'should find the document with decrypted host')
|
|
assert.equal(found.password, 'hunter2')
|
|
})
|
|
|
|
test('findOne returns decrypted data', async () => {
|
|
const doc = { host: 'findone.com', user: 'alice' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found.host, 'findone.com')
|
|
assert.equal(found.user, 'alice')
|
|
})
|
|
|
|
test('update encrypts new value and find decrypts it', async () => {
|
|
const doc = { host: 'todo-update.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
await db.dbAction('bookmarks', 'update', { _id: inserted._id }, { $set: { host: 'updated-enc.com' } })
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found.host, 'updated-enc.com')
|
|
})
|
|
|
|
test('data table: only userConfig record is encrypted', async () => {
|
|
// userConfig should be encrypted
|
|
await db.dbAction('data', 'insert', { _id: 'userConfig', value: 'topSecret123' })
|
|
// other data record should NOT be encrypted
|
|
await db.dbAction('data', 'insert', { _id: 'version', value: '1.0.0' })
|
|
const dbPath = path.join(dbFolder, 'electerm_data.db')
|
|
const raw = fs.readFileSync(dbPath, 'latin1')
|
|
assert.ok(!raw.includes('topSecret123'), 'userConfig value should NOT be plaintext on disk')
|
|
assert.ok(raw.includes('1.0.0'), 'non-userConfig data should be plaintext on disk')
|
|
})
|
|
|
|
test('data find returns decrypted userConfig, plain others', async () => {
|
|
const results = await db.dbAction('data', 'find')
|
|
const uc = results.find(r => r._id === 'userConfig')
|
|
const ver = results.find(r => r._id === 'version')
|
|
assert.ok(uc, 'should find userConfig')
|
|
assert.equal(uc.value, 'topSecret123', 'userConfig value should be decrypted')
|
|
assert.ok(ver, 'should find version')
|
|
assert.equal(ver.value, '1.0.0', 'version value should be readable')
|
|
})
|
|
|
|
test('profiles table is encrypted', async () => {
|
|
const doc = { name: 'myProfile', secret: 'profileSecret' }
|
|
await db.dbAction('profiles', 'insert', doc)
|
|
const dbPath = path.join(dbFolder, 'electerm.db')
|
|
const raw = fs.readFileSync(dbPath, 'latin1')
|
|
assert.ok(!raw.includes('profileSecret'), 'profiles db should NOT contain plaintext secret')
|
|
})
|
|
|
|
test('non-enc table (quickCommands) is NOT encrypted', async () => {
|
|
const doc = { cmd: 'ls -la' }
|
|
await db.dbAction('quickCommands', 'insert', doc)
|
|
const dbPath = path.join(dbFolder, 'electerm.db')
|
|
const raw = fs.readFileSync(dbPath, 'latin1')
|
|
assert.ok(raw.includes('ls -la'), 'non-enc tables should store data as plaintext')
|
|
})
|
|
|
|
test('remove works on enc table', async () => {
|
|
const doc = { host: 'to-delete.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
const changes = await db.dbAction('bookmarks', 'remove', { _id: inserted._id })
|
|
assert.ok(changes >= 1)
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found, null)
|
|
})
|
|
})
|
|
})
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// nedb tests (works on all Node versions)
|
|
// ---------------------------------------------------------------------------
|
|
describe('nedb createDb', () => {
|
|
const { createDb } = require('../../src/app/lib/nedb')
|
|
|
|
// --- without enc/dec ---
|
|
describe('without enc/dec', () => {
|
|
let db
|
|
let tmpDir
|
|
|
|
before(() => {
|
|
tmpDir = makeTmpDir()
|
|
db = createDb(tmpDir, 'testuser')
|
|
})
|
|
|
|
after(() => {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('insert and find returns original data', async () => {
|
|
const doc = { host: 'example.com', port: 22 }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
assert.ok(inserted._id)
|
|
assert.equal(inserted.host, 'example.com')
|
|
|
|
const results = await db.dbAction('bookmarks', 'find', {})
|
|
assert.ok(results.some(r => r.host === 'example.com'))
|
|
})
|
|
|
|
test('findOne returns correct document', async () => {
|
|
const doc = { host: 'other.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found.host, 'other.com')
|
|
})
|
|
|
|
test('update modifies data', async () => {
|
|
const doc = { host: 'update-me.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
await db.dbAction('bookmarks', 'update', { _id: inserted._id }, { $set: { host: 'updated.com' } })
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found.host, 'updated.com')
|
|
})
|
|
|
|
test('remove deletes document', async () => {
|
|
const doc = { host: 'remove-me.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
await db.dbAction('bookmarks', 'remove', { _id: inserted._id })
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found, null)
|
|
})
|
|
})
|
|
|
|
// --- with enc/dec ---
|
|
describe('with enc/dec', () => {
|
|
let db
|
|
let tmpDir
|
|
|
|
before(() => {
|
|
tmpDir = makeTmpDir()
|
|
db = createDb(tmpDir, 'testuser', encOpts)
|
|
})
|
|
|
|
after(() => {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('inserted bookmarks data is encrypted on disk', async () => {
|
|
const doc = { host: 'secret.com', password: 'hunter2' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
assert.ok(inserted._id)
|
|
|
|
// Read raw nedb file to confirm the value is not plain text
|
|
const nedbPath = path.join(
|
|
tmpDir, 'electerm', 'users', 'testuser', 'electerm.bookmarks.nedb'
|
|
)
|
|
const raw = fs.readFileSync(nedbPath, 'utf8')
|
|
assert.ok(!raw.includes('hunter2'), 'nedb file should NOT contain plaintext password')
|
|
assert.ok(!raw.includes('secret.com'), 'nedb file should NOT contain plaintext host')
|
|
})
|
|
|
|
test('find returns decrypted data', async () => {
|
|
const results = await db.dbAction('bookmarks', 'find', {})
|
|
const found = results.find(r => r.host === 'secret.com')
|
|
assert.ok(found, 'should find the document with decrypted host')
|
|
assert.equal(found.password, 'hunter2')
|
|
})
|
|
|
|
test('findOne returns decrypted data', async () => {
|
|
const doc = { host: 'findone.com', user: 'bob' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found.host, 'findone.com')
|
|
assert.equal(found.user, 'bob')
|
|
})
|
|
|
|
test('update encrypts new value and find decrypts it', async () => {
|
|
const doc = { host: 'todo-update.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
await db.dbAction('bookmarks', 'update', { _id: inserted._id }, { $set: { host: 'updated-enc.com' } })
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found.host, 'updated-enc.com')
|
|
})
|
|
|
|
test('data table: only userConfig record is encrypted', async () => {
|
|
await db.dbAction('data', 'insert', { _id: 'userConfig', secret: 'mySecret' })
|
|
await db.dbAction('data', 'insert', { _id: 'version', value: '1.0.0' })
|
|
const nedbPath = path.join(
|
|
tmpDir, 'electerm', 'users', 'testuser', 'electerm.data.nedb'
|
|
)
|
|
const raw = fs.readFileSync(nedbPath, 'utf8')
|
|
assert.ok(!raw.includes('mySecret'), 'userConfig secret should NOT be plaintext in nedb')
|
|
assert.ok(raw.includes('1.0.0'), 'non-userConfig data should be plaintext in nedb')
|
|
})
|
|
|
|
test('data find returns decrypted userConfig, plain others', async () => {
|
|
const results = await db.dbAction('data', 'find', {})
|
|
const uc = results.find(r => r._id === 'userConfig')
|
|
const ver = results.find(r => r._id === 'version')
|
|
assert.ok(uc, 'should find userConfig')
|
|
assert.equal(uc.secret, 'mySecret', 'userConfig secret should be decrypted')
|
|
assert.ok(ver, 'should find version')
|
|
assert.equal(ver.value, '1.0.0', 'version value should be readable')
|
|
})
|
|
|
|
test('profiles table is encrypted', async () => {
|
|
const doc = { name: 'myProfile', secret: 'profileSecret' }
|
|
await db.dbAction('profiles', 'insert', doc)
|
|
const nedbPath = path.join(
|
|
tmpDir, 'electerm', 'users', 'testuser', 'electerm.profiles.nedb'
|
|
)
|
|
const raw = fs.readFileSync(nedbPath, 'utf8')
|
|
assert.ok(!raw.includes('profileSecret'), 'profiles nedb should NOT contain plaintext secret')
|
|
})
|
|
|
|
test('non-enc table (quickCommands) is NOT encrypted', async () => {
|
|
const doc = { cmd: 'echo hello' }
|
|
await db.dbAction('quickCommands', 'insert', doc)
|
|
const nedbPath = path.join(
|
|
tmpDir, 'electerm', 'users', 'testuser', 'electerm.quickCommands.nedb'
|
|
)
|
|
const raw = fs.readFileSync(nedbPath, 'utf8')
|
|
assert.ok(raw.includes('echo hello'), 'non-enc tables should store data as plaintext')
|
|
})
|
|
|
|
test('remove works on enc table', async () => {
|
|
const doc = { host: 'to-delete.com' }
|
|
const inserted = await db.dbAction('bookmarks', 'insert', doc)
|
|
await db.dbAction('bookmarks', 'remove', { _id: inserted._id })
|
|
const found = await db.dbAction('bookmarks', 'findOne', { _id: inserted._id })
|
|
assert.equal(found, null)
|
|
})
|
|
})
|
|
})
|