Initial Node.js library and CLI

This commit is contained in:
sergeych committed 2026-08-19 14:07:16 +04:00
commit dd74209ed1
14 files changed
+1343

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
'use strict';
const assert = require('node:assert/strict');
const { execFileSync, spawnSync } = require('node:child_process');
const path = require('node:path');
const test = require('node:test');
const pkg = require('../package.json');
const cli = path.join(__dirname, '..', 'src', 'cli.js');
test('CLI prints concise help', () => {
const output = execFileSync(process.execPath, [cli, '--help'], { encoding: 'utf8' });
assert.match(output, /crypstie create \[file\|-\]/);
assert.match(output, /crypstie open <url>/);
});
test('CLI prints package version', () => {
assert.equal(execFileSync(process.execPath, [cli, '--version'], { encoding: 'utf8' }), `${pkg.version}\n`);
});
test('CLI rejects unknown commands without a stack trace', () => {
const result = spawnSync(process.execPath, [cli, 'oops'], { encoding: 'utf8' });
assert.equal(result.status, 1);
assert.match(result.stderr, /^crypstie: Unknown command/);
assert.equal(result.stderr.includes(' at '), false);
});
+127
View File
@@ -0,0 +1,127 @@
'use strict';
const assert = require('node:assert/strict');
const test = require('node:test');
const { Boss, SymmetricKey } = require('unicrypto');
const {
createCrypstie,
decryptCrypstiePayload,
encodeCrypstieKey,
initializeCrypto,
openCrypstie,
parseCrypstieUrl,
} = require('../src');
const KEY_BYTES = Uint8Array.from({ length: 32 }, (_, index) => index + 1);
const KEY_TEXT = Buffer.from(KEY_BYTES)
.toString('base64')
.replaceAll('+', '.')
.replaceAll('/', '_')
.replace(/=+$/, '');
test('parses the legacy Crypstie URL format', () => {
const parsed = parseCrypstieUrl(`https://crypstie.com/_sample#${KEY_TEXT}`);
assert.equal(parsed.id, 'sample');
assert.deepEqual(parsed.key, KEY_BYTES);
});
test('encodes and decodes legacy Crypstie keys', () => {
assert.deepEqual(parseCrypstieUrl(`https://crypstie.com/_x#${encodeCrypstieKey(KEY_BYTES)}`).key, KEY_BYTES);
});
test('rejects a link without its fragment key', () => {
assert.throws(
() => parseCrypstieUrl('https://crypstie.com/_sample'),
/no decryption key/,
);
});
test('decrypts a BOSS payload produced in the Crypstie wire format', async () => {
await initializeCrypto();
const key = new SymmetricKey({ keyBytes: KEY_BYTES });
const deleteAt = new Date('2030-01-02T03:04:05Z');
const packed = Boss.dump({
guid: 'sample',
encryptedData: key.etaEncryptSync(new TextEncoder().encode('совместимость работает')),
deleteAt,
burnOnShow: false,
showWide: true,
syntaxHighlighting: 'text',
editable: false,
ownerHandle: null,
});
const result = await decryptCrypstiePayload(packed, KEY_BYTES);
assert.equal(result.text, 'совместимость работает');
assert.equal(result.id, 'sample');
assert.equal(result.deleteAt.toISOString(), deleteAt.toISOString());
assert.equal(result.burnOnShow, false);
assert.equal(result.showWide, true);
});
test('downloads the encrypted payload without sending the fragment key', async () => {
await initializeCrypto();
const key = new SymmetricKey({ keyBytes: KEY_BYTES });
const packed = Boss.dump({
guid: 'sample',
encryptedData: key.etaEncryptSync(new TextEncoder().encode('secret')),
deleteAt: new Date('2030-01-02T03:04:05Z'),
burnOnShow: false,
showWide: false,
editable: false,
});
let requested;
const fakeFetch = async (url) => {
requested = url.href;
return new Response(packed, { status: 200 });
};
const result = await openCrypstie(
`https://crypstie.com/_sample#${KEY_TEXT}`,
{ fetch: fakeFetch },
);
assert.equal(requested, 'https://crypstie.com/api/crypstie/sample');
assert.equal(requested.includes(KEY_TEXT), false);
assert.equal(result.text, 'secret');
});
test('creates a server-compatible encrypted BOSS payload', async () => {
let request;
const fakeFetch = async (url, options) => {
request = { url: url.href, options };
return new Response('new_id', { status: 200 });
};
const deleteAt = new Date(Date.now() + 60_000);
const result = await createCrypstie('created by node', {
key: KEY_BYTES,
deleteAt,
rootUrl: 'https://example.test/base',
burnOnShow: true,
fetch: fakeFetch,
});
assert.equal(request.url, 'https://example.test/api/crypstie');
assert.equal(request.options.method, 'POST');
assert.equal(result.url, `https://example.test/_new_id#${KEY_TEXT}`);
assert.equal(result.burnOnShow, true);
const uploaded = Boss.load(new Uint8Array(await request.options.body.get('data').arrayBuffer()));
assert.equal(uploaded.burnOnShow, true);
assert.equal(
uploaded.deleteAt.toISOString(),
new Date(Math.floor(deleteAt.getTime() / 1000) * 1000).toISOString(),
);
assert.equal(
new TextDecoder().decode(new SymmetricKey({ keyBytes: KEY_BYTES }).etaDecryptSync(uploaded.encryptedData)),
'created by node',
);
});
test('reports authentication failure for a wrong key', async () => {
await initializeCrypto();
const key = new SymmetricKey({ keyBytes: KEY_BYTES });
const packed = Boss.dump({ encryptedData: key.etaEncryptSync(new TextEncoder().encode('secret')) });
const wrongKey = Uint8Array.from(KEY_BYTES, (byte) => byte ^ 0xff);
await assert.rejects(() => decryptCrypstiePayload(packed, wrongKey), /authenticate or decrypt/);
});
+27
View File
@@ -0,0 +1,27 @@
'use strict';
const assert = require('node:assert/strict');
const test = require('node:test');
const { createCrypstie, openCrypstie } = require('../src');
test(
'opens a reusable Crypstie from the deployed service',
{ skip: !process.env.CRYPSTIE_TEST_URL },
async () => {
const result = await openCrypstie(process.env.CRYPSTIE_TEST_URL);
assert.match(result.text, /^Crypstie Node compatibility fixture /);
assert.equal(result.burnOnShow, false);
},
);
test(
'creates and reopens a reusable Crypstie on the deployed service',
{ skip: process.env.CRYPSTIE_LIVE_WRITE !== '1' },
async () => {
const marker = `Crypstie Node compatibility fixture ${new Date().toISOString()}`;
const created = await createCrypstie(marker);
const opened = await openCrypstie(created.url);
assert.equal(opened.text, marker);
assert.equal(opened.burnOnShow, false);
},
);