Implement user account deletion flow
POST /user/delete (auth required) verifies the password via pass1 and transactionally removes the user's sessions, votes, entries and the user row, then clears the session cookie. Associated media files are removed best effort after commit. This was only a stub in the Flask original. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -32,13 +32,13 @@ func main() {
|
||||
|
||||
// --- User (öffentlich) ---
|
||||
r.Get("/u/{username}", handleUserPage)
|
||||
r.Get("/user/delete", handleUserDelete)
|
||||
|
||||
// --- Geschützt (Session erforderlich) ---
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(requireAuth)
|
||||
r.Post("/entry/create", handleCreateEntry)
|
||||
r.Get("/user/info", handleUserInfo)
|
||||
r.Post("/user/delete", handleUserDelete)
|
||||
})
|
||||
|
||||
// Hochgeladene Medien + alte statische Assets
|
||||
|
||||
+14
-3
@@ -174,9 +174,21 @@ Eigene Account-Informationen.
|
||||
|
||||
---
|
||||
|
||||
### `GET /user/delete`
|
||||
### `POST /user/delete`
|
||||
|
||||
Account löschen. (*Noch nicht implementiert.*)
|
||||
Eigenen Account dauerhaft löschen. Beiträge, Votes und Sessions werden
|
||||
mitgelöscht, zugehörige Mediendateien best effort entfernt.
|
||||
|
||||
**Auth:** erforderlich
|
||||
|
||||
**Request (form-encoded):**
|
||||
| Feld | Typ | Beschreibung |
|
||||
|------|-----|--------------|
|
||||
| `pass1` | string | Passwort zur Bestätigung |
|
||||
|
||||
**Response:**
|
||||
- `200` `{"status": "deleted"}` + löscht den `session`-Cookie
|
||||
- `403` bei falschem Passwort
|
||||
|
||||
---
|
||||
|
||||
@@ -223,7 +235,6 @@ CREATE TABLE vote (
|
||||
## Offene Punkte
|
||||
|
||||
- `/entry/:pid/interactions/:mode` ist auskommentiert — Voting-Logik muss neu designed werden
|
||||
- `/user/delete` ist noch ein Stub
|
||||
- Kein Rate-Limiting außer dem zufälligen `sleep` beim Login
|
||||
- `session`-Token ist nur `randbelow(999999999) + timestamp` — sollte auf `crypto/rand` umgestellt werden
|
||||
- Fehler-Responses sind aktuell Plaintext/HTML — in der Go-API einheitlich JSON
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -71,6 +72,68 @@ func handleUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// handleUserDelete löscht den eingeloggten Account dauerhaft, nach Bestätigung
|
||||
// durch das Passwort (Formularfeld pass1). Beiträge, Votes und Sessions werden
|
||||
// in einer Transaktion mitgelöscht; zugehörige Mediendateien best effort entfernt.
|
||||
func handleUserDelete(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusNotImplemented, "Account-Löschung ist noch nicht implementiert")
|
||||
uid := uidFromContext(r.Context())
|
||||
|
||||
var hash []byte
|
||||
if err := db.QueryRow(`SELECT password FROM user WHERE uid = ?`, uid).Scan(&hash); err != nil {
|
||||
writeError(w, http.StatusNotFound, "Nutzer nicht gefunden")
|
||||
return
|
||||
}
|
||||
|
||||
if bcrypt.CompareHashAndPassword(hash, []byte(r.FormValue("pass1"))) != nil {
|
||||
writeError(w, http.StatusForbidden, "Passwort ist falsch.")
|
||||
return
|
||||
}
|
||||
|
||||
// Mediendateien der Beiträge einsammeln, bevor die Zeilen verschwinden.
|
||||
var media []string
|
||||
if rows, err := db.Query(`SELECT filepath FROM entry WHERE uid = ?`, uid); err == nil {
|
||||
for rows.Next() {
|
||||
var fp string
|
||||
if rows.Scan(&fp) == nil && fp != "" && fp != "none" {
|
||||
media = append(media, fp)
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Löschen fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
for _, q := range []string{
|
||||
`DELETE FROM session WHERE uid = ?`,
|
||||
`DELETE FROM vote WHERE uid = ?`,
|
||||
`DELETE FROM entry WHERE uid = ?`,
|
||||
`DELETE FROM user WHERE uid = ?`,
|
||||
} {
|
||||
if _, err := tx.Exec(q, uid); err != nil {
|
||||
tx.Rollback()
|
||||
writeError(w, http.StatusInternalServerError, "Löschen fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Löschen fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
|
||||
// Erst nach erfolgreichem Commit die Dateien entfernen.
|
||||
for _, fp := range media {
|
||||
os.Remove(fp)
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "session",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
MaxAge: -1,
|
||||
})
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user