Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<artifactId>mysql</artifactId>
<version>1.21.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>2.0.17</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/example/AccountRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example;

import java.sql.SQLException;

public interface AccountRepository {
boolean findByNameAndPassword(String username, String password) throws SQLException;
int create(String firstName, String lastName, String ssn, String password, String username) throws SQLException;
int updatePassword(long userId, String newPassword) throws SQLException;
int delete(long userId) throws SQLException;
}
8 changes: 8 additions & 0 deletions src/main/java/com/example/DataSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example;

import java.sql.Connection;
import java.sql.SQLException;

public interface DataSource {
Connection getConnection() throws SQLException;
}
67 changes: 67 additions & 0 deletions src/main/java/com/example/JdbcAccountRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.example;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcAccountRepository implements AccountRepository {

private final DataSource dataSource;

public JdbcAccountRepository(DataSource dataSource) {
this.dataSource = dataSource;
}

@Override
public boolean findByNameAndPassword(String username, String password) throws SQLException {
String sql = "SELECT user_id FROM account WHERE name = ? AND password = ?";
// Använder try-with-resources för att säkerställa att Connection stängs
try (Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, username);
stmt.setString(2, password);
try (ResultSet rs = stmt.executeQuery()) {
return rs.next();
}
}
}
Comment on lines +16 to +28
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

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the repository structure and find schema files
git ls-files | grep -E "(schema|migration|\.sql)" | head -20

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 106


🏁 Script executed:

# Search for database schema definitions
find . -type f \( -name "*.sql" -o -name "*schema*" -o -name "*migration*" \) 2>/dev/null | head -20

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 108


🏁 Script executed:

# Look for password hashing related imports and methods in the codebase
rg -i "(bcrypt|argon|hash|password)" --type java -B 2 -A 2 | head -100

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 7764


🏁 Script executed:

# Find the JdbcAccountRepository class and examine the full context
fd -e java | xargs rg "JdbcAccountRepository" -l

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 167


🏁 Script executed:

# Search for where findByNameAndPassword is called from
rg "findByNameAndPassword" --type java -B 3 -A 3

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 1967


🏁 Script executed:

# Read the schema file to check for UNIQUE constraints on name column
cat src/main/resources/init.sql

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 4833


🏁 Script executed:

# Check the Main.java file for complete authentication flow
cat src/main/java/com/example/Main.java

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 10100


🏁 Script executed:

# Search for any password hashing or encoding utilities in the codebase
rg -i "(MessageDigest|encode|decode|hash|cipher)" --type java | head -50

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 61


🏁 Script executed:

# Check project dependencies (pom.xml or build.gradle)
fd -e pom.xml -o -e build.gradle | xargs cat 2>/dev/null | head -100

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 195


🏁 Script executed:

# Find and read pom.xml or build.gradle to check dependencies
find . -name "pom.xml" -o -name "build.gradle" | xargs cat 2>/dev/null

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 2940


🏁 Script executed:

# Read the complete JdbcAccountRepository implementation
cat src/main/java/com/example/JdbcAccountRepository.java

Repository: ithsjava25/databas-jdbc-johanbriger

Length of output: 2521


Store and compare password hashes, not plaintext values; enforce unique constraint on name column

  1. Password hashing: The codebase stores and compares passwords as plaintext (no bcrypt, argon2, or similar in dependencies). Passwords are inserted directly in create() and compared directly in findByNameAndPassword(). This is a critical security vulnerability. Before any password reaches the repository, hash and salt it (e.g., using Spring Security's BCryptPasswordEncoder or similar), and store only the hash. Update findByNameAndPassword() to accept a hashed password or implement a separate verification method that hashes the input before comparison. At minimum, rename the password parameter and column to passwordHash to signal that hashed values are expected.

  2. Name column uniqueness: The schema (init.sql) defines name as VARCHAR(255) with no UNIQUE constraint. Multiple users can have identical names, and login would succeed for any matching account without indication of duplicates. Add a UNIQUE constraint to the name column at the database level: ALTER TABLE account ADD UNIQUE (name);


@Override
public int create(String firstName, String lastName, String ssn, String password, String username) throws SQLException {
String sql = "INSERT INTO account (first_name, last_name, ssn, password, name) VALUES (?, ?, ?, ?, ?)";
try (Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, firstName);
stmt.setString(2, lastName);
stmt.setString(3, ssn);
stmt.setString(4, password);
stmt.setString(5, username);

return stmt.executeUpdate();
}
}

@Override
public int updatePassword(long userId, String newPassword) throws SQLException {
String sql = "UPDATE account SET password = ? WHERE user_id = ?";
try (Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, newPassword);
stmt.setLong(2, userId);

return stmt.executeUpdate();
}
}

@Override
public int delete(long userId) throws SQLException {
String sql = "DELETE FROM account WHERE user_id = ?";
try (Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setLong(1, userId);

return stmt.executeUpdate();
}
}
}
56 changes: 56 additions & 0 deletions src/main/java/com/example/JdbcMoonMissionRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.example;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class JdbcMoonMissionRepository implements MoonMissionRepository {

private final DataSource dataSource;

public JdbcMoonMissionRepository(DataSource dataSource) {
this.dataSource = dataSource;
}

@Override
public List<String> findAllSpacecraftNames() throws SQLException {
List<String> names = new ArrayList<>();
String sql = "SELECT spacecraft FROM moon_mission";
try (Connection connection = dataSource.getConnection();
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
names.add(rs.getString("spacecraft"));
}
}
return names;
}


@Override
public ResultSet findMissionById(long missionId, Connection connection) throws SQLException {
String sql = "SELECT * FROM moon_mission WHERE mission_id = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setLong(1, missionId);
return stmt.executeQuery();
}
Comment on lines +34 to +40
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 | 🟠 Major

Resource leak: PreparedStatement is never closed.

The PreparedStatement created on line 37 is not managed by try-with-resources. When the caller closes the ResultSet, the statement remains open, leading to resource exhaustion over time.

This is another reason to avoid returning ResultSet. Refactor to return a domain object:

-    @Override
-    public ResultSet findMissionById(long missionId, Connection connection) throws SQLException {
-        String sql = "SELECT * FROM moon_mission WHERE mission_id = ?";
-        PreparedStatement stmt = connection.prepareStatement(sql);
-        stmt.setLong(1, missionId);
-        return stmt.executeQuery();
-    }
+    @Override
+    public Optional<MoonMission> findMissionById(long missionId) throws SQLException {
+        String sql = "SELECT * FROM moon_mission WHERE mission_id = ?";
+        try (Connection connection = dataSource.getConnection();
+             PreparedStatement stmt = connection.prepareStatement(sql)) {
+            stmt.setLong(1, missionId);
+            try (ResultSet rs = stmt.executeQuery()) {
+                if (rs.next()) {
+                    return Optional.of(new MoonMission(
+                        rs.getLong("mission_id"),
+                        rs.getString("spacecraft"),
+                        rs.getDate("launch_date").toLocalDate(),
+                        rs.getString("outcome")
+                    ));
+                }
+                return Optional.empty();
+            }
+        }
+    }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/example/JdbcMoonMissionRepository.java around lines 34 to
40, the PreparedStatement created for the query is never closed and the method
returns a ResultSet (causing the statement to remain open); change the method to
not return ResultSet but instead use try-with-resources to open the
PreparedStatement and ResultSet, map the single-row result to a MoonMission
domain object (or Optional<MoonMission>) and return that, ensuring both
ResultSet and PreparedStatement are closed automatically; update method
signature and callers accordingly (or if you cannot change callers, document and
close the statement before returning by wrapping ResultSet consumption inside
the method and returning a materialized object).


@Override
public int countMissionsByYear(int year) throws SQLException {
String sql = "SELECT COUNT(*) FROM moon_mission WHERE YEAR(launch_date) = ?";
try (Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setInt(1, year);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return rs.getInt(1);
}
return 0;
}
}
}
}
Loading
Loading