From: PhiTux Date: Sat, 19 Jul 2025 15:00:00 +0000 (+0200) Subject: added option to delete own account X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=ac8530efcab06f57f20393c75f30b945135a90d2;p=DailyTxT.git added option to delete own account --- diff --git a/backend/handlers/users.go b/backend/handlers/users.go index 8f2f75d..c84ddd1 100644 --- a/backend/handlers/users.go +++ b/backend/handlers/users.go @@ -856,3 +856,122 @@ func ChangePassword(w http.ResponseWriter, r *http.Request) { "message": "Password changed successfully", }) } + +type DeleteAccountRequest struct { + Password string `json:"password"` +} + +func DeleteAccount(w http.ResponseWriter, r *http.Request) { + // Get user ID from context + userID, ok := r.Context().Value(utils.UserIDKey).(int) + if !ok { + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": false, + "message": "User not authenticated", + }) + return + } + + // Parse request body + var req DeleteAccountRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + utils.JSONResponse(w, http.StatusBadRequest, map[string]any{ + "success": false, + "message": "Invalid request body", + }) + return + } + + // Check if password is correct + users, err := utils.GetUsers() + if err != nil { + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": false, + "message": fmt.Sprintf("Error retrieving users: %v", err), + }) + return + } + usersList, ok := users["users"].([]any) + if !ok { + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": false, + "message": "Users data is not in the correct format", + }) + return + } + + var user map[string]any + for _, u := range usersList { + uMap, ok := u.(map[string]any) + if !ok { + continue + } + if id, ok := uMap["user_id"].(float64); ok && int(id) == userID { + user = uMap + break + } + } + + if user == nil { + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": false, + "message": "User not found", + }) + return + } + + password, ok := user["password"].(string) + if !ok { + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": false, + "message": "Current hashed password not found for user", + }) + return + } + + if !utils.VerifyPassword(req.Password, password) { + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": false, + "message": "Password is incorrect", + "password_incorrect": true, + }) + return + } + + // Remove user from users list + var newUsersList []any + for _, u := range usersList { + uMap, ok := u.(map[string]any) + if !ok { + continue + } + // Keep all users, except the one with the same user_id + if id, ok := uMap["user_id"].(float64); !ok || int(id) != userID { + utils.Logger.Printf("Keeping user with ID %f (%d)", id, userID) + newUsersList = append(newUsersList, u) + } + } + users["users"] = newUsersList + + if err := utils.WriteUsers(users); err != nil { + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": false, + "message": fmt.Sprintf("Error writing users data: %v", err), + }) + return + } + + // Delete directory of the user with all his data + if err := utils.DeleteUserData(userID); err != nil { + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": false, + "message": fmt.Sprintf("Error deleting user data of ID %d (account already deleted): %v", userID, err), + }) + utils.Logger.Printf("Error deleting user data of ID %d (You can savely delete the directory with the same id): %v", userID, err) + return + } + + utils.JSONResponse(w, http.StatusOK, map[string]any{ + "success": true, + }) +} diff --git a/backend/main.go b/backend/main.go index 16c5ab0..d308d81 100644 --- a/backend/main.go +++ b/backend/main.go @@ -40,6 +40,7 @@ func main() { mux.HandleFunc("GET /users/getUserSettings", middleware.RequireAuth(handlers.GetUserSettings)) mux.HandleFunc("POST /users/saveUserSettings", middleware.RequireAuth(handlers.SaveUserSettings)) mux.HandleFunc("POST /users/changePassword", middleware.RequireAuth(handlers.ChangePassword)) + mux.HandleFunc("POST /users/deleteAccount", middleware.RequireAuth(handlers.DeleteAccount)) mux.HandleFunc("POST /logs/saveLog", middleware.RequireAuth(handlers.SaveLog)) mux.HandleFunc("GET /logs/getLog", middleware.RequireAuth(handlers.GetLog)) diff --git a/backend/utils/file_handling.go b/backend/utils/file_handling.go index 07a0e1f..979bd82 100644 --- a/backend/utils/file_handling.go +++ b/backend/utils/file_handling.go @@ -447,3 +447,14 @@ func GetMonths(userID int, year string) ([]string, error) { return months, nil } + +func DeleteUserData(userID int) error { + // Try to remove the user directory + dirPath := filepath.Join(Settings.DataPath, strconv.Itoa(userID)) + if err := os.RemoveAll(dirPath); err != nil { + Logger.Printf("Error removing directory %s: %v", dirPath, err) + return fmt.Errorf("internal server error when trying to remove user data for ID %d", userID) + } + + return nil +} diff --git a/frontend/src/routes/(authed)/+layout.svelte b/frontend/src/routes/(authed)/+layout.svelte index 4644868..503da48 100644 --- a/frontend/src/routes/(authed)/+layout.svelte +++ b/frontend/src/routes/(authed)/+layout.svelte @@ -57,7 +57,6 @@ }); document.getElementById('settingsModal').addEventListener('shown.bs.modal', function () { - console.log("triggered 'shown.bs.modal' event"); const height = document.getElementById('modal-body').clientHeight; document.getElementById('settings-content').style.height = 'calc(' + height + 'px - 2rem)'; document.getElementById('settings-nav').style.height = 'calc(' + height + 'px - 2rem)'; @@ -73,12 +72,16 @@ }); }); - function logout() { + function logout(errorCode) { axios .get(API_URL + '/users/logout') .then((response) => { localStorage.removeItem('user'); - goto('/login'); + if (errorCode) { + goto(`/login?error=${errorCode}`); + } else { + goto('/login'); + } }) .catch((error) => { console.error(error); @@ -476,6 +479,46 @@ isChangingPassword = false; }); } + + let showConfirmDeleteAccount = $state(false); + let deleteAccountPassword = $state(''); + let isDeletingAccount = $state(false); + let deleteAccountPasswordIncorrect = $state(false); + let showDeleteAccountSuccess = $state(false); + + function deleteAccount() { + if (isDeletingAccount) return; + isDeletingAccount = true; + + axios + .post(API_URL + '/users/deleteAccount', { + password: deleteAccountPassword + }) + .then((response) => { + if (response.data.success) { + showDeleteAccountSuccess = true; + + // close modal + settingsModal.hide(); + + logout(410); // HTTP 410 Gone => Account deleted + } else if (response.data.password_incorrect) { + deleteAccountPasswordIncorrect = true; + } else { + console.error('Error deleting account'); + console.error(response.data); + } + }) + .catch((error) => { + console.error(error); + deleteAccountPasswordIncorrect = true; + }) + .finally(() => { + isDeletingAccount = false; + showConfirmDeleteAccount = false; + deleteAccountPassword = ''; + }); + }
@@ -516,7 +559,7 @@ -
@@ -839,7 +882,11 @@ deleteTag={askDeleteTag} /> {#if deleteTagId === tag.id} -