mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 10:11:27 +00:00
Merge pull request #1980 from cryptomator/feature/hub-integrated-device-registration
Register device from within client application
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
class CreateDeviceDto {
|
||||
|
||||
public String id;
|
||||
public String name;
|
||||
public String publicKey;
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user