From 547a1b03c7bc885d66b47fe935dad02c7a2752ae Mon Sep 17 00:00:00 2001 From: muhammadeko Date: Mon, 23 May 2022 18:47:34 +0700 Subject: [PATCH] use otp instead of token, implement createAccount and verifyUser, add some config --- build.gradle | 6 +- src/main/java/me/exzork/gcauth/Config.java | 4 +- src/main/java/me/exzork/gcauth/GCAuth.java | 8 +- .../handler/GCAuthAuthenticationHandler.java | 16 +++- .../handler/GCAuthExternalAuthenticator.java | 10 +-- .../gcauth/handler/GCAuthenticators.java | 5 +- .../exzork/gcauth/utils/Authentication.java | 78 ++++++++++++++----- 7 files changed, 91 insertions(+), 36 deletions(-) diff --git a/build.gradle b/build.gradle index a3b8546..d2b21c3 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ sourceCompatibility = 17 targetCompatibility = 17 group 'me.exzork.gcauth' -version '2.2.1' +version '2.3.0' repositories { mavenCentral() @@ -20,7 +20,7 @@ dependencies { implementation files('lib/grasscutter-1.1.2-dev.jar') implementation 'org.springframework.security:spring-security-crypto:5.6.3' implementation 'commons-logging:commons-logging:1.2' - implementation 'com.auth0:java-jwt:3.19.1' + implementation 'com.auth0:java-jwt:3.19.2' implementation 'javax.servlet:javax.servlet-api:4.0.1' } @@ -32,7 +32,7 @@ jar{ jar.baseName = 'gcauth' from{ configurations.runtimeClasspath.collect{ - if (it.name in ['spring-security-crypto-5.6.3.jar','commons-logging-1.2.jar','javax.servlet-api-4.0.1.jar','java-jwt-3.19.1.jar','jackson-annotations-2.13.2.jar','jackson-core-2.13.2.jar','jackson-databind-2.13.2.2.jar']) { + if (it.name in ['spring-security-crypto-5.6.3.jar','commons-logging-1.2.jar','javax.servlet-api-4.0.1.jar','java-jwt-3.19.2.jar','jackson-annotations-2.13.2.jar','jackson-core-2.13.2.jar','jackson-databind-2.13.2.2.jar']) { zipTree(it) } } diff --git a/src/main/java/me/exzork/gcauth/Config.java b/src/main/java/me/exzork/gcauth/Config.java index f0dba1c..986f293 100644 --- a/src/main/java/me/exzork/gcauth/Config.java +++ b/src/main/java/me/exzork/gcauth/Config.java @@ -5,6 +5,8 @@ import me.exzork.gcauth.utils.Authentication; public final class Config { public String Hash = "BCRYPT"; public String jwtSecret = Authentication.generateRandomString(32); + public long jwtExpiration = 86400; + public long otpExpiration = 300; public String[] defaultPermissions = new String[]{""}; - public String ACCESS_KEY = ""; + public String accessKey = ""; } diff --git a/src/main/java/me/exzork/gcauth/GCAuth.java b/src/main/java/me/exzork/gcauth/GCAuth.java index 330ab10..6fdf0a1 100644 --- a/src/main/java/me/exzork/gcauth/GCAuth.java +++ b/src/main/java/me/exzork/gcauth/GCAuth.java @@ -7,6 +7,7 @@ import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.plugin.Plugin; import static emu.grasscutter.Configuration.ACCOUNT; +import emu.grasscutter.plugin.PluginManager; import me.exzork.gcauth.handler.*; import me.exzork.gcauth.utils.Authentication; @@ -21,6 +22,9 @@ public class GCAuth extends Plugin { private static Config config; private File configFile; private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + public static GCAuth getInstance() { + return (GCAuth) Grasscutter.getPluginManager().getPlugin("GCAuth"); + } @Override public void onEnable() { @@ -65,10 +69,6 @@ public class GCAuth extends Plugin { } } - public static Config getConfigStatic() { - return config; - } - public Config getConfig() { return config; } diff --git a/src/main/java/me/exzork/gcauth/handler/GCAuthAuthenticationHandler.java b/src/main/java/me/exzork/gcauth/handler/GCAuthAuthenticationHandler.java index 05efc07..16f1faf 100644 --- a/src/main/java/me/exzork/gcauth/handler/GCAuthAuthenticationHandler.java +++ b/src/main/java/me/exzork/gcauth/handler/GCAuthAuthenticationHandler.java @@ -1,9 +1,11 @@ package me.exzork.gcauth.handler; import emu.grasscutter.auth.*; +import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; import emu.grasscutter.server.http.objects.ComboTokenResJson; import emu.grasscutter.server.http.objects.LoginResultJson; +import me.exzork.gcauth.utils.Authentication; public class GCAuthAuthenticationHandler implements AuthenticationSystem { private final Authenticator gcAuthAuthenticator = new GCAuthenticators.GCAuthAuthenticator(); @@ -13,7 +15,8 @@ public class GCAuthAuthenticationHandler implements AuthenticationSystem { @Override public void createAccount(String username, String password) { - // Unhandled. + password = Authentication.generateHash(password); + DatabaseHelper.createAccountWithPassword(username, password); } @Override @@ -23,7 +26,11 @@ public class GCAuthAuthenticationHandler implements AuthenticationSystem { @Override public Account verifyUser(String s) { - return null; + String uid = Authentication.getUsernameFromJwt(s); + if (uid == null) { + return null; + } + return DatabaseHelper.getAccountById(uid); } @Override @@ -45,4 +52,9 @@ public class GCAuthAuthenticationHandler implements AuthenticationSystem { public ExternalAuthenticator getExternalAuthenticator() { return externalAuthenticator; } + + @Override + public OAuthAuthenticator getOAuthAuthenticator() { + return null; + } } diff --git a/src/main/java/me/exzork/gcauth/handler/GCAuthExternalAuthenticator.java b/src/main/java/me/exzork/gcauth/handler/GCAuthExternalAuthenticator.java index 599e5f3..1683201 100644 --- a/src/main/java/me/exzork/gcauth/handler/GCAuthExternalAuthenticator.java +++ b/src/main/java/me/exzork/gcauth/handler/GCAuthExternalAuthenticator.java @@ -29,7 +29,7 @@ public class GCAuthExternalAuthenticator implements ExternalAuthenticator { authResponse.jwt = ""; } else { LoginGenerateToken loginGenerateToken = new Gson().fromJson(requestBody, LoginGenerateToken.class); - if (!GCAuth.getConfigStatic().ACCESS_KEY.isEmpty() && !GCAuth.getConfigStatic().ACCESS_KEY.equals(loginGenerateToken.access_key)){ + if (!GCAuth.getInstance().getConfig().accessKey.isEmpty() && !GCAuth.getInstance().getConfig().accessKey.equals(loginGenerateToken.access_key)){ authResponse.success = false; authResponse.message = "ERROR_ACCESS_KEY"; // ENG = "Error access key was sent with the request" authResponse.jwt = ""; @@ -77,7 +77,7 @@ public class GCAuthExternalAuthenticator implements ExternalAuthenticator { authResponse.jwt = ""; } else { RegisterAccount registerAccount = new Gson().fromJson(requestBody, RegisterAccount.class); - if (!GCAuth.getConfigStatic().ACCESS_KEY.isEmpty() && !GCAuth.getConfigStatic().ACCESS_KEY.equals(registerAccount.access_key)){ + if (!GCAuth.getInstance().getConfig().accessKey.isEmpty() && !GCAuth.getInstance().getConfig().accessKey.equals(registerAccount.access_key)){ authResponse.success = false; authResponse.message = "ERROR_ACCESS_KEY"; // ENG = "Error access key was sent with the request" authResponse.jwt = ""; @@ -130,8 +130,8 @@ public class GCAuthExternalAuthenticator implements ExternalAuthenticator { e.printStackTrace(); } if (authResponse.success) { - if (GCAuth.getConfigStatic().defaultPermissions.length > 0) { - for (String permission : GCAuth.getConfigStatic().defaultPermissions) { + if (GCAuth.getInstance().getConfig().defaultPermissions.length > 0) { + for (String permission : GCAuth.getInstance().getConfig().defaultPermissions) { account.addPermission(permission); } account.save(); @@ -154,7 +154,7 @@ public class GCAuthExternalAuthenticator implements ExternalAuthenticator { authResponse.jwt = ""; } else { ChangePasswordAccount changePasswordAccount = new Gson().fromJson(requestBody, ChangePasswordAccount.class); - if (!GCAuth.getConfigStatic().ACCESS_KEY.isEmpty() && !GCAuth.getConfigStatic().ACCESS_KEY.equals(changePasswordAccount.access_key)){ + if (!GCAuth.getInstance().getConfig().accessKey.isEmpty() && !GCAuth.getInstance().getConfig().accessKey.equals(changePasswordAccount.access_key)){ authResponse.success = false; authResponse.message = "ERROR_ACCESS_KEY"; // ENG = "Error access key was sent with the request" authResponse.jwt = ""; diff --git a/src/main/java/me/exzork/gcauth/handler/GCAuthenticators.java b/src/main/java/me/exzork/gcauth/handler/GCAuthenticators.java index 468ac67..d0fab2b 100644 --- a/src/main/java/me/exzork/gcauth/handler/GCAuthenticators.java +++ b/src/main/java/me/exzork/gcauth/handler/GCAuthenticators.java @@ -18,11 +18,10 @@ public class GCAuthenticators { var requestData = authenticationRequest.getPasswordRequest(); assert requestData != null; // This should never be null. - Account account = Authentication.getAccountByOneTimeToken(requestData.account); + Account account = Authentication.getAccountByOTP(requestData.account); if(account == null) { - Grasscutter.getLogger().info("[GCAuth] Client " + requestData.account + " tried to login with invalid one time token."); response.retcode = -201; - response.message = "Token is invalid"; + response.message = "OTP invalid"; return response; } diff --git a/src/main/java/me/exzork/gcauth/utils/Authentication.java b/src/main/java/me/exzork/gcauth/utils/Authentication.java index e1c65e1..e49c036 100644 --- a/src/main/java/me/exzork/gcauth/utils/Authentication.java +++ b/src/main/java/me/exzork/gcauth/utils/Authentication.java @@ -8,11 +8,15 @@ import me.exzork.gcauth.GCAuth; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; +import java.sql.Date; +import java.time.Instant; import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; public final class Authentication { - public static final HashMap tokens = new HashMap<>(); - private static Algorithm key = Algorithm.HMAC256(generateRandomString(32)); + public static final HashMap otps = new HashMap<>(); + private static Algorithm key = Algorithm.HMAC256(generateRandomNumber(32)); public static Algorithm getKey() { return key; } @@ -20,44 +24,59 @@ public final class Authentication { public static Account getAccountByUsernameAndPassword(String username, String password) { Account account = DatabaseHelper.getAccountByName(username); if (account == null) return null; - if(account.getPassword() != null && !account.getPassword().isEmpty()) { - if(!verifyPassword(password, account.getPassword())) account = null; + if (account.getPassword() != null && !account.getPassword().isEmpty()) { + if (!verifyPassword(password, account.getPassword())) account = null; } return account; } - public static Account getAccountByOneTimeToken(String token) { - String username = Authentication.tokens.get(token); + public static Account getAccountByOTP(String otp) { + String username = Authentication.otps.get(otp); if (username == null) return null; - Authentication.tokens.remove(token); + Authentication.otps.remove(otp); return DatabaseHelper.getAccountByName(username); } - public static String generateRandomString(int length){ - String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + public static String generateRandomNumber(int length) { + String chars = "0123456789"; StringBuilder sb = new StringBuilder(); - for(int i = 0; i < length; i++) { + for (int i = 0; i < length; i++) { sb.append(chars.charAt((int) (Math.random() * chars.length()))); } return sb.toString(); } - public static String generateOneTimeToken(Account account) { - String token = Authentication.generateRandomString(32); - Authentication.tokens.put(token, account.getUsername()); + public static String generateRandomString(int length) { + String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(chars.charAt((int) (Math.random() * chars.length()))); + } + return sb.toString(); + } + + public static String generateOTP(Account account) { + String token = Authentication.generateRandomNumber(6); + Authentication.otps.put(token, account.getUsername()); return token; } public static String generateJwt(Account account) { + String otp = generateOTP(account); + while (otps.get(otp) != null) { + otp = generateOTP(account); + } + watchOTP(otp); return JWT.create() - .withClaim("token",generateOneTimeToken(account)) - .withClaim("username",account.getUsername()) - .withClaim("uid",account.getPlayerUid()) + .withClaim("token", otp) + .withClaim("username", account.getUsername()) + .withClaim("uid", account.getPlayerUid()) + .withExpiresAt(Date.from(Instant.ofEpochSecond(System.currentTimeMillis() / 1000 + GCAuth.getInstance().getConfig().jwtExpiration))) .sign(key); } public static String generateHash(String password) { - return switch (GCAuth.getConfigStatic().Hash.toLowerCase()) { + return switch (GCAuth.getInstance().getConfig().Hash.toLowerCase()) { case "bcrypt" -> new BCryptPasswordEncoder().encode(password); case "scrypt" -> new SCryptPasswordEncoder().encode(password); default -> password; @@ -65,10 +84,33 @@ public final class Authentication { } private static boolean verifyPassword(String password, String hash) { - return switch (GCAuth.getConfigStatic().Hash.toLowerCase()) { + return switch (GCAuth.getInstance().getConfig().Hash.toLowerCase()) { case "bcrypt" -> new BCryptPasswordEncoder().matches(password, hash); case "scrypt" -> new SCryptPasswordEncoder().matches(password, hash); default -> password.equals(hash); }; } + + public static String getUsernameFromJwt(String jwt) { + String username = null; + try { + username = JWT.require(key) + .build() + .verify(jwt) + .getClaim("username") + .asString(); + }catch (Exception ignored) {} + return username; + } + + public static void watchOTP(String otp){ + Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + otps.remove(otp); + timer.cancel(); + } + },GCAuth.getInstance().getConfig().otpExpiration * 1000); + } }