Add client-side ZK and hotlink upload crypto with Pinia store.
Encrypt image bytes in the browser, keep keys out of POST bodies (URL fragment or path secret), and POST ciphertext via typed upload API helpers.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { base64ToBytes } from '@/utils/encoding/base64ToBytes';
|
||||
import { buildZkViewerPath } from '@/utils/share/buildZkViewerPath';
|
||||
import { decryptZkUpload } from '@/utils/upload/decryptZkUpload';
|
||||
import { prepareZkUploadRequest } from '@/utils/upload/prepareZkUploadRequest';
|
||||
|
||||
describe('prepareZkUploadRequest', () => {
|
||||
const options = { expiresAt: null, maxViews: null, passwordProtected: false as const };
|
||||
|
||||
it('returns fragmentKey separately from the POST request', async () => {
|
||||
const { request, fragmentKey } = await prepareZkUploadRequest(new Uint8Array([9]), options);
|
||||
|
||||
expect(fragmentKey).toMatch(/^[A-Za-z0-9_-]+$/);
|
||||
expect(request).toEqual({
|
||||
...options,
|
||||
iv: request.iv,
|
||||
ciphertext: request.ciphertext,
|
||||
passwordProtected: false
|
||||
});
|
||||
expect(request).not.toHaveProperty('fragmentKey');
|
||||
expect(Object.keys(request).sort()).toEqual([
|
||||
'ciphertext',
|
||||
'expiresAt',
|
||||
'iv',
|
||||
'maxViews',
|
||||
'passwordProtected'
|
||||
]);
|
||||
});
|
||||
|
||||
it('roundtrips plaintext using request fields and URL fragment key', async () => {
|
||||
const plaintext = new TextEncoder().encode('zk-payload');
|
||||
const { request, fragmentKey } = await prepareZkUploadRequest(plaintext, options);
|
||||
|
||||
const recovered = await decryptZkUpload(
|
||||
base64ToBytes(request.ciphertext),
|
||||
base64ToBytes(request.iv),
|
||||
fragmentKey
|
||||
);
|
||||
|
||||
expect(recovered).toEqual(plaintext);
|
||||
});
|
||||
|
||||
it('wires fragmentKey into the viewer URL hash for decryption', async () => {
|
||||
const plaintext = new TextEncoder().encode('share-link');
|
||||
const uploadId = '7f3e2a1b-0000-4000-8000-000000000001';
|
||||
const { request, fragmentKey } = await prepareZkUploadRequest(plaintext, options);
|
||||
const path = buildZkViewerPath(uploadId, fragmentKey);
|
||||
|
||||
const [pathWithoutHash, hashKey] = path.split('#');
|
||||
|
||||
expect(pathWithoutHash).toBe(`/v/${uploadId}`);
|
||||
expect(hashKey).toBe(fragmentKey);
|
||||
const recovered = await decryptZkUpload(
|
||||
base64ToBytes(request.ciphertext),
|
||||
base64ToBytes(request.iv),
|
||||
hashKey
|
||||
);
|
||||
|
||||
expect(recovered).toEqual(plaintext);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user