Merge pull request #9323 from streetwriters/fix/password-changing

Fix account password changing
This commit is contained in:
Abdullah Atta
2026-02-16 14:15:39 +05:00
committed by GitHub
27 changed files with 805 additions and 583 deletions

View File

@@ -73,7 +73,15 @@ export const ChangePassword = () => {
throw new Error(strings.backupFailed() + `: ${result.error}`);
}
await db.user.changePassword(oldPassword.current, password.current);
const passwordChanged = await db.user.changePassword(
oldPassword.current,
password.current
);
if (!passwordChanged) {
throw new Error("Could not change user account password.");
}
ToastManager.show({
heading: strings.passwordChangedSuccessfully(),
type: "success",

View File

@@ -39,7 +39,7 @@ export function hideAuth(context?: AuthParams["context"]) {
initialAuthMode.current === AuthMode.welcomeLogin ||
context === "intro"
) {
Navigation.replace("FluidPanelsView", {});
Navigation.navigate("FluidPanelsView", {});
} else {
Navigation.goBack();
}

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useRef, useState } from "react";
import { TextInput, View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { ToastManager } from "../../services/event-manager";
@@ -35,9 +34,9 @@ import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
export const ForgotPassword = () => {
export const ForgotPassword = ({ userEmail }: { userEmail: string }) => {
const { colors } = useThemeColors("sheet");
const email = useRef<string>(undefined);
const email = useRef<string>(userEmail);
const emailInputRef = useRef<TextInput>(null);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
@@ -87,94 +86,76 @@ export const ForgotPassword = () => {
return (
<>
<ActionSheet
onBeforeShow={(data) => (email.current = data)}
onClose={() => {
setSent(false);
setLoading(false);
}}
onOpen={() => {
emailInputRef.current?.setNativeProps({
text: email.current
});
}}
indicatorStyle={{
width: 100
}}
gestureEnabled
id="forgotpassword_sheet"
>
{sent ? (
<View
{sent ? (
<View
style={{
padding: DefaultAppStyles.GAP,
justifyContent: "center",
alignItems: "center",
paddingBottom: 50
}}
>
<IconButton
style={{
padding: DefaultAppStyles.GAP,
justifyContent: "center",
alignItems: "center",
paddingBottom: 50
width: null,
height: null
}}
color={colors.primary.accent}
name="email"
size={50}
/>
<Heading>{strings.recoveryEmailSent()}</Heading>
<Paragraph
style={{
textAlign: "center"
}}
>
<IconButton
style={{
width: null,
height: null
}}
color={colors.primary.accent}
name="email"
size={50}
/>
<Heading>{strings.recoveryEmailSent()}</Heading>
<Paragraph
style={{
textAlign: "center"
}}
>
{strings.recoveryEmailSentDesc()}
</Paragraph>
</View>
) : (
<View
style={{
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.primary.background,
zIndex: 10,
width: "100%",
padding: DefaultAppStyles.GAP
{strings.recoveryEmailSentDesc()}
</Paragraph>
</View>
) : (
<View
style={{
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.primary.background,
zIndex: 10,
width: "100%",
padding: DefaultAppStyles.GAP
}}
>
<DialogHeader title={strings.accountRecovery()} />
<Seperator />
<Input
fwdRef={emailInputRef}
onChangeText={(value) => {
email.current = value;
}}
>
<DialogHeader title={strings.accountRecovery()} />
<Seperator />
defaultValue={email.current}
onErrorCheck={(e) => setError(e)}
returnKeyLabel={strings.next()}
returnKeyType="next"
autoComplete="email"
validationType="email"
autoCorrect={false}
autoCapitalize="none"
errorMessage={strings.emailInvalid()}
placeholder={strings.email()}
onSubmit={() => {}}
/>
<Input
fwdRef={emailInputRef}
onChangeText={(value) => {
email.current = value;
}}
defaultValue={email.current}
onErrorCheck={(e) => setError(e)}
returnKeyLabel={strings.next()}
returnKeyType="next"
autoComplete="email"
validationType="email"
autoCorrect={false}
autoCapitalize="none"
errorMessage={strings.emailInvalid()}
placeholder={strings.email()}
onSubmit={() => {}}
/>
<Button
style={{
marginTop: DefaultAppStyles.GAP_VERTICAL,
width: "100%"
}}
loading={loading}
onPress={sendRecoveryEmail}
type="accent"
title={loading ? null : strings.next()}
/>
</View>
)}
</ActionSheet>
<Button
style={{
marginTop: DefaultAppStyles.GAP_VERTICAL,
width: "100%"
}}
loading={loading}
onPress={sendRecoveryEmail}
type="accent"
title={loading ? null : strings.next()}
/>
</View>
)}
</>
);
};

View File

@@ -25,7 +25,11 @@ import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { DDS } from "../../services/device-detection";
import { eSendEvent, ToastManager } from "../../services/event-manager";
import {
eSendEvent,
presentSheet,
ToastManager
} from "../../services/event-manager";
import Navigation from "../../services/navigation";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
@@ -110,7 +114,6 @@ export const Login = ({
return (
<>
<AuthHeader />
<ForgotPassword />
<Dialog context="two_factor_verify" />
<KeyboardAwareScrollView
style={{
@@ -257,13 +260,10 @@ export const Login = ({
paddingHorizontal: 0
}}
onPress={() => {
ToastManager.show({
type: "info",
message:
"Password changing has been disabled temporarily to address some issues faced by users. It will be enabled again once the issues have resolved."
if (loading || !email.current) return;
presentSheet({
component: <ForgotPassword userEmail={email.current} />
});
// if (loading || !email.current) return;
// SheetManager.show("forgotpassword_sheet");
}}
textStyle={{
textDecorationLine: "underline"

View File

@@ -97,7 +97,7 @@ export const SessionExpired = () => {
if (db.tokenManager._isTokenExpired(res))
throw new Error("token expired");
const key = await db.user.getEncryptionKey();
const key = await db.user.getDataEncryptionKeys();
if (!key) throw new Error("No encryption key found.");
Sync.run("global", false, "full", async (complete) => {

View File

@@ -144,7 +144,7 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
(sub: User["subscription"]) => {
if (sub.plan === SubscriptionPlan.FREE) return;
if (routeParams.context === "signup") {
Navigation.replace("FluidPanelsView", {});
Navigation.navigate("FluidPanelsView", {});
} else {
Navigation.goBack();
}
@@ -182,8 +182,9 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
>
<IconButton
name="close"
color={colors.primary.icon}
onPress={() => {
Navigation.replace("FluidPanelsView", {});
Navigation.navigate("FluidPanelsView", {});
}}
/>
</View>
@@ -196,7 +197,7 @@ const PayWall = (props: NavigationProps<"PayWall">) => {
return;
}
if (routeParams.context === "signup") {
Navigation.replace("FluidPanelsView", {});
Navigation.navigate("FluidPanelsView", {});
} else {
Navigation.goBack();
}
@@ -654,7 +655,7 @@ After trying all the privacy security oriented note taking apps, for the price a
type="accent"
onPress={() => {
if (routeParams.context === "signup") {
Navigation.replace("FluidPanelsView", {});
Navigation.navigate("FluidPanelsView", {});
} else {
Navigation.goBack();
}

View File

@@ -169,7 +169,7 @@ class RecoveryKeySheet extends React.Component {
};
onOpen = async () => {
let k = await db.user.getEncryptionKey();
let k = await db.user.getMasterKey();
this.user = await db.user.getUser();
if (k) {
this.setState({

View File

@@ -1034,13 +1034,6 @@ export const useEditor = (
state.current.isRestoringState = false;
}, []);
useEffect(() => {
eSubscribeEvent(eOnLoadNote + editorId, loadNote);
return () => {
eUnSubscribeEvent(eOnLoadNote + editorId, loadNote);
};
}, [editorId, loadNote, restoreEditorState, isDefaultEditor]);
const onContentChanged = (noteId?: string) => {
if (noteId) {
lastContentChangeTime.current[noteId] = Date.now();

View File

@@ -331,17 +331,10 @@ export const settingsGroups: SettingSection[] = [
{
id: "change-password",
name: strings.changePassword(),
// type: "screen",
type: "screen",
description: strings.changePasswordDesc(),
// component: "change-password",
icon: "form-textbox-password",
modifer: () => {
ToastManager.show({
type: "info",
message:
"Password changing has been disabled temporarily to address some issues faced by users. It will be enabled again once the issues have resolved."
});
}
component: "change-password",
icon: "form-textbox-password"
},
{
id: "change-email",

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "3.3.13-beta.1",
"version": "3.3.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.3.13-beta.1",
"version": "3.3.13",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -48,15 +48,23 @@ async function initializeDatabase(persistence: DatabasePersistence) {
await useKeyStore.getState().setValue("databaseKey", databaseKey);
}
// db.host({
// API_HOST: "https://api.notesnook.com",
// AUTH_HOST: "https://auth.streetwriters.co",
// SSE_HOST: "https://events.streetwriters.co",
// ISSUES_HOST: "https://issues.streetwriters.co",
// SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
// MONOGRAPH_HOST: "https://monogr.ph",
// NOTESNOOK_HOST: "https://notesnook.com",
// ...Config.get("serverUrls", {})
// });
const base = `http://localhost`;
db.host({
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
MONOGRAPH_HOST: "https://monogr.ph",
NOTESNOOK_HOST: "https://notesnook.com",
...Config.get("serverUrls", {})
API_HOST: `${base}:5264`,
AUTH_HOST: `${base}:8264`,
SSE_HOST: `${base}:7264`,
ISSUES_HOST: `${base}:2624`,
SUBSCRIPTIONS_HOST: `${base}:9264`
});
const storage = new NNStorage(
@@ -72,7 +80,7 @@ async function initializeDatabase(persistence: DatabasePersistence) {
dialect: (name, init) =>
createDialect({
name: persistence === "memory" ? ":memory:" : name,
encrypted: true,
encrypted: persistence !== "memory",
async: !isFeatureSupported("opfs"),
init,
multiTab
@@ -87,7 +95,10 @@ async function initializeDatabase(persistence: DatabasePersistence) {
synchronous: "normal",
pageSize: 8192,
cacheSize: -32000,
password: Buffer.from(databaseKey).toString("hex"),
password:
persistence === "memory"
? undefined
: Buffer.from(databaseKey).toString("hex"),
skipInitialization: !IS_DESKTOP_APP && multiTab
},
storage: storage,

View File

@@ -167,6 +167,8 @@ class _SQLiteWorker {
sql: string,
parameters?: SQLiteCompatibleType[]
): Promise<QueryResult<R>> {
if (!this.encrypted && !this.initialized) await this.initialize();
if (this.encrypted && !sql.startsWith("PRAGMA key")) {
await this.waitForDatabase();
}

View File

@@ -38,7 +38,7 @@ type RecoveryKeyDialogProps = BaseDialogProps<false>;
export const RecoveryKeyDialog = DialogManager.register(
function RecoveryKeyDialog(props: RecoveryKeyDialogProps) {
const key = usePromise(() =>
db.user.getEncryptionKey().then((key) => key?.key)
db.user.getMasterKey().then((key) => key?.key)
);
const [copyText, setCopyText] = useState("Copy to clipboard");

View File

@@ -27,7 +27,6 @@ import { RecoveryCodesDialog } from "../mfa/recovery-code-dialog";
import { MultifactorDialog } from "../mfa/multi-factor-dialog";
import { RecoveryKeyDialog } from "../recovery-key-dialog";
import { strings } from "@notesnook/intl";
import { ConfirmDialog } from "../confirm";
export const AuthenticationSettings: SettingsGroup[] = [
{
@@ -46,39 +45,38 @@ export const AuthenticationSettings: SettingsGroup[] = [
title: strings.changePassword(),
variant: "secondary",
action: async () => {
ConfirmDialog.show({
title: "Password changing has been disabled temporarily",
message:
"Password changing has been disabled temporarily to address some issues faced by users. It will be enabled again once the issues have resolved.",
positiveButtonText: "Ok"
const result = await showPasswordDialog({
title: strings.changePassword(),
message: strings.changePasswordDesc(),
inputs: {
oldPassword: {
label: strings.oldPassword(),
autoComplete: "current-password"
},
newPassword: {
label: strings.newPassword(),
autoComplete: "new-password"
}
},
validate: async ({ oldPassword, newPassword }) => {
try {
if (!(await createBackup({ noVerify: true }))) return false;
return (
(await db.user.changePassword(
oldPassword,
newPassword
)) || false
);
} catch (e) {
console.error(e);
return false;
}
}
});
return;
// const result = await showPasswordDialog({
// title: strings.changePassword(),
// message: strings.changePasswordDesc(),
// inputs: {
// oldPassword: {
// label: strings.oldPassword(),
// autoComplete: "current-password"
// },
// newPassword: {
// label: strings.newPassword(),
// autoComplete: "new-password"
// }
// },
// validate: async ({ oldPassword, newPassword }) => {
// if (!(await createBackup())) return false;
// await db.user.clearSessions();
// return (
// (await db.user.changePassword(oldPassword, newPassword)) ||
// false
// );
// }
// });
// if (result) {
// showToast("success", strings.passwordChangedSuccessfully());
// await RecoveryKeyDialog.show({});
// }
if (result) {
showToast("success", strings.passwordChangedSuccessfully());
await RecoveryKeyDialog.show({});
}
}
}
]

View File

@@ -146,13 +146,19 @@ class KeyStore extends BaseStore<KeyStore> {
activeCredentials = () => this.get().credentials.filter((c) => c.active);
init = async () => {
init = async (
config: { persistence: "memory" | "db" } = { persistence: "db" }
) => {
this.#metadataStore =
isFeatureSupported("indexedDB") && isFeatureSupported("clonableCryptoKey")
isFeatureSupported("indexedDB") &&
isFeatureSupported("clonableCryptoKey") &&
config.persistence !== "memory"
? new IndexedDBKVStore(`${this.dbName}-metadata`, "metadata")
: new MemoryKVStore();
this.#secretStore =
isFeatureSupported("indexedDB") && isFeatureSupported("clonableCryptoKey")
isFeatureSupported("indexedDB") &&
isFeatureSupported("clonableCryptoKey") &&
config.persistence !== "memory"
? new IndexedDBKVStore(`${this.dbName}-secrets`, "secrets")
: new MemoryKVStore();

View File

@@ -54,7 +54,12 @@ export async function startApp(children?: React.ReactNode) {
try {
const { Component, props, path } = await init();
await useKeyStore.getState().init();
const persistence =
(path !== "/sessionexpired" && path !== "/account/recovery") ||
Config.get("sessionExpired", false)
? "db"
: "memory";
await useKeyStore.getState().init({ persistence });
root.render(
<>
@@ -70,6 +75,7 @@ export async function startApp(children?: React.ReactNode) {
Component={Component}
path={path}
routeProps={props}
persistence={persistence}
/>
</AppLock>
{children}
@@ -96,9 +102,10 @@ function RouteWrapper(props: {
Component: (props: AuthProps) => JSX.Element;
path: Routes;
routeProps: AuthProps | null;
persistence?: "db" | "memory";
}) {
const [isMigrating, setIsMigrating] = useState(false);
const { Component, path, routeProps } = props;
const { Component, path, routeProps, persistence } = props;
useEffect(() => {
EV.subscribe(EVENTS.migrationStarted, (name) =>
@@ -109,12 +116,8 @@ function RouteWrapper(props: {
const result = usePromise(async () => {
performance.mark("load:database");
await loadDatabase(
path !== "/sessionexpired" || Config.get("sessionExpired", false)
? "db"
: "memory"
);
}, [path]);
await loadDatabase(persistence);
}, [path, persistence]);
if (result.status === "rejected") {
throw result.reason instanceof Error

View File

@@ -34,11 +34,11 @@ export function isTransferableStreamsSupported() {
controller.close();
}
});
window.postMessage(readable, [readable]);
window.postMessage(readable, window.location.origin, [readable]);
FEATURE_CHECKS.transferableStreams = true;
return true;
} catch {
console.log("Transferable streams not supported");
} catch (e) {
console.log("Transferable streams not supported", e);
FEATURE_CHECKS.transferableStreams = false;
return false;
}

View File

@@ -40,7 +40,7 @@ import AuthContainer from "../components/auth-container";
import { useTimer } from "../hooks/use-timer";
import { ErrorText } from "../components/error-text";
import { AuthenticatorType, User } from "@notesnook/core";
import { ConfirmDialog, showLogoutConfirmation } from "../dialogs/confirm";
import { showLogoutConfirmation } from "../dialogs/confirm";
import { TaskManager } from "../common/task-manager";
import { strings } from "@notesnook/intl";
import { ScrollContainer } from "@notesnook/ui";
@@ -339,16 +339,7 @@ function LoginPassword(props: BaseAuthComponentProps<"login:password">) {
type="button"
mt={2}
variant="anchor"
onClick={() => {
ConfirmDialog.show({
title: "Password changing has been disabled temporarily",
message:
"Password changing has been disabled temporarily to address some issues faced by users. It will be enabled again once the issues have resolved.",
positiveButtonText: "Ok"
});
return;
// navigate("recover", { email: formData.email })
}}
onClick={() => navigate("recover", { email: formData.email })}
sx={{ color: "paragraph", alignSelf: "end" }}
>
{strings.forgotPassword()}
@@ -508,16 +499,7 @@ function SessionExpiry(props: BaseAuthComponentProps<"sessionExpiry">) {
type="button"
mt={2}
variant="anchor"
onClick={() => {
ConfirmDialog.show({
title: "Password changing has been disabled temporarily",
message:
"Password changing has been disabled temporarily to address some issues faced by users. It will be enabled again once the issues have resolved.",
positiveButtonText: "Ok"
});
return;
// user && navigate("recover", { email: user.email })
}}
onClick={() => user && navigate("recover", { email: user.email })}
sx={{ color: "paragraph", alignSelf: "end" }}
>
{strings.forgotPassword()}

View File

@@ -25,7 +25,6 @@ import { Loader } from "../components/loader";
import { showToast } from "../utils/toast";
import AuthContainer from "../components/auth-container";
import { AuthField, SubmitButton } from "./auth";
import { createBackup, restoreBackupFile, selectBackupFile } from "../common";
import Config from "../utils/config";
import { ErrorText } from "../components/error-text";
import { EVENTS, User } from "@notesnook/core";
@@ -33,18 +32,14 @@ import { RecoveryKeyDialog } from "../dialogs/recovery-key-dialog";
import { strings } from "@notesnook/intl";
import { useKeyStore } from "../interfaces/key-store";
type RecoveryMethodType = "key" | "backup" | "reset";
type RecoveryMethodType = "key" | "reset";
type RecoveryMethodsFormData = Record<string, unknown>;
type RecoveryKeyFormData = {
recoveryKey: string;
};
type BackupFileFormData = {
backupFile: File;
};
type NewPasswordFormData = BackupFileFormData & {
type NewPasswordFormData = {
userResetRequired?: boolean;
password: string;
confirmPassword: string;
@@ -53,9 +48,7 @@ type NewPasswordFormData = BackupFileFormData & {
type RecoveryFormData = {
methods: RecoveryMethodsFormData;
"method:key": RecoveryKeyFormData;
"method:backup": BackupFileFormData;
"method:reset": NewPasswordFormData;
backup: RecoveryMethodsFormData;
new: NewPasswordFormData;
final: RecoveryMethodsFormData;
};
@@ -73,9 +66,7 @@ type BaseRecoveryComponentProps<TRoute extends RecoveryRoutes> = {
type RecoveryRoutes =
| "methods"
| "method:key"
| "method:backup"
| "method:reset"
| "backup"
| "new"
| "final";
type RecoveryProps = { route: RecoveryRoutes };
@@ -92,10 +83,6 @@ function getRouteComponent<TRoute extends RecoveryRoutes>(
return RecoveryMethods as RecoveryComponent<TRoute>;
case "method:key":
return RecoveryKeyMethod as RecoveryComponent<TRoute>;
case "method:backup":
return BackupFileMethod as RecoveryComponent<TRoute>;
case "backup":
return BackupData as RecoveryComponent<TRoute>;
case "method:reset":
case "new":
return NewPassword as RecoveryComponent<TRoute>;
@@ -108,9 +95,7 @@ function getRouteComponent<TRoute extends RecoveryRoutes>(
const routePaths: Record<RecoveryRoutes, string> = {
methods: "/account/recovery/methods",
"method:key": "/account/recovery/method/key",
"method:backup": "/account/recovery/method/backup",
"method:reset": "/account/recovery/method/reset",
backup: "/account/recovery/backup",
new: "/account/recovery/new",
final: "/account/recovery/final"
};
@@ -240,12 +225,6 @@ const recoveryMethods: RecoveryMethod[] = [
title: () => strings.recoveryKeyMethod(),
description: () => strings.recoveryKeyMethodDesc()
},
{
type: "backup",
testId: "step-backup",
title: () => strings.backupFileMethod(),
description: () => strings.backupFileMethodDesc()
},
{
type: "reset",
testId: "step-reset-account",
@@ -356,8 +335,7 @@ function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
await useKeyStore
.getState()
.setValue("userEncryptionKey", form.recoveryKey);
await db.sync({ type: "fetch", force: true });
navigate("backup");
navigate("new", {});
}}
>
<AuthField
@@ -383,86 +361,6 @@ function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
);
}
function BackupFileMethod(props: BaseRecoveryComponentProps<"method:backup">) {
const { navigate } = props;
const [backupFile, setBackupFile] =
useState<BackupFileFormData["backupFile"]>();
useEffect(() => {
if (!backupFile) return;
const backupFileInput = document.getElementById("backupFile");
if (!(backupFileInput instanceof HTMLInputElement)) return;
backupFileInput.value = backupFile?.name;
}, [backupFile]);
return (
<RecoveryForm
testId="step-backup-file"
type="method:backup"
title={strings.accountRecovery()}
subtitle={
<ErrorText
sx={{ fontSize: "body" }}
error={strings.backupFileRecoveryError()}
/>
}
onSubmit={async () => {
navigate("new", { backupFile, userResetRequired: true });
}}
>
<AuthField
id="backupFile"
type="text"
label={strings.selectBackupFile()}
helpText={strings.backupFileHelpText()}
autoComplete="none"
autoFocus
disabled
action={{
component: <Text variant={"body"}>{strings.browse()}</Text>,
onClick: async () => {
setBackupFile(await selectBackupFile());
}
}}
/>
<SubmitButton text={strings.startAccountRecovery()} />
<Button
type="button"
mt={4}
variant={"anchor"}
onClick={() => navigate("methods")}
sx={{ color: "paragraph" }}
>
{strings.dontHaveBackupFile()}
</Button>
</RecoveryForm>
);
}
function BackupData(props: BaseRecoveryComponentProps<"backup">) {
const { navigate } = props;
return (
<RecoveryForm
testId="step-backup-data"
type="backup"
title={strings.backupYourData()}
subtitle={strings.backupYourDataDesc()}
loading={{
title: strings.backingUpData() + "...",
subtitle: strings.backingUpDataWait()
}}
onSubmit={async () => {
await createBackup({ rescueMode: true, mode: "full" });
navigate("new");
}}
>
<SubmitButton text={strings.downloadBackupFile()} />
</RecoveryForm>
);
}
function NewPassword(props: BaseRecoveryComponentProps<"new">) {
const { navigate, formData } = props;
const [progress, setProgress] = useState(0);
@@ -498,11 +396,6 @@ function NewPassword(props: BaseRecoveryComponentProps<"new">) {
if (!(await db.user.resetPassword(form.password)))
throw new Error("Could not reset account password.");
if (formData?.backupFile) {
await restoreBackupFile(formData?.backupFile);
await db.sync({ type: "full", force: true });
}
navigate("final");
}}
>