Websocket auth up and running

This commit is contained in:
simon 2018-04-27 21:28:46 -04:00
parent dc034d1437
commit 113aa50d87
15 changed files with 157 additions and 44 deletions

View File

@ -6,6 +6,7 @@ import net.simon987.server.web.AlertMessage;
import net.simon987.server.web.AlertType; import net.simon987.server.web.AlertType;
import net.simon987.server.websocket.SocketServer; import net.simon987.server.websocket.SocketServer;
import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.app.VelocityEngine;
import org.json.simple.JSONObject;
import spark.ModelAndView; import spark.ModelAndView;
import spark.Spark; import spark.Spark;
import spark.template.velocity.VelocityTemplateEngine; import spark.template.velocity.VelocityTemplateEngine;
@ -153,11 +154,52 @@ public class Main {
request.session().attribute("messages", messages); request.session().attribute("messages", messages);
} }
response.redirect("/account"); response.redirect("/account");
return null; return null;
}); });
Spark.get("/server_info", (request, response) -> {
//TODO put that in a constructor somewhere
String address;
if (GameServer.INSTANCE.getConfig().getInt("use_ssl") == 0) {
address = "ws://" +
GameServer.INSTANCE.getConfig().getString("mar_address") + ":" +
GameServer.INSTANCE.getConfig().getString("mar_port") + "/socket";
} else {
address = "wss://" +
GameServer.INSTANCE.getConfig().getString("mar_address") + ":" +
GameServer.INSTANCE.getConfig().getString("mar_port") + "/socket";
}
String serverName = GameServer.INSTANCE.getConfig().getString("server_name");
int tickLength = GameServer.INSTANCE.getConfig().getInt("tick_length");
JSONObject json = new JSONObject();
String username = request.session().attribute("username");
if (username != null) {
String token = GameServer.INSTANCE.getUserManager().generateAndGetToken(username);
json.put("token", token);
json.put("username", username);
} else {
json.put("token", "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
json.put("username", "guest");
}
json.put("address", address);
json.put("serverName", serverName);
json.put("tickLength", tickLength);
response.header("Content-Type", "application/json");
return json.toJSONString();
});
Spark.after((request, response) -> response.header("Content-Encoding", "gzip")); Spark.after((request, response) -> response.header("Content-Encoding", "gzip"));
} }

View File

@ -23,10 +23,10 @@ public class RandomStringGenerator {
return new String(buf); return new String(buf);
} }
public static final String UPPER_ALPHA_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static final String UPPER_ALPHA_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String LOWER_ALPHA_CHARSET = UPPER_ALPHA_CHARSET.toLowerCase(Locale.ROOT); static final String LOWER_ALPHA_CHARSET = UPPER_ALPHA_CHARSET.toLowerCase(Locale.ROOT);
public static final String NUMERIC_CHARSET = "0123456789"; static final String NUMERIC_CHARSET = "0123456789";
public static final String ALPHANUMERIC_CHARSET = UPPER_ALPHA_CHARSET + LOWER_ALPHA_CHARSET + NUMERIC_CHARSET; static final String ALPHANUMERIC_CHARSET = UPPER_ALPHA_CHARSET + LOWER_ALPHA_CHARSET + NUMERIC_CHARSET;
private final Random random; private final Random random;

View File

@ -19,12 +19,14 @@ public class User implements MongoSerialisable {
private String userCode; private String userCode;
private String password; private String password;
private String accessToken;
private CPU cpu; private CPU cpu;
private ControllableUnit controlledUnit; private ControllableUnit controlledUnit;
private boolean guest; private boolean guest = false;
private boolean moderator = false;
public User() throws CancelledException { public User() throws CancelledException {
GameEvent event = new UserCreationEvent(this); GameEvent event = new UserCreationEvent(this);
@ -33,7 +35,6 @@ public class User implements MongoSerialisable {
throw new CancelledException(); throw new CancelledException();
} }
} }
public User(ControllableUnit unit) { public User(ControllableUnit unit) {
@ -51,6 +52,7 @@ public class User implements MongoSerialisable {
dbObject.put("controlledUnit", controlledUnit.getObjectId()); dbObject.put("controlledUnit", controlledUnit.getObjectId());
dbObject.put("cpu", cpu.mongoSerialise()); dbObject.put("cpu", cpu.mongoSerialise());
dbObject.put("password", password); dbObject.put("password", password);
dbObject.put("moderator", moderator);
return dbObject; return dbObject;
@ -62,6 +64,7 @@ public class User implements MongoSerialisable {
user.username = (String) obj.get("username"); user.username = (String) obj.get("username");
user.userCode = (String) obj.get("code"); user.userCode = (String) obj.get("code");
user.password = (String) obj.get("password"); user.password = (String) obj.get("password");
user.moderator = (boolean) obj.get("moderator");
user.getControlledUnit().setParent(user); user.getControlledUnit().setParent(user);
@ -114,4 +117,20 @@ public class User implements MongoSerialisable {
public void setPassword(String password) { public void setPassword(String password) {
this.password = password; this.password = password;
} }
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public boolean isModerator() {
return moderator;
}
public void setModerator(boolean moderator) {
this.moderator = moderator;
}
} }

View File

@ -3,6 +3,8 @@ package net.simon987.server.user;
import com.mongodb.*; import com.mongodb.*;
import net.simon987.server.GameServer; import net.simon987.server.GameServer;
import net.simon987.server.assembly.exception.CancelledException; import net.simon987.server.assembly.exception.CancelledException;
import net.simon987.server.crypto.RandomStringGenerator;
import net.simon987.server.logging.LogManager;
import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.security.crypto.bcrypt.BCrypt;
import java.util.ArrayList; import java.util.ArrayList;
@ -91,4 +93,42 @@ public class UserManager {
userCollection.save(user.mongoSerialise()); //Save new password immediately userCollection.save(user.mongoSerialise()); //Save new password immediately
} }
/**
* Generate and save a access token for websocket auth
*
* @param username Username
* @return The generated token
*/
public String generateAndGetToken(String username) {
User user = GameServer.INSTANCE.getGameUniverse().getUser(username);
if (user == null) {
return null;
}
RandomStringGenerator generator = new RandomStringGenerator(128);
String token = generator.nextString();
user.setAccessToken(token);
LogManager.LOGGER.fine("(Web) Generated access token for " + username);
return token;
}
public User validateAuthToken(String token) {
for (User user : GameServer.INSTANCE.getGameUniverse().getUsers()) {
if (user.getAccessToken().equals(token)) {
user.setAccessToken("");
return user;
}
}
return null;
}
} }

View File

@ -13,7 +13,7 @@ public class CodeRequestHandler implements MessageHandler {
LogManager.LOGGER.fine("(WS) Code request from " + user.getUser().getUsername()); LogManager.LOGGER.fine("(WS) Code request from " + user.getUser().getUsername());
if (user.isGuest()) { if (user.getUser().isGuest()) {
JSONObject response = new JSONObject(); JSONObject response = new JSONObject();

View File

@ -16,7 +16,7 @@ public class CodeUploadHandler implements MessageHandler {
LogManager.LOGGER.fine("(WS) Code upload from " + user.getUser().getUsername()); LogManager.LOGGER.fine("(WS) Code upload from " + user.getUser().getUsername());
if (user.isGuest()) { if (user.getUser().isGuest()) {
//Ignore //Ignore
} else { } else {

View File

@ -10,7 +10,7 @@ public class DebugCommandHandler implements MessageHandler {
public void handle(OnlineUser user, JSONObject json) { public void handle(OnlineUser user, JSONObject json) {
if (json.get("t").equals("debug") && user.isModerator()) { if (json.get("t").equals("debug") && user.getUser().isModerator()) {
DebugCommandEvent e = new DebugCommandEvent(json, user); DebugCommandEvent e = new DebugCommandEvent(json, user);
GameServer.INSTANCE.getEventDispatcher().dispatch(e); //Ignore cancellation GameServer.INSTANCE.getEventDispatcher().dispatch(e); //Ignore cancellation

View File

@ -15,7 +15,7 @@ public class FloppyHandler implements MessageHandler {
LogManager.LOGGER.fine("(WS) Floppy download request from " + user.getUser().getUsername()); LogManager.LOGGER.fine("(WS) Floppy download request from " + user.getUser().getUsername());
if (user.isGuest()) { if (user.getUser().isGuest()) {
return; return;
} }

View File

@ -10,7 +10,7 @@ public class KeypressHandler implements MessageHandler {
@Override @Override
public void handle(OnlineUser user, JSONObject json) { public void handle(OnlineUser user, JSONObject json) {
if (!user.isGuest()) { if (!user.getUser().isGuest()) {
if (json.get("t").equals("k")) { if (json.get("t").equals("k")) {
//LogManager.LOGGER.fine("(WS) Received keypress"); //LogManager.LOGGER.fine("(WS) Received keypress");

View File

@ -10,10 +10,6 @@ public class OnlineUser {
private Session webSocket; private Session webSocket;
private boolean guest;
private boolean moderator;
/** /**
* Associated game user (if authenticated) * Associated game user (if authenticated)
*/ */
@ -44,20 +40,4 @@ public class OnlineUser {
this.authenticated = authenticated; this.authenticated = authenticated;
} }
public void setGuest(boolean guest) {
this.guest = guest;
user.setGuest(guest);
}
public boolean isGuest() {
return guest;
}
public boolean isModerator() {
return moderator;
}
public void setModerator(boolean moderator) {
this.moderator = moderator;
}
} }

View File

@ -1,7 +1,9 @@
package net.simon987.server.websocket; package net.simon987.server.websocket;
import net.simon987.server.GameServer;
import net.simon987.server.game.ControllableUnit; import net.simon987.server.game.ControllableUnit;
import net.simon987.server.logging.LogManager; import net.simon987.server.logging.LogManager;
import net.simon987.server.user.User;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
@ -57,14 +59,46 @@ public class SocketServer {
} else { } else {
LogManager.LOGGER.info("(WS) Received message from unauthenticated user " + session.getRemoteAddress()); LogManager.LOGGER.info("(WS) Received message from unauthenticated user " + session.getRemoteAddress());
//todo
if (message.length() == 128) {
User user = GameServer.INSTANCE.getUserManager().validateAuthToken(message);
if (user != null) {
LogManager.LOGGER.info("(WS) User was successfully authenticated: " + user.getUsername());
onlineUser.setUser(user);
onlineUser.setAuthenticated(true);
try {
session.getRemote().sendString("{\"t\":\"auth\", \"m\":\"ok\"}");
} catch (IOException e) {
e.printStackTrace();
}
} else {
User guestUser = GameServer.INSTANCE.getGameUniverse().getOrCreateUser(GameServer.INSTANCE.getGameUniverse().getGuestUsername(), false);
onlineUser.setUser(guestUser);
onlineUser.setAuthenticated(true);
onlineUser.getUser().setGuest(true);
LogManager.LOGGER.info("(WS) Created guest user " +
onlineUser.getUser().getUsername() + session.getRemoteAddress());
try {
session.getRemote().sendString("{\"t\":\"auth\", \"m\":\"ok\"}");
} catch (IOException e) {
e.printStackTrace();
}
}
}
} }
} else { } else {
LogManager.LOGGER.severe("(WS) FIXME: SocketServer:onMessage"); LogManager.LOGGER.severe("(WS) FIXME: SocketServer:onMessage");
} }
} }
@ -81,7 +115,7 @@ public class SocketServer {
if (user.getWebSocket().isOpen()) { if (user.getWebSocket().isOpen()) {
if (user.isGuest()) { if (user.getUser().isGuest()) {
json.remove("c"); json.remove("c");
try { try {

View File

@ -18,7 +18,7 @@ public class UserInfoRequestHandler implements MessageHandler {
LogManager.LOGGER.fine("(WS) User info request from " + user.getUser().getUsername()); LogManager.LOGGER.fine("(WS) User info request from " + user.getUser().getUsername());
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
if (user.isGuest()) { if (user.getUser().isGuest()) {
json.put("worldX", GameServer.INSTANCE.getConfig().getInt("new_user_worldX")); json.put("worldX", GameServer.INSTANCE.getConfig().getInt("new_user_worldX"));
json.put("worldY", GameServer.INSTANCE.getConfig().getInt("new_user_worldY")); json.put("worldY", GameServer.INSTANCE.getConfig().getInt("new_user_worldY"));
json.put("dimension", GameServer.INSTANCE.getConfig().getString("new_user_dimension")); json.put("dimension", GameServer.INSTANCE.getConfig().getString("new_user_dimension"));

View File

@ -8,12 +8,11 @@ mysql_url=jdbc:mysql://localhost:3306/mar?useSSL=false
save_interval=5 save_interval=5
log_limit=2000000 log_limit=2000000
log_count=10 log_count=10
# Web server port use_ssl=0
webSocket_port=8887
webSocket_host=0.0.0.0
use_secure_webSocket=0
cert_path=certificates cert_path=certificates
mar_port=4567
mar_address=localhost
server_name=Official MAR server
# ---------------------------------------------- # ----------------------------------------------
# Length of a tick in ms # Length of a tick in ms
@ -86,7 +85,6 @@ wg_minCopperCount=0
# Maximum copper tile count for the WorldGenerator # Maximum copper tile count for the WorldGenerator
wg_maxCopperCount=2 wg_maxCopperCount=2
# ---------------------------------------------- # ----------------------------------------------
# Maximum execution time of user code in ms # Maximum execution time of user code in ms
user_timeout=100 user_timeout=100

View File

@ -695,7 +695,7 @@ var GameClient = (function () {
console.log("[MAR] Getting server info... "); console.log("[MAR] Getting server info... ");
} }
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open("GET", "./getServerInfo.php", true); xhr.open("GET", "./server_info", true);
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
if (DEBUG) { if (DEBUG) {

View File

@ -374,7 +374,7 @@ class GameClient {
} }
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.open("GET", "./getServerInfo.php", true); xhr.open("GET", "./server_info", true);
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {