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/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/identity"
14 "github.com/haileyok/cocoon/internal/helpers"
15 "github.com/haileyok/cocoon/models"
16 "github.com/haileyok/cocoon/plc"
17 "github.com/labstack/echo/v4"
18)
19
20type ComAtprotoIdentityUpdateHandleRequest struct {
21 Handle string `json:"handle" validate:"atproto-handle"`
22}
23
24func (s *Server) handleIdentityUpdateHandle(e echo.Context) error {
25 logger := s.logger.With("name", "handleIdentityUpdateHandle")
26
27 repo := e.Get("repo").(*models.RepoActor)
28
29 var req ComAtprotoIdentityUpdateHandleRequest
30 if err := e.Bind(&req); err != nil {
31 logger.Error("error binding", "error", err)
32 return helpers.ServerError(e, nil)
33 }
34
35 req.Handle = strings.ToLower(req.Handle)
36
37 if err := e.Validate(req); err != nil {
38 return helpers.InputError(e, nil)
39 }
40
41 ctx := context.WithValue(e.Request().Context(), "skip-cache", true)
42
43 if strings.HasPrefix(repo.Repo.Did, "did:plc:") {
44 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did)
45 if err != nil {
46 logger.Error("error fetching doc", "error", err)
47 return helpers.ServerError(e, nil)
48 }
49
50 latest := log[len(log)-1]
51
52 var newAka []string
53 for _, aka := range latest.Operation.AlsoKnownAs {
54 if aka == "at://"+repo.Handle {
55 continue
56 }
57 newAka = append(newAka, aka)
58 }
59
60 newAka = append(newAka, "at://"+req.Handle)
61
62 op := plc.Operation{
63 Type: "plc_operation",
64 VerificationMethods: latest.Operation.VerificationMethods,
65 RotationKeys: latest.Operation.RotationKeys,
66 AlsoKnownAs: newAka,
67 Services: latest.Operation.Services,
68 Prev: &latest.Cid,
69 }
70
71 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey)
72 if err != nil {
73 logger.Error("error parsing signing key", "error", err)
74 return helpers.ServerError(e, nil)
75 }
76
77 if err := s.plcClient.SignOp(k, &op); err != nil {
78 return err
79 }
80
81 if err := s.plcClient.SendOperation(e.Request().Context(), repo.Repo.Did, &op); err != nil {
82 return err
83 }
84 }
85
86 if err := s.passport.BustDoc(context.TODO(), repo.Repo.Did); err != nil {
87 logger.Warn("error busting did doc", "error", err)
88 }
89
90 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
91 RepoIdentity: &atproto.SyncSubscribeRepos_Identity{
92 Did: repo.Repo.Did,
93 Handle: to.StringPtr(req.Handle),
94 Seq: time.Now().UnixMicro(), // TODO: no
95 Time: time.Now().Format(util.ISO8601),
96 },
97 })
98
99 if err := s.db.Exec(ctx, "UPDATE actors SET handle = ? WHERE did = ?", nil, req.Handle, repo.Repo.Did).Error; err != nil {
100 logger.Error("error updating handle in db", "error", err)
101 return helpers.ServerError(e, nil)
102 }
103
104 return nil
105}