From 01c826692494efaad5540446bd582e094a41fbd8 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Sun, 26 May 2024 17:57:57 -0400 Subject: [PATCH] Add a forceRefresh option to Authflow (#100) * Add a forceRefresh option * Update index.d.ts * Update basic.js * add a lint error * Fix linting errors * add a lint error * Update basic.js --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 1 + docs/API.md | 4 ++++ examples/xbox/basic.js | 3 ++- index.d.ts | 7 +++---- src/MicrosoftAuthFlow.js | 30 +++++++++++++----------------- src/common/cache/FileCache.js | 10 +++++++--- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 68712f5..4f85009 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ npm install prismarine-auth - cacheDirectory? {String | Function} - Where we will store your tokens (optional) or a factory function that returns a cache. - options {Object?} - [flow] {enum} Required if options is specified - see [API.md](docs/API.md) for options + - [forceRefresh] {boolean} - Clear all cached tokens for the specified `username` to get new ones on subsequent token requests - [password] {string} - If passed we will do password based authentication. - [authTitle] {string} - See the [API.md](docs/API.md) - [deviceType] {string} - See the [API.md](docs/API.md) diff --git a/docs/API.md b/docs/API.md index 446ce76..1060f93 100644 --- a/docs/API.md +++ b/docs/API.md @@ -20,6 +20,7 @@ This is the main exposed class you interact with. Every instance holds its own t * `password` (optional) If you specify this option, we use password based auth. Note this may be unreliable. * `authTitle` - The client ID for the service you are logging into. When using the `msal` flow, this is your custom Azure client token. When using `live`, this is the Windows Live SSO client ID - used when authenticating as a Windows app (such as a vanilla Minecraft client). For a list of titles, see `require('prismarine-auth').Titles` and FAQ section below for more info. (Required if using `sisu` or `live` flow, on `msal` flow we fallback to a default client ID.) * `deviceType` (optional) if specifying an authTitle, the device type to auth as. For example, `Win32`, `iOS`, `Android`, `Nintendo` + * `forceRefresh` (optional) boolean - Clear all cached tokens for the specified `username` to get new ones on subsequent token requests * `codeCallback` (optional) The callback to call when doing device code auth. Otherwise, the code will be logged to the console. #### getMsaToken () : Promise @@ -94,6 +95,9 @@ As an example of usage, you could create a minimal in memory cache like this (no ```js class InMemoryCache { private cache = {} + async reset () { + // (should clear the data in the cache like a first run) + } async getCached () { return this.cache } diff --git a/examples/xbox/basic.js b/examples/xbox/basic.js index 0989195..5740481 100644 --- a/examples/xbox/basic.js +++ b/examples/xbox/basic.js @@ -1,3 +1,4 @@ const { Authflow } = require('prismarine-auth') -const flow = new Authflow() // No parameters needed +// No parameters needed - will login as Minecraft by default unless a custom Azure client ID is passed (see ./azure.js) +const flow = new Authflow() module.exports = flow.getXboxToken().then(console.log) diff --git a/index.d.ts b/index.d.ts index f28abdf..6371d0a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -11,10 +11,6 @@ declare module 'prismarine-auth' { * @param codeCallback Optional callback to recieve token information using device code auth */ constructor(username?: string, cache?: string | CacheFactory, options?: MicrosoftAuthFlowOptions, codeCallback?: (res: ServerDeviceCodeResponse) => void) - /** - * Deletes the caches in the specified cache directory. - */ - static resetTokenCaches(cache: string): boolean // Returns a Microsoft Oauth access token -- https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens getMsaToken(): Promise @@ -101,6 +97,8 @@ declare module 'prismarine-auth' { deviceVersion?: string password?: string flow: 'live' | 'msal' | 'sisu' + // Reset the cache and obtain fresh tokens for everything + forceRefresh?: boolean } export enum Titles { @@ -130,6 +128,7 @@ declare module 'prismarine-auth' { } export interface Cache { + reset(): Promise getCached(): Promise setCached(value: any): Promise setCachedPartial(value: any): Promise diff --git a/src/MicrosoftAuthFlow.js b/src/MicrosoftAuthFlow.js index 861a7d9..234b27b 100644 --- a/src/MicrosoftAuthFlow.js +++ b/src/MicrosoftAuthFlow.js @@ -26,6 +26,8 @@ async function retry (methodFn, beforeRetry, times) { } } +const CACHE_IDS = ['msal', 'live', 'sisu', 'xbl', 'bed', 'mca'] + class MicrosoftAuthFlow { constructor (username = '', cache = __dirname, options, codeCallback) { this.username = username @@ -33,11 +35,11 @@ class MicrosoftAuthFlow { throw new Error("Missing 'flow' argument in options. See docs for more information.") } this.options = options || { flow: 'live', authTitle: Titles.MinecraftNintendoSwitch } - this.initTokenManagers(username, cache) + this.initTokenManagers(username, cache, options?.forceRefresh) this.codeCallback = codeCallback } - initTokenManagers (username, cache) { + initTokenManagers (username, cache, forceRefresh) { if (typeof cache !== 'function') { let cachePath = cache @@ -48,13 +50,20 @@ class MicrosoftAuthFlow { fs.mkdirSync(cachePath, { recursive: true }) } } catch (e) { - console.log('Failed to open cache dir', e) + console.log('Failed to open cache dir', e, ' ... will use current dir') cachePath = __dirname } cache = ({ cacheName, username }) => { + if (!CACHE_IDS.includes(cacheName)) { + throw new Error(`Cannot instantiate cache for unknown ID: '${cacheName}'`) + } const hash = createHash(username) - return new FileCache(path.join(cachePath, `./${hash}_${cacheName}-cache.json`)) + const result = new FileCache(path.join(cachePath, `./${hash}_${cacheName}-cache.json`)) + if (forceRefresh) { + result.reset() + } + return result } } @@ -80,19 +89,6 @@ class MicrosoftAuthFlow { this.mca = new JavaTokenManager(cache({ cacheName: 'mca', username })) } - static resetTokenCaches (cache) { - if (!cache) throw new Error('You must provide a cache directory to reset.') - try { - if (fs.existsSync(cache)) { - fs.rmSync(cache, { recursive: true }) - return true - } - } catch (e) { - console.log('Failed to clear cache dir', e) - return false - } - } - async getMsaToken () { if (await this.msa.verifyTokens()) { debug('[msa] Using existing tokens') diff --git a/src/common/cache/FileCache.js b/src/common/cache/FileCache.js index ddfc1e6..0a41ce2 100644 --- a/src/common/cache/FileCache.js +++ b/src/common/cache/FileCache.js @@ -5,13 +5,17 @@ class FileCache { this.cacheLocation = cacheLocation } + async reset () { + const cached = {} + fs.writeFileSync(this.cacheLocation, JSON.stringify(cached)) + return cached + } + async loadInitialValue () { try { return JSON.parse(fs.readFileSync(this.cacheLocation, 'utf8')) } catch (e) { - const cached = {} - fs.writeFileSync(this.cacheLocation, JSON.stringify(cached)) - return cached + return this.reset() } }