Skip to content
184 changes: 159 additions & 25 deletions src/main/java/com/example/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,53 @@ static void main(String[] args) {
}

/**
* Kör huvudflödet för applikationen, inklusive inloggning och enkel meny för att lista månfärder.
* <p>
* Vad som har lagts till i denna version:
* Kör huvudflödet för applikationen, inklusive inloggning och enkel meny för att lista månfärder,
* skapa nya konton och ta bort konton.
* <p>
* Skapar en Scanner för användarinput.
* Frågar användaren efter användarnamn och lösenord och kontrollerar dessa mot databasen.
* Om inloggning lyckas skrivs "Login successful!" ut, annars avslutas programmet med felmeddelande.
* Efter lyckad inloggning visas en enkel meny med två alternativ:
* Efter lyckad inloggning visas en enkel meny med följande alternativ:
* <p>
* 1) Lista alla månfärder (namn på rymdfarkoster)
* Hämtar alla rymdfarkoster från tabellen "moon_mission" och skriver ut dem i ordning
* efter mission_id. Användaren får en snabb översikt över vilka månfärder som finns
* registrerade i databasen.
* <p>
* 2) Hämta månfärd efter mission_id
* Frågar efter ett specifikt mission_id och hämtar information om månfärden med det ID:t.
* Om månfärden finns skrivs namnet på rymdfarkosten ut, annars meddelas att månfärden inte
* hittades.
* <p>
* 3) Räkna månfärder för ett visst år
* Frågar användaren efter ett år och räknar antalet månfärder som har launch_date inom
* det året. Resultatet visar hur många månfärder som genomfördes under det valda året.
* <p>
* Lista alla månfärder (namn på rymdfarkoster).
* Avsluta programmet.
* 4) Skapa ett nytt konto (förnamn, efternamn, SSN, lösenord)
* Frågar efter användarens förnamn, efternamn, SSN och lösenord och skapar ett nytt konto
* i tabellen "account". Detta gör det möjligt för nya användare att registrera sig.
* <p>
* Valet "Lista månfärder" hämtar alla rymdfarkoster från tabellen "moon_mission" och skriver ut dem i ordning eftermission_id.
* Valet "Avsluta" bryter menyn och avslutar metoden.
* Ogiltiga val hanteras med ett felmeddelande.
* 5) Uppdatera lösenord för ett konto via user_id
* Frågar efter user_id för kontot som ska uppdateras samt det nya lösenordet. Uppdaterar
* kontots lösenord i databasen om kontot finns, annars meddelas att inget konto med
* angivet ID hittades.
* <p>
* 6) Ta bort ett konto via user_id
* Frågar efter user_id för kontot som ska tas bort och tar bort kontot från tabellen
* "account". Om kontot inte finns meddelas användaren om att inget konto hittades.
* <p>
* 0) Avsluta programmet
* Bryter menyn och avslutar metoden.
* <p>
* Ogiltiga val hanteras med felmeddelanden.
*/
public void run() {
// Resolve DB settings with precedence: System properties -> Environment variables
String jdbcUrl = resolveConfig("APP_JDBC_URL", "APP_JDBC_URL");
String dbUser = resolveConfig("APP_DB_USER", "APP_DB_USER");
String dbPass = resolveConfig("APP_DB_PASS", "APP_DB_PASS");

if (jdbcUrl == null || dbUser == null || dbPass == null) {
throw new IllegalStateException(
throw new IllegalStateException(
"Missing DB configuration. Provide APP_JDBC_URL, APP_DB_USER, APP_DB_PASS " +
"as system properties (-Dkey=value) or environment variables.");
}
Expand Down Expand Up @@ -74,22 +97,133 @@ public void run() {

while (true) {
System.out.println("1) List moon missions");
System.out.println("2) Get mission by ID");
System.out.println("3) Count missions for year");
System.out.println("4) Create new account");
System.out.println("5) Update account password");
System.out.println("6) Delete account");
System.out.println("0) Exit");

String choice = scanner.nextLine().trim();
if ("0".equals(choice)) {
break;
} else if ("1".equals(choice)) {
try (PreparedStatement ps = connection.prepareStatement(
"SELECT spacecraft FROM moon_mission ORDER BY mission_id"
)) {
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("spacecraft"));

switch (choice) {
case "0" -> {
return;
}
case "1" -> {
try (PreparedStatement ps = connection.prepareStatement(
"SELECT spacecraft FROM moon_mission ORDER BY mission_id"
)) {
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("spacecraft"));
}
}
}
case "2" -> {
System.out.println("Mission ID:");
int id;
try {
id = Integer.parseInt(scanner.nextLine().trim());
} catch (NumberFormatException e) {
System.out.println("Invalid mission ID");
break;
}
try (PreparedStatement ps = connection.prepareStatement(
"SELECT spacecraft FROM moon_mission WHERE mission_id = ?"
)) {
ps.setInt(1, id);
ResultSet rs = ps.executeQuery();
if (rs.next()) {

System.out.println(rs.getString("spacecraft"));
} else {
System.out.println("Mission not found");
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
System.out.println("Invalid option");

case "3" -> {
System.out.println("Year:");
int year;
try {
year = Integer.parseInt(scanner.nextLine().trim());
} catch (NumberFormatException e) {
System.out.println("Invalid year");
break;
}
try (PreparedStatement ps = connection.prepareStatement(
"SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?"
)) {
ps.setInt(1, year);
ResultSet rs = ps.executeQuery();
rs.next();
int count = rs.getInt(1);
System.out.println(year + " missions: " + count);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

case "4" -> {
System.out.println("First name:");
String firstName = scanner.nextLine().trim();
System.out.println("Last name:");
String lastName = scanner.nextLine().trim();
System.out.println("SSN:");
String ssn = scanner.nextLine().trim();
System.out.println("Password:");
String accountPassword = scanner.nextLine().trim();

try (PreparedStatement ps = connection.prepareStatement(
"INSERT INTO account(password, first_name, last_name, ssn) VALUES (?, ?, ?, ?)"
)) {
ps.setString(1, accountPassword);
ps.setString(2, firstName);
ps.setString(3, lastName);
ps.setString(4, ssn);
ps.executeUpdate();
}

System.out.println("Account created");
}
Comment on lines +166 to +187

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical security issues: plaintext passwords and PII handling.

Multiple security vulnerabilities identified:

  1. Plaintext password storage: Line 156 stores the password in plaintext, which is a critical security vulnerability. Passwords must be hashed using a strong algorithm (e.g., bcrypt, Argon2) before storage.

  2. Unprotected PII: SSN (Social Security Number) at line 159 is highly sensitive personally identifiable information (PII). It should be encrypted at rest and handled with extreme care. Consider:

    • Encrypting SSN before storage
    • Implementing access controls
    • Auditing access to SSN data
    • Complying with privacy regulations (GDPR, CCPA, etc.)
  3. No input validation: The code doesn't validate SSN format, password strength, or check for empty strings. This could lead to invalid data in the database.

  4. No duplicate checking: There's no check for duplicate SSNs or other unique constraints before insertion, which could cause unclear error messages.

Implement password hashing and SSN encryption before storing these values. Add input validation and duplicate checking to improve data integrity.


case "5" -> {
System.out.println("User ID of account to update:");
long userId = Long.parseLong(scanner.nextLine().trim());
System.out.println("New password:");
String newPassword = scanner.nextLine().trim();

try (PreparedStatement ps = connection.prepareStatement(
"UPDATE account SET password = ? WHERE user_id = ?"
)) {
ps.setString(1, newPassword);
ps.setLong(2, userId);
int rows = ps.executeUpdate();
if (rows > 0) {
System.out.println("Password updated");
} else {
System.out.println("No account found with that ID");
}
}
}
Comment on lines +189 to 207

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical security issues: missing authorization, plaintext passwords, and account enumeration.

Multiple critical and major security issues:

  1. Missing authorization: Any authenticated user can update ANY account's password. There's no check to verify that the logged-in user has permission to update the specified account. This is a critical authorization vulnerability.

  2. Plaintext password storage: Line 175 stores the new password in plaintext. Passwords must be hashed before storage.

  3. Account enumeration: Line 181 ("No account found with that ID") reveals whether a user ID exists in the system, which is an information disclosure vulnerability that attackers can exploit.

  4. Missing input validation: Long.parseLong at line 168 will throw an uncaught NumberFormatException for invalid input.

Required fixes:

  • Add authorization check to verify the user can only update their own password (or implement proper role-based access control)
  • Implement password hashing
  • Use generic error messages that don't reveal account existence
  • Add input validation with try-catch for parsing operations
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 166 to 184, the
password-update flow lacks input validation, authorization checks, hashes
passwords in plaintext, and leaks account existence; update it to (1) validate
and catch NumberFormatException around Long.parseLong and prompt/abort on bad
input, (2) verify the currently authenticated user's ID (or RBAC) matches the
target userId before proceeding and reject unauthorized attempts, (3) hash the
new password (e.g., BCrypt) and store the hash instead of plaintext, (4) after
executeUpdate return a generic success/failure message that does not reveal
whether the userId exists (e.g., "Password update completed" on success, and
"Unable to update password" on failure), and (5) ensure SQL and password
operations are wrapped in try/catch to log internal errors server-side without
exposing details to the user.


case "6" -> {
System.out.println("User ID of account to delete:");
long userId = Long.parseLong(scanner.nextLine().trim());

try (PreparedStatement ps = connection.prepareStatement(
"DELETE FROM account WHERE user_id = ?"
)) {
ps.setLong(1, userId);
int rows = ps.executeUpdate();
if (rows > 0) {
System.out.println("Account deleted");
} else {
System.out.println("No account found with that ID");
}
}
}
Comment on lines +209 to +224

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical security issue: missing authorization and safety checks.

Multiple critical security issues:

  1. Missing authorization: Any authenticated user can delete ANY account without permission checks. This is a critical authorization vulnerability that could lead to unauthorized data deletion.

  2. Account enumeration: Line 198 ("No account found with that ID") reveals whether a user ID exists, enabling attackers to enumerate valid accounts.

  3. Missing input validation: Long.parseLong at line 188 will throw an uncaught NumberFormatException for invalid input, crashing the application.

  4. No confirmation prompt: Deletion is permanent and irreversible, yet there's no confirmation step to prevent accidental deletions.

  5. No transaction management: Account deletion might need to cascade to related records or maintain referential integrity, but there's no transaction wrapping.

Required fixes:

  • Implement authorization to restrict users to deleting only their own accounts (or enforce role-based permissions)
  • Add confirmation prompt before deletion
  • Add input validation for user ID parsing
  • Use generic error messages
  • Consider transaction management if cascading deletes are needed
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 186 to 201, the
delete-account branch lacks auth, validation, confirmation, safe error handling
and transaction management; fix by (1) validating and parsing input safely
(catch NumberFormatException and re-prompt or abort), (2) enforcing
authorization so only the logged-in user's own account can be deleted or require
an elevated role check before proceeding, (3) prompt for an explicit
confirmation (y/N) before performing deletion, (4) wrap the delete in a DB
transaction (begin, execute, commit/rollback) to ensure referential integrity or
call a cascade-safe service/DAO instead of raw deletion here, (5) avoid
revealing whether the ID existed by returning a generic success/failure message
and log detailed errors internally, and (6) catch SQLExceptions and other
exceptions to rollback and handle errors gracefully instead of letting the app
crash.


default -> System.out.println("Invalid option");
}
}

Expand All @@ -106,11 +240,11 @@ public void run() {
* @return {@code true} if the application is in development mode; {@code false} otherwise
*/
private static boolean isDevMode(String[] args) {
if (Boolean.getBoolean("devMode")) //Add VM option -DdevMode=true
if (Boolean.getBoolean("devMode"))
return true;
if ("true".equalsIgnoreCase(System.getenv("DEV_MODE"))) //Environment variable DEV_MODE=true
if ("true".equalsIgnoreCase(System.getenv("DEV_MODE")))
return true;
return Arrays.asList(args).contains("--dev"); //Argument --dev
return Arrays.asList(args).contains("--dev");
}

/**
Expand Down