mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2026-09-27 04:51:45 +00:00
refactor: extract payload pack/unpack pipeline into @cryptgeon/shared
This commit is contained in:
@@ -2,4 +2,5 @@ export * from "./crypto.js";
|
||||
export * from "./types.js";
|
||||
export * from "./api.js";
|
||||
export * from "./compression.js";
|
||||
export * from "./payload.js";
|
||||
export { encode, decode } from "@msgpack/msgpack";
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { packContent, unpackContent } from "./payload";
|
||||
import { deriveKey, generateKey, bytesToUtf8, utf8ToBytes } from "./crypto";
|
||||
|
||||
describe("payload", () => {
|
||||
it("round-trips a text note through the full pipeline", async () => {
|
||||
const { data, extra, key } = await packContent({ type: "text", text: "hello world" });
|
||||
expect(extra.length).toBe(0);
|
||||
const content = unpackContent(data, key);
|
||||
expect(content).toEqual({ type: "text", data: "hello world" });
|
||||
});
|
||||
|
||||
it("round-trips with a password and sets extra", async () => {
|
||||
const { data, extra, key } = await packContent({ type: "text", text: "secret" }, "pw123");
|
||||
expect(extra.length).toBeGreaterThan(0);
|
||||
const content = unpackContent(data, key);
|
||||
expect(content).toEqual({ type: "text", data: "secret" });
|
||||
});
|
||||
|
||||
it("derives a deterministic key when password is set", async () => {
|
||||
const salt = utf8ToBytes("fixed-salt");
|
||||
const expected = deriveKey("pw", salt);
|
||||
const first = deriveKey("pw", salt);
|
||||
expect(first).toEqual(expected);
|
||||
});
|
||||
|
||||
it("round-trips files (FileDTO)", async () => {
|
||||
const file = { name: "a.txt", mime: "text/plain", size: 5, data: utf8ToBytes("hello") };
|
||||
const { data, key } = await packContent({ type: "files", files: [file] });
|
||||
const content = unpackContent(data, key);
|
||||
expect(content.type).toBe("files");
|
||||
if (content.type === "files") {
|
||||
expect(content.data).toHaveLength(1);
|
||||
expect(content.data[0].name).toBe("a.txt");
|
||||
expect(bytesToUtf8(content.data[0].data)).toBe("hello");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function deriveKeyContent(password: string, salt: Uint8Array): Uint8Array {
|
||||
return deriveKey(password, salt);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { encode, decode } from "@msgpack/msgpack";
|
||||
import { compress, decompress } from "./compression.js";
|
||||
import {
|
||||
deriveKey,
|
||||
encrypt,
|
||||
decrypt,
|
||||
generateKey,
|
||||
randomBytes,
|
||||
} from "./crypto.js";
|
||||
import type { FileDTO, NoteContent } from "./types.js";
|
||||
|
||||
export type NoteInput =
|
||||
| { type: "text"; text: string }
|
||||
| { type: "files"; files: (File | FileDTO)[] };
|
||||
|
||||
export type PackResult = {
|
||||
data: Uint8Array;
|
||||
extra: Uint8Array;
|
||||
key: Uint8Array;
|
||||
};
|
||||
|
||||
export async function packContent(
|
||||
input: NoteInput,
|
||||
password?: string,
|
||||
): Promise<PackResult> {
|
||||
let key: Uint8Array;
|
||||
let extra = new Uint8Array();
|
||||
if (password) {
|
||||
const salt = randomBytes(16);
|
||||
key = deriveKey(password, salt);
|
||||
extra = encode({ salt, N: 32768, r: 8, p: 1 });
|
||||
} else {
|
||||
key = generateKey();
|
||||
}
|
||||
|
||||
let content: NoteContent;
|
||||
if (input.type === "text") {
|
||||
content = { type: "text", data: input.text };
|
||||
} else {
|
||||
content = {
|
||||
type: "files",
|
||||
data: await Promise.all(
|
||||
input.files.map(async (file) => {
|
||||
const name = file instanceof File ? file.name : file.name;
|
||||
const mime = file instanceof File ? file.type : file.mime;
|
||||
const data = file instanceof File ? new Uint8Array(await file.arrayBuffer()) : file.data;
|
||||
return { name, mime, size: data.length, data };
|
||||
}),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const encoded = encrypt(compress(encode(content)), key);
|
||||
return { data: encoded, extra, key };
|
||||
}
|
||||
|
||||
export function unpackContent(data: Uint8Array, key: Uint8Array): NoteContent {
|
||||
const content = decode(decompress(decrypt(data, key))) as NoteContent;
|
||||
if (content.type !== "text" && content.type !== "files") {
|
||||
throw new Error("Unknown content type");
|
||||
}
|
||||
return content;
|
||||
}
|
||||
Reference in New Issue
Block a user