Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d946086
improve `buildActive` task
Skidamek Feb 18, 2025
cce13e2
Initial iteration of client authentication
Skidamek Feb 18, 2025
c9ee4a8
Ensure player associated to the secret is still whitelisted
Skidamek Feb 18, 2025
8f35e98
Always overwrite modpack content file to the newest even if there's n…
Skidamek Feb 18, 2025
14d2b91
Merge branch 'main' into auth
Skidamek Feb 18, 2025
b0884d7
Print actual url instead of class
Skidamek Feb 18, 2025
8a5c5c4
stuff:tm:
Skidamek Feb 21, 2025
8cf96b8
Rewrite network stack, ditch plain text HTTP for custom protocol secu…
Skidamek Feb 23, 2025
9d25a00
Add modrinth/cf urls back
Skidamek Feb 23, 2025
f3ebc28
Call `checkCanJoin` on server thread
Skidamek Feb 23, 2025
9e9c124
Save busy states of channels and make sure to use only free ones
Skidamek Feb 24, 2025
0cf2be2
Change authorization method
Skidamek Feb 24, 2025
e148e3a
Write download client without use of netty
Skidamek Feb 24, 2025
2f020f0
Fix NumberFormatException with old config
Skidamek Feb 24, 2025
4ea8ec6
Fix in game download progress bar
Skidamek Feb 24, 2025
1f78a71
Whoopsie
Skidamek Feb 24, 2025
04b26fd
Improvements, mainly DownloadClient stuff
Skidamek Feb 25, 2025
fb4afbd
dont relocate zstd
Skidamek Feb 25, 2025
d243b30
Added `automodpack host connections` command and don't use compressio…
Skidamek Feb 25, 2025
62ae8b9
Fixed to host and client
Skidamek Feb 25, 2025
37f1dfd
Reinitialize DownloadClient
Skidamek Feb 25, 2025
02c6244
Don't ever return *previous* key value lol
Skidamek Feb 25, 2025
fbd8480
Fix #328
Skidamek Feb 25, 2025
8976dcd
Small cleanups and always use compression since no noticeable diff wa…
Skidamek Feb 26, 2025
e20fdfc
Various improvements and beta 25 bump
Skidamek Feb 27, 2025
6e11813
Merge branch 'auth'
Skidamek Feb 27, 2025
b2da868
Enhance error handling and logging; improve address parsing and file …
Skidamek Feb 27, 2025
9746716
Remove console output for server start
Skidamek Feb 27, 2025
aea18b2
Fix error screen messages
Skidamek Feb 27, 2025
c919eb7
12 > 14
Skidamek Feb 27, 2025
984d9a8
Remove address comparison
Skidamek Feb 27, 2025
0d2c0eb
sanity stuff
Skidamek Feb 27, 2025
9e480c8
Improve mod detection
Skidamek Feb 28, 2025
46feb2e
Make sure to save secret for correct modpack
Skidamek Feb 28, 2025
7533e8f
Move stuff around to make 1.18 fabric work
Skidamek Feb 28, 2025
41fc772
Force utf8 on inputstream
Skidamek Feb 28, 2025
f724629
Use temp file instead
Skidamek Feb 28, 2025
6e57770
Throw out the parsing method add more logs
Skidamek Mar 1, 2025
cb27190
Dont load content every frame on ChangelogScreen
Skidamek Mar 1, 2025
bc94b5b
reinitialize workarounds
Skidamek Mar 1, 2025
5404899
Fix encoding issues on windows using `Files.writeString()`
Skidamek Mar 1, 2025
4e9119e
Minor fixes, forge services locator now checks nested jars, refactor …
Skidamek Mar 1, 2025
5572c79
logs
Skidamek Mar 2, 2025
7e069aa
bind on `hostLocalIp`
Skidamek Mar 2, 2025
aa8c960
always bind on 0.0.0.0
Skidamek Mar 2, 2025
0fefa5c
Fixes to selfupdater code
Skidamek Mar 2, 2025
cb279d2
bump to beta 26
Skidamek Mar 2, 2025
2e0ad54
Generate offline uuids
Skidamek Mar 3, 2025
a435855
bump to beta 27
Skidamek Mar 3, 2025
60ad8e0
Fix restart button
Skidamek Mar 6, 2025
e0b4aa6
remove unused code
Skidamek Mar 11, 2025
d97a39f
Allow offline players on online mode server
Skidamek Mar 11, 2025
8c1eba1
Merge branch 'patch-1' into main-1
suerion Mar 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ jobs:
chmod +x gradlew
if [ -z "${{ inputs.target_subproject }}" ]; then
echo "Building all subprojects"
./gradlew chiseledBuild
./gradlew clean
./gradlew chiseledBuild -x mergeJars
./gradlew mergeJars
else
args=$(echo "${{ inputs.target_subproject }}" | tr ',' '\n' | sed 's/$/:build/' | paste -sd ' ')
echo "Building with arguments=$args"
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ if (stonecutter.current.isActive) {
rootProject.tasks.register("buildActive") {
group = "project"
dependsOn(tasks.named("build"))
finalizedBy("mergeJars")
}
}

Expand Down
4 changes: 3 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ repositories {
dependencies {
implementation("org.apache.logging.log4j:log4j-core:2.20.0")
implementation("com.google.code.gson:gson:2.10.1")
implementation("io.netty:netty-all:4.1.111.Final")
implementation("io.netty:netty-all:4.1.118.Final")
implementation("org.bouncycastle:bcpkix-jdk18on:1.80")
implementation("com.github.luben:zstd-jni:1.5.7-1")
implementation("org.tomlj:tomlj:1.1.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2")
Expand Down
23 changes: 13 additions & 10 deletions core/src/main/java/pl/skidam/automodpack_core/GlobalVariables.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pl.skidam.automodpack_core.config.Jsons;
import pl.skidam.automodpack_core.loader.ModpackLoaderService;
import pl.skidam.automodpack_core.loader.NullLoaderManager;
import pl.skidam.automodpack_core.loader.LoaderManagerService;
import pl.skidam.automodpack_core.loader.NullModpackLoader;
import pl.skidam.automodpack_core.loader.*;
import pl.skidam.automodpack_core.modpack.Modpack;
import pl.skidam.automodpack_core.netty.HttpServer;
import pl.skidam.automodpack_core.protocol.netty.NettyServer;

import java.nio.file.Path;

Expand All @@ -23,31 +20,37 @@ public class GlobalVariables {
public static String LOADER;
public static LoaderManagerService LOADER_MANAGER = new NullLoaderManager();
public static ModpackLoaderService MODPACK_LOADER = new NullModpackLoader();
public static GameCallService GAME_CALL = new NullGameCall();
public static Path AUTOMODPACK_JAR;
public static Path MODS_DIR;
public static Modpack modpack;
public static HttpServer httpServer;
public static NettyServer hostServer;
public static Jsons.ServerConfigFields serverConfig;
public static Jsons.ClientConfigFields clientConfig;
public static final Path automodpackDir = Path.of("automodpack");
public final static Path hostModpackDir = automodpackDir.resolve("host-modpack");
public static final Path hostModpackDir = automodpackDir.resolve("host-modpack");
// TODO More server modpacks
// Main - required
// Addons - optional addon packs
// Switches - optional or required packs, chosen by the player, only one can be installed at a time
public final static Path hostContentModpackDir = hostModpackDir.resolve("main");
public static final Path hostContentModpackDir = hostModpackDir.resolve("main");
public static Path hostModpackContentFile = hostModpackDir.resolve("automodpack-content.json");
public static Path serverConfigFile = automodpackDir.resolve("automodpack-server.json");
public static Path serverCoreConfigFile = automodpackDir.resolve("automodpack-core.json");
public static final Path privateDir = automodpackDir.resolve(".private");
public static final Path serverSecretsFile = privateDir.resolve("automodpack-secrets.json");
public static final Path serverCertFile = privateDir.resolve("cert.crt");
public static final Path serverPrivateKeyFile = privateDir.resolve("key.pem");

// Client

// Client
public static final Path modpackContentTempFile = automodpackDir.resolve("automodpack-content.json.temp");
public static final Path clientConfigFile = automodpackDir.resolve("automodpack-client.json");
public static final Path clientSecretsFile = privateDir.resolve("automodpack-client-secrets.json");
public static final Path modpacksDir = automodpackDir.resolve("modpacks");

public static final String clientConfigFileOverrideResource = "overrides-automodpack-client.json";
public static String clientConfigOverride; // read from inside a jar file on preload, used instead of clientConfigFile if exists

public static Path selectedModpackDir;
public static String selectedModpackLink;
}
98 changes: 98 additions & 0 deletions core/src/main/java/pl/skidam/automodpack_core/auth/Secrets.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package pl.skidam.automodpack_core.auth;

import pl.skidam.automodpack_core.protocol.NetUtils;

import java.net.SocketAddress;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Base64;

import static pl.skidam.automodpack_core.GlobalVariables.*;

public class Secrets {
public static class Secret { // unfortunately has to be a class instead of record because of older gson version in 1.18 mc
private String secret; // and these also can't be final
private String fingerprint;
private Long timestamp;

public Secret(String secret, String fingerprint, Long timestamp) {
this.secret = secret;
this.fingerprint = fingerprint;
this.timestamp = timestamp;
}

public String secret() {
return secret;
}

public String fingerprint() {
return fingerprint;
}

public Long timestamp() {
return timestamp;
}

@Override
public String toString() {
return "Secret{" +
"secret='" + secret + '\'' +
", fingerprint='" + fingerprint + '\'' +
", timestamp=" + timestamp +
'}';
}
}

public static Secret generateSecret() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[32]; // 32 bytes = 256 bits
random.nextBytes(bytes);
String secret = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
String fingerprint = generateFingerprint(secret);
if (secret == null || fingerprint == null)
return null;

long timestamp = System.currentTimeMillis() / 1000;

return new Secret(secret, fingerprint, timestamp);
}

private static String generateFingerprint(String secret) {
try {
X509Certificate cert = hostServer.getCert();
if (cert == null)
return null;
return NetUtils.getFingerprint(cert, secret);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

public static boolean isSecretValid(String secretStr, SocketAddress address) {
if (!serverConfig.validateSecrets)
return true;

var playerSecretPair = SecretsStore.getHostSecret(secretStr);
if (playerSecretPair == null)
return false;

Secret secret = playerSecretPair.getValue();
if (secret == null)
return false;

String playerUuid = playerSecretPair.getKey();
if (!GAME_CALL.isPlayerAuthorized(address, playerUuid)) // check if associated player is still whitelisted
return false;

long secretLifetime = serverConfig.secretLifetime * 3600; // in seconds
long currentTime = System.currentTimeMillis() / 1000;

boolean valid = secret.timestamp() + secretLifetime > currentTime;

if (!valid)
return false;

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package pl.skidam.automodpack_core.auth;

import pl.skidam.automodpack_core.GlobalVariables;
import pl.skidam.automodpack_core.config.ConfigTools;
import pl.skidam.automodpack_core.config.Jsons;

import java.nio.file.Path;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class SecretsStore {
private static class SecretsCache {
private final ConcurrentMap<String, Secrets.Secret> cache;
private Jsons.SecretsFields db;
private final Path configFile;

public SecretsCache(Path configFile) {
this.configFile = configFile;
this.cache = new ConcurrentHashMap<>();
}

public synchronized void load() {
if (db != null)
return;
db = ConfigTools.load(configFile, Jsons.SecretsFields.class);
if (db != null && db.secrets != null && !db.secrets.isEmpty()) {
cache.putAll(db.secrets);
}
}

public synchronized void save() {
ConfigTools.save(configFile, db);
}

public Secrets.Secret get(String key) {
load();
return cache.get(key);
}

public void save(String key, Secrets.Secret secret) throws IllegalArgumentException {
if (key == null || key.isBlank() || secret == null || secret.secret().isBlank())
throw new IllegalArgumentException("Key or secret cannot be null or blank");
load();
cache.put(key, secret);
if (db == null) {
db = new Jsons.SecretsFields();
}
db.secrets.put(key, secret);
save();
}
}

private static final SecretsCache hostSecrets = new SecretsCache(GlobalVariables.serverSecretsFile);
private static final SecretsCache clientSecrets = new SecretsCache(GlobalVariables.clientSecretsFile);

public static Map.Entry<String, Secrets.Secret> getHostSecret(String secret) {
hostSecrets.load();
for (var entry : hostSecrets.cache.entrySet()) {
var thisSecret = entry.getValue().secret();
if (Objects.equals(thisSecret, secret)) {
return entry;
}
}

return null;
}

public static void saveHostSecret(String uuid, Secrets.Secret secret) {
hostSecrets.save(uuid, secret);
}

public static Secrets.Secret getClientSecret(String modpack) {
return clientSecrets.get(modpack);
}

public static void saveClientSecret(String modpack, Secrets.Secret secret) throws IllegalArgumentException {
clientSecrets.save(modpack, secret);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pl.skidam.automodpack_core.callbacks;

public interface IntCallback {
void run(int bytes);
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ public static Jsons.ModpackContentFields loadModpackContent(Path modpackContentF
return GSON.fromJson(json, Jsons.ModpackContentFields.class);
}
} catch (Exception e) {
LOGGER.error("Couldn't load modpack content!");
e.printStackTrace();
LOGGER.error("Couldn't load modpack content! {}", modpackContentFile.toAbsolutePath().normalize(), e);
}
return null;
}
Expand Down
11 changes: 10 additions & 1 deletion core/src/main/java/pl/skidam/automodpack_core/config/Jsons.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@

package pl.skidam.automodpack_core.config;

import pl.skidam.automodpack_core.auth.Secrets;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;


public class Jsons {

public static class ClientConfigFields {
Expand Down Expand Up @@ -39,6 +41,8 @@ public static class ServerConfigFields {
public boolean updateIpsOnEveryStart = false;
public int hostPort = -1;
public boolean reverseProxy = false;
public long secretLifetime = 336; // 336 hours = 14 days
public boolean validateSecrets = true;
public boolean selfUpdater = false;
public List<String> acceptedLoaders;
}
Expand All @@ -55,6 +59,11 @@ public static class WorkaroundFields {
public Set<String> workaroundMods;
}


public static class SecretsFields {
public Map<String, Secrets.Secret> secrets = new HashMap<>();
}

public static class ModpackContentFields {
public String modpackName = "";
public String automodpackVersion = "";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pl.skidam.automodpack_core.loader;

import java.net.SocketAddress;

public interface GameCallService {
boolean isPlayerAuthorized(SocketAddress address, String id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.util.Collection;

public interface LoaderManagerService {

enum ModPlatform { FABRIC, QUILT, FORGE, NEOFORGE }
enum EnvironmentType { CLIENT, SERVER, UNIVERSAL }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package pl.skidam.automodpack_core.loader;

import java.net.SocketAddress;

public class NullGameCall implements GameCallService {
@Override
public boolean isPlayerAuthorized(SocketAddress address, String id) {
return true;
}
}
Loading