-
Notifications
You must be signed in to change notification settings - Fork 61
[FEATURE] Модуль авторизации через Discord #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
459651f
d258260
ea3258b
d6e09b5
3fcfa9b
b788bc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| ``` | ||
|
|
||
| Если вам впадлу делать все эти изменения, то я приложил готовы билд рантайма. Он лежит рядом с билдом модуля. |
| 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, | ||
| ) | ||
| } |
| 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 | ||
| 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)) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Класс Desktop не является частью JavaFX. Используйте функцию открытия url из application
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Это какая функция? И вы же не имеете ввиду открытие окна браузера в самом лаунчере?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Патч нужно внести в репозиторий рантайма отдельным PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Но зачем? Не думаю, что фикс с onlyBrowser у меня правильно работает (webView в лаунчере появляется и сразу исчезает), так что какой смысл сливать костыль, который сделан только для данного модуля, в основной репозиторий?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Сделайте так что бы он не был костыльным