crypstie3-node/test/client.test.js

128 lines
4.2 KiB
JavaScript

'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/);
});