diff --git a/main/ui/pom.xml b/main/ui/pom.xml
index c1dc24234..6808f3887 100644
--- a/main/ui/pom.xml
+++ b/main/ui/pom.xml
@@ -99,5 +99,12 @@
org.cryptomator
commons-test
+
+
+
+ com.nulab-inc
+ zxcvbn
+ 1.1.1
+
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
index 33f7ae11f..5944cf999 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
@@ -5,6 +5,7 @@
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
+ * Jean-Noël Charon - password strength meter
*******************************************************************************/
package org.cryptomator.ui.controllers;
@@ -21,18 +22,24 @@ import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.settings.Localization;
+import org.cryptomator.ui.util.PasswordStrengthUtil;
+import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.layout.Region;
import javafx.scene.text.Text;
@Singleton
@@ -41,13 +48,16 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
private final Application app;
+ private final PasswordStrengthUtil strengthRater;
final ObjectProperty vault = new SimpleObjectProperty<>();
private Optional listener = Optional.empty();
+ private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
@Inject
- public ChangePasswordController(Application app, Localization localization) {
+ public ChangePasswordController(Application app, PasswordStrengthUtil strengthRater, Localization localization) {
super(localization);
this.app = app;
+ this.strengthRater = strengthRater;
}
@FXML
@@ -68,12 +78,43 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
@FXML
private Hyperlink downloadsPageLink;
+ @FXML
+ private Label passwordStrengthLabel;
+
+ @FXML
+ private Region passwordStrengthLevel0;
+
+ @FXML
+ private Region passwordStrengthLevel1;
+
+ @FXML
+ private Region passwordStrengthLevel2;
+
+ @FXML
+ private Region passwordStrengthLevel3;
+
+ @FXML
+ private Region passwordStrengthLevel4;
+
@Override
public void initialize() {
BooleanBinding oldPasswordIsEmpty = oldPasswordField.textProperty().isEmpty();
BooleanBinding newPasswordIsEmpty = newPasswordField.textProperty().isEmpty();
BooleanBinding passwordsDiffer = newPasswordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
changePasswordButton.disableProperty().bind(oldPasswordIsEmpty.or(newPasswordIsEmpty.or(passwordsDiffer)));
+ passwordStrength.bind(EasyBind.map(newPasswordField.textProperty(), strengthRater::computeRate));
+
+ passwordStrengthLevel0.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(0));
+ passwordStrengthLevel1.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(1));
+ passwordStrengthLevel2.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(2));
+ passwordStrengthLevel3.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(3));
+ passwordStrengthLevel4.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(4));
+ passwordStrengthLevel0.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel1.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel2.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel3.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel4.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
}
@Override
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
index 19a1187df..96016aa55 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
@@ -2,9 +2,10 @@
* Copyright (c) 2014, 2016 Sebastian Stenzel
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
- *
+ *
* Contributors:
* Sebastian Stenzel - initial API and implementation
+ * Jean-Noël Charon - password strength meter
******************************************************************************/
package org.cryptomator.ui.controllers;
@@ -20,29 +21,37 @@ import javax.inject.Singleton;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.settings.Localization;
+import org.cryptomator.ui.util.PasswordStrengthUtil;
+import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
+import javafx.scene.layout.Region;
@Singleton
public class InitializeController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
+ private final PasswordStrengthUtil strengthRater;
final ObjectProperty vault = new SimpleObjectProperty<>();
private Optional listener = Optional.empty();
+ private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
@Inject
- public InitializeController(Localization localization) {
+ public InitializeController(Localization localization, PasswordStrengthUtil strengthRater) {
super(localization);
+ this.strengthRater = strengthRater;
}
@FXML
@@ -57,11 +66,42 @@ public class InitializeController extends LocalizedFXMLViewController {
@FXML
private Label messageLabel;
+ @FXML
+ private Label passwordStrengthLabel;
+
+ @FXML
+ private Region passwordStrengthLevel0;
+
+ @FXML
+ private Region passwordStrengthLevel1;
+
+ @FXML
+ private Region passwordStrengthLevel2;
+
+ @FXML
+ private Region passwordStrengthLevel3;
+
+ @FXML
+ private Region passwordStrengthLevel4;
+
@Override
public void initialize() {
BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty();
BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
+ passwordStrength.bind(EasyBind.map(passwordField.textProperty(), strengthRater::computeRate));
+
+ passwordStrengthLevel0.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(0));
+ passwordStrengthLevel1.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(1));
+ passwordStrengthLevel2.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(2));
+ passwordStrengthLevel3.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(3));
+ passwordStrengthLevel4.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(4));
+ passwordStrengthLevel0.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel1.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel2.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel3.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel4.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
}
@Override
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
index 5c976e317..0ac30e0b2 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
@@ -154,6 +154,7 @@ public class WelcomeController extends LocalizedFXMLViewController {
Platform.runLater(() -> {
this.updateLink.setText(msg);
this.updateLink.setVisible(true);
+ this.updateLink.setDisable(false);
});
}
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java
new file mode 100644
index 000000000..b2871a9b3
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Jean-Noël Charon - initial API and implementation
+ *******************************************************************************/
+package org.cryptomator.ui.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.apache.commons.lang3.StringUtils;
+import org.cryptomator.ui.settings.Localization;
+
+import com.nulabinc.zxcvbn.Zxcvbn;
+
+import javafx.geometry.Insets;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.CornerRadii;
+import javafx.scene.paint.Color;
+
+@Singleton
+public class PasswordStrengthUtil {
+
+ private final Zxcvbn zxcvbn;
+ private final List sanitizedInputs;
+ private final Localization localization;
+
+ @Inject
+ public PasswordStrengthUtil(Localization localization) {
+ this.localization = localization;
+ this.zxcvbn = new Zxcvbn();
+ this.sanitizedInputs = new ArrayList<>();
+ this.sanitizedInputs.add("cryptomator");
+ }
+
+ public int computeRate(String password) {
+ if (StringUtils.isEmpty(password)) {
+ return -1;
+ } else {
+ return zxcvbn.measure(password, sanitizedInputs).getScore();
+ }
+ }
+
+ public Color getStrengthColor(Number score) {
+ switch (score.intValue()) {
+ case 0:
+ return Color.web("#e74c3c");
+ case 1:
+ return Color.web("#e67e22");
+ case 2:
+ return Color.web("#f1c40f");
+ case 3:
+ return Color.web("#40d47e");
+ case 4:
+ return Color.web("#27ae60");
+ default:
+ return Color.TRANSPARENT;
+ }
+ }
+
+ public Background getBackgroundWithStrengthColor(Number score) {
+ Color c = this.getStrengthColor(score);
+ BackgroundFill fill = new BackgroundFill(c, CornerRadii.EMPTY, Insets.EMPTY);
+ return new Background(fill);
+ }
+
+ public String getStrengthDescription(Number score) {
+ String key = "initialize.messageLabel.passwordStrength." + score.intValue();
+ if (localization.containsKey(key)) {
+ return localization.getString(key);
+ } else {
+ return "";
+ }
+ }
+
+}
diff --git a/main/ui/src/main/resources/css/linux_theme.css b/main/ui/src/main/resources/css/linux_theme.css
index 4beabf9e8..9c8fc2f3a 100644
--- a/main/ui/src/main/resources/css/linux_theme.css
+++ b/main/ui/src/main/resources/css/linux_theme.css
@@ -50,6 +50,10 @@
-fx-font-family: Ionicons;
}
+.caption-label {
+ -fx-font-size: 0.9em;
+}
+
/****************************************************************************
* *
* Hyperlinks *
diff --git a/main/ui/src/main/resources/css/mac_theme.css b/main/ui/src/main/resources/css/mac_theme.css
index bac4a985d..33d1e223e 100644
--- a/main/ui/src/main/resources/css/mac_theme.css
+++ b/main/ui/src/main/resources/css/mac_theme.css
@@ -49,6 +49,10 @@
-fx-font-family: Ionicons;
}
+.caption-label {
+ -fx-font-size: 0.9em;
+}
+
/****************************************************************************
* *
* Hyperlinks *
diff --git a/main/ui/src/main/resources/css/win_theme.css b/main/ui/src/main/resources/css/win_theme.css
index f0109b004..bfd569b54 100644
--- a/main/ui/src/main/resources/css/win_theme.css
+++ b/main/ui/src/main/resources/css/win_theme.css
@@ -42,6 +42,10 @@
-fx-font-family: Ionicons;
}
+.caption-label {
+ -fx-font-size: 0.9em;
+}
+
/****************************************************************************
* *
* Hyperlinks *
diff --git a/main/ui/src/main/resources/fxml/change_password.fxml b/main/ui/src/main/resources/fxml/change_password.fxml
index 93b7f18b0..44d985526 100644
--- a/main/ui/src/main/resources/fxml/change_password.fxml
+++ b/main/ui/src/main/resources/fxml/change_password.fxml
@@ -6,6 +6,7 @@
Contributors:
Sebastian Stenzel - initial API and implementation
+ Jean-Noël Charon - password strength meter
-->
@@ -20,7 +21,9 @@
-
+
+
+
@@ -46,10 +49,22 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
diff --git a/main/ui/src/main/resources/fxml/initialize.fxml b/main/ui/src/main/resources/fxml/initialize.fxml
index 65ca244c6..206843445 100644
--- a/main/ui/src/main/resources/fxml/initialize.fxml
+++ b/main/ui/src/main/resources/fxml/initialize.fxml
@@ -6,15 +6,22 @@
Contributors:
Sebastian Stenzel - initial API and implementation
+ Jean-Noël Charon - password strength meter
-->
+
+
-
+
+
+
+
+
@@ -30,17 +37,28 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
-
-
diff --git a/main/ui/src/main/resources/fxml/welcome.fxml b/main/ui/src/main/resources/fxml/welcome.fxml
index 14405ead3..aa6af8885 100644
--- a/main/ui/src/main/resources/fxml/welcome.fxml
+++ b/main/ui/src/main/resources/fxml/welcome.fxml
@@ -25,7 +25,7 @@
-
+
diff --git a/main/ui/src/main/resources/localization/en.txt b/main/ui/src/main/resources/localization/en.txt
index fec481349..2541ad92f 100644
--- a/main/ui/src/main/resources/localization/en.txt
+++ b/main/ui/src/main/resources/localization/en.txt
@@ -24,6 +24,11 @@ initialize.label.retypePassword=Retype password
initialize.button.ok=Create vault
initialize.messageLabel.alreadyInitialized=Vault already initialized
initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
+initialize.messageLabel.passwordStrength.0=Very weak
+initialize.messageLabel.passwordStrength.1=Weak
+initialize.messageLabel.passwordStrength.2=Fair
+initialize.messageLabel.passwordStrength.3=Strong
+initialize.messageLabel.passwordStrength.4=Very strong
# notfound.fxml
notfound.label=Vault couldn't be found. Has it been moved?