From 6053418526536a932f9e6ad51e648b7098eeb2da Mon Sep 17 00:00:00 2001 From: ilotterytea Date: Sat, 4 Jan 2025 18:17:36 +0500 Subject: feat: authentication system --- assets/i18n/en_us.json | 29 +- assets/i18n/ru_ru.json | 29 +- core/src/kz/ilotterytea/maxon/MaxonConstants.java | 9 +- core/src/kz/ilotterytea/maxon/MaxonGame.java | 179 ++++++------ .../kz/ilotterytea/maxon/localization/LineId.java | 9 + .../kz/ilotterytea/maxon/screens/MenuScreen.java | 206 ++++++++++++- .../ilotterytea/maxon/session/SessionClient.java | 323 +++++++++++++++++++++ 7 files changed, 648 insertions(+), 136 deletions(-) create mode 100644 core/src/kz/ilotterytea/maxon/session/SessionClient.java diff --git a/assets/i18n/en_us.json b/assets/i18n/en_us.json index ccbacb2..458113f 100644 --- a/assets/i18n/en_us.json +++ b/assets/i18n/en_us.json @@ -1,14 +1,11 @@ { "updater.info": "A new update is available!", - "menu.new_game": "New Game", "menu.continue": "CONTINUE", "menu.reset": "RESET", - "sounds.title": "Mixer", "sounds.music": "Music", "sounds.sfx": "SFX", - "credits.title": "Those who contributed", "credits.maxon": "Maxon himself", "credits.developer": "Developer", @@ -17,64 +14,54 @@ "credits.idea": "Idea dispenser", "credits.tester": "Tester", "credits.moral": "Moral supporter", - + "login.title": "Sign-in(tm) account", + "login.username": "Username", + "login.password": "Password", + "login.register": "Need account?", + "login.send": "Login", + "login.processing": "Authorising...", + "login.button": "Sign in...", + "login.authorised": "Logged in as %s", "giftbox.open": "You got", - "minigame.slots.nothing": "ALMOST WON...", "minigame.slots.prize": "YOU WON %s P$", "minigame.slots.bet": "BET", "minigame.slots.spin_button": "SPIN", "minigame.slots.exit_button": " X ", - "store.title": "Store", "store.buy": "Buy", "store.sell": "Sell", "store.x1": "1x", "store.x10": "10x", "store.pet_locked": "Pet Maxon more to unlock it...", - "pet.bror.name": "Sleepy Bror", "pet.bror.desc": "A falling asleep Bror will help you to pet Maxon almost to besvimers", - "pet.sandwich.name": "The Sandwich Cat", "pet.sandwich.desc": "Even though his head is shielded from the light by bread, he can still to pet Maxon by his cheeks.", - "pet.manlooshka.name": "Manlooshka", "pet.manlooshka.desc": "rrrrr", - "pet.thirsty.name": "The Thirsty Cat", "pet.thirsty.desc": "Every time the kitty drinks water, drops of spilled water fall on the screen and pet Maxon's dry cheeks.", - "pet.furios.name": "The Furios Cat", "pet.furios.desc": "Petting FURIOSLY !!!", - "pet.tvcat.name": "TV cat", "pet.tvcat.desc": "just a funny cat playing with tv", - "pet.progcat.name": "Programmer cat", "pet.progcat.desc": "Using his programming skills, he developed software to pet Maxon more efficiently.", - "pet.screamcat.name": "Screaming cat", "pet.screamcat.desc": "His scream is so loud, that it shakes Maxon's cheeks.", - "pet.hellcat.name": "Hellcat", "pet.hellcat.desc": "Petting Maxon with POWER OF HELL.", - "pet.lurker.name": "Lurking cat", "pet.lurker.desc": "Finds the best spots to pet Maxon in.", - "pet.piano.name": "Piano cat", "pet.piano.desc": "Gives best pettings with his trained paws.", - "pet.bee.name": "Bee cat", "pet.bee.desc": "Collects pollen from Maxon's cheeks.", - "pet.busy.name": "Business cat", "pet.busy.desc": "Pets Maxon with help of all his influence.", - "pet.aeae.name": "Aeae", "pet.aeae.desc": "back and forth, back and forth.", - "pet.succat.name": "Sucking cat", "pet.succat.desc": "Maskot. Increases amount of petting with his sucking power." } \ No newline at end of file diff --git a/assets/i18n/ru_ru.json b/assets/i18n/ru_ru.json index 2d092d6..1d8dc73 100644 --- a/assets/i18n/ru_ru.json +++ b/assets/i18n/ru_ru.json @@ -1,14 +1,11 @@ { "updater.info": "Доступно новое обновление!", - "menu.new_game": "Новая игра", "menu.continue": "ПРОДОЛЖИТЬ", "menu.reset": "СБРОСИТЬ", - "sounds.title": "Микшер", "sounds.music": "Музыка", "sounds.sfx": "SFX", - "credits.title": "Те, кто внёс вклад", "credits.maxon": "Максон", "credits.developer": "Разработчик", @@ -17,64 +14,54 @@ "credits.idea": "Раздатчик идей", "credits.tester": "Тестер", "credits.moral": "Моральная помощь", - + "login.title": "Sign-in(tm) аккаунт", + "login.username": "Имя пользователя", + "login.password": "Пароль", + "login.register": "Нужен аккаунт?", + "login.send": "Зайти", + "login.processing": "Авторизируюсь...", + "login.button": "Вход", + "login.authorised": "Зашёл как %s", "giftbox.open": "Ты получил", - "minigame.slots.nothing": "ПОЧТИ ВЫИГРАЛ...", "minigame.slots.prize": "ТЫ ВЫИГРАЛ %s P$", "minigame.slots.bet": "СТАВКА", "minigame.slots.spin_button": "КРУТАНУТЬ", "minigame.slots.exit_button": " X ", - "store.title": "Магазин", "store.buy": "Купить", "store.sell": "Продать", "store.x1": "1x", "store.x10": "10x", "store.pet_locked": "Гладь Махона больше, чтобы открыть...", - "pet.bror.name": "Cонный Брор", "pet.bror.desc": "Засыпающий Брор поможет тебе гладить Максона практически до потери сознания.", - "pet.sandwich.name": "Кот-бутерброд", "pet.sandwich.desc": "Даже, когда его голова накрыта хлебом от солнечного света, он всё ещё может гладить Максона за его щёчки.", - "pet.manlooshka.name": "Манлушка", "pet.manlooshka.desc": "*ррррр*", - "pet.thirsty.name": "Жаждущая кошка", "pet.thirsty.desc": "Каждый раз, когда он пьёт воду, капли пролитой воды падают на экран и гладят Максона за его сухие щёчки.", - "pet.furios.name": "Яростный кот", "pet.furios.desc": "ласкает НЕИСТОВО !!!", - "pet.tvcat.name": "Телевизионный котик", "pet.tvcat.desc": "он сильный", - "pet.progcat.name": "Кот-программист", "pet.progcat.desc": "Используя свои навыки программирования, он написал софт для более эффективного поглаживания Максона.", - "pet.screamcat.name": "Кричащий кот", "pet.screamcat.desc": "Его крик настолько громкий, что способен содрогать щёчки Максона.", - "pet.hellcat.name": "Адский кот", "pet.hellcat.desc": "Ласкает Максона силой ПРЕИСПОДНЕЙ.", - "pet.lurker.name": "Луркающий котик", "pet.lurker.desc": "Выявляет самые выгодные места для почесывания Максона.", - "pet.piano.name": "Котик-пианист", "pet.piano.desc": "Своими натренированными лапками дарит самые лучшие печесушки.", - "pet.bee.name": "Пчелокот", "pet.bee.desc": "Собирает пыльцу с щёчек Максона, попутно поглаживая их.", - "pet.busy.name": "Деловой кот", "pet.busy.desc": "Используя все свои бизнес-связи и своих работников, гладит Максона.", - "pet.aeae.name": "Аеае", "pet.aeae.desc": "туда-сюда, туда-сюда.", - "pet.succat.name": "Сосущий кот", "pet.succat.desc": "Талисман. Своими сосательными навыками увеличивает количество поглаживаний." } \ No newline at end of file diff --git a/core/src/kz/ilotterytea/maxon/MaxonConstants.java b/core/src/kz/ilotterytea/maxon/MaxonConstants.java index 5139ccc..cab66f4 100644 --- a/core/src/kz/ilotterytea/maxon/MaxonConstants.java +++ b/core/src/kz/ilotterytea/maxon/MaxonConstants.java @@ -18,6 +18,7 @@ public class MaxonConstants { public static final String GAME_APP_PACKAGE = "kz.ilotterytea." + GAME_APP_ID; public static final String GAME_APP_URL; public static final String GAME_VERSION = "1.0.1"; + public static final Integer GAME_PROTOCOL = 1; public static final String GAME_MAIN_DEVELOPER = "ilotterytea"; public static final List> GAME_DEVELOPERS = Arrays.asList( @@ -55,6 +56,12 @@ public class MaxonConstants { public static final DecimalFormat DECIMAL_FORMAT2 = new DecimalFormat("###,###"); public static final String GAME_VERSIONS_FILE_URL = "https://assets.ilotterytea.kz/maxon/versions.json"; + 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 final long DISCORD_APPLICATION_ID = 1051092609659052062L; @@ -68,7 +75,7 @@ public class MaxonConstants { for (int y = 0; y < checkers; y++) { for (int x = 0; x < checkers; x++) { - if ((x + y) % 2 == 0){ + if ((x + y) % 2 == 0) { pixmap.setColor(Color.MAGENTA); } else { pixmap.setColor(Color.BLACK); diff --git a/core/src/kz/ilotterytea/maxon/MaxonGame.java b/core/src/kz/ilotterytea/maxon/MaxonGame.java index 63de3e8..b53c510 100644 --- a/core/src/kz/ilotterytea/maxon/MaxonGame.java +++ b/core/src/kz/ilotterytea/maxon/MaxonGame.java @@ -8,93 +8,100 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch; import kz.ilotterytea.maxon.localization.LocalizationManager; import kz.ilotterytea.maxon.pets.PetManager; import kz.ilotterytea.maxon.screens.SplashScreen; +import kz.ilotterytea.maxon.session.SessionClient; import kz.ilotterytea.maxon.utils.GameUpdater; public class MaxonGame extends Game { - public SpriteBatch batch; - public AssetManager assetManager; - public Preferences prefs; - - private LocalizationManager locale; - private PetManager petManager; - - private DiscordActivityClient discordActivityClient; - - private static MaxonGame instance; - - public static MaxonGame getInstance() { - if (instance == null) { - instance = new MaxonGame(); - } - return instance; - } - - public PetManager getPetManager() { - return petManager; - } - - public DiscordActivityClient getDiscordActivityClient() { - return discordActivityClient; - } - - public LocalizationManager getLocale() { - return locale; - } - - public void setLocale(LocalizationManager locale) { - this.locale = locale; - } - - @Override - public void create () { - // Check the latest version - new GameUpdater().checkLatestUpdate(); - - batch = new SpriteBatch(); - prefs = Gdx.app.getPreferences(MaxonConstants.GAME_APP_PACKAGE); - locale = new LocalizationManager(Gdx.files.internal("i18n/" + prefs.getString("lang", "en_us") + ".json")); - - Gdx.graphics.setVSync(prefs.getBoolean("vsync", true)); - - if (prefs.getBoolean("fullscreen", false)) { - Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); - } else if ( - prefs.contains("width") || - prefs.contains("height") - ) { - int width = prefs.getInteger("width", 800); - - if (width < 800) { - width = 800; - prefs.putInteger("width", width); - } - - int height = prefs.getInteger("height", 600); - - if (height < 600) { - height = 600; - prefs.putInteger("height", height); - } - - prefs.flush(); - Gdx.graphics.setWindowedMode(width, height); - } - - assetManager = new AssetManager(); - petManager = new PetManager(assetManager); - - discordActivityClient = new DiscordActivityClient(); - - this.setScreen(new SplashScreen()); - } - - @Override - public void dispose () { - batch.dispose(); - for (String name : assetManager.getAssetNames()) { - assetManager.unload(name); - } - assetManager.dispose(); - discordActivityClient.dispose(); - } + public SpriteBatch batch; + public AssetManager assetManager; + public Preferences prefs; + + private LocalizationManager locale; + private PetManager petManager; + + private DiscordActivityClient discordActivityClient; + private SessionClient sessionClient; + + private static MaxonGame instance; + + public static MaxonGame getInstance() { + if (instance == null) { + instance = new MaxonGame(); + } + return instance; + } + + public PetManager getPetManager() { + return petManager; + } + + public DiscordActivityClient getDiscordActivityClient() { + return discordActivityClient; + } + + public SessionClient getSessionClient() { + return sessionClient; + } + + public LocalizationManager getLocale() { + return locale; + } + + public void setLocale(LocalizationManager locale) { + this.locale = locale; + } + + @Override + public void create() { + // Check the latest version + new GameUpdater().checkLatestUpdate(); + + sessionClient = new SessionClient(Gdx.app.getPreferences("kz.ilotterytea.SigninSession")); + batch = new SpriteBatch(); + prefs = Gdx.app.getPreferences(MaxonConstants.GAME_APP_PACKAGE); + locale = new LocalizationManager(Gdx.files.internal("i18n/" + prefs.getString("lang", "en_us") + ".json")); + + Gdx.graphics.setVSync(prefs.getBoolean("vsync", true)); + + if (prefs.getBoolean("fullscreen", false)) { + Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode()); + } else if ( + prefs.contains("width") || + prefs.contains("height") + ) { + int width = prefs.getInteger("width", 800); + + if (width < 800) { + width = 800; + prefs.putInteger("width", width); + } + + int height = prefs.getInteger("height", 600); + + if (height < 600) { + height = 600; + prefs.putInteger("height", height); + } + + prefs.flush(); + Gdx.graphics.setWindowedMode(width, height); + } + + assetManager = new AssetManager(); + petManager = new PetManager(assetManager); + + discordActivityClient = new DiscordActivityClient(); + + this.setScreen(new SplashScreen()); + } + + @Override + public void dispose() { + batch.dispose(); + for (String name : assetManager.getAssetNames()) { + assetManager.unload(name); + } + assetManager.dispose(); + discordActivityClient.dispose(); + } } diff --git a/core/src/kz/ilotterytea/maxon/localization/LineId.java b/core/src/kz/ilotterytea/maxon/localization/LineId.java index 2ef671a..23eedc1 100644 --- a/core/src/kz/ilotterytea/maxon/localization/LineId.java +++ b/core/src/kz/ilotterytea/maxon/localization/LineId.java @@ -24,6 +24,15 @@ public enum LineId { CreditsTester, CreditsMoral, + LoginTitle, + LoginUsername, + LoginPassword, + LoginRegister, + LoginSend, + LoginProcessing, + LoginButton, + LoginAuthorised, + GiftboxOpen, MinigameSlotsSpinbutton, diff --git a/core/src/kz/ilotterytea/maxon/screens/MenuScreen.java b/core/src/kz/ilotterytea/maxon/screens/MenuScreen.java index 43dfbca..ec60b33 100644 --- a/core/src/kz/ilotterytea/maxon/screens/MenuScreen.java +++ b/core/src/kz/ilotterytea/maxon/screens/MenuScreen.java @@ -1,6 +1,7 @@ package kz.ilotterytea.maxon.screens; -import com.badlogic.gdx.*; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Screen; import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.audio.Sound; @@ -27,7 +28,10 @@ import kz.ilotterytea.maxon.constants.SettingsConstants; import kz.ilotterytea.maxon.localization.LineId; import kz.ilotterytea.maxon.localization.LocalizationManager; import kz.ilotterytea.maxon.player.Savegame; -import kz.ilotterytea.maxon.ui.*; +import kz.ilotterytea.maxon.session.SessionClient; +import kz.ilotterytea.maxon.ui.DebugWidget; +import kz.ilotterytea.maxon.ui.SavegameWidget; +import kz.ilotterytea.maxon.ui.ShakingImageButton; import kz.ilotterytea.maxon.utils.GameUpdater; import kz.ilotterytea.maxon.utils.OsUtils; import net.mgsx.gltf.scene3d.attributes.PBRCubemapAttribute; @@ -38,14 +42,19 @@ import net.mgsx.gltf.scene3d.scene.SceneManager; import net.mgsx.gltf.scene3d.scene.SceneSkybox; import net.mgsx.gltf.scene3d.utils.EnvironmentUtil; import net.mgsx.gltf.scene3d.utils.IBLBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; public class MenuScreen implements Screen { + private static final Logger log = LoggerFactory.getLogger(MenuScreen.class); private MaxonGame game; private Stage stage; private Skin uiSkin; + private TextButton loginButton; + private Music menuMusic; private final Savegame savegame = Savegame.getInstance(); @@ -57,7 +66,8 @@ public class MenuScreen implements Screen { private Sound clickSound; private float soundVolume; - @Override public void show() { + @Override + public void show() { this.game = MaxonGame.getInstance(); game.getDiscordActivityClient().runThread(); @@ -317,7 +327,18 @@ public class MenuScreen implements Screen { } }); + // Login button + loginButton = new TextButton(game.getLocale().getLine(LineId.LoginButton), uiSkin); + loginButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + super.clicked(event, x, y); + showLoginWindow(); + } + }); + if (!OsUtils.isMobile) { + rightGameControlTable.add(loginButton).padRight(16f); rightGameControlTable.add(localeButton).size(iconSize).padRight(16f); rightGameControlTable.add(musicButton).size(iconSize).padRight(16f); rightGameControlTable.add(resolutionButton).size(iconSize); @@ -384,6 +405,57 @@ public class MenuScreen implements Screen { stage.act(delta); stage.draw(); + + // Login button logic + SessionClient session = game.getSessionClient(); + if (!session.isProcessing() && !session.isAuthorised() && !loginButton.getText().equals(game.getLocale().getLine(LineId.LoginButton))) { + loginButton.setText(game.getLocale().getLine(LineId.LoginButton)); + loginButton.clearListeners(); + loginButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + super.clicked(event, x, y); + showLoginWindow(); + } + + @Override + public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) { + super.enter(event, x, y, pointer, fromActor); + loginButton.setColor(new Color(0xffffffff)); + } + + @Override + public void exit(InputEvent event, float x, float y, int pointer, Actor fromActor) { + super.exit(event, x, y, pointer, fromActor); + loginButton.setColor(new Color(0xeeeeeeff)); + } + }); + loginButton.setStyle(uiSkin.get("default", TextButton.TextButtonStyle.class)); + loginButton.setDisabled(false); + } else if (session.isAuthorised() && !loginButton.getText().equals(game.getLocale().getFormattedLine(LineId.LoginAuthorised, session.getUsername()))) { + loginButton.setText(game.getLocale().getFormattedLine(LineId.LoginAuthorised, session.getUsername())); + loginButton.clearListeners(); + loginButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + super.clicked(event, x, y); + session.invalidateToken(); + } + + @Override + public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) { + super.enter(event, x, y, pointer, fromActor); + loginButton.setColor(new Color(0xffaaaaff)); + } + + @Override + public void exit(InputEvent event, float x, float y, int pointer, Actor fromActor) { + super.exit(event, x, y, pointer, fromActor); + loginButton.setColor(Color.WHITE); + } + }); + loginButton.setDisabled(true); + } } @Override @@ -392,9 +464,16 @@ public class MenuScreen implements Screen { sceneManager.updateViewport(width, height); } - @Override public void pause() {} - @Override public void resume() {} - @Override public void hide() { + @Override + public void pause() { + } + + @Override + public void resume() { + } + + @Override + public void hide() { for (Timer.Task task : tasks) { task.cancel(); } @@ -403,7 +482,9 @@ public class MenuScreen implements Screen { menuMusic.stop(); dispose(); } - @Override public void dispose() { + + @Override + public void dispose() { stage.dispose(); sceneManager.dispose(); } @@ -669,4 +750,115 @@ public class MenuScreen implements Screen { }); engineCredits.add(engineImage).height(OsUtils.isMobile ? 64f : 32f).width(OsUtils.isMobile ? 360f : 180f); } + + private void showLoginWindow() { + Image bgTint = new Image(uiSkin, "halftransparentblack"); + bgTint.setFillParent(true); + stage.addActor(bgTint); + + // Table + Table table = new Table(); + table.setFillParent(true); + table.align(Align.center); + + stage.addActor(table); + + // Window + Table window = new Table(uiSkin); + window.setBackground("bg"); + window.align(Align.center); + table.add(window).size(460f, 400f); + + // Table for title and close button + Table titleTable = new Table(uiSkin); + titleTable.setBackground("bg2"); + window.add(titleTable).growX().row(); + + // Title + Label titleLabel = new Label(game.getLocale().getLine(LineId.LoginTitle), uiSkin); + titleTable.add(titleLabel).pad(8f, 16f, 8f, 16f).growX(); + + // Close button + TextButton closeButton = new TextButton(" X ", uiSkin); + closeButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + super.clicked(event, x, y); + table.remove(); + bgTint.remove(); + clickSound.play(soundVolume); + } + }); + titleTable.add(closeButton).pad(8f, 16f, 8f, 16f); + + // Table for fields + Table contentTable = new Table(); + window.add(contentTable).pad(16f).grow().row(); + + // Username + Label usernameLabel = new Label(game.getLocale().getLine(LineId.LoginUsername), uiSkin); + contentTable.add(usernameLabel).grow().row(); + + TextField usernameField = new TextField("", uiSkin); + usernameField.setMessageText("..."); + usernameField.setTextFieldFilter((textField, c) -> Character.toString(c).matches("^[a-zA-Z0-9]")); + usernameField.setMaxLength(25); + contentTable.add(usernameField).padBottom(15f).grow().row(); + + // Password + Label passwordLabel = new Label(game.getLocale().getLine(LineId.LoginPassword), uiSkin); + contentTable.add(passwordLabel).grow().row(); + + TextField passwordField = new TextField("", uiSkin); + passwordField.setMessageText("..."); + + String[] passwords = {"", ""}; + + passwordField.setTextFieldListener((textField, c) -> { + String hiddenText = passwords[0]; + String fieldText = passwords[1]; + + if (c == 8 && !fieldText.isEmpty() && !hiddenText.isEmpty()) { + fieldText = fieldText.substring(0, fieldText.length() - 1); + hiddenText = hiddenText.substring(0, hiddenText.length() - 1); + } else if (c != 8) { + fieldText += '*'; + hiddenText += c; + } + + textField.setText(fieldText); + passwords[0] = hiddenText; + passwords[1] = fieldText; + }); + + contentTable.add(passwordField).grow().row(); + + // Register button + TextButton registerButton = new TextButton(game.getLocale().getLine(LineId.LoginRegister), uiSkin, "link"); + registerButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + super.clicked(event, x, y); + Gdx.net.openURI(MaxonConstants.IDENTITY_REGISTRATION_URL); + } + }); + contentTable.add(registerButton).padTop(15f).padBottom(15f).grow().row(); + + // Login button + TextButton sendButton = new TextButton(game.getLocale().getLine(LineId.LoginSend), uiSkin); + sendButton.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + super.clicked(event, x, y); + MaxonGame.getInstance().getSessionClient().authorize(usernameField.getText(), passwords[0]); + loginButton.setText(game.getLocale().getLine(LineId.LoginProcessing)); + loginButton.setDisabled(true); + // maybe we could somehow fire the close button instead of cv pasting + table.remove(); + bgTint.remove(); + clickSound.play(soundVolume); + } + }); + contentTable.add(sendButton).grow().row(); + } } diff --git a/core/src/kz/ilotterytea/maxon/session/SessionClient.java b/core/src/kz/ilotterytea/maxon/session/SessionClient.java new file mode 100644 index 0000000..31edd6e --- /dev/null +++ b/core/src/kz/ilotterytea/maxon/session/SessionClient.java @@ -0,0 +1,323 @@ +package kz.ilotterytea.maxon.session; + +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.maxon.MaxonConstants; +import kz.ilotterytea.maxon.utils.RandomUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SessionClient { + private final Logger log; + + private final Preferences sessionPreferences; + private final String clientToken; + private String accessToken; + private String userId, username, password; + private boolean isProcessing, isAuthorised; + + public SessionClient(Preferences sessionPreferences) { + startValidationThread(); + this.log = LoggerFactory.getLogger(SessionClient.class); + + this.clientToken = RandomUtils.generateRandomString(); + this.sessionPreferences = sessionPreferences; + this.isProcessing = false; + this.isAuthorised = false; + + if (sessionPreferences.contains("username") && sessionPreferences.contains("password")) { + this.authorize(sessionPreferences.getString("username"), sessionPreferences.getString("password")); + } + } + + public void authorize(String username, String password) { + log.info("Authorising..."); + 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(MaxonConstants.GAME_APP_ID.charAt(0)).toUpperCase() + MaxonConstants.GAME_APP_ID.substring(1))); + agent.addChild("version", new JsonValue(MaxonConstants.GAME_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(MaxonConstants.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) { + SessionClient.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.error("Failed to authorise: {} ({})", error, type); + + sessionPreferences.remove("username"); + sessionPreferences.remove("password"); + sessionPreferences.flush(); + + return; + } + + SessionClient.this.username = username; + SessionClient.this.password = password; + SessionClient.this.accessToken = json.get("data").getString("accessToken"); + SessionClient.this.userId = String.valueOf(json.get("data").get("user").getInt("id")); + SessionClient.this.isAuthorised = true; + log.info("Successfully authorised! Welcome back, {}!", SessionClient.this.username); + } catch (Exception e) { + log.error("An exception was thrown while authorising", e); + } + } + + @Override + public void failed(Throwable t) { + log.error("Failed to send an authorisation request", t); + } + + @Override + public void cancelled() { + log.info("Authorisation request was cancelled!"); + } + }); + } + + public void validateToken() { + if (clientToken == null || accessToken == null) { + return; + } + + log.info("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(MaxonConstants.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.error("Failed to validate: {} ({})", error, type); + accessToken = null; + userId = null; + isAuthorised = false; + authorize(username, password); + return; + } + + int expiresInSeconds = json.get("data").getInt("expiresInSeconds"); + + if (expiresInSeconds < 1000) { + refreshToken(); + } + + log.info("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.info("Validation request was cancelled!"); + } + }); + } + + public void invalidateToken() { + if (clientToken == null || accessToken == null) { + return; + } + + log.info("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(MaxonConstants.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.error("Failed to invalidate: {} ({})", error, type); + return; + } + + log.info("Invalidated! Bye, {}", username); + + accessToken = null; + userId = null; + username = null; + password = null; + isAuthorised = false; + sessionPreferences.remove("username"); + sessionPreferences.remove("password"); + sessionPreferences.flush(); + } catch (Exception ignored) { + } + } + + @Override + public void failed(Throwable t) { + } + + @Override + public void cancelled() { + } + }); + } + + public void refreshToken() { + if (clientToken == null || accessToken == null) { + return; + } + + log.info("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(MaxonConstants.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()); + + if (httpResponse.getStatus().getStatusCode() != HttpStatus.SC_OK) { + String type = json.get("error").getString("type"); + String error = json.get("error").getString("message"); + log.error("Failed to refresh: {} ({})", error, type); + accessToken = null; + userId = null; + isAuthorised = false; + log.warn(error); + return; + } + + accessToken = json.get("data").get("accessToken").asString(); + log.info("Token has been refreshed!"); + } 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.info("Refresh request was cancelled!"); + } + }); + } + + public boolean isAuthorised() { + return this.isAuthorised; + } + + public boolean isProcessing() { + return isProcessing; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getAccessToken() { + return accessToken; + } + + public String getClientToken() { + return clientToken; + } + + public String getUserId() { + return userId; + } + + private void startValidationThread() { + Timer.schedule(new Timer.Task() { + @Override + public void run() { + validateToken(); + } + }, 60000, 60000); + } +} -- cgit v1.2.3