onelnchr-mc-auth
Version:
Package to authenticate with minecraft. Fork of minecraft-auth by dommilosz which uses my own appID by default.
310 lines • 13.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.authFlowXBL = exports.authFlowRefresh = exports.authFlow = exports.getMinecraftToken = exports.authXSTS = exports.authXBL = exports.getTokenRefresh = exports.getToken = exports.createUrl = exports.generatePKCEPair = exports.listenForCode = exports.setup = void 0;
const http_1 = __importDefault(require("http"));
const http_client_methods_1 = require("http-client-methods");
const types_1 = require("../types");
const node_crypto_1 = require("node:crypto");
const dotenv = __importStar(require("dotenv"));
dotenv.config();
const appSecret = process.env.MSA_APPSECRET || "";
let config = {
scope: "XboxLive.signin offline_access",
redirectURL: "http://localhost:2626/token",
appID: "055b9745-e08e-4b35-a819-de33bddb4a6d",
appSecret: appSecret,
mode: "Web",
selectAccount: true
};
function setup(_config) {
config = { ...config, mode: "Web", ..._config };
}
exports.setup = setup;
async function createServer(serverConfig) {
return await new Promise((r, j) => {
// @ts-ignore
const server = http_1.default.createServer();
let _success = false;
server.listen(serverConfig.port, serverConfig.host, function () {
if (serverConfig.onstart) {
serverConfig.onstart(serverConfig.host, serverConfig.port);
}
else {
console.log(`MS Token Server is running on http://${serverConfig.host}:${serverConfig.port}`);
}
r(server);
});
server.on("close", function () {
if (serverConfig.onclose) {
serverConfig.onclose(_success);
}
});
server.on("error", (err) => {
j(err);
});
server.fullClose = function (success) {
_success = success;
if (server.abort) {
serverConfig.abort?.removeEventListener("abort", server.abort);
}
if (server.serverTimeout) {
clearTimeout(server.serverTimeout);
server.serverTimeout = undefined;
}
server.close();
};
return server;
});
}
async function _listenForCode(server, serverConfig) {
return await new Promise((r, j) => {
server.serverTimeout = setTimeout(async () => {
server.fullClose(false);
j("Timeout error");
}, serverConfig.timeout);
if (serverConfig.abort) {
server.abort = function () {
server.fullClose(false);
j("Aborted");
};
if (serverConfig.abort.aborted) {
server.abort();
}
else {
serverConfig.abort.addEventListener("abort", server.abort);
}
}
async function requestListener(req, res) {
if (!req.url)
return;
res.setHeader("Connection", "close");
switch (req.url.split('?')[0]) {
case '/token':
if (serverConfig.redirectAfterAuth) {
res.writeHead(301, {
Location: serverConfig.redirectAfterAuth,
});
}
res.end();
server.fullClose(true);
if (req.url.includes('?code')) {
let code = req.url.split('?code=')[1];
if (serverConfig.oncode) {
serverConfig.oncode(code);
}
r(code);
}
if (req.url.includes('?error')) {
const error = req.url.split('?error=')[1].split('&')[0];
const error_description = decodeURIComponent(req.url.split('&error_description=')[1]);
j(new types_1.AuthenticationError(error, error_description, ''));
}
break;
case '/url':
res.writeHead(200);
res.end(createUrl(serverConfig.pkcePair));
break;
case '/close':
res.writeHead(200);
res.end();
server.fullClose(false);
j("Closed");
break;
case '/auth':
res.writeHead(302, {
Location: createUrl(serverConfig.pkcePair),
});
res.end();
break;
default:
res.writeHead(302, {
Location: createUrl(serverConfig.pkcePair),
});
res.end();
break;
}
}
server.on('request', requestListener);
});
}
async function listenForCode(_serverConfig = {}) {
const serverConfig = { port: 2626, host: "localhost", timeout: 200 * 1000, redirectAfterAuth: "https://onelauncher.zmito.eu/launcher/afterauth", ..._serverConfig };
const server = await createServer(serverConfig);
return await _listenForCode(server, serverConfig);
}
exports.listenForCode = listenForCode;
function generatePKCEPair() {
const NUM_OF_BYTES = 32;
const HASH_ALG = "sha256";
const randomVerifier = (0, node_crypto_1.randomBytes)(NUM_OF_BYTES).toString('hex');
const hash = (0, node_crypto_1.createHash)(HASH_ALG).update(randomVerifier).digest('base64');
const challenge = hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); // Clean base64 to make it URL safe
return { verifier: randomVerifier, challenge };
}
exports.generatePKCEPair = generatePKCEPair;
function createUrl(PKCEPair) {
let encodedID = encodeURIComponent(config.appID ?? "");
let encodedUrl = encodeURIComponent(config.redirectURL);
let encodedScope = encodeURIComponent(config.scope);
let url = `https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=${encodedID}&response_type=code&redirect_uri=${encodedUrl}&scope=${encodedScope}`;
if (PKCEPair) {
let encodedChallenge = encodeURIComponent(PKCEPair.challenge);
url += `&code_challenge=${encodedChallenge}&code_challenge_method=S256`;
}
if (config.selectAccount) {
url += `&prompt=select_account`;
}
return url;
}
exports.createUrl = createUrl;
async function getToken(authCode, PKCEPair) {
let encodedID = encodeURIComponent(config.appID);
let encodedUrl = encodeURIComponent(config.redirectURL);
let url = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token';
let body = `client_id=${encodedID}&code=${authCode}&grant_type=authorization_code&redirect_uri=${encodedUrl}`;
if (config.mode === "Web") {
if (!config.appSecret) {
throw new types_1.AuthenticationError("App secret was not provided", "App secret was not provided in getToken");
}
let encodedSecret = encodeURIComponent(config.appSecret);
url = "https://login.live.com/oauth20_token.srf";
body = `client_id=${encodedID}&client_secret=${encodedSecret}&code=${authCode}&grant_type=authorization_code&redirect_uri=${encodedUrl}`;
}
if (PKCEPair) {
let encodedVerifier = encodeURIComponent(PKCEPair.verifier);
body += `&code_verifier=${encodedVerifier}&code_challenge_method=S256`;
}
let headers = { "Content-Type": "application/x-www-form-urlencoded" };
if (config.mode === "SPA") {
headers["Origin"] = config.redirectURL;
}
let response = await (0, http_client_methods_1.HttpPost)(url, body, headers);
let jsonResponse = JSON.parse(response);
if (jsonResponse.error) {
throw new types_1.AuthenticationError(jsonResponse.error, jsonResponse.error_description, jsonResponse.correlation_id);
}
return jsonResponse;
}
exports.getToken = getToken;
async function getTokenRefresh(refreshToken) {
let encodedID = encodeURIComponent(config.appID ?? "");
let encodedUrl = encodeURIComponent(config.redirectURL);
let url = 'https://login.live.com/oauth20_token.srf';
let body = `client_id=${encodedID}&refresh_token=${refreshToken}&grant_type=refresh_token&redirect_uri=${encodedUrl}`;
if (config.mode === "Web") {
if (!config.appSecret) {
throw new types_1.AuthenticationError("App secret was not provided", "App secret was not provided in getToken");
}
let encodedSecret = encodeURIComponent(config.appSecret);
url = "https://login.live.com/oauth20_token.srf";
body = `client_id=${encodedID}&client_secret=${encodedSecret}&refresh_token=${refreshToken}&grant_type=refresh_token&redirect_uri=${encodedUrl}`;
}
let headers = { "Content-Type": "application/x-www-form-urlencoded" };
if (config.mode === "SPA") {
headers["Origin"] = config.redirectURL;
}
const response = await (0, http_client_methods_1.HttpPost)(url, body, headers);
const jsonResponse = JSON.parse(response);
if (jsonResponse.error) {
throw new types_1.AuthenticationError(jsonResponse.error, jsonResponse.error_description, jsonResponse.correlation_id);
}
return jsonResponse;
}
exports.getTokenRefresh = getTokenRefresh;
async function authXBL(accessToken) {
const body = {
Properties: {
AuthMethod: 'RPS',
SiteName: 'user.auth.xboxlive.com',
RpsTicket: `d=${accessToken}`, // your access token from step 2 here
},
RelyingParty: 'http://auth.xboxlive.com',
TokenType: 'JWT',
};
const response = await (0, http_client_methods_1.HttpPost)('https://user.auth.xboxlive.com/user/authenticate', JSON.stringify(body), {
'Content-Type': 'application/json',
Accept: 'application/json',
});
const jsonResponse = JSON.parse(response);
return jsonResponse;
}
exports.authXBL = authXBL;
async function authXSTS(xblToken) {
const body = {
Properties: {
SandboxId: 'RETAIL',
UserTokens: [`${xblToken}`],
},
RelyingParty: 'rp://api.minecraftservices.com/',
TokenType: 'JWT',
};
const response = await (0, http_client_methods_1.HttpPost)('https://xsts.auth.xboxlive.com/xsts/authorize', JSON.stringify(body), {
'Content-Type': 'application/json',
Accept: 'application/json',
});
const jsonResponse = JSON.parse(response);
if (jsonResponse.XErr) {
throw new types_1.AuthenticationError(String(jsonResponse.XErr), jsonResponse.Message, jsonResponse.Redirect);
}
return jsonResponse;
}
exports.authXSTS = authXSTS;
async function getMinecraftToken(xstsToken, uhs) {
const body = {
identityToken: `XBL3.0 x=${uhs};${xstsToken}`,
};
const response = await (0, http_client_methods_1.HttpPost)('https://api.minecraftservices.com/authentication/login_with_xbox', JSON.stringify(body), {
'Content-Type': 'application/json',
Accept: 'application/json',
});
const jsonResponse = JSON.parse(response);
if (jsonResponse.errorMessage) {
throw new types_1.AuthenticationError("Error when getting minecraft token", jsonResponse.errorMessage, jsonResponse.path);
}
return jsonResponse;
}
exports.getMinecraftToken = getMinecraftToken;
async function authFlow(authCode, PKCEPair) {
const tokenRes = await getToken(authCode, PKCEPair);
return await authFlowXBL(tokenRes.access_token, tokenRes.refresh_token);
}
exports.authFlow = authFlow;
async function authFlowRefresh(refresh_token) {
const tokenRes = await getTokenRefresh(refresh_token);
return await authFlowXBL(tokenRes.access_token, tokenRes.refresh_token);
}
exports.authFlowRefresh = authFlowRefresh;
async function authFlowXBL(token, refresh_token) {
const xblRes = await authXBL(token);
const xstsRes = await authXSTS(xblRes.Token);
const mcToken = await getMinecraftToken(xstsRes.Token, xblRes.DisplayClaims.xui[0].uhs);
return { access_token: mcToken.access_token, refresh_token };
}
exports.authFlowXBL = authFlowXBL;
//# sourceMappingURL=MicrosoftAuth.js.map