minecraft-auth
Version:
Package to authenticate with minecraft using traditional yggdrasil, new microsoft authentication and non-premium.
287 lines • 12.3 kB
JavaScript
;
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");
let config = {
scope: "XboxLive.signin offline_access",
redirectURL: "http://localhost:8080/token",
appID: "747bf062-ab9c-4690-842d-a77d18d4cf82",
mode: "SPA", selectAccount: false
};
function setup(_config) {
if (_config.appSecret) {
config = { ...config, mode: "Web", ..._config };
}
else {
config = { ...config, mode: "SPA", ..._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: 8080, host: "localhost", timeout: 30 * 1000, ..._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