From 459651fd24b18f7a06095c82675dc199fc439a59 Mon Sep 17 00:00:00 2001 From: Meido Date: Thu, 23 Jun 2022 22:41:29 +0700 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=D0=9C=D0=BE=D0=B4=D1=83=D0=BB?= =?UTF-8?q?=D1=8C=20=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20Discord?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DiscordAuthSystem_module/README.md | 216 ++++++ DiscordAuthSystem_module/build.gradle | 15 + .../discordauthsystem/Config.java | 10 + .../discordauthsystem/ModuleImpl.java | 146 ++++ .../discordauthsystem/WebApi.java | 144 ++++ .../providers/DiscordApi.java | 122 +++ .../DiscordSystemAuthCoreProvider.java | 706 ++++++++++++++++++ 7 files changed, 1359 insertions(+) create mode 100644 DiscordAuthSystem_module/README.md create mode 100644 DiscordAuthSystem_module/build.gradle create mode 100644 DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java create mode 100644 DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java create mode 100644 DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java create mode 100644 DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordApi.java create mode 100644 DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java diff --git a/DiscordAuthSystem_module/README.md b/DiscordAuthSystem_module/README.md new file mode 100644 index 00000000..17a191ad --- /dev/null +++ b/DiscordAuthSystem_module/README.md @@ -0,0 +1,216 @@ +# DiscordAuthSystem + +Позволяет входить в лаунчер через Discord. +Модуль использует библиотеку [JSOUP](https://jsoup.org/download). + +#### Установка модуля + +1. Скопировать модуль **DiscordAuthSystem_module.jar** в папку **/LaunchServer/modules/** +2. Создать приложение в панели управления разработчика https://discord.com/developers/applications и скопировать его id, и секретный токен. +3. В настройках приложение discord oauth добавить redirect_url. Он должен состоять из пути до webapi + /auth/discord. Пример: http://127.0.0.1:9274/webapi/auth/discord +4. Настроить конфигурацию модуля +5. Добавить авторизацию в LaunchServer +6. [Опционально] Обновить Runtime + +#### Конфигурация модуля + +```json +{ + "clientId": "сюда вставляется id", + "clientSecret": "сюда вставляется секрет", + "redirectUrl": "это редирект, который вы указали", + "discordAuthorizeUrl": "https://discord.com/oauth2/authorize", + "discordApiEndpointVersion": "https://discord.com/api/v10", + "discordApiEndpoint": "https://discord.com/api" +} +``` + +#### Конфигурация в 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. +Модуль будет работать и без этого, но не так красиво. + +#### Изменение для открытия в окне браузера авторизации Дискорда (а не webview) + +```java +// /srcRuntime/src/main/java/pro/gravit/launcher/client/gui/scenes/login/methods/WebAuthMethod + +// Эти библиотеки нужно добавить +import java.awt.Desktop; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +// А этот модуль переписать +@Override +public CompletableFuture auth(AuthWebViewDetails details) { + overlay.future = new CompletableFuture<>(); + if (details.onlyBrowser) { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + try { + Desktop.getDesktop().browse(new URI(details.url)); + } catch (IOException | URISyntaxException e) { + e.printStackTrace(); + } + } + 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; +} +``` + +#### Изменение для сохранения состояния авторизации + +```java +// /srcRuntime/src/main/java/pro/gravit/launcher/client/gui/impl/GuiEventHandler + +// Эти библиотеки нужно добавить +import pro.gravit.launcher.events.request.AdditionalDataRequestEvent; +import java.util.Map; + +// А этот модуль переписать +@Override +public boolean eventHandle(T event) { + LogHelper.dev("Processing event %s", event.getType()); + if (event instanceof RequestEvent) { + if (!((RequestEvent) event).requestUUID.equals(RequestEvent.eventUUID)) + return false; + } + try { + if (event instanceof AuthRequestEvent) { + boolean isNextScene = application.getCurrentScene() instanceof LoginScene; + ((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) + ((LoginScene) application.getCurrentScene()).onGetProfiles(); + } + if (event instanceof AdditionalDataRequestEvent) { + AdditionalDataRequestEvent dataRequest = (AdditionalDataRequestEvent) event; + Map data = dataRequest.data; + + String type = data.get("type"); + + if (type != null && type.equals("ChangeRuntimeSettings")) { + application.runtimeSettings.login = data.get("login"); + application.runtimeSettings.oauthAccessToken = data.get("oauthAccessToken"); + application.runtimeSettings.oauthRefreshToken = data.get("oauthRefreshToken"); + application.runtimeSettings.oauthExpire = System.currentTimeMillis() + Integer.parseInt(data.get("oauthExpire")); + application.runtimeSettings.lastAuth = ((LoginScene) application.getCurrentScene()).getAuthAvailability(); + } + } + } catch (Throwable e) { + LogHelper.error(e); + } + return false; +} +``` + +```java +// /srcRuntime/src/main/java/pro/gravit/launcher/client/gui/scenes/login/LoginScene + +// Просто добавить модуль +public GetAvailabilityAuthRequestEvent.AuthAvailability getAuthAvailability() { + return this.authAvailability; +} +``` + +Если вам впадлу делать все эти изменения, то я приложил готовы билд рантайма. Он лежит рядом с билдом модуля. \ No newline at end of file diff --git a/DiscordAuthSystem_module/build.gradle b/DiscordAuthSystem_module/build.gradle new file mode 100644 index 00000000..8085b1dc --- /dev/null +++ b/DiscordAuthSystem_module/build.gradle @@ -0,0 +1,15 @@ +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") +} + +jar { + manifest.attributes("Module-Main-Class": mainClassName, + "Module-Config-Class": configClassName, + ) +} diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java new file mode 100644 index 00000000..636ec872 --- /dev/null +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java @@ -0,0 +1,10 @@ +package pro.gravit.launchermodules.discordauthsystem; + +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"; +} \ No newline at end of file diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java new file mode 100644 index 00000000..9a81c6bf --- /dev/null +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java @@ -0,0 +1,146 @@ +package pro.gravit.launchermodules.discordauthsystem; + +import com.google.gson.Gson; +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.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.core.AuthCoreProvider; +import pro.gravit.launchserver.modules.events.LaunchServerFullInitEvent; +import pro.gravit.launchserver.modules.impl.LaunchServerInitContext; +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 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); + 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)); + } + +// public void exit(ClosePhase closePhase) { +// if (jsonConfigurable != null && jsonConfigurable.getConfig() != null) +// save(); +// } +// +// public void load() { +// load(dbPath); +// } +// +// public void load(Path path) { +// { +// Path sessionsPath = path.resolve("Sessions.json"); +// if (!Files.exists(sessionsPath)) return; +// Type sessionsType = new TypeToken>() { +// }.getType(); +// try (Reader reader = IOHelper.newReader(sessionsPath)) { +// this.sessions = Launcher.gsonManager.configGson.fromJson(reader, sessionsType); +// } catch (IOException e) { +// LogHelper.error(e); +// } +// for (DiscordSystemAuthCoreProvider.DiscordUserSession sessionEntity : sessions) { +// if (sessionEntity.userEntityUUID != null) { +// sessionEntity.user = getUserByUUID(sessionEntity.userEntityUUID); +// } +// } +// } +// } +// +// public void save() { +// save(dbPath); +// } +// +// public void save(Path path) { +// { +// Path sessionsPath = path.resolve("Sessions.json"); +// Type sessionsType = new TypeToken>() { +// }.getType(); +// try (Writer writer = IOHelper.newWriter(sessionsPath)) { +// Launcher.gsonManager.configGson.toJson(sessions, sessionsType, writer); +// } catch (IOException e) { +// LogHelper.error(e); +// } +// } +// } +// +// public void preConfig(PreConfigPhase preConfigPhase) { +// AuthCoreProvider.providers.register("discordauthsystem", DiscordSystemAuthCoreProvider.class); +// } + +// public DiscordSystemAuthCoreProvider.DiscordUser getUser(JoinServerRequest request) { +// JsonElement responseUsername; +// JsonElement responseUUID; +// request.parameters = config.addParameters; +// try { +// JsonElement r = HTTPRequest.jsonRequest(gson.toJsonTree(request), new URL(config.backendUserUrl)); +// if (r == null) { +// return null; +// } +// JsonObject response = r.getAsJsonObject(); +// responseUsername = response.get("username"); +// responseUUID = response.get("uuid"); +// } catch (IllegalStateException | IOException ignore) { +// return null; +// } +// if (responseUsername != null && responseUUID != null) { +// return new DiscordSystemAuthCoreProvider.DiscordUser(responseUsername.getAsString(), UUID.fromString(responseUUID.getAsString())); +// } else { +// return null; +// } +// } + +// public DiscordSystemAuthCoreProvider.DiscordUserSession getSessionByAccessToken(String accessToken) { +// return sessions.stream().filter(e -> e.accessToken != null && e.accessToken.equals(accessToken)).findFirst().orElse(null); +// } + +// public DiscordSystemAuthCoreProvider.DiscordUserSession getSessionByRefreshToken(String refreshToken) { +// return sessions.stream().filter(e -> e.accessToken != null && e.refreshToken.equals(refreshToken)).findFirst().orElse(null); +// } + +// public boolean deleteSession(DiscordSystemAuthCoreProvider.DiscordUserSession entity) { +// return sessions.remove(entity); +// } + +// public boolean exitUser(DiscordSystemAuthCoreProvider.DiscordUser user) { +// return sessions.removeIf(e -> e.user == user); +// } +} \ No newline at end of file diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java new file mode 100644 index 00000000..18fc98fa --- /dev/null +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java @@ -0,0 +1,144 @@ +package pro.gravit.launchermodules.discordauthsystem; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pro.gravit.launcher.ClientPermissions; +import pro.gravit.launcher.events.RequestEvent; +import pro.gravit.launcher.events.request.AdditionalDataRequestEvent; +import pro.gravit.launcher.events.request.AuthRequestEvent; +import pro.gravit.launcher.profiles.PlayerProfile; +import pro.gravit.launchermodules.discordauthsystem.providers.DiscordApi; +import pro.gravit.launchermodules.discordauthsystem.providers.DiscordSystemAuthCoreProvider; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.AuthProviderPair; +import pro.gravit.launchserver.manangers.AuthManager; +import pro.gravit.launchserver.socket.Client; +import pro.gravit.launchserver.socket.NettyConnectContext; +import pro.gravit.launchserver.socket.handlers.NettyWebAPIHandler; +import pro.gravit.launchserver.socket.response.auth.AuthResponse; + +import java.text.Normalizer; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class WebApi implements NettyWebAPIHandler.SimpleSeverletHandler { + + private static final Pattern NONLATIN = Pattern.compile("[^\\w-]"); + private static final Pattern WHITESPACE = Pattern.compile("[\\s]"); + private final ModuleImpl module; + private final LaunchServer server; + private transient final Logger logger = LogManager.getLogger(); + + public WebApi(ModuleImpl module, LaunchServer server) { + this.module = module; + this.server = server; + } + + public static String toSlug(String input) { + String nowhitespace = WHITESPACE.matcher(input).replaceAll("_"); + String normalized = Normalizer.normalize(nowhitespace, Normalizer.Form.NFD); + return NONLATIN.matcher(normalized).replaceAll(""); + } + + @Override + public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectContext context) throws Exception { + //TODO: Переписать этот блок под новую реализацию + Map params = getParamsFromUri(msg.uri()); + + String state = params.get("state"); + + if (state == null || state.isEmpty()) { + sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.NOT_FOUND, "The \"state\" parameter was not found.")); + return; + } + + String code = params.get("code"); + + if (code == null || code.isEmpty()) { + sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.NOT_FOUND, "The \"code\" parameter was not found.")); + return; + } + + var accessTokenResponse = DiscordApi.getAccessTokenByCode(code); + + var response = DiscordApi.getDiscordUserByAccessToken(accessTokenResponse.access_token); + + AuthProviderPair pair = server.config.getAuthProviderPair(); + DiscordSystemAuthCoreProvider core = (DiscordSystemAuthCoreProvider) pair.core; + + DiscordSystemAuthCoreProvider.DiscordUser user = core.getUserByDiscordId(response.user.id); + if (user == null) { + String username = toSlug(response.user.username); + + if (username.length() == 0) { + sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.NOT_ACCEPTABLE, "Your nickname does not meet the requirements. Please change it.")); + return; + } + + if (core.getUserByUsername(username) != null) { + username += "_" + response.user.discriminator; + } + user = core.createUser( + state, + username, + accessTokenResponse.access_token, + accessTokenResponse.refresh_token, + accessTokenResponse.expires_in * 1000, + response.user.id + ); + } else { + user = core.updateDataUser(response.user.id, accessTokenResponse.access_token, accessTokenResponse.refresh_token, accessTokenResponse.expires_in * 1000); + } + if (user.isBanned()) { + sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.FORBIDDEN, "You have been banned!")); + return; + } + + String minecraftAccessToken; + AuthRequestEvent.OAuthRequestEvent oauth; + + AuthManager.AuthReport report = pair.core.authorize(user.getUsername(), null, null, true); + minecraftAccessToken = report.minecraftAccessToken(); + oauth = new AuthRequestEvent.OAuthRequestEvent(report.oauthAccessToken(), report.oauthRefreshToken(), report.oauthExpire()); + + DiscordSystemAuthCoreProvider.DiscordUser finalUser = user; + server.nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, ws) -> { + + Client client = ws.getClient(); + if (client == null) { + return; + } + String wsState = client.getProperty("state"); + if (wsState == null || wsState.isEmpty() || !wsState.equals(state)) { + return; + } + + client.coreObject = finalUser; + client.sessionObject = report.session(); + server.authManager.internalAuth(client, AuthResponse.ConnectTypes.CLIENT, pair, finalUser.getUsername(), finalUser.getUUID(), ClientPermissions.DEFAULT, true); + PlayerProfile playerProfile = server.authManager.getPlayerProfile(client); + AuthRequestEvent request = new AuthRequestEvent(ClientPermissions.DEFAULT, playerProfile, minecraftAccessToken, null, null, oauth); + request.requestUUID = RequestEvent.eventUUID; + + server.nettyServerSocketHandler.nettyServer.service.sendObject(ch, request); + + Map data = new HashMap(); + + data.put("type", "ChangeRuntimeSettings"); + data.put("login", finalUser.getUsername()); + data.put("oauthAccessToken", finalUser.getAccessToken()); + data.put("oauthRefreshToken", finalUser.getRefreshToken()); + data.put("oauthExpire", finalUser.getExpiresIn().toString()); + + AdditionalDataRequestEvent dataRequestEvent = new AdditionalDataRequestEvent(data); + dataRequestEvent.requestUUID = RequestEvent.eventUUID; + + server.nettyServerSocketHandler.nettyServer.service.sendObject(ch, dataRequestEvent); + }); + sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.OK, "You are successfully authorized! Please return to the launcher.")); + } +} diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordApi.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordApi.java new file mode 100644 index 00000000..3f277f7c --- /dev/null +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordApi.java @@ -0,0 +1,122 @@ +package pro.gravit.launchermodules.discordauthsystem.providers; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jsoup.Connection; +import org.jsoup.Jsoup; +import pro.gravit.launcher.Launcher; +import pro.gravit.launchermodules.discordauthsystem.Config; + +import java.io.IOException; + +public class DiscordApi { + private static final String GRANT_TYPE_AUTHORIZATION = "authorization_code"; + private static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; + private static Config config; + private static Logger logger; + + public static void initialize(Config config) { + DiscordApi.config = config; + DiscordApi.logger = LogManager.getLogger(); + } + + public static DiscordAccessTokenResponse sendRefreshToken(String refreshToken) throws IOException { + Connection request = Jsoup.connect(config.discordApiEndpointVersion + "/oauth2/token") + .data("client_id", config.clientId) + .data("client_secret", config.clientSecret) + .data("grant_type", GRANT_TYPE_REFRESH_TOKEN) + .data("refresh_token", refreshToken) + .ignoreContentType(true); + + + return Launcher.gsonManager.gson.fromJson( + request.post().body().text(), + DiscordAccessTokenResponse.class + ); + } + + public static DiscordAccessTokenResponse getAccessTokenByCode(String code) throws IOException { + Connection request = Jsoup.connect(config.discordApiEndpointVersion + "/oauth2/token") + .data("client_id", config.clientId) + .data("client_secret", config.clientSecret) + .data("grant_type", GRANT_TYPE_AUTHORIZATION) + .data("code", code) + .data("redirect_uri", config.redirectUrl) + .data("scope", "") + .ignoreContentType(true); + + + return Launcher.gsonManager.gson.fromJson( + request.post().body().text(), + DiscordAccessTokenResponse.class + ); + } + + public static OauthMeResponse getDiscordUserByAccessToken(String accessToken) throws IOException { + + org.jsoup.Connection request = Jsoup.connect(config.discordApiEndpoint + "/oauth2/@me") + .header("Authorization", "Bearer " + accessToken) + .ignoreContentType(true); + + return Launcher.gsonManager.gson.fromJson( + request.get().body().text(), + OauthMeResponse.class + ); + } + + public static class DiscordUserResponse { + public String id; + public String username; + public String discriminator; + public String avatar; + public String verified; + public String email; + public Integer flags; + public String banner; + public Integer accent_color; + public Integer premium_type; + public Integer public_flags; + + public DiscordUserResponse(String id, String username, String discriminator, String avatar, String verified, String email, Integer flags, String banner, Integer accent_color, Integer premium_type, Integer public_flags) { + this.id = id; + this.username = username; + this.discriminator = discriminator; + this.avatar = avatar; + this.verified = verified; + this.email = email; + this.flags = flags; + this.banner = banner; + this.accent_color = accent_color; + this.premium_type = premium_type; + this.public_flags = public_flags; + } + } + + public static class OauthMeResponse { + public String[] scopes; + public String expires; + public DiscordUserResponse user; + + public OauthMeResponse(String[] scopes, String expires, DiscordUserResponse user) { + this.scopes = scopes; + this.expires = expires; + this.user = user; + } + } + + public static class DiscordAccessTokenResponse { + public String access_token; + public String token_type; + public long expires_in; + public String refresh_token; + public String scope; + + public DiscordAccessTokenResponse(String access_token, String token_type, long expires_in, String refresh_token, String scope) { + this.access_token = access_token; + this.token_type = token_type; + this.expires_in = expires_in; + this.refresh_token = refresh_token; + this.scope = scope; + } + } +} diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java new file mode 100644 index 00000000..6304c95f --- /dev/null +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java @@ -0,0 +1,706 @@ +package pro.gravit.launchermodules.discordauthsystem.providers; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pro.gravit.launcher.ClientPermissions; +import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent; +import pro.gravit.launcher.request.auth.AuthRequest; +import pro.gravit.launcher.request.auth.details.AuthWebViewDetails; +import pro.gravit.launcher.request.secure.HardwareReportRequest; +import pro.gravit.launchermodules.discordauthsystem.ModuleImpl; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.AuthException; +import pro.gravit.launchserver.auth.MySQLSourceConfig; +import pro.gravit.launchserver.auth.core.AuthCoreProvider; +import pro.gravit.launchserver.auth.core.User; +import pro.gravit.launchserver.auth.core.UserSession; +import pro.gravit.launchserver.auth.core.interfaces.UserHardware; +import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportExit; +import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware; +import pro.gravit.launchserver.manangers.AuthManager; +import pro.gravit.launchserver.socket.Client; +import pro.gravit.launchserver.socket.response.auth.AuthResponse; +import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.SecurityHelper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.sql.*; +import java.util.Date; +import java.util.*; + +public class DiscordSystemAuthCoreProvider extends AuthCoreProvider implements AuthSupportExit, AuthSupportHardware { + private final transient Logger logger = LogManager.getLogger(); + public MySQLSourceConfig mySQLHolder; + public double criticalCompareLevel = 1.0; + public String uuidColumn; + public String usernameColumn; + public String accessTokenColumn; + public String refreshTokenColumn; + public String expiresInColumn; + public String discordIdColumn; + public String bannedAtColumn; + public String serverIDColumn; + public String hardwareIdColumn; + public String table; + public String tableHWID = "hwids"; + private transient ModuleImpl module; + // hwid sql + private transient String sqlFindHardwareByPublicKey; + private transient String sqlFindHardwareByData; + private transient String sqlFindHardwareById; + private transient String sqlCreateHardware; + private transient String sqlUpdateHardwarePublicKey; + private transient String sqlUpdateHardwareBanned; + private transient String sqlUpdateUser; + private transient String sqlUsersByHwidId; + + // Prepared SQL queries + private transient String queryByUUIDSQL; + private transient String queryByUsernameSQL; + private transient String queryByAccessTokenSQL; + private transient String queryByDiscordIdSQL; + private transient String insertNewUserSQL; + private transient String updateServerIDSQL; + + @Override + public void init(LaunchServer server) { + module = server.modulesManager.getModule(ModuleImpl.class); + if (mySQLHolder == null) logger.error("mySQLHolder cannot be null"); + if (uuidColumn == null) logger.error("uuidColumn cannot be null"); + if (usernameColumn == null) logger.error("usernameColumn cannot be null"); + if (accessTokenColumn == null) logger.error("accessTokenColumn cannot be null"); + if (refreshTokenColumn == null) logger.error("refreshTokenColumn cannot be null"); + if (expiresInColumn == null) logger.error("expiresInColumn cannot be null"); + if (discordIdColumn == null) logger.error("discordIdColumn cannot be null"); + if (bannedAtColumn == null) logger.error("bannedAtColumn cannot be null"); + if (serverIDColumn == null) logger.error("serverIDColumn cannot be null"); + if (hardwareIdColumn == null) logger.error("hardwareIdColumn cannot be null"); + if (table == null) logger.error("table cannot be null"); + + String userInfoCols = String.format("%s, %s, %s, %s, %s, %s, %s, %s, %s", uuidColumn, usernameColumn, accessTokenColumn, refreshTokenColumn, expiresInColumn, discordIdColumn, bannedAtColumn, serverIDColumn, hardwareIdColumn); + + queryByUsernameSQL = String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", + userInfoCols, table, usernameColumn); + + queryByUUIDSQL = String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", + userInfoCols, table, uuidColumn); + + queryByAccessTokenSQL = String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", + userInfoCols, table, accessTokenColumn); + + queryByDiscordIdSQL = String.format("SELECT %s FROM %s WHERE %s=? LIMIT 1", + userInfoCols, table, discordIdColumn); + + insertNewUserSQL = String.format("INSERT INTO %s (%s) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", table, userInfoCols); + updateServerIDSQL = String.format("UPDATE %s SET %s=? WHERE %s=?", table, serverIDColumn, discordIdColumn); + + String hardwareInfoCols = "id, hwDiskId, baseboardSerialNumber, displayId, bitness, totalMemory, logicalProcessors, physicalProcessors, processorMaxFreq, battery, id, graphicCard, banned, publicKey"; + if (sqlFindHardwareByPublicKey == null) + sqlFindHardwareByPublicKey = String.format("SELECT %s FROM %s WHERE `publicKey` = ?", hardwareInfoCols, tableHWID); + if (sqlFindHardwareById == null) + sqlFindHardwareById = String.format("SELECT %s FROM %s WHERE `id` = ?", hardwareInfoCols, tableHWID); + if (sqlUsersByHwidId == null) + sqlUsersByHwidId = String.format("SELECT %s FROM %s WHERE `%s` = ?", userInfoCols, table, hardwareIdColumn); + if (sqlFindHardwareByData == null) + sqlFindHardwareByData = String.format("SELECT %s FROM %s", hardwareInfoCols, tableHWID); + if (sqlCreateHardware == null) + sqlCreateHardware = String.format("INSERT INTO `%s` (`publickey`, `hwDiskId`, `baseboardSerialNumber`, `displayId`, `bitness`, `totalMemory`, `logicalProcessors`, `physicalProcessors`, `processorMaxFreq`, `graphicCard`, `battery`, `banned`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '0')", tableHWID); + if (sqlUpdateHardwarePublicKey == null) + sqlUpdateHardwarePublicKey = String.format("UPDATE %s SET `publicKey` = ? WHERE `id` = ?", tableHWID); + + sqlUpdateHardwareBanned = String.format("UPDATE %s SET `banned` = ? WHERE `id` = ?", tableHWID); + sqlUpdateUser = String.format("UPDATE %s SET `%s` = ? WHERE `%s` = ?", table, hardwareIdColumn, discordIdColumn); + } + + @Override + public User getUserByUsername(String username) { + return getDiscordUserByUsername(username); + } + + public DiscordUser getDiscordUserByUsername(String username) { + try { + return query(queryByUsernameSQL, username); + } catch (IOException e) { + logger.error("SQL error", e); + return null; + } + } + + public DiscordUser updateDataUser(String discordId, String accessToken, String refreshToken, Long expiresIn) { + try (Connection connection = mySQLHolder.getConnection()) { + return updateDataUser(connection, discordId, accessToken, refreshToken, expiresIn); + } catch (SQLException e) { + logger.error("updateDataUser SQL error", e); + return null; + } + } + + private DiscordUser updateDataUser(Connection connection, String discordId, String accessToken, String refreshToken, Long expiresIn) throws SQLException { + + ArrayList setList = new ArrayList(); + + if (accessToken != null) { + if (accessToken.length() == 0) { + setList.add(accessTokenColumn + " = " + null); + } else { + setList.add(accessTokenColumn + " = '" + accessToken + "'"); + } + } + + if (refreshToken != null) { + if (refreshToken.length() == 0) { + setList.add(refreshTokenColumn + " = " + null); + } else { + setList.add(refreshTokenColumn + " = '" + refreshToken + "'"); + } + } + + if (expiresIn != null) { + if (expiresIn == 0) { + setList.add(expiresInColumn + " = " + null); + } else { + setList.add(expiresInColumn + " = " + expiresIn); + } + } + + String sqlSet = String.join(", ", setList); + + if (sqlSet.length() != 0) { + String sql = String.format("UPDATE %s SET %s WHERE %s = %s", table, sqlSet, discordIdColumn, discordId); + PreparedStatement s = connection.prepareStatement(sql); + s.executeUpdate(); + } + + return getUserByDiscordId(discordId); + } + + @Override + public User getUserByLogin(String login) { + return getUserByUsername(login); + } + + @Override + public User getUserByUUID(UUID uuid) { + try { + return query(queryByUUIDSQL, uuid.toString()); + } catch (IOException e) { + logger.error("getUserByUUID SQL error", e); + return null; + } + } + + @Override + public User checkServer(Client client, String username, String serverID) throws IOException { + User user = getUserByUsername(username); + if (user == null) { + return null; + } + String usernameUser = user.getUsername(); + logger.info("checkServer username: " + usernameUser); + String serverId = user.getServerId(); + logger.info("checkServer serverId: " + serverId); + if (usernameUser != null && usernameUser.equals(username) && serverId != null && serverId.equals(serverID)) { + return user; + } + return null; + } + + @Override + public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException { + User user = client.getUser(); + if (user == null) return false; + String usernameUser = user.getUsername(); + logger.info("joinServer username: " + usernameUser); + String userAccessToken = user.getAccessToken(); + logger.info("joinServer userAccessToken: " + userAccessToken); + return usernameUser != null && usernameUser.equals(username) && userAccessToken != null && userAccessToken.equals(accessToken) && updateServerID(user, serverID); + } + + public DiscordUser getUserByAccessToken(String accessToken) { + try { + return query(queryByAccessTokenSQL, accessToken); + } catch (IOException e) { + logger.error("getUserByAccessToken SQL error", e); + return null; + } + } + + public DiscordUser getUserByDiscordId(String discordId) { + try { + return query(queryByDiscordIdSQL, discordId); + } catch (IOException e) { + logger.error("getUserByDiscordId SQL error", e); + return null; + } + } + + public DiscordUser createUser(String uuid, String username, String accessToken, String refreshToken, Long expiresIn, String discordId) { + try (Connection connection = mySQLHolder.getConnection()) { + return createUser(connection, uuid, username, accessToken, refreshToken, expiresIn, discordId); + } catch (SQLException e) { + logger.error("createUser SQL error", e); + return null; + } + } + + private DiscordUser createUser(Connection connection, String uuid, String username, String accessToken, String refreshToken, Long expiresIn, String discordId) throws SQLException { + PreparedStatement s = connection.prepareStatement(insertNewUserSQL); + s.setString(1, uuid); + s.setString(2, username); + s.setString(3, accessToken); + s.setString(4, refreshToken); + s.setLong(5, expiresIn); + s.setString(6, discordId); + s.setDate(7, null); + s.setString(8, null); + s.setString(9, null); + s.executeUpdate(); + return getUserByAccessToken(accessToken); + } + + @Override + public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired { + DiscordUser user = getUserByAccessToken(accessToken); + if (user == null) return null; + return new DiscordUserSession(user, accessToken); + } + + @Override + public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) { + try { + var response = DiscordApi.sendRefreshToken(refreshToken); + if (response == null) { + return null; + } + DiscordUser user = getUserByAccessToken(response.access_token); + if (user != null) { + updateDataUser(user.getDiscordId(), response.access_token, response.refresh_token, response.expires_in * 1000); + } + return AuthManager.AuthReport.ofOAuth(response.access_token, response.refresh_token, response.expires_in * 1000, null); + } catch (IOException e) { + logger.error("DiscordAuth refresh failed", e); + return null; + } + } + + @Override + public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws AuthException { + if (login == null) { + throw AuthException.userNotFound(); + } + + DiscordUser user = getDiscordUserByUsername(login); + + if (user == null) { + return null; + } + + if (user.accessToken == null) { + return null; + } + + DiscordUserSession session = new DiscordUserSession(user, user.accessToken); + return AuthManager.AuthReport.ofOAuth(user.accessToken, user.refreshToken, user.expiresIn * 1000, session); + } + + @Override + protected boolean updateServerID(User user, String serverID) throws IOException { + try (Connection c = mySQLHolder.getConnection()) { + DiscordUser discordUser = (DiscordUser) user; + discordUser.serverId = serverID; + PreparedStatement s = c.prepareStatement(updateServerIDSQL); + s.setString(1, serverID); + s.setString(2, discordUser.getDiscordId()); + s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); + return s.executeUpdate() > 0; + } catch (SQLException e) { + throw new IOException(e); + } + } + + @Override + public void close() throws IOException { + + } + + @Override + public boolean deleteSession(UserSession session) { + return exitUser(session.getUser()); + } + + @Override + public boolean exitUser(User user) { + DiscordUser discordUser = getUserByAccessToken(user.getAccessToken()); + if (discordUser == null) { + return true; + } + return updateDataUser(discordUser.getDiscordId(), "", null, null) != null; + } + + private void setUserHardwareId(Connection connection, String discordId, long hwidId) throws SQLException { + PreparedStatement s = connection.prepareStatement(sqlUpdateUser); + s.setLong(1, hwidId); + s.setString(2, discordId); + s.executeUpdate(); + } + + private DiscordUser query(String sql, String value) throws IOException { + try (Connection c = mySQLHolder.getConnection()) { + PreparedStatement s = c.prepareStatement(sql); + s.setString(1, value); + s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); + try (ResultSet set = s.executeQuery()) { + return constructUser(set); + } + } catch (SQLException e) { + throw new IOException(e); + } + } + + private DiscordUser constructUser(ResultSet set) throws SQLException { + return set.next() ? + new DiscordUser( + set.getString(usernameColumn), + UUID.fromString(set.getString(uuidColumn)), + set.getString(accessTokenColumn), + set.getString(refreshTokenColumn), + set.getLong(expiresInColumn), + set.getString(discordIdColumn), + set.getDate(bannedAtColumn), + set.getString(serverIDColumn), + set.getLong(hardwareIdColumn) + ) + : null; + } + + @Override + public List getDetails(Client client) { + String state = UUID.randomUUID().toString(); + client.setProperty("state", state); + String responseType = "code"; + String[] scope = new String[]{"identify", "guilds.join", "email"}; + String url = String.format("%s?response_type=%s&client_id=%s&scope=%s&state=%s&redirect_uri=%s&prompt=consent", module.config.discordAuthorizeUrl, responseType, module.config.clientId, String.join("%20", scope), state, module.config.redirectUrl); + return List.of(new AuthWebViewDetails(url, "https://google.com", true, true)); + } + + private DiscordUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException { + HardwareReportRequest.HardwareInfo hardwareInfo = new HardwareReportRequest.HardwareInfo(); + hardwareInfo.hwDiskId = set.getString("hwDiskId"); + hardwareInfo.baseboardSerialNumber = set.getString("baseboardSerialNumber"); + Blob displayId = set.getBlob("displayId"); + hardwareInfo.displayId = displayId == null ? null : IOHelper.read(displayId.getBinaryStream()); + hardwareInfo.bitness = set.getInt("bitness"); + hardwareInfo.totalMemory = set.getLong("totalMemory"); + hardwareInfo.logicalProcessors = set.getInt("logicalProcessors"); + hardwareInfo.physicalProcessors = set.getInt("physicalProcessors"); + hardwareInfo.processorMaxFreq = set.getLong("processorMaxFreq"); + hardwareInfo.battery = set.getBoolean("battery"); + hardwareInfo.graphicCard = set.getString("graphicCard"); + Blob publicKey = set.getBlob("publicKey"); + long id = set.getLong("id"); + boolean banned = set.getBoolean("banned"); + return new DiscordUserHardware(hardwareInfo, publicKey == null ? null : IOHelper.read(publicKey.getBinaryStream()), id, banned); + } + + @Override + public UserHardware getHardwareInfoByPublicKey(byte[] publicKey) { + try (Connection connection = mySQLHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlFindHardwareByPublicKey); + s.setBlob(1, new ByteArrayInputStream(publicKey)); + try (ResultSet set = s.executeQuery()) { + if (set.next()) { + return fetchHardwareInfo(set); + } else { + return null; + } + } + } catch (SQLException | IOException e) { + logger.error("SQL Error", e); + return null; + } + } + + @Override + public UserHardware getHardwareInfoByData(HardwareReportRequest.HardwareInfo info) { + try (Connection connection = mySQLHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlFindHardwareByData); + try (ResultSet set = s.executeQuery()) { + while (set.next()) { + DiscordUserHardware hw = fetchHardwareInfo(set); + HardwareInfoCompareResult result = compareHardwareInfo(hw.getHardwareInfo(), info); + if (result.compareLevel > criticalCompareLevel) { + return hw; + } + } + } + } catch (SQLException | IOException e) { + logger.error("SQL Error", e); + } + return null; + } + + @Override + public UserHardware getHardwareInfoById(String id) { + try (Connection connection = mySQLHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlFindHardwareById); + s.setLong(1, Long.parseLong(id)); + try (ResultSet set = s.executeQuery()) { + if (set.next()) { + return fetchHardwareInfo(set); + } else { + return null; + } + } + } catch (SQLException | IOException e) { + logger.error("SQL Error", e); + return null; + } + } + + @Override + public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey) { + try (Connection connection = mySQLHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlCreateHardware, Statement.RETURN_GENERATED_KEYS); + s.setBlob(1, new ByteArrayInputStream(publicKey)); + s.setString(2, hardwareInfo.hwDiskId); + s.setString(3, hardwareInfo.baseboardSerialNumber); + s.setBlob(4, hardwareInfo.displayId == null ? null : new ByteArrayInputStream(hardwareInfo.displayId)); + s.setInt(5, hardwareInfo.bitness); + s.setLong(6, hardwareInfo.totalMemory); + s.setInt(7, hardwareInfo.logicalProcessors); + s.setInt(8, hardwareInfo.physicalProcessors); + s.setLong(9, hardwareInfo.processorMaxFreq); + s.setString(10, hardwareInfo.graphicCard); + s.setBoolean(11, hardwareInfo.battery); + s.executeUpdate(); + try (ResultSet generatedKeys = s.getGeneratedKeys()) { + if (generatedKeys.next()) { + //writeHwidLog(connection, generatedKeys.getLong(1), publicKey); + long id = generatedKeys.getLong(1); + return new DiscordUserHardware(hardwareInfo, publicKey, id, false); + } + } + return null; + } catch (SQLException e) { + logger.error("SQL Error", e); + return null; + } + } + + @Override + public void connectUserAndHardware(UserSession userSession, UserHardware hardware) { + DiscordUserSession discordUserSession = (DiscordUserSession) userSession; + DiscordUser discordUser = discordUserSession.user; + DiscordUserHardware discordUserHardware = (DiscordUserHardware) hardware; + if (discordUser.hwidId == discordUserHardware.id) return; + discordUser.hwidId = discordUserHardware.id; + try (Connection connection = mySQLHolder.getConnection()) { + setUserHardwareId(connection, discordUser.getDiscordId(), discordUserHardware.id); + } catch (SQLException e) { + logger.error("SQL Error", e); + } + } + + @Override + public void addPublicKeyToHardwareInfo(UserHardware hardware, byte[] publicKey) { + DiscordUserHardware discordUserHardware = (DiscordUserHardware) hardware; + discordUserHardware.publicKey = publicKey; + try (Connection connection = mySQLHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlUpdateHardwarePublicKey); + s.setBlob(1, new ByteArrayInputStream(publicKey)); + s.setLong(2, discordUserHardware.id); + s.executeUpdate(); + } catch (SQLException e) { + logger.error("SQL error", e); + } + } + + @Override + public Iterable getUsersByHardwareInfo(UserHardware hardware) { + List users = new LinkedList<>(); + try (Connection c = mySQLHolder.getConnection()) { + PreparedStatement s = c.prepareStatement(sqlUsersByHwidId); + s.setLong(1, Long.parseLong(hardware.getId())); + s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); + try (ResultSet set = s.executeQuery()) { + while (!set.isLast()) { + users.add(constructUser(set)); + } + } + } catch (SQLException e) { + logger.error("SQL error", e); + return null; + } + return users; + } + + @Override + public void banHardware(UserHardware hardware) { + DiscordUserHardware discordUserHardware = (DiscordUserHardware) hardware; + discordUserHardware.banned = true; + try (Connection connection = mySQLHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned); + s.setBoolean(1, true); + s.setLong(2, discordUserHardware.id); + s.executeUpdate(); + } catch (SQLException e) { + logger.error("SQL Error", e); + } + } + + @Override + public void unbanHardware(UserHardware hardware) { + DiscordUserHardware discordUserHardware = (DiscordUserHardware) hardware; + discordUserHardware.banned = false; + try (Connection connection = mySQLHolder.getConnection()) { + PreparedStatement s = connection.prepareStatement(sqlUpdateHardwareBanned); + s.setBoolean(1, false); + s.setLong(2, discordUserHardware.id); + s.executeUpdate(); + } catch (SQLException e) { + logger.error("SQL error", e); + } + } + + public static class DiscordUser implements User { + public String username; + public String discordId; + public UUID uuid; + public ClientPermissions permissions; + public String serverId; + public String accessToken; + public String refreshToken; + public Long expiresIn; + public Date bannedAt; + protected Long hwidId; + + public DiscordUser(String username, UUID uuid, String accessToken, String refreshToken, Long expiresIn, String discordId, Date bannedAt, String serverId, Long hwidId) { + this.username = username; + this.uuid = uuid; + this.discordId = discordId; + this.accessToken = accessToken; + this.expiresIn = expiresIn; + this.bannedAt = bannedAt; + this.refreshToken = refreshToken; + this.permissions = new ClientPermissions(); + this.serverId = serverId; + this.hwidId = hwidId; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public UUID getUUID() { + return uuid; + } + + @Override + public String getServerId() { + return serverId; + } + + @Override + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public Long getExpiresIn() { + return expiresIn; + } + + public String getDiscordId() { + return discordId; + } + + @Override + public ClientPermissions getPermissions() { + return permissions; + } + + @Override + public boolean isBanned() { + return this.bannedAt != null; + } + } + + public static class DiscordUserSession implements UserSession { + private final String id; + public transient DiscordUser user; + public String accessToken; + public long expireMillis; + + public DiscordUserSession(DiscordUser user, String accessToken) { + this.id = SecurityHelper.randomStringToken(); + this.user = user; + this.accessToken = accessToken; + } + + @Override + public String getID() { + return id; + } + + @Override + public User getUser() { + return user; + } + + @Override + public long getExpireIn() { + return expireMillis; + } + } + + public static class DiscordUserHardware implements UserHardware { + + private final HardwareReportRequest.HardwareInfo hardwareInfo; + private final long id; + private byte[] publicKey; + private boolean banned; + + public DiscordUserHardware(HardwareReportRequest.HardwareInfo hardwareInfo, byte[] publicKey, long id, boolean banned) { + this.hardwareInfo = hardwareInfo; + this.publicKey = publicKey; + this.id = id; + this.banned = banned; + } + + @Override + public HardwareReportRequest.HardwareInfo getHardwareInfo() { + return hardwareInfo; + } + + @Override + public byte[] getPublicKey() { + return publicKey; + } + + @Override + public String getId() { + return String.valueOf(id); + } + + @Override + public boolean isBanned() { + return banned; + } + + @Override + public String toString() { + return "DiscordUserHardware{" + + "hardwareInfo=" + hardwareInfo + + ", publicKey=" + (publicKey == null ? null : new String(Base64.getEncoder().encode(publicKey))) + + ", id=" + id + + ", banned=" + banned + + '}'; + } + } +} \ No newline at end of file From d2582605ac9580dfbf18dc220016555b6a0b64e3 Mon Sep 17 00:00:00 2001 From: Meido Date: Thu, 23 Jun 2022 23:26:58 +0700 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B7=D0=B0=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=20=D0=B8=20=D0=BD=D0=B5=D0=BD=D1=83=D0=B6=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../discordauthsystem/ModuleImpl.java | 87 ------------------- .../DiscordSystemAuthCoreProvider.java | 9 +- 2 files changed, 4 insertions(+), 92 deletions(-) diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java index 9a81c6bf..585805ea 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java @@ -56,91 +56,4 @@ public void finish(LaunchServerFullInitEvent event) { DiscordApi.initialize(config); NettyWebAPIHandler.addNewSeverlet("auth/discord", new WebApi(this, launchServer)); } - -// public void exit(ClosePhase closePhase) { -// if (jsonConfigurable != null && jsonConfigurable.getConfig() != null) -// save(); -// } -// -// public void load() { -// load(dbPath); -// } -// -// public void load(Path path) { -// { -// Path sessionsPath = path.resolve("Sessions.json"); -// if (!Files.exists(sessionsPath)) return; -// Type sessionsType = new TypeToken>() { -// }.getType(); -// try (Reader reader = IOHelper.newReader(sessionsPath)) { -// this.sessions = Launcher.gsonManager.configGson.fromJson(reader, sessionsType); -// } catch (IOException e) { -// LogHelper.error(e); -// } -// for (DiscordSystemAuthCoreProvider.DiscordUserSession sessionEntity : sessions) { -// if (sessionEntity.userEntityUUID != null) { -// sessionEntity.user = getUserByUUID(sessionEntity.userEntityUUID); -// } -// } -// } -// } -// -// public void save() { -// save(dbPath); -// } -// -// public void save(Path path) { -// { -// Path sessionsPath = path.resolve("Sessions.json"); -// Type sessionsType = new TypeToken>() { -// }.getType(); -// try (Writer writer = IOHelper.newWriter(sessionsPath)) { -// Launcher.gsonManager.configGson.toJson(sessions, sessionsType, writer); -// } catch (IOException e) { -// LogHelper.error(e); -// } -// } -// } -// -// public void preConfig(PreConfigPhase preConfigPhase) { -// AuthCoreProvider.providers.register("discordauthsystem", DiscordSystemAuthCoreProvider.class); -// } - -// public DiscordSystemAuthCoreProvider.DiscordUser getUser(JoinServerRequest request) { -// JsonElement responseUsername; -// JsonElement responseUUID; -// request.parameters = config.addParameters; -// try { -// JsonElement r = HTTPRequest.jsonRequest(gson.toJsonTree(request), new URL(config.backendUserUrl)); -// if (r == null) { -// return null; -// } -// JsonObject response = r.getAsJsonObject(); -// responseUsername = response.get("username"); -// responseUUID = response.get("uuid"); -// } catch (IllegalStateException | IOException ignore) { -// return null; -// } -// if (responseUsername != null && responseUUID != null) { -// return new DiscordSystemAuthCoreProvider.DiscordUser(responseUsername.getAsString(), UUID.fromString(responseUUID.getAsString())); -// } else { -// return null; -// } -// } - -// public DiscordSystemAuthCoreProvider.DiscordUserSession getSessionByAccessToken(String accessToken) { -// return sessions.stream().filter(e -> e.accessToken != null && e.accessToken.equals(accessToken)).findFirst().orElse(null); -// } - -// public DiscordSystemAuthCoreProvider.DiscordUserSession getSessionByRefreshToken(String refreshToken) { -// return sessions.stream().filter(e -> e.accessToken != null && e.refreshToken.equals(refreshToken)).findFirst().orElse(null); -// } - -// public boolean deleteSession(DiscordSystemAuthCoreProvider.DiscordUserSession entity) { -// return sessions.remove(entity); -// } - -// public boolean exitUser(DiscordSystemAuthCoreProvider.DiscordUser user) { -// return sessions.removeIf(e -> e.user == user); -// } } \ No newline at end of file diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java index 6304c95f..9934388a 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java @@ -196,10 +196,10 @@ public User checkServer(Client client, String username, String serverID) throws if (user == null) { return null; } + String usernameUser = user.getUsername(); - logger.info("checkServer username: " + usernameUser); String serverId = user.getServerId(); - logger.info("checkServer serverId: " + serverId); + if (usernameUser != null && usernameUser.equals(username) && serverId != null && serverId.equals(serverID)) { return user; } @@ -210,10 +210,10 @@ public User checkServer(Client client, String username, String serverID) throws public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException { User user = client.getUser(); if (user == null) return false; + String usernameUser = user.getUsername(); - logger.info("joinServer username: " + usernameUser); String userAccessToken = user.getAccessToken(); - logger.info("joinServer userAccessToken: " + userAccessToken); + return usernameUser != null && usernameUser.equals(username) && userAccessToken != null && userAccessToken.equals(accessToken) && updateServerID(user, serverID); } @@ -476,7 +476,6 @@ public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwa s.executeUpdate(); try (ResultSet generatedKeys = s.getGeneratedKeys()) { if (generatedKeys.next()) { - //writeHwidLog(connection, generatedKeys.getLong(1), publicKey); long id = generatedKeys.getLong(1); return new DiscordUserHardware(hardwareInfo, publicKey, id, false); } From ea3258b6691471d25325094cc8af5839dd516667 Mon Sep 17 00:00:00 2001 From: Meido Date: Sun, 26 Jun 2022 11:00:44 +0700 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=B0=D1=85=D0=BE=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=20=D0=B3=D0=B8=D0=BB=D1=8C=D0=B4=D0=B8?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../discordauthsystem/Config.java | 24 +++- .../discordauthsystem/WebApi.java | 113 ++++++++++++++++-- .../providers/DiscordApi.java | 43 +++++++ .../DiscordSystemAuthCoreProvider.java | 9 +- 4 files changed, 174 insertions(+), 15 deletions(-) diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java index 636ec872..ccf3f3b6 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java @@ -1,5 +1,8 @@ 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"; @@ -7,4 +10,23 @@ public class Config { public String discordAuthorizeUrl = "https://discord.com/oauth2/authorize"; public String discordApiEndpointVersion = "https://discord.com/api/v10"; public String discordApiEndpoint = "https://discord.com/api"; -} \ No newline at end of file + + public List guildIdsJoined = new ArrayList<>(); + + public String guildIdGetNick = ""; + + public int usernameLimit = 32; + + 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; + } + } +} + diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java index 18fc98fa..d501d034 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java @@ -1,8 +1,10 @@ package pro.gravit.launchermodules.discordauthsystem; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.*; +import io.netty.util.CharsetUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import pro.gravit.launcher.ClientPermissions; @@ -20,11 +22,15 @@ import pro.gravit.launchserver.socket.handlers.NettyWebAPIHandler; import pro.gravit.launchserver.socket.response.auth.AuthResponse; +import java.io.File; import java.text.Normalizer; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + public class WebApi implements NettyWebAPIHandler.SimpleSeverletHandler { private static final Pattern NONLATIN = Pattern.compile("[^\\w-]"); @@ -46,7 +52,6 @@ public static String toSlug(String input) { @Override public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectContext context) throws Exception { - //TODO: Переписать этот блок под новую реализацию Map params = getParamsFromUri(msg.uri()); String state = params.get("state"); @@ -56,6 +61,28 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC return; } + AtomicBoolean userFined = new AtomicBoolean(false); + + server.nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, ws) -> { + + Client client = ws.getClient(); + if (client == null) { + return; + } + + String wsState = client.getProperty("state"); + if (wsState == null || wsState.isEmpty() || !wsState.equals(state)) { + return; + } + + userFined.set(true); + }); + + if (!userFined.get()) { + sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.NOT_FOUND, "The \"state\" parameter is invalid.")); + return; + } + String code = params.get("code"); if (code == null || code.isEmpty()) { @@ -63,25 +90,73 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC return; } - var accessTokenResponse = DiscordApi.getAccessTokenByCode(code); + DiscordApi.DiscordAccessTokenResponse accessTokenResponse = null; + + try { + accessTokenResponse = DiscordApi.getAccessTokenByCode(code); + } catch (Exception e) { + sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.FORBIDDEN, "Discord authorization denied your code.")); + return; + } var response = DiscordApi.getDiscordUserByAccessToken(accessTokenResponse.access_token); + if (!module.config.guildIdsJoined.isEmpty()) { + var guilds = DiscordApi.getUserGuilds(accessTokenResponse.access_token); + + var needGuilds = module.config.guildIdsJoined; + + for (var guild : guilds) { + needGuilds.removeIf(g -> Objects.equals(g.id, guild.id)); + } + + if (!needGuilds.isEmpty()) { + String body = "To enter the server you must be a member of these guilds: "; + List guildData = new ArrayList<>(); + for (var g : needGuilds) { + guildData.add("" + g.name + ""); + } + sendHttpResponse(ctx, simpleHtmlResponse(HttpResponseStatus.FORBIDDEN, body + String.join(", ", guildData))); + return; + } + } + AuthProviderPair pair = server.config.getAuthProviderPair(); DiscordSystemAuthCoreProvider core = (DiscordSystemAuthCoreProvider) pair.core; DiscordSystemAuthCoreProvider.DiscordUser user = core.getUserByDiscordId(response.user.id); + if (user == null) { - String username = toSlug(response.user.username); + String username; + if (module.config.guildIdGetNick.length() > 0) { + try { + var member = DiscordApi.getUserGuildMember(accessTokenResponse.access_token, module.config.guildIdGetNick); + username = member.nick; + } catch (Exception e) { + logger.error("DiscordApi.getUserGuildMember: " + e); + sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR, "An unexpected error occurred!")); + return; + } + } else { + username = toSlug(response.user.username); + } - if (username.length() == 0) { + var usernameLength = username.length(); + + if (usernameLength == 0) { sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.NOT_ACCEPTABLE, "Your nickname does not meet the requirements. Please change it.")); return; } + if (usernameLength > module.config.usernameLimit) { + username = username.substring(0, usernameLength-1); + } + if (core.getUserByUsername(username) != null) { + username = username.substring(0, usernameLength-1-response.user.discriminator.length()); username += "_" + response.user.discriminator; } + user = core.createUser( state, username, @@ -93,11 +168,14 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC } else { user = core.updateDataUser(response.user.id, accessTokenResponse.access_token, accessTokenResponse.refresh_token, accessTokenResponse.expires_in * 1000); } + if (user.isBanned()) { sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.FORBIDDEN, "You have been banned!")); return; } + + String minecraftAccessToken; AuthRequestEvent.OAuthRequestEvent oauth; @@ -112,6 +190,7 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC if (client == null) { return; } + String wsState = client.getProperty("state"); if (wsState == null || wsState.isEmpty() || !wsState.equals(state)) { return; @@ -141,4 +220,22 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC }); sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.OK, "You are successfully authorized! Please return to the launcher.")); } + + public FullHttpResponse simpleHtmlResponse(HttpResponseStatus status, String body) { + FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK); + response.setStatus(status); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8"); + + StringBuilder buf = new StringBuilder() + .append("\r\n") + .append("") + .append("\r\n") + .append(body) + .append("\r\n"); + + ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8); + response.content().writeBytes(buffer); + buffer.release(); + return response; + } } diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordApi.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordApi.java index 3f277f7c..288d9a0c 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordApi.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordApi.java @@ -1,5 +1,6 @@ package pro.gravit.launchermodules.discordauthsystem.providers; +import com.google.gson.reflect.TypeToken; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jsoup.Connection; @@ -8,6 +9,7 @@ import pro.gravit.launchermodules.discordauthsystem.Config; import java.io.IOException; +import java.util.List; public class DiscordApi { private static final String GRANT_TYPE_AUTHORIZATION = "authorization_code"; @@ -64,6 +66,47 @@ public static OauthMeResponse getDiscordUserByAccessToken(String accessToken) th ); } + public static List getUserGuilds(String accessToken) throws IOException { + org.jsoup.Connection request = Jsoup.connect(config.discordApiEndpoint + "/users/@me/guilds") + .header("Authorization", "Bearer " + accessToken) + .ignoreContentType(true); + + return Launcher.gsonManager.gson.fromJson( + request.get().body().text(), + new TypeToken>(){}.getType() + ); + } + + public static MemberGuildResponse getUserGuildMember(String accessToken, String guildId) throws IOException { + org.jsoup.Connection request = Jsoup.connect(config.discordApiEndpoint + "/users/@me/guilds/" + guildId + "/member") + .header("Authorization", "Bearer " + accessToken) + .ignoreContentType(true); + + return Launcher.gsonManager.gson.fromJson( + request.get().body().text(), + MemberGuildResponse.class + ); + } + + public static class UserGuildResponse { + public String id; + + public String name; + + public UserGuildResponse (String id, String name) { + this.id = id; + this.name = name; + } + } + + public static class MemberGuildResponse { + public String nick; + + public MemberGuildResponse (String nick) { + this.nick = nick; + } + } + public static class DiscordUserResponse { public String id; public String username; diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java index 9934388a..aa0b6a8b 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java @@ -196,10 +196,8 @@ public User checkServer(Client client, String username, String serverID) throws if (user == null) { return null; } - String usernameUser = user.getUsername(); String serverId = user.getServerId(); - if (usernameUser != null && usernameUser.equals(username) && serverId != null && serverId.equals(serverID)) { return user; } @@ -210,10 +208,8 @@ public User checkServer(Client client, String username, String serverID) throws public boolean joinServer(Client client, String username, String accessToken, String serverID) throws IOException { User user = client.getUser(); if (user == null) return false; - String usernameUser = user.getUsername(); String userAccessToken = user.getAccessToken(); - return usernameUser != null && usernameUser.equals(username) && userAccessToken != null && userAccessToken.equals(accessToken) && updateServerID(user, serverID); } @@ -379,9 +375,9 @@ public List getDetails( String state = UUID.randomUUID().toString(); client.setProperty("state", state); String responseType = "code"; - String[] scope = new String[]{"identify", "guilds.join", "email"}; + String[] scope = new String[]{"identify", "guilds", "guilds.members.read", "email"}; String url = String.format("%s?response_type=%s&client_id=%s&scope=%s&state=%s&redirect_uri=%s&prompt=consent", module.config.discordAuthorizeUrl, responseType, module.config.clientId, String.join("%20", scope), state, module.config.redirectUrl); - return List.of(new AuthWebViewDetails(url, "https://google.com", true, true)); + return List.of(new AuthWebViewDetails(url, "", true, true)); } private DiscordUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException { @@ -476,6 +472,7 @@ public UserHardware createHardwareInfo(HardwareReportRequest.HardwareInfo hardwa s.executeUpdate(); try (ResultSet generatedKeys = s.getGeneratedKeys()) { if (generatedKeys.next()) { + //writeHwidLog(connection, generatedKeys.getLong(1), publicKey); long id = generatedKeys.getLong(1); return new DiscordUserHardware(hardwareInfo, publicKey, id, false); } From d6e09b504be9415336de786acdc53a38279d2a62 Mon Sep 17 00:00:00 2001 From: Meido Date: Sun, 26 Jun 2022 11:01:24 +0700 Subject: [PATCH 4/6] =?UTF-8?q?dosc:=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=D1=8F=20runtime=20=D0=B2=20=D0=BF=D0=B0=D1=82=D1=87=20=D0=B8?= =?UTF-8?q?=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DiscordAuthSystem_module/README.md | 133 +++++------------- .../patch/DiscordAuthSystemRuntime.patch | 120 ++++++++++++++++ 2 files changed, 152 insertions(+), 101 deletions(-) create mode 100644 DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch diff --git a/DiscordAuthSystem_module/README.md b/DiscordAuthSystem_module/README.md index 17a191ad..311f7e6b 100644 --- a/DiscordAuthSystem_module/README.md +++ b/DiscordAuthSystem_module/README.md @@ -6,11 +6,17 @@ #### Установка модуля 1. Скопировать модуль **DiscordAuthSystem_module.jar** в папку **/LaunchServer/modules/** -2. Создать приложение в панели управления разработчика https://discord.com/developers/applications и скопировать его id, и секретный токен. -3. В настройках приложение discord oauth добавить redirect_url. Он должен состоять из пути до webapi + /auth/discord. Пример: http://127.0.0.1:9274/webapi/auth/discord -4. Настроить конфигурацию модуля -5. Добавить авторизацию в LaunchServer -6. [Опционально] Обновить Runtime +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 #### Конфигурация модуля @@ -21,7 +27,20 @@ "redirectUrl": "это редирект, который вы указали", "discordAuthorizeUrl": "https://discord.com/oauth2/authorize", "discordApiEndpointVersion": "https://discord.com/api/v10", - "discordApiEndpoint": "https://discord.com/api" + "discordApiEndpoint": "https://discord.com/api", + "guildIdsJoined": [ + { + "id": "id гильдии №1", + "name": "наименование гильдии", + "url": "ссылка для входа" + }, + { + "id": "id гильдии №2", + "name": "наименование гильдии", + "url": "ссылка для входа" + }, + ], + "guildIdGetNick": "id гильдии с которой будет браться ник. если не надо, то осталить пустым" } ``` @@ -112,105 +131,17 @@ ALTER TABLE `users` ADD CONSTRAINT `users_hwidfk` FOREIGN KEY (`hwidId`) REFERENCES `hwids` (`id`); ``` -## [Опционально] Обновить Runtime +#### [Опционально] Обновить Runtime Если вы хотите, чтобы окно открывалось в браузере, а также авторизация у -пользователя сохранялась, то необходимо будет отредактировать и пересобрать runtime. +пользователя сохранялась, то необходимо будет отредактировать (пропатчить) и пересобрать runtime. Модуль будет работать и без этого, но не так красиво. -#### Изменение для открытия в окне браузера авторизации Дискорда (а не webview) - -```java -// /srcRuntime/src/main/java/pro/gravit/launcher/client/gui/scenes/login/methods/WebAuthMethod - -// Эти библиотеки нужно добавить -import java.awt.Desktop; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; - -// А этот модуль переписать -@Override -public CompletableFuture auth(AuthWebViewDetails details) { - overlay.future = new CompletableFuture<>(); - if (details.onlyBrowser) { - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - try { - Desktop.getDesktop().browse(new URI(details.url)); - } catch (IOException | URISyntaxException e) { - e.printStackTrace(); - } - } - 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; -} -``` - -#### Изменение для сохранения состояния авторизации - -```java -// /srcRuntime/src/main/java/pro/gravit/launcher/client/gui/impl/GuiEventHandler - -// Эти библиотеки нужно добавить -import pro.gravit.launcher.events.request.AdditionalDataRequestEvent; -import java.util.Map; - -// А этот модуль переписать -@Override -public boolean eventHandle(T event) { - LogHelper.dev("Processing event %s", event.getType()); - if (event instanceof RequestEvent) { - if (!((RequestEvent) event).requestUUID.equals(RequestEvent.eventUUID)) - return false; - } - try { - if (event instanceof AuthRequestEvent) { - boolean isNextScene = application.getCurrentScene() instanceof LoginScene; - ((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) - ((LoginScene) application.getCurrentScene()).onGetProfiles(); - } - if (event instanceof AdditionalDataRequestEvent) { - AdditionalDataRequestEvent dataRequest = (AdditionalDataRequestEvent) event; - Map data = dataRequest.data; - - String type = data.get("type"); - - if (type != null && type.equals("ChangeRuntimeSettings")) { - application.runtimeSettings.login = data.get("login"); - application.runtimeSettings.oauthAccessToken = data.get("oauthAccessToken"); - application.runtimeSettings.oauthRefreshToken = data.get("oauthRefreshToken"); - application.runtimeSettings.oauthExpire = System.currentTimeMillis() + Integer.parseInt(data.get("oauthExpire")); - application.runtimeSettings.lastAuth = ((LoginScene) application.getCurrentScene()).getAuthAvailability(); - } - } - } catch (Throwable e) { - LogHelper.error(e); - } - return false; -} -``` - -```java -// /srcRuntime/src/main/java/pro/gravit/launcher/client/gui/scenes/login/LoginScene - -// Просто добавить модуль -public GetAvailabilityAuthRequestEvent.AuthAvailability getAuthAvailability() { - return this.authAvailability; -} +```shell +cd ./src/srcRuntime +git am DiscordAuthSystemRuntime.patch #Надеюсь не нужно объяснять, +# что тут нужен путь до файла DiscordAuthSystemRuntime.patch +gradlew build ``` Если вам впадлу делать все эти изменения, то я приложил готовы билд рантайма. Он лежит рядом с билдом модуля. \ No newline at end of file diff --git a/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch b/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch new file mode 100644 index 00000000..e8702ba0 --- /dev/null +++ b/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch @@ -0,0 +1,120 @@ +From 14be66edc2e037b803c4688100021e28212c88c6 Mon Sep 17 00:00:00 2001 +From: Meido +Date: Sun, 26 Jun 2022 03:03:55 +0700 +Subject: [PATCH] feat: DiscordAuthSystem_module + +--- + .../client/gui/impl/GuiEventHandler.java | 17 ++++++++++ + .../client/gui/scenes/login/LoginScene.java | 4 +++ + .../scenes/login/methods/WebAuthMethod.java | 31 ++++++++++++++----- + 3 files changed, 44 insertions(+), 8 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..5dd3e8c 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; + +@@ -32,6 +35,20 @@ public class GuiEventHandler implements RequestService.EventHandler { + if (isNextScene && ((LoginScene) application.getCurrentScene()).isLoginStarted) + ((LoginScene) application.getCurrentScene()).onGetProfiles(); + } ++ if (event instanceof AdditionalDataRequestEvent) { ++ AdditionalDataRequestEvent dataRequest = (AdditionalDataRequestEvent) event; ++ Map data = dataRequest.data; ++ ++ String type = data.get("type"); ++ ++ if (type != null && type.equals("ChangeRuntimeSettings")) { ++ application.runtimeSettings.login = data.get("login"); ++ application.runtimeSettings.oauthAccessToken = data.get("oauthAccessToken"); ++ application.runtimeSettings.oauthRefreshToken = data.get("oauthRefreshToken"); ++ application.runtimeSettings.oauthExpire = System.currentTimeMillis() + Integer.parseInt(data.get("oauthExpire")); ++ 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..0b9aa9d 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,10 @@ public class LoginScene extends AbstractScene { + LogHelper.info("Selected auth: %s", authAvailability.name); + } + ++ public GetAvailabilityAuthRequestEvent.AuthAvailability getAuthAvailability() { ++ return this.authAvailability; ++ } ++ + 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 { + @Override + public CompletableFuture 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)) { ++ 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 + From 3fcfa9bea94c02f4f5e1feed81fc62370906641e Mon Sep 17 00:00:00 2001 From: Meido Date: Mon, 27 Jun 2022 23:17:57 +0700 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=D0=B4=D0=BD=D0=B5=D0=B9=20=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D1=81=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DiscordAuthSystem_module/README.md | 5 +- DiscordAuthSystem_module/build.gradle | 2 + .../patch/DiscordAuthSystemRuntime.patch | 58 +++++++++++-------- .../discordauthsystem/Config.java | 5 +- .../discordauthsystem/ModuleImpl.java | 6 +- .../discordauthsystem/WebApi.java | 32 +++++----- .../DiscordSystemAuthCoreProvider.java | 57 ++++++++---------- .../responses/ExitResponse.java | 36 ++++++++++++ 8 files changed, 116 insertions(+), 85 deletions(-) create mode 100644 DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/responses/ExitResponse.java diff --git a/DiscordAuthSystem_module/README.md b/DiscordAuthSystem_module/README.md index 311f7e6b..87187421 100644 --- a/DiscordAuthSystem_module/README.md +++ b/DiscordAuthSystem_module/README.md @@ -38,9 +38,10 @@ "id": "id гильдии №2", "name": "наименование гильдии", "url": "ссылка для входа" - }, + } ], - "guildIdGetNick": "id гильдии с которой будет браться ник. если не надо, то осталить пустым" + "guildIdGetNick": "id гильдии с которой будет браться ник. если не надо, то оставить пустым", + "usernameRegex": "regex для валидации ника (если не нужно, то оставьте пустым)" } ``` diff --git a/DiscordAuthSystem_module/build.gradle b/DiscordAuthSystem_module/build.gradle index 8085b1dc..455bc585 100644 --- a/DiscordAuthSystem_module/build.gradle +++ b/DiscordAuthSystem_module/build.gradle @@ -6,6 +6,8 @@ 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 { diff --git a/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch b/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch index e8702ba0..55b55ffe 100644 --- a/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch +++ b/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch @@ -1,16 +1,16 @@ -From 14be66edc2e037b803c4688100021e28212c88c6 Mon Sep 17 00:00:00 2001 +From 2dff3aada7bd37142c77a7e78c92c18e45f011c5 Mon Sep 17 00:00:00 2001 From: Meido -Date: Sun, 26 Jun 2022 03:03:55 +0700 -Subject: [PATCH] feat: DiscordAuthSystem_module +Date: Sun, 26 Jun 2022 15:13:30 +0700 +Subject: [PATCH] DiscordAuthSystemRuntime --- - .../client/gui/impl/GuiEventHandler.java | 17 ++++++++++ - .../client/gui/scenes/login/LoginScene.java | 4 +++ + .../client/gui/impl/GuiEventHandler.java | 18 +++++++++-- + .../client/gui/scenes/login/LoginScene.java | 8 +++++ .../scenes/login/methods/WebAuthMethod.java | 31 ++++++++++++++----- - 3 files changed, 44 insertions(+), 8 deletions(-) + 3 files changed, 47 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..5dd3e8c 100644 +index c817abe..038886e 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; @@ -29,38 +29,46 @@ index c817abe..5dd3e8c 100644 public class GuiEventHandler implements RequestService.EventHandler { private final JavaFXApplication application; -@@ -32,6 +35,20 @@ public class GuiEventHandler implements RequestService.EventHandler { - if (isNextScene && ((LoginScene) application.getCurrentScene()).isLoginStarted) +@@ -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 (event instanceof AdditionalDataRequestEvent) { -+ AdditionalDataRequestEvent dataRequest = (AdditionalDataRequestEvent) event; -+ Map data = dataRequest.data; -+ -+ String type = data.get("type"); + -+ if (type != null && type.equals("ChangeRuntimeSettings")) { -+ application.runtimeSettings.login = data.get("login"); -+ application.runtimeSettings.oauthAccessToken = data.get("oauthAccessToken"); -+ application.runtimeSettings.oauthRefreshToken = data.get("oauthRefreshToken"); -+ application.runtimeSettings.oauthExpire = System.currentTimeMillis() + Integer.parseInt(data.get("oauthExpire")); -+ application.runtimeSettings.lastAuth = ((LoginScene) application.getCurrentScene()).getAuthAvailability(); ++ if (((LoginScene) application.getCurrentScene()).getSavePasswordCheckBox().isSelected()) { ++ 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..0b9aa9d 100644 +index 80cf2ed..b20984e 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,10 @@ public class LoginScene extends AbstractScene { +@@ -146,6 +146,14 @@ public class LoginScene extends AbstractScene { LogHelper.info("Selected auth: %s", authAvailability.name); } + public GetAvailabilityAuthRequestEvent.AuthAvailability getAuthAvailability() { + return this.authAvailability; + } ++ ++ public CheckBox getSavePasswordCheckBox() { ++ return this.savePasswordCheckBox; ++ } + public void addAuthAvailability(GetAvailabilityAuthRequestEvent.AuthAvailability authAvailability) { RadioButton radio = new RadioButton(); diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java index ccf3f3b6..88627387 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/Config.java @@ -10,12 +10,9 @@ public class Config { 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 guildIdsJoined = new ArrayList<>(); - public String guildIdGetNick = ""; - - public int usernameLimit = 32; + public String usernameRegex = ""; public static class DiscordGuild { public String id; diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java index 585805ea..efa140f6 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/ModuleImpl.java @@ -1,6 +1,5 @@ package pro.gravit.launchermodules.discordauthsystem; -import com.google.gson.Gson; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import pro.gravit.launcher.config.JsonConfigurable; @@ -10,10 +9,11 @@ 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.modules.impl.LaunchServerInitContext; +import pro.gravit.launchserver.socket.WebSocketService; import pro.gravit.launchserver.socket.handlers.NettyWebAPIHandler; import pro.gravit.utils.Version; import pro.gravit.utils.helper.LogHelper; @@ -34,6 +34,8 @@ public ModuleImpl() { 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; } } diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java index d501d034..285c194a 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java @@ -1,5 +1,6 @@ package pro.gravit.launchermodules.discordauthsystem; +import com.github.slugify.Slugify; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; @@ -38,6 +39,7 @@ public class WebApi implements NettyWebAPIHandler.SimpleSeverletHandler { private final ModuleImpl module; private final LaunchServer server; private transient final Logger logger = LogManager.getLogger(); + private final Slugify slg = Slugify.builder().underscoreSeparator(true).lowerCase(false).transliterator(true).build(); public WebApi(ModuleImpl module, LaunchServer server) { this.module = module; @@ -127,20 +129,22 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC DiscordSystemAuthCoreProvider.DiscordUser user = core.getUserByDiscordId(response.user.id); if (user == null) { - String username; + String username = response.user.username; if (module.config.guildIdGetNick.length() > 0) { try { var member = DiscordApi.getUserGuildMember(accessTokenResponse.access_token, module.config.guildIdGetNick); - username = member.nick; + if (member.nick != null) { + username = member.nick; + } } catch (Exception e) { logger.error("DiscordApi.getUserGuildMember: " + e); sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR, "An unexpected error occurred!")); return; } - } else { - username = toSlug(response.user.username); } + username = slg.slugify(username); + var usernameLength = username.length(); if (usernameLength == 0) { @@ -148,8 +152,11 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC return; } - if (usernameLength > module.config.usernameLimit) { - username = username.substring(0, usernameLength-1); + if (module.config.usernameRegex.length() > 0) { + if (!username.matches(module.config.usernameRegex)) { + sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.NOT_ACCEPTABLE, "Your nickname does not meet the requirements. Please change it.")); + return; + } } if (core.getUserByUsername(username) != null) { @@ -204,19 +211,6 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC request.requestUUID = RequestEvent.eventUUID; server.nettyServerSocketHandler.nettyServer.service.sendObject(ch, request); - - Map data = new HashMap(); - - data.put("type", "ChangeRuntimeSettings"); - data.put("login", finalUser.getUsername()); - data.put("oauthAccessToken", finalUser.getAccessToken()); - data.put("oauthRefreshToken", finalUser.getRefreshToken()); - data.put("oauthExpire", finalUser.getExpiresIn().toString()); - - AdditionalDataRequestEvent dataRequestEvent = new AdditionalDataRequestEvent(data); - dataRequestEvent.requestUUID = RequestEvent.eventUUID; - - server.nettyServerSocketHandler.nettyServer.service.sendObject(ch, dataRequestEvent); }); sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.OK, "You are successfully authorized! Please return to the launcher.")); } diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java index aa0b6a8b..b89e37d3 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java @@ -127,50 +127,41 @@ public DiscordUser getDiscordUserByUsername(String username) { } } - public DiscordUser updateDataUser(String discordId, String accessToken, String refreshToken, Long expiresIn) { + public DiscordUser updateDataUser(String discordId, String accessToken) { try (Connection connection = mySQLHolder.getConnection()) { - return updateDataUser(connection, discordId, accessToken, refreshToken, expiresIn); + return updateDataUser(connection, discordId, accessToken); } catch (SQLException e) { logger.error("updateDataUser SQL error", e); return null; } } - private DiscordUser updateDataUser(Connection connection, String discordId, String accessToken, String refreshToken, Long expiresIn) throws SQLException { - - ArrayList setList = new ArrayList(); - - if (accessToken != null) { - if (accessToken.length() == 0) { - setList.add(accessTokenColumn + " = " + null); - } else { - setList.add(accessTokenColumn + " = '" + accessToken + "'"); - } + public DiscordUser updateDataUser(String discordId, String accessToken, String refreshToken, Long expiresIn) { + try (Connection connection = mySQLHolder.getConnection()) { + return updateDataUser(connection, discordId, accessToken, refreshToken, expiresIn); + } catch (SQLException e) { + logger.error("updateDataUser SQL error", e); + return null; } + } - if (refreshToken != null) { - if (refreshToken.length() == 0) { - setList.add(refreshTokenColumn + " = " + null); - } else { - setList.add(refreshTokenColumn + " = '" + refreshToken + "'"); - } - } + private DiscordUser updateDataUser(Connection connection, String discordId, String accessToken) throws SQLException { + String sql = String.format("UPDATE %s SET %s=? WHERE %s = %s", table, accessTokenColumn, discordIdColumn, discordId); + PreparedStatement s = connection.prepareStatement(sql); + s.setString(1, accessToken); + s.executeUpdate(); - if (expiresIn != null) { - if (expiresIn == 0) { - setList.add(expiresInColumn + " = " + null); - } else { - setList.add(expiresInColumn + " = " + expiresIn); - } - } + return getUserByDiscordId(discordId); + } - String sqlSet = String.join(", ", setList); + private DiscordUser updateDataUser(Connection connection, String discordId, String accessToken, String refreshToken, Long expiresIn) throws SQLException { - if (sqlSet.length() != 0) { - String sql = String.format("UPDATE %s SET %s WHERE %s = %s", table, sqlSet, discordIdColumn, discordId); - PreparedStatement s = connection.prepareStatement(sql); - s.executeUpdate(); - } + String sql = String.format("UPDATE %s SET %s=?, %s=?, %s=? WHERE %s = %s", table, accessTokenColumn, refreshTokenColumn, expiresInColumn, discordIdColumn, discordId); + PreparedStatement s = connection.prepareStatement(sql); + s.setString(1, accessToken); + s.setString(2, refreshToken); + s.setLong(3, expiresIn); + s.executeUpdate(); return getUserByDiscordId(discordId); } @@ -331,7 +322,7 @@ public boolean exitUser(User user) { if (discordUser == null) { return true; } - return updateDataUser(discordUser.getDiscordId(), "", null, null) != null; + return updateDataUser(discordUser.getDiscordId(), null) != null; } private void setUserHardwareId(Connection connection, String discordId, long hwidId) throws SQLException { diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/responses/ExitResponse.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/responses/ExitResponse.java new file mode 100644 index 00000000..1bf81899 --- /dev/null +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/responses/ExitResponse.java @@ -0,0 +1,36 @@ +package pro.gravit.launchermodules.discordauthsystem.responses; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pro.gravit.launcher.events.RequestEvent; +import pro.gravit.launcher.events.request.ExitRequestEvent; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.socket.Client; +import pro.gravit.launchserver.socket.handlers.WebSocketFrameHandler; + +public class ExitResponse extends pro.gravit.launchserver.socket.response.auth.ExitResponse { + + public static void exit(LaunchServer server, WebSocketFrameHandler wsHandler, Channel channel, ExitRequestEvent.ExitReason reason) { + Client chClient = wsHandler.getClient(); + Client newCusClient = new Client(); + newCusClient.setProperty("state", chClient.getProperty("state")); + newCusClient.checkSign = chClient.checkSign; + wsHandler.setClient(newCusClient); + ExitRequestEvent event = new ExitRequestEvent(reason); + event.requestUUID = RequestEvent.eventUUID; + wsHandler.service.sendObject(channel, event); + } + + @Override + public void execute(ChannelHandlerContext ctx, Client client) { + var state = client.getProperty("state"); + super.execute(ctx, client); + if (username == null) { + WebSocketFrameHandler handler = ctx.pipeline().get(WebSocketFrameHandler.class); + handler.getClient().setProperty("state", state); + } + } + +} From b788bc2e1548531ce85a4e6084f39164239fdbf7 Mon Sep 17 00:00:00 2001 From: Meido Date: Wed, 29 Jun 2022 12:28:22 +0700 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D1=87=D0=BD=D1=83?= =?UTF-8?q?=D1=8E=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20authorize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DiscordAuthSystem_module/README.md | 2 +- .../patch/DiscordAuthSystemRuntime.patch | 21 ++-- .../discordauthsystem/WebApi.java | 106 ++--------------- .../DiscordSystemAuthCoreProvider.java | 108 ++++++++++++++++-- 4 files changed, 123 insertions(+), 114 deletions(-) diff --git a/DiscordAuthSystem_module/README.md b/DiscordAuthSystem_module/README.md index 87187421..62835cc3 100644 --- a/DiscordAuthSystem_module/README.md +++ b/DiscordAuthSystem_module/README.md @@ -41,7 +41,7 @@ } ], "guildIdGetNick": "id гильдии с которой будет браться ник. если не надо, то оставить пустым", - "usernameRegex": "regex для валидации ника (если не нужно, то оставьте пустым)" + "usernameRegex": "regex для валидации ника (если не нужно, то оставьте пустым)" } ``` diff --git a/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch b/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch index 55b55ffe..587b4165 100644 --- a/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch +++ b/DiscordAuthSystem_module/patch/DiscordAuthSystemRuntime.patch @@ -1,16 +1,16 @@ -From 2dff3aada7bd37142c77a7e78c92c18e45f011c5 Mon Sep 17 00:00:00 2001 +From ddeb1507e0a6e74737877906db56c62769dd9b2b Mon Sep 17 00:00:00 2001 From: Meido -Date: Sun, 26 Jun 2022 15:13:30 +0700 +Date: Wed, 29 Jun 2022 12:17:57 +0700 Subject: [PATCH] DiscordAuthSystemRuntime --- .../client/gui/impl/GuiEventHandler.java | 18 +++++++++-- - .../client/gui/scenes/login/LoginScene.java | 8 +++++ + .../client/gui/scenes/login/LoginScene.java | 9 ++++++ .../scenes/login/methods/WebAuthMethod.java | 31 ++++++++++++++----- - 3 files changed, 47 insertions(+), 10 deletions(-) + 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..038886e 100644 +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; @@ -42,7 +42,7 @@ index c817abe..038886e 100644 + if (isNextScene && ((LoginScene) application.getCurrentScene()).isLoginStarted) { ((LoginScene) application.getCurrentScene()).onGetProfiles(); + -+ if (((LoginScene) application.getCurrentScene()).getSavePasswordCheckBox().isSelected()) { ++ if (((LoginScene) application.getCurrentScene()).getSavePasswordCheckBoxSelected()) { + application.runtimeSettings.login = rawAuthResult.playerProfile.username; + application.runtimeSettings.oauthAccessToken = rawAuthResult.oauth.accessToken; + application.runtimeSettings.oauthRefreshToken = rawAuthResult.oauth.refreshToken; @@ -55,10 +55,10 @@ index c817abe..038886e 100644 } 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..b20984e 100644 +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,14 @@ public class LoginScene extends AbstractScene { +@@ -146,6 +146,15 @@ public class LoginScene extends AbstractScene { LogHelper.info("Selected auth: %s", authAvailability.name); } @@ -66,8 +66,9 @@ index 80cf2ed..b20984e 100644 + return this.authAvailability; + } + -+ public CheckBox getSavePasswordCheckBox() { -+ return this.savePasswordCheckBox; ++ public boolean getSavePasswordCheckBoxSelected() { ++ CheckBox checkBox = this.savePasswordCheckBox; ++ return checkBox != null && checkBox.isSelected(); + } + public void addAuthAvailability(GetAvailabilityAuthRequestEvent.AuthAvailability authAvailability) { diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java index 285c194a..c06028f5 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/WebApi.java @@ -13,9 +13,11 @@ import pro.gravit.launcher.events.request.AdditionalDataRequestEvent; import pro.gravit.launcher.events.request.AuthRequestEvent; import pro.gravit.launcher.profiles.PlayerProfile; +import pro.gravit.launcher.request.auth.password.AuthCodePassword; import pro.gravit.launchermodules.discordauthsystem.providers.DiscordApi; import pro.gravit.launchermodules.discordauthsystem.providers.DiscordSystemAuthCoreProvider; import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.AuthException; import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.manangers.AuthManager; import pro.gravit.launchserver.socket.Client; @@ -92,105 +94,21 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC return; } - DiscordApi.DiscordAccessTokenResponse accessTokenResponse = null; - - try { - accessTokenResponse = DiscordApi.getAccessTokenByCode(code); - } catch (Exception e) { - sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.FORBIDDEN, "Discord authorization denied your code.")); - return; - } - - var response = DiscordApi.getDiscordUserByAccessToken(accessTokenResponse.access_token); - - if (!module.config.guildIdsJoined.isEmpty()) { - var guilds = DiscordApi.getUserGuilds(accessTokenResponse.access_token); - - var needGuilds = module.config.guildIdsJoined; - - for (var guild : guilds) { - needGuilds.removeIf(g -> Objects.equals(g.id, guild.id)); - } - - if (!needGuilds.isEmpty()) { - String body = "To enter the server you must be a member of these guilds: "; - List guildData = new ArrayList<>(); - for (var g : needGuilds) { - guildData.add("" + g.name + ""); - } - sendHttpResponse(ctx, simpleHtmlResponse(HttpResponseStatus.FORBIDDEN, body + String.join(", ", guildData))); - return; - } - } - AuthProviderPair pair = server.config.getAuthProviderPair(); - DiscordSystemAuthCoreProvider core = (DiscordSystemAuthCoreProvider) pair.core; - - DiscordSystemAuthCoreProvider.DiscordUser user = core.getUserByDiscordId(response.user.id); - - if (user == null) { - String username = response.user.username; - if (module.config.guildIdGetNick.length() > 0) { - try { - var member = DiscordApi.getUserGuildMember(accessTokenResponse.access_token, module.config.guildIdGetNick); - if (member.nick != null) { - username = member.nick; - } - } catch (Exception e) { - logger.error("DiscordApi.getUserGuildMember: " + e); - sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR, "An unexpected error occurred!")); - return; - } - } + AuthManager.AuthReport report; - username = slg.slugify(username); - - var usernameLength = username.length(); - - if (usernameLength == 0) { - sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.NOT_ACCEPTABLE, "Your nickname does not meet the requirements. Please change it.")); - return; - } - - if (module.config.usernameRegex.length() > 0) { - if (!username.matches(module.config.usernameRegex)) { - sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.NOT_ACCEPTABLE, "Your nickname does not meet the requirements. Please change it.")); - return; - } - } - - if (core.getUserByUsername(username) != null) { - username = username.substring(0, usernameLength-1-response.user.discriminator.length()); - username += "_" + response.user.discriminator; - } - - user = core.createUser( - state, - username, - accessTokenResponse.access_token, - accessTokenResponse.refresh_token, - accessTokenResponse.expires_in * 1000, - response.user.id - ); - } else { - user = core.updateDataUser(response.user.id, accessTokenResponse.access_token, accessTokenResponse.refresh_token, accessTokenResponse.expires_in * 1000); - } - - if (user.isBanned()) { - sendHttpResponse(ctx, simpleResponse(HttpResponseStatus.FORBIDDEN, "You have been banned!")); + try { + report = pair.core.authorize("", null, new AuthCodePassword(code), true); + } catch (AuthException e) { + sendHttpResponse(ctx, simpleHtmlResponse(HttpResponseStatus.FORBIDDEN, e.getMessage())); return; } + String minecraftAccessToken = report.minecraftAccessToken(); + AuthRequestEvent.OAuthRequestEvent oauth = new AuthRequestEvent.OAuthRequestEvent(report.oauthAccessToken(), report.oauthRefreshToken(), report.oauthExpire()); + DiscordSystemAuthCoreProvider.DiscordUser user = (DiscordSystemAuthCoreProvider.DiscordUser) report.session().getUser(); - String minecraftAccessToken; - AuthRequestEvent.OAuthRequestEvent oauth; - - AuthManager.AuthReport report = pair.core.authorize(user.getUsername(), null, null, true); - minecraftAccessToken = report.minecraftAccessToken(); - oauth = new AuthRequestEvent.OAuthRequestEvent(report.oauthAccessToken(), report.oauthRefreshToken(), report.oauthExpire()); - - DiscordSystemAuthCoreProvider.DiscordUser finalUser = user; server.nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, ws) -> { Client client = ws.getClient(); @@ -203,9 +121,9 @@ public void handle(ChannelHandlerContext ctx, FullHttpRequest msg, NettyConnectC return; } - client.coreObject = finalUser; + client.coreObject = user; client.sessionObject = report.session(); - server.authManager.internalAuth(client, AuthResponse.ConnectTypes.CLIENT, pair, finalUser.getUsername(), finalUser.getUUID(), ClientPermissions.DEFAULT, true); + server.authManager.internalAuth(client, AuthResponse.ConnectTypes.CLIENT, pair, user.getUsername(), user.getUUID(), ClientPermissions.DEFAULT, true); PlayerProfile playerProfile = server.authManager.getPlayerProfile(client); AuthRequestEvent request = new AuthRequestEvent(ClientPermissions.DEFAULT, playerProfile, minecraftAccessToken, null, null, oauth); request.requestUUID = RequestEvent.eventUUID; diff --git a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java index b89e37d3..bdd1feb5 100644 --- a/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java +++ b/DiscordAuthSystem_module/src/main/java/pro/gravit/launchermodules/discordauthsystem/providers/DiscordSystemAuthCoreProvider.java @@ -1,15 +1,19 @@ package pro.gravit.launchermodules.discordauthsystem.providers; +import com.github.slugify.Slugify; +import io.netty.handler.codec.http.HttpResponseStatus; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import pro.gravit.launcher.ClientPermissions; import pro.gravit.launcher.events.request.GetAvailabilityAuthRequestEvent; import pro.gravit.launcher.request.auth.AuthRequest; import pro.gravit.launcher.request.auth.details.AuthWebViewDetails; +import pro.gravit.launcher.request.auth.password.AuthCodePassword; import pro.gravit.launcher.request.secure.HardwareReportRequest; import pro.gravit.launchermodules.discordauthsystem.ModuleImpl; import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.auth.AuthException; +import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.MySQLSourceConfig; import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.core.User; @@ -31,6 +35,7 @@ public class DiscordSystemAuthCoreProvider extends AuthCoreProvider implements AuthSupportExit, AuthSupportHardware { private final transient Logger logger = LogManager.getLogger(); + private final transient Slugify slg = Slugify.builder().underscoreSeparator(true).lowerCase(false).transliterator(true).build(); public MySQLSourceConfig mySQLHolder; public double criticalCompareLevel = 1.0; public String uuidColumn; @@ -146,9 +151,10 @@ public DiscordUser updateDataUser(String discordId, String accessToken, String r } private DiscordUser updateDataUser(Connection connection, String discordId, String accessToken) throws SQLException { - String sql = String.format("UPDATE %s SET %s=? WHERE %s = %s", table, accessTokenColumn, discordIdColumn, discordId); + String sql = String.format("UPDATE %s SET %s=? WHERE %s=?", table, accessTokenColumn, discordIdColumn); PreparedStatement s = connection.prepareStatement(sql); s.setString(1, accessToken); + s.setString(2, discordId); s.executeUpdate(); return getUserByDiscordId(discordId); @@ -156,11 +162,12 @@ private DiscordUser updateDataUser(Connection connection, String discordId, Stri private DiscordUser updateDataUser(Connection connection, String discordId, String accessToken, String refreshToken, Long expiresIn) throws SQLException { - String sql = String.format("UPDATE %s SET %s=?, %s=?, %s=? WHERE %s = %s", table, accessTokenColumn, refreshTokenColumn, expiresInColumn, discordIdColumn, discordId); + String sql = String.format("UPDATE %s SET %s=?, %s=?, %s=? WHERE %s=?", table, accessTokenColumn, refreshTokenColumn, expiresInColumn, discordIdColumn); PreparedStatement s = connection.prepareStatement(sql); s.setString(1, accessToken); s.setString(2, refreshToken); s.setLong(3, expiresIn); + s.setString(4, discordId); s.executeUpdate(); return getUserByDiscordId(discordId); @@ -273,18 +280,101 @@ public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthRespon @Override public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws AuthException { - if (login == null) { - throw AuthException.userNotFound(); + + DiscordApi.DiscordAccessTokenResponse accessTokenResponse; + + AuthCodePassword codePassword = (AuthCodePassword) password; + var code = codePassword.code; + + try { + accessTokenResponse = DiscordApi.getAccessTokenByCode(code); + } catch (Exception e) { + throw new AuthException("Discord authorization denied your code."); + } + + DiscordApi.OauthMeResponse response; + + try{ + response = DiscordApi.getDiscordUserByAccessToken(accessTokenResponse.access_token); + } catch (IOException e) { + throw new AuthException("Discord authorization denied your access_token."); + } + + if (!module.config.guildIdsJoined.isEmpty()) { + + List guilds; + + try { + guilds = DiscordApi.getUserGuilds(accessTokenResponse.access_token); + } catch (IOException e) { + throw new AuthException("Error getting user guilds."); + } + + + var needGuilds = module.config.guildIdsJoined; + + for (var guild : guilds) { + needGuilds.removeIf(g -> Objects.equals(g.id, guild.id)); + } + + if (!needGuilds.isEmpty()) { + String body = "To enter the server you must be a member of these guilds: "; + List guildData = new ArrayList<>(); + for (var g : needGuilds) { + guildData.add("" + g.name + ""); + } + throw new AuthException(body + String.join(", ", guildData)); + } } - DiscordUser user = getDiscordUserByUsername(login); + DiscordSystemAuthCoreProvider.DiscordUser user = getUserByDiscordId(response.user.id); if (user == null) { - return null; + String username = response.user.username; + if (module.config.guildIdGetNick.length() > 0) { + try { + var member = DiscordApi.getUserGuildMember(accessTokenResponse.access_token, module.config.guildIdGetNick); + if (member.nick != null) { + username = member.nick; + } + } catch (IOException e) { + throw new AuthException("An unexpected error occurred!"); + } + } + + username = slg.slugify(username); + + var usernameLength = username.length(); + + if (usernameLength == 0) { + throw new AuthException("Your nickname does not meet the requirements. Please change it."); + } + + if (module.config.usernameRegex.length() > 0) { + if (!username.matches(module.config.usernameRegex)) { + throw new AuthException("Your nickname does not meet the requirements. Please change it."); + } + } + + if (getUserByUsername(username) != null) { + username = username.substring(0, usernameLength-1-response.user.discriminator.length()); + username += "_" + response.user.discriminator; + } + + user = createUser( + UUID.randomUUID().toString(), + username, + accessTokenResponse.access_token, + accessTokenResponse.refresh_token, + accessTokenResponse.expires_in * 1000, + response.user.id + ); + } else { + user = updateDataUser(response.user.id, accessTokenResponse.access_token, accessTokenResponse.refresh_token, accessTokenResponse.expires_in * 1000); } - if (user.accessToken == null) { - return null; + if (user.isBanned()) { + throw new AuthException("You have been banned!"); } DiscordUserSession session = new DiscordUserSession(user, user.accessToken); @@ -368,7 +458,7 @@ public List getDetails( String responseType = "code"; String[] scope = new String[]{"identify", "guilds", "guilds.members.read", "email"}; String url = String.format("%s?response_type=%s&client_id=%s&scope=%s&state=%s&redirect_uri=%s&prompt=consent", module.config.discordAuthorizeUrl, responseType, module.config.clientId, String.join("%20", scope), state, module.config.redirectUrl); - return List.of(new AuthWebViewDetails(url, "", true, true)); + return List.of(new AuthWebViewDetails(url, module.config.redirectUrl, true, true)); } private DiscordUserHardware fetchHardwareInfo(ResultSet set) throws SQLException, IOException {