add account deletion
CLI only but backend supports calls from app.db.DeleteAccount already takes --delete-account user_id_number with optional --posts to also delete posts. if --posts is omitted all user posts will be updated to anonymous posts
This commit is contained in:
parent
3759f16ed3
commit
c87ca11a52
|
@ -1068,3 +1068,7 @@ func getTempInfo(app *App, key string, r *http.Request, w http.ResponseWriter) s
|
||||||
// Return value
|
// Return value
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteAccount(app *App, userID int64, posts bool) error {
|
||||||
|
return app.db.DeleteAccount(userID, posts)
|
||||||
|
}
|
||||||
|
|
45
app.go
45
app.go
|
@ -30,7 +30,7 @@ import (
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/writeas/go-strip-markdown"
|
stripmd "github.com/writeas/go-strip-markdown"
|
||||||
"github.com/writeas/impart"
|
"github.com/writeas/impart"
|
||||||
"github.com/writeas/web-core/auth"
|
"github.com/writeas/web-core/auth"
|
||||||
"github.com/writeas/web-core/converter"
|
"github.com/writeas/web-core/converter"
|
||||||
|
@ -681,6 +681,49 @@ func ResetPassword(apper Apper, username string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DoDeleteAccount runs the confirmation and account delete process.
|
||||||
|
func DoDeleteAccount(apper Apper, userID int64, posts bool) error {
|
||||||
|
// Connect to the database
|
||||||
|
apper.LoadConfig()
|
||||||
|
connectToDatabase(apper.App())
|
||||||
|
defer shutdown(apper.App())
|
||||||
|
|
||||||
|
// do not delete the root admin account
|
||||||
|
// TODO: check for other admins and skip?
|
||||||
|
if userID == 1 {
|
||||||
|
log.Error("Can not delete admin account")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
// check user exists
|
||||||
|
if _, err := apper.App().db.GetUserByID(userID); err != nil {
|
||||||
|
log.Error("%s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm deletion, w/ w/out posts
|
||||||
|
prompt := promptui.Prompt{
|
||||||
|
Templates: &promptui.PromptTemplates{
|
||||||
|
Success: "{{ . | bold | faint }}: ",
|
||||||
|
},
|
||||||
|
Label: fmt.Sprintf("Delete user with ID: %d", userID),
|
||||||
|
IsConfirm: true,
|
||||||
|
}
|
||||||
|
_, err := prompt.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Aborted...")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Deleting...")
|
||||||
|
err = deleteAccount(apper.App(), userID, posts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("%s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
log.Info("Success.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func connectToDatabase(app *App) {
|
func connectToDatabase(app *App) {
|
||||||
log.Info("Connecting to %s database...", app.cfg.Database.Type)
|
log.Info("Connecting to %s database...", app.cfg.Database.Type)
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,12 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/writeas/web-core/log"
|
"github.com/writeas/web-core/log"
|
||||||
"github.com/writeas/writefreely"
|
"github.com/writeas/writefreely"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -38,6 +39,8 @@ func main() {
|
||||||
// Admin actions
|
// Admin actions
|
||||||
createAdmin := flag.String("create-admin", "", "Create an admin with the given username:password")
|
createAdmin := flag.String("create-admin", "", "Create an admin with the given username:password")
|
||||||
createUser := flag.String("create-user", "", "Create a regular user with the given username:password")
|
createUser := flag.String("create-user", "", "Create a regular user with the given username:password")
|
||||||
|
deleteUserID := flag.Int64("delete-user", 0, "Delete a user with the given id, does not delete posts. Use `--delete-user id --posts`")
|
||||||
|
deletePosts := flag.Bool("posts", false, "Optionally delete the user's posts during account deletion")
|
||||||
resetPassUser := flag.String("reset-pass", "", "Reset the given user's password")
|
resetPassUser := flag.String("reset-pass", "", "Reset the given user's password")
|
||||||
outputVersion := flag.Bool("v", false, "Output the current version")
|
outputVersion := flag.Bool("v", false, "Output the current version")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
@ -102,6 +105,13 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
} else if *deleteUserID != 0 {
|
||||||
|
err := writefreely.DoDeleteAccount(app, *deleteUserID, *deletePosts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
} else if *migrate {
|
} else if *migrate {
|
||||||
err := writefreely.Migrate(app)
|
err := writefreely.Migrate(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
141
database.go
141
database.go
|
@ -61,7 +61,7 @@ type writestore interface {
|
||||||
GetAccessToken(userID int64) (string, error)
|
GetAccessToken(userID int64) (string, error)
|
||||||
GetTemporaryAccessToken(userID int64, validSecs int) (string, error)
|
GetTemporaryAccessToken(userID int64, validSecs int) (string, error)
|
||||||
GetTemporaryOneTimeAccessToken(userID int64, validSecs int, oneTime bool) (string, error)
|
GetTemporaryOneTimeAccessToken(userID int64, validSecs int, oneTime bool) (string, error)
|
||||||
DeleteAccount(userID int64) (l *string, err error)
|
DeleteAccount(userID int64, posts bool) error
|
||||||
ChangeSettings(app *App, u *User, s *userSettings) error
|
ChangeSettings(app *App, u *User, s *userSettings) error
|
||||||
ChangePassphrase(userID int64, sudo bool, curPass string, hashedPass []byte) error
|
ChangePassphrase(userID int64, sudo bool, curPass string, hashedPass []byte) error
|
||||||
|
|
||||||
|
@ -2079,22 +2079,14 @@ func (db *datastore) CollectionHasAttribute(id int64, attr string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *datastore) DeleteAccount(userID int64) (l *string, err error) {
|
// DeleteAccount will delete the entire account for userID, and if posts
|
||||||
debug := ""
|
// is true, also all posts associated with the userID
|
||||||
l = &debug
|
func (db *datastore) DeleteAccount(userID int64, posts bool) error {
|
||||||
|
|
||||||
t, err := db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
stringLogln(l, "Unable to begin: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all collections
|
// Get all collections
|
||||||
rows, err := db.Query("SELECT id, alias FROM collections WHERE owner_id = ?", userID)
|
rows, err := db.Query("SELECT id, alias FROM collections WHERE owner_id = ?", userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
log.Error("Unable to get collections: %v", err)
|
||||||
stringLogln(l, "Unable to get collections: %v", err)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
colls := []Collection{}
|
colls := []Collection{}
|
||||||
|
@ -2102,13 +2094,20 @@ func (db *datastore) DeleteAccount(userID int64) (l *string, err error) {
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err = rows.Scan(&c.ID, &c.Alias)
|
err = rows.Scan(&c.ID, &c.Alias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
log.Error("Unable to scan collection cols: %v", err)
|
||||||
stringLogln(l, "Unable to scan collection cols: %v", err)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
colls = append(colls, c)
|
colls = append(colls, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start transaction
|
||||||
|
t, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to begin: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up all collection related information
|
||||||
var res sql.Result
|
var res sql.Result
|
||||||
for _, c := range colls {
|
for _, c := range colls {
|
||||||
// TODO: user deleteCollection() func
|
// TODO: user deleteCollection() func
|
||||||
|
@ -2116,89 +2115,143 @@ func (db *datastore) DeleteAccount(userID int64) (l *string, err error) {
|
||||||
res, err = t.Exec("DELETE FROM collectionattributes WHERE collection_id = ?", c.ID)
|
res, err = t.Exec("DELETE FROM collectionattributes WHERE collection_id = ?", c.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
t.Rollback()
|
||||||
stringLogln(l, "Unable to delete attributes on %s: %v", c.Alias, err)
|
log.Error("Unable to delete attributes on %s: %v", c.Alias, err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
rs, _ := res.RowsAffected()
|
rs, _ := res.RowsAffected()
|
||||||
stringLogln(l, "Deleted %d for %s from collectionattributes", rs, c.Alias)
|
log.Info("Deleted %d for %s from collectionattributes", rs, c.Alias)
|
||||||
|
|
||||||
// Remove any optional collection password
|
// Remove any optional collection password
|
||||||
res, err = t.Exec("DELETE FROM collectionpasswords WHERE collection_id = ?", c.ID)
|
res, err = t.Exec("DELETE FROM collectionpasswords WHERE collection_id = ?", c.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
t.Rollback()
|
||||||
stringLogln(l, "Unable to delete passwords on %s: %v", c.Alias, err)
|
log.Error("Unable to delete passwords on %s: %v", c.Alias, err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
rs, _ = res.RowsAffected()
|
rs, _ = res.RowsAffected()
|
||||||
stringLogln(l, "Deleted %d for %s from collectionpasswords", rs, c.Alias)
|
log.Info("Deleted %d for %s from collectionpasswords", rs, c.Alias)
|
||||||
|
|
||||||
// Remove redirects to this collection
|
// Remove redirects to this collection
|
||||||
res, err = t.Exec("DELETE FROM collectionredirects WHERE new_alias = ?", c.Alias)
|
res, err = t.Exec("DELETE FROM collectionredirects WHERE new_alias = ?", c.Alias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
t.Rollback()
|
||||||
stringLogln(l, "Unable to delete redirects on %s: %v", c.Alias, err)
|
log.Error("Unable to delete redirects on %s: %v", c.Alias, err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
rs, _ = res.RowsAffected()
|
rs, _ = res.RowsAffected()
|
||||||
stringLogln(l, "Deleted %d for %s from collectionredirects", rs, c.Alias)
|
log.Info("Deleted %d for %s from collectionredirects", rs, c.Alias)
|
||||||
|
|
||||||
|
// Remove any collection keys
|
||||||
|
res, err = t.Exec("DELETE FROM collectionkeys WHERE collection_id = ?", c.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Rollback()
|
||||||
|
log.Error("Unable to delete keys on %s: %v", c.Alias, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rs, _ = res.RowsAffected()
|
||||||
|
log.Info("Deleted %d for %s from collectionkeys", rs, c.Alias)
|
||||||
|
|
||||||
|
// only remove collection in posts if not deleting the user posts
|
||||||
|
if !posts {
|
||||||
|
// Float all collection's posts
|
||||||
|
res, err = t.Exec("UPDATE posts SET collection_id = NULL WHERE collection_id = ? AND owner_id = ?", c.ID, userID)
|
||||||
|
if err != nil {
|
||||||
|
t.Rollback()
|
||||||
|
log.Error("Unable to update collection %s for posts: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rs, _ = res.RowsAffected()
|
||||||
|
log.Info("Removed %d posts from collection %s", rs, c.Alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: federate delete collection
|
||||||
|
|
||||||
|
// Remove remote follows
|
||||||
|
res, err = t.Exec("DELETE FROM remotefollows WHERE collection_id = ?", c.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Rollback()
|
||||||
|
log.Error("Unable to delete remote follows on %s: %v", c.Alias, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rs, _ = res.RowsAffected()
|
||||||
|
log.Info("Deleted %d for %s from remotefollows", rs, c.Alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete collections
|
// Delete collections
|
||||||
res, err = t.Exec("DELETE FROM collections WHERE owner_id = ?", userID)
|
res, err = t.Exec("DELETE FROM collections WHERE owner_id = ?", userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
t.Rollback()
|
||||||
stringLogln(l, "Unable to delete collections: %v", err)
|
log.Error("Unable to delete collections: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
rs, _ := res.RowsAffected()
|
rs, _ := res.RowsAffected()
|
||||||
stringLogln(l, "Deleted %d from collections", rs)
|
log.Info("Deleted %d from collections", rs)
|
||||||
|
|
||||||
// Delete tokens
|
// Delete tokens
|
||||||
res, err = t.Exec("DELETE FROM accesstokens WHERE user_id = ?", userID)
|
res, err = t.Exec("DELETE FROM accesstokens WHERE user_id = ?", userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
t.Rollback()
|
||||||
stringLogln(l, "Unable to delete access tokens: %v", err)
|
log.Error("Unable to delete access tokens: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
rs, _ = res.RowsAffected()
|
rs, _ = res.RowsAffected()
|
||||||
stringLogln(l, "Deleted %d from accesstokens", rs)
|
log.Info("Deleted %d from accesstokens", rs)
|
||||||
|
|
||||||
// Delete posts
|
// Delete posts
|
||||||
|
if posts {
|
||||||
|
// TODO: should maybe get each row so we can federate a delete
|
||||||
|
// if so needs to be outside of transaction like collections
|
||||||
res, err = t.Exec("DELETE FROM posts WHERE owner_id = ?", userID)
|
res, err = t.Exec("DELETE FROM posts WHERE owner_id = ?", userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
t.Rollback()
|
||||||
stringLogln(l, "Unable to delete posts: %v", err)
|
log.Error("Unable to delete posts: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
rs, _ = res.RowsAffected()
|
rs, _ = res.RowsAffected()
|
||||||
stringLogln(l, "Deleted %d from posts", rs)
|
log.Info("Deleted %d from posts", rs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete user attributes
|
||||||
res, err = t.Exec("DELETE FROM userattributes WHERE user_id = ?", userID)
|
res, err = t.Exec("DELETE FROM userattributes WHERE user_id = ?", userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
t.Rollback()
|
||||||
stringLogln(l, "Unable to delete attributes: %v", err)
|
log.Error("Unable to delete attributes: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
rs, _ = res.RowsAffected()
|
rs, _ = res.RowsAffected()
|
||||||
stringLogln(l, "Deleted %d from userattributes", rs)
|
log.Info("Deleted %d from userattributes", rs)
|
||||||
|
|
||||||
|
// Delete user invites
|
||||||
|
res, err = t.Exec("DELETE FROM userinvites WHERE owner_id = ?", userID)
|
||||||
|
if err != nil {
|
||||||
|
t.Rollback()
|
||||||
|
log.Error("Unable to delete invites: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rs, _ = res.RowsAffected()
|
||||||
|
log.Info("Deleted %d from userinvites", rs)
|
||||||
|
|
||||||
|
// Delete the user
|
||||||
res, err = t.Exec("DELETE FROM users WHERE id = ?", userID)
|
res, err = t.Exec("DELETE FROM users WHERE id = ?", userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
t.Rollback()
|
||||||
stringLogln(l, "Unable to delete user: %v", err)
|
log.Error("Unable to delete user: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
rs, _ = res.RowsAffected()
|
rs, _ = res.RowsAffected()
|
||||||
stringLogln(l, "Deleted %d from users", rs)
|
log.Info("Deleted %d from users", rs)
|
||||||
|
|
||||||
|
// Commit all changes to the database
|
||||||
err = t.Commit()
|
err = t.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Rollback()
|
t.Rollback()
|
||||||
stringLogln(l, "Unable to commit: %v", err)
|
log.Error("Unable to commit: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
// TODO: federate delete actor
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *datastore) GetAPActorKeys(collectionID int64) ([]byte, []byte) {
|
func (db *datastore) GetAPActorKeys(collectionID int64) ([]byte, []byte) {
|
||||||
|
|
Loading…
Reference in New Issue