Skip to content
148 changes: 148 additions & 0 deletions DiscordAuthSystem_module/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# DiscordAuthSystem

Позволяет входить в лаунчер через Discord.
Модуль использует библиотеку [JSOUP](https://jsoup.org/download).

#### Установка модуля

1. Скопировать модуль **DiscordAuthSystem_module.jar** в папку **/LaunchServer/modules/**
2. Создать приложение в панели управления разработчика https://discord.com/developers/applications, и секретный токен.
Если вам нужно проверять находится ли пользователь в необходимых вам гильдиях, то опциональные пункты обязательны.
1. Скопировать его CLIENT ID
2. Скопировать его CLIENT SECRET
3. [Опционально] Создать бота из данного приложения
4. [Опционально] Добавить его на необходимые вам сервера.
5. [Опционально] В настройках бота включить пункт "SERVER MEMBERS INTENT".
4. В настройках приложение discord oauth добавить redirect_url. Он должен состоять из пути до webapi + /auth/discord. Пример: http://127.0.0.1:9274/webapi/auth/discord
5. Настроить конфигурацию модуля
6. Добавить авторизацию в LaunchServer
7. [Опционально] Обновить Runtime

#### Конфигурация модуля

```json
{
"clientId": "сюда вставляется id",
"clientSecret": "сюда вставляется секрет",
"redirectUrl": "это редирект, который вы указали",
"discordAuthorizeUrl": "https://discord.com/oauth2/authorize",
"discordApiEndpointVersion": "https://discord.com/api/v10",
"discordApiEndpoint": "https://discord.com/api",
"guildIdsJoined": [
{
"id": "id гильдии №1",
"name": "наименование гильдии",
"url": "ссылка для входа"
},
{
"id": "id гильдии №2",
"name": "наименование гильдии",
"url": "ссылка для входа"
}
],
"guildIdGetNick": "id гильдии с которой будет браться ник. если не надо, то оставить пустым",
"usernameRegex": "regex для валидации ника (если не нужно, то оставьте пустым)"
}
```

#### Конфигурация в LaunchServer

```json
{
"std": {
"isDefault": true,
"core": {
"type": "discordauthsystem",
"mySQLHolder": {
"address": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"useHikari": false
},
"uuidColumn": "uuid",
"usernameColumn": "username",
"accessTokenColumn": "accessToken",
"refreshTokenColumn": "refreshToken",
"expiresInColumn": "expiresIn",
"discordIdColumn": "discordId",
"bannedAtColumn": "bannedAt",
"hardwareIdColumn": "hwidId",
"serverIDColumn": "serverID",
"table": "users",
"tableHwid": "hwids"
},
"textureProvider": {
"skinURL": "http://example.com/skins/%username%.png",
"cloakURL": "http://example.com/cloaks/%username%.png",
"type": "request"
},
"displayName": "Default"
}
}
```

- В mySQLHolder указывается коннект к mysql (данные аккаунтов хрантся там)
- \*\*\*\*Column - строки наименования колонок.
- tableHwid - таблица hwid юзеров.

#### Дефолтный запрос на создание таблицы

```mysql
-- Создаём таблицу пользователей
CREATE TABLE `users` (
`uuid` CHAR(36) UNIQUE,
`username` CHAR(32) UNIQUE,
`accessToken` CHAR(32) DEFAULT NULL,
`refreshToken` CHAR(32) DEFAULT NULL,
`expiresIn` BIGINT DEFAULT NULL,
`discordId` VARCHAR(32) DEFAULT NULL,
`bannedAt` DATETIME DEFAULT NULL,
`serverID` VARCHAR(41) DEFAULT NULL,
`hwidId` BIGINT DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Создаём таблицу hwids данных
CREATE TABLE `hwids` (
`id` bigint(20) NOT NULL,
`publickey` blob,
`hwDiskId` varchar(255) DEFAULT NULL,
`baseboardSerialNumber` varchar(255) DEFAULT NULL,
`graphicCard` varchar(255) DEFAULT NULL,
`displayId` blob,
`bitness` int(11) DEFAULT NULL,
`totalMemory` bigint(20) DEFAULT NULL,
`logicalProcessors` int(11) DEFAULT NULL,
`physicalProcessors` int(11) DEFAULT NULL,
`processorMaxFreq` bigint(11) DEFAULT NULL,
`battery` tinyint(1) NOT NULL DEFAULT "0",
`banned` tinyint(1) NOT NULL DEFAULT "0"
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Добавляем модификаторы hwids таблицы
ALTER TABLE `hwids`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `publickey` (`publickey`(255));
ALTER TABLE `hwids`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;

-- Связываем пользователей и hwids
ALTER TABLE `users`
ADD CONSTRAINT `users_hwidfk` FOREIGN KEY (`hwidId`) REFERENCES `hwids` (`id`);
```

#### [Опционально] Обновить Runtime

Если вы хотите, чтобы окно открывалось в браузере, а также авторизация у
пользователя сохранялась, то необходимо будет отредактировать (пропатчить) и пересобрать runtime.
Модуль будет работать и без этого, но не так красиво.

```shell
cd ./src/srcRuntime
git am DiscordAuthSystemRuntime.patch #Надеюсь не нужно объяснять,
# что тут нужен путь до файла DiscordAuthSystemRuntime.patch
gradlew build
```

Если вам впадлу делать все эти изменения, то я приложил готовы билд рантайма. Он лежит рядом с билдом модуля.
17 changes: 17 additions & 0 deletions DiscordAuthSystem_module/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
def mainClassName = "pro.gravit.launchermodules.discordauthsystem.ModuleImpl"
def configClassName = "pro.gravit.launchermodules.discordauthsystem.Config"

sourceCompatibility = '17'
targetCompatibility = '17'

dependencies {
implementation("org.jsoup:jsoup:1.15.1")
implementation("com.ibm.icu:icu4j:71.1")
implementation("com.github.slugify:slugify:3.0.1")
}

jar {
manifest.attributes("Module-Main-Class": mainClassName,
"Module-Config-Class": configClassName,
)
}
129 changes: 129 additions & 0 deletions DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
From ddeb1507e0a6e74737877906db56c62769dd9b2b Mon Sep 17 00:00:00 2001
From: Meido <popenkodaniil@mail.ru>
Date: Wed, 29 Jun 2022 12:17:57 +0700
Subject: [PATCH] DiscordAuthSystemRuntime

---
.../client/gui/impl/GuiEventHandler.java | 18 +++++++++--
.../client/gui/scenes/login/LoginScene.java | 9 ++++++
.../scenes/login/methods/WebAuthMethod.java | 31 ++++++++++++++-----
3 files changed, 48 insertions(+), 10 deletions(-)

diff --git a/src/main/java/pro/gravit/launcher/client/gui/impl/GuiEventHandler.java b/src/main/java/pro/gravit/launcher/client/gui/impl/GuiEventHandler.java
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Патч нужно внести в репозиторий рантайма отдельным PR

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Но зачем? Не думаю, что фикс с onlyBrowser у меня правильно работает (webView в лаунчере появляется и сразу исчезает), так что какой смысл сливать костыль, который сделан только для данного модуля, в основной репозиторий?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделайте так что бы он не был костыльным

index c817abe..c9b5f02 100644
--- a/src/main/java/pro/gravit/launcher/client/gui/impl/GuiEventHandler.java
+++ b/src/main/java/pro/gravit/launcher/client/gui/impl/GuiEventHandler.java
@@ -3,12 +3,15 @@ package pro.gravit.launcher.client.gui.impl;
import pro.gravit.launcher.client.gui.JavaFXApplication;
import pro.gravit.launcher.client.gui.scenes.login.LoginScene;
import pro.gravit.launcher.events.RequestEvent;
+import pro.gravit.launcher.events.request.AdditionalDataRequestEvent;
import pro.gravit.launcher.events.request.AuthRequestEvent;
import pro.gravit.launcher.request.RequestService;
import pro.gravit.launcher.request.WebSocketEvent;
import pro.gravit.launcher.request.websockets.ClientWebSocketService;
import pro.gravit.utils.helper.LogHelper;

+import java.util.Map;
+
public class GuiEventHandler implements RequestService.EventHandler {
private final JavaFXApplication application;

@@ -26,11 +29,22 @@ public class GuiEventHandler implements RequestService.EventHandler {
try {
if (event instanceof AuthRequestEvent) {
boolean isNextScene = application.getCurrentScene() instanceof LoginScene;
+ AuthRequestEvent rawAuthResult = (AuthRequestEvent) event;
((LoginScene) application.getCurrentScene()).isLoginStarted = true;
LogHelper.dev("Receive auth event. Send next scene %s", isNextScene ? "true" : "false");
- application.stateService.setAuthResult(null, (AuthRequestEvent) event);
- if (isNextScene && ((LoginScene) application.getCurrentScene()).isLoginStarted)
+ application.stateService.setAuthResult(null, rawAuthResult);
+ if (isNextScene && ((LoginScene) application.getCurrentScene()).isLoginStarted) {
((LoginScene) application.getCurrentScene()).onGetProfiles();
+
+ if (((LoginScene) application.getCurrentScene()).getSavePasswordCheckBoxSelected()) {
+ application.runtimeSettings.login = rawAuthResult.playerProfile.username;
+ application.runtimeSettings.oauthAccessToken = rawAuthResult.oauth.accessToken;
+ application.runtimeSettings.oauthRefreshToken = rawAuthResult.oauth.refreshToken;
+ application.runtimeSettings.oauthExpire = System.currentTimeMillis() + rawAuthResult.oauth.expire;
+ application.runtimeSettings.lastAuth = ((LoginScene) application.getCurrentScene()).getAuthAvailability();
+ }
+ }
+
}
} catch (Throwable e) {
LogHelper.error(e);
diff --git a/src/main/java/pro/gravit/launcher/client/gui/scenes/login/LoginScene.java b/src/main/java/pro/gravit/launcher/client/gui/scenes/login/LoginScene.java
index 80cf2ed..d12f6c0 100644
--- a/src/main/java/pro/gravit/launcher/client/gui/scenes/login/LoginScene.java
+++ b/src/main/java/pro/gravit/launcher/client/gui/scenes/login/LoginScene.java
@@ -146,6 +146,15 @@ public class LoginScene extends AbstractScene {
LogHelper.info("Selected auth: %s", authAvailability.name);
}

+ public GetAvailabilityAuthRequestEvent.AuthAvailability getAuthAvailability() {
+ return this.authAvailability;
+ }
+
+ public boolean getSavePasswordCheckBoxSelected() {
+ CheckBox checkBox = this.savePasswordCheckBox;
+ return checkBox != null && checkBox.isSelected();
+ }
+
public void addAuthAvailability(GetAvailabilityAuthRequestEvent.AuthAvailability authAvailability) {
RadioButton radio = new RadioButton();
radio.setToggleGroup(authToggleGroup);
diff --git a/src/main/java/pro/gravit/launcher/client/gui/scenes/login/methods/WebAuthMethod.java b/src/main/java/pro/gravit/launcher/client/gui/scenes/login/methods/WebAuthMethod.java
index cf888f1..d6cb6f9 100644
--- a/src/main/java/pro/gravit/launcher/client/gui/scenes/login/methods/WebAuthMethod.java
+++ b/src/main/java/pro/gravit/launcher/client/gui/scenes/login/methods/WebAuthMethod.java
@@ -13,6 +13,10 @@ import pro.gravit.launcher.request.auth.details.AuthWebViewDetails;
import pro.gravit.launcher.request.auth.password.AuthCodePassword;
import pro.gravit.utils.helper.LogHelper;

+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

@@ -52,15 +56,26 @@ public class WebAuthMethod extends AbstractAuthMethod<AuthWebViewDetails> {
@Override
public CompletableFuture<LoginScene.LoginAndPasswordResult> auth(AuthWebViewDetails details) {
overlay.future = new CompletableFuture<>();
- overlay.follow(details.url, details.redirectUrl, (r) -> {
- String code = r;
- LogHelper.debug("Code: %s", code);
- if(code.startsWith("?code=")) {
- code = r.substring("?code=".length(), r.indexOf("&"));
+ if (details.onlyBrowser) {
+ if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Класс Desktop не является частью JavaFX. Используйте функцию открытия url из application

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это какая функция? И вы же не имеете ввиду открытие окна браузера в самом лаунчере?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openURL

+ try {
+ Desktop.getDesktop().browse(new URI(details.url));
+ } catch (IOException | URISyntaxException e) {
+ e.printStackTrace();
+ }
}
- LogHelper.debug("Code: %s", code);
- overlay.future.complete(new LoginScene.LoginAndPasswordResult(null, new AuthCodePassword(code)));
- });
+ overlay.disable();
+ } else {
+ overlay.follow(details.url, details.redirectUrl, (r) -> {
+ String code = r;
+ LogHelper.debug("Code: %s", code);
+ if(code.startsWith("?code=")) {
+ code = r.substring("?code=".length(), r.indexOf("&"));
+ }
+ LogHelper.debug("Code: %s", code);
+ overlay.future.complete(new LoginScene.LoginAndPasswordResult(null, new AuthCodePassword(code)));
+ });
+ }
return overlay.future;
}

--
2.29.2.windows.2

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package pro.gravit.launchermodules.discordauthsystem;

import javax.xml.crypto.dsig.keyinfo.KeyValue;
import java.util.*;

public class Config {
public String clientId = "clientId";
public String clientSecret = "clientSecret";
public String redirectUrl = "redirectUrl";
public String discordAuthorizeUrl = "https://discord.com/oauth2/authorize";
public String discordApiEndpointVersion = "https://discord.com/api/v10";
public String discordApiEndpoint = "https://discord.com/api";
public List<DiscordGuild> guildIdsJoined = new ArrayList<>();
public String guildIdGetNick = "";
public String usernameRegex = "";

public static class DiscordGuild {
public String id;
public String name;
public String url;

public DiscordGuild(String id, String name, String url) {
this.id = id;
this.name = name;
this.url = url;
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package pro.gravit.launchermodules.discordauthsystem;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.config.JsonConfigurable;
import pro.gravit.launcher.modules.LauncherInitContext;
import pro.gravit.launcher.modules.LauncherModule;
import pro.gravit.launcher.modules.LauncherModuleInfo;
import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launchermodules.discordauthsystem.providers.DiscordApi;
import pro.gravit.launchermodules.discordauthsystem.providers.DiscordSystemAuthCoreProvider;
import pro.gravit.launchermodules.discordauthsystem.responses.ExitResponse;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.modules.events.LaunchServerFullInitEvent;
import pro.gravit.launchserver.socket.WebSocketService;
import pro.gravit.launchserver.socket.handlers.NettyWebAPIHandler;
import pro.gravit.utils.Version;
import pro.gravit.utils.helper.LogHelper;

import java.io.IOException;

public class ModuleImpl extends LauncherModule {
public static final Version version = new Version(1, 0, 0, 0, Version.Type.LTS);
private static boolean registred = false;
private final transient Logger logger = LogManager.getLogger();
public JsonConfigurable<Config> jsonConfigurable;
public Config config;

public ModuleImpl() {
super(new LauncherModuleInfo("DiscordAuthSystem", version, new String[]{"LaunchServerCore"}));
}

public void preConfig(PreConfigPhase preConfigPhase) {
if (!registred) {
AuthCoreProvider.providers.register("discordauthsystem", DiscordSystemAuthCoreProvider.class);
WebSocketService.providers.unregister("exit");
WebSocketService.providers.register("exit", ExitResponse.class);
registred = true;
}
}

@Override
public void init(LauncherInitContext initContext) {
registerEvent(this::preConfig, PreConfigPhase.class);
registerEvent(this::finish, LaunchServerFullInitEvent.class);
jsonConfigurable = modulesConfigManager.getConfigurable(Config.class, moduleInfo.name);
}

public void finish(LaunchServerFullInitEvent event) {
LaunchServer launchServer = event.server;
try {
jsonConfigurable.loadConfig();
config = jsonConfigurable.getConfig();
} catch (IOException e) {
LogHelper.error(e);
}
DiscordApi.initialize(config);
NettyWebAPIHandler.addNewSeverlet("auth/discord", new WebApi(this, launchServer));
}
}
Loading