package ins.frontend.utils; import org.openautonomousconnection.webserver.utils.PasswordHasher; import java.util.Objects; /** * Registration service for creating users with secure password hashing and basic validation. */ public final class RegistrationService { private static final int USERNAME_MIN = 5; private static final int USERNAME_MAX = 256; private static final int PASSWORD_MIN = 6; private static final int PASSWORD_MAX = 256; private final UserDao userDao; private final PasswordHasher hasher; /** * Creates a registration service. * * @param userDao user DAO * @param hasher password hasher */ public RegistrationService(UserDao userDao, PasswordHasher hasher) { this.userDao = Objects.requireNonNull(userDao, "userDao"); this.hasher = Objects.requireNonNull(hasher, "hasher"); } private static String normalizeUsername(String u) { if (u == null) return null; String t = u.trim(); return t.isEmpty() ? null : t; } private static String validate(String username, String password) { if (username == null) return "Missing username."; if (password == null) return "Missing password."; if (username.length() < USERNAME_MIN) return "Username too short (min " + USERNAME_MIN + ")."; if (username.length() > USERNAME_MAX) return "Username too long (max " + USERNAME_MAX + ")."; // Allow only a safe subset to avoid weird edge cases in UI and future. for (int i = 0; i < username.length(); i++) { char c = username.charAt(i); boolean ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.'; if (!ok) return "Username contains invalid characters."; } if (password.length() < PASSWORD_MIN) return "Password too short (min " + PASSWORD_MIN + ")."; if (password.length() > PASSWORD_MAX) return "Password too long."; return null; } /** * Registers a new user. * * @param usernameRaw raw username (from form) * @param passwordRaw raw password (from form) * @return result containing either userId or an error message */ public Result register(String usernameRaw, String passwordRaw) { String username = normalizeUsername(usernameRaw); String password = (passwordRaw == null) ? "" : passwordRaw; String validationError = validate(username, password); if (validationError != null) { return Result.error(validationError); } try { // Choose: store username as-is OR store sha256(username). // Your schema says: username(sha256 hex or plain) -> keep it plain for now. String usernameStored = username; if (userDao.findByUsername(usernameStored).isPresent()) { return Result.error("Username already exists."); } String passwordEncoded = hasher.hash(password); int userId = userDao.createUserWithNewUuid(usernameStored, passwordEncoded); return Result.ok(userId); } catch (Exception e) { return Result.error("Registration failed: " + e.getMessage()); } } /** * Registration result. * * @param ok whether succeeded * @param userId created user id, or -1 * @param error error message, or null */ public record Result(boolean ok, int userId, String error) { /** * Creates a success result. * * @param userId user id * @return result */ public static Result ok(int userId) { return new Result(true, userId, null); } /** * Creates an error result. * * @param error error message * @return result */ public static Result error(String error) { return new Result(false, -1, error); } } }