From ec0ba898cbe6e839eda7ffc03c672cbaf7fcc4f6 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Thu, 18 Jan 2024 11:34:00 +0100 Subject: [PATCH] feat: allow skipping IPNS record validation (#101) Adds a `validate` option to `getIPNS` which allows skipping validation of the returned record. Use this option when you are doing validation at a higher level. --- packages/client/src/client.ts | 39 ++++++++++++++++++++++++++--------- packages/client/src/index.ts | 12 ++++++++++- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 209a1da..791f0cb 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -10,7 +10,7 @@ import { parse as ndjson } from 'it-ndjson' import defer from 'p-defer' import PQueue from 'p-queue' import { DelegatedRoutingV1HttpApiClientContentRouting, DelegatedRoutingV1HttpApiClientPeerRouting } from './routings.js' -import type { DelegatedRoutingV1HttpApiClient, DelegatedRoutingV1HttpApiClientInit, PeerRecord } from './index.js' +import type { DelegatedRoutingV1HttpApiClient, DelegatedRoutingV1HttpApiClientInit, GetIPNSOptions, PeerRecord } from './index.js' import type { ContentRouting, PeerRouting, AbortOptions, PeerId } from '@libp2p/interface' import type { Multiaddr } from '@multiformats/multiaddr' import type { CID } from 'multiformats' @@ -195,7 +195,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV } } - async getIPNS (peerId: PeerId, options: AbortOptions = {}): Promise { + async getIPNS (peerId: PeerId, options: GetIPNSOptions = {}): Promise { log('getIPNS starts: %c', peerId) const signal = anySignal([this.shutDownController.signal, options.signal, AbortSignal.timeout(this.timeout)]) @@ -207,14 +207,17 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV return onFinish.promise }) + // https://specs.ipfs.tech/routing/http-routing-v1/ + const resource = `${this.clientUrl}routing/v1/ipns/${peerId.toCID().toString()}` + try { await onStart.promise - // https://specs.ipfs.tech/routing/http-routing-v1/ - const resource = `${this.clientUrl}routing/v1/ipns/${peerId.toCID().toString()}` const getOptions = { headers: { Accept: 'application/vnd.ipfs.ipns-record' }, signal } const res = await fetch(resource, getOptions) + log('getIPNS GET %s %d', resource, res.status) + if (res.status === 404) { // https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes // 404 (Not Found): must be returned if no matching records are found. @@ -231,10 +234,18 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV throw new CodeError('GET ipns response had no body', 'ERR_BAD_RESPONSE') } - const body = new Uint8Array(await res.arrayBuffer()) + const buf = await res.arrayBuffer() + const body = new Uint8Array(buf, 0, buf.byteLength) + + if (options.validate !== false) { + await ipnsValidator(peerIdToRoutingKey(peerId), body) + } - await ipnsValidator(peerIdToRoutingKey(peerId), body) return unmarshal(body) + } catch (err: any) { + log.error('getIPNS GET %s error:', resource, err) + + throw err } finally { signal.clear() onFinish.resolve() @@ -243,7 +254,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV } async putIPNS (peerId: PeerId, record: IPNSRecord, options: AbortOptions = {}): Promise { - log('getIPNS starts: %c', peerId) + log('putIPNS starts: %c', peerId) const signal = anySignal([this.shutDownController.signal, options.signal, AbortSignal.timeout(this.timeout)]) const onStart = defer() @@ -254,22 +265,30 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV return onFinish.promise }) + // https://specs.ipfs.tech/routing/http-routing-v1/ + const resource = `${this.clientUrl}routing/v1/ipns/${peerId.toCID().toString()}` + try { await onStart.promise const body = marshal(record) - // https://specs.ipfs.tech/routing/http-routing-v1/ - const resource = `${this.clientUrl}routing/v1/ipns/${peerId.toCID().toString()}` const getOptions = { method: 'PUT', headers: { 'Content-Type': 'application/vnd.ipfs.ipns-record' }, body, signal } const res = await fetch(resource, getOptions) + + log('putIPNS PUT %s %d', resource, res.status) + if (res.status !== 200) { throw new CodeError('PUT ipns response had status other than 200', 'ERR_BAD_RESPONSE') } + } catch (err: any) { + log.error('putIPNS PUT %s error:', resource, err.stack) + + throw err } finally { signal.clear() onFinish.resolve() - log('getIPNS finished: %c', peerId) + log('putIPNS finished: %c', peerId) } } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index df0137b..ca02c4e 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -78,6 +78,16 @@ export interface DelegatedRoutingV1HttpApiClientInit { timeout?: number } +export interface GetIPNSOptions extends AbortOptions { + /** + * By default incoming IPNS records are validated, pass false here to skip + * validation and just return the record. + * + * @default true + */ + validate?: boolean +} + export interface DelegatedRoutingV1HttpApiClient { /** * Returns an async generator of {@link PeerRecord}s that can provide the @@ -94,7 +104,7 @@ export interface DelegatedRoutingV1HttpApiClient { /** * Returns a promise of a {@link IPNSRecord} for the given {@link PeerId} */ - getIPNS(peerId: PeerId, options?: AbortOptions): Promise + getIPNS(peerId: PeerId, options?: GetIPNSOptions): Promise /** * Publishes the given {@link IPNSRecord} for the provided {@link PeerId}