Compare commits

...

3 commits

Author SHA1 Message Date
2009b68ccf
#8: Implement Login Logic
Some checks failed
Quality Check / Linting Check (push) Failing after 12s
Quality Check / Javadoc Check (push) Successful in 21s
Signed-off-by: Dominik Säume <Dominik.Saeume@hmmh.de>
2024-05-21 15:51:48 +02:00
b9eae8088f
#8: Implement Login Logic
Signed-off-by: Dominik Säume <Dominik.Saeume@hmmh.de>
2024-05-21 14:48:33 +02:00
375fbe4826
#8: Setup Model, DAO & Fixtures
Signed-off-by: Dominik Säume <Dominik.Saeume@hmmh.de>
2024-05-21 14:48:22 +02:00
12 changed files with 524 additions and 18 deletions

Binary file not shown.

View file

@ -1,6 +1,7 @@
package de.hitec.nhplus; package de.hitec.nhplus;
import de.hitec.nhplus.datastorage.ConnectionBuilder; import de.hitec.nhplus.datastorage.ConnectionBuilder;
import de.hitec.nhplus.login.LoginController;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
@ -35,7 +36,6 @@ public class Main extends Application {
public void start(Stage primaryStage) { public void start(Stage primaryStage) {
this.primaryStage = primaryStage; this.primaryStage = primaryStage;
executePassword(); executePassword();
//executeMainApplication();
} }
private void executePassword() { private void executePassword() {
@ -44,10 +44,19 @@ public class Main extends Application {
FXMLLoader loader = new FXMLLoader(Main.class.getResource("/de/hitec/nhplus/login/LoginView.fxml")); FXMLLoader loader = new FXMLLoader(Main.class.getResource("/de/hitec/nhplus/login/LoginView.fxml"));
BorderPane pane = loader.load(); BorderPane pane = loader.load();
Scene scene = new Scene(pane); Scene scene = new Scene(pane);
this.primaryStage.setTitle("NHPlus"); Stage loginStage = new Stage();
this.primaryStage.setScene(scene); loginStage.setTitle("NHPlus");
this.primaryStage.setResizable(true); loginStage.setScene(scene);
this.primaryStage.show(); loginStage.setResizable(false);
LoginController controller = loader.getController();
controller.initialize(loginStage);
loginStage.showAndWait();
if(controller.user == null){
executeMainApplication();
}
} catch (IOException exception) { } catch (IOException exception) {
exception.printStackTrace(); exception.printStackTrace();
} }

View file

@ -1,5 +1,6 @@
package de.hitec.nhplus.datastorage; package de.hitec.nhplus.datastorage;
import de.hitec.nhplus.login.database.UserDao;
import de.hitec.nhplus.medication.database.MedicationDao; import de.hitec.nhplus.medication.database.MedicationDao;
import de.hitec.nhplus.nurse.database.NurseDao; import de.hitec.nhplus.nurse.database.NurseDao;
import de.hitec.nhplus.patient.database.PatientDao; import de.hitec.nhplus.patient.database.PatientDao;
@ -63,4 +64,12 @@ public class DaoFactory {
public MedicationDao createMedicationDAO() { public MedicationDao createMedicationDAO() {
return new MedicationDao(ConnectionBuilder.getConnection()); return new MedicationDao(ConnectionBuilder.getConnection());
} }
/**
* @return A new {@link UserDao} instance with a database connection.
* @see de.hitec.nhplus.login.User User
*/
public UserDao createUserDAO() {
return new UserDao(ConnectionBuilder.getConnection());
}
} }

View file

@ -46,6 +46,11 @@ public class Fixtures {
medicationFixture.setupTable(connection); medicationFixture.setupTable(connection);
medicationFixture.load(); medicationFixture.load();
UserFixture userFixture = new UserFixture();
userFixture.dropTable(connection);
userFixture.setupTable(connection);
userFixture.load();
} catch (Exception exception) { } catch (Exception exception) {
System.out.println(exception.getMessage()); System.out.println(exception.getMessage());
} }

View file

@ -0,0 +1,83 @@
package de.hitec.nhplus.fixtures;
import de.hitec.nhplus.Main;
import de.hitec.nhplus.datastorage.DaoFactory;
import de.hitec.nhplus.login.User;
import de.hitec.nhplus.login.database.UserDao;
import de.hitec.nhplus.medication.Medication;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
public class UserFixture implements Fixture<User>{
private static final String SCHEMA = "/de/hitec/nhplus/login/database/User.sql";
private static final String PERMISSION_SCHEMA = "/de/hitec/nhplus/login/database/UserPermission.sql";
private static final String TO_NURSE_SCHEMA = "/de/hitec/nhplus/login/database/UserToNurse.sql";
@Override
public void dropTable(Connection connection) throws SQLException {
connection.createStatement().execute("DROP TABLE IF EXISTS user");
connection.createStatement().execute("DROP TABLE IF EXISTS user__permissions");
connection.createStatement().execute("DROP TABLE IF EXISTS user__nurse");
}
@Override
public void setupTable(Connection connection) throws SQLException {
final InputStream schema = Main.class.getResourceAsStream(SCHEMA);
final InputStream permissionSchema = Main.class.getResourceAsStream(PERMISSION_SCHEMA);
final InputStream toNurseSchema = Main.class.getResourceAsStream(TO_NURSE_SCHEMA);
assert schema != null;
assert permissionSchema != null;
assert toNurseSchema != null;
String SQL = new Scanner(schema, StandardCharsets.UTF_8)
.useDelimiter("\\A")
.next();
String permissionSQL = new Scanner(permissionSchema, StandardCharsets.UTF_8)
.useDelimiter("\\A")
.next();
String toNurseSQL = new Scanner(toNurseSchema, StandardCharsets.UTF_8)
.useDelimiter("\\A")
.next();
connection.createStatement().execute(SQL);
connection.createStatement().execute(permissionSQL);
connection.createStatement().execute(toNurseSQL);
}
@Override
public Map<String, User> load() throws SQLException {
List<User> users = new ArrayList<>();
User udo = new User(
"udo",
null,
null,
Integer.parseInt("00000001", 2),
null
);
udo.setPassword("uD0_187!");
users.add(udo);
User maria = new User(
"maria",
null,
null,
0,
null
);
maria.setPassword("H!mm3lf4hrt");
users.add(maria);
UserDao dao = DaoFactory.getInstance().createUserDAO();
Map<String, User> usersByUsername = new HashMap<>();
for (User user : users){
dao.create(user);
usersByUsername.put(user.getUsername(), user);
}
return usersByUsername;
}
}

View file

@ -1,8 +1,93 @@
package de.hitec.nhplus.login; package de.hitec.nhplus.login;
import de.hitec.nhplus.datastorage.DaoFactory;
import de.hitec.nhplus.login.database.UserDao;
import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.Arrays;
public class LoginController { public class LoginController {
public void initialize() { public User user;
@FXML
public TextField textFieldUsername;
@FXML
public PasswordField passwordField;
@FXML
public Button buttonSubmit;
private Stage stage;
private int loginTries = 0;
public void initialize(Stage stage) {
this.stage = stage;
}
private void handleWrongPasswordOrUsername() {
loginTries++;
// Shake
TranslateTransition ttUsername = new TranslateTransition(Duration.millis(50), textFieldUsername);
ttUsername.setByX(10);
ttUsername.setAutoReverse(true);
ttUsername.setCycleCount(6);
TranslateTransition ttPassword = new TranslateTransition(Duration.millis(50), passwordField);
ttPassword.setByX(10);
ttPassword.setAutoReverse(true);
ttPassword.setCycleCount(6);
ttUsername.play();
ttPassword.play();
// Timout
PauseTransition pause = new PauseTransition(Duration.seconds(3));
pause.setOnFinished(event -> {
if (loginTries == 3) {
stage.close();
}
buttonSubmit.setDisable(false);
});
pause.play();
}
@FXML
public void handleSubmit() {
buttonSubmit.setDisable(true);
UserDao dao = DaoFactory.getInstance().createUserDAO();
try {
int id = dao.readUserId(textFieldUsername.getText());
if (id == 0) {
handleWrongPasswordOrUsername();
return;
}
byte[] salt = dao.readPasswordSalt(id);
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(salt);
byte[] hash = md.digest(passwordField.getText().getBytes(StandardCharsets.UTF_8));
byte[] requiredHash = dao.readPasswordHash(id);
if (!Arrays.equals(hash, requiredHash)) {
handleWrongPasswordOrUsername();
return;
}
user = dao.read(id);
stage.close();
} catch (SQLException | NoSuchAlgorithmException exception) {
exception.printStackTrace();
}
} }
} }

View file

@ -0,0 +1,98 @@
package de.hitec.nhplus.login;
import de.hitec.nhplus.nurse.Nurse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class User {
private int id;
private String username;
private byte[] passwordSalt;
private byte[] passwordHash;
private int permissions = 0;
private Nurse nurse;
public User(
int id,
String username,
byte[] passwordSalt,
byte[] passwordHash,
int permissions,
Nurse nurse
) {
this.id = id;
this.username = username;
this.passwordSalt = passwordSalt;
this.passwordHash = passwordHash;
this.permissions = permissions;
this.nurse = nurse;
}
public User(
String username,
byte[] passwordSalt,
byte[] passwordHash,
int permissions,
Nurse nurse
) {
this.username = username;
this.passwordSalt = passwordSalt;
this.passwordHash = passwordHash;
this.permissions = permissions;
this.nurse = nurse;
}
public int getId() {
return id;
}
public byte[] getPasswordSalt() {
return passwordSalt;
}
public byte[] getPasswordHash() {
return passwordHash;
}
public void setPassword(String password) {
try {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[32];
random.nextBytes(salt);
this.passwordSalt = salt;
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(salt);
this.passwordHash = md.digest(password.getBytes(StandardCharsets.UTF_8));
}catch (NoSuchAlgorithmException exception){
exception.printStackTrace();
}
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getPermissions() {
return permissions;
}
public void setPermissions(int permissions) {
this.permissions = permissions;
}
public Nurse getNurse() {
return nurse;
}
public void setNurse(Nurse nurse) {
this.nurse = nurse;
}
}

View file

@ -0,0 +1,202 @@
package de.hitec.nhplus.login.database;
import de.hitec.nhplus.datastorage.Dao;
import de.hitec.nhplus.datastorage.DaoFactory;
import de.hitec.nhplus.login.User;
import de.hitec.nhplus.nurse.Nurse;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class UserDao implements Dao<User> {
protected final Connection connection;
public UserDao(Connection connection) {
this.connection = connection;
}
public int readUserId(String username) throws SQLException {
final String SQL = "SELECT id FROM user WHERE username = ?";
PreparedStatement statement = this.connection.prepareStatement(SQL);
statement.setString(1, username);
return statement.executeQuery().getInt(1);
}
public byte[] readPasswordSalt(int id) throws SQLException {
final String SQL = "SELECT passwordSalt FROM user WHERE id = ?";
PreparedStatement statement = this.connection.prepareStatement(SQL);
statement.setInt(1, id);
return statement.executeQuery().getBytes(1);
}
public byte[] readPasswordHash(int id) throws SQLException {
final String SQL = "SELECT passwordHash FROM user WHERE id = ?";
PreparedStatement statement = this.connection.prepareStatement(SQL);
statement.setInt(1, id);
return statement.executeQuery().getBytes(1);
}
@Override
public User read(int id) throws SQLException {
final String SQL = """
SELECT user.username, user.passwordSalt, user.passwordHash, user__permissions.permissions, user__nurse.nurseId
FROM user
LEFT JOIN user__permissions ON user.id = user__permissions.userId
LEFT JOIN user__nurse ON user.id = user__nurse.userId
WHERE user.id = ?;
""";
PreparedStatement statement = this.connection.prepareStatement(SQL);
statement.setInt(1, id);
ResultSet result = statement.executeQuery();
int nurseId = result.getInt(5);
Nurse nurse = null;
if (!result.wasNull()) {
nurse = DaoFactory.getInstance().createNurseDAO().read(nurseId);
}
return new User(
id,
result.getString(1),
result.getBytes(2),
result.getBytes(3),
result.getInt(4),
nurse
);
}
@Override
public void create(User user) throws SQLException {
connection.setAutoCommit(false); //Switch to Manual Commit, to do an SQL Transaction
final String userSQL = """
INSERT INTO user
(username, passwordSalt, passwordHash)
VALUES (?, ?, ?);
""";
PreparedStatement statement = this.connection.prepareStatement(userSQL);
statement.setString(1, user.getUsername());
statement.setBytes(2, user.getPasswordSalt());
statement.setBytes(3, user.getPasswordHash());
statement.execute();
ResultSet generatedKeys = connection.createStatement().executeQuery("SELECT id FROM user");
connection.commit(); //Finish SQL Transaction
connection.setAutoCommit(true); //Switch back Mode
if (!generatedKeys.next()) {
return;
}
int newId = generatedKeys.getInt(1);
final String permissionSQL = """
INSERT INTO user__permissions
(userId, permissions)
VALUES (?, ?);
""";
PreparedStatement permissionStatement = this.connection.prepareStatement(permissionSQL);
permissionStatement.setInt(1, newId);
permissionStatement.setInt(2, user.getPermissions());
permissionStatement.execute();
if (user.getNurse() == null) {
return;
}
final String nurseSQL = """
INSERT INTO user__nurse
(userId, nurseId)
VALUES (?, ?);
""";
PreparedStatement nurseStatement = this.connection.prepareStatement(nurseSQL);
permissionStatement.setInt(1, newId);
permissionStatement.setInt(2, user.getNurse().getId());
permissionStatement.execute();
}
@Override
public void update(User user) throws SQLException {
final String userSQL = """
UPDATE user SET
username = ?,
passwordSalt = ?,
passwordHash = ?
WHERE id = ?
""";
PreparedStatement statement = this.connection.prepareStatement(userSQL);
statement.setString(1, user.getUsername());
statement.setBytes(2, user.getPasswordSalt());
statement.setBytes(3, user.getPasswordHash());
statement.setInt(3, user.getId());
statement.executeUpdate();
final String permissionSQL = """
UPDATE user__permissions SET
permissions = ?
WHERE userId = ?
""";
PreparedStatement permissionStatement = this.connection.prepareStatement(permissionSQL);
permissionStatement.setInt(1, user.getPermissions());
permissionStatement.setInt(2, user.getId());
permissionStatement.executeUpdate();
if (user.getNurse() == null) {
final String nurseSQL = """
DELETE FROM user__nurse WHERE userId = ?
""";
this.connection.prepareStatement(nurseSQL).executeUpdate();
return;
}
final String nurseSQL = """
UPDATE user__nurse set
nurseId = ?
WHERE userId = ?
""";
PreparedStatement nurseStatement = this.connection.prepareStatement(nurseSQL);
nurseStatement.setInt(1, user.getNurse().getId());
nurseStatement.setInt(2, user.getId());
permissionStatement.executeUpdate();
}
@Override
public void delete(int id) throws SQLException {
final String SQL = """
DELETE FROM user WHERE user.id = ?;
""";
PreparedStatement preparedStatement = this.connection.prepareStatement(SQL);
preparedStatement.setInt(1, id);
preparedStatement.executeUpdate();
}
@Override
public List<User> readAll() throws SQLException {
final String SQL = """
SELECT user.id, user.username, user.passwordSalt, user.passwordHash, user__permissions.permissions, user__nurse.nurseId
FROM user
LEFT JOIN user__permissions ON user.id = user__permissions.userId
LEFT JOIN user__nurse ON user.id = user__nurse.userId
""";
ResultSet result = connection.prepareStatement(SQL).executeQuery();
List<User> users = new ArrayList<>();
while (result.next()) {
int nurseId = result.getInt(6);
Nurse nurse = null;
if (!result.wasNull()) {
nurse = DaoFactory.getInstance().createNurseDAO().read(nurseId);
}
users.add(new User(
result.getInt(1),
result.getString(2),
result.getBytes(3),
result.getBytes(4),
result.getInt(5),
nurse
));
}
return users;
}
}

View file

@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<BorderPane <BorderPane
xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns="http://javafx.com/javafx/17.0.2-ea"
xmlns:fx="http://javafx.com/fxml/1" xmlns:fx="http://javafx.com/fxml/1"
@ -22,11 +24,24 @@
<BorderPane.margin> <BorderPane.margin>
<Insets bottom="8.0" top="8.0"/> <Insets bottom="8.0" top="8.0"/>
</BorderPane.margin> </BorderPane.margin>
<TextField promptText="nutzername" VBox.vgrow="ALWAYS"/> <TextField
<PasswordField promptText="password" VBox.vgrow="ALWAYS"/> fx:id="textFieldUsername"
promptText="nutzername"
VBox.vgrow="ALWAYS"
/>
<PasswordField
fx:id="passwordField"
promptText="password"
VBox.vgrow="ALWAYS"
/>
</VBox> </VBox>
</center> </center>
<bottom> <bottom>
<Button text="Bestätigen" BorderPane.alignment="CENTER" /> <Button
fx:id="buttonSubmit"
text="Bestätigen"
BorderPane.alignment="CENTER"
onAction="#handleSubmit"
/>
</bottom> </bottom>
</BorderPane> </BorderPane>

View file

@ -1,7 +1,7 @@
CREATE TABLE user CREATE TABLE user
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL, username TEXT NOT NULL UNIQUE,
passwordSalt BLOB NOT NULL, passwordSalt BLOB NOT NULL,
passwordHash BLOB NOT NULL passwordHash BLOB NOT NULL
) )

View file

@ -1,6 +1,6 @@
CREATE TABLE user__permissions CREATE TABLE user__permissions
( (
userId INTEGER NOT NULL, userId INTEGER NOT NULL UNIQUE,
permissions INTEGER, -- Binary Bitmask for Permissions permissions INTEGER NOT NULL, -- Binary Bitmask for Permissions
FOREIGN KEY (userId) REFERENCES user (id) ON DELETE CASCADE FOREIGN KEY (userId) REFERENCES user (id) ON DELETE CASCADE
) )

View file

@ -1,7 +1,7 @@
CREATE TABLE user__nurse CREATE TABLE user__nurse
( (
userId INTEGER NOT NULL, userId INTEGER NOT NULL UNIQUE,
nurseId INTEGER NOT NULL, nurseId INTEGER NOT NULL UNIQUE,
FOREIGN KEY (userId) REFERENCES user (id) ON DELETE CASCADE, FOREIGN KEY (userId) REFERENCES user (id) ON DELETE CASCADE,
FOREIGN KEY (nurseId) REFERENCES nurse (id) ON DELETE CASCADE FOREIGN KEY (nurseId) REFERENCES nurse (id) ON DELETE CASCADE
) )