summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorilotterytea <iltsu@alright.party>2025-01-23 14:53:48 +0500
committerilotterytea <iltsu@alright.party>2025-01-23 14:53:48 +0500
commit3fd5917ef5333c4c9ee8c79ab360b654459626f2 (patch)
tree84ec5c41950ecb59ed9e9fd0d3f38315ec1d038f
parent35fafc313b8c9a7425af0d1fb930ed33c3c8413a (diff)
feat: client-side sign-in authorization system
-rw-r--r--core/src/main/java/kz/ilotterytea/frogartha/FrogarthaGame.java3
-rw-r--r--core/src/main/java/kz/ilotterytea/frogartha/screens/MenuScreen.java54
-rw-r--r--core/src/main/java/kz/ilotterytea/frogartha/sessions/IdentityClient.java320
-rw-r--r--core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java6
-rw-r--r--core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java3
-rw-r--r--shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java11
-rw-r--r--shared/src/main/java/kz/ilotterytea/frogartha/utils/RandomUtils.java28
7 files changed, 390 insertions, 35 deletions
diff --git a/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaGame.java b/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaGame.java
index bf4159b..3d6d28f 100644
--- a/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaGame.java
+++ b/core/src/main/java/kz/ilotterytea/frogartha/FrogarthaGame.java
@@ -1,6 +1,7 @@
package kz.ilotterytea.frogartha;
import com.badlogic.gdx.Game;
+import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetManager;
import kz.ilotterytea.frogartha.assets.AssetUtils;
import kz.ilotterytea.frogartha.screens.SplashScreen;
@@ -24,7 +25,7 @@ public class FrogarthaGame extends Game {
AssetUtils.setup(assetManager);
AssetUtils.queue(assetManager);
- identityClient = new IdentityClient();
+ identityClient = new IdentityClient(Gdx.app.getPreferences("kz.ilotterytea.SigninIdentity"));
sessionClient = new SessionClient();
setScreen(new SplashScreen());
diff --git a/core/src/main/java/kz/ilotterytea/frogartha/screens/MenuScreen.java b/core/src/main/java/kz/ilotterytea/frogartha/screens/MenuScreen.java
index 13b69ea..f5948e2 100644
--- a/core/src/main/java/kz/ilotterytea/frogartha/screens/MenuScreen.java
+++ b/core/src/main/java/kz/ilotterytea/frogartha/screens/MenuScreen.java
@@ -14,21 +14,29 @@ import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
+import kz.ilotterytea.frogartha.FrogarthaConstants;
import kz.ilotterytea.frogartha.FrogarthaGame;
import kz.ilotterytea.frogartha.assets.Assets;
import kz.ilotterytea.frogartha.domain.RoomTopic;
+import kz.ilotterytea.frogartha.sessions.IdentityClient;
+import kz.ilotterytea.frogartha.sessions.SessionClient;
import kz.ilotterytea.frogartha.ui.menu.RoomTopicWidget;
public class MenuScreen implements Screen {
private FrogarthaGame game;
+ private SessionClient session;
+ private IdentityClient identity;
+
private Stage stage;
- private Table gameTable, topicTable;
+ private Table gameTable, topicTable, credentialsTable;
private Label authorizingLabel;
@Override
public void show() {
game = FrogarthaGame.getInstance();
+ session = game.getSessionClient();
+ identity = game.getIdentityClient();
createStage();
@@ -44,17 +52,24 @@ public class MenuScreen implements Screen {
stage.draw();
if (
- game.getIdentityClient().isAuthorized() &&
- !game.getSessionClient().isJoined() &&
+ identity.isAuthorized() &&
+ !session.isJoined() &&
authorizingLabel.hasParent()
) {
gameTable.clear();
gameTable.add(topicTable).grow();
+ } else if (
+ !identity.isProcessing() &&
+ !identity.isAuthorized() &&
+ authorizingLabel.hasParent()
+ ) {
+ gameTable.clear();
+ gameTable.add(credentialsTable).grow().maxWidth(384f);
}
- if (game.getSessionClient().hasThrown()) {
+ if (session.hasThrown()) {
game.setScreen(new KickScreen());
- } else if (game.getSessionClient().isJoined()) {
+ } else if (session.isJoined()) {
game.setScreen(new GameScreen());
}
}
@@ -126,6 +141,7 @@ public class MenuScreen implements Screen {
// --- Authorizing label ---
authorizingLabel = new Label("Authorizing", skin);
authorizingLabel.setAlignment(Align.center);
+ gameTable.add(authorizingLabel).grow();
// --- Topic selector ---
topicTable = new Table();
@@ -164,8 +180,7 @@ public class MenuScreen implements Screen {
}
// --- Credentials ---
- Table credentialsTable = new Table();
- gameTable.add(credentialsTable).grow().maxWidth(384f);
+ credentialsTable = new Table();
// Username
Label usernameLabel = new Label("Username", skin);
@@ -177,13 +192,36 @@ public class MenuScreen implements Screen {
usernameField.setMaxLength(25);
credentialsTable.add(usernameField).padBottom(15f).grow().row();
+ // Password
+ Label passwordLabel = new Label("Password", skin);
+ credentialsTable.add(passwordLabel).grow().row();
+
+ TextField passwordField = new TextField("", skin);
+ passwordField.setPasswordMode(true);
+ passwordField.setPasswordCharacter('*');
+ passwordField.setMessageText("...");
+ passwordField.setTextFieldFilter((tf, c) -> Character.toString(c).matches("^[a-zA-Z0-9]"));
+ passwordField.setMaxLength(25);
+ credentialsTable.add(passwordField).padBottom(15f).grow().row();
+
+ // Register button
+ TextButton registerButton = new TextButton("Need account?", skin, "link");
+ registerButton.addListener(new ClickListener() {
+ @Override
+ public void clicked(InputEvent event, float x, float y) {
+ super.clicked(event, x, y);
+ Gdx.net.openURI(FrogarthaConstants.URLS.IDENTITY_REGISTRATION_URL);
+ }
+ });
+ credentialsTable.add(registerButton).padTop(15f).padBottom(15f).grow().row();
+
// Login button
TextButton loginButton = new TextButton("Login", skin);
loginButton.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
super.clicked(event, x, y);
- FrogarthaGame.getInstance().getIdentityClient().authorize(usernameField.getText());
+ FrogarthaGame.getInstance().getIdentityClient().authorize(usernameField.getText(), passwordField.getText());
gameTable.removeActor(credentialsTable);
gameTable.add(authorizingLabel).grow();
}
diff --git a/core/src/main/java/kz/ilotterytea/frogartha/sessions/IdentityClient.java b/core/src/main/java/kz/ilotterytea/frogartha/sessions/IdentityClient.java
index 64e7270..10af0e0 100644
--- a/core/src/main/java/kz/ilotterytea/frogartha/sessions/IdentityClient.java
+++ b/core/src/main/java/kz/ilotterytea/frogartha/sessions/IdentityClient.java
@@ -1,53 +1,331 @@
package kz.ilotterytea.frogartha.sessions;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.Net;
+import com.badlogic.gdx.Preferences;
+import com.badlogic.gdx.net.HttpRequestBuilder;
+import com.badlogic.gdx.net.HttpStatus;
+import com.badlogic.gdx.utils.JsonReader;
+import com.badlogic.gdx.utils.JsonValue;
+import com.badlogic.gdx.utils.JsonWriter;
+import com.badlogic.gdx.utils.Timer;
+import kz.ilotterytea.frogartha.FrogarthaConstants;
import kz.ilotterytea.frogartha.FrogarthaGame;
import kz.ilotterytea.frogartha.utils.Logger;
+import kz.ilotterytea.frogartha.utils.RandomUtils;
public class IdentityClient {
private final Logger log;
- private final FrogarthaGame game;
- private String username;
- private boolean isProcessing, isAuthorized, inRoom;
+ private final Preferences sessionPreferences;
+ private final String clientToken;
+ private String accessToken;
+ private String userId, username, password;
+ private boolean isProcessing, isAuthorized;
- public IdentityClient() {
+ public IdentityClient(Preferences sessionPreferences) {
+ startValidationThread();
this.log = new Logger(IdentityClient.class);
- this.game = FrogarthaGame.getInstance();
-
- this.username = null;
+ this.clientToken = RandomUtils.generateRandomString();
+ this.sessionPreferences = sessionPreferences;
this.isProcessing = false;
this.isAuthorized = false;
- this.inRoom = false;
+
+ if (sessionPreferences.contains("username") && sessionPreferences.contains("password")) {
+ this.authorize(sessionPreferences.getString("username"), sessionPreferences.getString("password"));
+ }
+ }
+
+ public void authorize(String username, String password) {
+ log.log("Authorizing...");
+ this.isProcessing = true;
+ sessionPreferences.putString("username", username);
+ sessionPreferences.putString("password", password);
+ sessionPreferences.flush();
+
+ JsonValue agent = new JsonValue(JsonValue.ValueType.object);
+ agent.addChild("name", new JsonValue(String.valueOf(FrogarthaConstants.Game.APP_ID.charAt(0)).toUpperCase() + FrogarthaConstants.Game.APP_ID.substring(1)));
+ agent.addChild("version", new JsonValue(FrogarthaConstants.Game.APP_PROTOCOL));
+
+ JsonValue json = new JsonValue(JsonValue.ValueType.object);
+ json.addChild("agent", agent);
+ json.addChild("username", new JsonValue(username));
+ json.addChild("password", new JsonValue(password));
+ json.addChild("clientToken", new JsonValue(clientToken));
+
+ Net.HttpRequest request =
+ new HttpRequestBuilder()
+ .newRequest()
+ .method(Net.HttpMethods.POST)
+ .url(FrogarthaConstants.URLS.IDENTITY_AUTHENTICATION_URL)
+ .timeout(20000)
+ .header("Content-Type", "application/json")
+ .content(json.toJson(JsonWriter.OutputType.json))
+ .build();
+
+ Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() {
+ @Override
+ public void handleHttpResponse(Net.HttpResponse httpResponse) {
+ IdentityClient.this.isProcessing = false;
+
+ try {
+ JsonValue json = new JsonReader().parse(httpResponse.getResultAsString());
+
+ if (httpResponse.getStatus().getStatusCode() != HttpStatus.SC_OK) {
+ String type = json.get("error").getString("type");
+ String error = json.get("error").getString("message");
+ log.log("Failed to authorize: {} ({})", error, type);
+
+ sessionPreferences.remove("username");
+ sessionPreferences.remove("password");
+ sessionPreferences.flush();
+
+ return;
+ }
+
+ IdentityClient.this.username = username;
+ IdentityClient.this.password = password;
+ IdentityClient.this.accessToken = json.get("data").getString("accessToken");
+ IdentityClient.this.userId = String.valueOf(json.get("data").get("user").getInt("id"));
+ IdentityClient.this.isAuthorized = true;
+ log.log("Successfully authorized! Welcome back, {}!", IdentityClient.this.username);
+
+ FrogarthaGame.getInstance().getSessionClient().connect();
+ } catch (Exception e) {
+ log.error("An exception was thrown while authorizing", e);
+ }
+ }
+
+ @Override
+ public void failed(Throwable t) {
+ log.error("Failed to send an authorization request", t);
+ }
+
+ @Override
+ public void cancelled() {
+ log.log("Authorization request was cancelled!");
+ }
+ });
+ }
+
+ public void validateToken() {
+ if (clientToken == null || accessToken == null) {
+ return;
+ }
+
+ log.log("Validating token...");
+
+ JsonValue json = new JsonValue(JsonValue.ValueType.object);
+ json.addChild("clientToken", new JsonValue(clientToken));
+ json.addChild("accessToken", new JsonValue(accessToken));
+
+ Net.HttpRequest request =
+ new HttpRequestBuilder()
+ .newRequest()
+ .method(Net.HttpMethods.POST)
+ .url(FrogarthaConstants.URLS.IDENTITY_VALIDATE_URL)
+ .timeout(20000)
+ .header("Content-Type", "application/json")
+ .content(json.toJson(JsonWriter.OutputType.json))
+ .build();
+
+ Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() {
+ @Override
+ public void handleHttpResponse(Net.HttpResponse httpResponse) {
+ try {
+ JsonValue json = new JsonReader().parse(httpResponse.getResultAsString());
+
+ if (httpResponse.getStatus().getStatusCode() != HttpStatus.SC_OK) {
+ String type = json.get("error").getString("type");
+ String error = json.get("error").getString("message");
+ log.log("Failed to validate: {} ({})", error, type);
+ accessToken = null;
+ userId = null;
+ isAuthorized = false;
+ FrogarthaGame.getInstance().getSessionClient().close();
+ authorize(username, password);
+ return;
+ }
+
+ int expiresInSeconds = json.get("data").getInt("expiresInSeconds");
+
+ if (expiresInSeconds < 1000) {
+ refreshToken();
+ }
+
+ log.log("Token validated!");
+ } catch (Exception e) {
+ log.error("An exception was thrown while validating", e);
+ }
+ }
+
+ @Override
+ public void failed(Throwable t) {
+ log.error("Failed to send a validation request", t);
+ }
+
+ @Override
+ public void cancelled() {
+ log.log("Validation request was cancelled!");
+ }
+ });
+ }
+
+ public void invalidateToken() {
+ if (clientToken == null || accessToken == null) {
+ return;
+ }
+
+ log.log("Invalidating token...");
+
+ JsonValue json = new JsonValue(JsonValue.ValueType.object);
+ json.addChild("clientToken", new JsonValue(clientToken));
+ json.addChild("accessToken", new JsonValue(accessToken));
+
+ Net.HttpRequest request =
+ new HttpRequestBuilder()
+ .newRequest()
+ .method(Net.HttpMethods.POST)
+ .url(FrogarthaConstants.URLS.IDENTITY_INVALIDATE_URL)
+ .timeout(20000)
+ .header("Content-Type", "application/json")
+ .content(json.toJson(JsonWriter.OutputType.json))
+ .build();
+
+ Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() {
+ @Override
+ public void handleHttpResponse(Net.HttpResponse httpResponse) {
+ try {
+ JsonValue json = new JsonReader().parse(httpResponse.getResultAsString());
+
+ if (httpResponse.getStatus().getStatusCode() != HttpStatus.SC_OK) {
+ String type = json.get("error").getString("type");
+ String error = json.get("error").getString("message");
+ log.log("Failed to invalidate: {} ({})", error, type);
+ return;
+ }
+
+ log.log("Invalidated! Bye, {}", username);
+
+ accessToken = null;
+ userId = null;
+ username = null;
+ password = null;
+ isAuthorized = false;
+ sessionPreferences.remove("username");
+ sessionPreferences.remove("password");
+ sessionPreferences.flush();
+
+ FrogarthaGame.getInstance().getSessionClient().close();
+ } catch (Exception ignored) {
+ }
+ }
+
+ @Override
+ public void failed(Throwable t) {
+ }
+
+ @Override
+ public void cancelled() {
+ }
+ });
}
+ public void refreshToken() {
+ if (clientToken == null || accessToken == null) {
+ return;
+ }
+
+ log.log("Refreshing token...");
+
+ JsonValue json = new JsonValue(JsonValue.ValueType.object);
+ json.addChild("clientToken", new JsonValue(clientToken));
+ json.addChild("accessToken", new JsonValue(accessToken));
+
+ Net.HttpRequest request =
+ new HttpRequestBuilder()
+ .newRequest()
+ .method(Net.HttpMethods.POST)
+ .url(FrogarthaConstants.URLS.IDENTITY_REFRESH_URL)
+ .timeout(20000)
+ .header("Content-Type", "application/json")
+ .content(json.toJson(JsonWriter.OutputType.json))
+ .build();
+
+ Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() {
+ @Override
+ public void handleHttpResponse(Net.HttpResponse httpResponse) {
+ try {
+ JsonValue json = new JsonReader().parse(httpResponse.getResultAsString());
- public void authorize(String username) {
- this.username = username;
- game.getSessionClient().connect();
+ if (httpResponse.getStatus().getStatusCode() != HttpStatus.SC_OK) {
+ String type = json.get("error").getString("type");
+ String error = json.get("error").getString("message");
+ log.log("Failed to refresh: {} ({})", error, type);
+ accessToken = null;
+ userId = null;
+ isAuthorized = false;
+ log.log(error);
+ FrogarthaGame.getInstance().getSessionClient().close();
+ return;
+ }
+
+ accessToken = json.get("data").get("accessToken").asString();
+ log.log("Token has been refreshed!");
+
+ FrogarthaGame.getInstance().getSessionClient().updateIdentity();
+ } catch (Exception e) {
+ log.error("An exception was thrown while refreshing", e);
+ }
+ }
+
+ @Override
+ public void failed(Throwable t) {
+ log.error("Failed to send a refresh request", t);
+ }
+
+ @Override
+ public void cancelled() {
+ log.log("Refresh request was cancelled!");
+ }
+ });
+ }
+
+ public boolean isAuthorized() {
+ return this.isAuthorized;
+ }
+
+ public boolean isProcessing() {
+ return isProcessing;
}
public String getUsername() {
return username;
}
- public boolean isAuthorized() {
- return isAuthorized;
+ public String getPassword() {
+ return password;
}
- public void setAuthorized(boolean authorized) {
- isAuthorized = authorized;
+ public String getAccessToken() {
+ return accessToken;
}
- public boolean isInRoom() {
- return inRoom;
+ public String getClientToken() {
+ return clientToken;
}
- public void setInRoom(boolean inRoom) {
- this.inRoom = inRoom;
+ public String getUserId() {
+ return userId;
}
- public boolean isProcessing() {
- return isProcessing;
+ private void startValidationThread() {
+ Timer.schedule(new Timer.Task() {
+ @Override
+ public void run() {
+ validateToken();
+ }
+ }, 60000, 60000);
}
}
diff --git a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java
index ba05f37..a696121 100644
--- a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java
+++ b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java
@@ -56,7 +56,7 @@ public class SessionClient implements WebSocketListener {
@Override
public boolean onClose(WebSocket webSocket, WebSocketCloseCode code, String reason) {
log.log("Connection closed! Reason: {} {}", code.getCode(), reason);
- game.getIdentityClient().setAuthorized(false);
+ setLastThrow(null);
return true;
}
@@ -168,7 +168,9 @@ public class SessionClient implements WebSocketListener {
private void setLastThrow(Throwable throwable) {
this.lastThrow = throwable;
- game.getIdentityClient().setInRoom(false);
+ joined = false;
+ isJoining = false;
+ topic = null;
playerDataMap.clear();
if (connection.isOpen()) {
connection.close();
diff --git a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java
index 55f3bdb..9ced32b 100644
--- a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java
+++ b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java
@@ -20,7 +20,6 @@ public class SessionHandlers {
log.log("The server identified me!");
client.setConnectionId(event.getPlayerId());
- game.getIdentityClient().setAuthorized(true);
}
public static void handlePlayerJumpEvent(PlayerJumpEvent event) {
@@ -96,7 +95,6 @@ public class SessionHandlers {
}
client.getPlayerDataMap().putAll(map);
- game.getIdentityClient().setInRoom(true);
}
public static void handleSenderLeftRoomEvent(SenderLeftRoomEvent event) {
@@ -106,7 +104,6 @@ public class SessionHandlers {
}
client.getPlayerDataMap().clear();
- game.getIdentityClient().setInRoom(false);
}
public static void handlePlayerJoinedRoomEvent(PlayerJoinedRoomEvent event) {
diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java b/shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java
index e205bf5..1e55d52 100644
--- a/shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java
+++ b/shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java
@@ -1,8 +1,19 @@
package kz.ilotterytea.frogartha;
public class FrogarthaConstants {
+ public static class Game {
+ public static final String APP_ID = "frogartha";
+ public static final int APP_PROTOCOL = 1;
+ }
+
public static class URLS {
public static final String SESSION_WSS = "ws://localhost:20015";
+ public static final String IDENTITY_BASE_URL = "https://id.ilotterytea.kz";
+ public static final String IDENTITY_INVALIDATE_URL = IDENTITY_BASE_URL + "/invalidate";
+ public static final String IDENTITY_VALIDATE_URL = IDENTITY_BASE_URL + "/validate";
+ public static final String IDENTITY_REFRESH_URL = IDENTITY_BASE_URL + "/refresh";
+ public static final String IDENTITY_AUTHENTICATION_URL = IDENTITY_BASE_URL + "/authenticate";
+ public static final String IDENTITY_REGISTRATION_URL = IDENTITY_BASE_URL;
}
public static class Player {
diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/utils/RandomUtils.java b/shared/src/main/java/kz/ilotterytea/frogartha/utils/RandomUtils.java
new file mode 100644
index 0000000..2933aac
--- /dev/null
+++ b/shared/src/main/java/kz/ilotterytea/frogartha/utils/RandomUtils.java
@@ -0,0 +1,28 @@
+package kz.ilotterytea.frogartha.utils;
+
+import java.util.Random;
+
+public class RandomUtils {
+ public static final char[] CHARACTER_POOL = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
+ public static final int TOKEN_LENGTH = 32;
+
+ public static String generateRandomString() {
+ return generateRandomString(CHARACTER_POOL, TOKEN_LENGTH);
+ }
+
+ public static String generateRandomString(int length) {
+ return generateRandomString(CHARACTER_POOL, length);
+ }
+
+ public static String generateRandomString(char[] characterPool, int length) {
+ StringBuilder output = new StringBuilder();
+ Random random = new Random();
+
+ for (int i = 0; i < length; i++) {
+ char character = characterPool[random.nextInt(0, characterPool.length)];
+ output.append(character);
+ }
+
+ return output.toString();
+ }
+}