Add self-service account deletion

Settings → Delete account removes the signed-in account and everything it
owns. DELETE /api/me re-checks the password (guarding an unattended session),
then wipes the user's events, config, sessions and user row in one transaction
and removes their photos/<user_id>/ directory. The client clears the account's
local cache and returns to the login screen.

Bumps the service-worker cache so clients pick up the new UI.

Verified: wrong password is rejected (401, data intact); correct password
returns 204, invalidates the session, drops all rows to zero and removes the
photo dir; the email can be re-registered afterwards. Confirmed end to end in
a headless-browser run of the Settings → delete flow.
This commit is contained in:
Alexander Heldt
2026-07-09 19:20:40 +00:00
parent acf2931fb4
commit 5c016ca49e
7 changed files with 152 additions and 2 deletions
+44
View File
@@ -1522,6 +1522,50 @@
settingsDialog.close();
});
// ---------- delete account ----------
const deleteAccountDialog = document.getElementById("delete-account-dialog");
const deleteAccountPassword = document.getElementById("delete-account-password");
const deleteAccountError = document.getElementById("delete-account-error");
const deleteAccountConfirm = document.getElementById("delete-account-confirm");
document.getElementById("delete-account-btn").addEventListener("click", () => {
settingsDialog.close();
deleteAccountPassword.value = "";
deleteAccountError.hidden = true;
deleteAccountDialog.showModal();
setTimeout(() => deleteAccountPassword.focus(), 50);
});
deleteAccountConfirm.addEventListener("click", async () => {
deleteAccountError.hidden = true;
deleteAccountConfirm.disabled = true;
try {
const res = await fetch("api/me", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: deleteAccountPassword.value }),
});
if (!res.ok) {
const msg = (await res.text()).trim();
throw new Error(res.status === 401 ? "Incorrect password" : (msg || `HTTP ${res.status}`));
}
// Account is gone server-side. Wipe this user's local cache before the
// reload drops us back on the login screen.
try {
localStorage.removeItem(eventsKey());
localStorage.removeItem(configKey());
} catch { /* ignore */ }
clearUser();
deleteAccountDialog.close();
location.reload();
} catch (err) {
deleteAccountError.textContent = err.message || "Something went wrong";
deleteAccountError.hidden = false;
} finally {
deleteAccountConfirm.disabled = false;
}
});
// ---------- wiring ----------
document.querySelectorAll("button.action").forEach(btn => {
btn.addEventListener("click", () => openNoteDialog(btn.dataset.type));