package ins.frontend.utils; import org.openautonomousconnection.webserver.utils.Sha256; import java.sql.*; import java.util.Objects; import java.util.Optional; import java.util.UUID; /** * DAO for users table: * users(id, uid(uuid string), username(sha256 hex or plain), password(pbkdf2...)) */ public final class UserDao { private final DataSourceProvider dataSource; /** * Creates a UserDao. * * @param dataSource connection provider */ public UserDao(DataSourceProvider dataSource) { this.dataSource = Objects.requireNonNull(dataSource, "dataSource"); } /** * Finds a user by username. * * @param uid exact uid match * @return optional row * @throws SQLException on SQL errors */ public Optional findByUid(UUID uid) throws SQLException { if (uid == null) return Optional.empty(); String sql = "SELECT id, uid, username, password FROM users WHERE uid = ? LIMIT 1"; try (Connection c = dataSource.getConnection(); PreparedStatement ps = c.prepareStatement(sql)) { ps.setString(1, uid.toString()); try (ResultSet rs = ps.executeQuery()) { if (!rs.next()) return Optional.empty(); return Optional.of(new UserRow( rs.getInt("id"), rs.getString("uid"), rs.getString("username"), rs.getString("password") )); } } } /** * Creates a new user. * * @param uid uuid string (36 chars) * @param username username string (your choice: raw or sha256 hex) * @param passwordEncoded encoded password * @return generated users.id * @throws SQLException on SQL errors */ public int createUser(String uid, String username, String passwordEncoded) throws SQLException { if (uid == null || uid.isBlank()) throw new IllegalArgumentException("uid must not be blank"); if (username == null || username.isBlank()) throw new IllegalArgumentException("username must not be blank"); if (passwordEncoded == null || passwordEncoded.isBlank()) throw new IllegalArgumentException("passwordEncoded must not be blank"); String sql = "INSERT INTO users (uid, username, password) VALUES (?, ?, ?)"; try (Connection c = dataSource.getConnection(); PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, uid); ps.setString(2, Sha256.hex(username)); ps.setString(3, passwordEncoded); ps.executeUpdate(); try (ResultSet rs = ps.getGeneratedKeys()) { if (rs.next()) return rs.getInt(1); } } throw new SQLException("No generated key returned for users.id"); } /** * Convenience: creates a user with a new random UUID. * * @param username username * @param passwordEncoded encoded password * @return generated users.id * @throws SQLException on SQL errors */ public int createUserWithNewUuid(String username, String passwordEncoded) throws SQLException { UUID uuid = UUID.randomUUID(); while (findByUid(uuid).isPresent()) uuid = UUID.randomUUID(); return createUser(uuid.toString(), username, passwordEncoded); } /** * Finds a user by username. * * @param username exact username match * @return optional row * @throws SQLException on SQL errors */ public Optional findByUsername(String username) throws SQLException { if (username == null || username.isBlank()) return Optional.empty(); username = Sha256.hex(username); String sql = "SELECT id, uid, username, password FROM users WHERE username = ? LIMIT 1"; try (Connection c = dataSource.getConnection(); PreparedStatement ps = c.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs = ps.executeQuery()) { if (!rs.next()) return Optional.empty(); return Optional.of(new UserRow( rs.getInt("id"), rs.getString("uid"), rs.getString("username"), rs.getString("password") )); } } } /** * Connection provider abstraction. */ public interface DataSourceProvider { /** * @return open SQL connection * @throws SQLException on errors */ Connection getConnection() throws SQLException; } /** * User row. * * @param id users.id * @param uid users.uid (uuid) * @param username users.username * @param passwordEncoded users.password */ public record UserRow(int id, String uid, String username, String passwordEncoded) { } }