From 45fbbd71f301c7ec93b9db76e9b77b4f7c9c0bcc Mon Sep 17 00:00:00 2001 From: Frederic Henrichs Date: Tue, 15 Oct 2024 12:13:35 +0200 Subject: [PATCH] frontend: add button to delete account to user page. --- .../public/locales/de-DE/translation.json | 5 +- frontend/public/locales/de/translation.json | 5 +- frontend/public/locales/en/translation.json | 5 +- frontend/src/components/login.tsx | 3 + frontend/src/index.tsx | 4 + frontend/src/pages/user.tsx | 81 ++++++++++++++++--- 6 files changed, 91 insertions(+), 12 deletions(-) diff --git a/frontend/public/locales/de-DE/translation.json b/frontend/public/locales/de-DE/translation.json index 9f86746..0df6560 100644 --- a/frontend/public/locales/de-DE/translation.json +++ b/frontend/public/locales/de-DE/translation.json @@ -12,7 +12,10 @@ "current_password_error_message": "Darf nicht leer sein.", "new_password": "Neues Passwort", "new_password_error_message": "Muss mindestens 8 Zeichen lang sein und jeweils mindestens einen Klein- und Großbuchstaben sowie ein Sonderzeichen enthalten.", - "close": "Schließen" + "close": "Schließen", + "delete_user": "Account löschen", + "password": "Passwort", + "password_invalid": "Passwort ist falsch" }, "recovery": { "recovery": "Passwort zurücksetzen", diff --git a/frontend/public/locales/de/translation.json b/frontend/public/locales/de/translation.json index 9f86746..0df6560 100644 --- a/frontend/public/locales/de/translation.json +++ b/frontend/public/locales/de/translation.json @@ -12,7 +12,10 @@ "current_password_error_message": "Darf nicht leer sein.", "new_password": "Neues Passwort", "new_password_error_message": "Muss mindestens 8 Zeichen lang sein und jeweils mindestens einen Klein- und Großbuchstaben sowie ein Sonderzeichen enthalten.", - "close": "Schließen" + "close": "Schließen", + "delete_user": "Account löschen", + "password": "Passwort", + "password_invalid": "Passwort ist falsch" }, "recovery": { "recovery": "Passwort zurücksetzen", diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index a7ebe90..e2ae299 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -12,7 +12,10 @@ "current_password_error_message": "Must not be empty.", "new_password": "New password", "new_password_error_message": "Must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters", - "close": "Close" + "close": "Close", + "delete_user": "Delete account", + "password": "Password", + "password_invalid": "Password is wrong" }, "recovery": { "recovery": "Password recovery", diff --git a/frontend/src/components/login.tsx b/frontend/src/components/login.tsx index d25d1a4..26061fb 100644 --- a/frontend/src/components/login.tsx +++ b/frontend/src/components/login.tsx @@ -49,6 +49,9 @@ export class Login extends Component<{}, LoginState> { return; } + const loginSaltBs64 = Base64.fromUint8Array(login_salt); + window.localStorage.setItem("LoginKey", loginSaltBs64); + const login_key = await generate_hash(this.state.password, login_salt); const login_schema: LoginSchema = { diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 1198e2f..983532b 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -53,6 +53,10 @@ async function refresh_access_token() { credentials: "include" }); + if (!localStorage.getItem("loginKey") || !localStorage.getItem("secret_key")) { + loggedIn.value = AppState.LoggedOut + } + if (resp.status == 200) { loggedIn.value = AppState.LoggedIn; } else { diff --git a/frontend/src/pages/user.tsx b/frontend/src/pages/user.tsx index cef2071..6803e85 100644 --- a/frontend/src/pages/user.tsx +++ b/frontend/src/pages/user.tsx @@ -30,6 +30,9 @@ import { useTranslation } from "react-i18next"; import { Card, Container } from "react-bootstrap"; import { signal } from "@preact/signals"; import { PasswordComponent } from "../components/password_component"; +import i18n from "../i18n"; +import { showAlert } from "../components/Alert"; +import { Base64 } from "js-base64"; interface UserState { @@ -116,15 +119,18 @@ class UserComponent extends Component<{}, State> { } export function User() { - const [show, setShow] = useState(false); + const [showPasswordReset, setShowPasswordReset] = useState(false); + const [deleteUser, setDeleteUser] = useState({show: false, password: "", password_valid: true}); const [currentPassword, setCurrentPassword] = useState(""); const [currentPasswordIsValid, setCurrentPasswordIsValid] = useState(true); const [newPassword, setNewPassword] = useState(""); const [newPasswordIsValid, setNewPasswordIsValid] = useState(true); const validated = signal(false); - const handleClose = () => setShow(false); - const handleShow = () => setShow(true); + const handleUpdatePasswordClose = () => setShowPasswordReset(false); + const handleUpdatePasswordShow = () => setShowPasswordReset(true); + const handleDelteUserClose = () => setDeleteUser({...deleteUser, show: false}); + const handleDeleteUserShow = () => setDeleteUser({...deleteUser, show: true}); const checkPasswords = () => { let ret = true; @@ -147,7 +153,7 @@ export function User() { return ret; } - const submit = async (e: SubmitEvent) => { + const submitUpdatePassword = async (e: SubmitEvent) => { e.preventDefault(); if (!checkPasswords()) { @@ -202,24 +208,81 @@ export function User() { }); if (resp.status === 200) { logout(true); - handleClose(); + handleUpdatePasswordClose(); } }; + const submitDeleteUser = async (e: SubmitEvent) => { + e.preventDefault(); + + const t = i18n.t; + + const loginSaltBs64 = window.localStorage.getItem("LoginKey"); + const loginSalt = Base64.toUint8Array(loginSaltBs64); + const loginKey = await generate_hash(deleteUser.password, loginSalt) + + const resp = await fetch(BACKEND + "/user/delete", { + credentials: "include", + method: "DELETE", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({login_key: [].slice.call(loginKey)}) + }); + + if (resp.status === 200) { + location.reload(); + } else if (resp.status === 400) { + setDeleteUser({...deleteUser, password_valid: false}) + } else { + showAlert(`${t("alert_default_text")}: ${resp.status} ${await resp.text()}`, "danger") + handleDelteUserClose(); + } + } + const {t} = useTranslation("", {useSuspense: false, keyPrefix: "user"}); return (<> - + - -
+ {/* Delete user modal */} + + + + + {t("delete_user")} + + + + + {t("password")} + setDeleteUser({...deleteUser, password: (e.target as HTMLInputElement).value})} isInvalid={!deleteUser.password_valid} invalidMessage={t("password_invalid")} /> + + + + + + + +
+ + {/* Reset password modal */} + +
{t("change_password")} @@ -238,7 +301,7 @@ export function User() { -