1package server
2
3import (
4 "context"
5 "slices"
6 "strings"
7 "time"
8
9 "github.com/bluesky-social/indigo/api/atproto"
10 "github.com/bluesky-social/indigo/atproto/atcrypto"
11 "github.com/bluesky-social/indigo/events"
12 "github.com/bluesky-social/indigo/util"
13 "github.com/haileyok/cocoon/internal/helpers"
14 "github.com/haileyok/cocoon/models"
15 "github.com/haileyok/cocoon/plc"
16 "github.com/labstack/echo/v4"
17)
18
19type ComAtprotoSubmitPlcOperationRequest struct {
20 Operation plc.Operation `json:"operation"`
21}
22
23func (s *Server) handleSubmitPlcOperation(e echo.Context) error {
24 repo := e.Get("repo").(*models.RepoActor)
25
26 var req ComAtprotoSubmitPlcOperationRequest
27 if err := e.Bind(&req); err != nil {
28 s.logger.Error("error binding", "error", err)
29 return helpers.ServerError(e, nil)
30 }
31
32 if err := e.Validate(req); err != nil {
33 return helpers.InputError(e, nil)
34 }
35 if !strings.HasPrefix(repo.Repo.Did, "did:plc:") {
36 return helpers.InputError(e, nil)
37 }
38
39 op := req.Operation
40
41 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey)
42 if err != nil {
43 s.logger.Error("error parsing key", "error", err)
44 return helpers.ServerError(e, nil)
45 }
46 required, err := s.plcClient.CreateDidCredentials(k, "", repo.Actor.Handle)
47 if err != nil {
48 s.logger.Error("error crating did credentials", "error", err)
49 return helpers.ServerError(e, nil)
50 }
51
52 for _, expectedKey := range required.RotationKeys {
53 if !slices.Contains(op.RotationKeys, expectedKey) {
54 return helpers.InputError(e, nil)
55 }
56 }
57 if op.Services["atproto_pds"].Type != "AtprotoPersonalDataServer" {
58 return helpers.InputError(e, nil)
59 }
60 if op.Services["atproto_pds"].Endpoint != required.Services["atproto_pds"].Endpoint {
61 return helpers.InputError(e, nil)
62 }
63 if op.VerificationMethods["atproto"] != required.VerificationMethods["atproto"] {
64 return helpers.InputError(e, nil)
65 }
66 if op.AlsoKnownAs[0] != required.AlsoKnownAs[0] {
67 return helpers.InputError(e, nil)
68 }
69
70 if err := s.plcClient.SendOperation(e.Request().Context(), repo.Repo.Did, &op); err != nil {
71 return err
72 }
73
74 if err := s.passport.BustDoc(context.TODO(), repo.Repo.Did); err != nil {
75 s.logger.Warn("error busting did doc", "error", err)
76 }
77
78 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
79 RepoIdentity: &atproto.SyncSubscribeRepos_Identity{
80 Did: repo.Repo.Did,
81 Seq: time.Now().UnixMicro(), // TODO: no
82 Time: time.Now().Format(util.ISO8601),
83 },
84 })
85
86 return nil
87}