mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 10:37:35 +00:00
* refactor(remote): one paired E2EE handshake core for desktop, CLI, web and mobile clients Every paired client now builds its hello, checks the host's ready, seals its auth frame and opens host frames through src/shared/paired-e2ee-client-handshake. Platforms inject their own NaCl/base64 primitives. The v2 client session and its pure-JS key schedule (@noble/hashes 1.8.0, the version mobile pins) move to src/shared, and the per-host v2 latch policy is shared with an injected store. No wire change: frames match the captured legacy and v2 fixtures byte for byte, and a cross-version test drives the core against the released host and the released client against this host. The auth step is now async so a device-key signer can slot in later. * fix(remote): keep @noble/hashes out of the CLI's unbundled runtime The packaged CLI loads src/shared output from disk, so a bare @noble/hashes import failed the packaged-runtime check. The v2 key schedule is now a primitive: Node clients use the node:crypto schedule (moved to src/shared so the CLI can reach it), while the browser and phone keep the pure-JS one. * fix(mobile): re-check the v2 latch before sending a legacy auth frame The auth step is async, so another socket can latch the host to v2 while a legacy auth frame is being sealed. Refuse it at send time, as before. --------- Co-authored-by: m4air <m4air@Mac.localdomain>
121 lines
3.8 KiB
TypeScript
121 lines
3.8 KiB
TypeScript
import { Buffer } from 'node:buffer'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import { encrypt, encryptBytes } from './e2ee-crypto'
|
|
import type { RemoteRuntimeClientError } from './remote-runtime-client-error'
|
|
import { RemoteRuntimeSubscriptionFrameRouter } from './remote-runtime-subscription-frame-router'
|
|
import { createFixedKeyLegacyHandshakeForTest } from './paired-e2ee-fixed-key-test-fixture'
|
|
|
|
const SHARED_KEY = new Uint8Array(32).fill(7)
|
|
|
|
function createAwaitingAuthRouter() {
|
|
const fail = vi.fn<(error: RemoteRuntimeClientError) => void>()
|
|
const router = new RemoteRuntimeSubscriptionFrameRouter<unknown>({
|
|
handshake: createFixedKeyLegacyHandshakeForTest(SHARED_KEY),
|
|
sealAuth: async () => '{}',
|
|
isSettled: () => false,
|
|
serializedRequest: '{}',
|
|
requestId: 'request-1',
|
|
send: vi.fn(),
|
|
fail,
|
|
onAuthenticated: vi.fn(),
|
|
callbacks: { onResponse: vi.fn() }
|
|
})
|
|
router.state = 'awaiting_authenticated'
|
|
return { fail, router }
|
|
}
|
|
|
|
function handleEncryptedAuthFrame(
|
|
router: RemoteRuntimeSubscriptionFrameRouter<unknown>,
|
|
plaintext: string
|
|
): void {
|
|
router.handleFrame(Buffer.from(encrypt(plaintext, SHARED_KEY)), false)
|
|
}
|
|
|
|
describe('RemoteRuntimeSubscriptionFrameRouter authentication frames', () => {
|
|
it('reports malformed authentication frames as invalid responses', () => {
|
|
const { fail, router } = createAwaitingAuthRouter()
|
|
|
|
handleEncryptedAuthFrame(router, 'not-json')
|
|
|
|
expect(fail).toHaveBeenCalledOnce()
|
|
expect(fail.mock.calls[0][0]).toMatchObject({
|
|
code: 'invalid_runtime_response',
|
|
message: 'Remote Orca runtime returned an invalid E2EE auth frame.'
|
|
})
|
|
})
|
|
|
|
it('reports explicit authentication rejection as a rejected pairing token', () => {
|
|
const { fail, router } = createAwaitingAuthRouter()
|
|
const rejection = JSON.stringify({
|
|
type: 'e2ee_error',
|
|
error: { code: 'unauthorized' }
|
|
})
|
|
|
|
handleEncryptedAuthFrame(router, rejection)
|
|
|
|
expect(fail).toHaveBeenCalledOnce()
|
|
expect(fail.mock.calls[0][0]).toMatchObject({
|
|
code: 'unauthorized',
|
|
message: 'Remote Orca runtime rejected the pairing token.'
|
|
})
|
|
})
|
|
})
|
|
|
|
function createReadyRouter(callbacks: {
|
|
onResponse: (response: unknown) => void
|
|
onBinary?: (bytes: Uint8Array) => void
|
|
}) {
|
|
const fail = vi.fn<(error: RemoteRuntimeClientError) => void>()
|
|
const router = new RemoteRuntimeSubscriptionFrameRouter<unknown>({
|
|
handshake: createFixedKeyLegacyHandshakeForTest(SHARED_KEY),
|
|
sealAuth: async () => '{}',
|
|
isSettled: () => false,
|
|
serializedRequest: '{}',
|
|
requestId: 'request-1',
|
|
send: vi.fn(),
|
|
fail,
|
|
onAuthenticated: vi.fn(),
|
|
callbacks
|
|
})
|
|
router.state = 'ready'
|
|
return { fail, router }
|
|
}
|
|
|
|
describe('RemoteRuntimeSubscriptionFrameRouter consumer callbacks', () => {
|
|
it('fails the subscription instead of throwing out of the socket message handler', () => {
|
|
const { fail, router } = createReadyRouter({
|
|
onResponse: () => {
|
|
throw new Error('Unknown environment: env-1')
|
|
}
|
|
})
|
|
const response = JSON.stringify({
|
|
id: 'request-1',
|
|
ok: true,
|
|
result: {},
|
|
_meta: { runtimeId: 'r' }
|
|
})
|
|
|
|
expect(() => handleEncryptedAuthFrame(router, response)).not.toThrow()
|
|
expect(fail).toHaveBeenCalledOnce()
|
|
expect(fail.mock.calls[0][0]).toMatchObject({
|
|
code: 'runtime_error',
|
|
message: 'Unknown environment: env-1'
|
|
})
|
|
})
|
|
|
|
it('fails the subscription when a binary consumer throws', () => {
|
|
const { fail, router } = createReadyRouter({
|
|
onResponse: vi.fn(),
|
|
onBinary: () => {
|
|
throw new Error('consumer exploded')
|
|
}
|
|
})
|
|
|
|
expect(() =>
|
|
router.handleFrame(Buffer.from(encryptBytes(new Uint8Array([1, 2, 3]), SHARED_KEY)), true)
|
|
).not.toThrow()
|
|
expect(fail).toHaveBeenCalledOnce()
|
|
expect(fail.mock.calls[0][0]).toMatchObject({ code: 'runtime_error' })
|
|
})
|
|
})
|