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:
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
6
src/main/java/org/example/petshopdesktop/auth/Role.java
Normal file
6
src/main/java/org/example/petshopdesktop/auth/Role.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package org.example.petshopdesktop.auth;
|
||||
|
||||
public enum Role {
|
||||
ADMIN,
|
||||
STAFF
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/main/java/org/example/petshopdesktop/models/User.java
Normal file
27
src/main/java/org/example/petshopdesktop/models/User.java
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user