mirror of
https://github.com/WorldEditAxe/eaglerproxy.git
synced 2024-11-10 15:56:05 -08:00
187 lines
8.1 KiB
JavaScript
187 lines
8.1 KiB
JavaScript
import fs from "fs";
|
|
import crypto from "crypto";
|
|
import Constants from "prismarine-auth/src/common/Constants.js";
|
|
const { Endpoints, msalConfig } = Constants;
|
|
import LiveTokenManager from "prismarine-auth/src/TokenManagers/LiveTokenManager.js";
|
|
import JavaTokenManager from "prismarine-auth/src/TokenManagers/MinecraftJavaTokenManager.js";
|
|
import XboxTokenManager from "prismarine-auth/src/TokenManagers/XboxTokenManager.js";
|
|
import MsaTokenManager from "prismarine-auth/src/TokenManagers/MsaTokenManager.js";
|
|
import BedrockTokenManager from "prismarine-auth/src/TokenManagers/MinecraftBedrockTokenManager.js";
|
|
async function retry(methodFn, beforeRetry, times) {
|
|
while (times--) {
|
|
if (times !== 0) {
|
|
try {
|
|
return await methodFn();
|
|
}
|
|
catch (e) {
|
|
if (e instanceof URIError) {
|
|
throw e;
|
|
}
|
|
else {
|
|
// debug(e);
|
|
}
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
await beforeRetry();
|
|
}
|
|
else {
|
|
return await methodFn();
|
|
}
|
|
}
|
|
}
|
|
export class CustomAuthflow {
|
|
username;
|
|
options;
|
|
codeCallback;
|
|
msa;
|
|
doTitleAuth;
|
|
xbl;
|
|
mba;
|
|
mca;
|
|
constructor(username = "", cache, options, codeCallback) {
|
|
this.username = username;
|
|
if (options && !options.flow) {
|
|
throw new Error("Missing 'flow' argument in options. See docs for more information.");
|
|
}
|
|
this.options = options || { flow: "msal" };
|
|
this.initTokenManagers(username, cache);
|
|
this.codeCallback = codeCallback;
|
|
}
|
|
initTokenManagers(username, cache) {
|
|
if (this.options.flow === "live" || this.options.flow === "sisu") {
|
|
if (!this.options.authTitle)
|
|
throw new Error(`Please specify an "authTitle" in Authflow constructor when using ${this.options.flow} flow`);
|
|
this.msa = new LiveTokenManager(this.options.authTitle, ["service::user.auth.xboxlive.com::MBI_SSL"], cache({ cacheName: this.options.flow, username }));
|
|
this.doTitleAuth = true;
|
|
}
|
|
else if (this.options.flow === "msal") {
|
|
const config = Object.assign({ ...msalConfig }, this.options.authTitle ? { auth: { ...msalConfig.auth, clientId: this.options.authTitle } } : {});
|
|
this.msa = new MsaTokenManager(config, ["XboxLive.signin", "offline_access"], cache({ cacheName: "msal", username }));
|
|
}
|
|
else {
|
|
throw new Error(`Unknown flow: ${this.options.flow} (expected "live", "sisu", or "msal")`);
|
|
}
|
|
const keyPair = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
this.xbl = new XboxTokenManager(keyPair, cache({ cacheName: "xbl", username }));
|
|
this.mba = new BedrockTokenManager(cache({ cacheName: "bed", username }));
|
|
this.mca = new JavaTokenManager(cache({ cacheName: "mca", username }));
|
|
}
|
|
static resetTokenCaches(cache) {
|
|
if (!cache)
|
|
throw new Error("You must provide a cache directory to reset.");
|
|
try {
|
|
if (fs.existsSync(cache)) {
|
|
fs.rmSync(cache, { recursive: true });
|
|
return true;
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log("Failed to clear cache dir", e);
|
|
return false;
|
|
}
|
|
}
|
|
async getMsaToken() {
|
|
if (await this.msa.verifyTokens()) {
|
|
const { token } = await this.msa.getAccessToken();
|
|
return token;
|
|
}
|
|
else {
|
|
const ret = await this.msa.authDeviceCode((response) => {
|
|
if (this.codeCallback)
|
|
return this.codeCallback(response);
|
|
console.info("[msa] First time signing in. Please authenticate now:");
|
|
console.info(response.message);
|
|
});
|
|
if (ret.account) {
|
|
console.info(`[msa] Signed in as ${ret.account.username}`);
|
|
}
|
|
else {
|
|
// We don't get extra account data here per scope
|
|
console.info("[msa] Signed in with Microsoft");
|
|
}
|
|
return ret.accessToken;
|
|
}
|
|
}
|
|
async getXboxToken(relyingParty = this.options.relyingParty || Endpoints.XboxRelyingParty, forceRefresh = false) {
|
|
const options = { ...this.options, relyingParty };
|
|
const { xstsToken, userToken, deviceToken, titleToken } = await this.xbl.getCachedTokens(relyingParty);
|
|
if (xstsToken.valid && !forceRefresh) {
|
|
return xstsToken.data;
|
|
}
|
|
if (options.password) {
|
|
const xsts = await this.xbl.doReplayAuth(this.username, options.password, options);
|
|
return xsts;
|
|
}
|
|
return await retry(async () => {
|
|
const msaToken = await this.getMsaToken();
|
|
// sisu flow generates user and title tokens differently to other flows and should also be used to refresh them if they are invalid
|
|
if (options.flow === "sisu" && (!userToken.valid || !deviceToken.valid || !titleToken.valid)) {
|
|
const dt = await this.xbl.getDeviceToken(options);
|
|
const sisu = await this.xbl.doSisuAuth(msaToken, dt, options);
|
|
return sisu;
|
|
}
|
|
const ut = userToken.token ?? (await this.xbl.getUserToken(msaToken, options.flow === "msal"));
|
|
const dt = deviceToken.token ?? (await this.xbl.getDeviceToken(options));
|
|
const tt = titleToken.token ?? (this.doTitleAuth ? await this.xbl.getTitleToken(msaToken, dt) : undefined);
|
|
const xsts = await this.xbl.getXSTSToken({ userToken: ut, deviceToken: dt, titleToken: tt }, options);
|
|
return xsts;
|
|
}, () => {
|
|
this.msa.forceRefresh = true;
|
|
}, 2);
|
|
}
|
|
async getMinecraftJavaToken(options = {}, quit) {
|
|
const response = { token: "", entitlements: {}, profile: {} };
|
|
if (await this.mca.verifyTokens()) {
|
|
const { token } = await this.mca.getCachedAccessToken();
|
|
response.token = token;
|
|
}
|
|
else {
|
|
await retry(async () => {
|
|
const xsts = await this.getXboxToken(Endpoints.PCXSTSRelyingParty);
|
|
response.token = await this.mca.getAccessToken(xsts);
|
|
if (quit.quit)
|
|
return;
|
|
}, () => {
|
|
this.xbl.forceRefresh = true;
|
|
}, 2);
|
|
}
|
|
if (quit.quit)
|
|
return;
|
|
if (options.fetchEntitlements) {
|
|
response.entitlements = await this.mca.fetchEntitlements(response.token).catch((e) => { });
|
|
}
|
|
if (options.fetchProfile) {
|
|
response.profile = await this.mca.fetchProfile(response.token).catch((e) => { });
|
|
}
|
|
if (options.fetchCertificates) {
|
|
response.certificates = await this.mca.fetchCertificates(response.token).catch((e) => []);
|
|
}
|
|
return response;
|
|
}
|
|
async getMinecraftBedrockToken(publicKey) {
|
|
// TODO: Fix cache, in order to do cache we also need to cache the ECDH keys so disable it
|
|
// is this even a good idea to cache?
|
|
if ((await this.mba.verifyTokens()) && false) {
|
|
// eslint-disable-line
|
|
const { chain } = this.mba.getCachedAccessToken();
|
|
return chain;
|
|
}
|
|
else {
|
|
if (!publicKey)
|
|
throw new Error("Need to specifiy a ECDH x509 URL encoded public key");
|
|
return await retry(async () => {
|
|
const xsts = await this.getXboxToken(Endpoints.BedrockXSTSRelyingParty);
|
|
const token = await this.mba.getAccessToken(publicKey, xsts);
|
|
// If we want to auth with a title ID, make sure there's a TitleID in the response
|
|
const body = JSON.parse(Buffer.from(token.chain[1].split(".")[1], "base64").toString());
|
|
if (!body.extraData.titleId && this.doTitleAuth) {
|
|
throw Error("missing titleId in response");
|
|
}
|
|
return token.chain;
|
|
}, () => {
|
|
this.xbl.forceRefresh = true;
|
|
}, 2);
|
|
}
|
|
}
|
|
}
|