Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Returns a `Client` instance and perform login.
* id : The selected profiles uuid in short form (without `-`) needed for logging in with access and client Tokens.
* authServer : auth server, default to https://authserver.mojang.com
* sessionServer : session server, default to https://sessionserver.mojang.com
* servicesServer : services server, default to https://api.minecraftservices.com
* keepAlive : send keep alive packets : default to true
* closeTimeout : end the connection after this delay in milliseconds if server doesn't answer to ping, default to `120*1000`
* noPongTimeout : after the server opened the connection, wait for a default of `5*1000` after pinging and answers without the latency
Expand Down
37 changes: 36 additions & 1 deletion src/client/mojangAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const yggdrasil = require('yggdrasil')
const fs = require('fs').promises
const mcDefaultFolderPath = require('minecraft-folder-path')
const path = require('path')
const crypto = require('crypto')

const launcherDataFile = 'launcher_accounts.json'

Expand Down Expand Up @@ -33,6 +34,32 @@ module.exports = async function (client, options) {
}
}

// Adapted from https://github.com/PrismarineJS/prismarine-auth/blob/1aef6e1387d94fca839f2811d17ac6659ae556b4/src/TokenManagers/MinecraftJavaTokenManager.js#L101
const toDER = pem => pem.split('\n').slice(1, -1).reduce((acc, cur) => Buffer.concat([acc, Buffer.from(cur, 'base64')]), Buffer.alloc(0))
async function fetchCertificates (accessToken) {
const servicesServer = options.servicesServer ?? 'https://api.minecraftservices.com'
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
}
const res = await fetch(`${servicesServer}/player/certificates`, { headers, method: 'post' })
if (!res.ok) throw Error(`Certificates request returned status ${res.status}`)
const cert = await res.json()
const profileKeys = {
publicPEM: cert.keyPair.publicKey,
privatePEM: cert.keyPair.privateKey,
publicDER: toDER(cert.keyPair.publicKey),
privateDER: toDER(cert.keyPair.privateKey),
signature: Buffer.from(cert.publicKeySignature, 'base64'),
signatureV2: Buffer.from(cert.publicKeySignatureV2, 'base64'),
expiresOn: new Date(cert.expiresAt),
refreshAfter: new Date(cert.refreshedAfter)
}
profileKeys.public = crypto.createPublicKey({ key: profileKeys.publicDER, format: 'der', type: 'spki' })
profileKeys.private = crypto.createPrivateKey({ key: profileKeys.privateDER, format: 'der', type: 'pkcs8' })
return { profileKeys }
}

function getProfileId (auths) {
try {
const lowerUsername = options.username.toLowerCase()
Expand All @@ -47,7 +74,7 @@ module.exports = async function (client, options) {

if (options.haveCredentials) {
// make a request to get the case-correct username before connecting.
const cb = function (err, session) {
const cb = async function (err, session) {
if (options.profilesFolder) {
getLauncherProfiles().then((auths) => {
if (!auths.accounts) auths.accounts = []
Expand Down Expand Up @@ -104,6 +131,14 @@ module.exports = async function (client, options) {
} else {
client.session = session
client.username = session.selectedProfile.name
if (!options.disableChatSigning) {
try {
const certificates = await fetchCertificates(session.accessToken)
Object.assign(client, certificates)
} catch (e) {
console.warn(`Failed to fetch player certificates: ${e}`)
}
}
options.accessToken = session.accessToken
client.emit('session', session)
options.connect(client)
Expand Down
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ declare module 'minecraft-protocol' {
accessToken?: string
authServer?: string
authTitle?: string
servicesServer?: string
sessionServer?: string
keepAlive?: boolean
closeTimeout?: number
Expand Down
Loading