mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
new recovery package in common
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.cryptomator.common.vaults.VaultState.Value;
|
||||
|
||||
public final class BackupRestorer {
|
||||
|
||||
private BackupRestorer() {}
|
||||
|
||||
public static boolean restoreIfPresent(Path vaultPath, Value vaultState) {
|
||||
Path targetFile;
|
||||
switch (vaultState) {
|
||||
case VAULT_CONFIG_MISSING -> targetFile = vaultPath.resolve("vault.cryptomator");
|
||||
case MASTERKEY_MISSING -> targetFile = vaultPath.resolve("masterkey.cryptomator");
|
||||
default -> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try (Stream<Path> files = Files.list(vaultPath)) {
|
||||
return files
|
||||
.filter(file -> isValidBackupFileForState(file.getFileName().toString(), vaultState))
|
||||
.findFirst()
|
||||
.map(backupFile -> copyBackupFile(backupFile, targetFile))
|
||||
.orElse(false);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidBackupFileForState(String fileName, Value vaultState) {
|
||||
return switch (vaultState) {
|
||||
case VAULT_CONFIG_MISSING -> fileName.startsWith("vault.cryptomator") && fileName.endsWith(".bkup");
|
||||
case MASTERKEY_MISSING -> fileName.startsWith("masterkey.cryptomator") && fileName.endsWith(".bkup");
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean copyBackupFile(Path backupFile, Path configPath) {
|
||||
try {
|
||||
Files.copy(backupFile, configPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptolib.api.*;
|
||||
|
||||
import static org.cryptomator.common.Constants.DEFAULT_KEY_ID;
|
||||
|
||||
public final class CryptoFsInitializer {
|
||||
|
||||
private CryptoFsInitializer() {}
|
||||
|
||||
public static void init(Path recoveryPath,
|
||||
Masterkey masterkey,
|
||||
IntegerProperty shorteningThreshold,
|
||||
CryptorProvider.Scheme scheme) throws IOException, CryptoException {
|
||||
|
||||
MasterkeyLoader loader = ignored -> masterkey.copy();
|
||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties //
|
||||
.cryptoFileSystemProperties() //
|
||||
.withCipherCombo(scheme) //
|
||||
.withKeyLoader(loader) //
|
||||
.withShorteningThreshold(shorteningThreshold.get()) //
|
||||
.build();
|
||||
CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.*;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME;
|
||||
|
||||
public final class MasterkeyService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyService.class);
|
||||
|
||||
private MasterkeyService() {}
|
||||
|
||||
public static void recoverFromRecoveryKey(String recoveryKey, RecoveryKeyFactory recoveryKeyFactory, Path recoveryPath, CharSequence newPassword) throws IOException {
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey, newPassword);
|
||||
}
|
||||
|
||||
public static Masterkey load(MasterkeyFileAccess masterkeyFileAccess, Path masterkeyFilePath, CharSequence password) throws IOException {
|
||||
return masterkeyFileAccess.load(masterkeyFilePath, password);
|
||||
}
|
||||
|
||||
public static Optional<CryptorProvider.Scheme> validateRecoveryKeyAndDetectCombo(RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, MasterkeyFileAccess masterkeyFileAccess, AtomicBoolean illegalArgumentExceptionOccurred) {
|
||||
|
||||
var tmpPass = UUID.randomUUID().toString();
|
||||
try(RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||
var tempRecoveryPath = recoveryDirectory.getRecoveryPath();
|
||||
recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, tempRecoveryPath, tmpPass);
|
||||
var masterkeyFilePath = tempRecoveryPath.resolve(MASTERKEY_FILENAME);
|
||||
|
||||
try (Masterkey mk = load(masterkeyFileAccess, masterkeyFilePath, tmpPass)) {
|
||||
return detect(mk.getEncoded(), vault.getPath());
|
||||
} catch (IOException | CryptoException e) {
|
||||
LOG.info("Recovery key validation failed", e);
|
||||
return Optional.empty();
|
||||
} catch (IllegalArgumentException e) {
|
||||
illegalArgumentExceptionOccurred.set(true);
|
||||
return Optional.empty();
|
||||
}
|
||||
} catch (IOException | CryptoException e) {
|
||||
LOG.info("Recovery key validation failed");
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.info("Recovery key has an illegal argument");
|
||||
illegalArgumentExceptionOccurred.set(true);
|
||||
}
|
||||
return Optional.empty();
|
||||
|
||||
}
|
||||
public static Optional<CryptorProvider.Scheme> detect(byte[] masterkey, Path vaultPath) {
|
||||
try (Stream<Path> paths = Files.walk(vaultPath.resolve(DATA_DIR_NAME))) {
|
||||
Path c9rFile = paths.filter(p -> p.toString().endsWith(".c9r"))
|
||||
.findFirst().orElse(null);
|
||||
if (c9rFile == null) {
|
||||
LOG.info("No *.c9r file found in {}", vaultPath);
|
||||
return Optional.empty();
|
||||
}
|
||||
return determineScheme(c9rFile, masterkey); // jetzt auch ein Optional
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Failed to inspect vault", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<CryptorProvider.Scheme> determineScheme(Path c9rFile, byte[] masterkey) {
|
||||
try {
|
||||
ByteBuffer header = ByteBuffer.wrap(Files.readAllBytes(c9rFile));
|
||||
return Arrays.stream(CryptorProvider.Scheme.values())
|
||||
.filter(s -> tryDecrypt(header, new Masterkey(masterkey), s))
|
||||
.findFirst();
|
||||
} catch (IOException e) {
|
||||
LOG.info("Failed to decrypt .c9r file", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean tryDecrypt(ByteBuffer header, Masterkey masterkey, CryptorProvider.Scheme scheme) {
|
||||
try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey, SecureRandom.getInstanceStrong())) {
|
||||
cryptor.fileHeaderCryptor().decryptHeader(header.duplicate());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
public enum RecoveryActionType {
|
||||
RESTORE_VAULT_CONFIG,
|
||||
RESTORE_MASTERKEY,
|
||||
RESET_PASSWORD,
|
||||
SHOW_KEY,
|
||||
CONVERT_VAULT
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.cryptomator.common.recovery;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
|
||||
public final class RecoveryDirectory implements AutoCloseable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryDirectory.class);
|
||||
|
||||
private final Path recoveryPath;
|
||||
private final Path vaultPath;
|
||||
|
||||
|
||||
private static Path addR(Path p){
|
||||
return p.resolve("r");
|
||||
}
|
||||
|
||||
private RecoveryDirectory(Path vaultPath) {
|
||||
this.vaultPath = vaultPath;
|
||||
this.recoveryPath = addR(vaultPath);
|
||||
}
|
||||
|
||||
public static RecoveryDirectory create(Path vaultPath) throws IOException {
|
||||
//TODO: Files.createTmpDirectory Doku lesen und ggf nutzen
|
||||
Path recovery = addR(vaultPath);
|
||||
Files.createDirectory(recovery);
|
||||
return new RecoveryDirectory(vaultPath);
|
||||
}
|
||||
|
||||
public void moveRecoveredFiles() throws IOException {
|
||||
Files.move(recoveryPath.resolve(MASTERKEY_FILENAME), vaultPath.resolve(MASTERKEY_FILENAME), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.move(recoveryPath.resolve(VAULTCONFIG_FILENAME), vaultPath.resolve(VAULTCONFIG_FILENAME)); //TODO: ? StandardCopyOption.REPLACE_EXISTING
|
||||
}
|
||||
|
||||
private void deleteRecoveryDirectory() {
|
||||
try (var paths = Files.walk(recoveryPath)) {
|
||||
paths.sorted(Comparator.reverseOrder()).forEach(p -> { //TODO: wieso reverseOrder
|
||||
try {
|
||||
Files.delete(p);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Unable to delete {}. Please delete it manually.", p);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to clean up recovery directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
deleteRecoveryDirectory();
|
||||
}
|
||||
|
||||
public Path getRecoveryPath() {
|
||||
return recoveryPath;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,9 +26,11 @@ import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.MASTERKEY_MISSING;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.PROCESSING;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.recovery.BackupRestorer;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
@@ -146,45 +148,35 @@ public class VaultListManager {
|
||||
return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault();
|
||||
}
|
||||
}
|
||||
|
||||
public static VaultState.Value redetermineVaultState(Vault vault) {
|
||||
VaultState state = vault.stateProperty();
|
||||
VaultState.Value previousState = state.getValue();
|
||||
return switch (previousState) {
|
||||
case LOCKED, NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, MASTERKEY_MISSING -> {
|
||||
try {
|
||||
var determinedState = determineVaultState(vault.getPath(), vault.getVaultSettings());
|
||||
if (determinedState == MASTERKEY_MISSING) {
|
||||
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
|
||||
if (KeyLoadingStrategy.isHubVault(vaultScheme)) {
|
||||
determinedState = LOCKED;
|
||||
}
|
||||
}
|
||||
if (determinedState == LOCKED) {
|
||||
vault.getVaultConfigCache().reloadConfig();
|
||||
}
|
||||
state.set(determinedState);
|
||||
yield determinedState;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to determine vault state for {}", vault.getPath(), e);
|
||||
state.set(ERROR);
|
||||
vault.setLastKnownException(e);
|
||||
yield ERROR;
|
||||
VaultState state = vault.stateProperty();
|
||||
VaultState.Value previous = state.getValue();
|
||||
|
||||
if (previous.equals(UNLOCKED)||previous.equals(PROCESSING)) {
|
||||
return previous;
|
||||
}
|
||||
|
||||
try {
|
||||
VaultState.Value determined = determineVaultState(vault.getPath(), vault.getVaultSettings());
|
||||
|
||||
if (determined == MASTERKEY_MISSING) {
|
||||
if (KeyLoadingStrategy.isHubVault(vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme())) {
|
||||
determined = LOCKED;
|
||||
}
|
||||
}
|
||||
case ERROR -> {
|
||||
try {
|
||||
var determinedState = determineVaultState(vault.getPath(), vault.getVaultSettings());
|
||||
state.set(determinedState);
|
||||
yield determinedState;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to redetermine vault state for {}", vault.getPath(), e);
|
||||
vault.setLastKnownException(e);
|
||||
yield ERROR;
|
||||
}
|
||||
|
||||
if (determined == LOCKED) {
|
||||
vault.getVaultConfigCache().reloadConfig();
|
||||
}
|
||||
case UNLOCKED, PROCESSING -> previousState;
|
||||
};
|
||||
|
||||
state.set(determined);
|
||||
return determined;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to (re)determine vault state for {}", vault.getPath(), e);
|
||||
vault.setLastKnownException(e);
|
||||
state.set(ERROR);
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private static VaultState.Value determineVaultState(Path pathToVault, VaultSettings vaultSettings) throws IOException {
|
||||
@@ -195,9 +187,12 @@ public class VaultListManager {
|
||||
return VaultState.Value.MISSING;
|
||||
}
|
||||
|
||||
boolean vaultConfigRestored = Files.notExists(pathToVaultConfig) && RecoverUtil.restoreBackupIfAvailable(pathToVaultConfig, VaultState.Value.VAULT_CONFIG_MISSING);
|
||||
boolean vaultConfigRestored = Files.notExists(pathToVaultConfig)
|
||||
&& BackupRestorer.restoreIfPresent(pathToVaultConfig.getParent(), VaultState.Value.VAULT_CONFIG_MISSING);
|
||||
|
||||
boolean masterkeyRestored = Files.notExists(pathToMasterkey) && KeyLoadingStrategy.isMasterkeyFileVault(vaultSettings.lastKnownKeyLoader.get()) && RecoverUtil.restoreBackupIfAvailable(pathToMasterkey, VaultState.Value.MASTERKEY_MISSING);
|
||||
boolean masterkeyRestored = Files.notExists(pathToMasterkey)
|
||||
&& KeyLoadingStrategy.isMasterkeyFileVault(vaultSettings.lastKnownKeyLoader.get())
|
||||
&& BackupRestorer.restoreIfPresent(pathToMasterkey.getParent(), VaultState.Value.MASTERKEY_MISSING);
|
||||
|
||||
if (vaultConfigRestored || masterkeyRestored) {
|
||||
return LOCKED;
|
||||
|
||||
@@ -22,12 +22,15 @@ import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultComponent;
|
||||
import org.cryptomator.common.vaults.VaultConfigCache;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.integrations.mount.MountService;
|
||||
import org.cryptomator.integrations.uiappearance.Theme;
|
||||
@@ -131,14 +134,50 @@ public class ChooseExistingVaultController implements FxController {
|
||||
@FXML
|
||||
public void restoreVaultConfigWithRecoveryKey() {
|
||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||
Optional<Vault> optionalVault = RecoverUtil.checkAndPrepareVaultFromDirectory(directoryChooser, window, dialogs, vaultComponentFactory, mountServices);
|
||||
|
||||
File selectedDirectory;
|
||||
do {
|
||||
selectedDirectory = directoryChooser.showDialog(window);
|
||||
boolean hasSubfolderD = new File(selectedDirectory, "d").isDirectory();
|
||||
|
||||
if (!hasSubfolderD) {
|
||||
dialogs.prepareNoDDirectorySelectedDialog(window).build().showAndWait();
|
||||
selectedDirectory = null;
|
||||
}
|
||||
} while (selectedDirectory == null);
|
||||
|
||||
Optional<Vault> optionalVault = prepareVault(selectedDirectory,vaultComponentFactory,
|
||||
mountServices);
|
||||
//TODO: optional raus, und mit error dialog arbeiten (UI kram in UI package!) hier nur fehler werfen
|
||||
optionalVault.ifPresent(vault -> {
|
||||
ObjectProperty<RecoverUtil.Type> recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.RESTORE_VAULT_CONFIG);
|
||||
ObjectProperty<RecoveryActionType> recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG);
|
||||
recoveryKeyWindow.create(vault, window, recoverTypeProperty).showIsHubVaultDialogWindow();
|
||||
});
|
||||
}
|
||||
|
||||
public static Optional<Vault> prepareVault(File selectedDirectory, VaultComponent.Factory vaultComponentFactory, List<MountService> mountServices) {
|
||||
|
||||
Path selectedPath = selectedDirectory.toPath();
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||
vaultSettings.path.set(selectedPath);
|
||||
if (selectedPath.getFileName() != null) {
|
||||
vaultSettings.displayName.set(selectedPath.getFileName().toString());
|
||||
} else {
|
||||
vaultSettings.displayName.set("defaultVaultName");
|
||||
}
|
||||
|
||||
var wrapper = new VaultConfigCache(vaultSettings);
|
||||
Vault vault = vaultComponentFactory.create(vaultSettings, wrapper, VAULT_CONFIG_MISSING, null).vault(); //TODO: VAULT_CONFIG_MISSING nicht sicher, stand nochmal überprüfen
|
||||
|
||||
//due to https://github.com/cryptomator/cryptomator/issues/2880#issuecomment-1680313498
|
||||
var nameOfWinfspLocalMounter = "org.cryptomator.frontend.fuse.mount.WinFspMountProvider";
|
||||
if (SystemUtils.IS_OS_WINDOWS && vaultSettings.path.get().toString().contains("Dropbox") && mountServices.stream().anyMatch(s -> s.getClass().getName().equals(nameOfWinfspLocalMounter))) {
|
||||
vaultSettings.mountService.setValue(nameOfWinfspLocalMounter);
|
||||
}
|
||||
|
||||
return Optional.of(vault);
|
||||
}
|
||||
|
||||
/* Getter */
|
||||
|
||||
public ObservableValue<Image> screenshotProperty() {
|
||||
|
||||
@@ -4,7 +4,7 @@ import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
@@ -122,7 +122,7 @@ abstract class ConvertVaultModule {
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyValidateController.class)
|
||||
static FxController bindRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
|
||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, new SimpleObjectProperty<>(RecoverUtil.Type.CONVERT_VAULT), null, null);
|
||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, new SimpleObjectProperty<>(RecoveryActionType.CONVERT_VAULT), null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -61,14 +61,14 @@ public class VaultDetailMissingVaultController implements FxController {
|
||||
dialogs.prepareContactHubAdmin(window).build().showAndWait();
|
||||
}
|
||||
else {
|
||||
ObjectProperty<RecoverUtil.Type> recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.RESTORE_VAULT_CONFIG);
|
||||
ObjectProperty<RecoveryActionType> recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG);
|
||||
recoveryKeyWindow.create(vault.get(), window, recoverTypeProperty).showIsHubVaultDialogWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void restoreMasterkey() {
|
||||
ObjectProperty<RecoverUtil.Type> recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.RESTORE_MASTERKEY);
|
||||
ObjectProperty<RecoveryActionType> recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.RESTORE_MASTERKEY);
|
||||
recoveryKeyWindow.create(vault.get(), window, recoverTypeProperty).showRecoveryKeyRecoverWindow();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.cryptomator.ui.recoverykey;
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
@@ -55,7 +55,7 @@ public interface RecoveryKeyComponent {
|
||||
|
||||
RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, //
|
||||
@BindsInstance @Named("keyRecoveryOwner") Stage owner, //
|
||||
@BindsInstance @Named("recoverType") ObjectProperty<RecoverUtil.Type> recoverType);
|
||||
@BindsInstance @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import javafx.stage.Stage;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
@@ -19,12 +19,12 @@ public class RecoveryKeyIsHubVaultController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> recoverykeyRecoverScene;
|
||||
private final ObjectProperty<RecoverUtil.Type> recoverType;
|
||||
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyIsHubVaultController(@RecoveryKeyWindow Stage window, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene, //
|
||||
@Named("recoverType") ObjectProperty<RecoverUtil.Type> recoverType, //
|
||||
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||
ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
window.setTitle(resourceBundle.getString("recoveryKey.recoverVaultConfig.title"));
|
||||
@@ -40,7 +40,7 @@ public class RecoveryKeyIsHubVaultController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void recover() {
|
||||
recoverType.set(RecoverUtil.Type.RESTORE_VAULT_CONFIG);
|
||||
recoverType.set(RecoveryActionType.RESTORE_VAULT_CONFIG);
|
||||
window.setScene(recoverykeyRecoverScene.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
@@ -177,7 +177,7 @@ abstract class RecoveryKeyModule {
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyValidateController.class)
|
||||
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty<RecoverUtil.Type> recoverType, @Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) {
|
||||
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, @Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) {
|
||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, recoverType, cipherCombo, masterkeyFileAccess);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
@@ -27,7 +27,7 @@ public class RecoveryKeyRecoverController implements FxController {
|
||||
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> expertSettingsScene, //
|
||||
ResourceBundle resourceBundle, @Named("recoverType") ObjectProperty<RecoverUtil.Type> recoverType) {
|
||||
ResourceBundle resourceBundle, @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType) {
|
||||
this.window = window;
|
||||
|
||||
this.nextScene = switch (recoverType.get()) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import javax.inject.Named;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.Task;
|
||||
@@ -19,7 +20,10 @@ import java.util.concurrent.ExecutorService;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.recovery.CryptoFsInitializer;
|
||||
import org.cryptomator.common.recovery.MasterkeyService;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.recovery.RecoveryDirectory;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
@@ -50,7 +54,7 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final IntegerProperty shorteningThreshold;
|
||||
private final ObjectProperty<RecoverUtil.Type> recoverType;
|
||||
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||
private final ObjectProperty<CryptorProvider.Scheme> cipherCombo;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final StringProperty buttonText = new SimpleStringProperty();
|
||||
@@ -70,7 +74,8 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
MasterkeyFileAccess masterkeyFileAccess, //
|
||||
VaultListManager vaultListManager, //
|
||||
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
|
||||
@Named("recoverType") ObjectProperty<RecoverUtil.Type> recoverType, @Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo,//
|
||||
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo,//
|
||||
ResourceBundle resourceBundle, Dialogs dialogs) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
@@ -90,8 +95,8 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
initButtonText(recoverType.get());
|
||||
}
|
||||
|
||||
private void initButtonText(RecoverUtil.Type type) {
|
||||
if (type == RecoverUtil.Type.RESTORE_MASTERKEY) {
|
||||
private void initButtonText(RecoveryActionType type) {
|
||||
if (type == RecoveryActionType.RESTORE_MASTERKEY) {
|
||||
buttonText.set(resourceBundle.getString("generic.button.close"));
|
||||
} else {
|
||||
buttonText.set(resourceBundle.getString("generic.button.back"));
|
||||
@@ -100,42 +105,68 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
if (recoverType.getValue().equals(RecoverUtil.Type.RESTORE_MASTERKEY)) {
|
||||
if (recoverType.getValue().equals(RecoveryActionType.RESTORE_MASTERKEY)) {
|
||||
window.close();
|
||||
} else {
|
||||
window.setScene(recoverExpertSettingsScene.get());
|
||||
}
|
||||
}
|
||||
@FXML
|
||||
public void restorePassword() {
|
||||
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||
Path recoveryPath = recoveryDirectory.getRecoveryPath();
|
||||
MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
|
||||
|
||||
Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME);
|
||||
|
||||
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, newPasswordController.passwordField.getCharacters())) {
|
||||
CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold, cipherCombo.get());
|
||||
}
|
||||
|
||||
recoveryDirectory.moveRecoveredFiles();
|
||||
|
||||
if (!vaultListManager.containsVault(vault.getPath())) {
|
||||
vaultListManager.add(vault.getPath());
|
||||
}
|
||||
|
||||
dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle)
|
||||
.setTitleKey("recoveryKey.recoverVaultConfig.title")
|
||||
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message")
|
||||
.build().showAndWait();
|
||||
window.close();
|
||||
|
||||
} catch (IOException | CryptoException e) {
|
||||
LOG.error("Recovery process failed", e);
|
||||
appWindows.showErrorWindow(e, window, null);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void resetPassword() {
|
||||
if (vault.isMissingVaultConfig()) {
|
||||
try {
|
||||
Path recoveryPath = RecoverUtil.createRecoveryDirectory(vault.getPath());
|
||||
RecoverUtil.createNewMasterkeyFile(recoveryKeyFactory, recoveryPath, recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME);
|
||||
Task<Void> task = new ResetPasswordTask(recoveryKeyFactory, vault, recoveryKey, newPasswordController);
|
||||
|
||||
try (Masterkey masterkey = RecoverUtil.loadMasterkey(masterkeyFileAccess, masterkeyFilePath, newPasswordController.passwordField.getCharacters())) {
|
||||
RecoverUtil.initializeCryptoFileSystem(recoveryPath, masterkey, shorteningThreshold, cipherCombo.get());
|
||||
}
|
||||
task.setOnScheduled(_ -> LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath()));
|
||||
|
||||
RecoverUtil.moveRecoveredFiles(recoveryPath, vault.getPath());
|
||||
RecoverUtil.deleteRecoveryDirectory(recoveryPath);
|
||||
RecoverUtil.addVaultToList(vaultListManager, vault.getPath());
|
||||
|
||||
dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle).setTitleKey("recoveryKey.recoverVaultConfig.title").setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message").build().showAndWait();
|
||||
window.close();
|
||||
|
||||
} catch (IOException | CryptoException e) {
|
||||
LOG.error("Recovery process failed", e);
|
||||
task.setOnSucceeded(_ -> {
|
||||
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
if (vault.getState().equals(org.cryptomator.common.vaults.VaultState.Value.MASTERKEY_MISSING)) {
|
||||
dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle)
|
||||
.setTitleKey("recoveryKey.recoverMasterkey.title")
|
||||
.setMessageKey("recoveryKey.recover.resetMasterkeyFileSuccess.message")
|
||||
.build().showAndWait();
|
||||
} else {
|
||||
dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle)
|
||||
.build().showAndWait();
|
||||
}
|
||||
} else {
|
||||
Task<Void> task = RecoverUtil.createResetPasswordTask( //
|
||||
resourceBundle, owner, recoveryKeyFactory, //
|
||||
vault, recoveryKey, newPasswordController, //
|
||||
window, appWindows, dialogs);
|
||||
executor.submit(task);
|
||||
}
|
||||
window.close();
|
||||
});
|
||||
|
||||
task.setOnFailed(_ -> {
|
||||
LOG.error("Resetting password failed.", task.getException());
|
||||
appWindows.showErrorWindow(task.getException(), window, null);
|
||||
});
|
||||
|
||||
executor.submit(task);
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
@@ -155,5 +186,37 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
public boolean isPasswordSufficientAndMatching() {
|
||||
return newPasswordController.isGoodPassword();
|
||||
}
|
||||
private final ReadOnlyBooleanWrapper vaultConfigMissing = new ReadOnlyBooleanWrapper();
|
||||
|
||||
public ReadOnlyBooleanProperty vaultConfigMissingProperty() {
|
||||
return vaultConfigMissing.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public boolean isVaultConfigMissing() {
|
||||
return vault.isMissingVaultConfig();
|
||||
}
|
||||
|
||||
private static class ResetPasswordTask extends Task<Void> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResetPasswordTask.class);
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final Vault vault;
|
||||
private final StringProperty recoveryKey;
|
||||
private final NewPasswordController newPasswordController;
|
||||
|
||||
public ResetPasswordTask(RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, NewPasswordController newPasswordController) {
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.vault = vault;
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.newPasswordController = newPasswordController;
|
||||
|
||||
setOnFailed(_ -> LOG.error("Failed to reset password", getException()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void call() throws IOException, IllegalArgumentException {
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Strings;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.ObservableUtil;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||
@@ -28,6 +27,9 @@ import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.cryptomator.common.recovery.MasterkeyService;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
|
||||
public class RecoveryKeyValidateController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
|
||||
@@ -43,7 +45,7 @@ public class RecoveryKeyValidateController implements FxController {
|
||||
private final ObjectProperty<RecoveryKeyState> recoveryKeyState;
|
||||
private final ObjectProperty<CryptorProvider.Scheme> cipherCombo;
|
||||
private final AutoCompleter autoCompleter;
|
||||
private final ObjectProperty<RecoverUtil.Type> recoverType;
|
||||
private final ObjectProperty<RecoveryActionType> recoverType;
|
||||
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||
|
||||
private volatile boolean isWrongKey;
|
||||
@@ -54,7 +56,7 @@ public class RecoveryKeyValidateController implements FxController {
|
||||
@Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, //
|
||||
StringProperty recoveryKey, //
|
||||
RecoveryKeyFactory recoveryKeyFactory, //
|
||||
@Named("recoverType") ObjectProperty<RecoverUtil.Type> recoverType, //
|
||||
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
|
||||
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo,//
|
||||
MasterkeyFileAccess masterkeyFileAccess) {
|
||||
this.vault = vault;
|
||||
@@ -137,7 +139,7 @@ public class RecoveryKeyValidateController implements FxController {
|
||||
switch (recoverType.get()) {
|
||||
case RESTORE_VAULT_CONFIG -> {
|
||||
AtomicBoolean illegalArgumentExceptionOccurred = new AtomicBoolean(false);
|
||||
var combo = RecoverUtil.validateRecoveryKeyAndGetCombo(
|
||||
var combo = MasterkeyService.validateRecoveryKeyAndDetectCombo(
|
||||
recoveryKeyFactory, vault, recoveryKey, masterkeyFileAccess, illegalArgumentExceptionOccurred);
|
||||
combo.ifPresent(cipherCombo::set);
|
||||
if (illegalArgumentExceptionOccurred.get()) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.recovery.RecoveryActionType;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -50,13 +50,13 @@ public class MasterkeyOptionsController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void showRecoveryKey() {
|
||||
ObjectProperty<RecoverUtil.Type> recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.SHOW_KEY);
|
||||
ObjectProperty<RecoveryActionType> recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.SHOW_KEY);
|
||||
recoveryKeyWindow.create(vault, window, recoverTypeProperty).showRecoveryKeyCreationWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showRecoverVaultDialog() {
|
||||
ObjectProperty<RecoverUtil.Type> recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.RESET_PASSWORD);
|
||||
ObjectProperty<RecoveryActionType> recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.RESET_PASSWORD);
|
||||
recoveryKeyWindow.create(vault, window, recoverTypeProperty).showRecoveryKeyRecoverWindow();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||
<buttons>
|
||||
<Button text="${controller.buttonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
<Button text="%recoveryKey.recover.resetBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#resetPassword" disable="${!controller.passwordSufficientAndMatching}"/>
|
||||
<Button text="%recoveryKey.recover.resetBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#resetPassword" managed="${!controller.vaultConfigMissing}" disable="${!controller.passwordSufficientAndMatching}"/>
|
||||
<Button text="%recoveryKey.recover.recoverBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#restorePassword" managed="${controller.vaultConfigMissing}" disable="${!controller.passwordSufficientAndMatching}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
|
||||
@@ -519,6 +519,7 @@ recoveryKey.recover.invalidKey=This recovery key is not valid
|
||||
recoveryKey.printout.heading=Cryptomator Recovery Key\n"%s"\n
|
||||
### Reset Password
|
||||
recoveryKey.recover.resetBtn=Reset
|
||||
recoveryKey.recover.recoverBtn=Recover
|
||||
### Recovery Key Password Reset Success
|
||||
recoveryKey.recover.resetSuccess.message=Password reset successful
|
||||
recoveryKey.recover.resetSuccess.description=You can unlock your vault with the new password.
|
||||
|
||||
Reference in New Issue
Block a user