Bladeren bron

Merge pull request #1980 from cryptomator/feature/hub-integrated-device-registration

Register device from within client application
Sebastian Stenzel 3 jaren geleden
bovenliggende
commit
b142904cc3

+ 1 - 1
src/main/java/module-info.java

@@ -45,6 +45,7 @@ module org.cryptomator.desktop {
 	exports org.cryptomator.ui.keyloading.hub to com.fasterxml.jackson.databind;
 
 	opens org.cryptomator.common.settings to com.google.gson;
+	opens org.cryptomator.ui.keyloading.hub to com.google.gson, javafx.fxml;
 
 	opens org.cryptomator.common to javafx.fxml;
 	opens org.cryptomator.common.vaults to javafx.fxml;
@@ -55,7 +56,6 @@ module org.cryptomator.desktop {
 	opens org.cryptomator.ui.forgetPassword to javafx.fxml;
 	opens org.cryptomator.ui.fxapp to javafx.fxml;
 	opens org.cryptomator.ui.health to javafx.fxml;
-	opens org.cryptomator.ui.keyloading.hub to javafx.fxml;
 	opens org.cryptomator.ui.keyloading.masterkeyfile to javafx.fxml;
 	opens org.cryptomator.ui.lock to javafx.fxml;
 	opens org.cryptomator.ui.mainwindow to javafx.fxml;

+ 7 - 19
src/main/java/org/cryptomator/ui/keyloading/hub/AuthFlowController.java

@@ -1,15 +1,12 @@
 package org.cryptomator.ui.keyloading.hub;
 
+import com.nimbusds.jose.JWEObject;
 import dagger.Lazy;
-import org.cryptomator.ui.common.ErrorComponent;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.common.FxmlFile;
 import org.cryptomator.ui.common.FxmlScene;
-import org.cryptomator.ui.common.UserInteractionLock;
 import org.cryptomator.ui.keyloading.KeyLoading;
 import org.cryptomator.ui.keyloading.KeyLoadingScoped;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -24,29 +21,27 @@ import javafx.scene.Scene;
 import javafx.stage.Stage;
 import javafx.stage.WindowEvent;
 import java.net.URI;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicReference;
 
 @KeyLoadingScoped
 public class AuthFlowController implements FxController {
 
-	private static final Logger LOG = LoggerFactory.getLogger(AuthFlowController.class);
-
 	private final Application application;
 	private final Stage window;
 	private final ExecutorService executor;
 	private final String deviceId;
 	private final HubConfig hubConfig;
 	private final AtomicReference<String> tokenRef;
-	private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result;
+	private final CompletableFuture<JWEObject> result;
 	private final Lazy<Scene> receiveKeyScene;
-	private final ErrorComponent.Builder errorComponent;
 	private final ObjectProperty<URI> authUri;
 	private final StringBinding authHost;
 	private AuthFlowTask task;
 
 	@Inject
-	public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene, ErrorComponent.Builder errorComponent) {
+	public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
 		this.application = application;
 		this.window = window;
 		this.executor = executor;
@@ -55,7 +50,6 @@ public class AuthFlowController implements FxController {
 		this.tokenRef = tokenRef;
 		this.result = result;
 		this.receiveKeyScene = receiveKeyScene;
-		this.errorComponent = errorComponent;
 		this.authUri = new SimpleObjectProperty<>();
 		this.authHost = Bindings.createStringBinding(this::getAuthHost, authUri);
 		this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
@@ -64,7 +58,7 @@ public class AuthFlowController implements FxController {
 	@FXML
 	public void initialize() {
 		assert task == null;
-		task = new AuthFlowTask(hubConfig, new AuthFlowContext(deviceId), this::setAuthUri);;
+		task = new AuthFlowTask(hubConfig, new AuthFlowContext(deviceId), this::setAuthUri);
 		task.setOnFailed(this::authFailed);
 		task.setOnSucceeded(this::authSucceeded);
 		executor.submit(task);
@@ -88,11 +82,7 @@ public class AuthFlowController implements FxController {
 	private void windowClosed(WindowEvent windowEvent) {
 		// stop server, if it is still running
 		task.cancel();
-		// if not already interacted, mark this workflow as cancelled:
-		if (result.awaitingInteraction().get()) {
-			LOG.debug("Authorization cancelled by user.");
-			result.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
-		}
+		result.cancel(true);
 	}
 
 	private void authSucceeded(WorkerStateEvent workerStateEvent) {
@@ -102,11 +92,9 @@ public class AuthFlowController implements FxController {
 	}
 
 	private void authFailed(WorkerStateEvent workerStateEvent) {
-		result.interacted(HubKeyLoadingModule.HubLoadingResult.FAILED);
 		window.requestFocus();
 		var exception = workerStateEvent.getSource().getException();
-		LOG.error("Authentication failed", exception);
-		errorComponent.cause(exception).window(window).build().showErrorScene();
+		result.completeExceptionally(exception);
 	}
 
 	/* Getter/Setter */

+ 9 - 0
src/main/java/org/cryptomator/ui/keyloading/hub/CreateDeviceDto.java

@@ -0,0 +1,9 @@
+package org.cryptomator.ui.keyloading.hub;
+
+class CreateDeviceDto {
+
+	public String id;
+	public String name;
+	public String publicKey;
+
+}

+ 3 - 15
src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java

@@ -17,7 +17,6 @@ import org.cryptomator.ui.common.FxmlLoaderFactory;
 import org.cryptomator.ui.common.FxmlScene;
 import org.cryptomator.ui.common.NewPasswordController;
 import org.cryptomator.ui.common.PasswordStrengthUtil;
-import org.cryptomator.ui.common.UserInteractionLock;
 import org.cryptomator.ui.keyloading.KeyLoading;
 import org.cryptomator.ui.keyloading.KeyLoadingScoped;
 import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
@@ -28,17 +27,12 @@ import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.util.Objects;
 import java.util.ResourceBundle;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicReference;
 
 @Module
 public abstract class HubKeyLoadingModule {
 
-	public enum HubLoadingResult {
-		SUCCESS,
-		FAILED,
-		CANCELLED
-	}
-
 	@Provides
 	@KeyLoadingScoped
 	static HubConfig provideHubConfig(@KeyLoading Vault vault) {
@@ -67,14 +61,8 @@ public abstract class HubKeyLoadingModule {
 
 	@Provides
 	@KeyLoadingScoped
-	static AtomicReference<JWEObject> provideJweRef() {
-		return new AtomicReference<>();
-	}
-
-	@Provides
-	@KeyLoadingScoped
-	static UserInteractionLock<HubLoadingResult> provideResultLock() {
-		return new UserInteractionLock<>(null);
+	static CompletableFuture<JWEObject> provideResult() {
+		return new CompletableFuture<>();
 	}
 
 	@Binds

+ 15 - 15
src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java

@@ -8,7 +8,6 @@ import org.cryptomator.cryptolib.api.Masterkey;
 import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
 import org.cryptomator.ui.common.FxmlFile;
 import org.cryptomator.ui.common.FxmlScene;
-import org.cryptomator.ui.common.UserInteractionLock;
 import org.cryptomator.ui.keyloading.KeyLoading;
 import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
 import org.cryptomator.ui.unlock.UnlockCancelledException;
@@ -19,7 +18,9 @@ import javafx.scene.Scene;
 import javafx.stage.Stage;
 import javafx.stage.Window;
 import java.net.URI;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 
 @KeyLoading
 public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
@@ -30,37 +31,37 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
 
 	private final Stage window;
 	private final Lazy<Scene> authFlowScene;
-	private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> userInteraction;
+	private final CompletableFuture<JWEObject> result;
 	private final DeviceKey deviceKey;
-	private final AtomicReference<JWEObject> jweRef;
 
 	@Inject
-	public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> userInteraction, DeviceKey deviceKey, AtomicReference<JWEObject> jweRef) {
+	public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey) {
 		this.window = window;
 		this.authFlowScene = authFlowScene;
-		this.userInteraction = userInteraction;
+		this.result = result;
 		this.deviceKey = deviceKey;
-		this.jweRef = jweRef;
 	}
 
 	@Override
 	public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
 		Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
 		try {
-			return switch (auth()) {
-				case SUCCESS -> JWEHelper.decrypt(jweRef.get(), deviceKey.get().getPrivate());
-				case FAILED -> throw new MasterkeyLoadingFailedException("failed to load keypair");
-				case CANCELLED -> throw new UnlockCancelledException("User cancelled auth workflow");
-			};
+			startAuthFlow();
+			var jwe = result.get();
+			return JWEHelper.decrypt(jwe, deviceKey.get().getPrivate());
 		} catch (DeviceKey.DeviceKeyRetrievalException e) {
-			throw new MasterkeyLoadingFailedException("Failed to create or load device key pair", e);
+			throw new MasterkeyLoadingFailedException("Failed to load keypair", e);
+		} catch (CancellationException e) {
+			throw new UnlockCancelledException("User cancelled auth workflow", e);
 		} catch (InterruptedException e) {
 			Thread.currentThread().interrupt();
 			throw new UnlockCancelledException("Loading interrupted", e);
+		} catch (ExecutionException e) {
+			throw new MasterkeyLoadingFailedException("Failed to retrieve key", e);
 		}
 	}
 
-	private HubKeyLoadingModule.HubLoadingResult auth() throws InterruptedException {
+	private void startAuthFlow() {
 		Platform.runLater(() -> {
 			window.setScene(authFlowScene.get());
 			window.show();
@@ -72,7 +73,6 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
 				window.centerOnScreen();
 			}
 		});
-		return userInteraction.awaitInteraction();
 	}
 
 }

+ 19 - 28
src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java

@@ -3,13 +3,12 @@ package org.cryptomator.ui.keyloading.hub;
 import com.nimbusds.jose.JWEObject;
 import dagger.Lazy;
 import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.ui.common.ErrorComponent;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.common.FxmlFile;
 import org.cryptomator.ui.common.FxmlScene;
-import org.cryptomator.ui.common.UserInteractionLock;
 import org.cryptomator.ui.keyloading.KeyLoading;
 import org.cryptomator.ui.keyloading.KeyLoadingScoped;
+import org.eclipse.jetty.io.RuntimeIOException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,6 +29,7 @@ import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.text.ParseException;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -42,24 +42,20 @@ public class ReceiveKeyController implements FxController {
 	private final Stage window;
 	private final String deviceId;
 	private final String bearerToken;
-	private final AtomicReference<JWEObject> jweRef;
-	private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result;
+	private final CompletableFuture<JWEObject> result;
 	private final Lazy<Scene> registerDeviceScene;
 	private final Lazy<Scene> unauthorizedScene;
-	private final ErrorComponent.Builder errorComponent;
 	private final URI vaultBaseUri;
 	private final HttpClient httpClient;
 
 	@Inject
-	public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, AtomicReference<JWEObject> jweRef, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, ErrorComponent.Builder errorComponent) {
+	public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene) {
 		this.window = window;
 		this.deviceId = deviceId;
 		this.bearerToken = Objects.requireNonNull(tokenRef.get());
-		this.jweRef = jweRef;
 		this.result = result;
 		this.registerDeviceScene = registerDeviceScene;
 		this.unauthorizedScene = unauthorizedScene;
-		this.errorComponent = errorComponent;
 		this.vaultBaseUri = getVaultBaseUri(vault);
 		this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
 		this.httpClient = HttpClient.newBuilder().executor(executor).build();
@@ -73,30 +69,30 @@ public class ReceiveKeyController implements FxController {
 				.GET() //
 				.build();
 		httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) //
-				.whenCompleteAsync(this::loadedExistingKey, Platform::runLater);
+				.thenAcceptAsync(this::loadedExistingKey, Platform::runLater) //
+				.exceptionally(this::retrievalFailed);
 	}
 
-	private void loadedExistingKey(HttpResponse<InputStream> response, Throwable error) {
-		if (error != null) {
-			retrievalFailed(error);
-		} else {
+	private void loadedExistingKey(HttpResponse<InputStream> response) {
+		try {
 			switch (response.statusCode()) {
 				case 200 -> retrievalSucceeded(response);
 				case 403 -> accessNotGranted();
 				case 404 -> needsDeviceRegistration();
-				default -> retrievalFailed(new IOException("Unexpected response " + response.statusCode()));
+				default -> throw new IOException("Unexpected response " + response.statusCode());
 			}
+		} catch (IOException e) {
+			throw new RuntimeIOException(e);
 		}
 	}
 
-	private void retrievalSucceeded(HttpResponse<InputStream> response) {
+	private void retrievalSucceeded(HttpResponse<InputStream> response) throws IOException {
 		try {
 			var string = HttpHelper.readBody(response);
-			jweRef.set(JWEObject.parse(string));
-			result.interacted(HubKeyLoadingModule.HubLoadingResult.SUCCESS);
+			result.complete(JWEObject.parse(string));
 			window.close();
-		} catch (ParseException | IOException e) {
-			retrievalFailed(e);
+		} catch (ParseException e) {
+			throw new IOException("Failed to parse JWE", e);
 		}
 	}
 
@@ -108,10 +104,9 @@ public class ReceiveKeyController implements FxController {
 		window.setScene(unauthorizedScene.get());
 	}
 
-	private void retrievalFailed(Throwable cause) {
-		result.interacted(HubKeyLoadingModule.HubLoadingResult.FAILED);
-		LOG.error("Key retrieval failed", cause);
-		errorComponent.cause(cause).window(window).build().showErrorScene();
+	private Void retrievalFailed(Throwable cause) {
+		result.completeExceptionally(cause);
+		return null;
 	}
 
 	@FXML
@@ -120,11 +115,7 @@ public class ReceiveKeyController implements FxController {
 	}
 
 	private void windowClosed(WindowEvent windowEvent) {
-		// if not already interacted, mark this workflow as cancelled:
-		if (result.awaitingInteraction().get()) {
-			LOG.debug("Authorization cancelled by user.");
-			result.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
-		}
+		result.cancel(true);
 	}
 
 	private static URI appendPath(URI base, String path) {

+ 59 - 36
src/main/java/org/cryptomator/ui/keyloading/hub/RegisterDeviceController.java

@@ -1,57 +1,92 @@
 package org.cryptomator.ui.keyloading.hub;
 
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.interfaces.DecodedJWT;
 import com.google.common.io.BaseEncoding;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.nimbusds.jose.JWEObject;
 import org.cryptomator.common.settings.DeviceKey;
-import org.cryptomator.cryptolib.common.MessageDigestSupplier;
 import org.cryptomator.cryptolib.common.P384KeyPair;
 import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.common.UserInteractionLock;
 import org.cryptomator.ui.keyloading.KeyLoading;
 import org.cryptomator.ui.keyloading.KeyLoadingScoped;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
-import javafx.application.Application;
+import javax.inject.Named;
+import javafx.application.Platform;
 import javafx.fxml.FXML;
+import javafx.scene.control.TextField;
 import javafx.stage.Stage;
 import javafx.stage.WindowEvent;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import java.nio.charset.StandardCharsets;
-import java.security.KeyPair;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicReference;
 
 @KeyLoadingScoped
 public class RegisterDeviceController implements FxController {
 
-	private final Application application;
+	private static final Logger LOG = LoggerFactory.getLogger(RegisterDeviceController.class);
+	private static final Gson GSON = new GsonBuilder().setLenient().create();
+
 	private final Stage window;
 	private final HubConfig hubConfig;
+	private final String bearerToken;
+	private final String deviceId;
 	private final P384KeyPair keyPair;
-	private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result;
-	private final String verificationCode;
+	private final CompletableFuture<JWEObject> result;
+	private final DecodedJWT jwt;
+	private final HttpClient httpClient;
+
+	public TextField deviceNameField;
 
 	@Inject
-	public RegisterDeviceController(Application application, SecureRandom csprng, @KeyLoading Stage window, HubConfig hubConfig, DeviceKey deviceKey, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result) {
-		this.application = application;
+	public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<JWEObject> result, @Named("bearerToken") AtomicReference<String> bearerToken) {
 		this.window = window;
 		this.hubConfig = hubConfig;
+		this.deviceId = deviceId;
 		this.keyPair = Objects.requireNonNull(deviceKey.get());
 		this.result = result;
+		this.bearerToken = Objects.requireNonNull(bearerToken.get());
+		this.jwt = JWT.decode(this.bearerToken);
 		this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
-		this.verificationCode = String.format("%06d", csprng.nextInt(1_000_000));
+		this.httpClient = HttpClient.newBuilder().executor(executor).build();
 	}
 
 	@FXML
-	public void browse() {
+	public void register() {
+		var keyUri = URI.create("http://localhost:9090/devices/" + deviceId); // TODO lol hubConfig.deviceRegistrationUrl
 		var deviceKey = keyPair.getPublic().getEncoded();
-		var encodedKey = BaseEncoding.base64Url().omitPadding().encode(deviceKey);
-		var hashedKey = MessageDigestSupplier.SHA256.get().digest(deviceKey);
-		var deviceId = BaseEncoding.base16().encode(hashedKey);
-		var hash = computeVerificationHash(deviceId + encodedKey + verificationCode);
-		var url = hubConfig.deviceRegistrationUrl + "&device_key=" + encodedKey + "&device_id=" + deviceId + "&verification_hash=" + hash;
-		application.getHostServices().showDocument(url);
+		var dto = new CreateDeviceDto();
+		dto.id = deviceId;
+		dto.name = deviceNameField.getText();
+		dto.publicKey = BaseEncoding.base64Url().omitPadding().encode(deviceKey);
+		var json = GSON.toJson(dto); // TODO: do we want to keep GSON? doesn't support records -.-
+		var request = HttpRequest.newBuilder(keyUri) //
+				.header("Authorization", "Bearer " + bearerToken) //
+				.header("Content-Type", "application/json").PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
+				.build();
+		httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()) //
+				.thenAcceptAsync(this::registrationSucceeded, Platform::runLater) //
+				.exceptionally(this::registrationFailed);
+	}
+
+	private void registrationSucceeded(HttpResponse<Void> voidHttpResponse) {
+		LOG.info("Registered!");
+		window.close(); // TODO: show visual feedback "please wait for device authorization"
+	}
+
+	private Void registrationFailed(Throwable cause) {
+		result.completeExceptionally(cause);
+		return null;
 	}
 
 	@FXML
@@ -60,26 +95,14 @@ public class RegisterDeviceController implements FxController {
 	}
 
 	private void windowClosed(WindowEvent windowEvent) {
-		// if not already interacted, mark this workflow as cancelled:
-		if (result.awaitingInteraction().get()) {
-			result.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
-		}
-	}
-
-	private static String computeVerificationHash(String input) {
-		try {
-			var digest = MessageDigest.getInstance("SHA-256");
-			digest.update(StandardCharsets.UTF_8.encode(input));
-			return BaseEncoding.base64Url().omitPadding().encode(digest.digest());
-		} catch (NoSuchAlgorithmException e) {
-			throw new IllegalStateException("Every implementation of the Java platform is required to support SHA-256.");
-		}
+		result.cancel(true);
 	}
 
 	/* Getter */
 
-	public String getVerificationCode() {
-		return verificationCode;
+	public String getUserName() {
+		return jwt.getClaim("email").asString();
 	}
 
+
 }

+ 5 - 12
src/main/java/org/cryptomator/ui/keyloading/hub/UnauthorizedDeviceController.java

@@ -1,27 +1,24 @@
 package org.cryptomator.ui.keyloading.hub;
 
+import com.nimbusds.jose.JWEObject;
 import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.common.UserInteractionLock;
 import org.cryptomator.ui.keyloading.KeyLoading;
 import org.cryptomator.ui.keyloading.KeyLoadingScoped;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
 import javafx.fxml.FXML;
 import javafx.stage.Stage;
 import javafx.stage.WindowEvent;
+import java.util.concurrent.CompletableFuture;
 
 @KeyLoadingScoped
 public class UnauthorizedDeviceController implements FxController {
 
-	private static final Logger LOG = LoggerFactory.getLogger(UnauthorizedDeviceController.class);
-
 	private final Stage window;
-	private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result;
+	private final CompletableFuture<JWEObject> result;
 
 	@Inject
-	public UnauthorizedDeviceController(@KeyLoading Stage window, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result) {
+	public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture<JWEObject> result) {
 		this.window = window;
 		this.result = result;
 		this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
@@ -33,10 +30,6 @@ public class UnauthorizedDeviceController implements FxController {
 	}
 
 	private void windowClosed(WindowEvent windowEvent) {
-		// if not already interacted, mark this workflow as cancelled:
-		if (result.awaitingInteraction().get()) {
-			LOG.debug("Authorization cancelled. Device not authorized.");
-			result.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
-		}
+		result.cancel(true);
 	}
 }

+ 11 - 13
src/main/resources/fxml/hub_register_device.fxml

@@ -1,16 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
+<?import org.cryptomator.ui.controls.FormattedLabel?>
 <?import javafx.geometry.Insets?>
 <?import javafx.scene.control.Button?>
 <?import javafx.scene.control.ButtonBar?>
 <?import javafx.scene.control.Label?>
+<?import javafx.scene.control.TextField?>
 <?import javafx.scene.image.Image?>
 <?import javafx.scene.image.ImageView?>
 <?import javafx.scene.layout.HBox?>
 <?import javafx.scene.layout.VBox?>
-<?import javafx.scene.text.Text?>
-<?import javafx.scene.text.TextFlow?>
 <VBox xmlns:fx="http://javafx.com/fxml"
 	  xmlns="http://javafx.com/javafx"
 	  fx:controller="org.cryptomator.ui.keyloading.hub.RegisterDeviceController"
@@ -30,11 +29,14 @@
 			</HBox>
 
 			<VBox spacing="6">
-				<Label text="This device is not yet known to Cryptomator Hub. Please register this device first." wrapText="true"/>
-				<TextFlow styleClass="text-flow">
-					<Text text="Verification Code: "/>
-					<Text styleClass="label-large" text="${controller.verificationCode}"/>
-				</TextFlow>
+				<Label text="TODO This device is not yet known to Cryptomator Hub. Please register this device first." wrapText="true"/>
+				<HBox spacing="6" alignment="CENTER_LEFT">
+					<FormattedLabel format="TODO User %s" arg1="${controller.userName}"/>
+				</HBox>
+				<HBox spacing="6" alignment="CENTER_LEFT">
+					<Label text="TODO Device Name" labelFor="$deviceNameField"/>
+					<TextField fx:id="deviceNameField"/>
+				</HBox>
 			</VBox>
 		</HBox>
 
@@ -42,11 +44,7 @@
 			<ButtonBar buttonMinWidth="120" buttonOrder="+CU">
 				<buttons>
 					<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
-					<Button text="Register Device" ButtonBar.buttonData="OTHER" defaultButton="true" onAction="#browse" contentDisplay="LEFT">
-						<graphic>
-							<FontAwesome5IconView glyph="LINK" glyphSize="12"/>
-						</graphic>
-					</Button>
+					<Button text="TODO Register Device" ButtonBar.buttonData="OTHER" defaultButton="true" onAction="#register"/>
 				</buttons>
 			</ButtonBar>
 		</VBox>