Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 41 additions & 32 deletions server/handle_server_create_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/bluesky-social/indigo/util"
"github.com/haileyok/cocoon/internal/helpers"
"github.com/haileyok/cocoon/models"
"github.com/ipfs/go-cid"
"github.com/labstack/echo/v4"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
Expand Down Expand Up @@ -219,40 +220,23 @@ func (s *Server) handleCreateAccount(e echo.Context) error {
}
}

if request.Did == nil || *request.Did == "" {
bs := s.getBlockstore(signupDid)

clk := syntax.NewTIDClock(0)
r := &atp.Repo{
DID: syntax.DID(signupDid),
Clock: clk,
MST: mst.NewEmptyTree(),
RecordStore: bs,
}

root, rev, err := commitRepo(context.TODO(), bs, r, urepo.SigningKey)
if err != nil {
logger.Error("error committing", "error", err)
return helpers.ServerError(e, nil)
}
root, rev, err := s.initializeGenesisRepo(context.TODO(), urepo.Did, urepo.SigningKey)
if err != nil {
logger.Error("error initializing genesis repo", "error", err)
return helpers.ServerError(e, nil)
}

if err := s.UpdateRepo(context.TODO(), urepo.Did, root, rev); err != nil {
logger.Error("error updating repo after commit", "error", err)
return helpers.ServerError(e, nil)
}
s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
RepoIdentity: &atproto.SyncSubscribeRepos_Identity{
Did: urepo.Did,
Handle: to.StringPtr(request.Handle),
Seq: time.Now().UnixMicro(), // TODO: no
Time: time.Now().Format(util.ISO8601),
},
})

s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{
RepoIdentity: &atproto.SyncSubscribeRepos_Identity{
Did: urepo.Did,
Handle: to.StringPtr(request.Handle),
Seq: time.Now().UnixMicro(), // TODO: no
Time: time.Now().Format(util.ISO8601),
},
})

if err := s.emitRepoSync(context.TODO(), urepo.Did, rev, root); err != nil {
logger.Error("error emitting repo sync event", "error", err)
}
if err := s.emitRepoSync(context.TODO(), urepo.Did, rev, root); err != nil {
logger.Error("error emitting repo sync event", "error", err)
}

sess, err := s.createSession(ctx, &urepo)
Expand All @@ -277,3 +261,28 @@ func (s *Server) handleCreateAccount(e echo.Context) error {
Did: signupDid,
})
}

// initializeGenesisRepo commits an empty MST for did and records it as the
// repo's current head.
func (s *Server) initializeGenesisRepo(ctx context.Context, did string, signingKey []byte) (cid.Cid, string, error) {
bs := s.getBlockstore(did)

clk := syntax.NewTIDClock(0)
r := &atp.Repo{
DID: syntax.DID(did),
Clock: clk,
MST: mst.NewEmptyTree(),
RecordStore: bs,
}

root, rev, err := commitRepo(ctx, bs, r, signingKey)
if err != nil {
return cid.Undef, "", fmt.Errorf("commit genesis repo: %w", err)
}

if err := s.UpdateRepo(ctx, did, root, rev); err != nil {
return cid.Undef, "", fmt.Errorf("update repo after genesis commit: %w", err)
}

return root, rev, nil
}
95 changes: 95 additions & 0 deletions server/handle_server_create_account_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package server

import (
"context"
"encoding/json"
"net/http"
"testing"
"time"

"github.com/bluesky-social/indigo/atproto/atcrypto"
"github.com/bluesky-social/indigo/events"
"github.com/haileyok/cocoon/identity"
)

// TestCreateAccountInitializesRepoForExistingDID asserts an account created
// via the existing-DID flow gets a genesis repo commit and #identity/#sync
// events, same as the flow where cocoon mints the DID itself.
func TestCreateAccountInitializesRepoForExistingDID(t *testing.T) {
s := newTestServer(t)

persister, err := NewDbPersister(s.db.Client(), time.Hour)
if err != nil {
t.Fatalf("new persister: %v", err)
}
s.evtman = events.NewEventManager(persister)

k, err := atcrypto.GeneratePrivateKeyK256()
if err != nil {
t.Fatalf("generate key: %v", err)
}
pub, err := k.PublicKey()
if err != nil {
t.Fatalf("derive public key: %v", err)
}

const did = "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa"
const handle = "alice.pds.test"

cache := identity.NewMemCache(10)
if err := cache.PutDoc(did, &identity.DidDoc{
Id: did,
VerificationMethods: []identity.DidDocVerificationMethod{
{
Id: did + "#atproto",
Type: "Multikey",
Controller: did,
PublicKeyMultibase: pub.Multibase(),
},
},
}); err != nil {
t.Fatalf("seed passport cache: %v", err)
}
s.passport = identity.NewPassport(nil, cache)

tok := mintServiceAuthToken(t, k.Bytes(), did, s.config.Did, "com.atproto.server.createAccount", time.Now().Add(time.Minute))

body, err := json.Marshal(map[string]string{
"handle": handle,
"email": "alice@test.invalid",
"password": "correct-horse-battery-staple",
"did": did,
})
if err != nil {
t.Fatalf("marshal request: %v", err)
}

c, rec := newRequestContext(http.MethodPost, "/xrpc/com.atproto.server.createAccount", string(body), map[string]string{
"authorization": "Bearer " + tok,
})

if err := s.handleCreateAccount(c); err != nil {
t.Fatalf("handleCreateAccount: %v", err)
}
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}

urepo, err := s.getRepoActorByDid(context.Background(), did)
if err != nil {
t.Fatalf("getRepoActorByDid: %v", err)
}
if urepo.Repo.Rev == "" {
t.Fatal("repo has no rev after account creation via the existing-DID flow")
}
if len(urepo.Repo.Root) == 0 {
t.Fatal("repo has no root after account creation via the existing-DID flow")
}

types := eventTypesFor(t, s, did)
for _, want := range []string{"identity", "sync"} {
if !contains(types, want) {
t.Fatalf("missing %q event after account creation; got %v", want, types)
}
}
}
Loading