Release rolling / release (push) Successful in 12m24s
Default peers are read-only; admin requires seed proof and operators redeem AutoPass packages with signed grants. ACL UI and docs match the new trust model.
117 lines
3.5 KiB
JavaScript
117 lines
3.5 KiB
JavaScript
/**
|
|
* Full PeerSession + handler registration over HyperDHT.
|
|
*/
|
|
import test from 'brittle'
|
|
import DHT from 'hyperdht'
|
|
import b4a from 'b4a'
|
|
import createTestnet from 'hyperdht/testnet.js'
|
|
import { PeerSession } from '../server/rpc/session.js'
|
|
import { registerAllHandlers } from '../server/rpc/register.js'
|
|
import { Methods, Roles } from '../shared/protocol.js'
|
|
import { initAuthKeys } from '../server/core/auth-keys.js'
|
|
import { createAdminProof } from '../shared/crypto-auth.js'
|
|
import { getClientIdentity } from '../client/identity.js'
|
|
|
|
// Allow browse in tests (production default is deny without PEARDOCK_BROWSE_ROOTS)
|
|
process.env.PEARDOCK_BROWSE_ROOTS = process.env.PEARDOCK_BROWSE_ROOTS || '/'
|
|
|
|
const SEED = 'ab'.repeat(32)
|
|
|
|
test('PearDockConnection + PeerSession ping round-trip', async (t) => {
|
|
const testnet = await createTestnet()
|
|
const { bootstrap } = testnet
|
|
|
|
const serverDht = new DHT({ bootstrap })
|
|
// Deterministic seed for admin proof verification
|
|
const seedBuf = b4a.from(SEED, 'hex')
|
|
const keyPair = DHT.keyPair(seedBuf)
|
|
initAuthKeys({
|
|
seedHex: SEED,
|
|
publicKeyHex: b4a.toString(keyPair.publicKey, 'hex'),
|
|
})
|
|
const server = serverDht.createServer()
|
|
|
|
server.on('connection', (socket) => {
|
|
const session = new PeerSession(socket, {
|
|
serverPublicKey: keyPair.publicKey,
|
|
})
|
|
registerAllHandlers(session)
|
|
})
|
|
|
|
await server.listen(keyPair)
|
|
const publicKeyHex = Buffer.from(keyPair.publicKey).toString('hex')
|
|
|
|
const clientIdentity = getClientIdentity()
|
|
const clientDht = new DHT({ bootstrap, keyPair: clientIdentity.keyPair })
|
|
const socket = clientDht.connect(keyPair.publicKey)
|
|
await new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => reject(new Error('timeout')), 15000)
|
|
const done = () => {
|
|
clearTimeout(timer)
|
|
resolve()
|
|
}
|
|
socket.once('connect', done)
|
|
socket.once('open', done)
|
|
socket.once('error', (e) => {
|
|
clearTimeout(timer)
|
|
reject(e)
|
|
})
|
|
})
|
|
|
|
const { default: ProtomuxRPC } = await import('protomux-rpc')
|
|
const { PROTOCOL } = await import('../shared/protocol.js')
|
|
const { encodings } = await import('../shared/encodings.js')
|
|
|
|
const rpc = new ProtomuxRPC(socket, {
|
|
id: keyPair.publicKey,
|
|
protocol: PROTOCOL,
|
|
...encodings,
|
|
})
|
|
|
|
// Viewer without proof: ping ok, mutate denied
|
|
const hsViewer = await rpc.request(
|
|
Methods.handshake,
|
|
{ clientName: 'test', clientVersion: '0' },
|
|
encodings
|
|
)
|
|
t.is(hsViewer.role, Roles.viewer)
|
|
|
|
const pong = await rpc.request(Methods.ping, {}, encodings)
|
|
t.ok(pong.success)
|
|
t.ok(pong.pong)
|
|
|
|
try {
|
|
await rpc.request(Methods.browseDirectory, { path: '/' }, encodings)
|
|
t.fail('viewer should not browse')
|
|
} catch (err) {
|
|
t.ok(
|
|
err.code === 'PERMISSION_DENIED' ||
|
|
err.cause?.code === 'PERMISSION_DENIED' ||
|
|
/Permission denied/i.test(err.message + (err.cause?.message || ''))
|
|
)
|
|
}
|
|
|
|
// Admin via seed HMAC proof
|
|
const adminProof = createAdminProof(SEED, {
|
|
peerId: clientIdentity.publicKeyHex,
|
|
serverPublicKeyHex: publicKeyHex,
|
|
})
|
|
const hsAdmin = await rpc.request(
|
|
Methods.handshake,
|
|
{ clientName: 'test', clientVersion: '0', adminProof },
|
|
encodings
|
|
)
|
|
t.is(hsAdmin.role, Roles.admin)
|
|
t.is(hsAdmin.auth?.mode, 'seed')
|
|
|
|
const browse = await rpc.request(Methods.browseDirectory, { path: '/' }, encodings)
|
|
t.ok(browse.success)
|
|
t.ok(Array.isArray(browse.contents))
|
|
|
|
socket.destroy()
|
|
await server.close()
|
|
await serverDht.destroy()
|
|
await clientDht.destroy()
|
|
await testnet.destroy()
|
|
})
|