An atproto PDS written in Go
103
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v0.6.0 177 lines 3.7 kB view raw
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 "github.com/bluesky-social/indigo/atproto/atcrypto" 17 "github.com/bluesky-social/indigo/util" 18 "github.com/haileyok/cocoon/identity" 19) 20 21type Client struct { 22 h *http.Client 23 service string 24 pdsHostname string 25 rotationKey *atcrypto.PrivateKeyK256 26} 27 28type ClientArgs struct { 29 H *http.Client 30 Service string 31 RotationKey []byte 32 PdsHostname string 33} 34 35func NewClient(args *ClientArgs) (*Client, error) { 36 if args.Service == "" { 37 args.Service = "https://plc.directory" 38 } 39 40 if args.H == nil { 41 args.H = util.RobustHTTPClient() 42 } 43 44 rk, err := atcrypto.ParsePrivateBytesK256([]byte(args.RotationKey)) 45 if err != nil { 46 return nil, err 47 } 48 49 return &Client{ 50 h: args.H, 51 service: args.Service, 52 rotationKey: rk, 53 pdsHostname: args.PdsHostname, 54 }, nil 55} 56 57func (c *Client) CreateDID(sigkey *atcrypto.PrivateKeyK256, recovery string, handle string) (string, *Operation, error) { 58 creds, err := c.CreateDidCredentials(sigkey, recovery, handle) 59 if err != nil { 60 return "", nil, err 61 } 62 63 op := Operation{ 64 Type: "plc_operation", 65 VerificationMethods: creds.VerificationMethods, 66 RotationKeys: creds.RotationKeys, 67 AlsoKnownAs: creds.AlsoKnownAs, 68 Services: creds.Services, 69 Prev: nil, 70 } 71 72 if err := c.SignOp(sigkey, &op); err != nil { 73 return "", nil, err 74 } 75 76 did, err := DidFromOp(&op) 77 if err != nil { 78 return "", nil, err 79 } 80 81 return did, &op, nil 82} 83 84func (c *Client) CreateDidCredentials(sigkey *atcrypto.PrivateKeyK256, recovery string, handle string) (*DidCredentials, error) { 85 pubsigkey, err := sigkey.PublicKey() 86 if err != nil { 87 return nil, err 88 } 89 90 pubrotkey, err := c.rotationKey.PublicKey() 91 if err != nil { 92 return nil, err 93 } 94 95 // todo 96 rotationKeys := []string{pubrotkey.DIDKey()} 97 if recovery != "" { 98 rotationKeys = func(recovery string) []string { 99 newRotationKeys := []string{recovery} 100 for _, k := range rotationKeys { 101 newRotationKeys = append(newRotationKeys, k) 102 } 103 return newRotationKeys 104 }(recovery) 105 } 106 107 creds := DidCredentials{ 108 VerificationMethods: map[string]string{ 109 "atproto": pubsigkey.DIDKey(), 110 }, 111 RotationKeys: rotationKeys, 112 AlsoKnownAs: []string{ 113 "at://" + handle, 114 }, 115 Services: map[string]identity.OperationService{ 116 "atproto_pds": { 117 Type: "AtprotoPersonalDataServer", 118 Endpoint: "https://" + c.pdsHostname, 119 }, 120 }, 121 } 122 123 return &creds, nil 124} 125 126func (c *Client) SignOp(sigkey *atcrypto.PrivateKeyK256, op *Operation) error { 127 b, err := op.MarshalCBOR() 128 if err != nil { 129 return err 130 } 131 132 sig, err := c.rotationKey.HashAndSign(b) 133 if err != nil { 134 return err 135 } 136 137 op.Sig = base64.RawURLEncoding.EncodeToString(sig) 138 139 return nil 140} 141 142func (c *Client) SendOperation(ctx context.Context, did string, op *Operation) error { 143 b, err := json.Marshal(op) 144 if err != nil { 145 return err 146 } 147 148 req, err := http.NewRequestWithContext(ctx, "POST", c.service+"/"+url.QueryEscape(did), bytes.NewBuffer(b)) 149 if err != nil { 150 return err 151 } 152 153 req.Header.Add("content-type", "application/json") 154 155 resp, err := c.h.Do(req) 156 if err != nil { 157 return err 158 } 159 defer resp.Body.Close() 160 161 b, err = io.ReadAll(resp.Body) 162 if err != nil { 163 return fmt.Errorf("error sending operation. status code: %d, response: %s", resp.StatusCode, string(b)) 164 } 165 166 return nil 167} 168 169func DidFromOp(op *Operation) (string, error) { 170 b, err := op.MarshalCBOR() 171 if err != nil { 172 return "", err 173 } 174 s := sha256.Sum256(b) 175 b32 := strings.ToLower(base32.StdEncoding.EncodeToString(s[:])) 176 return "did:plc:" + b32[0:24], nil 177}