mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-12-22 16:41:46 +03:00
[FEATURE] Возможность использования кастомных параметров в AuthProvider(OAuth, 2FA и пр)
This commit is contained in:
parent
de144d90e0
commit
1b5fb36b0a
17 changed files with 112 additions and 43 deletions
|
@ -1,11 +1,12 @@
|
|||
package pro.gravit.launchserver.auth.provider;
|
||||
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
public final class AcceptAuthProvider extends AuthProvider {
|
||||
|
||||
@Override
|
||||
public AuthProviderResult auth(String login, String password, String ip) {
|
||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) {
|
||||
return new AuthProviderResult(login, SecurityHelper.randomStringToken(), srv); // Same as login
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.utils.ProviderMap;
|
||||
|
@ -39,9 +40,9 @@ public static void registerProviders() {
|
|||
* Throws an exception {@link AuthException} {@link pro.gravit.utils.HookException} if the verification script returned a meaningful error
|
||||
* In other cases, throwing an exception indicates a serious error
|
||||
*/
|
||||
public abstract AuthProviderResult auth(String login, String password, String ip) throws Exception;
|
||||
public abstract AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws Exception;
|
||||
|
||||
public void preAuth(String login, String password, String customText, String ip) {
|
||||
public void preAuth(String login, AuthRequest.AuthPasswordInterface password, String customText, String ip) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.launchserver.dao.User;
|
||||
import pro.gravit.launchserver.manangers.hook.AuthHookManager;
|
||||
|
@ -10,21 +12,22 @@
|
|||
public class HibernateAuthProvider extends AuthProvider {
|
||||
public boolean autoReg;
|
||||
@Override
|
||||
public AuthProviderResult auth(String login, String password, String ip) throws Exception {
|
||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws Exception {
|
||||
if(!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
||||
User user = srv.config.dao.userService.findUserByUsername(login);
|
||||
if(user == null && autoReg)
|
||||
{
|
||||
AuthHookManager.RegContext context = new AuthHookManager.RegContext(login, password, ip, false);
|
||||
AuthHookManager.RegContext context = new AuthHookManager.RegContext(login, ((AuthPlainPassword) password).password, ip, false);
|
||||
if(srv.authHookManager.registraion.hook(context))
|
||||
{
|
||||
user = srv.config.dao.userService.registerNewUser(login, password);
|
||||
user = srv.config.dao.userService.registerNewUser(login, ((AuthPlainPassword) password).password);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new AuthException("Registration canceled. Try again later");
|
||||
}
|
||||
}
|
||||
if(user == null || !user.verifyPassword(password))
|
||||
if(user == null || !user.verifyPassword(((AuthPlainPassword) password).password))
|
||||
{
|
||||
if(user ==null) throw new AuthException("Username incorrect");
|
||||
else throw new AuthException("Username or password incorrect");
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
import com.google.gson.JsonElement;
|
||||
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.utils.HTTPRequest;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
|
@ -42,8 +45,9 @@ public authRequest(String username, String password, String ip, String apiKey) {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuthProviderResult auth(String login, String password, String ip) throws IOException {
|
||||
authRequest authRequest = new authRequest(login, password, ip, apiKey);
|
||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws IOException {
|
||||
if(!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
||||
authRequest authRequest = new authRequest(login, ((AuthPlainPassword) password).password, ip, apiKey);
|
||||
JsonElement request = gson.toJsonTree(authRequest);
|
||||
JsonElement content = HTTPRequest.jsonRequest(request, url);
|
||||
if (!content.isJsonObject())
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
import java.sql.SQLException;
|
||||
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
||||
|
@ -29,10 +31,11 @@ public void init(LaunchServer srv) {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuthProviderResult auth(String login, String password, String ip) throws SQLException, AuthException {
|
||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws SQLException, AuthException {
|
||||
if(!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
||||
try (Connection c = mySQLHolder.getConnection()) {
|
||||
PreparedStatement s = c.prepareStatement(query);
|
||||
String[] replaceParams = {"login", login, "password", password, "ip", ip};
|
||||
String[] replaceParams = {"login", login, "password", ((AuthPlainPassword) password).password, "ip", ip};
|
||||
for (int i = 0; i < queryParams.length; i++)
|
||||
s.setString(i + 1, CommonHelper.replace(queryParams[i], replaceParams));
|
||||
|
||||
|
|
|
@ -3,13 +3,16 @@
|
|||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.utils.helper.VerifyHelper;
|
||||
|
||||
public final class NullAuthProvider extends AuthProvider {
|
||||
private volatile AuthProvider provider;
|
||||
|
||||
@Override
|
||||
public AuthProviderResult auth(String login, String password, String ip) throws Exception {
|
||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws Exception {
|
||||
return getProvider().auth(login, password, ip);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
import java.sql.SQLException;
|
||||
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
|
||||
import pro.gravit.utils.helper.CommonHelper;
|
||||
|
@ -20,9 +22,10 @@ public final class PostgreSQLAuthProvider extends AuthProvider {
|
|||
private boolean usePermission;
|
||||
|
||||
@Override
|
||||
public AuthProviderResult auth(String login, String password, String ip) throws SQLException, AuthException {
|
||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws SQLException, AuthException {
|
||||
if(!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
||||
try (Connection c = postgreSQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(query)) {
|
||||
String[] replaceParams = {"login", login, "password", password, "ip", ip};
|
||||
String[] replaceParams = {"login", login, "password", ((AuthPlainPassword) password).password, "ip", ip};
|
||||
for (int i = 0; i < queryParams.length; i++) {
|
||||
s.setString(i + 1, CommonHelper.replace(queryParams[i], replaceParams));
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launchserver.Reconfigurable;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.utils.command.Command;
|
||||
|
@ -23,7 +24,7 @@ public RejectAuthProvider(String message) {
|
|||
private ArrayList<String> whitelist;
|
||||
|
||||
@Override
|
||||
public AuthProviderResult auth(String login, String password, String ip) throws AuthException {
|
||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws AuthException {
|
||||
if (whitelist != null) {
|
||||
for (String username : whitelist) {
|
||||
if (login.equals(username)) {
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.utils.helper.CommonHelper;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
@ -27,8 +30,9 @@ public void init(LaunchServer srv) {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AuthProviderResult auth(String login, String password, String ip) throws IOException {
|
||||
String currentResponse = IOHelper.request(new URL(getFormattedURL(login, password, ip)));
|
||||
public AuthProviderResult auth(String login, AuthRequest.AuthPasswordInterface password, String ip) throws IOException {
|
||||
if(!(password instanceof AuthPlainPassword)) throw new AuthException("This password type not supported");
|
||||
String currentResponse = IOHelper.request(new URL(getFormattedURL(login, ((AuthPlainPassword) password).password, ip)));
|
||||
|
||||
// Match username
|
||||
Matcher matcher = pattern.matcher(currentResponse);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launchserver.LaunchServer;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.provider.AuthProvider;
|
||||
|
@ -37,7 +38,7 @@ public void invoke(String... args) throws Exception {
|
|||
|
||||
// Authenticate
|
||||
AuthProvider provider = pair.provider;
|
||||
AuthProviderResult result = provider.auth(login, password, "127.0.0.1");
|
||||
AuthProviderResult result = provider.auth(login, new AuthPlainPassword(password), "127.0.0.1");
|
||||
UUID uuid = pair.handler.auth(result);
|
||||
|
||||
// Print auth successful message
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
import pro.gravit.launcher.hwid.HWID;
|
||||
import pro.gravit.launcher.hwid.OshiHWID;
|
||||
import pro.gravit.launcher.profiles.ClientProfile;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launcher.request.auth.password.AuthRSAPassword;
|
||||
import pro.gravit.launchserver.auth.AuthException;
|
||||
import pro.gravit.launchserver.auth.AuthProviderPair;
|
||||
import pro.gravit.launchserver.auth.hwid.HWIDException;
|
||||
|
@ -34,15 +37,7 @@ public class AuthResponse extends SimpleResponse {
|
|||
public String customText;
|
||||
public boolean getSession;
|
||||
|
||||
public String password;
|
||||
public byte[] encryptedPassword;
|
||||
|
||||
public AuthResponse(String login, String password, String auth_id, OshiHWID hwid) {
|
||||
this.login = login;
|
||||
this.password = password;
|
||||
this.auth_id = auth_id;
|
||||
this.hwid = hwid;
|
||||
}
|
||||
public AuthRequest.AuthPasswordInterface password;
|
||||
|
||||
public String auth_id;
|
||||
public ConnectTypes authType;
|
||||
|
@ -65,10 +60,11 @@ public void execute(ChannelHandlerContext ctx, Client clientData) throws Excepti
|
|||
AuthProvider.authError("Don't skip Launcher Update");
|
||||
return;
|
||||
}
|
||||
if (password == null) {
|
||||
if(password instanceof AuthRSAPassword)
|
||||
{
|
||||
try {
|
||||
password = IOHelper.decode(SecurityHelper.newRSADecryptCipher(server.privateKey).
|
||||
doFinal(encryptedPassword));
|
||||
password = new AuthPlainPassword(IOHelper.decode(SecurityHelper.newRSADecryptCipher(server.privateKey).
|
||||
doFinal(((AuthRSAPassword) password).password)));
|
||||
} catch (IllegalBlockSizeException | BadPaddingException ignored) {
|
||||
throw new AuthException("Password decryption error");
|
||||
}
|
||||
|
@ -76,7 +72,7 @@ public void execute(ChannelHandlerContext ctx, Client clientData) throws Excepti
|
|||
AuthProviderPair pair;
|
||||
if (auth_id.isEmpty()) pair = server.config.getAuthProviderPair();
|
||||
else pair = server.config.getAuthProviderPair(auth_id);
|
||||
AuthContext context = new AuthContext(0, login, password.length(), customText, client, null, ip, authType);
|
||||
AuthContext context = new AuthContext(0, login, customText, client, null, ip, authType);
|
||||
AuthProvider provider = pair.provider;
|
||||
server.authHookManager.preHook.hook(context, clientData);
|
||||
provider.preAuth(login, password, customText, ip);
|
||||
|
@ -135,10 +131,9 @@ public void execute(ChannelHandlerContext ctx, Client clientData) throws Excepti
|
|||
}
|
||||
|
||||
public static class AuthContext {
|
||||
public AuthContext(long session, String login, int password_length, String customText, String client, String hwid, String ip, ConnectTypes authType) {
|
||||
public AuthContext(long session, String login, String customText, String client, String hwid, String ip, ConnectTypes authType) {
|
||||
this.session = session;
|
||||
this.login = login;
|
||||
this.password_length = password_length;
|
||||
this.customText = customText;
|
||||
this.client = client;
|
||||
this.hwid = hwid;
|
||||
|
@ -148,6 +143,7 @@ public AuthContext(long session, String login, int password_length, String custo
|
|||
|
||||
public long session;
|
||||
public String login;
|
||||
@Deprecated
|
||||
public int password_length; //Use AuthProvider for get password
|
||||
public String client;
|
||||
public String hwid;
|
||||
|
|
|
@ -5,14 +5,22 @@
|
|||
import pro.gravit.launcher.events.request.AuthRequestEvent;
|
||||
import pro.gravit.launcher.hwid.HWID;
|
||||
import pro.gravit.launcher.request.Request;
|
||||
import pro.gravit.launcher.request.auth.password.AuthPlainPassword;
|
||||
import pro.gravit.launcher.request.auth.password.AuthRSAPassword;
|
||||
import pro.gravit.launcher.request.websockets.WebSocketRequest;
|
||||
import pro.gravit.utils.ProviderMap;
|
||||
import pro.gravit.utils.helper.VerifyHelper;
|
||||
|
||||
public final class AuthRequest extends Request<AuthRequestEvent> implements WebSocketRequest {
|
||||
public static ProviderMap<AuthPasswordInterface> providers = new ProviderMap<>();
|
||||
public interface AuthPasswordInterface
|
||||
{
|
||||
boolean check();
|
||||
}
|
||||
@LauncherNetworkAPI
|
||||
private final String login;
|
||||
@LauncherNetworkAPI
|
||||
private final byte[] encryptedPassword;
|
||||
private final AuthPasswordInterface password;
|
||||
@LauncherNetworkAPI
|
||||
private final String auth_id;
|
||||
@LauncherNetworkAPI
|
||||
|
@ -25,8 +33,6 @@ public final class AuthRequest extends Request<AuthRequestEvent> implements WebS
|
|||
private final ConnectTypes authType;
|
||||
@LauncherNetworkAPI
|
||||
public boolean initProxy;
|
||||
@LauncherNetworkAPI
|
||||
public String password;
|
||||
|
||||
public enum ConnectTypes {
|
||||
@LauncherNetworkAPI
|
||||
|
@ -42,7 +48,7 @@ public enum ConnectTypes {
|
|||
@LauncherAPI
|
||||
public AuthRequest(String login, byte[] password, HWID hwid) {
|
||||
this.login = VerifyHelper.verify(login, VerifyHelper.NOT_EMPTY, "Login can't be empty");
|
||||
this.encryptedPassword = password.clone();
|
||||
this.password = new AuthRSAPassword(password.clone());
|
||||
this.hwid = hwid;
|
||||
customText = "";
|
||||
auth_id = "";
|
||||
|
@ -53,7 +59,7 @@ public AuthRequest(String login, byte[] password, HWID hwid) {
|
|||
@LauncherAPI
|
||||
public AuthRequest(String login, byte[] password, HWID hwid, String auth_id) {
|
||||
this.login = VerifyHelper.verify(login, VerifyHelper.NOT_EMPTY, "Login can't be empty");
|
||||
this.encryptedPassword = password.clone();
|
||||
this.password = new AuthRSAPassword(password.clone());
|
||||
this.hwid = hwid;
|
||||
this.auth_id = auth_id;
|
||||
customText = "";
|
||||
|
@ -64,7 +70,7 @@ public AuthRequest(String login, byte[] password, HWID hwid, String auth_id) {
|
|||
@LauncherAPI
|
||||
public AuthRequest(String login, byte[] password, HWID hwid, String customText, String auth_id) {
|
||||
this.login = VerifyHelper.verify(login, VerifyHelper.NOT_EMPTY, "Login can't be empty");
|
||||
this.encryptedPassword = password.clone();
|
||||
this.password = new AuthRSAPassword(password.clone());
|
||||
this.hwid = hwid;
|
||||
this.auth_id = auth_id;
|
||||
this.customText = customText;
|
||||
|
@ -74,7 +80,7 @@ public AuthRequest(String login, byte[] password, HWID hwid, String customText,
|
|||
|
||||
public AuthRequest(String login, byte[] encryptedPassword, String auth_id, ConnectTypes authType) {
|
||||
this.login = login;
|
||||
this.encryptedPassword = encryptedPassword;
|
||||
this.password = new AuthRSAPassword(encryptedPassword.clone());
|
||||
this.auth_id = auth_id;
|
||||
this.authType = authType;
|
||||
this.hwid = null;
|
||||
|
@ -84,8 +90,7 @@ public AuthRequest(String login, byte[] encryptedPassword, String auth_id, Conne
|
|||
|
||||
public AuthRequest(String login, String password, String auth_id, ConnectTypes authType) {
|
||||
this.login = login;
|
||||
this.password = password;
|
||||
this.encryptedPassword = null;
|
||||
this.password = new AuthPlainPassword(password);
|
||||
this.auth_id = auth_id;
|
||||
this.authType = authType;
|
||||
this.hwid = null;
|
||||
|
@ -97,4 +102,12 @@ public AuthRequest(String login, String password, String auth_id, ConnectTypes a
|
|||
public String getType() {
|
||||
return "auth";
|
||||
}
|
||||
private static boolean registerProviders = false;
|
||||
public static void registerProviders() {
|
||||
if(!registerProviders) {
|
||||
providers.register("plain", AuthPlainPassword.class);
|
||||
providers.register("rsa", AuthRSAPassword.class);
|
||||
registerProviders = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package pro.gravit.launcher.request.auth.password;
|
||||
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
|
||||
public class AuthPlainPassword implements AuthRequest.AuthPasswordInterface {
|
||||
public final String password;
|
||||
|
||||
public AuthPlainPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package pro.gravit.launcher.request.auth.password;
|
||||
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
|
||||
public class AuthRSAPassword implements AuthRequest.AuthPasswordInterface {
|
||||
public final byte[] password;
|
||||
|
||||
public AuthRSAPassword(byte[] password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
import pro.gravit.launcher.hasher.HashedEntry;
|
||||
import pro.gravit.launcher.hasher.HashedEntryAdapter;
|
||||
import pro.gravit.launcher.request.WebSocketEvent;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.utils.ProviderMap;
|
||||
import pro.gravit.utils.UniversalJsonAdapter;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
@ -41,6 +42,7 @@ public static void appendTypeAdapters(GsonBuilder builder)
|
|||
builder.registerTypeAdapter(HashedEntry.class, new HashedEntryAdapter());
|
||||
builder.registerTypeAdapter(WebSocketEvent.class, new UniversalJsonAdapter<>(ClientWebSocketService.results));
|
||||
builder.registerTypeAdapter(WebSocketRequest.class, new UniversalJsonAdapter<>(ClientWebSocketService.requests));
|
||||
builder.registerTypeAdapter(AuthRequest.AuthPasswordInterface.class, new UniversalJsonAdapter<>(AuthRequest.providers));
|
||||
}
|
||||
|
||||
private static URI createURL(String address) {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
import pro.gravit.launcher.request.Request;
|
||||
import pro.gravit.launcher.request.RequestException;
|
||||
import pro.gravit.launcher.request.WebSocketEvent;
|
||||
import pro.gravit.launcher.request.auth.AuthRequest;
|
||||
import pro.gravit.utils.helper.JVMHelper;
|
||||
import pro.gravit.utils.helper.LogHelper;
|
||||
|
||||
|
@ -20,6 +21,7 @@ public class StandartClientWebSocketService extends ClientWebSocketService {
|
|||
|
||||
public StandartClientWebSocketService(String address) throws SSLException {
|
||||
super(address);
|
||||
AuthRequest.registerProviders();
|
||||
}
|
||||
|
||||
public class RequestFuture implements Future<WebSocketEvent> {
|
||||
|
|
2
modules
2
modules
|
@ -1 +1 @@
|
|||
Subproject commit 8d7a95d0707539ab18fba83bef6ef9f09b70c0ad
|
||||
Subproject commit 3e485e5ae681cd1c136fc25a49d5dd339bc4d30a
|
Loading…
Reference in a new issue