use otp instead of token, implement createAccount and verifyUser, add some config

This commit is contained in:
muhammadeko 2022-05-23 18:47:34 +07:00
parent 66df9935d2
commit 547a1b03c7
No known key found for this signature in database
GPG Key ID: 51366716C10E98B1
7 changed files with 91 additions and 36 deletions

View File

@ -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)
}
}

View File

@ -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 = "";
}

View File

@ -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;
}

View File

@ -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<LoginResultJson> 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;
}
}

View File

@ -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 = "";

View File

@ -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;
}

View File

@ -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<String,String> tokens = new HashMap<>();
private static Algorithm key = Algorithm.HMAC256(generateRandomString(32));
public static final HashMap<String, String> 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);
}
}