master
go 239 lines 5.83 KB
Raw
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(ctx); 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 }