From 7272ca8448eb40af251a79b413e3a02821a84b48 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 10 Dec 2023 09:15:11 -0800 Subject: [PATCH] Delete data from backend when uninstalling, fixes #132 (#143) --- backend/server/internal/database/db.go | 30 +++++++++++++++++-- .../server/internal/server/api_handlers.go | 12 ++++++++ backend/server/internal/server/srv.go | 1 + client/cmd/install.go | 7 ++++- shared/data.go | 3 ++ 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/backend/server/internal/database/db.go b/backend/server/internal/database/db.go index cd44760..cf51bd7 100644 --- a/backend/server/internal/database/db.go +++ b/backend/server/internal/database/db.go @@ -197,6 +197,32 @@ func (db *DB) ApplyDeletionRequestsToBackend(ctx context.Context, requests []*sh return db.DeleteMessagesFromBackend(ctx, userId, deletedMessages) } +func (db *DB) UninstallDevice(ctx context.Context, userId, deviceId string) (int64, error) { + // Note that this is deleting entries that are destined to be *read* by this device. If there are other devices on this account, + // those queues are unaffected. + r1 := db.WithContext(ctx).Where("user_id = ? AND device_id = ?", userId, deviceId).Delete(&shared.EncHistoryEntry{}) + if r1.Error != nil { + return 0, fmt.Errorf("UninstallDevice: failed to delete entries: %w", r1.Error) + } + // Note that this is deleting deletion requests that were destined to be processed by this device. So if this device deleted anything, + // those requests are still pending. + r2 := db.WithContext(ctx).Where("user_id = ? AND destination_device_id = ?", userId, deviceId).Delete(&shared.DeletionRequest{}) + if r2.Error != nil { + return 0, fmt.Errorf("UninstallDevice: failed to delete deletion requests: %w", r2.Error) + } + // Similarly, note that this is deleting dump requests from this device so that other devices won't respond to these. + r3 := db.WithContext(ctx).Where("user_id = ? AND requesting_device_id = ?", userId, deviceId).Delete(&shared.DumpRequest{}) + if r3.Error != nil { + return 0, fmt.Errorf("UninstallDevice: failed to delete dump requests: %w", r3.Error) + } + // Lastly, update the flag so that we know this device has been deleted + r := db.WithContext(ctx).Model(&shared.Device{}).Where("user_id = ? AND device_id = ?", userId, deviceId).Update("uninstall_date", time.Now().UTC()) + if r.Error != nil { + return 0, fmt.Errorf("UnisntallDevice: failed to update uninstall_date: %w", r.Error) + } + return r1.RowsAffected + r2.RowsAffected + r3.RowsAffected, nil +} + func (db *DB) DeleteMessagesFromBackend(ctx context.Context, userId string, deletedMessages []shared.MessageIdentifier) (int64, error) { tx := db.WithContext(ctx).Where("false") for _, message := range deletedMessages { @@ -219,8 +245,8 @@ func (db *DB) DeleteMessagesFromBackend(ctx context.Context, userId string, dele } } result := tx.Delete(&shared.EncHistoryEntry{}) - if tx.Error != nil { - return 0, fmt.Errorf("tx.Error: %w", tx.Error) + if result.Error != nil { + return 0, fmt.Errorf("result.Error: %w", result.Error) } return result.RowsAffected, nil } diff --git a/backend/server/internal/server/api_handlers.go b/backend/server/internal/server/api_handlers.go index b96b992..dc79912 100644 --- a/backend/server/internal/server/api_handlers.go +++ b/backend/server/internal/server/api_handlers.go @@ -345,3 +345,15 @@ func (s *Server) testOnlyOverrideAiSuggestions(w http.ResponseWriter, r *http.Re func (s *Server) pingHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) } + +func (s *Server) apiUninstallHandler(w http.ResponseWriter, r *http.Request) { + userId := getRequiredQueryParam(r, "user_id") + deviceId := getRequiredQueryParam(r, "device_id") + numDeleted, err := s.db.UninstallDevice(r.Context(), userId, deviceId) + if err != nil { + panic(fmt.Errorf("failed to UninstallDevice(user_id=%s, device_id=%s): %w", userId, deviceId, err)) + } + fmt.Printf("apiUninstallHandler: Deleted %d items from the DB\n", numDeleted) + w.Header().Set("Content-Length", "0") + w.WriteHeader(http.StatusOK) +} diff --git a/backend/server/internal/server/srv.go b/backend/server/internal/server/srv.go index 97ef3b4..423457c 100644 --- a/backend/server/internal/server/srv.go +++ b/backend/server/internal/server/srv.go @@ -112,6 +112,7 @@ func (s *Server) Run(ctx context.Context, addr string) error { mux.Handle("/api/v1/add-deletion-request", middlewares(http.HandlerFunc(s.addDeletionRequestHandler))) mux.Handle("/api/v1/slsa-status", middlewares(http.HandlerFunc(s.slsaStatusHandler))) mux.Handle("/api/v1/feedback", middlewares(http.HandlerFunc(s.feedbackHandler))) + mux.Handle("/api/v1/uninstall", middlewares(http.HandlerFunc(s.apiUninstallHandler))) mux.Handle("/api/v1/ai-suggest", middlewares(http.HandlerFunc(s.aiSuggestionHandler))) mux.Handle("/api/v1/ping", middlewares(http.HandlerFunc(s.pingHandler))) mux.Handle("/healthcheck", middlewares(http.HandlerFunc(s.healthCheckHandler))) diff --git a/client/cmd/install.go b/client/cmd/install.go index 202243d..7030d42 100644 --- a/client/cmd/install.go +++ b/client/cmd/install.go @@ -119,6 +119,12 @@ var uninstallCmd = &cobra.Command{ lib.CheckFatalError(err) _, _ = lib.ApiPost(ctx, "/api/v1/feedback", "application/json", reqBody) lib.CheckFatalError(uninstall(ctx)) + _, err = lib.ApiPost(ctx, "/api/v1/uninstall?user_id="+data.UserId(hctx.GetConf(ctx).UserSecret)+"&device_id="+hctx.GetConf(ctx).DeviceId, "application/json", []byte{}) + if err == nil { + fmt.Println("Successfully uninstalled hishtory, please restart your terminal...") + } else { + fmt.Printf("Uninstall completed, but received server error: %v", err) + } }, } @@ -529,7 +535,6 @@ func uninstall(ctx context.Context) error { if err != nil { return err } - fmt.Println("Successfully uninstalled hishtory, please restart your terminal...") return nil } diff --git a/shared/data.go b/shared/data.go index c12275a..2589e0c 100644 --- a/shared/data.go +++ b/shared/data.go @@ -21,6 +21,7 @@ type EncHistoryEntry struct { ReadCount int `json:"read_count"` } +// TODO: Move this struct to the backend directory type Device struct { UserId string `json:"user_id"` DeviceId string `json:"device_id"` @@ -32,6 +33,8 @@ type Device struct { RegistrationDate time.Time `json:"registration_date"` // Test devices, that should be aggressively cleaned from the DB IsIntegrationTestDevice bool `json:"is_integration_test_device"` + // Whether this device was uninstalled + UninstallDate time.Time `json:"uninstall_date"` } // Represents a request to get all history entries from a given device. Used as part of bootstrapping