Auto-create account in both MySQL and PG

This commit is contained in:
P0nk
2024-09-26 07:59:27 +02:00
parent bf9c02bc16
commit 647e67f6e8
13 changed files with 158 additions and 49 deletions

View File

@@ -7,7 +7,9 @@ import constants.id.ItemId;
import constants.id.MapId; import constants.id.MapId;
import database.PgDatabaseConnection; import database.PgDatabaseConnection;
import database.character.CharacterRepository; import database.character.CharacterRepository;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CharacterCreator { public class CharacterCreator {
private final PgDatabaseConnection connection; private final PgDatabaseConnection connection;
private final CharacterRepository chrRepository; private final CharacterRepository chrRepository;
@@ -20,9 +22,13 @@ public class CharacterCreator {
public boolean createNew(NewCharacterSpec spec, int accountId, int worldId) { public boolean createNew(NewCharacterSpec spec, int accountId, int worldId) {
CharacterStats stats = getStarterStats(spec, accountId, worldId); CharacterStats stats = getStarterStats(spec, accountId, worldId);
try {
connection.getHandle().useTransaction(h -> { connection.getHandle().useTransaction(h -> {
// chrRepository.insert(h, stats); // TODO: account needs to exist first chrRepository.insert(h, stats);
}); });
} catch (Exception e) {
log.warn("Failed to create new character in PG", e);
}
Item guide = getStarterGuide(spec.type()); Item guide = getStarterGuide(spec.type());
// TODO, save: // TODO, save:
// - character // - character

View File

@@ -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) {
}

View File

@@ -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();
}
}
}

View File

@@ -64,6 +64,7 @@ public class CharacterSaver {
try (Handle handle = pgConnection.getHandle()) { try (Handle handle = pgConnection.getHandle()) {
handle.useTransaction(h -> doPostgresSave(h, chr)); handle.useTransaction(h -> doPostgresSave(h, chr));
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error saving chr to PG: " + e.getMessage());
log.error("Error saving chr {} to PG", chr.getName(), e); log.error("Error saving chr {} to PG", chr.getName(), e);
} }

View File

@@ -9,6 +9,7 @@ import database.character.CharacterSaver;
import database.drop.DropProvider; import database.drop.DropProvider;
import lombok.Builder; import lombok.Builder;
import server.shop.ShopFactory; import server.shop.ShopFactory;
import service.AccountService;
import service.BanService; import service.BanService;
import service.NoteService; import service.NoteService;
import service.TransitionService; import service.TransitionService;
@@ -20,6 +21,7 @@ import java.util.Objects;
*/ */
@Builder @Builder
public record ChannelDependencies( public record ChannelDependencies(
AccountService accountService,
CharacterCreator characterCreator, CharacterLoader characterLoader, CharacterSaver characterSaver, CharacterCreator characterCreator, CharacterLoader characterLoader, CharacterSaver characterSaver,
NoteService noteService, FredrickProcessor fredrickProcessor, MakerProcessor makerProcessor, NoteService noteService, FredrickProcessor fredrickProcessor, MakerProcessor makerProcessor,
DropProvider dropProvider, CommandsExecutor commandsExecutor, ShopFactory shopFactory, DropProvider dropProvider, CommandsExecutor commandsExecutor, ShopFactory shopFactory,
@@ -27,6 +29,7 @@ public record ChannelDependencies(
) { ) {
public ChannelDependencies { public ChannelDependencies {
Objects.requireNonNull(accountService);
Objects.requireNonNull(characterCreator); Objects.requireNonNull(characterCreator);
Objects.requireNonNull(characterLoader); Objects.requireNonNull(characterLoader);
Objects.requireNonNull(characterSaver); Objects.requireNonNull(characterSaver);

View File

@@ -281,7 +281,8 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler()); registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler());
registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler()); registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler());
registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler()); 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.RELOG, new RelogRequestHandler());
registerHandler(RecvOpcode.SERVERLIST_REQUEST, new ServerlistRequestHandler()); registerHandler(RecvOpcode.SERVERLIST_REQUEST, new ServerlistRequestHandler());
registerHandler(RecvOpcode.SERVERSTATUS_REQUEST, new ServerStatusRequestHandler()); registerHandler(RecvOpcode.SERVERSTATUS_REQUEST, new ServerStatusRequestHandler());
@@ -291,7 +292,7 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.VIEW_ALL_CHAR, new ViewAllCharHandler()); registerHandler(RecvOpcode.VIEW_ALL_CHAR, new ViewAllCharHandler());
registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler()); registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler());
registerHandler(RecvOpcode.REGISTER_PIN, new RegisterPinHandler()); 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.REGISTER_PIC, new RegisterPicHandler());
registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler()); registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler());
registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler()); registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler());

View File

@@ -44,6 +44,7 @@ import constants.net.OpcodeConstants;
import constants.net.ServerConstants; import constants.net.ServerConstants;
import database.PgDatabaseConfig; import database.PgDatabaseConfig;
import database.PgDatabaseConnection; import database.PgDatabaseConnection;
import database.account.AccountRepository;
import database.character.CharacterLoader; import database.character.CharacterLoader;
import database.character.CharacterRepository; import database.character.CharacterRepository;
import database.character.CharacterSaver; import database.character.CharacterSaver;
@@ -88,6 +89,7 @@ import server.expeditions.ExpeditionBossLog;
import server.life.PlayerNPC; import server.life.PlayerNPC;
import server.quest.Quest; import server.quest.Quest;
import server.shop.ShopFactory; import server.shop.ShopFactory;
import service.AccountService;
import service.BanService; import service.BanService;
import service.NoteService; import service.NoteService;
import service.TransitionService; import service.TransitionService;
@@ -1015,6 +1017,7 @@ public class Server {
DropProvider dropProvider = new DropProvider(new DropRepository(connection)); DropProvider dropProvider = new DropProvider(new DropRepository(connection));
ShopFactory shopFactory = new ShopFactory(new ShopDao(connection)); ShopFactory shopFactory = new ShopFactory(new ShopDao(connection));
ChannelDependencies channelDependencies = ChannelDependencies.builder() ChannelDependencies channelDependencies = ChannelDependencies.builder()
.accountService(new AccountService(new AccountRepository(connection)))
.characterCreator(new CharacterCreator(connection, characterRepository)) .characterCreator(new CharacterCreator(connection, characterRepository))
.characterLoader(new CharacterLoader(monsterCardRepository)) .characterLoader(new CharacterLoader(monsterCardRepository))
.characterSaver(characterSaver) .characterSaver(characterSaver)

View File

@@ -22,25 +22,21 @@
package net.server.handlers.login; package net.server.handlers.login;
import client.Client; import client.Client;
import lombok.extern.slf4j.Slf4j;
import net.AbstractPacketHandler; import net.AbstractPacketHandler;
import net.packet.InPacket; import net.packet.InPacket;
import service.TransitionService;
import tools.PacketCreator; import tools.PacketCreator;
/* /*
* @author David * @author David
*/ */
@Slf4j
public final class GuestLoginHandler extends AbstractPacketHandler { public final class GuestLoginHandler extends AbstractPacketHandler {
private final TransitionService transitionService;
public GuestLoginHandler(TransitionService transitionService) {
this.transitionService = transitionService;
}
@Override @Override
public final void handlePacket(InPacket p, Client c) { public final void handlePacket(InPacket p, Client c) {
c.sendPacket(PacketCreator.sendGuestTOS()); c.sendPacket(PacketCreator.sendGuestTOS());
//System.out.println(slea.toString()); //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);
} }
} }

View File

@@ -33,6 +33,7 @@ import net.server.coordinator.session.Hwid;
import net.server.world.World; import net.server.world.World;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import service.AccountService;
import service.TransitionService; import service.TransitionService;
import tools.BCrypt; import tools.BCrypt;
import tools.DatabaseConnection; import tools.DatabaseConnection;
@@ -42,17 +43,18 @@ import tools.PacketCreator;
import java.sql.Connection; import java.sql.Connection;
import java.sql.Date; import java.sql.Date;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.Calendar; import java.util.Calendar;
public final class LoginPasswordHandler implements PacketHandler { public final class LoginPasswordHandler implements PacketHandler {
private static final Logger log = LoggerFactory.getLogger(LoginPasswordHandler.class); private static final Logger log = LoggerFactory.getLogger(LoginPasswordHandler.class);
private final AccountService accountService;
private final TransitionService transitionService; private final TransitionService transitionService;
public LoginPasswordHandler(TransitionService transitionService) { public LoginPasswordHandler(AccountService accountService, TransitionService transitionService) {
this.accountService = accountService;
this.transitionService = transitionService; this.transitionService = transitionService;
} }
@@ -80,21 +82,10 @@ public final class LoginPasswordHandler implements PacketHandler {
if (YamlConfig.config.server.AUTOMATIC_REGISTER && loginok == 5) { if (YamlConfig.config.server.AUTOMATIC_REGISTER && loginok == 5) {
try (Connection con = DatabaseConnection.getConnection(); try {
PreparedStatement ps = con.prepareStatement("INSERT INTO accounts (name, password, birthday, tempban) VALUES (?, ?, ?, ?);", Statement.RETURN_GENERATED_KEYS)) { //Jayd: Added birthday, tempban int accountId = createAccountPostgres(login, pwd);
ps.setString(1, login); createAccountMysql(accountId, login, pwd);
ps.setString(2, BCrypt.hashpw(pwd, BCrypt.gensalt(12))); c.setAccID(accountId);
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();
} finally { } finally {
loginok = c.login(login, pwd, hwid); 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 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) { if (!YamlConfig.config.server.USE_CHARACTER_ACCOUNT_CHECK) {
return; return;

View File

@@ -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));
}
}

View File

@@ -8,7 +8,7 @@ CREATE TABLE account
logged_in smallint DEFAULT 0 NOT NULL, logged_in smallint DEFAULT 0 NOT NULL,
created_at timestamp DEFAULT now() NOT NULL, created_at timestamp DEFAULT now() NOT NULL,
last_login timestamp, last_login timestamp,
birthday date NOT NULL, birthdate date NOT NULL,
banned boolean DEFAULT false NOT NULL, banned boolean DEFAULT false NOT NULL,
banreason text, banreason text,
macs text, macs text,
@@ -25,6 +25,10 @@ CREATE TABLE account
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE (name) UNIQUE (name)
); );
CREATE UNIQUE INDEX lower_account_name_idx ON "account" (lower(name) ); CREATE UNIQUE INDEX lower_account_name_idx ON "account" (lower(name));
GRANT SELECT, UPDATE ON TABLE account TO ${server-username}; GRANT SELECT, INSERT, UPDATE ON TABLE account TO ${server-username};
GRANT USAGE ON SEQUENCE account_id_seq 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');

View File

@@ -9,6 +9,7 @@ import database.PgDatabaseConfig;
import database.PgDatabaseConnection; import database.PgDatabaseConnection;
import database.migration.FlywayRunner; import database.migration.FlywayRunner;
import database.monsterbook.MonsterCardRepository; import database.monsterbook.MonsterCardRepository;
import org.jdbi.v3.core.Handle;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
@@ -120,10 +121,12 @@ class CharacterSaverTest {
SELECT level SELECT level
FROM chr FROM chr
WHERE id = :id"""; WHERE id = :id""";
return pgConnection.getHandle().createQuery(sql) try (Handle handle = pgConnection.getHandle()) {
return handle.createQuery(sql)
.bind("id", chrId) .bind("id", chrId)
.mapTo(Integer.class) .mapTo(Integer.class)
.one(); .one();
} }
}
} }

View File

@@ -2,6 +2,8 @@ package testutil;
import client.CharacterStats; import client.CharacterStats;
import database.PgDatabaseConnection; import database.PgDatabaseConnection;
import database.account.Account;
import database.account.AccountRepository;
import database.character.CharacterRepository; import database.character.CharacterRepository;
import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Handle;
@@ -10,24 +12,20 @@ import java.time.LocalDate;
public class TestData { public class TestData {
public static GeneratedIds create(PgDatabaseConnection connection) { public static GeneratedIds create(PgDatabaseConnection connection) {
int accountId = insertAccount(connection);
try (Handle handle = connection.getHandle()) { try (Handle handle = connection.getHandle()) {
int accountId = insertAccount(handle);
int chrId = insertChr(handle, accountId); int chrId = insertChr(handle, accountId);
return new GeneratedIds(accountId, chrId); return new GeneratedIds(accountId, chrId);
} }
} }
private static int insertAccount(Handle handle) { private static int insertAccount(PgDatabaseConnection connection) {
String sql = """ Account account = Account.builder()
INSERT INTO account (name, password, birthday) .name("accountname")
VALUES (:name, :password, :birthday)"""; .password("accountpassword")
return handle.createUpdate(sql) .birthdate(LocalDate.now())
.bind("name", "accountname") .build();
.bind("password", "accountpassword") return new AccountRepository(connection).insert(account);
.bind("birthday", LocalDate.of(2005, 5, 11))
.executeAndReturnGeneratedKeys()
.mapTo(Integer.class)
.one();
} }
private static int insertChr(Handle handle, int accountId) { private static int insertChr(Handle handle, int accountId) {