Skip to content

Commit

Permalink
Cache fixes (#73)
Browse files Browse the repository at this point in the history
  • Loading branch information
LucienHH authored Sep 18, 2023
1 parent a51a660 commit 85b66dc
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 74 deletions.
53 changes: 27 additions & 26 deletions src/MicrosoftAuthFlow.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,39 +113,40 @@ class MicrosoftAuthFlow {

async getXboxToken (relyingParty = this.options.relyingParty || Endpoints.XboxRelyingParty) {
const options = { ...this.options, relyingParty }
if (await this.xbl.verifyTokens(relyingParty)) {

const { xstsToken, userToken, deviceToken, titleToken } = await this.xbl.getCachedTokens(relyingParty)

if (xstsToken.valid) {
debug('[xbl] Using existing XSTS token')
const { data } = await this.xbl.getCachedXstsToken(relyingParty)
return data
} else if (options.password) {
return xstsToken.data
}

if (options.password) {
debug('[xbl] password is present, trying to authenticate using xboxreplay/xboxlive-auth')
const xsts = await this.xbl.doReplayAuth(this.username, options.password, options)
return xsts
} else {
debug('[xbl] Need to obtain tokens')
return await retry(async () => {
const msaToken = await this.getMsaToken()
}

if (options.flow === 'sisu') {
debug(`[xbl] Sisu flow selected, trying to authenticate with authTitle ID ${options.authTitle}`)
const deviceToken = await this.xbl.getDeviceToken(options)
const sisu = await this.xbl.doSisuAuth(msaToken, deviceToken, options)
return sisu
}
debug('[xbl] Need to obtain tokens')

const userToken = await this.xbl.getUserToken(msaToken, options.flow === 'msal')
return await retry(async () => {
const msaToken = await this.getMsaToken()

if (this.doTitleAuth) {
const deviceToken = await this.xbl.getDeviceToken(options)
const titleToken = await this.xbl.getTitleToken(msaToken, deviceToken)
const xsts = await this.xbl.getXSTSToken({ userToken, deviceToken, titleToken }, options)
return xsts
} else {
const xsts = await this.xbl.getXSTSToken({ userToken }, options)
return xsts
}
}, () => { this.msa.forceRefresh = true }, 2)
}
// sisu flow generates user and title tokens differently to other flows and should also be used to refresh them if they are invalid
if (options.flow === 'sisu' && (!userToken.valid || !deviceToken.valid || !titleToken.valid)) {
debug(`[xbl] Sisu flow selected, trying to authenticate with authTitle ID ${options.authTitle}`)
const dt = await this.xbl.getDeviceToken(options)
const sisu = await this.xbl.doSisuAuth(msaToken, dt, options)
return sisu
}

const ut = userToken.token ?? await this.xbl.getUserToken(msaToken, options.flow === 'msal')
const dt = deviceToken.token ?? await this.xbl.getDeviceToken(options)
const tt = titleToken.token ?? (this.doTitleAuth ? await this.xbl.getTitleToken(msaToken, dt) : undefined)

const xsts = await this.xbl.getXSTSToken({ userToken: ut, deviceToken: dt, titleToken: tt }, options)
return xsts
}, () => { this.msa.forceRefresh = true }, 2)
}

async getMinecraftJavaToken (options = {}) {
Expand Down
86 changes: 38 additions & 48 deletions src/TokenManagers/XboxTokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ const { checkStatus, createHash } = require('../common/Util')
const UUID = require('uuid-1345')
const nextUUID = () => UUID.v3({ namespace: '6ba7b811-9dad-11d1-80b4-00c04fd430c8', name: Date.now().toString() })

const checkIfValid = (expires) => {
const remainingMs = new Date(expires) - Date.now()
const valid = remainingMs > 1000
return valid
}

// Manages Xbox Live tokens for xboxlive.com
class XboxTokenManager {
constructor (ecKey, cache) {
Expand All @@ -24,34 +30,28 @@ class XboxTokenManager {
this.headers = { 'Cache-Control': 'no-store, must-revalidate, no-cache', 'x-xbl-contract-version': 1 }
}

async getCachedUserToken () {
const { userToken: token } = await this.cache.getCached()
if (!token) return
const until = new Date(token.NotAfter)
const dn = Date.now()
const remainingMs = until - dn
const valid = remainingMs > 1000
return { valid, token: token.Token, data: token }
async setCachedToken (data) {
await this.cache.setCachedPartial(data)
}

async getCachedXstsToken (relyingParty) {
const key = createHash(relyingParty)
const { [key]: token } = await this.cache.getCached()
if (!token) return
const until = new Date(token.expiresOn)
const dn = Date.now()
const remainingMs = until - dn
const valid = remainingMs > 1000
return { valid, token: token.XSTSToken, data: token }
}
async getCachedTokens (relyingParty) {
const cachedTokens = await this.cache.getCached()

async setCachedUserToken (data) {
await this.cache.setCachedPartial({ userToken: data })
}
const xstsHash = createHash(relyingParty)

const result = {}

async setCachedXstsToken (data, relyingParty) {
const key = createHash(relyingParty)
await this.cache.setCachedPartial({ [key]: data })
for (const token of ['userToken', 'titleToken', 'deviceToken']) {
const cached = cachedTokens[token]
result[token] = cached && checkIfValid(cached.NotAfter)
? { valid: true, token: cached.Token, data: cached }
: { valid: false }
}
result.xstsToken = cachedTokens[xstsHash] && checkIfValid(cachedTokens[xstsHash].expiresOn)
? { valid: true, data: cachedTokens[xstsHash] }
: { valid: false }

return result
}

checkTokenError (errorCode, response) {
Expand All @@ -64,26 +64,6 @@ class XboxTokenManager {
else throw new Error(`Xbox Live authentication failed to obtain a XSTS token. XErr: ${errorCode}\n${JSON.stringify(response)}`)
}

async verifyTokens (relyingParty) {
const ut = await this.getCachedUserToken()
const xt = await this.getCachedXstsToken(relyingParty)
if (!ut || !xt || this.forceRefresh) {
return false
}
debug('[xbl] have user, xsts', ut, xt)
if (ut.valid && xt.valid) {
return true
} else if (ut.valid && !xt.valid) {
try {
await this.getXSTSToken({ userToken: ut.token }, { relyingParty })
return true
} catch (e) {
return false
}
}
return false
}

async getUserToken (accessToken, azure) {
debug('[xbl] obtaining xbox token with ms token', accessToken)
const preamble = azure ? 'd=' : 't='
Expand All @@ -103,7 +83,9 @@ class XboxTokenManager {
const headers = { ...this.headers, signature, 'Content-Type': 'application/json', accept: 'application/json', 'x-xbl-contract-version': '2' }

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

await this.setCachedToken({ userToken: ret })

debug('[xbl] user token:', ret)
return ret.Token
}
Expand Down Expand Up @@ -144,7 +126,7 @@ class XboxTokenManager {
const preAuthResponse = await XboxLiveAuth.preAuth()
const logUserResponse = await XboxLiveAuth.logUser(preAuthResponse, { email, password })
const xblUserToken = await XboxLiveAuth.exchangeRpsTicketForUserToken(logUserResponse.access_token)
await this.setCachedUserToken(xblUserToken)
await this.setCachedToken({ userToken: xblUserToken })
debug('[xbl] user token:', xblUserToken)
const xsts = await this.getXSTSToken({ userToken: xblUserToken.Token }, options)
return xsts
Expand Down Expand Up @@ -185,7 +167,8 @@ class XboxTokenManager {
expiresOn: ret.AuthorizationToken.NotAfter
}

await this.setCachedXstsToken(xsts, options.relyingParty)
await this.setCachedToken({ userToken: ret.UserToken, titleToken: ret.TitleToken, [createHash(options.relyingParty)]: xsts })

debug('[xbl] xsts', xsts)
return xsts
}
Expand Down Expand Up @@ -222,7 +205,8 @@ class XboxTokenManager {
expiresOn: ret.NotAfter
}

await this.setCachedXstsToken(xsts, options.relyingParty)
await this.setCachedToken({ [createHash(options.relyingParty)]: xsts })

debug('[xbl] xsts', xsts)
return xsts
}
Expand Down Expand Up @@ -250,6 +234,9 @@ class XboxTokenManager {
const headers = { ...this.headers, Signature: signature }

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

await this.setCachedToken({ deviceToken: ret })

debug('Xbox Device Token', ret)
return ret.Token
}
Expand All @@ -273,6 +260,9 @@ class XboxTokenManager {
const headers = { ...this.headers, Signature: signature }

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

await this.setCachedToken({ titleToken: ret })

debug('Xbox Title Token', ret)
return ret.Token
}
Expand Down

0 comments on commit 85b66dc

Please sign in to comment.