RuneHive-Game
Loading...
Searching...
No Matches
LoginSession.java
Go to the documentation of this file.
1package com.runehive.net.session;
2
3import com.jcabi.jdbc.JdbcSession;
4import com.runehive.Config;
5import com.runehive.RuneHive;
6import com.runehive.content.bot.BotUtility;
7import com.runehive.game.service.ForumService;
8import com.runehive.game.world.World;
9import com.runehive.game.world.entity.mob.player.BannedPlayers;
10import com.runehive.game.world.entity.mob.player.Player;
11import com.runehive.game.world.entity.mob.player.persist.PlayerSerializer;
12import com.runehive.game.world.entity.mob.player.profile.Profile;
13import com.runehive.game.world.entity.mob.player.profile.ProfileRepository;
14import com.runehive.net.codec.game.GamePacketDecoder;
15import com.runehive.net.codec.game.GamePacketEncoder;
16import com.runehive.net.codec.login.LoginDetailsPacket;
17import com.runehive.net.codec.login.LoginResponse;
18import com.runehive.net.codec.login.LoginResponsePacket;
19import com.runehive.util.Stopwatch;
20import com.runehive.util.Utility;
21import de.mkammerer.argon2.Argon2Factory.Argon2Types;
22import io.netty.channel.Channel;
23import io.netty.channel.ChannelFutureListener;
24import io.netty.channel.ChannelPipeline;
25import org.jire.runehiveps.Argon2;
26import org.mindrot.jbcrypt.BCrypt;
27import org.slf4j.Logger;
28import org.slf4j.LoggerFactory;
29
30import java.time.Instant;
31import java.util.Date;
32import java.util.concurrent.ConcurrentHashMap;
33import java.util.concurrent.ConcurrentMap;
34import java.util.concurrent.TimeUnit;
35import java.util.concurrent.atomic.AtomicInteger;
36
37/**
38 * Represents a {@link Session} for authenticating users logging in.
39 *
40 * @author nshusa
41 */
42public final class LoginSession extends Session {
43
44 private static final Logger logger = LoggerFactory.getLogger(LoginSession.class);
45
46 private static final ConcurrentMap<String, FailedLoginAttempt> failedLogins = new ConcurrentHashMap<>();
47
48 public LoginSession(Channel channel) {
49 super(channel);
50 }
51
52 @Override
53 public void handleClientPacket(Object o) {
54 if (o instanceof LoginDetailsPacket) {
57 //handleUserLoginDetails(packet);
58 }
59 }
60
62 final ConcurrentMap<String, FailedLoginAttempt> failedLogins = LoginSession.failedLogins;
63
64 final String username = packet.getUsername();
65
66 final FailedLoginAttempt attempt = failedLogins.get(username);
67 if (attempt != null) {
68 final Stopwatch stopwatch = attempt.getStopwatch();
69 final AtomicInteger atomicTime = attempt.getAttempt();
70 final int time = atomicTime.get();
72 && !stopwatch.elapsed(Config.FAILED_LOGIN_TIMEOUT, TimeUnit.MINUTES)) {
74 return;
75 } else if (time >= Config.FAILED_LOGIN_ATTEMPTS
76 && stopwatch.elapsed(Config.FAILED_LOGIN_TIMEOUT, TimeUnit.MINUTES)) {
77 failedLogins.remove(username);
78 } else {
79 atomicTime.incrementAndGet();
80 }
81 }
82
83 final Player player = new Player(username);
84 final String password = packet.getPassword();
85 player.setPassword(password);
86
87 final LoginResponse response = evaluate(player);
88 if (response == LoginResponse.INVALID_CREDENTIALS) {
89 if (!failedLogins.containsKey(username)) {
90 failedLogins.put(username, new FailedLoginAttempt());
91 }
92 } else if (response == LoginResponse.NORMAL) {
93 failedLogins.remove(username);
94 }
95
96 final Channel channel = this.channel;
97
98 if (response != LoginResponse.NORMAL) {
99 sendFailedResponse(channel, response);
100 return;
101 }
102
103 final Argon2Types argon2Type = Argon2.argon2Type(player.getPassword());
104 if (argon2Type != Argon2.DEFAULT_TYPE) {
105 // needs rehashing (this should be moved onto another thread, as hashing is slow)
106 final String passwordHash = Argon2.getDefault().hash(
107 Argon2.DEFAULT_ITERATIONS,
108 Argon2.DEFAULT_MEMORY,
109 Argon2.DEFAULT_PARALLELISM,
110 password);
111 player.setPassword(passwordHash); // update password to hashed version
112 }
113
114 ProfileRepository.put(new Profile(username, player.lastHost, player.hostList, player.right));
115
116 channel.writeAndFlush(new LoginResponsePacket(response, player.right, false))
117 .addListener((ChannelFutureListener) sourceFuture -> {
118 try {
119 final ChannelPipeline pipeline = channel.pipeline();
120 pipeline.replace("login-decoder",
121 "game-decoder", new GamePacketDecoder(packet.getDecryptor()));
122 pipeline.replace("login-encoder",
123 "game-encoder", new GamePacketEncoder(packet.getEncryptor()));
124
125 final GameSession session = new GameSession(channel, player);
127 player.setSession(session);
128
129 World.queueLogin(player);
130 } catch (final Exception e) {
131 logger.error("Failed to queue login for \"" + username + "\"", e);
132 }
133 });
134 }
135
136 private static void sendFailedResponse(final Channel channel, final LoginResponse response) {
137 channel.writeAndFlush(new LoginResponsePacket(response))
138 .addListener(ChannelFutureListener.CLOSE);
139 }
140
141 private LoginResponse evaluate(Player player) {
142 final String username = player.getUsername();
143 final String password = player.getPassword();
144 final boolean isEmail = username.indexOf('@') != -1;
145
146 // prevents users from logging in before the server is ready to accept connections
147 if (!RuneHive.serverStarted.get()) {
149 }
150
151 // prevents users from using accounts with bot names
152 for (String botName : BotUtility.BOT_NAMES) {
153 if (username.equalsIgnoreCase(botName)) {
155 }
156 }
157
158 // the world is currently full
161 }
162
163 // prevents users from logging in if the world is being updated
164 if (World.update.get()) {
166 }
167
168 if (BannedPlayers.bans.contains(username.toLowerCase())) {
170 }
171
172 if (isEmail) {
175 }
176
177 if (username.length() > Config.EMAIL_MAX_CHARACTERS || username.length() < Config.EMAIL_MIN_CHARACTERS) {
179 }
180
181 // does email have illegal characters
182 if (!(username.matches("^[a-zA-Z0-9.@]{1," + Config.EMAIL_MAX_CHARACTERS + "}$"))) {
184 }
185 } else if (username.length() < Config.USERNAME_MIN_CHARACTERS) {
187 } else if (username.length() > Config.USERNAME_MAX_CHARACTERS) {
189 } else if (World.getPlayerByHash(Utility.nameToLong(username)).isPresent()) { // this user is already online
191 } else if (!(username.matches("^[a-zA-Z0-9 ]{1," + Config.USERNAME_MAX_CHARACTERS + "}$"))) { // does username have illegal characters
193 } else if (password.isEmpty()/* || password.length() > Config.PASSWORD_MAX_CHARACTERS*/) {
195 }
196
197 if (World.search(username).isPresent()) {
199 }
200
202 // check username and password from client with username and password from forum
203 final LoginResponse response = authenticatedForumUser(player, isEmail);
204 if (response != LoginResponse.NORMAL) {
205 return response;
206 }
207 }
208
209 LoginResponse response = PlayerSerializer.load(player, password);
210
211 if (World.searchAll(username).isPresent()) {
213 }
214
215 return response;
216 }
217
218 private LoginResponse authenticatedForumUser(Player player, boolean isEmail) {
219 final String username = player.getUsername();
220 try {
221 final LoginResponse response = new JdbcSession(ForumService.getConnectionPool())
222 .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) = ?")
223 .set(username.toUpperCase())
224 .select((rset, stmt) -> {
225 if (rset.next()) {
226 final int memberId = rset.getInt(1);
227 final String passwordHash = rset.getString(2);
228 final String forumUsername = isEmail ? rset.getString(3) : username;
229 final long unixTime = rset.getLong(isEmail ? 4 : 3);
230
231 if (isBanned(unixTime)) {
233 }
234
235 if (passwordHash.isEmpty()) {
237 } else if (BCrypt.checkpw(player.getPassword(), passwordHash)) {
238 player.setMemberId(memberId);
239 player.setUsername(forumUsername);
240 player.setPassword(passwordHash);
241 return LoginResponse.NORMAL;
242 } else {
244 }
245 }
247 });
248 return response;
249 } catch (Exception ex) {
250 ex.printStackTrace();
251 }
253 }
254
255 private boolean isBanned(long unixTime) {
256 // not banned
257 if (unixTime == 0) {
258 return false;
259 } else if (unixTime == -1) { // perm ban
260 return true;
261 }
262
263 final Date date = Date.from(Instant.ofEpochSecond(unixTime));
264
265 final Date currentDate = Date.from(Instant.now());
266
267 return date.after(currentDate);
268 }
269
270 /**
271 * A data class that represents a failed login attempt.
272 *
273 * @author nshusa
274 */
275 private static final class FailedLoginAttempt {
276
277 private final AtomicInteger attempt = new AtomicInteger(0);
279
280 public AtomicInteger getAttempt() {
281 return attempt;
282 }
283
285 return stopwatch;
286 }
287
288 }
289
290}
The class that contains setting-related constants for the server.
Definition Config.java:24
static final boolean FORUM_INTEGRATION
If forum integration is true, users can only login if they enter the same username and password that'...
Definition Config.java:117
static final int USERNAME_MIN_CHARACTERS
Definition Config.java:300
static final int USERNAME_MAX_CHARACTERS
Definition Config.java:299
static final int FAILED_LOGIN_ATTEMPTS
Definition Config.java:132
static final int FAILED_LOGIN_TIMEOUT
Definition Config.java:133
static final int EMAIL_MAX_CHARACTERS
Definition Config.java:297
static final AttributeKey< Session > SESSION_KEY
The session key.
Definition Config.java:90
static final int MAX_PLAYERS
The maximum amount of players that can be held within the game world.
Definition Config.java:166
static final int EMAIL_MIN_CHARACTERS
Definition Config.java:298
static final AtomicBoolean serverStarted
Definition RuneHive.java:59
static RuneHive getInstance()
LoginExecutorService getLoginExecutorService()
Holds all the constants used by bot.
static final String[] BOT_NAMES
List of all available bot names.
static HikariDataSource getConnectionPool()
Represents the game world.
Definition World.java:46
static final AtomicBoolean update
Definition World.java:72
static int getPlayerCount()
Gets the amount of valid players online.
Definition World.java:490
static Optional< Player > search(String name)
Gets a player by name.
Definition World.java:147
static Optional< Player > getPlayerByHash(long usernameHash)
Definition World.java:183
static void queueLogin(Player player)
Handles queueing the player logins.
Definition World.java:129
static Optional< Player > searchAll(String name)
Definition World.java:160
This class represents a character controlled by a player.
Definition Player.java:125
static LoginResponse load(Player player, String expectedPassword)
Handles the profile repository, used for gathering important information for all created profiles.
static void put(Profile profile)
Puts a profile into the hash map.
void execute(final LoginSession loginSession, final LoginDetailsPacket loginDetailsPacket)
The class that reads packets from the client into GamePacket's.
An immutable message that is written through a channel and forwarded to the LoginResponseEncoder wher...
Represents a Session when a Player has been authenticated and active in the game world.
A data class that represents a failed login attempt.
static final ConcurrentMap< String, FailedLoginAttempt > failedLogins
void handleClientPacket(Object o)
The method that is called when the client sends packets to the server.
LoginResponse evaluate(Player player)
void handleUserLoginDetails(final LoginDetailsPacket packet)
LoginResponse authenticatedForumUser(Player player, boolean isEmail)
static void sendFailedResponse(final Channel channel, final LoginResponse response)
final Channel channel
The channel attached to this session.
Definition Session.java:19
Session(Channel channel)
Creates a new Session.
Definition Session.java:32
boolean elapsed(long time, TimeUnit unit)
static Stopwatch start()
Handles miscellaneous methods.
Definition Utility.java:27
static long nameToLong(String text)
Converts the first 12 characters in a string of text to a hash.
Definition Utility.java:185
Represents the enumerated login response codes.