diff --git a/README.md b/README.md
index 8eb7dce..1c87815 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,9 @@ const bedrock = require('bedrock-protocol')
const server = new bedrock.createServer({
host: '0.0.0.0', // optional. host to bind as.
port: 19132, // optional
- version: '1.16.220' // optional. The server version, latest if not specified.
+ version: '1.16.220', // optional. The server version, latest if not specified.
+ // Optional for some servers which verify the title ID:
+ // authTitle: bedrock.title.MinecraftNintendoSwitch
})
server.on('connect', client => {
diff --git a/docs/API.md b/docs/API.md
index 071cb6b..23de8f1 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -13,6 +13,7 @@ Returns a `Client` instance and connects to the server.
| version | *optional* | Version to connect as.
(Future feature, see [#69][1]) If not specified, should automatically match server version.
(Current feature) Defaults to latest version. |
| offline | *optional* | default to **false**. Set this to true to disable Microsoft/Xbox auth. |
| username | Conditional | Required if `offline` set to true : Username to connect to server as. |
+| authTitle | *optional* | The title ID to connect as, see the README for usage. |
| connectTimeout | *optional* | default to **9000ms**. How long to wait in milliseconds while trying to connect to server. |
| onMsaCode | *optional* | Callback called when signing in with a microsoft account with device code auth, `data` is an object documented [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code#device-authorization-response) |
| profilesFolder | *optional* | Where to store cached authentication tokens. Defaults to .minecraft, or the node_modules folder if not found. |
diff --git a/docs/FAQ.md b/docs/FAQ.md
index c4a5f06..556eedf 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -4,4 +4,7 @@ This issue occurs due to loopback restrictions on Windows 10 UWP apps. To lift t
```ps
CheckNetIsolation LoopbackExempt -a -n="Microsoft.MinecraftUWP_8wekyb3d8bbwe"
-```
\ No newline at end of file
+```
+## Kicked during login
+
+Some servers can kick you if you don't set `authTitle` as explained in the README.
\ No newline at end of file
diff --git a/examples/clientReadmeExample.js b/examples/clientReadmeExample.js
index 32cffaf..09a29b5 100644
--- a/examples/clientReadmeExample.js
+++ b/examples/clientReadmeExample.js
@@ -4,7 +4,9 @@ const client = bedrock.createClient({
host: 'localhost', // optional
port: 19132, // optional, default 19132
username: 'Notch', // the username you want to join as, optional if online mode
- offline: false // optional, default false. if true, do not login with Xbox Live. You will not be asked to sign-in if set to true.
+ offline: false, // optional, default false. if true, do not login with Xbox Live. You will not be asked to sign-in if set to true.
+ // Optional for some servers which verify the title ID:
+ // authTitle: bedrock.title.MinecraftNintendoSwitch
})
client.on('text', (packet) => { // Listen for chat messages and echo them back.
diff --git a/index.d.ts b/index.d.ts
index 26b764a..b08e207 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -3,6 +3,8 @@ import EventEmitter from "events"
declare module "bedrock-protocol" {
type Version = '1.16.220' | '1.16.210' | '1.16.201'
+ enum title { MinecraftNintendoSwitch, MinecraftJava }
+
export interface Options {
// The string version to start the client or server as
version: number,
diff --git a/index.js b/index.js
index a4e2f49..e3241a5 100644
--- a/index.js
+++ b/index.js
@@ -9,6 +9,7 @@ const { Server } = require('./src/server')
const { Relay } = require('./src/relay')
const { createClient, ping } = require('./src/createClient')
const { createServer } = require('./src/createServer')
+const Title = require('./src/client/titles')
module.exports = {
Client,
@@ -16,5 +17,6 @@ module.exports = {
Relay,
createClient,
ping,
- createServer
+ createServer,
+ title: Title
}
diff --git a/package.json b/package.json
index 1022e71..ba7d7d6 100644
--- a/package.json
+++ b/package.json
@@ -21,15 +21,17 @@
],
"license": "MIT",
"dependencies": {
- "@azure/msal-node": "^1.0.0-beta.6",
+ "@azure/msal-node": "^1.1.0",
"@xboxreplay/xboxlive-auth": "^3.3.3",
"debug": "^4.3.1",
+ "jose-node-cjs-runtime": "^3.12.1",
"jsonwebtoken": "^8.5.1",
"jsp-raknet": "^2.1.0",
"minecraft-folder-path": "^1.1.0",
"node-fetch": "^2.6.1",
"prismarine-nbt": "^1.5.0",
"protodef": "extremeheat/node-protodef#patch-1",
+ "smart-buffer": "^4.1.0",
"uuid-1345": "^1.0.2"
},
"optionalDependencies": {
diff --git a/src/client/auth.js b/src/client/auth.js
index 2d8d30e..d7ffc8d 100644
--- a/src/client/auth.js
+++ b/src/client/auth.js
@@ -66,7 +66,7 @@ async function authenticatePassword (client, options) {
*/
async function authenticateDeviceCode (client, options) {
try {
- const flow = new MsAuthFlow(options.username, options.profilesFolder, options.onMsaCode)
+ const flow = new MsAuthFlow(options.username, options.profilesFolder, options, options.onMsaCode)
const chain = await flow.getMinecraftToken(client.clientX509)
// console.log('Chain', chain)
diff --git a/src/client/authConstants.js b/src/client/authConstants.js
index 65c2575..1f5066f 100644
--- a/src/client/authConstants.js
+++ b/src/client/authConstants.js
@@ -1,4 +1,10 @@
module.exports = {
XSTSRelyingParty: 'https://multiplayer.minecraft.net/',
- MinecraftAuth: 'https://multiplayer.minecraft.net/authentication'
+ MinecraftAuth: 'https://multiplayer.minecraft.net/authentication',
+ XboxDeviceAuth: 'https://device.auth.xboxlive.com/device/authenticate',
+ XboxTitleAuth: 'https://title.auth.xboxlive.com/title/authenticate',
+ XstsAuthorize: 'https://xsts.auth.xboxlive.com/xsts/authorize',
+
+ LiveDeviceCodeRequest: 'https://login.live.com/oauth20_connect.srf',
+ LiveTokenRequest: 'https://login.live.com/oauth20_token.srf'
}
diff --git a/src/client/authFlow.js b/src/client/authFlow.js
index 7e496ba..1f67aa2 100644
--- a/src/client/authFlow.js
+++ b/src/client/authFlow.js
@@ -4,7 +4,7 @@ const fs = require('fs')
const debug = require('debug')('minecraft-protocol')
const mcDefaultFolderPath = require('minecraft-folder-path')
const authConstants = require('./authConstants')
-const { MsaTokenManager, XboxTokenManager, MinecraftTokenManager } = require('./tokens')
+const { LiveTokenManager, MsaTokenManager, XboxTokenManager, MinecraftTokenManager } = require('./tokens')
// Initialize msal
// Docs: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/request.md#public-apis-1
@@ -30,7 +30,8 @@ async function retry (methodFn, beforeRetry, times) {
}
class MsAuthFlow {
- constructor (username, cacheDir, codeCallback) {
+ constructor (username, cacheDir, options = {}, codeCallback) {
+ this.options = options
this.initTokenCaches(username, cacheDir)
this.codeCallback = codeCallback
}
@@ -50,14 +51,22 @@ class MsAuthFlow {
}
const cachePaths = {
+ live: path.join(cachePath, `./${hash}_live-cache.json`),
msa: path.join(cachePath, `./${hash}_msa-cache.json`),
xbl: path.join(cachePath, `./${hash}_xbl-cache.json`),
bed: path.join(cachePath, `./${hash}_bed-cache.json`)
}
- const scopes = ['XboxLive.signin', 'offline_access']
- this.msa = new MsaTokenManager(msalConfig, scopes, cachePaths.msa)
- this.xbl = new XboxTokenManager(authConstants.XSTSRelyingParty, cachePaths.xbl)
+ if (this.options.authTitle) { // Login with login.live.com
+ const scopes = ['service::user.auth.xboxlive.com::MBI_SSL']
+ this.msa = new LiveTokenManager(this.options.authTitle, scopes, cachePaths.live)
+ } else { // Login with microsoftonline.com
+ const scopes = ['XboxLive.signin', 'offline_access']
+ this.msa = new MsaTokenManager(msalConfig, scopes, cachePaths.msa)
+ }
+
+ const keyPair = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' })
+ this.xbl = new XboxTokenManager(authConstants.XSTSRelyingParty, keyPair, cachePaths.xbl)
this.mca = new MinecraftTokenManager(cachePaths.bed)
}
@@ -87,7 +96,11 @@ class MsAuthFlow {
if (this.codeCallback) this.codeCallback(response)
})
- console.info(`[msa] Signed in as ${ret.account.username}`)
+ 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')
+ }
debug('[msa] got auth result', ret)
return ret.accessToken
@@ -96,15 +109,23 @@ class MsAuthFlow {
async getXboxToken () {
if (await this.xbl.verifyTokens()) {
- debug('[xbl] Using existing tokens')
+ debug('[xbl] Using existing XSTS token')
return this.xbl.getCachedXstsToken().data
} else {
debug('[xbl] Need to obtain tokens')
return await retry(async () => {
const msaToken = await this.getMsaToken()
- const ut = await this.xbl.getUserToken(msaToken)
- const xsts = await this.xbl.getXSTSToken(ut)
- return xsts
+ const ut = await this.xbl.getUserToken(msaToken, !this.options.authTitle)
+
+ if (this.options.authTitle) {
+ const deviceToken = await this.xbl.getDeviceToken({ DeviceType: 'Nintendo', Version: '0.0.0' })
+ const titleToken = await this.xbl.getTitleToken(msaToken, deviceToken)
+ const xsts = await this.xbl.getXSTSToken(ut, deviceToken, titleToken)
+ return xsts
+ } else {
+ const xsts = await this.xbl.getXSTSToken(ut)
+ return xsts
+ }
}, () => { this.msa.forceRefresh = true }, 2)
}
}
@@ -122,6 +143,12 @@ class MsAuthFlow {
const xsts = await this.getXboxToken()
debug('[xbl] xsts data', xsts)
const token = await this.mca.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())
+ console.log(this.options.authTitle)
+ if (!body.extraData.titleId && this.options.authTitle) {
+ throw Error('missing titleId in response')
+ }
return token.chain
}, () => { this.xbl.forceRefresh = true }, 2)
}
diff --git a/src/client/titles.js b/src/client/titles.js
new file mode 100644
index 0000000..66ca06c
--- /dev/null
+++ b/src/client/titles.js
@@ -0,0 +1,4 @@
+module.exports = {
+ MinecraftNintendoSwitch: '00000000441cc96b',
+ MinecraftJava: '00000000402b5328'
+}
diff --git a/src/client/tokens.js b/src/client/tokens.js
index 5dda941..f4436d6 100644
--- a/src/client/tokens.js
+++ b/src/client/tokens.js
@@ -5,6 +5,166 @@ const fs = require('fs')
const path = require('path')
const fetch = require('node-fetch')
const authConstants = require('./authConstants')
+const crypto = require('crypto')
+const { nextUUID } = require('../datatypes/util')
+const { SmartBuffer } = require('smart-buffer')
+const jose = require('jose-node-cjs-runtime/jwk/from_key_like')
+
+class LiveTokenManager {
+ constructor (clientId, scopes, cacheLocation) {
+ this.clientId = clientId
+ this.scopes = scopes
+ this.cacheLocation = cacheLocation
+ this.reloadCache()
+ }
+
+ reloadCache () {
+ try {
+ this.cache = require(this.cacheLocation)
+ } catch (e) {
+ this.cache = {}
+ fs.writeFileSync(this.cacheLocation, JSON.stringify(this.cache))
+ }
+ }
+
+ async verifyTokens () {
+ if (this.forceRefresh) try { await this.refreshTokens() } catch { }
+ const at = this.getAccessToken()
+ const rt = this.getRefreshToken()
+ if (!at || !rt) {
+ return false
+ }
+ debug('[live] have at, rt', at, rt)
+ if (at.valid && rt) {
+ return true
+ } else {
+ try {
+ await this.refreshTokens()
+ return true
+ } catch (e) {
+ console.warn('Error refreshing token', e) // TODO: looks like an error happens here
+ return false
+ }
+ }
+ }
+
+ async refreshTokens () {
+ const rtoken = this.getRefreshToken()
+ if (!rtoken) {
+ throw new Error('Cannot refresh without refresh token')
+ }
+
+ const codeRequest = {
+ method: 'post',
+ body: new URLSearchParams({ scope: this.scopes, client_id: this.clientId, grant_type: 'refresh_token', refresh_token: rtoken.token }).toString(),
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ credentials: 'include' // This cookie handler does not work on node-fetch ...
+ }
+
+ const token = await fetch(authConstants.LiveTokenRequest, codeRequest).then(checkStatus)
+ this.updateCachce(token)
+ return token
+ }
+
+ getAccessToken () {
+ const token = this.cache.token
+ if (!token) return
+ const until = new Date(token.obtainedOn + token.expires_in) - Date.now()
+ const valid = until > 1000
+ return { valid, until: until, token: token.access_token }
+ }
+
+ getRefreshToken () {
+ const token = this.cache.token
+ if (!token) return
+ const until = new Date(token.obtainedOn + token.expires_in) - Date.now()
+ const valid = until > 1000
+ return { valid, until: until, token: token.refresh_token }
+ }
+
+ updateCachce (data) {
+ data.obtainedOn = Date.now()
+ this.cache.token = data
+ fs.writeFileSync(this.cacheLocation, JSON.stringify(this.cache))
+ }
+
+ async authDeviceCode (deviceCodeCallback) {
+ const acquireTime = Date.now()
+ const codeRequest = {
+ method: 'post',
+ body: new URLSearchParams({ scope: this.scopes, client_id: this.clientId, response_type: 'device_code' }).toString(),
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ credentials: 'include' // This cookie handler does not work on node-fetch ...
+ }
+
+ debug('Requesting live device token', codeRequest)
+
+ const cookies = []
+
+ const res = await fetch(authConstants.LiveDeviceCodeRequest, codeRequest)
+ .then(res => {
+ if (res.status !== 200) {
+ res.text().then(console.warn)
+ throw Error('Failed to request live.com device code')
+ }
+ for (const cookie of Object.values(res.headers.raw()['set-cookie'])) {
+ const [keyval] = cookie.split(';')
+ cookies.push(keyval)
+ }
+ return res
+ })
+ .then(checkStatus).then(resp => {
+ resp.message = `To sign in, use a web browser to open the page ${resp.verification_uri} and enter the code ${resp.user_code} to authenticate.`
+ deviceCodeCallback(resp)
+ return resp
+ })
+ const expireTime = acquireTime + (res.expires_in * 1000) - 100 /* for safety */
+
+ this.polling = true
+ while (this.polling && expireTime > Date.now()) {
+ await new Promise(resolve => setTimeout(resolve, res.interval * 1000))
+ try {
+ const verifi = {
+ method: 'post',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ Cookie: cookies.join('; ')
+ },
+ body: new URLSearchParams({
+ client_id: this.clientId,
+ device_code: res.device_code,
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
+ }).toString()
+ }
+
+ const token = await fetch(authConstants.LiveTokenRequest + '?client_id=' + this.clientId, verifi)
+ .then(res => res.json()).then(res => {
+ if (res.error) {
+ if (res.error === 'authorization_pending') {
+ debug('[live] Still waiting:', res.error_description)
+ } else {
+ throw Error(`Failed to acquire authorization code from device token (${res.error}) - ${res.error_description}`)
+ }
+ } else {
+ return res
+ }
+ })
+ if (!token) continue
+ this.updateCachce(token)
+ this.polling = false
+ return { accessToken: token.access_token }
+ } catch (e) {
+ console.debug(e)
+ }
+ }
+ this.polling = false
+ throw Error('Authenitcation failed, timed out')
+ }
+}
// Manages Microsoft account tokens
class MsaTokenManager {
@@ -103,7 +263,7 @@ class MsaTokenManager {
}
async verifyTokens () {
- if (this.forceRefresh) try { await this.refreshTokens() } catch {}
+ if (this.forceRefresh) try { await this.refreshTokens() } catch { }
const at = this.getAccessToken()
const rt = this.getRefreshToken()
if (!at || !rt) {
@@ -149,14 +309,20 @@ class MsaTokenManager {
// Manages Xbox Live tokens for xboxlive.com
class XboxTokenManager {
- constructor (relyingParty, cacheLocation) {
+ constructor (relyingParty, ecKey, cacheLocation) {
this.relyingParty = relyingParty
+ this.key = ecKey
+ jose.fromKeyLike(ecKey.publicKey).then(jwk => {
+ this.jwk = { ...jwk, alg: 'ES256', use: 'sig' }
+ })
this.cacheLocation = cacheLocation || path.join(__dirname, './xbl-cache.json')
try {
this.cache = require(this.cacheLocation)
} catch (e) {
this.cache = {}
}
+
+ this.headers = { 'Cache-Control': 'no-store, must-revalidate, no-cache', 'x-xbl-contract-version': 1 }
}
getCachedUserToken () {
@@ -209,24 +375,145 @@ class XboxTokenManager {
return false
}
- async getUserToken (msaAccessToken) {
+ async getUserToken (msaAccessToken, azure) {
debug('[xbl] obtaining xbox token with ms token', msaAccessToken)
- if (!msaAccessToken.startsWith('d=')) { msaAccessToken = 'd=' + msaAccessToken }
+ msaAccessToken = (azure ? 'd=' : 't=') + msaAccessToken
const xblUserToken = await XboxLiveAuth.exchangeRpsTicketForUserToken(msaAccessToken)
this.setCachedUserToken(xblUserToken)
debug('[xbl] user token:', xblUserToken)
return xblUserToken
}
- async getXSTSToken (xblUserToken) {
- debug('[xbl] obtaining xsts token with xbox user token', xblUserToken.Token)
- const xsts = await XboxLiveAuth.exchangeUserTokenForXSTSIdentity(
- xblUserToken.Token, { XSTSRelyingParty: this.relyingParty, raw: false }
- )
+ // Make signature for the data being sent to server with our private key; server is sent our public key in plaintext
+ sign (url, authorizationToken, payload) {
+ // Their backend servers use Windows epoch timestamps, account for that. The server is very picky,
+ // bad percision or wrong epoch may fail the request.
+ const windowsTimestamp = (BigInt((Date.now() / 1000) | 0) + 11644473600n) * 10000000n
+ // Only the /uri?and-query-string
+ const pathAndQuery = new URL(url).pathname
+
+ // Allocate the buffer for signature, TS, path, tokens and payload and NUL termination
+ const allocSize = /* sig */ 5 + /* ts */ 9 + /* POST */ 5 + pathAndQuery.length + 1 + authorizationToken.length + 1 + payload.length + 1
+ const buf = SmartBuffer.fromSize(allocSize)
+ buf.writeInt32BE(1) // Policy Version
+ buf.writeUInt8(0)
+ buf.writeBigUInt64BE(windowsTimestamp)
+ buf.writeUInt8(0) // null term
+ buf.writeStringNT('POST')
+ buf.writeStringNT(pathAndQuery)
+ buf.writeStringNT(authorizationToken)
+ buf.writeStringNT(payload)
+
+ // Get the signature from the payload
+ const signature = crypto.sign('SHA256', buf.toBuffer(), { key: this.key.privateKey, dsaEncoding: 'ieee-p1363' })
+
+ const header = SmartBuffer.fromSize(signature.length + 12)
+ header.writeInt32BE(1) // Policy Version
+ header.writeBigUInt64BE(windowsTimestamp)
+ header.writeBuffer(signature) // Add signature at end of header
+
+ return header.toBuffer()
+ }
+
+ // If we don't need Xbox Title Authentication, we can have xboxreplay lib
+ // handle the auth, otherwise we need to build the request ourselves with
+ // the extra token data.
+ async getXSTSToken (xblUserToken, deviceToken, titleToken) {
+ if (deviceToken && titleToken) return this.getXSTSTokenWithTitle(xblUserToken, deviceToken, titleToken)
+
+ debug('[xbl] obtaining xsts token with xbox user token (with XboxReplay)', xblUserToken.Token)
+ const xsts = await XboxLiveAuth.exchangeUserTokenForXSTSIdentity(xblUserToken.Token, { XSTSRelyingParty: this.relyingParty, raw: false })
this.setCachedXstsToken(xsts)
debug('[xbl] xsts', xsts)
return xsts
}
+
+ async getXSTSTokenWithTitle (xblUserToken, deviceToken, titleToken, optionalDisplayClaims) {
+ const userToken = xblUserToken.Token
+ debug('[xbl] obtaining xsts token with xbox user token', userToken)
+
+ const payload = {
+ RelyingParty: this.relyingParty,
+ TokenType: 'JWT',
+ Properties: {
+ UserTokens: [userToken],
+ DeviceToken: deviceToken,
+ TitleToken: titleToken,
+ OptionalDisplayClaims: optionalDisplayClaims,
+ ProofKey: this.jwk,
+ SandboxId: 'RETAIL'
+ }
+ }
+
+ const body = JSON.stringify(payload)
+ const signature = this.sign(authConstants.XstsAuthorize, '', body).toString('base64')
+
+ const headers = { ...this.headers, Signature: signature }
+
+ const ret = await fetch(authConstants.XstsAuthorize, { method: 'post', headers, body }).then(checkStatus)
+ const xsts = {
+ userXUID: ret.DisplayClaims.xui[0].xid || null,
+ userHash: ret.DisplayClaims.xui[0].uhs,
+ XSTSToken: ret.Token,
+ expiresOn: ret.NotAfter
+ }
+
+ this.setCachedXstsToken(xsts)
+ debug('[xbl] xsts', xsts)
+ return xsts
+ }
+
+ /**
+ * Requests an Xbox Live-related device token that uniquely links the XToken (aka xsts token)
+ * @param {{ DeviceType, Version }} asDevice The hardware type and version to auth as, for example Android or Nintendo
+ */
+ async getDeviceToken (asDevice) {
+ const payload = {
+ Properties: {
+ AuthMethod: 'ProofOfPossession',
+ Id: `{${nextUUID()}}`,
+ DeviceType: asDevice.DeviceType || 'Android',
+ SerialNumber: `{${nextUUID()}}`,
+ Version: asDevice.Version || '10',
+ ProofKey: this.jwk
+ },
+ RelyingParty: 'http://auth.xboxlive.com',
+ TokenType: 'JWT'
+ }
+
+ const body = JSON.stringify(payload)
+
+ const signature = this.sign(authConstants.XboxDeviceAuth, '', body).toString('base64')
+
+ const headers = { ...this.headers, Signature: signature }
+
+ const ret = await fetch(authConstants.XboxDeviceAuth, { method: 'post', headers, body }).then(checkStatus)
+ debug('Xbox Device Token', ret)
+ return ret.Token
+ }
+
+ // This *only* works with live.com auth
+ async getTitleToken (msaAccessToken, deviceToken) {
+ const payload = {
+ Properties: {
+ AuthMethod: 'RPS',
+ DeviceToken: deviceToken,
+ RpsTicket: 't=' + msaAccessToken,
+ SiteName: 'user.auth.xboxlive.com',
+ ProofKey: this.jwk
+ },
+ RelyingParty: 'http://auth.xboxlive.com',
+ TokenType: 'JWT'
+ }
+ const body = JSON.stringify(payload)
+ const signature = this.sign(authConstants.XboxTitleAuth, '', body).toString('base64')
+
+ const headers = { ...this.headers, Signature: signature }
+
+ const ret = await fetch(authConstants.XboxTitleAuth, { method: 'post', headers, body }).then(checkStatus)
+ debug('Xbox Title Token', ret)
+ return ret.Token
+ }
}
// Manages Minecraft tokens for sessionserver.mojang.com
@@ -276,16 +563,14 @@ class MinecraftTokenManager {
async getAccessToken (clientPublicKey, xsts) {
debug('[mc] authing to minecraft', clientPublicKey, xsts)
- const getFetchOptions = {
- headers: {
- 'Content-Type': 'application/json',
- 'User-Agent': 'node-minecraft-protocol',
- Authorization: `XBL3.0 x=${xsts.userHash};${xsts.XSTSToken}`
- }
+ const headers = {
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'node-minecraft-protocol',
+ Authorization: `XBL3.0 x=${xsts.userHash};${xsts.XSTSToken}`
}
const MineServicesResponse = await fetch(authConstants.MinecraftAuth, {
method: 'post',
- ...getFetchOptions,
+ headers,
body: JSON.stringify({ identityPublicKey: clientPublicKey })
}).then(checkStatus)
@@ -299,8 +584,9 @@ function checkStatus (res) {
if (res.ok) { // res.status >= 200 && res.status < 300
return res.json()
} else {
+ debug('Request fail', res)
throw Error(res.statusText)
}
}
-module.exports = { MsaTokenManager, XboxTokenManager, MinecraftTokenManager }
+module.exports = { LiveTokenManager, MsaTokenManager, XboxTokenManager, MinecraftTokenManager }