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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.PasswordField?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<VBox alignment="CENTER" prefHeight="400.0" prefWidth="380.0" spacing="16.0"
|
||||
style="-fx-background-color: #2C3E50;"
|
||||
xmlns="http://javafx.com/javafx/25"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="org.example.petshopdesktop.controllers.LoginController">
|
||||
<padding>
|
||||
<Insets bottom="40.0" left="50.0" right="50.0" top="40.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<Label text="🐾 Pet Shop Manager" textFill="WHITE">
|
||||
<font>
|
||||
<Font name="Comic Sans MS Bold" size="22.0" />
|
||||
</font>
|
||||
<VBox.margin>
|
||||
<Insets bottom="10.0" />
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
|
||||
<Label text="Username" textFill="#cccccc">
|
||||
<font>
|
||||
<Font name="System Bold" size="13.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<TextField fx:id="txtUsername" promptText="Enter username"
|
||||
style="-fx-background-color: #3d5166; -fx-text-fill: white; -fx-prompt-text-fill: #888; -fx-background-radius: 8; -fx-border-width: 0;">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="12.0" right="12.0" top="10.0" />
|
||||
</padding>
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</TextField>
|
||||
|
||||
<Label text="Password" textFill="#cccccc">
|
||||
<font>
|
||||
<Font name="System Bold" size="13.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<PasswordField fx:id="txtPassword" promptText="Enter password"
|
||||
style="-fx-background-color: #3d5166; -fx-text-fill: white; -fx-prompt-text-fill: #888; -fx-background-radius: 8; -fx-border-width: 0;">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="12.0" right="12.0" top="10.0" />
|
||||
</padding>
|
||||
<font>
|
||||
<Font size="14.0" />
|
||||
</font>
|
||||
</PasswordField>
|
||||
|
||||
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true">
|
||||
<font>
|
||||
<Font size="13.0" />
|
||||
</font>
|
||||
</Label>
|
||||
|
||||
<Button fx:id="btnLogin" mnemonicParsing="false" onAction="#btnLoginClicked"
|
||||
prefWidth="280.0"
|
||||
style="-fx-background-color: #FF6B6B; -fx-background-radius: 8; -fx-cursor: hand;"
|
||||
text="Login" textFill="WHITE">
|
||||
<font>
|
||||
<Font name="System Bold" size="15.0" />
|
||||
</font>
|
||||
<padding>
|
||||
<Insets bottom="12.0" left="12.0" right="12.0" top="12.0" />
|
||||
</padding>
|
||||
<VBox.margin>
|
||||
<Insets top="8.0" />
|
||||
</VBox.margin>
|
||||
</Button>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -16,12 +16,12 @@
|
||||
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<Label text="Name" textFill="WHITE">
|
||||
<Label fx:id="lblUsername" text="Name" textFill="WHITE">
|
||||
<font>
|
||||
<Font name="Comic Sans MS Bold" size="30.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label text="Pet Store Manager" textFill="#ffe66d">
|
||||
<Label fx:id="lblRole" text="Pet Store Manager" textFill="#ffe66d">
|
||||
<font>
|
||||
<Font name="Comic Sans MS Bold" size="16.0" />
|
||||
</font>
|
||||
@@ -106,6 +106,17 @@
|
||||
<Insets bottom="12.0" left="12.0" right="45.0" top="12.0" />
|
||||
</padding>
|
||||
</Button>
|
||||
<Button fx:id="btnLogout" mnemonicParsing="false" onAction="#btnLogoutClicked" prefWidth="250.0" style="-fx-background-color: #34495E; -fx-background-radius: 8; -fx-cursor: hand;" text="🚪 Logout" textFill="#cccccc">
|
||||
<font>
|
||||
<Font name="Comic Sans MS Bold" size="14.0" />
|
||||
</font>
|
||||
<padding>
|
||||
<Insets bottom="12.0" left="12.0" right="94.0" top="12.0" />
|
||||
</padding>
|
||||
<VBox.margin>
|
||||
<Insets top="20.0" />
|
||||
</VBox.margin>
|
||||
</Button>
|
||||
</children>
|
||||
</VBox>
|
||||
</left>
|
||||
|
||||
Reference in New Issue
Block a user