An atproto PDS written in Go
at main 4.5 kB view raw
1package server 2 3import ( 4 "context" 5 "time" 6 7 "github.com/Azure/go-autorest/autorest/to" 8 "github.com/bluesky-social/indigo/api/atproto" 9 "github.com/bluesky-social/indigo/events" 10 "github.com/bluesky-social/indigo/util" 11 "github.com/haileyok/cocoon/internal/helpers" 12 "github.com/labstack/echo/v4" 13 "golang.org/x/crypto/bcrypt" 14) 15 16type ComAtprotoServerDeleteAccountRequest struct { 17 Did string `json:"did" validate:"required"` 18 Password string `json:"password" validate:"required"` 19 Token string `json:"token" validate:"required"` 20} 21 22func (s *Server) handleServerDeleteAccount(e echo.Context) error { 23 var req ComAtprotoServerDeleteAccountRequest 24 if err := e.Bind(&req); err != nil { 25 s.logger.Error("error binding", "error", err) 26 return helpers.ServerError(e, nil) 27 } 28 29 if err := e.Validate(&req); err != nil { 30 s.logger.Error("error validating", "error", err) 31 return helpers.ServerError(e, nil) 32 } 33 34 urepo, err := s.getRepoActorByDid(req.Did) 35 if err != nil { 36 s.logger.Error("error getting repo", "error", err) 37 return echo.NewHTTPError(400, "account not found") 38 } 39 40 if err := bcrypt.CompareHashAndPassword([]byte(urepo.Repo.Password), []byte(req.Password)); err != nil { 41 s.logger.Error("password mismatch", "error", err) 42 return echo.NewHTTPError(401, "Invalid did or password") 43 } 44 45 if urepo.Repo.AccountDeleteCode == nil || urepo.Repo.AccountDeleteCodeExpiresAt == nil { 46 s.logger.Error("no deletion token found for account") 47 return echo.NewHTTPError(400, map[string]interface{}{ 48 "error": "InvalidToken", 49 "message": "Token is invalid", 50 }) 51 } 52 53 if *urepo.Repo.AccountDeleteCode != req.Token { 54 s.logger.Error("deletion token mismatch") 55 return echo.NewHTTPError(400, map[string]interface{}{ 56 "error": "InvalidToken", 57 "message": "Token is invalid", 58 }) 59 } 60 61 if time.Now().UTC().After(*urepo.Repo.AccountDeleteCodeExpiresAt) { 62 s.logger.Error("deletion token expired") 63 return echo.NewHTTPError(400, map[string]interface{}{ 64 "error": "ExpiredToken", 65 "message": "Token is expired", 66 }) 67 } 68 69 tx := s.db.BeginDangerously() 70 if tx.Error != nil { 71 s.logger.Error("error starting transaction", "error", tx.Error) 72 return helpers.ServerError(e, nil) 73 } 74 75 if err := tx.Exec("DELETE FROM blocks WHERE did = ?", nil, req.Did).Error; err != nil { 76 tx.Rollback() 77 s.logger.Error("error deleting blocks", "error", err) 78 return helpers.ServerError(e, nil) 79 } 80 81 if err := tx.Exec("DELETE FROM records WHERE did = ?", nil, req.Did).Error; err != nil { 82 tx.Rollback() 83 s.logger.Error("error deleting records", "error", err) 84 return helpers.ServerError(e, nil) 85 } 86 87 if err := tx.Exec("DELETE FROM blobs WHERE did = ?", nil, req.Did).Error; err != nil { 88 tx.Rollback() 89 s.logger.Error("error deleting blobs", "error", err) 90 return helpers.ServerError(e, nil) 91 } 92 93 if err := tx.Exec("DELETE FROM tokens WHERE did = ?", nil, req.Did).Error; err != nil { 94 tx.Rollback() 95 s.logger.Error("error deleting tokens", "error", err) 96 return helpers.ServerError(e, nil) 97 } 98 99 if err := tx.Exec("DELETE FROM refresh_tokens WHERE did = ?", nil, req.Did).Error; err != nil { 100 tx.Rollback() 101 s.logger.Error("error deleting refresh tokens", "error", err) 102 return helpers.ServerError(e, nil) 103 } 104 105 if err := tx.Exec("DELETE FROM reserved_keys WHERE did = ?", nil, req.Did).Error; err != nil { 106 tx.Rollback() 107 s.logger.Error("error deleting reserved keys", "error", err) 108 return helpers.ServerError(e, nil) 109 } 110 111 if err := tx.Exec("DELETE FROM invite_codes WHERE did = ?", nil, req.Did).Error; err != nil { 112 tx.Rollback() 113 s.logger.Error("error deleting invite codes", "error", err) 114 return helpers.ServerError(e, nil) 115 } 116 117 if err := tx.Exec("DELETE FROM actors WHERE did = ?", nil, req.Did).Error; err != nil { 118 tx.Rollback() 119 s.logger.Error("error deleting actor", "error", err) 120 return helpers.ServerError(e, nil) 121 } 122 123 if err := tx.Exec("DELETE FROM repos WHERE did = ?", nil, req.Did).Error; err != nil { 124 tx.Rollback() 125 s.logger.Error("error deleting repo", "error", err) 126 return helpers.ServerError(e, nil) 127 } 128 129 if err := tx.Commit().Error; err != nil { 130 s.logger.Error("error committing transaction", "error", err) 131 return helpers.ServerError(e, nil) 132 } 133 134 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 135 RepoAccount: &atproto.SyncSubscribeRepos_Account{ 136 Active: false, 137 Did: req.Did, 138 Status: to.StringPtr("deleted"), 139 Seq: time.Now().UnixMicro(), 140 Time: time.Now().Format(util.ISO8601), 141 }, 142 }) 143 144 return e.NoContent(200) 145}