diff --git a/client/src/main/java/envoy/client/data/ClientConfig.java b/client/src/main/java/envoy/client/data/ClientConfig.java index a7763e0..1d6498e 100644 --- a/client/src/main/java/envoy/client/data/ClientConfig.java +++ b/client/src/main/java/envoy/client/data/ClientConfig.java @@ -35,8 +35,6 @@ public final class ClientConfig extends Config { put("server", "s", identity()); put("port", "p", Integer::parseInt); put("localDB", "db", File::new); - put("user", "u", identity()); - put("password", "pw", identity()); } /** @@ -56,28 +54,4 @@ public final class ClientConfig extends Config { * @since Envoy Client v0.1-alpha */ public File getLocalDB() { return (File) items.get("localDB").get(); } - - /** - * @return the user name - * @since Envoy Client v0.3-alpha - */ - public String getUser() { - final String user = (String) items.get("user").get(); - return user.equals("") ? null : user; - } - - /** - * @return the password - * @since Envoy Client v0.3-alpha - */ - public String getPassword() { - final String password = (String) items.get("password").get(); - return password.equals("") ? null : password; - } - - /** - * @return {@code true} if user name and password are set - * @since Envoy Client v0.3-alpha - */ - public boolean hasLoginCredentials() { return getUser() != null && getPassword() != null; } } diff --git a/client/src/main/java/envoy/client/data/LocalDB.java b/client/src/main/java/envoy/client/data/LocalDB.java index d44caa0..63e567a 100644 --- a/client/src/main/java/envoy/client/data/LocalDB.java +++ b/client/src/main/java/envoy/client/data/LocalDB.java @@ -8,6 +8,10 @@ import envoy.data.*; import envoy.event.*; import envoy.util.SerializationUtils; +import dev.kske.eventbus.Event; +import dev.kske.eventbus.EventBus; +import dev.kske.eventbus.EventListener; + /** * Stores information about the current {@link User} and their {@link Chat}s. * For message ID generation a {@link IDGenerator} is stored as well. @@ -21,7 +25,7 @@ import envoy.util.SerializationUtils; * @author Kai S. K. Engelbart * @since Envoy Client v0.3-alpha */ -public final class LocalDB { +public final class LocalDB implements EventListener { private User user; private Map users = new HashMap<>(); @@ -29,7 +33,8 @@ public final class LocalDB { private IDGenerator idGenerator; private CacheMap cacheMap = new CacheMap(); private Instant lastSync = Instant.EPOCH; - private File dbDir, userFile, idGeneratorFile, usersFile; + private String authToken; + private File dbDir, userFile, idGeneratorFile, lastLoginFile, usersFile; /** * Constructs an empty local database. To serialize any user-specific data to @@ -42,6 +47,7 @@ public final class LocalDB { */ public LocalDB(File dbDir) throws IOException { this.dbDir = dbDir; + EventBus.getInstance().registerListener(this); // Test if the database directory is actually a directory if (dbDir.exists() && !dbDir.isDirectory()) @@ -49,6 +55,7 @@ public final class LocalDB { // Initialize global files idGeneratorFile = new File(dbDir, "id_gen.db"); + lastLoginFile = new File(dbDir, "last_login.db"); usersFile = new File(dbDir, "users.db"); // Initialize offline caches @@ -76,12 +83,16 @@ public final class LocalDB { * @since Envoy Client v0.3-alpha */ public void save(boolean isOnline) throws IOException { + // Save users SerializationUtils.write(usersFile, users); // Save user data and last sync time stamp if (user != null) SerializationUtils.write(userFile, chats, cacheMap, isOnline ? Instant.now() : lastSync); + // Save last login information + if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken); + // Save id generator if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator); } @@ -120,10 +131,24 @@ public final class LocalDB { idGenerator = SerializationUtils.read(idGeneratorFile, IDGenerator.class); } catch (ClassNotFoundException | IOException e) {} } + + /** + * Loads the last login information. Any exception thrown during this process is + * ignored. + * + * @since Envoy Client v0.2-beta + */ + public void loadLastLogin() { + try (var in = new ObjectInputStream(new FileInputStream(lastLoginFile))) { + user = (User) in.readObject(); + authToken = (String) in.readObject(); + } catch (ClassNotFoundException | IOException e) {} + } + /** * Synchronizes the contact list of the client user with the chat and user * storage. - * + * * @since Envoy Client v0.1-beta */ public void synchronize() { @@ -143,6 +168,11 @@ public final class LocalDB { .forEach(chats::add); } + @Event + private void onNewAuthToken(NewAuthToken evt) { + authToken = evt.get(); + } + /** * @return a {@code Map} of all users stored locally with their * user names as keys @@ -204,6 +234,12 @@ public final class LocalDB { */ public Instant getLastSync() { return lastSync; } + /** + * @return the authentication token of the user + * @since Envoy Client v0.2-beta + */ + public String getAuthToken() { return authToken; } + /** * Searches for a message by ID. * @@ -217,7 +253,7 @@ public final class LocalDB { /** * Searches for a chat by recipient ID. - * + * * @param recipientID the ID of the chat's recipient * @return an optional containing the chat * @since Envoy Client v0.1-beta diff --git a/client/src/main/java/envoy/client/net/Client.java b/client/src/main/java/envoy/client/net/Client.java index 0db67eb..9e41343 100644 --- a/client/src/main/java/envoy/client/net/Client.java +++ b/client/src/main/java/envoy/client/net/Client.java @@ -77,10 +77,12 @@ public final class Client implements EventListener, Closeable { // Create object receiver receiver = new Receiver(socket.getInputStream()); - // Register user creation processor, contact list processor and message cache + // Register user creation processor, contact list processor, message cache and + // authentication token receiver.registerProcessor(User.class, sender -> this.sender = sender); receiver.registerProcessors(cacheMap.getMap()); receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); }); + receiver.registerProcessor(NewAuthToken.class, eventBus::dispatch); rejected = false; @@ -173,8 +175,7 @@ public final class Client implements EventListener, Closeable { receiver.registerProcessor(ProfilePicChange.class, eventBus::dispatch); // Process requests to not send any more attachments as they will not be shown - // to - // other users + // to other users receiver.registerProcessor(NoAttachments.class, eventBus::dispatch); // Process group creation results - they might have been disabled on the server diff --git a/client/src/main/java/envoy/client/ui/Startup.java b/client/src/main/java/envoy/client/ui/Startup.java index 30757c5..0b94d8a 100644 --- a/client/src/main/java/envoy/client/ui/Startup.java +++ b/client/src/main/java/envoy/client/ui/Startup.java @@ -77,27 +77,40 @@ public final class Startup extends Application { // Prepare handshake localDB.loadIDGenerator(); + localDB.loadLastLogin(); context.setLocalDB(localDB); + // Configure stage stage.setTitle("Envoy"); stage.getIcons().add(IconUtil.loadIcon("envoy_logo")); + // Create scene context final var sceneContext = new SceneContext(stage); context.setSceneContext(sceneContext); - // Perform automatic login if configured - if (config.hasLoginCredentials()) - performHandshake(new LoginCredentials(config.getUser(), config.getPassword(), false, Startup.VERSION, loadLastSync(config.getUser()))); - else sceneContext.load(SceneInfo.LOGIN_SCENE); + // Authenticate with token if present + if (localDB.getAuthToken() != null) { + logger.info("Attempting authentication with token..."); + localDB.initializeUserStorage(); + localDB.loadUserData(); + if (!performHandshake( + LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(), VERSION, localDB.getLastSync()))) + sceneContext.load(SceneInfo.LOGIN_SCENE); + } else { + + // Load login scene + sceneContext.load(SceneInfo.LOGIN_SCENE); + } } /** * Tries to perform a Handshake with the server. * * @param credentials the credentials to use for the handshake + * @return whether the handshake was successful or offline mode could be entered * @since Envoy Client v0.2-beta */ - public static void performHandshake(LoginCredentials credentials) { + public static boolean performHandshake(LoginCredentials credentials) { final var cacheMap = new CacheMap(); cacheMap.put(Message.class, new Cache()); cacheMap.put(GroupMessage.class, new Cache()); @@ -109,10 +122,13 @@ public final class Startup extends Application { if (client.isOnline()) { loadChatScene(); client.initReceiver(localDB, cacheMap); + return true; + } else { + return false; } } catch (IOException | InterruptedException | TimeoutException e) { logger.log(Level.INFO, "Could not connect to server. Entering offline mode..."); - attemptOfflineMode(credentials.getIdentifier()); + return attemptOfflineMode(credentials.getIdentifier()); } } @@ -121,9 +137,10 @@ public final class Startup extends Application { * for a given user. * * @param identifier the identifier of the user - currently his username + * @return whether the offline mode could be entered * @since Envoy Client v0.2-beta */ - public static void attemptOfflineMode(String identifier) { + public static boolean attemptOfflineMode(String identifier) { try { // Try entering offline mode localDB.loadUsers(); @@ -131,10 +148,12 @@ public final class Startup extends Application { if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown"); client.setSender(clientUser); loadChatScene(); + return true; } catch (final Exception e) { new Alert(AlertType.ERROR, "Client error: " + e).showAndWait(); logger.log(Level.SEVERE, "Offline mode could not be loaded: ", e); System.exit(1); + return false; } } diff --git a/client/src/main/java/envoy/client/ui/controller/LoginScene.java b/client/src/main/java/envoy/client/ui/controller/LoginScene.java index 506170e..285f3de 100644 --- a/client/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/client/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -1,5 +1,6 @@ package envoy.client.ui.controller; +import java.time.Instant; import java.util.logging.*; import javafx.fxml.FXML; @@ -45,6 +46,9 @@ public final class LoginScene implements EventListener { @FXML private Button loginButton; + @FXML + private CheckBox cbStaySignedIn; + @FXML private Button offlineModeButton; @@ -54,10 +58,10 @@ public final class LoginScene implements EventListener { @FXML private ImageView logo; - private boolean registration = false; + private boolean registration; - private static final Logger logger = EnvoyLog.getLogger(LoginScene.class); - private static final ClientConfig config = ClientConfig.getInstance(); + private static final Logger logger = EnvoyLog.getLogger(LoginScene.class); + private static final ClientConfig config = ClientConfig.getInstance(); @FXML private void initialize() { @@ -74,16 +78,21 @@ public final class LoginScene implements EventListener { @FXML private void loginButtonPressed() { + final String user = userTextField.getText(), pass = passwordField.getText(), repeatPass = repeatPasswordField.getText(); + final boolean requestToken = cbStaySignedIn.isSelected(); // Prevent registration with unequal passwords - if (registration && !passwordField.getText().equals(repeatPasswordField.getText())) { + if (registration && !pass.equals(repeatPass)) { new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait(); repeatPasswordField.clear(); - } else if (!Bounds.isValidContactName(userTextField.getText())) { + } else if (!Bounds.isValidContactName(user)) { new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait(); userTextField.clear(); - } else Startup.performHandshake(new LoginCredentials(userTextField.getText(), passwordField.getText(), registration, Startup.VERSION, - Startup.loadLastSync(userTextField.getText()))); + } else { + Instant lastSync = Startup.loadLastSync(userTextField.getText()); + Startup.performHandshake(registration ? LoginCredentials.registration(user, pass, requestToken, Startup.VERSION, lastSync) + : LoginCredentials.login(user, pass, requestToken, Startup.VERSION, lastSync)); + } } @FXML diff --git a/client/src/main/java/module-info.java b/client/src/main/java/module-info.java index bfcf8d2..4382521 100644 --- a/client/src/main/java/module-info.java +++ b/client/src/main/java/module-info.java @@ -23,4 +23,5 @@ module envoy.client { opens envoy.client.ui.custom to javafx.graphics, javafx.fxml; opens envoy.client.ui.settings to envoy.client.util; opens envoy.client.net to dev.kske.eventbus; + opens envoy.client.data to dev.kske.eventbus; } diff --git a/client/src/main/resources/fxml/LoginScene.fxml b/client/src/main/resources/fxml/LoginScene.fxml index 1ade0f8..e1316c4 100644 --- a/client/src/main/resources/fxml/LoginScene.fxml +++ b/client/src/main/resources/fxml/LoginScene.fxml @@ -2,6 +2,7 @@ + @@ -32,9 +33,7 @@ -