Implement RBAC with login screen for admin and staff roles

- Add Role enum (ADMIN, STAFF) and UserSession singleton
- Add User model and UserDB with SHA2-256 authentication
- Add login screen (login-view.fxml + LoginController)
- Update main-layout-view.fxml: dynamic username/role labels, logout button
- Update MainLayoutController: hide Inventory/Suppliers/Product-Suppliers for STAFF, show username/role, handle logout
- Update PetShopApplication to start with login screen and initialize users table
- Update module-info.java to open/export auth package
This commit is contained in:
2026-02-21 13:27:50 -07:00
parent fb16da91e2
commit a12af3f0ee
10 changed files with 363 additions and 4 deletions

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -0,0 +1,6 @@
package org.example.petshopdesktop.auth;
public enum Role {
ADMIN,
STAFF
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}