diff --git a/src/main/java/client/creator/CharacterCreator.java b/src/main/java/client/creator/CharacterCreator.java index 9674a4e45c..576d31e905 100644 --- a/src/main/java/client/creator/CharacterCreator.java +++ b/src/main/java/client/creator/CharacterCreator.java @@ -7,7 +7,9 @@ import constants.id.ItemId; import constants.id.MapId; import database.PgDatabaseConnection; import database.character.CharacterRepository; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class CharacterCreator { private final PgDatabaseConnection connection; private final CharacterRepository chrRepository; @@ -20,9 +22,13 @@ public class CharacterCreator { public boolean createNew(NewCharacterSpec spec, int accountId, int worldId) { CharacterStats stats = getStarterStats(spec, accountId, worldId); - connection.getHandle().useTransaction(h -> { - // chrRepository.insert(h, stats); // TODO: account needs to exist first - }); + try { + connection.getHandle().useTransaction(h -> { + chrRepository.insert(h, stats); + }); + } catch (Exception e) { + log.warn("Failed to create new character in PG", e); + } Item guide = getStarterGuide(spec.type()); // TODO, save: // - character diff --git a/src/main/java/database/account/Account.java b/src/main/java/database/account/Account.java new file mode 100644 index 0000000000..1984811012 --- /dev/null +++ b/src/main/java/database/account/Account.java @@ -0,0 +1,10 @@ +package database.account; + +import lombok.Builder; + +import java.time.LocalDate; + +@Builder +public record Account(String name, String password, boolean acceptedTos, LocalDate birthdate, String pin, String pic, + int loggedIn) { +} diff --git a/src/main/java/database/account/AccountRepository.java b/src/main/java/database/account/AccountRepository.java new file mode 100644 index 0000000000..608fba9f44 --- /dev/null +++ b/src/main/java/database/account/AccountRepository.java @@ -0,0 +1,31 @@ +package database.account; + +import database.PgDatabaseConnection; +import org.jdbi.v3.core.Handle; + +public class AccountRepository { + private final PgDatabaseConnection connection; + + public AccountRepository(PgDatabaseConnection connection) { + this.connection = connection; + } + + public Account getByName(String name) { + return null; // TODO + } + + public Integer insert(Account account) { + String sql = """ + INSERT INTO account (name, password, birthdate) + VALUES (:name, :password, :birthdate)"""; + try (Handle handle = connection.getHandle()) { + return handle.createUpdate(sql) + .bind("name", account.name()) + .bind("password", account.password()) + .bind("birthdate", account.birthdate()) + .executeAndReturnGeneratedKeys("id") + .mapTo(Integer.class) + .one(); + } + } +} diff --git a/src/main/java/database/character/CharacterSaver.java b/src/main/java/database/character/CharacterSaver.java index eb57167aeb..5539e6a95d 100644 --- a/src/main/java/database/character/CharacterSaver.java +++ b/src/main/java/database/character/CharacterSaver.java @@ -64,6 +64,7 @@ public class CharacterSaver { try (Handle handle = pgConnection.getHandle()) { handle.useTransaction(h -> doPostgresSave(h, chr)); } catch (Exception e) { + System.err.println("Error saving chr to PG: " + e.getMessage()); log.error("Error saving chr {} to PG", chr.getName(), e); } diff --git a/src/main/java/net/ChannelDependencies.java b/src/main/java/net/ChannelDependencies.java index 9944ba7bde..256ef3e462 100644 --- a/src/main/java/net/ChannelDependencies.java +++ b/src/main/java/net/ChannelDependencies.java @@ -9,6 +9,7 @@ import database.character.CharacterSaver; import database.drop.DropProvider; import lombok.Builder; import server.shop.ShopFactory; +import service.AccountService; import service.BanService; import service.NoteService; import service.TransitionService; @@ -20,6 +21,7 @@ import java.util.Objects; */ @Builder public record ChannelDependencies( + AccountService accountService, CharacterCreator characterCreator, CharacterLoader characterLoader, CharacterSaver characterSaver, NoteService noteService, FredrickProcessor fredrickProcessor, MakerProcessor makerProcessor, DropProvider dropProvider, CommandsExecutor commandsExecutor, ShopFactory shopFactory, @@ -27,6 +29,7 @@ public record ChannelDependencies( ) { public ChannelDependencies { + Objects.requireNonNull(accountService); Objects.requireNonNull(characterCreator); Objects.requireNonNull(characterLoader); Objects.requireNonNull(characterSaver); diff --git a/src/main/java/net/PacketProcessor.java b/src/main/java/net/PacketProcessor.java index 5ad7b2d899..0d16bb3a58 100644 --- a/src/main/java/net/PacketProcessor.java +++ b/src/main/java/net/PacketProcessor.java @@ -281,7 +281,8 @@ public final class PacketProcessor { registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler()); registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler()); registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler()); - registerHandler(RecvOpcode.LOGIN_PASSWORD, new LoginPasswordHandler(channelDeps.transitionService())); + registerHandler(RecvOpcode.LOGIN_PASSWORD, new LoginPasswordHandler(channelDeps.accountService(), + channelDeps.transitionService())); registerHandler(RecvOpcode.RELOG, new RelogRequestHandler()); registerHandler(RecvOpcode.SERVERLIST_REQUEST, new ServerlistRequestHandler()); registerHandler(RecvOpcode.SERVERSTATUS_REQUEST, new ServerStatusRequestHandler()); @@ -291,7 +292,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.VIEW_ALL_CHAR, new ViewAllCharHandler()); registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler()); registerHandler(RecvOpcode.REGISTER_PIN, new RegisterPinHandler()); - registerHandler(RecvOpcode.GUEST_LOGIN, new GuestLoginHandler(channelDeps.transitionService())); + registerHandler(RecvOpcode.GUEST_LOGIN, new GuestLoginHandler()); registerHandler(RecvOpcode.REGISTER_PIC, new RegisterPicHandler()); registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler()); registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler()); diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index ad937d2d76..89e6556aa8 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -44,6 +44,7 @@ import constants.net.OpcodeConstants; import constants.net.ServerConstants; import database.PgDatabaseConfig; import database.PgDatabaseConnection; +import database.account.AccountRepository; import database.character.CharacterLoader; import database.character.CharacterRepository; import database.character.CharacterSaver; @@ -88,6 +89,7 @@ import server.expeditions.ExpeditionBossLog; import server.life.PlayerNPC; import server.quest.Quest; import server.shop.ShopFactory; +import service.AccountService; import service.BanService; import service.NoteService; import service.TransitionService; @@ -1015,6 +1017,7 @@ public class Server { DropProvider dropProvider = new DropProvider(new DropRepository(connection)); ShopFactory shopFactory = new ShopFactory(new ShopDao(connection)); ChannelDependencies channelDependencies = ChannelDependencies.builder() + .accountService(new AccountService(new AccountRepository(connection))) .characterCreator(new CharacterCreator(connection, characterRepository)) .characterLoader(new CharacterLoader(monsterCardRepository)) .characterSaver(characterSaver) diff --git a/src/main/java/net/server/handlers/login/GuestLoginHandler.java b/src/main/java/net/server/handlers/login/GuestLoginHandler.java index 70c33c63d2..a18c1c6249 100644 --- a/src/main/java/net/server/handlers/login/GuestLoginHandler.java +++ b/src/main/java/net/server/handlers/login/GuestLoginHandler.java @@ -22,25 +22,21 @@ package net.server.handlers.login; import client.Client; +import lombok.extern.slf4j.Slf4j; import net.AbstractPacketHandler; import net.packet.InPacket; -import service.TransitionService; import tools.PacketCreator; /* * @author David */ +@Slf4j public final class GuestLoginHandler extends AbstractPacketHandler { - private final TransitionService transitionService; - - public GuestLoginHandler(TransitionService transitionService) { - this.transitionService = transitionService; - } @Override public final void handlePacket(InPacket p, Client c) { c.sendPacket(PacketCreator.sendGuestTOS()); //System.out.println(slea.toString()); - new LoginPasswordHandler(transitionService).handlePacket(p, c); + log.error("Unexpected guest login. How did you trigger this? It shouldn't be possible in v83. Packet: {}", p); } } diff --git a/src/main/java/net/server/handlers/login/LoginPasswordHandler.java b/src/main/java/net/server/handlers/login/LoginPasswordHandler.java index fdfe826179..40bae25cfd 100644 --- a/src/main/java/net/server/handlers/login/LoginPasswordHandler.java +++ b/src/main/java/net/server/handlers/login/LoginPasswordHandler.java @@ -33,6 +33,7 @@ import net.server.coordinator.session.Hwid; import net.server.world.World; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import service.AccountService; import service.TransitionService; import tools.BCrypt; import tools.DatabaseConnection; @@ -42,17 +43,18 @@ import tools.PacketCreator; import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; import java.sql.Timestamp; import java.util.Calendar; public final class LoginPasswordHandler implements PacketHandler { private static final Logger log = LoggerFactory.getLogger(LoginPasswordHandler.class); + + private final AccountService accountService; private final TransitionService transitionService; - public LoginPasswordHandler(TransitionService transitionService) { + public LoginPasswordHandler(AccountService accountService, TransitionService transitionService) { + this.accountService = accountService; this.transitionService = transitionService; } @@ -80,21 +82,10 @@ public final class LoginPasswordHandler implements PacketHandler { if (YamlConfig.config.server.AUTOMATIC_REGISTER && loginok == 5) { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("INSERT INTO accounts (name, password, birthday, tempban) VALUES (?, ?, ?, ?);", Statement.RETURN_GENERATED_KEYS)) { //Jayd: Added birthday, tempban - ps.setString(1, login); - ps.setString(2, BCrypt.hashpw(pwd, BCrypt.gensalt(12))); - ps.setDate(3, Date.valueOf(DefaultDates.getBirthday())); - ps.setTimestamp(4, Timestamp.valueOf(DefaultDates.getTempban())); - ps.executeUpdate(); - - try (ResultSet rs = ps.getGeneratedKeys()) { - rs.next(); - c.setAccID(rs.getInt(1)); - } - } catch (SQLException e) { - c.setAccID(-1); - e.printStackTrace(); + try { + int accountId = createAccountPostgres(login, pwd); + createAccountMysql(accountId, login, pwd); + c.setAccID(accountId); } finally { loginok = c.login(login, pwd, hwid); } @@ -126,6 +117,24 @@ public final class LoginPasswordHandler implements PacketHandler { } } + private int createAccountPostgres(String name, String password) { + return accountService.createNew(name, password); + } + + private void createAccountMysql(int id, String name, String password) { + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO accounts (id, name, password, birthday, tempban) VALUES (?, ?, ?, ?, ?);")) { + ps.setInt(1, id); + ps.setString(2, name); + ps.setString(3, BCrypt.hashpw(password, BCrypt.gensalt(12))); + ps.setDate(4, Date.valueOf(DefaultDates.getBirthday())); + ps.setTimestamp(5, Timestamp.valueOf(DefaultDates.getTempban())); + ps.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + private void checkChar(Client c) { // issue with multiple chars from same account login found by shavit, resinate if (!YamlConfig.config.server.USE_CHARACTER_ACCOUNT_CHECK) { return; diff --git a/src/main/java/service/AccountService.java b/src/main/java/service/AccountService.java new file mode 100644 index 0000000000..e7db415663 --- /dev/null +++ b/src/main/java/service/AccountService.java @@ -0,0 +1,44 @@ +package service; + +import database.account.Account; +import database.account.AccountRepository; +import lombok.extern.slf4j.Slf4j; +import tools.BCrypt; + +import java.time.LocalDate; +import java.util.Optional; + +@Slf4j +public class AccountService { + private static final int PASSWORD_HASH_SALT_LOG_ROUNDS = 12; + private static final LocalDate GMS_RELEASE = LocalDate.of(2005, 5, 11); + + private final AccountRepository accountRepository; + + public AccountService(AccountRepository accountRepository) { + this.accountRepository = accountRepository; + } + + public int createNew(String name, String password) { + Account newAccount = Account.builder() + .name(name) + .password(hashPassword(password)) + .birthdate(GMS_RELEASE) + .build(); + + Integer accountId; + try { + accountId = accountRepository.insert(newAccount); + } catch (Exception e) { + log.error("Failed to create new account", e); + throw new RuntimeException("Failed to create new account"); + } + + return Optional.ofNullable(accountId) + .orElseThrow(() -> new RuntimeException("Failed to create new account - missing id")); + } + + private String hashPassword(String password) { + return BCrypt.hashpw(password, BCrypt.gensalt(PASSWORD_HASH_SALT_LOG_ROUNDS)); + } +} diff --git a/src/main/resources/db/migration/postgresql/V0.2__account.sql b/src/main/resources/db/migration/postgresql/V0.2__account.sql index c297c48249..ab4c8cbbd6 100644 --- a/src/main/resources/db/migration/postgresql/V0.2__account.sql +++ b/src/main/resources/db/migration/postgresql/V0.2__account.sql @@ -8,7 +8,7 @@ CREATE TABLE account logged_in smallint DEFAULT 0 NOT NULL, created_at timestamp DEFAULT now() NOT NULL, last_login timestamp, - birthday date NOT NULL, + birthdate date NOT NULL, banned boolean DEFAULT false NOT NULL, banreason text, macs text, @@ -25,6 +25,10 @@ CREATE TABLE account PRIMARY KEY (id), UNIQUE (name) ); -CREATE UNIQUE INDEX lower_account_name_idx ON "account" (lower(name) ); -GRANT SELECT, UPDATE ON TABLE account TO ${server-username}; +CREATE UNIQUE INDEX lower_account_name_idx ON "account" (lower(name)); +GRANT SELECT, INSERT, UPDATE ON TABLE account TO ${server-username}; GRANT USAGE ON SEQUENCE account_id_seq TO ${server-username}; +ALTER SEQUENCE account_id_seq RESTART WITH 1000; + +-- INSERT INTO account (id, name, password, pin, pic, birthdate) +-- VALUES (1, 'admin', '$2y$12$aFD9BDeUocDMY1X4tDYDyeJw/HhkQwCQWs3KAY7gCaRG0cpqJcaL.', '0000', '000000', '2005-05-11'); diff --git a/src/test/java/database/character/CharacterSaverTest.java b/src/test/java/database/character/CharacterSaverTest.java index bc214bde11..ad84c4e7a9 100644 --- a/src/test/java/database/character/CharacterSaverTest.java +++ b/src/test/java/database/character/CharacterSaverTest.java @@ -9,6 +9,7 @@ import database.PgDatabaseConfig; import database.PgDatabaseConnection; import database.migration.FlywayRunner; import database.monsterbook.MonsterCardRepository; +import org.jdbi.v3.core.Handle; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -120,10 +121,12 @@ class CharacterSaverTest { SELECT level FROM chr WHERE id = :id"""; - return pgConnection.getHandle().createQuery(sql) - .bind("id", chrId) - .mapTo(Integer.class) - .one(); + try (Handle handle = pgConnection.getHandle()) { + return handle.createQuery(sql) + .bind("id", chrId) + .mapTo(Integer.class) + .one(); + } } } diff --git a/src/test/java/testutil/TestData.java b/src/test/java/testutil/TestData.java index a8a1bce717..dadb56b8f7 100644 --- a/src/test/java/testutil/TestData.java +++ b/src/test/java/testutil/TestData.java @@ -2,6 +2,8 @@ package testutil; import client.CharacterStats; import database.PgDatabaseConnection; +import database.account.Account; +import database.account.AccountRepository; import database.character.CharacterRepository; import org.jdbi.v3.core.Handle; @@ -10,24 +12,20 @@ import java.time.LocalDate; public class TestData { public static GeneratedIds create(PgDatabaseConnection connection) { + int accountId = insertAccount(connection); try (Handle handle = connection.getHandle()) { - int accountId = insertAccount(handle); int chrId = insertChr(handle, accountId); return new GeneratedIds(accountId, chrId); } } - private static int insertAccount(Handle handle) { - String sql = """ - INSERT INTO account (name, password, birthday) - VALUES (:name, :password, :birthday)"""; - return handle.createUpdate(sql) - .bind("name", "accountname") - .bind("password", "accountpassword") - .bind("birthday", LocalDate.of(2005, 5, 11)) - .executeAndReturnGeneratedKeys() - .mapTo(Integer.class) - .one(); + private static int insertAccount(PgDatabaseConnection connection) { + Account account = Account.builder() + .name("accountname") + .password("accountpassword") + .birthdate(LocalDate.now()) + .build(); + return new AccountRepository(connection).insert(account); } private static int insertChr(Handle handle, int accountId) {