forked from hailey.at/cocoon
An atproto PDS written in Go
at main 4.6 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 ctx := e.Request().Context() 24 logger := s.logger.With("name", "handleServerDeleteAccount") 25 26 var req ComAtprotoServerDeleteAccountRequest 27 if err := e.Bind(&req); err != nil { 28 logger.Error("error binding", "error", err) 29 return helpers.ServerError(e, nil) 30 } 31 32 if err := e.Validate(&req); err != nil { 33 logger.Error("error validating", "error", err) 34 return helpers.ServerError(e, nil) 35 } 36 37 urepo, err := s.getRepoActorByDid(ctx, req.Did) 38 if err != nil { 39 logger.Error("error getting repo", "error", err) 40 return echo.NewHTTPError(400, "account not found") 41 } 42 43 if err := bcrypt.CompareHashAndPassword([]byte(urepo.Repo.Password), []byte(req.Password)); err != nil { 44 logger.Error("password mismatch", "error", err) 45 return echo.NewHTTPError(401, "Invalid did or password") 46 } 47 48 if urepo.Repo.AccountDeleteCode == nil || urepo.Repo.AccountDeleteCodeExpiresAt == nil { 49 logger.Error("no deletion token found for account") 50 return echo.NewHTTPError(400, map[string]interface{}{ 51 "error": "InvalidToken", 52 "message": "Token is invalid", 53 }) 54 } 55 56 if *urepo.Repo.AccountDeleteCode != req.Token { 57 logger.Error("deletion token mismatch") 58 return echo.NewHTTPError(400, map[string]interface{}{ 59 "error": "InvalidToken", 60 "message": "Token is invalid", 61 }) 62 } 63 64 if time.Now().UTC().After(*urepo.Repo.AccountDeleteCodeExpiresAt) { 65 logger.Error("deletion token expired") 66 return echo.NewHTTPError(400, map[string]interface{}{ 67 "error": "ExpiredToken", 68 "message": "Token is expired", 69 }) 70 } 71 72 tx := s.db.BeginDangerously(ctx) 73 if tx.Error != nil { 74 logger.Error("error starting transaction", "error", tx.Error) 75 return helpers.ServerError(e, nil) 76 } 77 78 status := "error" 79 func() { 80 if status == "error" { 81 if err := tx.Rollback().Error; err != nil { 82 logger.Error("error rolling back after delete failure", "err", err) 83 } 84 } 85 }() 86 87 if err := tx.Exec("DELETE FROM blocks WHERE did = ?", nil, req.Did).Error; err != nil { 88 logger.Error("error deleting blocks", "error", err) 89 return helpers.ServerError(e, nil) 90 } 91 92 if err := tx.Exec("DELETE FROM records WHERE did = ?", nil, req.Did).Error; err != nil { 93 logger.Error("error deleting records", "error", err) 94 return helpers.ServerError(e, nil) 95 } 96 97 if err := tx.Exec("DELETE FROM blobs WHERE did = ?", nil, req.Did).Error; err != nil { 98 logger.Error("error deleting blobs", "error", err) 99 return helpers.ServerError(e, nil) 100 } 101 102 if err := tx.Exec("DELETE FROM tokens WHERE did = ?", nil, req.Did).Error; err != nil { 103 logger.Error("error deleting tokens", "error", err) 104 return helpers.ServerError(e, nil) 105 } 106 107 if err := tx.Exec("DELETE FROM refresh_tokens WHERE did = ?", nil, req.Did).Error; err != nil { 108 logger.Error("error deleting refresh tokens", "error", err) 109 return helpers.ServerError(e, nil) 110 } 111 112 if err := tx.Exec("DELETE FROM reserved_keys WHERE did = ?", nil, req.Did).Error; err != nil { 113 logger.Error("error deleting reserved keys", "error", err) 114 return helpers.ServerError(e, nil) 115 } 116 117 if err := tx.Exec("DELETE FROM invite_codes WHERE did = ?", nil, req.Did).Error; err != nil { 118 logger.Error("error deleting invite codes", "error", err) 119 return helpers.ServerError(e, nil) 120 } 121 122 if err := tx.Exec("DELETE FROM actors WHERE did = ?", nil, req.Did).Error; err != nil { 123 logger.Error("error deleting actor", "error", err) 124 return helpers.ServerError(e, nil) 125 } 126 127 if err := tx.Exec("DELETE FROM repos WHERE did = ?", nil, req.Did).Error; err != nil { 128 logger.Error("error deleting repo", "error", err) 129 return helpers.ServerError(e, nil) 130 } 131 132 status = "ok" 133 134 if err := tx.Commit().Error; err != nil { 135 logger.Error("error committing transaction", "error", err) 136 return helpers.ServerError(e, nil) 137 } 138 139 s.evtman.AddEvent(context.TODO(), &events.XRPCStreamEvent{ 140 RepoAccount: &atproto.SyncSubscribeRepos_Account{ 141 Active: false, 142 Did: req.Did, 143 Status: to.StringPtr("deleted"), 144 Seq: time.Now().UnixMicro(), 145 Time: time.Now().Format(util.ISO8601), 146 }, 147 }) 148 149 return e.NoContent(200) 150}