An atproto PDS written in Go
at main 3.2 kB view raw
1package server 2 3import ( 4 "context" 5 "strings" 6 "time" 7 8 "github.com/Azure/go-autorest/autorest/to" 9 "github.com/bluesky-social/indigo/atproto/atcrypto" 10 "github.com/haileyok/cocoon/identity" 11 "github.com/haileyok/cocoon/internal/helpers" 12 "github.com/haileyok/cocoon/models" 13 "github.com/haileyok/cocoon/plc" 14 "github.com/labstack/echo/v4" 15) 16 17type ComAtprotoSignPlcOperationRequest struct { 18 Token string `json:"token"` 19 VerificationMethods *map[string]string `json:"verificationMethods"` 20 RotationKeys *[]string `json:"rotationKeys"` 21 AlsoKnownAs *[]string `json:"alsoKnownAs"` 22 Services *map[string]identity.OperationService `json:"services"` 23} 24 25type ComAtprotoSignPlcOperationResponse struct { 26 Operation plc.Operation `json:"operation"` 27} 28 29func (s *Server) handleSignPlcOperation(e echo.Context) error { 30 logger := s.logger.With("name", "handleSignPlcOperation") 31 32 repo := e.Get("repo").(*models.RepoActor) 33 34 var req ComAtprotoSignPlcOperationRequest 35 if err := e.Bind(&req); err != nil { 36 logger.Error("error binding", "error", err) 37 return helpers.ServerError(e, nil) 38 } 39 40 if !strings.HasPrefix(repo.Repo.Did, "did:plc:") { 41 return helpers.InputError(e, nil) 42 } 43 44 if repo.PlcOperationCode == nil || repo.PlcOperationCodeExpiresAt == nil { 45 return helpers.InputError(e, to.StringPtr("InvalidToken")) 46 } 47 48 if *repo.PlcOperationCode != req.Token { 49 return helpers.InvalidTokenError(e) 50 } 51 52 if time.Now().UTC().After(*repo.PlcOperationCodeExpiresAt) { 53 return helpers.ExpiredTokenError(e) 54 } 55 56 ctx := context.WithValue(e.Request().Context(), "skip-cache", true) 57 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did) 58 if err != nil { 59 logger.Error("error fetching doc", "error", err) 60 return helpers.ServerError(e, nil) 61 } 62 63 latest := log[len(log)-1] 64 65 op := plc.Operation{ 66 Type: "plc_operation", 67 VerificationMethods: latest.Operation.VerificationMethods, 68 RotationKeys: latest.Operation.RotationKeys, 69 AlsoKnownAs: latest.Operation.AlsoKnownAs, 70 Services: latest.Operation.Services, 71 Prev: &latest.Cid, 72 } 73 if req.VerificationMethods != nil { 74 op.VerificationMethods = *req.VerificationMethods 75 } 76 if req.RotationKeys != nil { 77 op.RotationKeys = *req.RotationKeys 78 } 79 if req.AlsoKnownAs != nil { 80 op.AlsoKnownAs = *req.AlsoKnownAs 81 } 82 if req.Services != nil { 83 op.Services = *req.Services 84 } 85 86 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey) 87 if err != nil { 88 logger.Error("error parsing signing key", "error", err) 89 return helpers.ServerError(e, nil) 90 } 91 92 if err := s.plcClient.SignOp(k, &op); err != nil { 93 logger.Error("error signing plc operation", "error", err) 94 return helpers.ServerError(e, nil) 95 } 96 97 if err := s.db.Exec(ctx, "UPDATE repos SET plc_operation_code = NULL, plc_operation_code_expires_at = NULL WHERE did = ?", nil, repo.Repo.Did).Error; err != nil { 98 logger.Error("error updating repo", "error", err) 99 return helpers.ServerError(e, nil) 100 } 101 102 return e.JSON(200, ComAtprotoSignPlcOperationResponse{ 103 Operation: op, 104 }) 105}