diff --git a/appview/db/db.go b/appview/db/db.go index f5f6f4c7..b0d4e0eb 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -7,7 +7,7 @@ import ( ) type DB struct { - db *sql.DB + Db *sql.DB } func Make(dbPath string) (*DB, error) { @@ -43,5 +43,5 @@ func Make(dbPath string) (*DB, error) { if err != nil { return nil, err } - return &DB{db: db}, nil + return &DB{Db: db}, nil } diff --git a/appview/db/pubkeys.go b/appview/db/pubkeys.go index f2fb4db1..632c0fca 100644 --- a/appview/db/pubkeys.go +++ b/appview/db/pubkeys.go @@ -1,19 +1,26 @@ package db import ( + "database/sql" "encoding/json" "time" ) func (d *DB) AddPublicKey(did, name, key string) error { query := `insert into public_keys (did, name, key) values (?, ?, ?)` - _, err := d.db.Exec(query, did, name, key) + _, err := d.Db.Exec(query, did, name, key) + return err +} + +func (d *DB) AddPublicKeyTx(tx *sql.Tx, did, name, key string) error { + query := `insert into public_keys (did, name, key) values (?, ?, ?)` + _, err := tx.Exec(query, did, name, key) return err } func (d *DB) RemovePublicKey(did string) error { query := `delete from public_keys where did = ?` - _, err := d.db.Exec(query, did) + _, err := d.Db.Exec(query, did) return err } @@ -38,7 +45,7 @@ func (p PublicKey) MarshalJSON() ([]byte, error) { func (d *DB) GetAllPublicKeys() ([]PublicKey, error) { var keys []PublicKey - rows, err := d.db.Query(`select key, name, did, created from public_keys`) + rows, err := d.Db.Query(`select key, name, did, created from public_keys`) if err != nil { return nil, err } @@ -64,7 +71,7 @@ func (d *DB) GetAllPublicKeys() ([]PublicKey, error) { func (d *DB) GetPublicKeys(did string) ([]PublicKey, error) { var keys []PublicKey - rows, err := d.db.Query(`select did, key, name, created from public_keys where did = ?`, did) + rows, err := d.Db.Query(`select did, key, name, created from public_keys where did = ?`, did) if err != nil { return nil, err } diff --git a/appview/db/registration.go b/appview/db/registration.go index 2899c9be..40679c0d 100644 --- a/appview/db/registration.go +++ b/appview/db/registration.go @@ -35,7 +35,7 @@ const ( func (d *DB) RegistrationsByDid(did string) ([]Registration, error) { var registrations []Registration - rows, err := d.db.Query(` + rows, err := d.Db.Query(` select domain, did, created, registered from registrations where did = ? `, did) @@ -75,7 +75,38 @@ func (d *DB) RegistrationByDomain(domain string) (*Registration, error) { var registeredAt *int64 var registration Registration - err := d.db.QueryRow(` + err := d.Db.QueryRow(` + select domain, did, created, registered from registrations + where domain = ? + `, domain).Scan(®istration.Domain, ®istration.ByDid, &createdAt, ®isteredAt) + + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } else { + return nil, err + } + } + + createdAtTime := time.Unix(*createdAt, 0) + var registeredAtTime *time.Time + if registeredAt != nil { + x := time.Unix(*registeredAt, 0) + registeredAtTime = &x + } + + registration.Created = &createdAtTime + registration.Registered = registeredAtTime + + return ®istration, nil +} + +func (d *DB) RegistrationByDomainTx(tx *sql.Tx, domain string) (*Registration, error) { + var createdAt *int64 + var registeredAt *int64 + var registration Registration + + err := tx.QueryRow(` select domain, did, created, registered from registrations where domain = ? `, domain).Scan(®istration.Domain, ®istration.ByDid, &createdAt, ®isteredAt) @@ -122,7 +153,7 @@ func (d *DB) GenerateRegistrationKey(domain, did string) (string, error) { secret := uuid.New().String() - _, err = d.db.Exec(` + _, err = d.Db.Exec(` insert into registrations (domain, did, secret) values (?, ?, ?) on conflict(domain) do update set did = excluded.did, secret = excluded.secret @@ -136,7 +167,19 @@ func (d *DB) GenerateRegistrationKey(domain, did string) (string, error) { } func (d *DB) GetRegistrationKey(domain string) (string, error) { - res := d.db.QueryRow(`select secret from registrations where domain = ?`, domain) + res := d.Db.QueryRow(`select secret from registrations where domain = ?`, domain) + + var secret string + err := res.Scan(&secret) + if err != nil || secret == "" { + return "", err + } + + return secret, nil +} + +func (d *DB) GetRegistrationKeyTx(tx *sql.Tx, domain string) (string, error) { + res := tx.QueryRow(`select secret from registrations where domain = ?`, domain) var secret string err := res.Scan(&secret) @@ -148,7 +191,17 @@ func (d *DB) GetRegistrationKey(domain string) (string, error) { } func (d *DB) Register(domain string) error { - _, err := d.db.Exec(` + _, err := d.Db.Exec(` + update registrations + set registered = strftime('%s', 'now') + where domain = ?; + `, domain) + + return err +} + +func (d *DB) RegisterTx(tx *sql.Tx, domain string) error { + _, err := tx.Exec(` update registrations set registered = strftime('%s', 'now') where domain = ?; diff --git a/appview/db/repos.go b/appview/db/repos.go index 22851b22..219e5aba 100644 --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -1,5 +1,7 @@ package db +import "database/sql" + type Repo struct { Did string Name string @@ -10,7 +12,7 @@ type Repo struct { func (d *DB) GetAllReposByDid(did string) ([]Repo, error) { var repos []Repo - rows, err := d.db.Query(`select did, name, knot, created from repos where did = ?`, did) + rows, err := d.Db.Query(`select did, name, knot, created from repos where did = ?`, did) if err != nil { return nil, err } @@ -36,7 +38,7 @@ func (d *DB) GetAllReposByDid(did string) ([]Repo, error) { func (d *DB) GetRepo(did, name string) (*Repo, error) { var repo Repo - row := d.db.QueryRow(`select did, name, knot, created from repos where did = ? and name = ?`, did, name) + row := d.Db.QueryRow(`select did, name, knot, created from repos where did = ? and name = ?`, did, name) var createdAt *int64 if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt); err != nil { return nil, err @@ -47,11 +49,16 @@ func (d *DB) GetRepo(did, name string) (*Repo, error) { } func (d *DB) AddRepo(repo *Repo) error { - _, err := d.db.Exec(`insert into repos (did, name, knot) values (?, ?, ?)`, repo.Did, repo.Name, repo.Knot) + _, err := d.Db.Exec(`insert into repos (did, name, knot) values (?, ?, ?)`, repo.Did, repo.Name, repo.Knot) + return err +} + +func (d *DB) AddRepoTx(tx *sql.Tx, repo *Repo) error { + _, err := tx.Exec(`insert into repos (did, name, knot) values (?, ?, ?)`, repo.Did, repo.Name, repo.Knot) return err } func (d *DB) RemoveRepo(did, name, knot string) error { - _, err := d.db.Exec(`delete from repos where did = ? and name = ? and knot = ?`, did, name, knot) + _, err := d.Db.Exec(`delete from repos where did = ? and name = ? and knot = ?`, did, name, knot) return err } diff --git a/appview/state/state.go b/appview/state/state.go index d7e8e1a9..b25e93e4 100644 --- a/appview/state/state.go +++ b/appview/state/state.go @@ -200,7 +200,16 @@ func (s *State) SettingsKeys(w http.ResponseWriter, r *http.Request) { return } - if err := s.db.AddPublicKey(did, name, key); err != nil { + // Start transaction + tx, err := s.db.Db.Begin() + if err != nil { + log.Printf("failed to start transaction: %s", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + defer tx.Rollback() // Will rollback if not committed + + if err := s.db.AddPublicKeyTx(tx, did, name, key); err != nil { log.Printf("adding public key: %s", err) return } @@ -223,6 +232,13 @@ func (s *State) SettingsKeys(w http.ResponseWriter, r *http.Request) { return } + // If everything succeeded, commit the transaction + if err := tx.Commit(); err != nil { + log.Printf("failed to commit transaction: %s", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + log.Println("created atproto record: ", resp.Uri) return @@ -240,7 +256,16 @@ func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { } log.Println("checking ", domain) - secret, err := s.db.GetRegistrationKey(domain) + // Start transaction + tx, err := s.db.Db.Begin() + if err != nil { + log.Printf("failed to start transaction: %s", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + defer tx.Rollback() // Will rollback if not committed + + secret, err := s.db.GetRegistrationKeyTx(tx, domain) if err != nil { log.Printf("no key found for domain %s: %s\n", domain, err) return @@ -285,23 +310,23 @@ func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { return } - // mark as registered - err = s.db.Register(domain) + // mark as registered within transaction + err = s.db.RegisterTx(tx, domain) if err != nil { log.Println("failed to register domain", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - // set permissions for this did as owner - reg, err := s.db.RegistrationByDomain(domain) + // set permissions for this did as owner within transaction + reg, err := s.db.RegistrationByDomainTx(tx, domain) if err != nil { log.Println("failed to register domain", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - // add basic acls for this domain + // add basic acls for this domain within transaction err = s.enforcer.AddDomain(domain) if err != nil { log.Println("failed to setup owner of domain", err) @@ -309,7 +334,7 @@ func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { return } - // add this did as owner of this domain + // add this did as owner of this domain within transaction err = s.enforcer.AddOwner(domain, reg.ByDid) if err != nil { log.Println("failed to setup owner of domain", err) @@ -317,6 +342,13 @@ func (s *State) InitKnotServer(w http.ResponseWriter, r *http.Request) { return } + // Commit transaction + if err := tx.Commit(); err != nil { + log.Printf("failed to commit transaction: %s", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + w.Write([]byte("check success")) } @@ -411,34 +443,23 @@ func (s *State) AddMember(w http.ResponseWriter, r *http.Request) { } log.Printf("adding %s to %s\n", memberIdent.Handle.String(), domain) - // announce this relation into the firehose, store into owners' pds - client, _ := s.auth.AuthorizedClient(r) - currentUser := s.auth.GetUser(r) - addedAt := time.Now().Format(time.RFC3339) - resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ - Collection: tangled.KnotMemberNSID, - Repo: currentUser.Did, - Rkey: s.TID(), - Record: &lexutil.LexiconTypeDecoder{ - Val: &tangled.KnotMember{ - Member: memberIdent.DID.String(), - Domain: domain, - AddedAt: &addedAt, - }}, - }) - // invalid record + // Start transaction + tx, err := s.db.Db.Begin() if err != nil { - log.Printf("failed to create record: %s", err) + log.Printf("failed to start transaction: %s", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) return } - log.Println("created atproto record: ", resp.Uri) + defer tx.Rollback() // Will rollback if not committed - secret, err := s.db.GetRegistrationKey(domain) + // Get registration key within transaction + secret, err := s.db.GetRegistrationKeyTx(tx, domain) if err != nil { log.Printf("no key found for domain %s: %s\n", domain, err) return } + // Make the external call to the knot server ksClient, err := NewSignedClient(domain, secret) if err != nil { log.Println("failed to create client to ", domain) @@ -447,7 +468,8 @@ func (s *State) AddMember(w http.ResponseWriter, r *http.Request) { ksResp, err := ksClient.AddMember(memberIdent.DID.String(), []string{}) if err != nil { - log.Printf("failet to make request to %s: %s", domain, err) + log.Printf("failed to make request to %s: %s", domain, err) + return } if ksResp.StatusCode != http.StatusNoContent { @@ -455,12 +477,41 @@ func (s *State) AddMember(w http.ResponseWriter, r *http.Request) { return } + // Create ATProto record within transaction + client, _ := s.auth.AuthorizedClient(r) + currentUser := s.auth.GetUser(r) + addedAt := time.Now().Format(time.RFC3339) + resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ + Collection: tangled.KnotMemberNSID, + Repo: currentUser.Did, + Rkey: s.TID(), + Record: &lexutil.LexiconTypeDecoder{ + Val: &tangled.KnotMember{ + Member: memberIdent.DID.String(), + Domain: domain, + AddedAt: &addedAt, + }}, + }) + if err != nil { + log.Printf("failed to create record: %s", err) + return + } + + // Update RBAC within transaction err = s.enforcer.AddMember(domain, memberIdent.DID.String()) if err != nil { w.Write([]byte(fmt.Sprint("failed to add member: ", err))) return } + // If everything succeeded, commit the transaction + if err := tx.Commit(); err != nil { + log.Printf("failed to commit transaction: %s", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + log.Println("created atproto record: ", resp.Uri) w.Write([]byte(fmt.Sprint("added member: ", memberIdent.Handle.String()))) } @@ -494,7 +545,16 @@ func (s *State) AddRepo(w http.ResponseWriter, r *http.Request) { return } - secret, err := s.db.GetRegistrationKey(domain) + // Start transaction + tx, err := s.db.Db.Begin() + if err != nil { + log.Printf("failed to start transaction: %s", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + defer tx.Rollback() // Will rollback if not committed + + secret, err := s.db.GetRegistrationKeyTx(tx, domain) if err != nil { log.Printf("no key found for domain %s: %s\n", domain, err) return @@ -503,6 +563,7 @@ func (s *State) AddRepo(w http.ResponseWriter, r *http.Request) { client, err := NewSignedClient(domain, secret) if err != nil { log.Println("failed to create client to ", domain) + return } resp, err := client.NewRepo(user.Did, repoName) @@ -515,25 +576,32 @@ func (s *State) AddRepo(w http.ResponseWriter, r *http.Request) { return } - // add to local db + // add to local db within transaction repo := &db.Repo{ Did: user.Did, Name: repoName, Knot: domain, } - err = s.db.AddRepo(repo) + err = s.db.AddRepoTx(tx, repo) if err != nil { log.Println("failed to add repo to db", err) return } - // acls + // acls within transaction err = s.enforcer.AddRepo(user.Did, domain, filepath.Join(user.Did, repoName)) if err != nil { log.Println("failed to set up acls", err) return } + // Commit transaction + if err := tx.Commit(); err != nil { + log.Printf("failed to commit transaction: %s", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + w.Write([]byte("created!")) } }