feat: implement CookieBridge M1 — core protocol & relay server
- Protocol spec: encrypted envelope format, device identity (Ed25519 + X25519), LWW conflict resolution with Lamport clocks - E2E encryption: XChaCha20-Poly1305 via sodium-native, X25519 key exchange - WebSocket relay server: stateless message forwarding, device auth via challenge-response, offline message queuing, ping/pong keepalive - Device pairing: time-limited pairing codes, key exchange broker via HTTP - Sync protocol: envelope builder/opener, conflict-resolving cookie store - 31 tests passing (crypto, pairing, conflict resolution, full integration) Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
57
projects/cookiebridge/tests/pairing.test.ts
Normal file
57
projects/cookiebridge/tests/pairing.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { PairingStore, generatePairingCode } from "../src/pairing/index.js";
|
||||
|
||||
describe("PairingStore", () => {
|
||||
it("creates and finds a pairing session", () => {
|
||||
const store = new PairingStore();
|
||||
const session = store.create("device-a-id", "device-a-x25519-pub");
|
||||
expect(session.pairingCode).toHaveLength(6);
|
||||
expect(session.deviceId).toBe("device-a-id");
|
||||
|
||||
const found = store.find(session.pairingCode);
|
||||
expect(found).not.toBeNull();
|
||||
expect(found!.deviceId).toBe("device-a-id");
|
||||
});
|
||||
|
||||
it("consumes a session (one-time use)", () => {
|
||||
const store = new PairingStore();
|
||||
const session = store.create("d1", "pub1");
|
||||
|
||||
const consumed = store.consume(session.pairingCode);
|
||||
expect(consumed).not.toBeNull();
|
||||
expect(consumed!.deviceId).toBe("d1");
|
||||
|
||||
// Second consume returns null
|
||||
const again = store.consume(session.pairingCode);
|
||||
expect(again).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for unknown code", () => {
|
||||
const store = new PairingStore();
|
||||
expect(store.find("999999")).toBeNull();
|
||||
});
|
||||
|
||||
it("expires sessions after TTL", () => {
|
||||
const store = new PairingStore();
|
||||
const session = store.create("d1", "pub1");
|
||||
|
||||
// Manually expire by setting expiresAt in the past
|
||||
// We access the internal session via find and mutate it
|
||||
const found = store.find(session.pairingCode);
|
||||
if (found) {
|
||||
(found as { expiresAt: number }).expiresAt = Date.now() - 1000;
|
||||
}
|
||||
|
||||
expect(store.find(session.pairingCode)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("generatePairingCode", () => {
|
||||
it("generates 6-digit codes", () => {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const code = generatePairingCode();
|
||||
expect(code).toHaveLength(6);
|
||||
expect(/^\d{6}$/.test(code)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user