package io.gitlab.jfronny.inceptum.launcher.api.account; import io.gitlab.jfronny.commons.ref.R; import io.gitlab.jfronny.gson.compile.annotations.GSerializable; import io.gitlab.jfronny.inceptum.common.*; import io.gitlab.jfronny.inceptum.launcher.LauncherEnv; import io.gitlab.jfronny.inceptum.launcher.gson.MicrosoftAccountAdapter; import io.gitlab.jfronny.inceptum.launcher.model.microsoft.response.*; import io.gitlab.jfronny.inceptum.launcher.gson.MicrosoftAccountMeta; import io.gitlab.jfronny.inceptum.launcher.model.microsoft.*; import java.io.IOException; import java.net.URISyntaxException; import java.util.*; @GSerializable(with = MicrosoftAccountAdapter.class, configure = GsonPreset.Config.class) public class MicrosoftAccount { /** * The username/email/id of the account. */ public String accountId; /** * The account's Minecraft username. */ public String minecraftUsername; /** * The UUID of the account. */ public String uuid; /** * The access token. */ public String accessToken; /** * The Microsoft oauth token. */ public OauthTokenResponse oauthToken; /** * The xsts auth response. */ public XboxLiveAuthResponse xstsAuth; /** * The date that the accessToken expires at. */ public Date accessTokenExpiresAt; /** * If the user must login again. This is usually the result of a failed * accessToken refresh. */ public boolean mustLogin; public MicrosoftAccount(MicrosoftAccountMeta meta) { this.accountId = meta.accountId; this.minecraftUsername = meta.minecraftUsername; this.uuid = meta.uuid; this.accessToken = meta.accessToken; this.oauthToken = meta.oauthToken; this.xstsAuth = meta.xstsAuth; this.accessTokenExpiresAt = meta.accessTokenExpiresAt; this.mustLogin = meta.mustLogin; } public MicrosoftAccountMeta toMeta() { return new MicrosoftAccountMeta( accountId, minecraftUsername, uuid, accessToken, oauthToken, xstsAuth, accessTokenExpiresAt, mustLogin ); } public MicrosoftAccount(OauthTokenResponse oauthTokenResponse, XboxLiveAuthResponse xstsAuthResponse, LoginResponse loginResponse, Profile profile) { update(oauthTokenResponse, xstsAuthResponse, loginResponse, profile); } public void update(OauthTokenResponse oauthTokenResponse, XboxLiveAuthResponse xstsAuthResponse, LoginResponse loginResponse, Profile profile) { this.oauthToken = oauthTokenResponse; this.xstsAuth = xstsAuthResponse; this.accessToken = loginResponse.accessToken; this.minecraftUsername = profile.name; this.uuid = profile.id; this.accountId = loginResponse.username; this.mustLogin = false; this.accessTokenExpiresAt = new Date(); this.accessTokenExpiresAt.time += loginResponse.expiresIn * 1000; } public String getAccessToken() { return accessToken; } public String getSessionToken() { return accessToken; } public String getCurrentUsername() throws IOException, URISyntaxException { Profile profile = MicrosoftAuthAPI.getMcProfile(accessToken); if (profile.name == null) throw new IOException("Got null name"); return profile.name; } public void updateSkinPreCheck() { this.refreshAccessToken(); } public String getSkinUrl() throws IOException, URISyntaxException { Profile profile = MicrosoftAuthAPI.getMcProfile(accessToken); return Optional.of(profile.skins).orElse(new ArrayList<>()) .stream() .filter(s -> s.state.equalsIgnoreCase("ACTIVE")) .findFirst() .map(s -> s.url) .orElse(null); } public boolean refreshAccessToken() { return refreshAccessToken(false); } public boolean refreshAccessToken(boolean force) { try { if (force || new Date().after(this.oauthToken.expiresAt)) { Utils.LOGGER.info("Oauth token expired. Attempting to refresh"); OauthTokenResponse oauthTokenResponse = MicrosoftAuthAPI.refreshAccessToken(oauthToken.refreshToken); if (oauthTokenResponse == null) { mustLogin = true; AccountManager.saveAccounts(); Utils.LOGGER.error("Failed to refresh accessToken"); return false; } this.oauthToken = oauthTokenResponse; AccountManager.saveAccounts(); } if (force || new Date().after(this.xstsAuth.notAfter)) { Utils.LOGGER.info("xsts auth expired. Attempting to get new auth"); XboxLiveAuthResponse xboxLiveAuthResponse = MicrosoftAuthAPI.getXBLToken(this.oauthToken.accessToken); this.xstsAuth = MicrosoftAuthAPI.getXstsToken(xboxLiveAuthResponse.token); if (xstsAuth == null) { mustLogin = true; AccountManager.saveAccounts(); Utils.LOGGER.error("Failed to get XBLToken"); return false; } AccountManager.saveAccounts(); } if (force || new Date().after(this.accessTokenExpiresAt)) { LoginResponse loginResponse = MicrosoftAuthAPI.loginToMinecraft(identityToken); if (loginResponse == null) { mustLogin = true; AccountManager.saveAccounts(); Utils.LOGGER.error("Failed to login to Minecraft"); return false; } this.accessToken = loginResponse.accessToken; this.accountId = loginResponse.username; this.accessTokenExpiresAt = new Date(); this.accessTokenExpiresAt.time += loginResponse.expiresIn * 1000; AccountManager.saveAccounts(); } } catch (Exception e) { mustLogin = true; AccountManager.saveAccounts(); Utils.LOGGER.error("Exception refreshing accessToken", e); return false; } return true; } private String getIdentityToken() { return "XBL3.0 x=" + xstsAuth.displayClaims.xui[0].uhs + ";" + xstsAuth.token; } public boolean ensureAccessTokenValid() { if (mustLogin) { LauncherEnv.showOkCancel("You must login again in order to continue", "Login expired", () -> LauncherEnv.showLoginRefreshPrompt(this), R::nop); return false; } else return true; } @Override public String toString() { return minecraftUsername; } }