+1
-1
identity/types.go
+1
-1
identity/types.go
···
4
4
Context []string `json:"@context"`
5
5
Id string `json:"id"`
6
6
AlsoKnownAs []string `json:"alsoKnownAs"`
7
-
VerificationMethods []DidDocVerificationMethod `json:"verificationMethods"`
7
+
VerificationMethods []DidDocVerificationMethod `json:"verificationMethod"`
8
8
Service []DidDocService `json:"service"`
9
9
}
10
10
+6
internal/db/db.go
+6
internal/db/db.go
···
25
25
return db.cli.Clauses(clauses...).Create(value)
26
26
}
27
27
28
+
func (db *DB) Save(value any, clauses []clause.Expression) *gorm.DB {
29
+
db.mu.Lock()
30
+
defer db.mu.Unlock()
31
+
return db.cli.Clauses(clauses...).Save(value)
32
+
}
33
+
28
34
func (db *DB) Exec(sql string, clauses []clause.Expression, values ...any) *gorm.DB {
29
35
db.mu.Lock()
30
36
defer db.mu.Unlock()
+16
internal/helpers/helpers.go
+16
internal/helpers/helpers.go
···
32
32
return genericError(e, 400, msg)
33
33
}
34
34
35
+
func UnauthorizedError(e echo.Context, suffix *string) error {
36
+
msg := "Unauthorized"
37
+
if suffix != nil {
38
+
msg += ". " + *suffix
39
+
}
40
+
return genericError(e, 401, msg)
41
+
}
42
+
43
+
func ForbiddenError(e echo.Context, suffix *string) error {
44
+
msg := "Forbidden"
45
+
if suffix != nil {
46
+
msg += ". " + *suffix
47
+
}
48
+
return genericError(e, 403, msg)
49
+
}
50
+
35
51
func InvalidTokenError(e echo.Context) error {
36
52
return InputError(e, to.StringPtr("InvalidToken"))
37
53
}
+45
-35
server/handle_server_create_account.go
+45
-35
server/handle_server_create_account.go
···
10
10
"github.com/Azure/go-autorest/autorest/to"
11
11
"github.com/bluesky-social/indigo/api/atproto"
12
12
"github.com/bluesky-social/indigo/atproto/atcrypto"
13
-
"github.com/bluesky-social/indigo/atproto/syntax"
14
13
"github.com/bluesky-social/indigo/events"
15
14
"github.com/bluesky-social/indigo/repo"
16
15
"github.com/bluesky-social/indigo/util"
···
39
38
func (s *Server) handleCreateAccount(e echo.Context) error {
40
39
var request ComAtprotoServerCreateAccountRequest
41
40
42
-
var signupDid string
43
-
customDidHeader := e.Request().Header.Get("authorization")
44
-
if customDidHeader != "" {
45
-
pts := strings.Split(customDidHeader, " ")
46
-
if len(pts) != 2 {
47
-
return helpers.InputError(e, to.StringPtr("InvalidDid"))
48
-
}
49
-
50
-
_, err := syntax.ParseDID(pts[1])
51
-
if err != nil {
52
-
return helpers.InputError(e, to.StringPtr("InvalidDid"))
53
-
}
54
-
55
-
signupDid = pts[1]
56
-
}
57
-
58
41
if err := e.Bind(&request); err != nil {
59
42
s.logger.Error("error receiving request", "endpoint", "com.atproto.server.createAccount", "error", err)
60
43
return helpers.ServerError(e, nil)
···
85
68
}
86
69
}
87
70
}
71
+
72
+
var signupDid string
73
+
if request.Did != nil {
74
+
signupDid = *request.Did;
75
+
76
+
token := strings.TrimSpace(strings.Replace(e.Request().Header.Get("authorization"), "Bearer ", "", 1))
77
+
if token == "" {
78
+
return helpers.UnauthorizedError(e, to.StringPtr("must authenticate to use an existing did"))
79
+
}
80
+
authDid, err := s.validateServiceAuth(e.Request().Context(), token, "com.atproto.server.createAccount")
81
+
82
+
if err != nil {
83
+
s.logger.Warn("error validating authorization token", "endpoint", "com.atproto.server.createAccount", "error", err)
84
+
return helpers.UnauthorizedError(e, to.StringPtr("invalid authorization token"))
85
+
}
86
+
87
+
if authDid != signupDid {
88
+
return helpers.ForbiddenError(e, to.StringPtr("auth did did not match signup did"))
89
+
}
90
+
}
88
91
89
92
// see if the handle is already taken
90
-
_, err := s.getActorByHandle(request.Handle)
93
+
actor, err := s.getActorByHandle(request.Handle)
91
94
if err != nil && err != gorm.ErrRecordNotFound {
92
95
s.logger.Error("error looking up handle in db", "endpoint", "com.atproto.server.createAccount", "error", err)
93
96
return helpers.ServerError(e, nil)
94
97
}
95
-
if err == nil {
98
+
if err == nil && actor.Did != signupDid {
96
99
return helpers.InputError(e, to.StringPtr("HandleNotAvailable"))
97
100
}
98
101
99
-
if did, err := s.passport.ResolveHandle(e.Request().Context(), request.Handle); err == nil && did != "" {
102
+
if did, err := s.passport.ResolveHandle(e.Request().Context(), request.Handle); err == nil && did != signupDid {
100
103
return helpers.InputError(e, to.StringPtr("HandleNotAvailable"))
101
104
}
102
105
···
114
117
}
115
118
116
119
// see if the email is already taken
117
-
_, err = s.getRepoByEmail(request.Email)
120
+
existingRepo, err := s.getRepoByEmail(request.Email)
118
121
if err != nil && err != gorm.ErrRecordNotFound {
119
122
s.logger.Error("error looking up email in db", "endpoint", "com.atproto.server.createAccount", "error", err)
120
123
return helpers.ServerError(e, nil)
121
124
}
122
-
if err == nil {
125
+
if err == nil && existingRepo.Did != signupDid {
123
126
return helpers.InputError(e, to.StringPtr("EmailNotAvailable"))
124
127
}
125
128
···
160
163
SigningKey: k.Bytes(),
161
164
}
162
165
163
-
actor := models.Actor{
164
-
Did: signupDid,
165
-
Handle: request.Handle,
166
-
}
167
-
168
-
if err := s.db.Create(&urepo, nil).Error; err != nil {
169
-
s.logger.Error("error inserting new repo", "error", err)
170
-
return helpers.ServerError(e, nil)
171
-
}
166
+
if actor == nil {
167
+
actor = &models.Actor{
168
+
Did: signupDid,
169
+
Handle: request.Handle,
170
+
}
172
171
173
-
if err := s.db.Create(&actor, nil).Error; err != nil {
174
-
s.logger.Error("error inserting new actor", "error", err)
175
-
return helpers.ServerError(e, nil)
172
+
if err := s.db.Create(&urepo, nil).Error; err != nil {
173
+
s.logger.Error("error inserting new repo", "error", err)
174
+
return helpers.ServerError(e, nil)
175
+
}
176
+
177
+
if err := s.db.Create(&actor, nil).Error; err != nil {
178
+
s.logger.Error("error inserting new actor", "error", err)
179
+
return helpers.ServerError(e, nil)
180
+
}
181
+
} else {
182
+
if err := s.db.Save(&actor, nil).Error; err != nil {
183
+
s.logger.Error("error inserting new actor", "error", err)
184
+
return helpers.ServerError(e, nil)
185
+
}
176
186
}
177
187
178
-
if customDidHeader == "" {
188
+
if request.Did == nil || *request.Did == "" {
179
189
bs := s.getBlockstore(signupDid)
180
190
r := repo.NewRepo(context.TODO(), signupDid, bs)
181
191
-1
server/server.go
-1
server/server.go
···
423
423
// public
424
424
s.echo.GET("/xrpc/com.atproto.identity.resolveHandle", s.handleResolveHandle)
425
425
s.echo.POST("/xrpc/com.atproto.server.createAccount", s.handleCreateAccount)
426
-
s.echo.POST("/xrpc/com.atproto.server.createAccount", s.handleCreateAccount)
427
426
s.echo.POST("/xrpc/com.atproto.server.createSession", s.handleCreateSession)
428
427
s.echo.GET("/xrpc/com.atproto.server.describeServer", s.handleDescribeServer)
429
428
+91
server/service_auth.go
+91
server/service_auth.go
···
1
+
package server
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
"strings"
7
+
8
+
"github.com/bluesky-social/indigo/atproto/atcrypto"
9
+
"github.com/bluesky-social/indigo/atproto/identity"
10
+
atproto_identity "github.com/bluesky-social/indigo/atproto/identity"
11
+
"github.com/bluesky-social/indigo/atproto/syntax"
12
+
"github.com/golang-jwt/jwt/v4"
13
+
)
14
+
15
+
type ES256KSigningMethod struct {
16
+
alg string
17
+
}
18
+
19
+
func (m *ES256KSigningMethod) Alg() string {
20
+
return m.alg
21
+
}
22
+
23
+
func (m *ES256KSigningMethod) Verify(signingString string, signature string, key interface{}) error {
24
+
signatureBytes, err := jwt.DecodeSegment(signature)
25
+
if err != nil {
26
+
return err
27
+
}
28
+
return key.(atcrypto.PublicKey).HashAndVerifyLenient([]byte(signingString), signatureBytes)
29
+
}
30
+
31
+
func (m *ES256KSigningMethod) Sign(signingString string, key interface{}) (string, error) {
32
+
return "", fmt.Errorf("unimplemented")
33
+
}
34
+
35
+
func init() {
36
+
ES256K := ES256KSigningMethod{alg: "ES256K"}
37
+
jwt.RegisterSigningMethod(ES256K.Alg(), func() jwt.SigningMethod {
38
+
return &ES256K
39
+
})
40
+
}
41
+
42
+
func (s *Server) validateServiceAuth(ctx context.Context, rawToken string, nsid string) (string, error) {
43
+
token := strings.TrimSpace(rawToken)
44
+
45
+
parsedToken, err := jwt.ParseWithClaims(token, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
46
+
did := syntax.DID(token.Claims.(jwt.MapClaims)["iss"].(string))
47
+
didDoc, err := s.passport.FetchDoc(ctx, did.String());
48
+
if err != nil {
49
+
return nil, fmt.Errorf("unable to resolve did %s: %s", did, err)
50
+
}
51
+
52
+
verificationMethods := make([]atproto_identity.DocVerificationMethod, len(didDoc.VerificationMethods))
53
+
for i, verificationMethod := range didDoc.VerificationMethods {
54
+
verificationMethods[i] = atproto_identity.DocVerificationMethod{
55
+
ID: verificationMethod.Id,
56
+
Type: verificationMethod.Type,
57
+
PublicKeyMultibase: verificationMethod.PublicKeyMultibase,
58
+
Controller: verificationMethod.Controller,
59
+
}
60
+
}
61
+
services := make([]atproto_identity.DocService, len(didDoc.Service))
62
+
for i, service := range didDoc.Service {
63
+
services[i] = atproto_identity.DocService{
64
+
ID: service.Id,
65
+
Type: service.Type,
66
+
ServiceEndpoint: service.ServiceEndpoint,
67
+
}
68
+
}
69
+
parsedIdentity := atproto_identity.ParseIdentity(&identity.DIDDocument{
70
+
DID: did,
71
+
AlsoKnownAs: didDoc.AlsoKnownAs,
72
+
VerificationMethod: verificationMethods,
73
+
Service: services,
74
+
})
75
+
76
+
key, err := parsedIdentity.PublicKey()
77
+
if err != nil {
78
+
return nil, fmt.Errorf("signing key not found for did %s: %s", did, err)
79
+
}
80
+
return key, nil
81
+
})
82
+
if err != nil {
83
+
return "", fmt.Errorf("invalid token: %s", err)
84
+
}
85
+
86
+
claims := parsedToken.Claims.(jwt.MapClaims)
87
+
if claims["lxm"] != nsid {
88
+
return "", fmt.Errorf("bad jwt lexicon method (\"lxm\"). must match: %s", nsid)
89
+
}
90
+
return claims["iss"].(string), nil
91
+
}