/ core / coreapi / name.go
name.go
  1  package coreapi
  2  
  3  import (
  4  	"context"
  5  	"errors"
  6  	"fmt"
  7  	"strings"
  8  	"time"
  9  
 10  	"github.com/ipfs/boxo/ipns"
 11  	keystore "github.com/ipfs/boxo/keystore"
 12  	"github.com/ipfs/boxo/namesys"
 13  	"github.com/ipfs/kubo/tracing"
 14  	"go.opentelemetry.io/otel/attribute"
 15  	"go.opentelemetry.io/otel/trace"
 16  
 17  	"github.com/ipfs/boxo/path"
 18  	coreiface "github.com/ipfs/kubo/core/coreiface"
 19  	caopts "github.com/ipfs/kubo/core/coreiface/options"
 20  	ci "github.com/libp2p/go-libp2p/core/crypto"
 21  	peer "github.com/libp2p/go-libp2p/core/peer"
 22  )
 23  
 24  type NameAPI CoreAPI
 25  
 26  // Publish announces new IPNS name and returns the new IPNS entry.
 27  func (api *NameAPI) Publish(ctx context.Context, p path.Path, opts ...caopts.NamePublishOption) (ipns.Name, error) {
 28  	ctx, span := tracing.Span(ctx, "CoreAPI.NameAPI", "Publish", trace.WithAttributes(attribute.String("path", p.String())))
 29  	defer span.End()
 30  
 31  	if err := api.checkPublishAllowed(); err != nil {
 32  		return ipns.Name{}, err
 33  	}
 34  
 35  	options, err := caopts.NamePublishOptions(opts...)
 36  	if err != nil {
 37  		return ipns.Name{}, err
 38  	}
 39  	span.SetAttributes(
 40  		attribute.Bool("allowoffline", options.AllowOffline),
 41  		attribute.String("key", options.Key),
 42  		attribute.Float64("validtime", options.ValidTime.Seconds()),
 43  	)
 44  	if options.TTL != nil {
 45  		span.SetAttributes(attribute.Float64("ttl", options.TTL.Seconds()))
 46  	}
 47  
 48  	// Handle different publishing modes
 49  	if options.AllowDelegated {
 50  		// AllowDelegated mode: check if delegated publishers are configured
 51  		cfg, err := api.repo.Config()
 52  		if err != nil {
 53  			return ipns.Name{}, fmt.Errorf("failed to read config: %w", err)
 54  		}
 55  		delegatedPublishers := cfg.DelegatedPublishersWithAutoConf()
 56  		if len(delegatedPublishers) == 0 {
 57  			return ipns.Name{}, errors.New("no delegated publishers configured: add Ipns.DelegatedPublishers or use --allow-offline for local-only publishing")
 58  		}
 59  		// For allow-delegated mode, we only require that we have delegated publishers configured
 60  		// The node doesn't need P2P connectivity since we're using HTTP publishing
 61  	} else {
 62  		// Normal mode: check online status with allow-offline flag
 63  		err = api.checkOnline(options.AllowOffline)
 64  		if err != nil {
 65  			return ipns.Name{}, err
 66  		}
 67  	}
 68  
 69  	k, err := keylookup(api.privateKey, api.repo.Keystore(), options.Key)
 70  	if err != nil {
 71  		return ipns.Name{}, err
 72  	}
 73  
 74  	eol := time.Now().Add(options.ValidTime)
 75  
 76  	publishOptions := []namesys.PublishOption{
 77  		namesys.PublishWithEOL(eol),
 78  		namesys.PublishWithIPNSOption(ipns.WithV1Compatibility(options.CompatibleWithV1)),
 79  	}
 80  
 81  	if options.TTL != nil {
 82  		publishOptions = append(publishOptions, namesys.PublishWithTTL(*options.TTL))
 83  	}
 84  
 85  	if options.Sequence != nil {
 86  		publishOptions = append(publishOptions, namesys.PublishWithSequence(*options.Sequence))
 87  	}
 88  
 89  	err = api.namesys.Publish(ctx, k, p, publishOptions...)
 90  	if err != nil {
 91  		return ipns.Name{}, err
 92  	}
 93  
 94  	pid, err := peer.IDFromPrivateKey(k)
 95  	if err != nil {
 96  		return ipns.Name{}, err
 97  	}
 98  
 99  	return ipns.NameFromPeer(pid), nil
100  }
101  
102  func (api *NameAPI) Search(ctx context.Context, name string, opts ...caopts.NameResolveOption) (<-chan coreiface.IpnsResult, error) {
103  	ctx, span := tracing.Span(ctx, "CoreAPI.NameAPI", "Search", trace.WithAttributes(attribute.String("name", name)))
104  	defer span.End()
105  
106  	options, err := caopts.NameResolveOptions(opts...)
107  	if err != nil {
108  		return nil, err
109  	}
110  
111  	span.SetAttributes(attribute.Bool("cache", options.Cache))
112  
113  	err = api.checkOnline(true)
114  	if err != nil {
115  		return nil, err
116  	}
117  
118  	var resolver namesys.Resolver = api.namesys
119  	if !options.Cache {
120  		resolver, err = namesys.NewNameSystem(api.routing,
121  			namesys.WithDatastore(api.repo.Datastore()),
122  			namesys.WithDNSResolver(api.dnsResolver))
123  		if err != nil {
124  			return nil, err
125  		}
126  	}
127  
128  	if !strings.HasPrefix(name, "/ipns/") {
129  		name = "/ipns/" + name
130  	}
131  
132  	p, err := path.NewPath(name)
133  	if err != nil {
134  		return nil, err
135  	}
136  
137  	out := make(chan coreiface.IpnsResult)
138  	go func() {
139  		defer close(out)
140  		for res := range resolver.ResolveAsync(ctx, p, options.ResolveOpts...) {
141  			select {
142  			case out <- coreiface.IpnsResult{Path: res.Path, Err: res.Err}:
143  			case <-ctx.Done():
144  				return
145  			}
146  		}
147  	}()
148  
149  	return out, nil
150  }
151  
152  // Resolve attempts to resolve the newest version of the specified name and
153  // returns its path.
154  func (api *NameAPI) Resolve(ctx context.Context, name string, opts ...caopts.NameResolveOption) (path.Path, error) {
155  	ctx, span := tracing.Span(ctx, "CoreAPI.NameAPI", "Resolve", trace.WithAttributes(attribute.String("name", name)))
156  	defer span.End()
157  
158  	ctx, cancel := context.WithCancel(ctx)
159  	defer cancel()
160  
161  	results, err := api.Search(ctx, name, opts...)
162  	if err != nil {
163  		return nil, err
164  	}
165  
166  	err = coreiface.ErrResolveFailed
167  	var p path.Path
168  
169  	for res := range results {
170  		p, err = res.Path, res.Err
171  		if err != nil {
172  			break
173  		}
174  	}
175  
176  	return p, err
177  }
178  
179  func keylookup(self ci.PrivKey, kstore keystore.Keystore, k string) (ci.PrivKey, error) {
180  	////////////////////
181  	// Lookup by name //
182  	////////////////////
183  
184  	// First, lookup self.
185  	if k == "self" {
186  		return self, nil
187  	}
188  
189  	// Then, look in the keystore.
190  	res, err := kstore.Get(k)
191  	if res != nil {
192  		return res, nil
193  	}
194  
195  	if err != nil && err != keystore.ErrNoSuchKey {
196  		return nil, err
197  	}
198  
199  	keys, err := kstore.List()
200  	if err != nil {
201  		return nil, err
202  	}
203  
204  	//////////////////
205  	// Lookup by ID //
206  	//////////////////
207  	targetPid, err := peer.Decode(k)
208  	if err != nil {
209  		return nil, keystore.ErrNoSuchKey
210  	}
211  
212  	// First, check self.
213  	pid, err := peer.IDFromPrivateKey(self)
214  	if err != nil {
215  		return nil, fmt.Errorf("failed to determine peer ID for private key: %w", err)
216  	}
217  	if pid == targetPid {
218  		return self, nil
219  	}
220  
221  	// Then, look in the keystore.
222  	for _, key := range keys {
223  		privKey, err := kstore.Get(key)
224  		if err != nil {
225  			return nil, err
226  		}
227  
228  		pid, err := peer.IDFromPrivateKey(privKey)
229  		if err != nil {
230  			return nil, err
231  		}
232  
233  		if targetPid == pid {
234  			return privKey, nil
235  		}
236  	}
237  
238  	return nil, errors.New("no key by the given name or PeerID was found")
239  }