This commit is contained in:
2026-02-22 17:39:42 -07:00
parent a12af3f0ee
commit bbf06456f5
5 changed files with 53 additions and 0 deletions

View File

@@ -1,6 +1,13 @@
package org.example.petshopdesktop.auth; package org.example.petshopdesktop.auth;
/*
Petshop Desktop
Purpose: Application role definitions used by session state and role based access control.
*/
public enum Role { public enum Role {
// Administrative access, includes system management screens.
ADMIN, ADMIN,
// Staff access, limited to day to day operational screens.
STAFF STAFF
} }

View File

@@ -1,14 +1,24 @@
package org.example.petshopdesktop.auth; package org.example.petshopdesktop.auth;
/*
Petshop Desktop
Purpose: In memory session state for the authenticated user.
Notes: Session is process local and cleared on logout or application restart.
*/
public class UserSession { public class UserSession {
// Singleton instance used to share session state across controllers.
private static UserSession instance; private static UserSession instance;
// Current authenticated username, null when logged out.
private String username; private String username;
// Current authenticated role, null when logged out.
private Role role; private Role role;
private UserSession() {} private UserSession() {}
// Lazily initialised singleton accessor.
public static UserSession getInstance() { public static UserSession getInstance() {
if (instance == null) { if (instance == null) {
instance = new UserSession(); instance = new UserSession();
@@ -16,11 +26,13 @@ public class UserSession {
return instance; return instance;
} }
// Stores identity and role for the active session.
public void login(String username, Role role) { public void login(String username, Role role) {
this.username = username; this.username = username;
this.role = role; this.role = role;
} }
// Clears session state and returns the application to an unauthenticated state.
public void logout() { public void logout() {
this.username = null; this.username = null;
this.role = null; this.role = null;
@@ -34,10 +46,13 @@ public class UserSession {
return role; return role;
} }
// Convenience check for administrative privileges.
// Role.ADMIN.equals(role) remains safe when role is null.
public boolean isAdmin() { public boolean isAdmin() {
return Role.ADMIN.equals(role); return Role.ADMIN.equals(role);
} }
// Session is considered active only when both username and role are set.
public boolean isLoggedIn() { public boolean isLoggedIn() {
return username != null && role != null; return username != null && role != null;
} }

View File

@@ -14,6 +14,10 @@ import org.example.petshopdesktop.models.User;
import java.sql.SQLException; import java.sql.SQLException;
/*
Petshop Desktop
Purpose: Authentication controller responsible for validating credentials and initialising the user session.
*/
public class LoginController { public class LoginController {
@FXML @FXML
@@ -27,15 +31,18 @@ public class LoginController {
@FXML @FXML
void btnLoginClicked(ActionEvent event) { void btnLoginClicked(ActionEvent event) {
// Input normalisation keeps authentication behaviour consistent.
String username = txtUsername.getText().trim(); String username = txtUsername.getText().trim();
String password = txtPassword.getText(); String password = txtPassword.getText();
// Basic validation to avoid unnecessary database calls.
if (username.isEmpty() || password.isEmpty()) { if (username.isEmpty() || password.isEmpty()) {
lblError.setText("Please enter username and password."); lblError.setText("Please enter username and password.");
return; return;
} }
try { try {
// Credential verification returns a fully populated User on success.
User user = UserDB.authenticate(username, password); User user = UserDB.authenticate(username, password);
if (user == null) { if (user == null) {
lblError.setText("Invalid username or password."); lblError.setText("Invalid username or password.");
@@ -43,6 +50,7 @@ public class LoginController {
return; return;
} }
// Session state is stored in memory for use by controllers and UI RBAC.
UserSession.getInstance().login(user.getUsername(), user.getRole()); UserSession.getInstance().login(user.getUsername(), user.getRole());
openMainLayout(); openMainLayout();
@@ -53,6 +61,7 @@ public class LoginController {
private void openMainLayout() { private void openMainLayout() {
try { try {
// View transition into the post login application shell.
FXMLLoader loader = new FXMLLoader( FXMLLoader loader = new FXMLLoader(
getClass().getResource("/org/example/petshopdesktop/main-layout-view.fxml")); getClass().getResource("/org/example/petshopdesktop/main-layout-view.fxml"));
Scene scene = new Scene(loader.load()); Scene scene = new Scene(loader.load());

View File

@@ -11,6 +11,10 @@ import javafx.scene.layout.StackPane;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.auth.UserSession;
/*
Petshop Desktop
Purpose: Main application shell controller, includes navigation and UI level role based access control.
*/
public class MainLayoutController { public class MainLayoutController {
@FXML @FXML
@@ -108,6 +112,7 @@ public class MainLayoutController {
@FXML @FXML
void btnLogoutClicked(ActionEvent event) { void btnLogoutClicked(ActionEvent event) {
// Logout clears session state before returning to the login view.
UserSession.getInstance().logout(); UserSession.getInstance().logout();
try { try {
FXMLLoader loader = new FXMLLoader( FXMLLoader loader = new FXMLLoader(
@@ -124,16 +129,22 @@ public class MainLayoutController {
@FXML @FXML
public void initialize() { public void initialize() {
// RBAC state is applied once during initial layout load.
applyRBAC(); applyRBAC();
// Default landing view after successful authentication.
loadView("pet-view.fxml"); loadView("pet-view.fxml");
} }
private void applyRBAC() { private void applyRBAC() {
UserSession session = UserSession.getInstance(); UserSession session = UserSession.getInstance();
// Session identity is displayed in the header for clarity and auditing.
lblUsername.setText(session.getUsername()); lblUsername.setText(session.getUsername());
lblRole.setText(session.getRole().toString()); lblRole.setText(session.getRole().toString());
// UI level RBAC hides admin only navigation entries for non admin users.
// setManaged(false) removes the node from layout calculations to avoid empty spacing.
boolean isAdmin = session.isAdmin(); boolean isAdmin = session.isAdmin();
btnInventory.setVisible(isAdmin); btnInventory.setVisible(isAdmin);
btnInventory.setManaged(isAdmin); btnInventory.setManaged(isAdmin);
@@ -141,6 +152,8 @@ public class MainLayoutController {
btnSuppliers.setManaged(isAdmin); btnSuppliers.setManaged(isAdmin);
btnProductSuppliers.setVisible(isAdmin); btnProductSuppliers.setVisible(isAdmin);
btnProductSuppliers.setManaged(isAdmin); btnProductSuppliers.setManaged(isAdmin);
// Privileged operations should still be enforced within the relevant controllers and database methods.
} }
/** /**

View File

@@ -5,6 +5,10 @@ import org.example.petshopdesktop.models.User;
import java.sql.*; import java.sql.*;
/*
Petshop Desktop
Purpose: User authentication and role lookup against the users table.
*/
public class UserDB { public class UserDB {
/** /**
@@ -29,7 +33,11 @@ public class UserDB {
if (rs.next()) { if (rs.next()) {
int userId = rs.getInt("user_id"); int userId = rs.getInt("user_id");
String uname = rs.getString("username"); String uname = rs.getString("username");
// Role values are stored in the database as strings and normalised to match the enum.
// Table constraints limit role values, Role.valueOf is expected to be safe under normal operation.
Role role = Role.valueOf(rs.getString("role").toUpperCase()); Role role = Role.valueOf(rs.getString("role").toUpperCase());
return new User(userId, uname, role); return new User(userId, uname, role);
} }
} }
@@ -51,6 +59,7 @@ public class UserDB {
) )
"""; """;
// Default accounts support initial development and testing, credentials should be rotated or removed for deployment.
String seedAdmin = """ String seedAdmin = """
INSERT IGNORE INTO users (username, password_hash, role) INSERT IGNORE INTO users (username, password_hash, role)
VALUES ('admin', SHA2('admin123', 256), 'ADMIN') VALUES ('admin', SHA2('admin123', 256), 'ADMIN')