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 repo := e.Get("repo").(*models.RepoActor)
31
32 var req ComAtprotoSignPlcOperationRequest
33 if err := e.Bind(&req); err != nil {
34 s.logger.Error("error binding", "error", err)
35 return helpers.ServerError(e, nil)
36 }
37
38 if !strings.HasPrefix(repo.Repo.Did, "did:plc:") {
39 return helpers.InputError(e, nil)
40 }
41
42 if repo.PlcOperationCode == nil || repo.PlcOperationCodeExpiresAt == nil {
43 return helpers.InputError(e, to.StringPtr("InvalidToken"))
44 }
45
46 if *repo.PlcOperationCode != req.Token {
47 return helpers.InvalidTokenError(e)
48 }
49
50 if time.Now().UTC().After(*repo.PlcOperationCodeExpiresAt) {
51 return helpers.ExpiredTokenError(e)
52 }
53
54 ctx := context.WithValue(e.Request().Context(), "skip-cache", true)
55 log, err := identity.FetchDidAuditLog(ctx, nil, repo.Repo.Did)
56 if err != nil {
57 s.logger.Error("error fetching doc", "error", err)
58 return helpers.ServerError(e, nil)
59 }
60
61 latest := log[len(log)-1]
62
63 op := plc.Operation{
64 Type: "plc_operation",
65 VerificationMethods: latest.Operation.VerificationMethods,
66 RotationKeys: latest.Operation.RotationKeys,
67 AlsoKnownAs: latest.Operation.AlsoKnownAs,
68 Services: latest.Operation.Services,
69 Prev: &latest.Cid,
70 }
71 if req.VerificationMethods != nil {
72 op.VerificationMethods = *req.VerificationMethods
73 }
74 if req.RotationKeys != nil {
75 op.RotationKeys = *req.RotationKeys
76 }
77 if req.AlsoKnownAs != nil {
78 op.AlsoKnownAs = *req.AlsoKnownAs
79 }
80 if req.Services != nil {
81 op.Services = *req.Services
82 }
83
84 k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey)
85 if err != nil {
86 s.logger.Error("error parsing signing key", "error", err)
87 return helpers.ServerError(e, nil)
88 }
89
90 if err := s.plcClient.SignOp(k, &op); err != nil {
91 s.logger.Error("error signing plc operation", "error", err)
92 return helpers.ServerError(e, nil)
93 }
94
95 if err := s.db.Exec("UPDATE repos SET plc_operation_code = NULL, plc_operation_code_expires_at = NULL WHERE did = ?", nil, repo.Repo.Did).Error; err != nil {
96 s.logger.Error("error updating repo", "error", err)
97 return helpers.ServerError(e, nil)
98 }
99
100 return e.JSON(200, ComAtprotoSignPlcOperationResponse{
101 Operation: op,
102 })
103}