73public final class LoginSession
extends Session {
75 private static final Logger logger = LoggerFactory.getLogger(LoginSession.class);
77 private static final ConcurrentMap<String, FailedLoginAttempt> failedLogins =
new ConcurrentHashMap<>();
79 public LoginSession(Channel
channel) {
87 OSRoyale.getInstance().getLoginExecutorService().execute(
this, packet);
93 final ConcurrentMap<String, FailedLoginAttempt> failedLogins =
LoginSession.failedLogins;
95 final String username = packet.getUsername();
97 final FailedLoginAttempt attempt = failedLogins.get(username);
98 if (attempt !=
null) {
99 final Stopwatch stopwatch = attempt.getStopwatch();
100 final AtomicInteger atomicTime = attempt.getAttempt();
101 final int time = atomicTime.get();
102 if (time >=
Config.FAILED_LOGIN_ATTEMPTS
103 && !stopwatch.elapsed(
Config.FAILED_LOGIN_TIMEOUT, TimeUnit.MINUTES)) {
106 }
else if (time >=
Config.FAILED_LOGIN_ATTEMPTS
107 && stopwatch.elapsed(
Config.FAILED_LOGIN_TIMEOUT, TimeUnit.MINUTES)) {
108 failedLogins.remove(username);
110 atomicTime.incrementAndGet();
114 final Player player =
new Player(username);
115 final String password = packet.getPassword();
116 player.setPassword(password);
118 final LoginResponse response = evaluate(player);
119 if (response == LoginResponse.INVALID_CREDENTIALS) {
120 if (!failedLogins.containsKey(username)) {
121 failedLogins.put(username,
new FailedLoginAttempt());
123 }
else if (response == LoginResponse.NORMAL) {
124 failedLogins.remove(username);
129 if (response != LoginResponse.NORMAL) {
130 sendFailedResponse(
channel, response);
134 final Argon2Types argon2Type = Argon2.argon2Type(player.getPassword());
135 if (argon2Type != Argon2.DEFAULT_TYPE) {
137 final String passwordHash = Argon2.getDefault().hash(
138 Argon2.DEFAULT_ITERATIONS,
139 Argon2.DEFAULT_MEMORY,
140 Argon2.DEFAULT_PARALLELISM,
142 player.setPassword(passwordHash);
145 ProfileRepository.put(
new Profile(username, player.lastHost, player.hostList, player.right));
147 channel.writeAndFlush(
new LoginResponsePacket(response, player.right,
false))
148 .addListener((ChannelFutureListener) sourceFuture -> {
150 final ChannelPipeline pipeline =
channel.pipeline();
151 pipeline.replace(
"login-decoder",
152 "game-decoder",
new GamePacketDecoder(packet.getDecryptor()));
153 pipeline.replace(
"login-encoder",
154 "game-encoder",
new GamePacketEncoder(packet.getEncryptor()));
156 final GameSession session =
new GameSession(
channel, player);
157 channel.attr(Config.SESSION_KEY).set(session);
158 player.setSession(session);
160 World.queueLogin(player);
161 }
catch (
final Exception e) {
162 logger.error(
"Failed to queue login for \"" + username +
"\"", e);
167 private static void sendFailedResponse(
final Channel
channel,
final LoginResponse response) {
168 channel.writeAndFlush(
new LoginResponsePacket(response))
169 .addListener(ChannelFutureListener.CLOSE);
172 private LoginResponse evaluate(Player player) {
173 final String username = player.getUsername();
174 final String password = player.getPassword();
175 final boolean isEmail = username.indexOf(
'@') != -1;
178 if (!OSRoyale.serverStarted.get()) {
179 return LoginResponse.SERVER_BEING_UPDATED;
183 for (String botName : BotUtility.BOT_NAMES) {
184 if (username.equalsIgnoreCase(botName)) {
185 return LoginResponse.INSUFFICIENT_PERMSSION;
190 if (World.getPlayerCount() == Config.MAX_PLAYERS) {
191 return LoginResponse.WORLD_FULL;
195 if (World.update.get()) {
196 return LoginResponse.SERVER_BEING_UPDATED;
199 if (BannedPlayers.bans.contains(username.toLowerCase())) {
200 return LoginResponse.ACCOUNT_DISABLED;
204 if (!Config.FORUM_INTEGRATION) {
205 return LoginResponse.BAD_USERNAME;
208 if (username.length() > Config.EMAIL_MAX_CHARACTERS || username.length() < Config.EMAIL_MIN_CHARACTERS) {
209 return LoginResponse.INVALID_EMAIL;
213 if (!(username.matches(
"^[a-zA-Z0-9.@]{1," + Config.EMAIL_MAX_CHARACTERS +
"}$"))) {
214 return LoginResponse.INVALID_CREDENTIALS;
216 }
else if (username.length() < Config.USERNAME_MIN_CHARACTERS) {
217 return LoginResponse.SHORT_USERNAME;
218 }
else if (username.length() > Config.USERNAME_MAX_CHARACTERS) {
219 return LoginResponse.BAD_USERNAME;
220 }
else if (World.getPlayerByHash(Utility.nameToLong(username)).isPresent()) {
221 return LoginResponse.ACCOUNT_ONLINE;
222 }
else if (!(username.matches(
"^[a-zA-Z0-9 ]{1," + Config.USERNAME_MAX_CHARACTERS +
"}$"))) {
223 return LoginResponse.INVALID_CREDENTIALS;
224 }
else if (password.isEmpty()) {
225 return LoginResponse.INVALID_CREDENTIALS;
228 if (World.search(username).isPresent()) {
229 return LoginResponse.ACCOUNT_ONLINE;
232 if (Config.FORUM_INTEGRATION) {
234 final LoginResponse response = authenticatedForumUser(player, isEmail);
235 if (response != LoginResponse.NORMAL) {
240 LoginResponse response = PlayerSerializer.load(player, password);
242 if (World.searchAll(username).isPresent()) {
243 return LoginResponse.ACCOUNT_ONLINE;
249 private LoginResponse authenticatedForumUser(Player player,
boolean isEmail) {
250 final String username = player.getUsername();
252 final LoginResponse response =
new JdbcSession(ForumService.getConnectionPool())
253 .sql(isEmail ?
"SELECT member_id, members_pass_hash, name, temp_ban FROM core_members WHERE UPPER(email) = ?" :
"SELECT member_id, members_pass_hash, temp_ban FROM core_members WHERE UPPER(name) = ?")
254 .set(username.toUpperCase())
255 .select((rset, stmt) -> {
257 final int memberId = rset.getInt(1);
258 final String passwordHash = rset.getString(2);
259 final String forumUsername = isEmail ? rset.getString(3) : username;
260 final long unixTime = rset.getLong(isEmail ? 4 : 3);
262 if (isBanned(unixTime)) {
263 return LoginResponse.ACCOUNT_DISABLED;
266 if (passwordHash.isEmpty()) {
267 return LoginResponse.INVALID_CREDENTIALS;
268 }
else if (BCrypt.checkpw(player.getPassword(), passwordHash)) {
269 player.setMemberId(memberId);
270 player.setUsername(forumUsername);
271 player.setPassword(passwordHash);
272 return LoginResponse.NORMAL;
274 return LoginResponse.INVALID_CREDENTIALS;
277 return LoginResponse.FORUM_REGISTRATION;
280 }
catch (Exception ex) {
281 ex.printStackTrace();
283 return LoginResponse.LOGIN_SERVER_OFFLINE;
286 private boolean isBanned(
long unixTime) {
290 }
else if (unixTime == -1) {
294 final Date date = Date.from(Instant.ofEpochSecond(unixTime));
296 final Date currentDate = Date.from(Instant.now());
298 return date.after(currentDate);
306 private static final class FailedLoginAttempt {
308 private final AtomicInteger attempt =
new AtomicInteger(0);
309 private final Stopwatch stopwatch = Stopwatch.start();
311 public AtomicInteger getAttempt() {
315 public Stopwatch getStopwatch() {