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
10 changes: 10 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ declare module 'prismarine-auth' {
refreshAfter: string
}

export interface ProxyOptions {
host: string
port: number
username?: string
password?: string
type?: string
}

export interface MicrosoftAuthFlowOptions {
// If using Azure auth, specify an custom object to pass to MSAL
msalConfig?: object
Expand All @@ -118,6 +126,8 @@ declare module 'prismarine-auth' {
flow: 'live' | 'msal' | 'sisu'
// Reset the cache and obtain fresh tokens for everything
forceRefresh?: boolean
// Proxy configuration
proxy?: ProxyOptions
}

export enum Titles {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"@azure/msal-node": "^2.0.2",
"@xboxreplay/xboxlive-auth": "^5.1.0",
"debug": "^4.3.3",
"fetch-socks": "^1.3.0",
"smart-buffer": "^4.1.0",
"undici": "^7.0.0",
"uuid-1345": "^1.0.2"
}
}
36 changes: 30 additions & 6 deletions src/MicrosoftAuthFlow.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const fs = require('fs')
const path = require('path')
const crypto = require('crypto')
const debug = require('debug')('prismarine-auth')
const { socksDispatcher } = require('fetch-socks')
const { ProxyAgent } = require('undici')

const Titles = require('./common/Titles')
const { createHash } = require('./common/Util')
Expand All @@ -16,6 +18,27 @@ const BedrockTokenManager = require('./TokenManagers/MinecraftBedrockTokenManage
const PlayfabTokenManager = require('./TokenManagers/PlayfabTokenManager')
const MinecraftServicesTokenManager = require('./TokenManagers/MinecraftBedrockServicesManager')

function buildHttpProxyUrl (proxy) {
const auth = proxy.username ? `${proxy.username}:${proxy.password || ''}@` : ''
return `http://${auth}${proxy.host}:${proxy.port}`
}

function wrapFetch (dispatcher) {
return (url, options = {}) => fetch(url, { ...options, dispatcher })
}

function createProxyFetch (proxy) {
if (proxy.type === 'socks5') {
const config = { type: 5, host: proxy.host, port: proxy.port }
if (proxy.username) {
config.userId = proxy.username
config.password = proxy.password
}
return wrapFetch(socksDispatcher(config))
}
return wrapFetch(new ProxyAgent(buildHttpProxyUrl(proxy)))
}

async function retry (methodFn, beforeRetry, times) {
while (times--) {
if (times !== 0) {
Expand All @@ -37,6 +60,7 @@ class MicrosoftAuthFlow {
throw new Error("Missing 'flow' argument in options. See docs for more information.")
}
this.options = options || { flow: 'live', authTitle: Titles.MinecraftNintendoSwitch }
this.fetch = this.options.proxy ? createProxyFetch(this.options.proxy) : fetch
this.initTokenManagers(username, cache, options?.forceRefresh)
this.codeCallback = codeCallback
}
Expand Down Expand Up @@ -71,7 +95,7 @@ class MicrosoftAuthFlow {

if (this.options.flow === 'live' || this.options.flow === 'sisu') {
if (!this.options.authTitle) throw new Error(`Please specify an "authTitle" in Authflow constructor when using ${this.options.flow} flow`)
this.msa = new LiveTokenManager(this.options.authTitle, ['service::user.auth.xboxlive.com::MBI_SSL'], cache({ cacheName: this.options.flow, username }))
this.msa = new LiveTokenManager(this.options.authTitle, ['service::user.auth.xboxlive.com::MBI_SSL'], cache({ cacheName: this.options.flow, username }), this.fetch)
this.doTitleAuth = true
} else if (this.options.flow === 'msal') {
let config = this.options.msalConfig
Expand All @@ -86,11 +110,11 @@ class MicrosoftAuthFlow {
}

const keyPair = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' })
this.xbl = new XboxTokenManager(keyPair, cache({ cacheName: 'xbl', username }))
this.mba = new BedrockTokenManager(cache({ cacheName: 'bed', username }))
this.mca = new JavaTokenManager(cache({ cacheName: 'mca', username }))
this.mcs = new MinecraftServicesTokenManager(cache({ cacheName: 'mcs', username }))
this.pfb = new PlayfabTokenManager(cache({ cacheName: 'pfb', username }))
this.xbl = new XboxTokenManager(keyPair, cache({ cacheName: 'xbl', username }), this.fetch)
this.mba = new BedrockTokenManager(cache({ cacheName: 'bed', username }), this.fetch)
this.mca = new JavaTokenManager(cache({ cacheName: 'mca', username }), this.fetch)
this.mcs = new MinecraftServicesTokenManager(cache({ cacheName: 'mcs', username }), this.fetch)
this.pfb = new PlayfabTokenManager(cache({ cacheName: 'pfb', username }), this.fetch)
}

async getMsaToken () {
Expand Down
9 changes: 5 additions & 4 deletions src/TokenManagers/LiveTokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ const { Endpoints } = require('../common/Constants')
const { checkStatus } = require('../common/Util')

class LiveTokenManager {
constructor (clientId, scopes, cache) {
constructor (clientId, scopes, cache, fetchFn = fetch) {
this.clientId = clientId
this.scopes = scopes
this.cache = cache
this.fetch = fetchFn
}

async verifyTokens () {
Expand Down Expand Up @@ -46,7 +47,7 @@ class LiveTokenManager {
credentials: 'include' // This cookie handler does not work on node-fetch ...
}

const token = await fetch(Endpoints.live.tokenRequest, codeRequest).then(checkStatus)
const token = await this.fetch(Endpoints.live.tokenRequest, codeRequest).then(checkStatus)
this.updateCache(token)
return token
}
Expand Down Expand Up @@ -91,7 +92,7 @@ class LiveTokenManager {

const cookies = []

const res = await fetch(Endpoints.live.deviceCodeRequest, codeRequest)
const res = await this.fetch(Endpoints.live.deviceCodeRequest, codeRequest)
.then(res => {
if (res.status !== 200) {
res.text().then(console.warn)
Expand Down Expand Up @@ -128,7 +129,7 @@ class LiveTokenManager {
}).toString()
}

const token = await fetch(Endpoints.live.tokenRequest + '?client_id=' + this.clientId, verifi)
const token = await this.fetch(Endpoints.live.tokenRequest + '?client_id=' + this.clientId, verifi)
.then(res => res.json()).then(res => {
if (res.error) {
if (res.error === 'authorization_pending') {
Expand Down
5 changes: 3 additions & 2 deletions src/TokenManagers/MinecraftBedrockServicesManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ const { Endpoints } = require('../common/Constants')
const { checkStatus } = require('../common/Util')

class MinecraftBedrockServicesTokenManager {
constructor (cache) {
constructor (cache, fetchFn = fetch) {
this.cache = cache
this.fetch = fetchFn
}

async getCachedAccessToken () {
Expand All @@ -25,7 +26,7 @@ class MinecraftBedrockServicesTokenManager {
}

async getAccessToken (sessionTicket, options = {}) {
const response = await fetch(Endpoints.minecraftBedrock.servicesSessionStart, {
const response = await this.fetch(Endpoints.minecraftBedrock.servicesSessionStart, {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Expand Down
5 changes: 3 additions & 2 deletions src/TokenManagers/MinecraftBedrockTokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ const { Endpoints } = require('../common/Constants')
const { checkStatusWithHelp } = require('../common/Util')

class BedrockTokenManager {
constructor (cache) {
constructor (cache, fetchFn = fetch) {
this.cache = cache
this.fetch = fetchFn
}

async getCachedAccessToken () {
Expand Down Expand Up @@ -51,7 +52,7 @@ class BedrockTokenManager {
'User-Agent': 'MCPE/UWP',
Authorization: `XBL3.0 x=${xsts.userHash};${xsts.XSTSToken}`
}
const MineServicesResponse = await fetch(Endpoints.minecraftBedrock.authenticate, {
const MineServicesResponse = await this.fetch(Endpoints.minecraftBedrock.authenticate, {
method: 'post',
headers,
body: JSON.stringify({ identityPublicKey: clientPublicKey })
Expand Down
13 changes: 7 additions & 6 deletions src/TokenManagers/MinecraftJavaTokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ const reportReasons = {
const toDER = pem => pem.split('\n').slice(1, -1).reduce((acc, cur) => Buffer.concat([acc, Buffer.from(cur, 'base64')]), Buffer.alloc(0))

class MinecraftJavaTokenManager {
constructor (cache) {
constructor (cache, fetchFn = fetch) {
this.cache = cache
this.fetch = fetchFn
}

async getCachedAccessToken () {
Expand Down Expand Up @@ -65,7 +66,7 @@ class MinecraftJavaTokenManager {

async getAccessToken (xsts) {
debug('[mc] authing to minecraft', xsts)
const MineServicesResponse = await fetch(Endpoints.minecraftJava.loginWithXbox, {
const MineServicesResponse = await this.fetch(Endpoints.minecraftJava.loginWithXbox, {
method: 'post',
...fetchOptions,
body: JSON.stringify({ identityToken: `XBL3.0 x=${xsts.userHash};${xsts.XSTSToken}` })
Expand All @@ -79,7 +80,7 @@ class MinecraftJavaTokenManager {
async fetchProfile (accessToken) {
debug(`[mc] fetching minecraft profile with ${accessToken.slice(0, 16)}`)
const headers = { ...fetchOptions.headers, Authorization: `Bearer ${accessToken}` }
const profile = await fetch(Endpoints.minecraftJava.profile, { headers })
const profile = await this.fetch(Endpoints.minecraftJava.profile, { headers })
.then(checkStatus)
debug(`[mc] got profile response: ${profile}`)
return profile
Expand All @@ -93,15 +94,15 @@ class MinecraftJavaTokenManager {
async fetchEntitlements (accessToken) {
debug(`[mc] fetching entitlements with ${accessToken.slice(0, 16)}`)
const headers = { ...fetchOptions.headers, Authorization: `Bearer ${accessToken}` }
const entitlements = await fetch(Endpoints.minecraftJava.entitlements + `?requestId=${crypto.randomUUID()}`, { headers }).then(checkStatus)
const entitlements = await this.fetch(Endpoints.minecraftJava.entitlements + `?requestId=${crypto.randomUUID()}`, { headers }).then(checkStatus)
debug(`[mc] got entitlement response: ${entitlements}`)
return entitlements
}

async fetchCertificates (accessToken) {
debug(`[mc] fetching key-pair with ${accessToken.slice(0, 16)}`)
const headers = { ...fetchOptions.headers, Authorization: `Bearer ${accessToken}` }
const cert = await fetch(Endpoints.minecraftJava.certificates, { method: 'post', headers }).then(checkStatus)
const cert = await this.fetch(Endpoints.minecraftJava.certificates, { method: 'post', headers }).then(checkStatus)
debug('[mc] got key-pair')
const profileKeys = {
publicPEM: cert.keyPair.publicKey,
Expand Down Expand Up @@ -176,7 +177,7 @@ class MinecraftJavaTokenManager {
}

debug('[mc] reporting player with payload', body)
const reportResponse = await fetch(Endpoints.minecraftJava.reportPlayer, { method: 'post', headers, body }).then(checkStatus)
const reportResponse = await this.fetch(Endpoints.minecraftJava.reportPlayer, { method: 'post', headers, body }).then(checkStatus)
debug('[mc] server response for report', reportResponse)
return true
}
Expand Down
5 changes: 3 additions & 2 deletions src/TokenManagers/PlayfabTokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ const debug = require('debug')('prismarine-auth')
const { Endpoints } = require('../common/Constants')

class PlayfabTokenManager {
constructor (cache) {
constructor (cache, fetchFn = fetch) {
this.cache = cache
this.fetch = fetchFn
}

async setCachedAccessToken (data) {
Expand All @@ -21,7 +22,7 @@ class PlayfabTokenManager {
}

async getAccessToken (xsts) {
const response = await fetch(Endpoints.PlayfabLoginWithXbox, {
const response = await this.fetch(Endpoints.PlayfabLoginWithXbox, {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Expand Down
13 changes: 7 additions & 6 deletions src/TokenManagers/XboxTokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ const checkIfValid = (expires) => {

// Manages Xbox Live tokens for xboxlive.com
class XboxTokenManager {
constructor (ecKey, cache) {
constructor (ecKey, cache, fetchFn = fetch) {
this.key = ecKey
this.jwk = { ...ecKey.publicKey.export({ format: 'jwk' }), alg: 'ES256', use: 'sig' }
this.cache = cache
this.fetch = fetchFn

this.headers = { 'Cache-Control': 'no-store, must-revalidate, no-cache', 'x-xbl-contract-version': 1 }
}
Expand Down Expand Up @@ -78,7 +79,7 @@ class XboxTokenManager {
const signature = this.sign(Endpoints.xbox.userAuth, '', body).toString('base64')
const headers = { ...this.headers, signature, 'Content-Type': 'application/json', accept: 'application/json', 'x-xbl-contract-version': '2' }

const ret = await fetch(Endpoints.xbox.userAuth, { method: 'post', headers, body }).then(checkStatus)
const ret = await this.fetch(Endpoints.xbox.userAuth, { method: 'post', headers, body }).then(checkStatus)

await this.setCachedToken({ userToken: ret })

Expand Down Expand Up @@ -150,7 +151,7 @@ class XboxTokenManager {

const headers = { Signature: signature }

const req = await fetch(Endpoints.xbox.sisuAuthorize, { method: 'post', headers, body })
const req = await this.fetch(Endpoints.xbox.sisuAuthorize, { method: 'post', headers, body })
const ret = await req.json()
if (!req.ok) this.checkTokenError(parseInt(req.headers.get('x-err')), ret)

Expand Down Expand Up @@ -189,7 +190,7 @@ class XboxTokenManager {

const headers = { ...this.headers, Signature: signature }

const req = await fetch(Endpoints.xbox.xstsAuthorize, { method: 'post', headers, body })
const req = await this.fetch(Endpoints.xbox.xstsAuthorize, { method: 'post', headers, body })
const ret = await req.json()
if (!req.ok) this.checkTokenError(ret.XErr, ret)

Expand Down Expand Up @@ -228,7 +229,7 @@ class XboxTokenManager {
const signature = this.sign(Endpoints.xbox.deviceAuth, '', body).toString('base64')
const headers = { ...this.headers, Signature: signature }

const ret = await fetch(Endpoints.xbox.deviceAuth, { method: 'post', headers, body }).then(checkStatus)
const ret = await this.fetch(Endpoints.xbox.deviceAuth, { method: 'post', headers, body }).then(checkStatus)

await this.setCachedToken({ deviceToken: ret })

Expand All @@ -254,7 +255,7 @@ class XboxTokenManager {

const headers = { ...this.headers, Signature: signature }

const ret = await fetch(Endpoints.xbox.titleAuth, { method: 'post', headers, body }).then(checkStatus)
const ret = await this.fetch(Endpoints.xbox.titleAuth, { method: 'post', headers, body }).then(checkStatus)

await this.setCachedToken({ titleToken: ret })

Expand Down