diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8fb11b1..1bd18cd 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/dev/ksan/CASystem/KeystoreService.java b/src/main/java/dev/ksan/CASystem/KeystoreService.java
index 5fcc2d2..51aea48 100644
--- a/src/main/java/dev/ksan/CASystem/KeystoreService.java
+++ b/src/main/java/dev/ksan/CASystem/KeystoreService.java
@@ -3,6 +3,7 @@ package dev.ksan.CASystem;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@@ -18,7 +19,7 @@ public class KeystoreService {
private static String KEYSTORE_TYPE = "PKCS12";
private static String KEY_ALIAS = "key";
- private static final Path USER_KEYSORE_DIR = Path.of("keystores/users");
+ private static final Path USER_KEYSTORE_DIR = Path.of("keystores/users");
private static final Path CA_KEYSTORE_DIR = Path.of("keystores/ca");
@@ -35,7 +36,7 @@ public class KeystoreService {
keyStore.setKeyEntry(KEY_ALIAS, privateKey, keystorePassword, chain);
- Path path = USER_KEYSORE_DIR.resolve(username + ".p12");
+ Path path = USER_KEYSTORE_DIR.resolve(username + ".p12");
Files.createDirectories(path.getParent());
@@ -46,14 +47,14 @@ public class KeystoreService {
}
public static PrivateKey getPrivateKey(String username, char[] keystorePassword) throws Exception {
- KeyStore keystore = loadKeyStore(USER_KEYSORE_DIR.resolve(username+".p12"), keystorePassword);
+ KeyStore keystore = loadKeyStore(USER_KEYSTORE_DIR.resolve(username+".p12"), keystorePassword);
return (PrivateKey) keystore.getKey(KEY_ALIAS, keystorePassword);
}
public static X509Certificate getCertificate(String username, char[] keystorePassword) throws Exception {
- KeyStore keyStore = loadKeyStore(USER_KEYSORE_DIR.resolve(username+".p12"), keystorePassword);
+ KeyStore keyStore = loadKeyStore(USER_KEYSTORE_DIR.resolve(username+".p12"), keystorePassword);
return (X509Certificate) keyStore.getCertificate(KEY_ALIAS);
}
@@ -122,6 +123,7 @@ public class KeystoreService {
KeyStore ks = loadKeystore(CA_KEYSTORE_DIR.resolve(caName + ".p12"), caPassword);
return (PrivateKey) ks.getKey(KEY_ALIAS, caPassword);
}
+
private static KeyStore loadKeystore(Path path, char[] password) throws Exception {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE);
try (InputStream is = Files.newInputStream(path)) {
@@ -129,4 +131,5 @@ public class KeystoreService {
}
return ks;
}
+
}
diff --git a/src/main/java/dev/ksan/CASystem/LoginService.java b/src/main/java/dev/ksan/CASystem/LoginService.java
index 0455ce3..2483f8c 100644
--- a/src/main/java/dev/ksan/CASystem/LoginService.java
+++ b/src/main/java/dev/ksan/CASystem/LoginService.java
@@ -108,6 +108,7 @@ public class LoginService {
user.setFailedLoginAttempts(user.getFailedLoginAttempts() + 1);
+ System.out.println("Attempt failed: " + user.getFailedLoginAttempts());
if (user.getFailedLoginAttempts() >= 3) {
user.setRevoked(true);
diff --git a/src/main/java/dev/ksan/Users/User.java b/src/main/java/dev/ksan/Users/User.java
index 1e175f2..4c23457 100644
--- a/src/main/java/dev/ksan/Users/User.java
+++ b/src/main/java/dev/ksan/Users/User.java
@@ -17,7 +17,6 @@ public abstract class User implements Serializable {
public User(){
- //generate key
}
public PublicKey getPublicKey() {
diff --git a/src/main/java/dev/ksan/Users/UserRepository.java b/src/main/java/dev/ksan/Users/UserRepository.java
index 57a4e69..ac517ea 100644
--- a/src/main/java/dev/ksan/Users/UserRepository.java
+++ b/src/main/java/dev/ksan/Users/UserRepository.java
@@ -136,20 +136,35 @@ public class UserRepository {
StandardCopyOption.REPLACE_EXISTING);
}
+ //this was annoying i dont wannt look at java.nio.channels.ClosedChannelException again
private static Object deserialize(Path path) throws Exception {
if (!Files.exists(path)) return null;
- try (FileInputStream fis = new FileInputStream(path.toFile());
- FileLock fileLock = fis.getChannel().tryLock(0, Long.MAX_VALUE, true); // shared read lock
- ObjectInputStream ois = new ObjectInputStream(fis)) {
+ FileInputStream fis = new FileInputStream(path.toFile());
+ FileLock fileLock = null;
+
+ try {
+ fileLock = fis.getChannel().tryLock(0, Long.MAX_VALUE, true);
if (fileLock == null) {
throw new IllegalStateException("Could not acquire read lock on: " + path);
}
- return ois.readObject();
+
+ ObjectInputStream ois = new ObjectInputStream(fis);
+ Object obj = ois.readObject();
+ ois.close(); // close stream AFTER reading
+
+ return obj;
+
+ } finally {
+ if (fileLock != null && fileLock.isValid()) {
+ fileLock.release(); // release FIRST
+ }
+ fis.close(); // then close channel
}
}
+
private static List findAll(
Path dir, Class type, String lockPrefix) throws Exception {
diff --git a/src/main/java/dev/ksan/Users/Voter.java b/src/main/java/dev/ksan/Users/Voter.java
index 6b60be1..dca04b8 100644
--- a/src/main/java/dev/ksan/Users/Voter.java
+++ b/src/main/java/dev/ksan/Users/Voter.java
@@ -9,7 +9,7 @@ public class Voter extends User {
private String lastName;
private String username;
- public Voter(String name, String username, String lastName){
+ public Voter(String name, String lastName, String username){
super();
this.name = name;
this.username = username;
diff --git a/src/main/java/dev/ksan/ui/HomeController.java b/src/main/java/dev/ksan/ui/HomeController.java
index 40d908c..907e115 100644
--- a/src/main/java/dev/ksan/ui/HomeController.java
+++ b/src/main/java/dev/ksan/ui/HomeController.java
@@ -23,7 +23,7 @@ public class HomeController {
@FXML
public void initialize() {
- loginButton.setOnAction(e -> System.out.println("Go to login"));
+ loginButton.setOnAction(e -> goToLogin());
registerButton.setOnAction(e -> goToRegister());
}
@@ -47,4 +47,23 @@ public class HomeController {
}
}
+ private void goToLogin() {
+ try {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/LoginView.fxml"));
+ Scene oldScene = stage.getScene();
+ Scene scene = new Scene(loader.load(),
+ oldScene.getWidth(),
+ oldScene.getHeight()
+ );
+
+ LoginController controller = loader.getController();
+ controller.setStage(stage);
+
+ stage.setScene(scene);
+ stage.setTitle("Register");
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/dev/ksan/ui/LoginController.java b/src/main/java/dev/ksan/ui/LoginController.java
new file mode 100644
index 0000000..575efaf
--- /dev/null
+++ b/src/main/java/dev/ksan/ui/LoginController.java
@@ -0,0 +1,313 @@
+package dev.ksan.ui;
+
+import dev.ksan.CASystem.CAType;
+import dev.ksan.CASystem.KeystoreService;
+import dev.ksan.CASystem.LoginService;
+import dev.ksan.Users.Organizer;
+import dev.ksan.Users.User;
+import dev.ksan.Users.Voter;
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+import javafx.scene.input.Dragboard;
+import javafx.scene.input.TransferMode;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.stage.FileChooser;
+import javafx.stage.Stage;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+public class LoginController {
+
+ @FXML private StackPane dropZone;
+ //currently everything is saved as .p12? so we also need to insert password here???? maybe??
+ // or change it so that the user just has the plain cert and we hand that in? and we get
+ // the private only key then after they login finally
+ // this is the password field for the p12 keystore
+ @FXML private PasswordField passwordField;
+ @FXML private Label dropZoneLabel;
+ @FXML private Label errorLabel;
+ @FXML private Button loginButton;
+
+ @FXML private VBox form1;
+ @FXML private VBox form2;
+ @FXML
+ private Button finishLoginButton;
+
+ @FXML
+ private TextField usernameField;
+
+ @FXML
+ private PasswordField userPasswordField;
+
+
+ private User certUser;
+
+ private Stage stage;
+ private File droppedFile;
+ private X509Certificate loadedCert;
+ private CAType detectedCAType;
+
+ public void setStage(Stage stage) {
+ this.stage = stage;
+ }
+
+ @FXML
+ public void initialize() {
+ showForm1();
+ dropZone.setStyle(
+ "-fx-border-color: #888;" +
+ "-fx-border-width: 2;" +
+ "-fx-border-radius: 8;" +
+ "-fx-background-radius: 8;" +
+ "-fx-background-color: #f9f9f9;"
+ );
+
+ errorLabel.setVisible(false);
+ loginButton.setDisable(true); // disabled until cert + password are provided
+
+ setupDropZone();
+
+ passwordField.textProperty().addListener((obs, oldVal, newVal) -> {
+ loginButton.setDisable(droppedFile == null || newVal.trim().isEmpty());
+ });
+ userPasswordField.textProperty().addListener((obs, oldVal, newVal) -> {
+ finishLoginButton.setDisable( newVal.trim().isEmpty());
+ });
+ }
+
+ private void setupDropZone() {
+ dropZone.setOnDragEntered(e -> {
+ if (e.getDragboard().hasFiles())
+ dropZone.setStyle(dropZone.getStyle() + "-fx-border-color: #4A90D9;");
+ });
+
+ dropZone.setOnDragExited(e -> {
+ dropZone.setStyle(dropZone.getStyle().replace("-fx-border-color: #4A90D9;", "-fx-border-color: #888;"));
+ });
+
+ dropZone.setOnDragOver(event -> {
+ if (event.getGestureSource() != dropZone && event.getDragboard().hasFiles()) {
+ event.acceptTransferModes(TransferMode.COPY);
+ }
+ event.consume();
+ });
+
+ dropZone.setOnDragDropped(event -> {
+ Dragboard db = event.getDragboard();
+ boolean success = false;
+
+ if (db.hasFiles()) {
+ File file = db.getFiles().get(0);
+ if (file.getName().endsWith(".p12")) {
+ droppedFile = file;
+ dropZoneLabel.setText("📄 " + file.getName());
+ success = true;
+ showError("Certificate loaded. Enter your password and click Login.");
+ } else {
+ showError("Please drop a .p12 keystore file.");
+ }
+ }
+
+ event.setDropCompleted(success);
+ event.consume();
+ });
+
+ dropZone.setOnMouseClicked(e -> browseForKeystore());
+ }
+
+ private void browseForKeystore() {
+ FileChooser fileChooser = new FileChooser();
+ fileChooser.setTitle("Select your keystore (.p12)");
+ fileChooser.getExtensionFilters().add(
+ new FileChooser.ExtensionFilter("PKCS12 Keystore", "*.p12"));
+
+ File file = fileChooser.showOpenDialog(stage);
+ if (file != null) {
+ droppedFile = file;
+ dropZoneLabel.setText("📄 " + file.getName());
+ }
+ }
+
+ @FXML
+ public void onLoginClicked() {
+ clearError();
+
+ if (droppedFile == null) {
+ showError("Please provide your keystore (.p12) file.");
+ return;
+ }
+
+ String password = passwordField.getText();
+ if (password.isBlank()) {
+ showError("Please enter your password.");
+ return;
+ }
+
+ String identity = droppedFile.getName().replace(".p12", "");
+
+ loginButton.setDisable(true);
+
+ Task loginTask = new Task<>() {
+ @Override
+ protected User call() throws Exception {
+
+ char[] ksPassword = KeystoreService.deriveKeystorePassword(password, identity);
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+
+ try (InputStream is = new FileInputStream(droppedFile)) {
+ ks.load(is, Arrays.copyOf(ksPassword, ksPassword.length));
+ } catch (Exception e) {
+ // TODO
+ // if this also counts as unsuccessful attempt then we need
+ // to add handle failed attempt here too
+ }
+
+
+ X509Certificate cert = (X509Certificate) ks.getCertificate("key");
+ if (cert == null)
+ throw new SecurityException("No certificate found in keystore.");
+
+ CAType caType = LoginService.validateCertificate(cert);
+
+
+ showForm2();
+ loadedCert = cert;
+ detectedCAType = caType;
+ return LoginService.validateCredentials(identity, password, cert, caType);
+ }
+ };
+
+ loginTask.setOnSucceeded(e -> {
+ User user = loginTask.getValue();
+ certUser=user;
+ });
+
+ loginTask.setOnFailed(e -> {
+ certUser=null;
+ Throwable ex = loginTask.getException();
+ showError(ex.getMessage());
+ loginButton.setDisable(false);
+ });
+
+ new Thread(loginTask).start();
+ }
+ @FXML
+ public void onFinalLoginClicked() {
+ clearError();
+
+
+ String password = userPasswordField.getText();
+ if (password.isBlank()) {
+ showError("Please enter your password.");
+ return;
+ }
+
+
+ finishLoginButton.setDisable(true);
+
+ Task loginTask = new Task<>() {
+ @Override
+ protected Boolean call() throws Exception {
+
+
+ return userLogin();
+ }
+ };
+
+ loginTask.setOnSucceeded(e -> {
+ navigateToUserUI(certUser);
+ });
+
+ loginTask.setOnFailed(e -> {
+ certUser = null;
+ loadedCert = null;
+ detectedCAType = null;
+ Throwable ex = loginTask.getException();
+ showError(ex.getMessage());
+ finishLoginButton.setDisable(true);
+ });
+
+ new Thread(loginTask).start();
+ }
+
+ private boolean userLogin() {
+
+ try{
+
+ User user = LoginService.validateCredentials(usernameField.getText().trim(),
+ userPasswordField.getText().trim(), loadedCert, detectedCAType);
+ return user == certUser;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return true;
+
+ }
+
+
+ // TODO maybe pass in the certificate and/or keypair?
+ private void navigateToUserUI(User user) {
+ try {
+
+ if (user instanceof Organizer organizer) {
+ FXMLLoader loader = new FXMLLoader(
+ getClass().getResource("/fxml/OrganizerView.fxml"));
+ Parent root = loader.load();
+ OrganizerController controller = loader.getController();
+ controller.setOrganizer(organizer);
+ controller.setStage(stage);
+ stage.setScene(new Scene(root));
+
+ } else if (user instanceof Voter voter) {
+ FXMLLoader loader = new FXMLLoader(
+ getClass().getResource("/fxml/VoterView.fxml"));
+ Parent root = loader.load();
+ VoterController controller = loader.getController();
+ controller.setVoter(voter);
+ controller.setStage(stage);
+ stage.setScene(new Scene(root));
+ }
+
+ } catch (Exception e) {
+ showError("Failed to load UI: " + e.getMessage());
+ loginButton.setDisable(false);
+ }
+ }
+
+ private void showError(String message) {
+ Platform.runLater(() -> {
+ errorLabel.setText(message);
+ errorLabel.setVisible(true);
+ });
+ }
+
+ private void clearError() {
+ errorLabel.setText("");
+ errorLabel.setVisible(false);
+ }
+
+
+ @FXML
+ private void showForm2() {
+ form1.setVisible(false);
+ form2.setVisible(true);
+ }
+
+ @FXML
+ private void showForm1() {
+ form1.setVisible(true);
+ form2.setVisible(false);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/dev/ksan/ui/OrganizerController.java b/src/main/java/dev/ksan/ui/OrganizerController.java
new file mode 100644
index 0000000..53b94f8
--- /dev/null
+++ b/src/main/java/dev/ksan/ui/OrganizerController.java
@@ -0,0 +1,18 @@
+package dev.ksan.ui;
+
+import dev.ksan.Users.Organizer;
+import javafx.stage.Stage;
+
+public class OrganizerController {
+ private Organizer organizer;
+
+ private Stage stage;
+ public void setOrganizer(Organizer organizer) {
+ this.organizer = organizer;
+ }
+
+ public void setStage(Stage stage) {
+ this.stage = stage;
+
+ }
+}
diff --git a/src/main/java/dev/ksan/ui/RegisterOrganizerController.java b/src/main/java/dev/ksan/ui/RegisterOrganizerController.java
index 6f38270..23c85f6 100644
--- a/src/main/java/dev/ksan/ui/RegisterOrganizerController.java
+++ b/src/main/java/dev/ksan/ui/RegisterOrganizerController.java
@@ -1,5 +1,8 @@
package dev.ksan.ui;
+import dev.ksan.CASystem.RegistrationService;
+import dev.ksan.Users.Organizer;
+import dev.ksan.Users.Voter;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
@@ -31,4 +34,69 @@ public class RegisterOrganizerController {
public void setStage(Stage stage) {
this.stage = stage;
}
+
+ @FXML
+ public void initialize() {
+ registerButton.setOnAction(event -> tryRegisterVoter());
+ }
+
+ private void tryRegisterVoter() {
+
+ try {
+ if (!checkAndHighlightFields()) return;
+
+ Organizer organizer = new Organizer(nameField.getText().trim(),idField.getText().trim());
+
+
+
+
+ RegistrationService.registerOrganizer(organizer, passwordField.getText());
+
+ }catch (Exception e) {
+ displayError("Username already Taken");
+ }
+
+ }
+
+ //TODO
+ private void loginUser(Voter voter) {
+ }
+
+
+ //TODO
+ private void displayError(String usernameAlreadyTaken) {
+ }
+
+ // return true if everything is filled
+ private boolean checkAndHighlightFields() {
+ if (nameField.getText().trim().isEmpty()) {
+ nameField.setStyle("-fx-text-fill: red");
+ return false;
+ }else{
+ nameField.setStyle("-fx-text-fill: white");
+ }
+ if (idField.getText().isEmpty()) {
+ idField.setStyle("-fx-text-fill: red");
+ return false;
+ }else{
+ idField.setStyle("-fx-text-fill: white");
+ }
+ if (passwordField.getText().isEmpty()) {
+ passwordField.setStyle("-fx-text-fill: red");
+ return false;
+ }else{
+ passwordField.setStyle("-fx-text-fill: white");
+ }
+ if (confirmPasswordField.getText().isEmpty() || !confirmPasswordField.getText().equals(passwordField.getText())) {
+ confirmPasswordField.setStyle("-fx-text-fill: red");
+ displayError("Passwords do not match");
+ return false;
+ }else{
+ confirmPasswordField.setStyle("-fx-text-fill: white");
+ }
+
+
+ return true;
+ }
+
}
diff --git a/src/main/java/dev/ksan/ui/RegisterVoterController.java b/src/main/java/dev/ksan/ui/RegisterVoterController.java
index 213f0b6..e44fe77 100644
--- a/src/main/java/dev/ksan/ui/RegisterVoterController.java
+++ b/src/main/java/dev/ksan/ui/RegisterVoterController.java
@@ -1,5 +1,8 @@
package dev.ksan.ui;
+import dev.ksan.CASystem.RegistrationService;
+import dev.ksan.Users.UserRepository;
+import dev.ksan.Users.Voter;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
@@ -38,6 +41,79 @@ public class RegisterVoterController {
}
@FXML
public void initialize() {
+ registerButton.setOnAction(event -> tryRegisterVoter());
}
+ private void tryRegisterVoter() {
+
+ System.out.println("Registering voter");
+ try {
+ if (!checkAndHighlightFields()) return;
+
+ Voter voter = new Voter(nameField.getText().trim(), surnameField.getText().trim(),
+ usernameField.getText().trim());
+
+
+
+ RegistrationService.registerVoter(voter, passwordField.getText());
+
+ loginUser(voter);
+ }catch (Exception e) {
+ displayError("Username already Taken");
+ }
+
+ }
+
+ //TODO
+ private void loginUser(Voter voter) {
+ System.out.println("registered: " + voter);
+ }
+
+
+ //TODO
+ private void displayError(String usernameAlreadyTaken) {
+ }
+
+ // return true if everything is filled
+ private boolean checkAndHighlightFields() {
+ if (nameField.getText().isEmpty()) {
+ nameField.setStyle("-fx-text-fill: red");
+ return false;
+ }else{
+ nameField.setStyle("-fx-text-fill: black");
+ }
+ if (surnameField.getText().isEmpty()) {
+ surnameField.setStyle("-fx-text-fill: red");
+ return false;
+ }else{
+ surnameField.setStyle("-fx-text-fill: black");
+ }
+ if (usernameField.getText().isEmpty()) {
+ usernameField.setStyle("-fx-text-fill: red");
+ return false;
+ }else{
+ usernameField.setStyle("-fx-text-fill: black");
+ }
+ if (passwordField.getText().isEmpty()) {
+ passwordField.setStyle("-fx-text-fill: red");
+ return false;
+ }else{
+ passwordField.setStyle("-fx-text-fill: black");
+ }
+ if (confirmPasswordField.getText().isEmpty() || !confirmPasswordField.getText().equals(passwordField.getText())) {
+ confirmPasswordField.setStyle("-fx-text-fill: red");
+ displayError("Passwords do not match");
+ return false;
+ }else{
+ confirmPasswordField.setStyle("-fx-text-fill: black");
+ }
+
+ if(UserRepository.voterExists(usernameField.getText().trim())){
+ displayError("Username already Taken");
+ return false;
+ }
+ return true;
+ }
+
+
}
diff --git a/src/main/java/dev/ksan/ui/VoterController.java b/src/main/java/dev/ksan/ui/VoterController.java
new file mode 100644
index 0000000..726b9e0
--- /dev/null
+++ b/src/main/java/dev/ksan/ui/VoterController.java
@@ -0,0 +1,17 @@
+package dev.ksan.ui;
+
+import dev.ksan.Users.Voter;
+import javafx.stage.Stage;
+
+public class VoterController {
+ private Voter voter;
+ private Stage stage;
+
+ public void setVoter(Voter voter) {
+ this.voter = voter;
+ }
+
+ public void setStage(Stage stage) {
+ this.stage = stage;
+ }
+}
diff --git a/src/main/resources/fxml/LoginView.fxml b/src/main/resources/fxml/LoginView.fxml
index 71977ec..694bb47 100644
--- a/src/main/resources/fxml/LoginView.fxml
+++ b/src/main/resources/fxml/LoginView.fxml
@@ -1,14 +1,88 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/RegisterVoterView.fxml b/src/main/resources/fxml/RegisterVoterView.fxml
index 30a62f2..b769444 100644
--- a/src/main/resources/fxml/RegisterVoterView.fxml
+++ b/src/main/resources/fxml/RegisterVoterView.fxml
@@ -12,7 +12,6 @@
-
@@ -83,7 +82,7 @@
-
+
diff --git a/src/main/resources/fxml/VoterView.fxml b/src/main/resources/fxml/VoterView.fxml
index e69de29..6f7f04d 100644
--- a/src/main/resources/fxml/VoterView.fxml
+++ b/src/main/resources/fxml/VoterView.fxml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+