Add password confirmation to account delete dialog, v1/tiers test
This commit is contained in:
@@ -175,7 +175,7 @@
|
||||
"account_basics_password_dialog_confirm_password_label": "Confirm password",
|
||||
"account_basics_password_dialog_button_cancel": "Cancel",
|
||||
"account_basics_password_dialog_button_submit": "Change password",
|
||||
"account_basics_password_dialog_current_password_incorrect": "Current password incorrect",
|
||||
"account_basics_password_dialog_current_password_incorrect": "Password incorrect",
|
||||
"account_usage_title": "Usage",
|
||||
"account_usage_of_limit": "of {{limit}}",
|
||||
"account_usage_unlimited": "Unlimited",
|
||||
@@ -199,8 +199,8 @@
|
||||
"account_usage_basis_ip_description": "Usage stats and limits for this account are based on your IP address, so they may be shared with other users. Limits shown above are approximates based on the existing rate limits.",
|
||||
"account_delete_title": "Delete account",
|
||||
"account_delete_description": "Permanently delete your account",
|
||||
"account_delete_dialog_description": "This will permanently delete your account, including all data that is stored on the server. If you really want to proceed, please type '{{username}}' in the text box below.",
|
||||
"account_delete_dialog_label": "Type '{{username}}' to delete account",
|
||||
"account_delete_dialog_description": "This will permanently delete your account, including all data that is stored on the server. If you really want to proceed, please confirm with your password in the box below.",
|
||||
"account_delete_dialog_label": "Password",
|
||||
"account_delete_dialog_button_cancel": "Cancel",
|
||||
"account_delete_dialog_button_submit": "Permanently delete account",
|
||||
"account_delete_dialog_billing_warning": "Deleting your account also cancels your billing subscription immediately. You will not have access to the billing dashboard anymore.",
|
||||
|
||||
@@ -106,14 +106,19 @@ class AccountApi {
|
||||
return account;
|
||||
}
|
||||
|
||||
async delete() {
|
||||
async delete(password) {
|
||||
const url = accountUrl(config.base_url);
|
||||
console.log(`[AccountApi] Deleting user account ${url}`);
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: withBearerAuth({}, session.token())
|
||||
headers: withBearerAuth({}, session.token()),
|
||||
body: JSON.stringify({
|
||||
password: password
|
||||
})
|
||||
});
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
if (response.status === 400) {
|
||||
throw new IncorrectPasswordError();
|
||||
} else if (response.status === 401 || response.status === 403) {
|
||||
throw new UnauthorizedError();
|
||||
} else if (response.status !== 200) {
|
||||
throw new Error(`Unexpected server response ${response.status}`);
|
||||
@@ -132,7 +137,7 @@ class AccountApi {
|
||||
})
|
||||
});
|
||||
if (response.status === 400) {
|
||||
throw new CurrentPasswordWrongError();
|
||||
throw new IncorrectPasswordError();
|
||||
} else if (response.status === 401 || response.status === 403) {
|
||||
throw new UnauthorizedError();
|
||||
} else if (response.status !== 200) {
|
||||
@@ -397,9 +402,9 @@ export class AccountCreateLimitReachedError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export class CurrentPasswordWrongError extends Error {
|
||||
export class IncorrectPasswordError extends Error {
|
||||
constructor() {
|
||||
super("Current password incorrect");
|
||||
super("Password incorrect");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import DialogActions from "@mui/material/DialogActions";
|
||||
import routes from "./routes";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import {formatBytes, formatShortDate, formatShortDateTime} from "../app/utils";
|
||||
import accountApi, {CurrentPasswordWrongError, UnauthorizedError} from "../app/AccountApi";
|
||||
import accountApi, {IncorrectPasswordError, UnauthorizedError} from "../app/AccountApi";
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import {Pref, PrefGroup} from "./Pref";
|
||||
import db from "../app/db";
|
||||
@@ -128,7 +128,7 @@ const ChangePasswordDialog = (props) => {
|
||||
props.onClose();
|
||||
} catch (e) {
|
||||
console.log(`[Account] Error changing password`, e);
|
||||
if ((e instanceof CurrentPasswordWrongError)) {
|
||||
if ((e instanceof IncorrectPasswordError)) {
|
||||
setErrorText(t("account_basics_password_dialog_current_password_incorrect"));
|
||||
} else if ((e instanceof UnauthorizedError)) {
|
||||
session.resetAndRedirect(routes.login);
|
||||
@@ -414,26 +414,10 @@ const DeleteAccount = () => {
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleDialogCancel = () => {
|
||||
const handleDialogClose = () => {
|
||||
setDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleDialogSubmit = async () => {
|
||||
try {
|
||||
await accountApi.delete();
|
||||
await db.delete();
|
||||
setDialogOpen(false);
|
||||
console.debug(`[Account] Account deleted`);
|
||||
session.resetAndRedirect(routes.app);
|
||||
} catch (e) {
|
||||
console.log(`[Account] Error deleting account`, e);
|
||||
if ((e instanceof UnauthorizedError)) {
|
||||
session.resetAndRedirect(routes.login);
|
||||
}
|
||||
// TODO show error
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Pref title={t("account_delete_title")} description={t("account_delete_description")}>
|
||||
<div>
|
||||
@@ -444,8 +428,7 @@ const DeleteAccount = () => {
|
||||
<DeleteAccountDialog
|
||||
key={`deleteAccountDialog${dialogKey}`}
|
||||
open={dialogOpen}
|
||||
onCancel={handleDialogCancel}
|
||||
onSubmit={handleDialogSubmit}
|
||||
onClose={handleDialogClose}
|
||||
/>
|
||||
</Pref>
|
||||
)
|
||||
@@ -454,24 +437,42 @@ const DeleteAccount = () => {
|
||||
const DeleteAccountDialog = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { account } = useContext(AccountContext);
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [errorText, setErrorText] = useState("");
|
||||
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const buttonEnabled = username === session.username();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await accountApi.delete(password);
|
||||
await db.delete();
|
||||
console.debug(`[Account] Account deleted`);
|
||||
session.resetAndRedirect(routes.app);
|
||||
} catch (e) {
|
||||
console.log(`[Account] Error deleting account`, e);
|
||||
if ((e instanceof IncorrectPasswordError)) {
|
||||
setErrorText(t("account_basics_password_dialog_current_password_incorrect"));
|
||||
} else if ((e instanceof UnauthorizedError)) {
|
||||
session.resetAndRedirect(routes.login);
|
||||
}
|
||||
// TODO show error
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
|
||||
<Dialog open={props.open} onClose={props.onClose} fullScreen={fullScreen}>
|
||||
<DialogTitle>{t("account_delete_title")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body1">
|
||||
{t("account_delete_dialog_description", { username: session.username()})}
|
||||
{t("account_delete_dialog_description")}
|
||||
</Typography>
|
||||
<TextField
|
||||
margin="dense"
|
||||
id="account-delete-confirm"
|
||||
label={t("account_delete_dialog_label", { username: session.username()})}
|
||||
aria-label={t("account_delete_dialog_label", { username: session.username()})}
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={ev => setUsername(ev.target.value)}
|
||||
label={t("account_delete_dialog_label")}
|
||||
aria-label={t("account_delete_dialog_label")}
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={ev => setPassword(ev.target.value)}
|
||||
fullWidth
|
||||
variant="standard"
|
||||
/>
|
||||
@@ -479,10 +480,10 @@ const DeleteAccountDialog = (props) => {
|
||||
<Alert severity="warning" sx={{mt: 1}}>{t("account_delete_dialog_billing_warning")}</Alert>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onCancel}>{t("account_delete_dialog_button_cancel")}</Button>
|
||||
<Button onClick={props.onSubmit} color="error" disabled={!buttonEnabled}>{t("account_delete_dialog_button_submit")}</Button>
|
||||
</DialogActions>
|
||||
<DialogFooter status={errorText}>
|
||||
<Button onClick={props.onClose}>{t("account_delete_dialog_button_cancel")}</Button>
|
||||
<Button onClick={handleSubmit} color="error" disabled={password.length === 0}>{t("account_delete_dialog_button_submit")}</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user