+31
-15
plc/client.go
+31
-15
plc/client.go
···
55
55
}
56
56
57
57
func (c *Client) CreateDID(sigkey *atcrypto.PrivateKeyK256, recovery string, handle string) (string, *Operation, error) {
58
-
pubsigkey, err := sigkey.PublicKey()
58
+
creds, err := c.CreateDidCredentials(sigkey, recovery, handle)
59
59
if err != nil {
60
60
return "", nil, err
61
61
}
62
62
63
-
pubrotkey, err := c.rotationKey.PublicKey()
63
+
op := Operation{
64
+
Type: "plc_operation",
65
+
VerificationMethods: creds.VerificationMethods,
66
+
RotationKeys: creds.RotationKeys,
67
+
AlsoKnownAs: creds.AlsoKnownAs,
68
+
Services: creds.Services,
69
+
Prev: nil,
70
+
}
71
+
72
+
if err := c.SignOp(sigkey, &op); err != nil {
73
+
return "", nil, err
74
+
}
75
+
76
+
did, err := DidFromOp(&op)
64
77
if err != nil {
65
78
return "", nil, err
66
79
}
67
80
81
+
return did, &op, nil
82
+
}
83
+
84
+
func (c *Client) CreateDidCredentials(sigkey *atcrypto.PrivateKeyK256, recovery string, handle string) (*DidCredentials, error) {
85
+
pubsigkey, err := sigkey.PublicKey()
86
+
if err != nil {
87
+
return nil, err
88
+
}
89
+
90
+
pubrotkey, err := c.rotationKey.PublicKey()
91
+
if err != nil {
92
+
return nil, err
93
+
}
94
+
68
95
// todo
69
96
rotationKeys := []string{pubrotkey.DIDKey()}
70
97
if recovery != "" {
···
77
104
}(recovery)
78
105
}
79
106
80
-
op := Operation{
81
-
Type: "plc_operation",
107
+
creds := DidCredentials{
82
108
VerificationMethods: map[string]string{
83
109
"atproto": pubsigkey.DIDKey(),
84
110
},
···
92
118
Endpoint: "https://" + c.pdsHostname,
93
119
},
94
120
},
95
-
Prev: nil,
96
121
}
97
122
98
-
if err := c.SignOp(sigkey, &op); err != nil {
99
-
return "", nil, err
100
-
}
101
-
102
-
did, err := DidFromOp(&op)
103
-
if err != nil {
104
-
return "", nil, err
105
-
}
106
-
107
-
return did, &op, nil
123
+
return &creds, nil
108
124
}
109
125
110
126
func (c *Client) SignOp(sigkey *atcrypto.PrivateKeyK256, op *Operation) error {
+8
plc/types.go
+8
plc/types.go
···
8
8
cbg "github.com/whyrusleeping/cbor-gen"
9
9
)
10
10
11
+
12
+
type DidCredentials struct {
13
+
VerificationMethods map[string]string `json:"verificationMethods"`
14
+
RotationKeys []string `json:"rotationKeys"`
15
+
AlsoKnownAs []string `json:"alsoKnownAs"`
16
+
Services map[string]identity.OperationService `json:"services"`
17
+
}
18
+
11
19
type Operation struct {
12
20
Type string `json:"type"`
13
21
VerificationMethods map[string]string `json:"verificationMethods"`
+24
server/handle_identity_get_recommended_did_credentials.go
+24
server/handle_identity_get_recommended_did_credentials.go
···
1
+
package server
2
+
3
+
import (
4
+
"github.com/bluesky-social/indigo/atproto/atcrypto"
5
+
"github.com/haileyok/cocoon/internal/helpers"
6
+
"github.com/haileyok/cocoon/models"
7
+
"github.com/labstack/echo/v4"
8
+
)
9
+
10
+
func (s *Server) handleGetRecommendedDidCredentials(e echo.Context) error {
11
+
repo := e.Get("repo").(*models.RepoActor)
12
+
k, err := atcrypto.ParsePrivateBytesK256(repo.SigningKey)
13
+
if err != nil {
14
+
s.logger.Error("error parsing key", "error", err)
15
+
return helpers.ServerError(e, nil)
16
+
}
17
+
creds, err := s.plcClient.CreateDidCredentials(k, "", repo.Actor.Handle)
18
+
if err != nil {
19
+
s.logger.Error("error crating did credentials", "error", err)
20
+
return helpers.ServerError(e, nil)
21
+
}
22
+
23
+
return e.JSON(200, creds)
24
+
}
+87
server/handle_identity_submit_plc_operation.go
+87
server/handle_identity_submit_plc_operation.go
···
1
+
package server
2
+
3
+
import (
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
+
19
+
type ComAtprotoSubmitPlcOperationRequest struct {
20
+
Operation plc.Operation `json:"operation"`
21
+
}
22
+
23
+
func (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
+
}
+1
-1
server/handle_import_repo.go
+1
-1
server/handle_import_repo.go
+2
server/server.go
+2
server/server.go
···
459
459
s.echo.GET("/xrpc/com.atproto.server.getSession", s.handleGetSession, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
460
460
s.echo.POST("/xrpc/com.atproto.server.refreshSession", s.handleRefreshSession, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
461
461
s.echo.POST("/xrpc/com.atproto.server.deleteSession", s.handleDeleteSession, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
462
+
s.echo.GET("/xrpc/com.atproto.identity.getRecommendedDidCredentials", s.handleGetRecommendedDidCredentials, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
462
463
s.echo.POST("/xrpc/com.atproto.identity.updateHandle", s.handleIdentityUpdateHandle, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
464
+
s.echo.POST("/xrpc/com.atproto.identity.submitPlcOperation", s.handleSubmitPlcOperation, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
463
465
s.echo.POST("/xrpc/com.atproto.server.confirmEmail", s.handleServerConfirmEmail, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
464
466
s.echo.POST("/xrpc/com.atproto.server.requestEmailConfirmation", s.handleServerRequestEmailConfirmation, s.handleLegacySessionMiddleware, s.handleOauthSessionMiddleware)
465
467
s.echo.POST("/xrpc/com.atproto.server.requestPasswordReset", s.handleServerRequestPasswordReset) // AUTH NOT REQUIRED FOR THIS ONE