feat(auth): enable users to delete their account #164
All checks were successful
Build Docker Image / Explore-Gitea-Actions (push) Successful in 48s
All checks were successful
Build Docker Image / Explore-Gitea-Actions (push) Successful in 48s
This commit is contained in:
@@ -31,10 +31,12 @@ func getHandler(db *sql.DB) http.Handler {
|
||||
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/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))
|
||||
|
||||
@@ -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 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
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
var b []byte = make([]byte, 32)
|
||||
_, err := rand.Reader.Read(b)
|
||||
_, err = rand.Reader.Read(b)
|
||||
if err != nil {
|
||||
utils.LogError("Could not generate token", err)
|
||||
return
|
||||
}
|
||||
token := base64.StdEncoding.EncodeToString(b)
|
||||
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())
|
||||
}
|
||||
|
||||
|
||||
16
template/auth/delete_account.templ
Normal file
16
template/auth/delete_account.templ
Normal file
@@ -0,0 +1,16 @@
|
||||
package auth
|
||||
|
||||
templ DeleteAccountComp() {
|
||||
<main class="h-full flex items-center justify-center">
|
||||
<div class="card bg-neutral text-neutral-content w-96">
|
||||
<div class="card-body items-center text-center">
|
||||
<h2 class="card-title">Delete Account</h2>
|
||||
<p>Do you really want to delete all your data? This cannot be undone!</p>
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<a href="/" class="btn btn-ghost">Cancel</a>
|
||||
<button hx-get="/api/auth/delete-account" class="btn btn-primary">Delete Account</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
@@ -3,8 +3,30 @@ package auth
|
||||
templ UserComp(user string) {
|
||||
<div id="user-info" class="flex gap-5 items-center">
|
||||
if user != "" {
|
||||
<a hx-get="/api/auth/signout" hx-target="#user-info" class="btn btn-sm">Sign Out</a>
|
||||
<p>{ user }</p>
|
||||
<div class="group inline-block relative">
|
||||
<button
|
||||
class="font-semibold py-2 px-4 inline-flex items-center"
|
||||
>
|
||||
<span class="mr-1">{ user }</span>
|
||||
<svg
|
||||
class="fill-current h-4 w-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="absolute hidden group-hover:block w-full">
|
||||
<ul class="menu bg-base-300 rounded-box w-fit float-right mr-4 p-3">
|
||||
<li class="mb-1">
|
||||
<a hx-get="/api/auth/signout" hx-target="#user-info">Sign Out</a>
|
||||
</li>
|
||||
<li><a href="/auth/delete-account" class="text-error">Delete Account</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
<a href="/auth/signup" class="btn btn-sm">Sign Up</a>
|
||||
<a href="/auth/signin" class="btn btn-sm">Sign In</a>
|
||||
|
||||
@@ -15,7 +15,7 @@ templ Layout(slot templ.Component, user templ.Component) {
|
||||
</head>
|
||||
<body>
|
||||
<div class="h-screen flex flex-col">
|
||||
<div class="flex justify-end items-center gap-2 py-1 px-2 md:gap-10 md:px-10 md:py-2 shadow">
|
||||
<div class="flex justify-end items-center gap-2 py-1 px-2 h-12 md:gap-10 md:px-10 md:py-2 shadow">
|
||||
<a href="/" class="flex-1 flex gap-2">
|
||||
<img src="/static/favicon.svg" alt="ME-FIT logo"/>
|
||||
<span>ME-FIT</span>
|
||||
|
||||
Reference in New Issue
Block a user