From 9039998e98f8766d167f64c282306695d65e3fde Mon Sep 17 00:00:00 2001 From: Tim Wundenberg Date: Wed, 11 Sep 2024 18:17:44 +0200 Subject: [PATCH] feat(auth): enable users to delete their account #164 --- handler.go | 4 +- service/auth.go | 96 ++++++++++++++++++++++++++---- template/auth/delete_account.templ | 16 +++++ template/auth/user.templ | 26 +++++++- template/layout.templ | 2 +- 5 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 template/auth/delete_account.templ diff --git a/handler.go b/handler.go index e4faa5b..20964d7 100644 --- a/handler.go +++ b/handler.go @@ -30,11 +30,13 @@ func getHandler(db *sql.DB) http.Handler { // Don't use auth middleware for these routes, as it makes redirecting very difficult, if the mail is not yet verified router.Handle("/auth/signin", service.HandleSignInPage(db)) router.Handle("/auth/signup", service.HandleSignUpPage(db)) - router.Handle("/auth/verify", service.HandleSignUpVerifyPage(db)) // Hint for the user to verify their email + router.Handle("/auth/verify", service.HandleSignUpVerifyPage(db)) // Hint for the user to verify their email + router.Handle("/auth/delete-account", service.HandleDeleteAccountPage(db)) router.Handle("/auth/verify-email", service.HandleSignUpVerifyResponsePage(db)) // The link contained in the email router.Handle("/api/auth/signup", service.HandleSignUpComp(db)) router.Handle("/api/auth/signin", service.HandleSignInComp(db)) router.Handle("/api/auth/signout", service.HandleSignOutComp(db)) + router.Handle("/api/auth/delete-account", service.HandleDeleteAccountComp(db)) router.Handle("/api/auth/verify-resend", service.HandleVerifyResendComp(db)) return middleware.Logging(middleware.EnableCors(router)) diff --git a/service/auth.go b/service/auth.go index d9054fc..6cce269 100644 --- a/service/auth.go +++ b/service/auth.go @@ -1,6 +1,7 @@ package service import ( + "context" "crypto/rand" "crypto/subtle" "database/sql" @@ -84,6 +85,24 @@ func HandleSignUpVerifyPage(db *sql.DB) http.HandlerFunc { } } +func HandleDeleteAccountPage(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // An enverified email should be able to delete their account + user := utils.GetUserFromSession(db, r) + if user == nil { + utils.DoRedirect(w, r, "/auth/signin") + } else { + userComp := UserInfoComp(user) + comp := auth.DeleteAccountComp() + err := template.Layout(comp, userComp).Render(r.Context(), w) + if err != nil { + utils.LogError("Failed to render verify page", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + } + } +} + func HandleSignUpVerifyResponsePage(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := r.URL.Query().Get("token") @@ -283,6 +302,48 @@ func HandleSignOutComp(db *sql.DB) http.HandlerFunc { } } +func HandleDeleteAccountComp(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user := utils.GetUserFromSession(db, r) + if user == nil { + utils.DoRedirect(w, r, "/auth/signin") + return + } + + _, err := db.Exec("DELETE FROM workout WHERE user_id = ?", user.Id) + if err != nil { + utils.LogError("Could not delete workouts", err) + utils.TriggerToast(w, r, "error", "Internal Server Error") + return + } + + _, err = db.Exec("DELETE FROM user_token WHERE user_uuid = ?", user.Id) + if err != nil { + utils.LogError("Could not delete user tokens", err) + utils.TriggerToast(w, r, "error", "Internal Server Error") + return + } + + _, err = db.Exec("DELETE FROM session WHERE user_uuid = ?", user.Id) + if err != nil { + utils.LogError("Could not delete sessions", err) + utils.TriggerToast(w, r, "error", "Internal Server Error") + return + } + + _, err = db.Exec("DELETE FROM user WHERE user_uuid = ?", user.Id) + if err != nil { + utils.LogError("Could not delete user", err) + utils.TriggerToast(w, r, "error", "Internal Server Error") + return + } + + go utils.SendMail(user.Email, "Account deleted", "Your account has been deleted") + + utils.DoRedirect(w, r, "/") + } +} + func HandleVerifyResendComp(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user := utils.GetUserFromSession(db, r) @@ -298,23 +359,36 @@ func HandleVerifyResendComp(db *sql.DB) http.HandlerFunc { } func sendVerificationEmail(db *sql.DB, r *http.Request, userId string, email string) { - var b []byte = make([]byte, 32) - _, err := rand.Reader.Read(b) - if err != nil { - utils.LogError("Could not generate token", err) + + var token string + err := db.QueryRow("SELECT token FROM user_token WHERE user_uuid = ? AND type = 'email_verify'", userId).Scan(&token) + if err != nil && err != sql.ErrNoRows { + utils.LogError("Could not get token", err) return } - token := base64.StdEncoding.EncodeToString(b) - _, err = db.Exec("INSERT INTO user_token (user_uuid, type, token, created_at) VALUES (?, 'email_verify', ?, datetime())", userId, token) - if err != nil { - utils.LogError("Could not insert token", err) - return + if token == "" { + var b []byte = make([]byte, 32) + _, err = rand.Reader.Read(b) + if err != nil { + utils.LogError("Could not generate token", err) + return + } + token = base64.StdEncoding.EncodeToString(b) + + _, err = db.Exec("INSERT INTO user_token (user_uuid, type, token, created_at) VALUES (?, 'email_verify', ?, datetime())", userId, token) + if err != nil { + utils.LogError("Could not insert token", err) + return + } } var w strings.Builder - tempMail.Register(token).Render(r.Context(), &w) - + err = tempMail.Register(token).Render(context.Background(), &w) + if err != nil { + utils.LogError("Could not render welcome email", err) + return + } utils.SendMail(email, "Welcome to ME-FIT", w.String()) } diff --git a/template/auth/delete_account.templ b/template/auth/delete_account.templ new file mode 100644 index 0000000..8663752 --- /dev/null +++ b/template/auth/delete_account.templ @@ -0,0 +1,16 @@ +package auth + +templ DeleteAccountComp() { +
+
+
+

Delete Account

+

Do you really want to delete all your data? This cannot be undone!

+
+ Cancel + +
+
+
+
+} diff --git a/template/auth/user.templ b/template/auth/user.templ index 4b639be..ed40d10 100644 --- a/template/auth/user.templ +++ b/template/auth/user.templ @@ -3,8 +3,30 @@ package auth templ UserComp(user string) {
if user != "" { - Sign Out -

{ user }

+
+ + +
} else { Sign Up Sign In diff --git a/template/layout.templ b/template/layout.templ index e5b6d36..f118a81 100644 --- a/template/layout.templ +++ b/template/layout.templ @@ -15,7 +15,7 @@ templ Layout(slot templ.Component, user templ.Component) {