Merge pull request #202 from writeas/T319-delete-account

add account deletion

Ref T319
This commit is contained in:
Matt Baer 2020-02-08 15:00:49 -05:00 committed by GitHub
commit f8a40fac4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 154 additions and 49 deletions

View File

@ -187,7 +187,11 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque
var err error var err error
p.User, err = app.db.GetUserForAuth(username) p.User, err = app.db.GetUserForAuth(username)
if err != nil { if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user: %v", err)} if err == ErrUserNotFound {
return err
}
log.Error("Could not get user: %v", err)
return impart.HTTPError{http.StatusInternalServerError, err.Error()}
} }
flashes, _ := getSessionFlashes(app, w, r, nil) flashes, _ := getSessionFlashes(app, w, r, nil)

48
app.go
View File

@ -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"
@ -689,6 +689,52 @@ func ResetPassword(apper Apper, username string) error {
return nil return nil
} }
// DoDeleteAccount runs the confirmation and account delete process.
func DoDeleteAccount(apper Apper, username string) error {
// Connect to the database
apper.LoadConfig()
connectToDatabase(apper.App())
defer shutdown(apper.App())
// check user exists
u, err := apper.App().db.GetUserForAuth(username)
if err != nil {
log.Error("%s", err)
os.Exit(1)
}
userID := u.ID
// do not delete the admin account
// TODO: check for other admins and skip?
if u.IsAdmin() {
log.Error("Can not delete admin account")
os.Exit(1)
}
// confirm deletion, w/ w/out posts
prompt := promptui.Prompt{
Templates: &promptui.PromptTemplates{
Success: "{{ . | bold | faint }}: ",
},
Label: fmt.Sprintf("Really delete user : %s", username),
IsConfirm: true,
}
_, err = prompt.Run()
if err != nil {
log.Info("Aborted...")
os.Exit(0)
}
log.Info("Deleting...")
err = apper.App().db.DeleteAccount(userID)
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)

View File

@ -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,7 @@ 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")
deleteUsername := flag.String("delete-user", "", "Delete a user with the given username")
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 +104,13 @@ func main() {
os.Exit(1) os.Exit(1)
} }
os.Exit(0) os.Exit(0)
} else if *deleteUsername != "" {
err := writefreely.DoDeleteAccount(app, *deleteUsername)
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 {

View File

@ -64,7 +64,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) 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
@ -2114,22 +2114,13 @@ 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
debug := "" func (db *datastore) DeleteAccount(userID int64) error {
l = &debug
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{}
@ -2137,103 +2128,158 @@ 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
// Delete tokens // Delete tokens
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)
// 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 user attributes
res, err = t.Exec("DELETE FROM oauth_users WHERE user_id = ?", userID)
if err != nil {
t.Rollback()
log.Error("Unable to delete oauth_users: %v", err)
return err
}
rs, _ = res.RowsAffected()
log.Info("Deleted %d from oauth_users", rs)
// Delete posts // Delete 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) {