|
|
|
@ -2,6 +2,7 @@
|
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
|
|
import * as React from "react";
|
|
|
|
|
import * as Etebase from "etebase";
|
|
|
|
|
import { useSelector, useDispatch } from "react-redux";
|
|
|
|
|
|
|
|
|
|
import Select from "@material-ui/core/Select";
|
|
|
|
@ -13,12 +14,17 @@ import Switch from "@material-ui/core/Switch";
|
|
|
|
|
import InputLabel from "@material-ui/core/InputLabel";
|
|
|
|
|
|
|
|
|
|
import { StoreState } from "../store";
|
|
|
|
|
import { setSettings } from "../store/actions";
|
|
|
|
|
import { setSettings, login, pushMessage } from "../store/actions";
|
|
|
|
|
|
|
|
|
|
import Container from "../widgets/Container";
|
|
|
|
|
import AppBarOverride from "../widgets/AppBarOverride";
|
|
|
|
|
import PrettyFingerprint from "../widgets/PrettyFingerprint";
|
|
|
|
|
import { useCredentials } from "../credentials";
|
|
|
|
|
import { Button } from "@material-ui/core";
|
|
|
|
|
import ConfirmationDialog from "../widgets/ConfirmationDialog";
|
|
|
|
|
import PasswordField from "../widgets/PasswordField";
|
|
|
|
|
import Alert from "@material-ui/lab/Alert";
|
|
|
|
|
import { PASSWORD_MIN_LENGTH, startTask, enforcePasswordRules } from "../helpers";
|
|
|
|
|
|
|
|
|
|
function SecurityFingerprint() {
|
|
|
|
|
const etebase = useCredentials()!;
|
|
|
|
@ -35,6 +41,133 @@ function SecurityFingerprint() {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ChangePasswordFormErrors {
|
|
|
|
|
oldPassword?: string;
|
|
|
|
|
newPassword?: string;
|
|
|
|
|
|
|
|
|
|
general?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ChangePassword() {
|
|
|
|
|
const etebase = useCredentials()!;
|
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
|
const [showDialog, setShowDialog] = React.useState(false);
|
|
|
|
|
const [oldPassword, setOldPassword] = React.useState("");
|
|
|
|
|
const [newPassword, setNewPassword] = React.useState("");
|
|
|
|
|
const [errors, setErrors] = React.useState<ChangePasswordFormErrors>({});
|
|
|
|
|
|
|
|
|
|
const styles = {
|
|
|
|
|
infoAlert: {
|
|
|
|
|
marginTop: 20,
|
|
|
|
|
},
|
|
|
|
|
textField: {
|
|
|
|
|
marginTop: 20,
|
|
|
|
|
width: "18em",
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function handleInputChange(func: (value: string) => void) {
|
|
|
|
|
return (event: React.ChangeEvent<any>) => {
|
|
|
|
|
func(event.target.value);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onChangePassword() {
|
|
|
|
|
try {
|
|
|
|
|
const fieldNotEmpty = "Password can't be empty.";
|
|
|
|
|
const errors: ChangePasswordFormErrors = {};
|
|
|
|
|
if (!oldPassword) {
|
|
|
|
|
errors.oldPassword = fieldNotEmpty;
|
|
|
|
|
}
|
|
|
|
|
if (!newPassword) {
|
|
|
|
|
errors.newPassword = fieldNotEmpty;
|
|
|
|
|
} else {
|
|
|
|
|
const passwordRulesError = enforcePasswordRules(newPassword);
|
|
|
|
|
if (passwordRulesError) {
|
|
|
|
|
errors.newPassword = passwordRulesError;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setErrors(errors);
|
|
|
|
|
if (Object.keys(errors).length > 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await startTask(async () => {
|
|
|
|
|
const serverUrl = etebase.serverUrl;
|
|
|
|
|
const username = etebase.user.username;
|
|
|
|
|
try {
|
|
|
|
|
const etebase = await Etebase.Account.login(username, oldPassword, serverUrl);
|
|
|
|
|
await etebase.logout();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (e instanceof Etebase.UnauthorizedError) {
|
|
|
|
|
setErrors({ oldPassword: "Error: wrong encryption password." });
|
|
|
|
|
} else {
|
|
|
|
|
setErrors({ oldPassword: e.toString() });
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await etebase.changePassword(newPassword);
|
|
|
|
|
dispatch(login(etebase));
|
|
|
|
|
dispatch(pushMessage({ message: "Password successfully changed.", severity: "success" }));
|
|
|
|
|
setShowDialog(false);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
setErrors({ newPassword: e.toString() });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} finally {
|
|
|
|
|
// Cleanup
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<p>
|
|
|
|
|
Change your password by clicking here;
|
|
|
|
|
</p>
|
|
|
|
|
<Button color="secondary" variant="contained" onClick={() => setShowDialog(true)}>
|
|
|
|
|
Change Password
|
|
|
|
|
</Button>
|
|
|
|
|
<ConfirmationDialog
|
|
|
|
|
title="Change Password"
|
|
|
|
|
key={showDialog}
|
|
|
|
|
open={showDialog}
|
|
|
|
|
onOk={onChangePassword}
|
|
|
|
|
onCancel={() => setShowDialog(false)}
|
|
|
|
|
>
|
|
|
|
|
<PasswordField
|
|
|
|
|
style={styles.textField}
|
|
|
|
|
error={!!errors.oldPassword}
|
|
|
|
|
helperText={errors.oldPassword}
|
|
|
|
|
label="Current Password"
|
|
|
|
|
value={oldPassword}
|
|
|
|
|
onChange={handleInputChange(setOldPassword)}
|
|
|
|
|
/>
|
|
|
|
|
<PasswordField
|
|
|
|
|
style={styles.textField}
|
|
|
|
|
error={!!errors.newPassword}
|
|
|
|
|
helperText={errors.newPassword}
|
|
|
|
|
label="New Password"
|
|
|
|
|
inputProps={{
|
|
|
|
|
minLength: PASSWORD_MIN_LENGTH,
|
|
|
|
|
}}
|
|
|
|
|
value={newPassword}
|
|
|
|
|
onChange={handleInputChange(setNewPassword)}
|
|
|
|
|
/>
|
|
|
|
|
{errors.general && (
|
|
|
|
|
<Alert severity="error" style={styles.infoAlert}>{errors.general}</Alert>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Alert severity="warning" style={styles.infoAlert}>
|
|
|
|
|
Please make sure you remember your password, as it <em>can't</em> be recovered if lost!
|
|
|
|
|
</Alert>
|
|
|
|
|
</ConfirmationDialog>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default React.memo(function Settings() {
|
|
|
|
|
const etebase = useCredentials();
|
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
@ -58,6 +191,9 @@ export default React.memo(function Settings() {
|
|
|
|
|
<h1>Account</h1>
|
|
|
|
|
<h2>Security Fingerprint</h2>
|
|
|
|
|
<SecurityFingerprint />
|
|
|
|
|
|
|
|
|
|
<h2>Password</h2>
|
|
|
|
|
<ChangePassword />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|