Skip to content

Commit

Permalink
feat: allow skipping IPNS record validation (#101)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
achingbrain authored Jan 18, 2024
1 parent 0fdea42 commit ec0ba89
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 11 deletions.
39 changes: 29 additions & 10 deletions packages/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -195,7 +195,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
}
}

async getIPNS (peerId: PeerId, options: AbortOptions = {}): Promise<IPNSRecord> {
async getIPNS (peerId: PeerId, options: GetIPNSOptions = {}): Promise<IPNSRecord> {
log('getIPNS starts: %c', peerId)

const signal = anySignal([this.shutDownController.signal, options.signal, AbortSignal.timeout(this.timeout)])
Expand All @@ -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.
Expand All @@ -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()
Expand All @@ -243,7 +254,7 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
}

async putIPNS (peerId: PeerId, record: IPNSRecord, options: AbortOptions = {}): Promise<void> {
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()
Expand All @@ -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)
}
}

Expand Down
12 changes: 11 additions & 1 deletion packages/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<IPNSRecord>
getIPNS(peerId: PeerId, options?: GetIPNSOptions): Promise<IPNSRecord>

/**
* Publishes the given {@link IPNSRecord} for the provided {@link PeerId}
Expand Down

0 comments on commit ec0ba89

Please sign in to comment.