From 70ef1298dbac4917c3f163a08c62eb5defc05140 Mon Sep 17 00:00:00 2001 From: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com> Date: Thu, 4 Sep 2025 21:00:14 +0200 Subject: [PATCH] chore: resolve conflicts --- .../tm-core/src/auth/auth-manager.test.ts | 54 ++++++++++--------- packages/tm-core/src/auth/auth-manager.ts | 22 ++++++-- packages/tm-core/src/auth/credential-store.ts | 38 ++++++++----- 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/packages/tm-core/src/auth/auth-manager.test.ts b/packages/tm-core/src/auth/auth-manager.test.ts index fc9e9ba0..e7238921 100644 --- a/packages/tm-core/src/auth/auth-manager.test.ts +++ b/packages/tm-core/src/auth/auth-manager.test.ts @@ -3,16 +3,18 @@ */ import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { AuthManager } from './auth-manager'; +import { AuthManager } from './auth-manager.js'; // Mock the logger to verify warnings -vi.mock('../logger', () => ({ - getLogger: () => ({ - warn: vi.fn(), - info: vi.fn(), - debug: vi.fn(), - error: vi.fn() - }) +const mockLogger = { + warn: vi.fn(), + info: vi.fn(), + debug: vi.fn(), + error: vi.fn() +}; + +vi.mock('../logger/index.js', () => ({ + getLogger: () => mockLogger })); describe('AuthManager Singleton', () => { @@ -24,59 +26,61 @@ describe('AuthManager Singleton', () => { it('should return the same instance on multiple calls', () => { const instance1 = AuthManager.getInstance(); const instance2 = AuthManager.getInstance(); - + expect(instance1).toBe(instance2); }); it('should use config on first call', () => { - const config = { + const config = { baseUrl: 'https://test.auth.com', configDir: '/test/config', configFile: '/test/config/auth.json' }; - + const instance = AuthManager.getInstance(config); expect(instance).toBeDefined(); }); it('should warn when config is provided after initialization', () => { - const logger = vi.mocked(require('../logger').getLogger()); - + // Clear previous calls + mockLogger.warn.mockClear(); + // First call with config AuthManager.getInstance({ baseUrl: 'https://first.auth.com' }); - + // Second call with different config AuthManager.getInstance({ baseUrl: 'https://second.auth.com' }); - + // Verify warning was logged - expect(logger.warn).toHaveBeenCalledWith( + expect(mockLogger.warn).toHaveBeenCalledWith( 'getInstance called with config after initialization; config is ignored.' ); }); it('should not warn when no config is provided after initialization', () => { - const logger = vi.mocked(require('../logger').getLogger()); - + // Clear previous calls + mockLogger.warn.mockClear(); + // First call with config AuthManager.getInstance({ configDir: '/test/config' }); - + // Second call without config AuthManager.getInstance(); - + // Verify no warning was logged - expect(logger.warn).not.toHaveBeenCalled(); + expect(mockLogger.warn).not.toHaveBeenCalled(); }); it('should allow resetting the instance', () => { const instance1 = AuthManager.getInstance(); - + // Reset the instance AuthManager.resetInstance(); - + // Get new instance const instance2 = AuthManager.getInstance(); - + // They should be different instances expect(instance1).not.toBe(instance2); }); -}); \ No newline at end of file +}); diff --git a/packages/tm-core/src/auth/auth-manager.ts b/packages/tm-core/src/auth/auth-manager.ts index 3242a3fd..f59931cf 100644 --- a/packages/tm-core/src/auth/auth-manager.ts +++ b/packages/tm-core/src/auth/auth-manager.ts @@ -7,10 +7,11 @@ import { OAuthFlowOptions, AuthenticationError, AuthConfig -} from './types'; -import { CredentialStore } from './credential-store'; -import { OAuthService } from './oauth-service'; -import { SupabaseAuthClient } from '../clients/supabase-client'; +} from './types.js'; +import { CredentialStore } from './credential-store.js'; +import { OAuthService } from './oauth-service.js'; +import { SupabaseAuthClient } from '../clients/supabase-client.js'; +import { getLogger } from '../logger/index.js'; /** * Authentication manager class @@ -33,10 +34,23 @@ export class AuthManager { static getInstance(config?: Partial): AuthManager { if (!AuthManager.instance) { AuthManager.instance = new AuthManager(config); + } else if (config) { + // Warn if config is provided after initialization + const logger = getLogger('AuthManager'); + logger.warn( + 'getInstance called with config after initialization; config is ignored.' + ); } return AuthManager.instance; } + /** + * Reset the singleton instance (useful for testing) + */ + static resetInstance(): void { + AuthManager.instance = null as any; + } + /** * Get stored authentication credentials */ diff --git a/packages/tm-core/src/auth/credential-store.ts b/packages/tm-core/src/auth/credential-store.ts index d9218d8e..c4d47f85 100644 --- a/packages/tm-core/src/auth/credential-store.ts +++ b/packages/tm-core/src/auth/credential-store.ts @@ -4,9 +4,9 @@ import fs from 'fs'; import path from 'path'; -import { AuthCredentials, AuthenticationError, AuthConfig } from './types'; -import { getAuthConfig } from './config'; -import { getLogger } from '../logger'; +import { AuthCredentials, AuthenticationError, AuthConfig } from './types.js'; +import { getAuthConfig } from './config.js'; +import { getLogger } from '../logger/index.js'; export class CredentialStore { private logger = getLogger('CredentialStore'); @@ -29,17 +29,16 @@ export class CredentialStore { fs.readFileSync(this.config.configFile, 'utf-8') ) as AuthCredentials; - // Normalize/migrate timestamps to numeric (handles both string ISO dates and numbers) - const expiresAtMs = - typeof authData.expiresAt === 'number' - ? authData.expiresAt - : authData.expiresAt - ? Date.parse(authData.expiresAt as unknown as string) - : undefined; - - // Update the authData with normalized timestamp - if (expiresAtMs !== undefined) { - authData.expiresAt = expiresAtMs; + // Parse expiration time for validation (expects ISO string format) + let expiresAtMs: number | undefined; + + if (authData.expiresAt) { + expiresAtMs = Date.parse(authData.expiresAt); + if (isNaN(expiresAtMs)) { + // Invalid date string - treat as expired + this.logger.error(`Invalid expiresAt format: ${authData.expiresAt}`); + return null; + } } // Check if token is expired (API keys never expire) @@ -90,6 +89,17 @@ export class CredentialStore { // Add timestamp authData.savedAt = new Date().toISOString(); + + // Validate expiresAt is a valid ISO string if present + if (authData.expiresAt) { + const ms = Date.parse(authData.expiresAt); + if (isNaN(ms)) { + throw new AuthenticationError( + `Invalid expiresAt format: ${authData.expiresAt}`, + 'SAVE_FAILED' + ); + } + } // Save credentials atomically with secure permissions const tempFile = `${this.config.configFile}.tmp`;