UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

221 lines (175 loc) β€’ 7.4 kB
import { describe, expect, it } from "vitest"; import { BrowserCryptoProvider } from "../providers/BrowserCryptoProvider.ts"; describe("BrowserCryptoProvider", () => { it("should throw on hashPassword", () => { const crypto = new BrowserCryptoProvider(); expect(() => crypto.hashPassword()).toThrow("not supported in the browser"); }); it("should throw on verifyPassword", () => { const crypto = new BrowserCryptoProvider(); expect(() => crypto.verifyPassword()).toThrow( "not supported in the browser", ); }); it("should produce a deterministic sha256 hash", async () => { const crypto = new BrowserCryptoProvider(); const a = await crypto.hash("hello"); const b = await crypto.hash("hello"); expect(a).toEqual(b); expect(a).toHaveLength(64); }); it("should produce different hashes for different inputs", async () => { const crypto = new BrowserCryptoProvider(); expect(await crypto.hash("a")).not.toEqual(await crypto.hash("b")); }); it("should produce a deterministic hmac", async () => { const crypto = new BrowserCryptoProvider(); const a = await crypto.hmac("payload", "secret"); const b = await crypto.hmac("payload", "secret"); expect(a).toEqual(b); expect(a).toHaveLength(64); }); it("should produce different hmacs for different secrets", async () => { const crypto = new BrowserCryptoProvider(); const a = await crypto.hmac("payload", "key1"); const b = await crypto.hmac("payload", "key2"); expect(a).not.toEqual(b); }); it("should verify a valid hmac", async () => { const crypto = new BrowserCryptoProvider(); const sig = await crypto.hmac("data", "secret"); expect(await crypto.verifyHmac("data", sig, "secret")).toBe(true); }); it("should reject an invalid hmac", async () => { const crypto = new BrowserCryptoProvider(); const sig = await crypto.hmac("data", "secret"); expect(await crypto.verifyHmac("tampered", sig, "secret")).toBe(false); expect(await crypto.verifyHmac("data", "badsig", "secret")).toBe(false); expect(await crypto.verifyHmac("data", sig, "wrongkey")).toBe(false); }); it("should encrypt and decrypt a string", async () => { const crypto = new BrowserCryptoProvider(); const encrypted = await crypto.encrypt("hello world", "mykey"); expect(encrypted).toContain(":"); expect(encrypted).not.toContain("hello"); const decrypted = await crypto.decrypt(encrypted, "mykey"); expect(decrypted).toEqual("hello world"); }); it("should produce different ciphertexts for same plaintext", async () => { const crypto = new BrowserCryptoProvider(); const a = await crypto.encrypt("same", "key"); const b = await crypto.encrypt("same", "key"); expect(a).not.toEqual(b); }); it("should fail to decrypt with wrong key", async () => { const crypto = new BrowserCryptoProvider(); const encrypted = await crypto.encrypt("secret", "correct"); await expect(() => crypto.decrypt(encrypted, "wrong")).rejects.toThrow(); }); it("should fail to decrypt invalid ciphertext format", async () => { const crypto = new BrowserCryptoProvider(); await expect(() => crypto.decrypt("invalid", "key")).rejects.toThrow( "Invalid ciphertext format", ); }); it("should handle unicode in encrypt/decrypt", async () => { const crypto = new BrowserCryptoProvider(); const plaintext = "Hello δΈ–η•Œ 🌍"; const encrypted = await crypto.encrypt(plaintext, "key"); expect(await crypto.decrypt(encrypted, "key")).toEqual(plaintext); }); it("should compare strings in constant time", () => { const crypto = new BrowserCryptoProvider(); expect(crypto.equals("abc", "abc")).toBe(true); expect(crypto.equals("abc", "xyz")).toBe(false); expect(crypto.equals("short", "longer")).toBe(false); }); it("should generate a valid UUID", () => { const crypto = new BrowserCryptoProvider(); const uuid = crypto.randomUUID(); expect(uuid).toMatch( /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, ); }); it("should generate unique UUIDs", () => { const crypto = new BrowserCryptoProvider(); expect(crypto.randomUUID()).not.toEqual(crypto.randomUUID()); }); it("should generate random text of requested length", () => { const crypto = new BrowserCryptoProvider(); expect(crypto.randomText(16)).toHaveLength(16); expect(crypto.randomText(32)).toHaveLength(32); expect(crypto.randomText(1)).toHaveLength(1); }); it("should generate url-safe random text", () => { const crypto = new BrowserCryptoProvider(); const text = crypto.randomText(100); expect(text).toMatch(/^[A-Za-z0-9_-]+$/); }); it("should generate numeric codes of requested length", () => { const crypto = new BrowserCryptoProvider(); const code = crypto.randomCode(6); expect(code).toHaveLength(6); expect(code).toMatch(/^\d{6}$/); }); it("should generate single-digit codes", () => { const crypto = new BrowserCryptoProvider(); const code = crypto.randomCode(1); expect(code).toHaveLength(1); expect(code).toMatch(/^\d$/); }); describe("passphrase-based encryption (protected folios)", () => { // Iteration count tuned down for tests β€” production paths use the // 600k default. Using the default here would push the suite well // past Vitest's per-test timeout in CI. const ITER = 1_000; it("round-trips plaintext through encrypt β†’ decrypt", async () => { const crypto = new BrowserCryptoProvider(); const salt = await crypto.hash("salt-seed"); const key = await crypto.deriveKeyFromPassphrase( "correct horse battery staple", salt, ITER, ); const envelope = await crypto.encryptWithPassphrase( "the secret is in the duck pond", key, salt, ITER, ); const out = await crypto.decryptWithPassphrase( envelope, "correct horse battery staple", ); expect(out).toBe("the secret is in the duck pond"); }); it("rejects the wrong passphrase with OperationError-shaped failure", async () => { const crypto = new BrowserCryptoProvider(); const salt = await crypto.hash("salt-seed"); const key = await crypto.deriveKeyFromPassphrase("right one", salt, ITER); const envelope = await crypto.encryptWithPassphrase( "loot stash coordinates: 42N, 7E", key, salt, ITER, ); // Web Crypto throws DOMException("OperationError") on auth-tag // mismatch. We don't pin the exact class β€” just that it rejects. await expect( crypto.decryptWithPassphrase(envelope, "wrong one"), ).rejects.toThrow(); }); it("emits a fresh IV on every encrypt with the same key", async () => { const crypto = new BrowserCryptoProvider(); const salt = await crypto.hash("salt-seed"); const key = await crypto.deriveKeyFromPassphrase("pw", salt, ITER); const a = JSON.parse( await crypto.encryptWithPassphrase("same content", key, salt, ITER), ) as { iv: string }; const b = JSON.parse( await crypto.encryptWithPassphrase("same content", key, salt, ITER), ) as { iv: string }; expect(a.iv).not.toBe(b.iv); }); }); });