1package plc
2
3import (
4 "bytes"
5 "context"
6 "crypto/sha256"
7 "encoding/base32"
8 "encoding/base64"
9 "encoding/json"
10 "fmt"
11 "io"
12 "net/http"
13 "net/url"
14 "strings"
15
16 did "github.com/whyrusleeping/go-did"
17 otel "go.opentelemetry.io/otel"
18)
19
20type PLCServer struct {
21 Host string
22 C *http.Client
23}
24
25func (s *PLCServer) GetDocument(ctx context.Context, didstr string) (*did.Document, error) {
26 ctx, span := otel.Tracer("gosky").Start(ctx, "plsResolveDid")
27 defer span.End()
28
29 if s.C == nil {
30 s.C = http.DefaultClient
31 }
32
33 req, err := http.NewRequest("GET", s.Host+"/"+didstr, nil)
34 if err != nil {
35 return nil, err
36 }
37
38 resp, err := s.C.Do(req.WithContext(ctx))
39 if err != nil {
40 return nil, err
41 }
42
43 defer resp.Body.Close()
44
45 if resp.StatusCode != 200 {
46 return nil, fmt.Errorf("get did request failed (code %d): %s", resp.StatusCode, resp.Status)
47 }
48
49 var doc did.Document
50 if err := json.NewDecoder(resp.Body).Decode(&doc); err != nil {
51 return nil, err
52 }
53
54 return &doc, nil
55}
56
57func (s *PLCServer) FlushCacheFor(did string) {
58 return
59}
60
61type CreateOp struct {
62 Type string `json:"type" cborgen:"type"`
63 SigningKey string `json:"signingKey" cborgen:"signingKey"`
64 RecoveryKey string `json:"recoveryKey" cborgen:"recoveryKey"`
65 Handle string `json:"handle" cborgen:"handle"`
66 Service string `json:"service" cborgen:"service"`
67 Prev *string `json:"prev" cborgen:"prev"`
68 Sig string `json:"sig" cborgen:"sig,omitempty"`
69}
70
71func (s *PLCServer) CreateDID(ctx context.Context, sigkey *did.PrivKey, recovery string, handle string, service string) (string, error) {
72 if s.C == nil {
73 s.C = http.DefaultClient
74 }
75
76 op := CreateOp{
77 Type: "create",
78 SigningKey: sigkey.Public().DID(),
79 RecoveryKey: recovery,
80 Handle: handle,
81 Service: service,
82 }
83
84 buf := new(bytes.Buffer)
85 if err := op.MarshalCBOR(buf); err != nil {
86 return "", err
87 }
88
89 sig, err := sigkey.Sign(buf.Bytes())
90 if err != nil {
91 return "", err
92 }
93
94 op.Sig = base64.RawURLEncoding.EncodeToString(sig)
95
96 opdid, err := didForCreateOp(&op)
97 if err != nil {
98 return "", err
99 }
100
101 body, err := json.Marshal(op)
102 if err != nil {
103 return "", err
104 }
105
106 req, err := http.NewRequest("POST", s.Host+"/"+url.QueryEscape(opdid), bytes.NewReader(body))
107 if err != nil {
108 return "", err
109 }
110
111 req.Header.Set("Content-Type", "application/json")
112
113 resp, err := s.C.Do(req)
114 if err != nil {
115 return "", err
116 }
117
118 defer resp.Body.Close()
119
120 if resp.StatusCode != 200 {
121 b, _ := io.ReadAll(resp.Body)
122 fmt.Println(string(b))
123 return "", fmt.Errorf("bad response from create call: %d %s", resp.StatusCode, resp.Status)
124
125 }
126
127 return opdid, nil
128}
129
130func (s *PLCServer) UpdateUserHandle(ctx context.Context, did string, handle string) error {
131 return fmt.Errorf("handle updates not yet implemented")
132}
133
134func didForCreateOp(op *CreateOp) (string, error) {
135 buf := new(bytes.Buffer)
136 if err := op.MarshalCBOR(buf); err != nil {
137 return "", err
138 }
139
140 h := sha256.Sum256(buf.Bytes())
141 enchash := base32.StdEncoding.EncodeToString(h[:])
142 enchash = strings.ToLower(enchash)
143 return "did:plc:" + enchash[:24], nil
144}