mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
* refactor(cloud): share PostgreSQL schema startup between services * feat(cloud): add durable native push notification gateway * infra(push): define dedicated gateway resources and operational checks * fix(push): bound cross-host admission and simplify gateway configuration * fix(push): validate deploy configuration and preserve topic-error registrations
107 lines
4.0 KiB
TypeScript
107 lines
4.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
buildPushHostChallengePlaintext,
|
|
buildPushHostProofMacInput,
|
|
buildPushHostProofTranscript,
|
|
PUSH_HOST_CHALLENGE_PLAINTEXT_DOMAIN,
|
|
PUSH_HOST_PROOF_TRANSCRIPT_DOMAIN,
|
|
PUSH_HOST_PROOF_TRANSCRIPT_FIELD_COUNT
|
|
} from './push-host-proof-transcript.js'
|
|
import { PUSH_LIMITS } from './push-limits.js'
|
|
|
|
const transcriptInput = {
|
|
gatewayOrigin: 'https://push.onorca.dev',
|
|
gatewayEphemeralPublicKey: new Uint8Array(32).fill(7),
|
|
challengeNonce: new Uint8Array(24).fill(9),
|
|
challengeId: 'challenge-1',
|
|
issuedAt: 1_700_000_000_000,
|
|
expiresAt: 1_700_000_000_000 + PUSH_LIMITS.challengeTtlMs,
|
|
hostFingerprint: 'abcdefghijklmnop',
|
|
hostPublicKey: new Uint8Array(32).fill(4)
|
|
}
|
|
|
|
describe('push host proof transcript', () => {
|
|
it('is deterministic and order dependent', () => {
|
|
const first = buildPushHostProofTranscript(transcriptInput)
|
|
const second = buildPushHostProofTranscript({ ...transcriptInput })
|
|
expect(Buffer.from(first).equals(Buffer.from(second))).toBe(true)
|
|
const different = buildPushHostProofTranscript({
|
|
...transcriptInput,
|
|
challengeId: 'challenge-2'
|
|
})
|
|
expect(Buffer.from(first).equals(Buffer.from(different))).toBe(false)
|
|
})
|
|
|
|
it('encodes exactly the ten specified fields in order', () => {
|
|
const transcript = buildPushHostProofTranscript(transcriptInput)
|
|
const view = new DataView(transcript.buffer, transcript.byteOffset, transcript.byteLength)
|
|
const names: string[] = []
|
|
let offset = 0
|
|
while (offset < transcript.byteLength) {
|
|
const nameLength = view.getUint32(offset, false)
|
|
offset += 4
|
|
names.push(Buffer.from(transcript.slice(offset, offset + nameLength)).toString('utf8'))
|
|
offset += nameLength
|
|
offset += 4 + view.getUint32(offset, false)
|
|
}
|
|
expect(names).toEqual([
|
|
'protocol',
|
|
'version',
|
|
'gatewayOrigin',
|
|
'gatewayEphemeralPublicKey',
|
|
'challengeNonce',
|
|
'challengeId',
|
|
'issuedAt',
|
|
'expiresAt',
|
|
'hostFingerprint',
|
|
'hostPublicKey'
|
|
])
|
|
expect(names).toHaveLength(PUSH_HOST_PROOF_TRANSCRIPT_FIELD_COUNT)
|
|
expect(offset).toBe(transcript.byteLength)
|
|
})
|
|
|
|
it('rejects mis-sized key material', () => {
|
|
expect(() =>
|
|
buildPushHostProofTranscript({
|
|
...transcriptInput,
|
|
hostPublicKey: new Uint8Array(31)
|
|
})
|
|
).toThrow('hostPublicKey must be 32 bytes')
|
|
expect(() =>
|
|
buildPushHostProofTranscript({ ...transcriptInput, challengeNonce: new Uint8Array(23) })
|
|
).toThrow('challengeNonce must be 24 bytes')
|
|
})
|
|
|
|
it('frames the challenge plaintext as domain, length, transcript, secret', () => {
|
|
const transcript = buildPushHostProofTranscript(transcriptInput)
|
|
const secret = new Uint8Array(32).fill(11)
|
|
const plaintext = buildPushHostChallengePlaintext(transcript, secret)
|
|
const domain = Buffer.from(`${PUSH_HOST_CHALLENGE_PLAINTEXT_DOMAIN}\0`, 'utf8')
|
|
expect(Buffer.from(plaintext.slice(0, domain.byteLength)).equals(domain)).toBe(true)
|
|
const declared = new DataView(
|
|
plaintext.buffer,
|
|
plaintext.byteOffset + domain.byteLength,
|
|
4
|
|
).getUint32(0, false)
|
|
expect(declared).toBe(transcript.byteLength)
|
|
expect(plaintext.byteLength).toBe(domain.byteLength + 4 + transcript.byteLength + 32)
|
|
expect(
|
|
Buffer.from(plaintext.slice(plaintext.byteLength - 32)).equals(Buffer.from(secret))
|
|
).toBe(true)
|
|
expect(() => buildPushHostChallengePlaintext(transcript, new Uint8Array(16))).toThrow(
|
|
'challengeSecret must be 32 bytes'
|
|
)
|
|
})
|
|
|
|
it('separates the ack mac input from the challenge domain', () => {
|
|
const transcript = buildPushHostProofTranscript(transcriptInput)
|
|
const macInput = buildPushHostProofMacInput(transcript)
|
|
expect(Buffer.from(macInput).toString('utf8')).toContain(
|
|
`${PUSH_HOST_PROOF_TRANSCRIPT_DOMAIN}\0ack\0`
|
|
)
|
|
expect(macInput.byteLength).toBe(
|
|
Buffer.byteLength(`${PUSH_HOST_PROOF_TRANSCRIPT_DOMAIN}\0ack\0`) + transcript.byteLength
|
|
)
|
|
})
|
|
})
|