mirror of
https://github.com/PrismarineJS/node-minecraft-protocol.git
synced 2024-11-14 19:04:59 -05:00
Integrate Authentication for Microsoft Accounts (#788)
* add node-fetch and @xboxreplay/xboxlive-auth for microsoft/xbox auth * decide which authentication to use based on options; if options.auth === 'microsoft' then use microsoft/xbox auth, else use Yggdrasil until they kill that. * push working auth * commentary * eslint does not like me :( * User-Agent works just fine without version * linting = 95% of development * revert changes to encrypt.js * set haveCredentials to whether or not we have a token. Technically this will always be true so...? * eslint * mod+create: api + example * mod: readme.md
This commit is contained in:
parent
72aef6788c
commit
5d723e9a04
7 changed files with 120 additions and 2 deletions
|
@ -77,7 +77,8 @@ Returns a `Client` instance and perform login.
|
||||||
* password : can be omitted (if the tokens and profilesFolder are also omitted then it tries to connect in offline mode)
|
* password : can be omitted (if the tokens and profilesFolder are also omitted then it tries to connect in offline mode)
|
||||||
* host : default to localhost
|
* host : default to localhost
|
||||||
* clientToken : generated if a password is given
|
* clientToken : generated if a password is given
|
||||||
* accessToken : generated if a password is given
|
* accessToken : generated if a password or microsoft account is given
|
||||||
|
* auth : the type of auth server to use, either 'microsoft' or 'mojang'. default to 'mojang'
|
||||||
* authServer : auth server, default to https://authserver.mojang.com
|
* authServer : auth server, default to https://authserver.mojang.com
|
||||||
* sessionServer : session server, default to https://sessionserver.mojang.com
|
* sessionServer : session server, default to https://sessionserver.mojang.com
|
||||||
* keepAlive : send keep alive packets : default to true
|
* keepAlive : send keep alive packets : default to true
|
||||||
|
|
|
@ -65,6 +65,7 @@ var client = mc.createClient({
|
||||||
port: 25565, // optional
|
port: 25565, // optional
|
||||||
username: "email@example.com",
|
username: "email@example.com",
|
||||||
password: "12345678",
|
password: "12345678",
|
||||||
|
auth: 'mojang' // optional; by default uses mojang, if using a microsoft account, set to 'microsoft'
|
||||||
});
|
});
|
||||||
client.on('chat', function(packet) {
|
client.on('chat', function(packet) {
|
||||||
// Listen for chat messages and echo them back.
|
// Listen for chat messages and echo them back.
|
||||||
|
|
32
examples/client_microsoft_auth/client_microsoft_auth.js
Normal file
32
examples/client_microsoft_auth/client_microsoft_auth.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const mc = require('minecraft-protocol')
|
||||||
|
|
||||||
|
if (process.argv.length < 4 || process.argv.length > 6) {
|
||||||
|
console.log('Usage : node echo.js <host> <port> [<email>] [<password>]')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = mc.createClient({
|
||||||
|
host: process.argv[2],
|
||||||
|
port: parseInt(process.argv[3]),
|
||||||
|
username: process.argv[4], // your microsoft account email
|
||||||
|
password: process.argv[5], // your microsoft account password
|
||||||
|
auth: 'microsoft' // This option must be present and set to 'microsoft' to use Microsoft Account Authentication. Failure to do so will result in yggdrasil throwing invalid account information.
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on('connect', function () {
|
||||||
|
console.info('connected')
|
||||||
|
})
|
||||||
|
client.on('disconnect', function (packet) {
|
||||||
|
console.log('disconnected: ' + packet.reason)
|
||||||
|
})
|
||||||
|
client.on('chat', function (packet) {
|
||||||
|
const jsonMsg = JSON.parse(packet.message)
|
||||||
|
if (jsonMsg.translate === 'chat.type.announcement' || jsonMsg.translate === 'chat.type.text') {
|
||||||
|
const username = jsonMsg.with[0].text
|
||||||
|
const msg = jsonMsg.with[1]
|
||||||
|
if (username === client.username) return
|
||||||
|
client.write('chat', { message: msg })
|
||||||
|
}
|
||||||
|
})
|
8
examples/client_microsoft_auth/package.json
Normal file
8
examples/client_microsoft_auth/package.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "node-minecraft-protocol-example",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
},
|
||||||
|
"description": "A node-minecraft-protocol example"
|
||||||
|
}
|
|
@ -43,6 +43,7 @@
|
||||||
"standard": "^16.0.1"
|
"standard": "^16.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@xboxreplay/xboxlive-auth": "^3.3.0",
|
||||||
"aes-js": "^3.1.2",
|
"aes-js": "^3.1.2",
|
||||||
"buffer-equal": "^1.0.0",
|
"buffer-equal": "^1.0.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
|
@ -51,6 +52,7 @@
|
||||||
"lodash.merge": "^4.3.0",
|
"lodash.merge": "^4.3.0",
|
||||||
"minecraft-data": "^2.70.0",
|
"minecraft-data": "^2.70.0",
|
||||||
"minecraft-folder-path": "^1.1.0",
|
"minecraft-folder-path": "^1.1.0",
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
"node-rsa": "^0.4.2",
|
"node-rsa": "^0.4.2",
|
||||||
"prismarine-nbt": "^1.3.0",
|
"prismarine-nbt": "^1.3.0",
|
||||||
"protodef": "^1.8.0",
|
"protodef": "^1.8.0",
|
||||||
|
|
72
src/client/microsoftAuth.js
Normal file
72
src/client/microsoftAuth.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
const XboxLiveAuth = require('@xboxreplay/xboxlive-auth')
|
||||||
|
const fetch = require('node-fetch')
|
||||||
|
|
||||||
|
const XSTSRelyingParty = 'rp://api.minecraftservices.com/'
|
||||||
|
const MinecraftServicesLogWithXbox = 'https://api.minecraftservices.com/authentication/login_with_xbox'
|
||||||
|
const MinecraftServicesEntitlement = 'https://api.minecraftservices.com/entitlements/mcstore'
|
||||||
|
const MinecraftServicesProfile = 'https://api.minecraftservices.com/minecraft/profile'
|
||||||
|
|
||||||
|
const getFetchOptions = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'node-minecraft-protocol'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticates with Xbox Live, then Authenticates with Minecraft, Checks Entitlements and Gets Profile.
|
||||||
|
* @function
|
||||||
|
* @param {object} client - The client passed to protocol
|
||||||
|
* @param {object} options - Client Options
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = async (client, options) => {
|
||||||
|
// Use external library to authenticate with
|
||||||
|
const XAuthResponse = await XboxLiveAuth.authenticate(options.username, options.password, { XSTSRelyingParty })
|
||||||
|
.catch((err) => {
|
||||||
|
if (err.details) throw new Error(`Unable to authenticate with Xbox Live: ${JSON.stringify(err.details)}`)
|
||||||
|
else throw Error(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
const MineServicesResponse = await fetch(MinecraftServicesLogWithXbox, {
|
||||||
|
method: 'post',
|
||||||
|
...getFetchOptions,
|
||||||
|
body: JSON.stringify({ identityToken: `XBL3.0 x=${XAuthResponse.userHash};${XAuthResponse.XSTSToken}` })
|
||||||
|
}).then(checkStatus)
|
||||||
|
|
||||||
|
options.haveCredentials = MineServicesResponse.access_token != null
|
||||||
|
|
||||||
|
getFetchOptions.headers.Authorization = `Bearer ${MineServicesResponse.access_token}`
|
||||||
|
const MineEntitlements = await fetch(MinecraftServicesEntitlement, getFetchOptions).then(checkStatus)
|
||||||
|
if (MineEntitlements.items.length === 0) throw Error('This user does not have any items on its accounts according to minecraft services.')
|
||||||
|
|
||||||
|
const MinecraftProfile = await fetch(MinecraftServicesProfile, getFetchOptions).then(checkStatus)
|
||||||
|
if (!MinecraftProfile.id) throw Error('This user does not own minecraft according to minecraft services.')
|
||||||
|
|
||||||
|
// This profile / session here could be simplified down to where it just passes the uuid of the player to encrypt.js
|
||||||
|
// That way you could remove some lines of code. It accesses client.session.selectedProfile.id so /shrug.
|
||||||
|
// - Kashalls
|
||||||
|
const profile = {
|
||||||
|
name: MinecraftProfile.name,
|
||||||
|
id: MinecraftProfile.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = {
|
||||||
|
accessToken: MineServicesResponse.access_token,
|
||||||
|
selectedProfile: profile,
|
||||||
|
availableProfile: [profile]
|
||||||
|
}
|
||||||
|
client.session = session
|
||||||
|
client.username = MinecraftProfile.name
|
||||||
|
options.accessToken = MineServicesResponse.access_token
|
||||||
|
client.emit('session', session)
|
||||||
|
options.connect(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStatus (res) {
|
||||||
|
if (res.ok) { // res.status >= 200 && res.status < 300
|
||||||
|
return res.json()
|
||||||
|
} else {
|
||||||
|
throw Error(res.statusText)
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ const encrypt = require('./client/encrypt')
|
||||||
const keepalive = require('./client/keepalive')
|
const keepalive = require('./client/keepalive')
|
||||||
const compress = require('./client/compress')
|
const compress = require('./client/compress')
|
||||||
const auth = require('./client/auth')
|
const auth = require('./client/auth')
|
||||||
|
const microsoftAuth = require('./client/microsoftAuth')
|
||||||
const setProtocol = require('./client/setProtocol')
|
const setProtocol = require('./client/setProtocol')
|
||||||
const play = require('./client/play')
|
const play = require('./client/play')
|
||||||
const tcpDns = require('./client/tcp_dns')
|
const tcpDns = require('./client/tcp_dns')
|
||||||
|
@ -33,7 +34,8 @@ function createClient (options) {
|
||||||
const client = new Client(false, version.minecraftVersion, options.customPackets, hideErrors)
|
const client = new Client(false, version.minecraftVersion, options.customPackets, hideErrors)
|
||||||
|
|
||||||
tcpDns(client, options)
|
tcpDns(client, options)
|
||||||
auth(client, options)
|
if (options.auth === 'microsoft') microsoftAuth(client, options)
|
||||||
|
else auth(client, options)
|
||||||
if (options.version === false) autoVersion(client, options)
|
if (options.version === false) autoVersion(client, options)
|
||||||
setProtocol(client, options)
|
setProtocol(client, options)
|
||||||
keepalive(client, options)
|
keepalive(client, options)
|
||||||
|
|
Loading…
Reference in a new issue