diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 7459e876..d1f07dd9 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -8,7 +8,9 @@ module org.example.petshopdesktop {
opens org.example.petshopdesktop to javafx.fxml;
opens org.example.petshopdesktop.controllers.dialogcontrollers to javafx.fxml;
opens org.example.petshopdesktop.controllers to javafx.fxml;
+ opens org.example.petshopdesktop.auth to javafx.fxml;
exports org.example.petshopdesktop;
exports org.example.petshopdesktop.controllers;
+ exports org.example.petshopdesktop.auth;
}
\ No newline at end of file
diff --git a/src/main/java/org/example/petshopdesktop/PetShopApplication.java b/src/main/java/org/example/petshopdesktop/PetShopApplication.java
index 8a8fcd65..06def37b 100644
--- a/src/main/java/org/example/petshopdesktop/PetShopApplication.java
+++ b/src/main/java/org/example/petshopdesktop/PetShopApplication.java
@@ -4,15 +4,21 @@ import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
+import org.example.petshopdesktop.database.UserDB;
import java.io.IOException;
public class PetShopApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
- FXMLLoader fxmlLoader = new FXMLLoader(PetShopApplication.class.getResource("main-layout-view.fxml"));
+ try {
+ UserDB.initializeTable();
+ } catch (Exception e) {
+ System.err.println("Warning: could not initialize users table: " + e.getMessage());
+ }
+ FXMLLoader fxmlLoader = new FXMLLoader(PetShopApplication.class.getResource("login-view.fxml"));
Scene scene = new Scene(fxmlLoader.load());
- stage.setTitle("Pet Shop Manager");
+ stage.setTitle("Pet Shop Manager - Login");
stage.setScene(scene);
stage.show();
}
diff --git a/src/main/java/org/example/petshopdesktop/auth/Role.java b/src/main/java/org/example/petshopdesktop/auth/Role.java
new file mode 100644
index 00000000..64f38459
--- /dev/null
+++ b/src/main/java/org/example/petshopdesktop/auth/Role.java
@@ -0,0 +1,6 @@
+package org.example.petshopdesktop.auth;
+
+public enum Role {
+ ADMIN,
+ STAFF
+}
diff --git a/src/main/java/org/example/petshopdesktop/auth/UserSession.java b/src/main/java/org/example/petshopdesktop/auth/UserSession.java
new file mode 100644
index 00000000..748c9546
--- /dev/null
+++ b/src/main/java/org/example/petshopdesktop/auth/UserSession.java
@@ -0,0 +1,44 @@
+package org.example.petshopdesktop.auth;
+
+public class UserSession {
+
+ private static UserSession instance;
+
+ private String username;
+ private Role role;
+
+ private UserSession() {}
+
+ public static UserSession getInstance() {
+ if (instance == null) {
+ instance = new UserSession();
+ }
+ return instance;
+ }
+
+ public void login(String username, Role role) {
+ this.username = username;
+ this.role = role;
+ }
+
+ public void logout() {
+ this.username = null;
+ this.role = null;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+
+ public boolean isAdmin() {
+ return Role.ADMIN.equals(role);
+ }
+
+ public boolean isLoggedIn() {
+ return username != null && role != null;
+ }
+}
diff --git a/src/main/java/org/example/petshopdesktop/controllers/LoginController.java b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java
new file mode 100644
index 00000000..43bb3334
--- /dev/null
+++ b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java
@@ -0,0 +1,67 @@
+package org.example.petshopdesktop.controllers;
+
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.control.Label;
+import javafx.scene.control.PasswordField;
+import javafx.scene.control.TextField;
+import javafx.stage.Stage;
+import org.example.petshopdesktop.auth.UserSession;
+import org.example.petshopdesktop.database.UserDB;
+import org.example.petshopdesktop.models.User;
+
+import java.sql.SQLException;
+
+public class LoginController {
+
+ @FXML
+ private TextField txtUsername;
+
+ @FXML
+ private PasswordField txtPassword;
+
+ @FXML
+ private Label lblError;
+
+ @FXML
+ void btnLoginClicked(ActionEvent event) {
+ String username = txtUsername.getText().trim();
+ String password = txtPassword.getText();
+
+ if (username.isEmpty() || password.isEmpty()) {
+ lblError.setText("Please enter username and password.");
+ return;
+ }
+
+ try {
+ User user = UserDB.authenticate(username, password);
+ if (user == null) {
+ lblError.setText("Invalid username or password.");
+ txtPassword.clear();
+ return;
+ }
+
+ UserSession.getInstance().login(user.getUsername(), user.getRole());
+ openMainLayout();
+
+ } catch (SQLException e) {
+ lblError.setText("Database error: " + e.getMessage());
+ }
+ }
+
+ private void openMainLayout() {
+ try {
+ FXMLLoader loader = new FXMLLoader(
+ getClass().getResource("/org/example/petshopdesktop/main-layout-view.fxml"));
+ Scene scene = new Scene(loader.load());
+ Stage stage = (Stage) txtUsername.getScene().getWindow();
+ stage.setScene(scene);
+ stage.setTitle("Pet Shop Manager");
+ } catch (Exception e) {
+ lblError.setText("Error loading application: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java b/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java
index 431d38d4..3584a0cc 100644
--- a/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java
+++ b/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java
@@ -4,8 +4,12 @@ import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
+import javafx.scene.Scene;
import javafx.scene.control.Button;
+import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
+import javafx.stage.Stage;
+import org.example.petshopdesktop.auth.UserSession;
public class MainLayoutController {
@@ -18,6 +22,9 @@ public class MainLayoutController {
@FXML
private Button btnInventory;
+ @FXML
+ private Button btnLogout;
+
@FXML
private Button btnPets;
@@ -36,6 +43,12 @@ public class MainLayoutController {
@FXML
private Button btnSuppliers;
+ @FXML
+ private Label lblUsername;
+
+ @FXML
+ private Label lblRole;
+
@FXML
private StackPane spContentArea;
@@ -93,11 +106,43 @@ public class MainLayoutController {
updateButtons(btnSuppliers);
}
+ @FXML
+ void btnLogoutClicked(ActionEvent event) {
+ UserSession.getInstance().logout();
+ try {
+ FXMLLoader loader = new FXMLLoader(
+ getClass().getResource("/org/example/petshopdesktop/login-view.fxml"));
+ Scene scene = new Scene(loader.load());
+ Stage stage = (Stage) btnLogout.getScene().getWindow();
+ stage.setScene(scene);
+ stage.setTitle("Pet Shop Manager - Login");
+ } catch (Exception e) {
+ System.err.println("Error loading login view: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
@FXML
public void initialize() {
+ applyRBAC();
loadView("pet-view.fxml");
}
+ private void applyRBAC() {
+ UserSession session = UserSession.getInstance();
+
+ lblUsername.setText(session.getUsername());
+ lblRole.setText(session.getRole().toString());
+
+ boolean isAdmin = session.isAdmin();
+ btnInventory.setVisible(isAdmin);
+ btnInventory.setManaged(isAdmin);
+ btnSuppliers.setVisible(isAdmin);
+ btnSuppliers.setManaged(isAdmin);
+ btnProductSuppliers.setVisible(isAdmin);
+ btnProductSuppliers.setManaged(isAdmin);
+ }
+
/**
* Load a view when a button is clicked on the navigation
* @param fxmlFile the fxmlFile name to be loaded
diff --git a/src/main/java/org/example/petshopdesktop/database/UserDB.java b/src/main/java/org/example/petshopdesktop/database/UserDB.java
new file mode 100644
index 00000000..8909d211
--- /dev/null
+++ b/src/main/java/org/example/petshopdesktop/database/UserDB.java
@@ -0,0 +1,71 @@
+package org.example.petshopdesktop.database;
+
+import org.example.petshopdesktop.auth.Role;
+import org.example.petshopdesktop.models.User;
+
+import java.sql.*;
+
+public class UserDB {
+
+ /**
+ * Authenticate a user by username and password.
+ * Passwords are stored as SHA-256 hex digests in the database.
+ *
+ * @param username the username to authenticate
+ * @param password the plaintext password
+ * @return the User if credentials are valid, or null if authentication fails
+ */
+ public static User authenticate(String username, String password) throws SQLException {
+ String sql = "SELECT user_id, username, role FROM users " +
+ "WHERE username = ? AND password_hash = SHA2(?, 256)";
+
+ try (Connection conn = ConnectionDB.getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+
+ ps.setString(1, username);
+ ps.setString(2, password);
+
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ int userId = rs.getInt("user_id");
+ String uname = rs.getString("username");
+ Role role = Role.valueOf(rs.getString("role").toUpperCase());
+ return new User(userId, uname, role);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Create the users table and seed default admin/staff accounts if they do not exist.
+ * Passwords are stored as SHA2-256 hashes.
+ */
+ public static void initializeTable() throws SQLException {
+ String createTable = """
+ CREATE TABLE IF NOT EXISTS users (
+ user_id INT AUTO_INCREMENT PRIMARY KEY,
+ username VARCHAR(100) NOT NULL UNIQUE,
+ password_hash CHAR(64) NOT NULL,
+ role ENUM('ADMIN','STAFF') NOT NULL
+ )
+ """;
+
+ String seedAdmin = """
+ INSERT IGNORE INTO users (username, password_hash, role)
+ VALUES ('admin', SHA2('admin123', 256), 'ADMIN')
+ """;
+
+ String seedStaff = """
+ INSERT IGNORE INTO users (username, password_hash, role)
+ VALUES ('staff', SHA2('staff123', 256), 'STAFF')
+ """;
+
+ try (Connection conn = ConnectionDB.getConnection();
+ Statement st = conn.createStatement()) {
+ st.executeUpdate(createTable);
+ st.executeUpdate(seedAdmin);
+ st.executeUpdate(seedStaff);
+ }
+ }
+}
diff --git a/src/main/java/org/example/petshopdesktop/models/User.java b/src/main/java/org/example/petshopdesktop/models/User.java
new file mode 100644
index 00000000..02089be4
--- /dev/null
+++ b/src/main/java/org/example/petshopdesktop/models/User.java
@@ -0,0 +1,27 @@
+package org.example.petshopdesktop.models;
+
+import org.example.petshopdesktop.auth.Role;
+
+public class User {
+ private int userId;
+ private String username;
+ private Role role;
+
+ public User(int userId, String username, Role role) {
+ this.userId = userId;
+ this.username = username;
+ this.role = role;
+ }
+
+ public int getUserId() {
+ return userId;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+}
diff --git a/src/main/resources/org/example/petshopdesktop/login-view.fxml b/src/main/resources/org/example/petshopdesktop/login-view.fxml
new file mode 100644
index 00000000..65199c95
--- /dev/null
+++ b/src/main/resources/org/example/petshopdesktop/login-view.fxml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml b/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml
index 9579dff6..983d07a9 100644
--- a/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml
@@ -16,12 +16,12 @@
-