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}