From f790984a95892b6c5f4119067d64884930298467 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 25 Jul 2025 00:44:04 +0000 Subject: [PATCH] fix(oauth): remove OAuth session caching to prevent authentication issues --- changelog.md | 9 ++++ package.json | 2 +- readme.md | 29 ---------- test/test.oauth.caching.ts | 105 ------------------------------------- ts/bunq.classes.account.ts | 102 +++++++++-------------------------- 5 files changed, 34 insertions(+), 213 deletions(-) delete mode 100644 test/test.oauth.caching.ts diff --git a/changelog.md b/changelog.md index 4aa59ab..8e03cbb 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-07-22 - 3.1.2 - fix(oauth) +Remove OAuth session caching to prevent authentication issues + +- Removed static OAuth session cache that was causing incomplete session issues +- Each OAuth token now creates a fresh session without caching +- Removed cache management methods (clearOAuthCache, clearOAuthCacheForToken, getOAuthCacheSize) +- Simplified init() method to treat OAuth tokens the same as regular API keys +- OAuth tokens still handle "Superfluous authentication" errors with initWithExistingInstallation + ## 2025-07-22 - 3.1.1 - fix(oauth) Fix OAuth token authentication flow for existing installations diff --git a/package.json b/package.json index 2d934b3..304b3a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apiclient.xyz/bunq", - "version": "3.1.1", + "version": "3.1.2", "private": false, "description": "A full-featured TypeScript/JavaScript client for the bunq API", "type": "module", diff --git a/readme.md b/readme.md index 3ca9cab..fbcc2a9 100644 --- a/readme.md +++ b/readme.md @@ -449,35 +449,6 @@ const accounts = await bunq.getAccounts(); // According to bunq documentation: // "Just use the OAuth Token (access_token) as a normal bunq API key" - -// OAuth Session Caching (v3.0.9+) -// The library automatically caches OAuth sessions to prevent multiple authentication attempts - -// Multiple instances with the same OAuth token will reuse the cached session -const bunq1 = new BunqAccount({ - apiKey: 'your-oauth-access-token', - deviceName: 'OAuth App Instance 1', - environment: 'PRODUCTION', - isOAuthToken: true -}); - -const bunq2 = new BunqAccount({ - apiKey: 'your-oauth-access-token', // Same token - deviceName: 'OAuth App Instance 2', - environment: 'PRODUCTION', - isOAuthToken: true -}); - -await bunq1.init(); // Creates new session -await bunq2.init(); // Reuses cached session from bunq1 - -// This prevents "Superfluous authentication" errors when multiple instances -// try to authenticate with the same OAuth token - -// Cache management methods -BunqAccount.clearOAuthCache(); // Clear all cached OAuth sessions -BunqAccount.clearOAuthCacheForToken('token', 'PRODUCTION'); // Clear specific token -const cacheSize = BunqAccount.getOAuthCacheSize(); // Get current cache size ``` ### Error Handling diff --git a/test/test.oauth.caching.ts b/test/test.oauth.caching.ts deleted file mode 100644 index c849053..0000000 --- a/test/test.oauth.caching.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import * as bunq from '../ts/index.js'; - -tap.test('should cache and reuse OAuth sessions', async () => { - // Create first OAuth account instance - const oauthBunq1 = new bunq.BunqAccount({ - apiKey: 'test-oauth-token-cache', - deviceName: 'OAuth Test App 1', - environment: 'SANDBOX', - isOAuthToken: true - }); - - // Create second OAuth account instance with same token - const oauthBunq2 = new bunq.BunqAccount({ - apiKey: 'test-oauth-token-cache', - deviceName: 'OAuth Test App 2', - environment: 'SANDBOX', - isOAuthToken: true - }); - - try { - // Initialize first instance - await oauthBunq1.init(); - console.log('First OAuth instance initialized'); - - // Check cache size - const cacheSize1 = bunq.BunqAccount.getOAuthCacheSize(); - console.log(`Cache size after first init: ${cacheSize1}`); - - // Initialize second instance - should reuse cached session - await oauthBunq2.init(); - console.log('Second OAuth instance should have reused cached session'); - - // Both instances should share the same API context - expect(oauthBunq1.apiContext).toEqual(oauthBunq2.apiContext); - - // Cache size should still be 1 - const cacheSize2 = bunq.BunqAccount.getOAuthCacheSize(); - expect(cacheSize2).toEqual(1); - - } catch (error) { - // Expected to fail with invalid token, but we can test the caching logic - console.log('OAuth caching test completed (expected auth failure with mock token)'); - } -}); - -tap.test('should handle OAuth session cache clearing', async () => { - // Create OAuth account instance - const oauthBunq = new bunq.BunqAccount({ - apiKey: 'test-oauth-token-clear', - deviceName: 'OAuth Test App', - environment: 'SANDBOX', - isOAuthToken: true - }); - - try { - await oauthBunq.init(); - } catch (error) { - // Expected failure with mock token - } - - // Clear specific token from cache - bunq.BunqAccount.clearOAuthCacheForToken('test-oauth-token-clear', 'SANDBOX'); - - // Clear all OAuth cache - bunq.BunqAccount.clearOAuthCache(); - - // Cache should be empty - const cacheSize = bunq.BunqAccount.getOAuthCacheSize(); - expect(cacheSize).toEqual(0); - - console.log('OAuth cache clearing test passed'); -}); - -tap.test('should handle different OAuth tokens separately', async () => { - const oauthBunq1 = new bunq.BunqAccount({ - apiKey: 'test-oauth-token-1', - deviceName: 'OAuth Test App 1', - environment: 'SANDBOX', - isOAuthToken: true - }); - - const oauthBunq2 = new bunq.BunqAccount({ - apiKey: 'test-oauth-token-2', - deviceName: 'OAuth Test App 2', - environment: 'SANDBOX', - isOAuthToken: true - }); - - try { - await oauthBunq1.init(); - await oauthBunq2.init(); - } catch (error) { - // Expected failures with mock tokens - } - - // Should have 2 different cached sessions - const cacheSize = bunq.BunqAccount.getOAuthCacheSize(); - console.log(`Cache size with different tokens: ${cacheSize}`); - - // Clear cache for cleanup - bunq.BunqAccount.clearOAuthCache(); -}); - -tap.start(); \ No newline at end of file diff --git a/ts/bunq.classes.account.ts b/ts/bunq.classes.account.ts index ad7c698..e7c4413 100644 --- a/ts/bunq.classes.account.ts +++ b/ts/bunq.classes.account.ts @@ -17,9 +17,6 @@ export interface IBunqConstructorOptions { * the main bunq account */ export class BunqAccount { - // Static cache for OAuth token sessions to prevent multiple authentication attempts - private static oauthSessionCache = new Map(); - public options: IBunqConstructorOptions; public apiContext: BunqApiContext; public userId: number; @@ -35,59 +32,32 @@ export class BunqAccount { * Initialize the bunq account */ public async init() { - // For OAuth tokens, check if we already have a cached session - if (this.options.isOAuthToken) { - const cacheKey = `${this.options.apiKey}_${this.options.environment}`; - const cachedContext = BunqAccount.oauthSessionCache.get(cacheKey); - - if (cachedContext && cachedContext.hasValidSession()) { - // Reuse existing session - this.apiContext = cachedContext; - console.log('Reusing existing OAuth session from cache'); - } else { - // Create new context and cache it - this.apiContext = new BunqApiContext({ - apiKey: this.options.apiKey, - environment: this.options.environment, - deviceDescription: this.options.deviceName, - permittedIps: this.options.permittedIps, - isOAuthToken: this.options.isOAuthToken - }); - - try { - await this.apiContext.init(); - // Cache the successfully initialized context - BunqAccount.oauthSessionCache.set(cacheKey, this.apiContext); - } catch (error) { - // Handle "Superfluous authentication" or "Authentication token already has a user session" errors - if (error instanceof BunqApiError) { - const errorMessages = error.errors.map(e => e.error_description).join(' '); - if (errorMessages.includes('Superfluous authentication') || - errorMessages.includes('Authentication token already has a user session')) { - console.log('OAuth token already has installation/device, attempting to create new session...'); - // Try to create a new session with existing installation/device - await this.apiContext.initWithExistingInstallation(); - // Cache the context with new session - BunqAccount.oauthSessionCache.set(cacheKey, this.apiContext); - } else { - throw error; - } - } else { - throw error; - } - } - } - } else { - // Regular API key flow - this.apiContext = new BunqApiContext({ - apiKey: this.options.apiKey, - environment: this.options.environment, - deviceDescription: this.options.deviceName, - permittedIps: this.options.permittedIps, - isOAuthToken: this.options.isOAuthToken - }); + // Create API context for both OAuth tokens and regular API keys + this.apiContext = new BunqApiContext({ + apiKey: this.options.apiKey, + environment: this.options.environment, + deviceDescription: this.options.deviceName, + permittedIps: this.options.permittedIps, + isOAuthToken: this.options.isOAuthToken + }); + try { await this.apiContext.init(); + } catch (error) { + // Handle "Superfluous authentication" or "Authentication token already has a user session" errors + if (error instanceof BunqApiError && this.options.isOAuthToken) { + const errorMessages = error.errors.map(e => e.error_description).join(' '); + if (errorMessages.includes('Superfluous authentication') || + errorMessages.includes('Authentication token already has a user session')) { + console.log('OAuth token already has installation/device, attempting to create new session...'); + // Try to create a new session with existing installation/device + await this.apiContext.initWithExistingInstallation(); + } else { + throw error; + } + } else { + throw error; + } } // Create user instance @@ -207,28 +177,4 @@ export class BunqAccount { this.apiContext = null; } } - - /** - * Clear the OAuth session cache - */ - public static clearOAuthCache(): void { - BunqAccount.oauthSessionCache.clear(); - console.log('OAuth session cache cleared'); - } - - /** - * Clear a specific OAuth token from the cache - */ - public static clearOAuthCacheForToken(apiKey: string, environment: 'SANDBOX' | 'PRODUCTION'): void { - const cacheKey = `${apiKey}_${environment}`; - BunqAccount.oauthSessionCache.delete(cacheKey); - console.log(`OAuth session cache cleared for token in ${environment} environment`); - } - - /** - * Get the current size of the OAuth cache - */ - public static getOAuthCacheSize(): number { - return BunqAccount.oauthSessionCache.size; - } }