mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-11-15 03:31:15 +03:00
[FEATURE] MySQL and PostgreSQL AuthCoreProvider use OAuth
This commit is contained in:
parent
afbed1345f
commit
6aed114791
5 changed files with 139 additions and 11 deletions
|
@ -1,5 +1,7 @@
|
|||
package pro.gravit.launchserver.auth.core;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
|
@ -13,6 +15,7 @@
|
|||
import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware;
|
||||
import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware;
|
||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
||||
import pro.gravit.launchserver.helper.LegacySessionHelper;
|
||||
import pro.gravit.launchserver.manangers.AuthManager;
|
||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||
import pro.gravit.utils.helper.IOHelper;
|
||||
|
@ -21,6 +24,8 @@
|
|||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -30,6 +35,7 @@ public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHa
|
|||
private transient final Logger logger = LogManager.getLogger();
|
||||
public MySQLSourceConfig mySQLHolder;
|
||||
|
||||
public int expireSeconds = 3600;
|
||||
public String uuidColumn;
|
||||
public String usernameColumn;
|
||||
public String accessTokenColumn;
|
||||
|
@ -63,6 +69,8 @@ public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHa
|
|||
private transient String updateAuthSQL;
|
||||
private transient String updateServerIDSQL;
|
||||
|
||||
private transient LaunchServer server;
|
||||
|
||||
@Override
|
||||
public User getUserByUsername(String username) {
|
||||
try {
|
||||
|
@ -95,13 +103,39 @@ public User getUserByLogin(String login) {
|
|||
|
||||
@Override
|
||||
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
||||
try {
|
||||
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
|
||||
var user = (MySQLUser) getUserByUUID(info.uuid());
|
||||
if(user == null) {
|
||||
return null;
|
||||
}
|
||||
return new MySQLUserSession(user);
|
||||
} catch (ExpiredJwtException e) {
|
||||
throw new OAuthAccessTokenExpired();
|
||||
} catch (JwtException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
|
||||
String[] parts = refreshToken.split("\\.");
|
||||
if(parts.length != 2) {
|
||||
return null;
|
||||
}
|
||||
String username = parts[0];
|
||||
String token = parts[1];
|
||||
var user = (MySQLUser) getUserByUsername(username);
|
||||
if(user == null || user.password == null) {
|
||||
return null;
|
||||
}
|
||||
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
|
||||
if(!token.equals(realToken)) {
|
||||
return null;
|
||||
}
|
||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now().plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new MySQLUserSession(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
||||
|
@ -119,17 +153,20 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
|
|||
}
|
||||
}
|
||||
MySQLUserSession session = new MySQLUserSession(mySQLUser);
|
||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(mySQLUser, LocalDateTime.now().plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||
var refreshToken = LegacySessionHelper.makeRefreshTokenFromPassword(mySQLUser.username, mySQLUser.password, server.keyAgreementManager.legacySalt);
|
||||
if (minecraftAccess) {
|
||||
String minecraftAccessToken = SecurityHelper.randomStringToken();
|
||||
updateAuth(mySQLUser, minecraftAccessToken);
|
||||
return AuthManager.AuthReport.ofMinecraftAccessToken(minecraftAccessToken, session);
|
||||
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
} else {
|
||||
return AuthManager.AuthReport.ofMinecraftAccessToken(null, session);
|
||||
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
this.server = server;
|
||||
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");
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package pro.gravit.launchserver.auth.core;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import pro.gravit.launcher.ClientPermissions;
|
||||
|
@ -10,18 +12,21 @@
|
|||
import pro.gravit.launchserver.auth.MySQLSourceConfig;
|
||||
import pro.gravit.launchserver.auth.PostgreSQLSourceConfig;
|
||||
import pro.gravit.launchserver.auth.password.PasswordVerifier;
|
||||
import pro.gravit.launchserver.helper.LegacySessionHelper;
|
||||
import pro.gravit.launchserver.manangers.AuthManager;
|
||||
import pro.gravit.launchserver.socket.response.auth.AuthResponse;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PostgresSQLCoreProvider extends AuthCoreProvider {
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
public PostgreSQLSourceConfig postgresSQLHolder;
|
||||
|
||||
public int expireSeconds = 3600;
|
||||
public String uuidColumn;
|
||||
public String usernameColumn;
|
||||
public String accessTokenColumn;
|
||||
|
@ -42,6 +47,8 @@ public class PostgresSQLCoreProvider extends AuthCoreProvider {
|
|||
private transient String updateAuthSQL;
|
||||
private transient String updateServerIDSQL;
|
||||
|
||||
private transient LaunchServer server;
|
||||
|
||||
@Override
|
||||
public User getUserByUsername(String username) {
|
||||
try {
|
||||
|
@ -74,13 +81,39 @@ public User getUserByLogin(String login) {
|
|||
|
||||
@Override
|
||||
public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired {
|
||||
try {
|
||||
var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey);
|
||||
var user = (MySQLCoreProvider.MySQLUser) getUserByUUID(info.uuid());
|
||||
if(user == null) {
|
||||
return null;
|
||||
}
|
||||
return new MySQLCoreProvider.MySQLUserSession(user);
|
||||
} catch (ExpiredJwtException e) {
|
||||
throw new OAuthAccessTokenExpired();
|
||||
} catch (JwtException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) {
|
||||
String[] parts = refreshToken.split("\\.");
|
||||
if(parts.length != 2) {
|
||||
return null;
|
||||
}
|
||||
String username = parts[0];
|
||||
String token = parts[1];
|
||||
var user = (MySQLCoreProvider.MySQLUser) getUserByUsername(username);
|
||||
if(user == null || user.password == null) {
|
||||
return null;
|
||||
}
|
||||
var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt);
|
||||
if(!token.equals(realToken)) {
|
||||
return null;
|
||||
}
|
||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now().plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||
return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new MySQLCoreProvider.MySQLUserSession(user));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext context, AuthRequest.AuthPasswordInterface password, boolean minecraftAccess) throws IOException {
|
||||
|
@ -98,17 +131,20 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c
|
|||
}
|
||||
}
|
||||
MySQLUserSession session = new MySQLUserSession(postgresSQLUser);
|
||||
var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(postgresSQLUser, LocalDateTime.now().plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey);
|
||||
var refreshToken = LegacySessionHelper.makeRefreshTokenFromPassword(postgresSQLUser.username, postgresSQLUser.password, server.keyAgreementManager.legacySalt);
|
||||
if (minecraftAccess) {
|
||||
String minecraftAccessToken = SecurityHelper.randomStringToken();
|
||||
updateAuth(postgresSQLUser, minecraftAccessToken);
|
||||
return AuthManager.AuthReport.ofMinecraftAccessToken(minecraftAccessToken, session);
|
||||
return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
} else {
|
||||
return AuthManager.AuthReport.ofMinecraftAccessToken(null, session);
|
||||
return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LaunchServer server) {
|
||||
this.server = server;
|
||||
if (postgresSQLHolder == null) logger.error("postgresSQLHolder cannot be null");
|
||||
if (uuidColumn == null) logger.error("uuidColumn cannot be null");
|
||||
if (usernameColumn == null) logger.error("usernameColumn cannot be null");
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package pro.gravit.launchserver.helper;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import pro.gravit.launchserver.auth.core.User;
|
||||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LegacySessionHelper {
|
||||
public static String makeAccessJwtTokenFromString(User user, LocalDateTime expirationTime, ECPrivateKey privateKey) {
|
||||
return Jwts.builder()
|
||||
.setIssuer("LaunchServer")
|
||||
.setSubject(user.getUsername())
|
||||
.claim("uuid", user.getUUID().toString())
|
||||
.setExpiration(Date.from(expirationTime
|
||||
.toInstant(ZoneOffset.UTC)))
|
||||
.signWith(privateKey)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public static JwtTokenInfo getJwtInfoFromAccessToken(String token, ECPublicKey publicKey) {
|
||||
var parser = Jwts.parserBuilder()
|
||||
.requireIssuer("LaunchServer")
|
||||
.setSigningKey(publicKey)
|
||||
.build();
|
||||
var claims = parser.parseClaimsJws(token);
|
||||
var uuid = UUID.fromString(claims.getBody().get("uuid", String.class));
|
||||
var username = claims.getBody().getSubject();
|
||||
return new JwtTokenInfo(username, uuid);
|
||||
}
|
||||
|
||||
public static String makeRefreshTokenFromPassword(String username, String rawPassword, String secretSalt) {
|
||||
return SecurityHelper.toHex(SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,
|
||||
String.format("%s.%s.%s.%s", secretSalt, username, rawPassword, secretSalt)));
|
||||
}
|
||||
|
||||
public record JwtTokenInfo(String username, UUID uuid) {
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
import pro.gravit.utils.helper.SecurityHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyPair;
|
||||
import java.security.SecureRandom;
|
||||
|
@ -20,13 +21,15 @@ public class KeyAgreementManager {
|
|||
public final ECPrivateKey ecdsaPrivateKey;
|
||||
public final RSAPublicKey rsaPublicKey;
|
||||
public final RSAPrivateKey rsaPrivateKey;
|
||||
public final String legacySalt;
|
||||
private transient final Logger logger = LogManager.getLogger();
|
||||
|
||||
public KeyAgreementManager(ECPublicKey ecdsaPublicKey, ECPrivateKey ecdsaPrivateKey, RSAPublicKey rsaPublicKey, RSAPrivateKey rsaPrivateKey) {
|
||||
public KeyAgreementManager(ECPublicKey ecdsaPublicKey, ECPrivateKey ecdsaPrivateKey, RSAPublicKey rsaPublicKey, RSAPrivateKey rsaPrivateKey, String legacySalt) {
|
||||
this.ecdsaPublicKey = ecdsaPublicKey;
|
||||
this.ecdsaPrivateKey = ecdsaPrivateKey;
|
||||
this.rsaPublicKey = rsaPublicKey;
|
||||
this.rsaPrivateKey = rsaPrivateKey;
|
||||
this.legacySalt = legacySalt;
|
||||
}
|
||||
|
||||
public KeyAgreementManager(Path keyDirectory) throws IOException, InvalidKeySpecException {
|
||||
|
@ -62,5 +65,12 @@ public KeyAgreementManager(Path keyDirectory) throws IOException, InvalidKeySpec
|
|||
IOHelper.write(rsaPublicKeyPath, rsaPublicKey.getEncoded());
|
||||
IOHelper.write(rsaPrivateKeyPath, rsaPrivateKey.getEncoded());
|
||||
}
|
||||
Path legacySaltPath = keyDirectory.resolve("legacySalt");
|
||||
if(IOHelper.isFile(legacySaltPath)) {
|
||||
legacySalt = new String(IOHelper.read(legacySaltPath), StandardCharsets.UTF_8);
|
||||
} else {
|
||||
legacySalt = SecurityHelper.randomStringToken();
|
||||
IOHelper.write(legacySaltPath, legacySalt.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ public final class Version implements Comparable<Version> {
|
|||
|
||||
public static final int MAJOR = 5;
|
||||
public static final int MINOR = 2;
|
||||
public static final int PATCH = 8;
|
||||
public static final int PATCH = 9;
|
||||
public static final int BUILD = 1;
|
||||
public static final Version.Type RELEASE = Type.STABLE;
|
||||
public static final Version.Type RELEASE = Type.EXPERIMENTAL;
|
||||
public final int major;
|
||||
public final int minor;
|
||||
public final int patch;
|
||||
|
|
Loading…
Reference in a new issue