From 85b66dc5bdd4b6ce9b763bdf18a79fa9ac3357c1 Mon Sep 17 00:00:00 2001 From: LucienHH <66429271+LucienHH@users.noreply.github.com> Date: Mon, 18 Sep 2023 19:01:32 +0100 Subject: [PATCH] Cache fixes (#73) --- src/MicrosoftAuthFlow.js | 53 +++++++++-------- src/TokenManagers/XboxTokenManager.js | 86 ++++++++++++--------------- 2 files changed, 65 insertions(+), 74 deletions(-) diff --git a/src/MicrosoftAuthFlow.js b/src/MicrosoftAuthFlow.js index d8694af..a583120 100644 --- a/src/MicrosoftAuthFlow.js +++ b/src/MicrosoftAuthFlow.js @@ -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 = {}) { diff --git a/src/TokenManagers/XboxTokenManager.js b/src/TokenManagers/XboxTokenManager.js index 5f369b6..305db68 100644 --- a/src/TokenManagers/XboxTokenManager.js +++ b/src/TokenManagers/XboxTokenManager.js @@ -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) { @@ -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) { @@ -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=' @@ -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 } @@ -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 @@ -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 } @@ -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 } @@ -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 } @@ -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 }