diff --git a/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java b/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java index 320862dc5..c81b8bc87 100644 --- a/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java +++ b/components/launcher-api/src/main/java/pro/gravit/launcher/base/events/request/SetProfileRequestEvent.java @@ -1,32 +1,56 @@ package pro.gravit.launcher.base.events.request; import pro.gravit.launcher.core.LauncherNetworkAPI; +import pro.gravit.launcher.base.events.ExtendedTokenRequestEvent; import pro.gravit.launcher.base.events.RequestEvent; import pro.gravit.launcher.base.profiles.ClientProfile; import java.util.UUID; -public class SetProfileRequestEvent extends RequestEvent { +public class SetProfileRequestEvent extends RequestEvent implements ExtendedTokenRequestEvent { + public static final String CLIENT_PROFILE_EXTENDED_TOKEN_NAME = "clientProfile"; @SuppressWarnings("unused") private static final UUID uuid = UUID.fromString("08c0de9e-4364-4152-9066-8354a3a48541"); @LauncherNetworkAPI public final ClientProfile newProfile; @LauncherNetworkAPI public final String tag; + public final String profileExtendedToken; + public final long profileExtendedTokenExpire; public SetProfileRequestEvent(ClientProfile newProfile) { - this.newProfile = newProfile; - this.tag = null; + this(newProfile, null, null, 0); } public SetProfileRequestEvent(ClientProfile newProfile, String tag) { + this(newProfile, tag, null, 0); + } + + public SetProfileRequestEvent(ClientProfile newProfile, String tag, String profileExtendedToken, long profileExtendedTokenExpire) { this.newProfile = newProfile; this.tag = tag; + this.profileExtendedToken = profileExtendedToken; + this.profileExtendedTokenExpire = profileExtendedTokenExpire; } @Override public String getType() { return "setProfile"; } + + @Override + public String getExtendedTokenName() { + return CLIENT_PROFILE_EXTENDED_TOKEN_NAME; + } + + @Override + public String getExtendedToken() { + return profileExtendedToken; + } + + @Override + public long getExtendedTokenExpire() { + return profileExtendedTokenExpire; + } } diff --git a/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java b/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java index 6c74f129e..88d33a621 100644 --- a/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java +++ b/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalFile.java @@ -14,6 +14,7 @@ public class OptionalFile implements ProfileFeatureAPI.OptionalMod { public List actions; @LauncherNetworkAPI public boolean mark; + public transient boolean enabledByDefault; @LauncherNetworkAPI public boolean visible = true; @LauncherNetworkAPI diff --git a/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalView.java b/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalView.java index 7837a1278..215468250 100644 --- a/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalView.java +++ b/components/launcher-api/src/main/java/pro/gravit/launcher/base/profiles/optional/OptionalView.java @@ -18,9 +18,6 @@ public class OptionalView { public OptionalView(ClientProfile profile) { this.all = profile.getOptional(); - for (OptionalFile f : this.all) { - if (f.mark) enable(f, true, null); - } } public OptionalView(OptionalView view) { diff --git a/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java b/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java index cabbb5b5a..33066e370 100644 --- a/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java +++ b/components/launcher-runtime/src/main/java/pro/gravit/launcher/runtime/backend/ProfileSettingsImpl.java @@ -51,6 +51,7 @@ public ProfileSettingsImpl(ClientProfile profile, LauncherBackendImpl backend) { this.flags.add(Flag.LINUX_WAYLAND_SUPPORT); } processTriggers(profile, this.view); + applyEnabledByDefault(this.view); } @Override @@ -183,7 +184,7 @@ public ProfileSettingsImpl copy() { cloned.profile = profile; cloned.ram = new HashMap<>(ram); cloned.flags = new HashSet<>(flags); - cloned.enabled = new HashSet<>(enabled); + cloned.enabled = enabled == null ? null : new HashSet<>(enabled); if(view != null) { cloned.view = new OptionalView(profile, view); } @@ -207,12 +208,16 @@ public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) { this.profile = profile; this.view = new OptionalView(profile); processTriggers(profile, this.view); - for(var e : enabled) { - var opt = profile.getOptionalFile(e); - if(opt == null) { - continue; + if (enabled == null) { + applyEnabledByDefault(this.view); + } else { + for(var e : enabled) { + var opt = profile.getOptionalFile(e); + if (opt == null) { + continue; + } + enableOptional(opt, (var1, var2) -> {}); } - enableOptional(opt, (var1, var2) -> {}); } if(this.saveJavaPath != null) { backend.getAvailableJava().thenAccept((javas) -> { @@ -237,11 +242,12 @@ public void initAfterGson(ClientProfile profile, LauncherBackendImpl backend) { public void processTriggers(ClientProfile profile, OptionalView view) { TriggerManagerContext context = new TriggerManagerContext(profile); for (OptionalFile optional : view.all) { + optional.enabledByDefault = optional.mark; if (optional.limited) { if (!backend.hasPermission("launcher.runtime.optionals.%s.%s.show" .formatted(profile.getUUID(), optional.name.toLowerCase(Locale.ROOT)))) { - view.disable(optional, null); + optional.enabledByDefault = false; optional.visible = false; } else { optional.visible = true; @@ -260,10 +266,17 @@ public void processTriggers(ClientProfile profile, OptionalView view) { } } if (isRequired) { - if (fail == 0) view.enable(optional, true, null); - else view.disable(optional, null); + optional.enabledByDefault = fail == 0; } else { - if (success > 0) view.enable(optional, false, null); + if (success > 0) optional.enabledByDefault = true; + } + } + } + + private void applyEnabledByDefault(OptionalView view) { + for (OptionalFile optional : view.all) { + if (optional.visible && optional.enabledByDefault) { + view.enable(optional, false, null); } } } diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java index 085c82a30..77dc5ce03 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java @@ -6,6 +6,7 @@ import org.apache.logging.log4j.Logger; import pro.gravit.launcher.base.ClientPermissions; import pro.gravit.launcher.base.events.request.AuthRequestEvent; +import pro.gravit.launcher.base.events.request.SetProfileRequestEvent; import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.PlayerProfile; import pro.gravit.launcher.base.request.auth.AuthRequest; @@ -20,6 +21,7 @@ import pro.gravit.launchserver.auth.core.interfaces.session.UserSessionSupportKeys; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportProperties; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportTextures; +import pro.gravit.launchserver.auth.profiles.ProfilesProvider; import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.response.auth.AuthResponse; @@ -29,12 +31,15 @@ import javax.crypto.Cipher; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.*; public class AuthManager { private transient final LaunchServer server; private transient final Logger logger = LogManager.getLogger(); private transient final JwtParser checkServerTokenParser; + private transient final JwtParser clientProfileTokenParser; public AuthManager(LaunchServer server) { this.server = server; @@ -43,6 +48,11 @@ public AuthManager(LaunchServer server) { .require("tokenType", "checkServer") .verifyWith(server.keyAgreementManager.ecdsaPublicKey) .build(); + this.clientProfileTokenParser = Jwts.parser() + .requireIssuer("LaunchServer") + .require("tokenType", SetProfileRequestEvent.CLIENT_PROFILE_EXTENDED_TOKEN_NAME) + .verifyWith(server.keyAgreementManager.ecdsaPublicKey) + .build(); } public String newCheckServerToken(String serverName, String authId, boolean publicOnly) { @@ -66,6 +76,34 @@ public CheckServerTokenInfo parseCheckServerToken(String token) { } } + public String newClientProfileToken(UUID profileUUID, String profileTag, String authId) { + var builder = Jwts.builder() + .issuer("LaunchServer") + .claim("tokenType", SetProfileRequestEvent.CLIENT_PROFILE_EXTENDED_TOKEN_NAME) + .claim("profileUUID", profileUUID.toString()); + if (profileTag != null) { + builder = builder.claim("profileTag", profileTag); + } + if (authId != null) { + builder = builder.claim("authId", authId); + } + return builder.setExpiration(Date.from(LocalDateTime.now().plusSeconds(server.config.netty.security.launcherTokenExpire).toInstant(ZoneOffset.UTC))) + .signWith(server.keyAgreementManager.ecdsaPrivateKey) + .compact(); + } + + public ClientProfileTokenInfo parseClientProfileToken(String token) { + try { + var jwt = clientProfileTokenParser.parseClaimsJws(token).getBody(); + return new ClientProfileTokenInfo( + UUID.fromString(jwt.get("profileUUID", String.class)), + jwt.get("profileTag", String.class), + jwt.get("authId", String.class)); + } catch (Exception e) { + return null; + } + } + /** * Create AuthContext * @@ -298,6 +336,9 @@ private AuthRequest.AuthPasswordInterface tryDecryptPasswordPlain(AuthRequest.Au public record CheckServerTokenInfo(String serverName, String authId, boolean isPublic) { } + public record ClientProfileTokenInfo(UUID profileUUID, String profileTag, String authId) { + } + public static class CheckServerVerifier implements RestoreResponse.ExtendedTokenProvider { private final LaunchServer server; @@ -319,11 +360,45 @@ public boolean accept(Client client, AuthProviderPair pair, String extendedToken client.permissions.addPerm("launchserver.checkserver.extended"); client.permissions.addPerm("launchserver.profile.%s.show".formatted(info.serverName)); } + try { + UUID profileUUID = UUID.fromString(info.serverName); + ProfilesProvider.CompletedProfile profile = server.config.profilesProvider.get(profileUUID, null); + if(profile != null) { + client.profile = profile; + } + } catch (IllegalArgumentException ignored) { + // serverName may be a custom value, profile restore from this token is optional + } client.setProperty("launchserver.serverName", info.serverName); return true; } } + public static class ClientProfileTokenVerifier implements RestoreResponse.ExtendedTokenProvider { + private final LaunchServer server; + + public ClientProfileTokenVerifier(LaunchServer server) { + this.server = server; + } + + @Override + public boolean accept(Client client, AuthProviderPair pair, String extendedToken) { + var info = server.authManager.parseClientProfileToken(extendedToken); + if (info == null) { + return false; + } + if(info.authId() != null && pair != null && !info.authId().equals(pair.name)) { + return false; + } + ProfilesProvider.CompletedProfile profile = server.config.profilesProvider.get(info.profileUUID(), info.profileTag()); + if (profile == null) { + return false; + } + client.profile = profile; + return true; + } + } + public static class CheckServerReport { public UUID uuid; public User user; diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java index 9412aeb3e..5e724f998 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreResponse.java @@ -4,6 +4,7 @@ import pro.gravit.launcher.base.events.request.AuthRequestEvent; import pro.gravit.launcher.base.events.request.LauncherRequestEvent; import pro.gravit.launcher.base.events.request.RestoreRequestEvent; +import pro.gravit.launcher.base.events.request.SetProfileRequestEvent; import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.core.AuthCoreProvider; @@ -34,6 +35,7 @@ public static void registerProviders(LaunchServer server) { providers.put("publicKey", new AdvancedProtectHandler.PublicKeyTokenVerifier(server)); providers.put("hardware", new AdvancedProtectHandler.HardwareInfoTokenVerifier(server)); providers.put("checkServer", new AuthManager.CheckServerVerifier(server)); + providers.put(SetProfileRequestEvent.CLIENT_PROFILE_EXTENDED_TOKEN_NAME, new AuthManager.ClientProfileTokenVerifier(server)); registeredProviders = true; } } diff --git a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java index 814a6c28c..ff9745441 100644 --- a/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java +++ b/components/launchserver/src/main/java/pro/gravit/launchserver/socket/response/auth/SetProfileResponse.java @@ -30,7 +30,9 @@ public void execute(ChannelHandlerContext ctx, Client client) { return; } client.profile = profile; - sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag())); + sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag(), + server.authManager.newClientProfileToken(profile.getUuid(), profile.getTag(), client.auth_id), + server.config.netty.security.launcherTokenExpire * 1000)); } @Override