UNPKG

@sudowealth/schwab-api

Version:

TypeScript client for Charles Schwab API with OAuth support, market data, trading functionality, and complete type safety

94 lines (93 loc) 4.52 kB
import { handleApiError } from '../errors.js'; import { createLogger } from '../utils/secure-logger.js'; import { getMetadata, cloneRequestWithMetadata } from './middleware-metadata.js'; const logger = createLogger('TokenAuth'); /** * Default token authentication options */ export const DEFAULT_TOKEN_AUTH_OPTIONS = { advanced: { refreshExpiring: true, refreshThresholdMs: 300_000, // 5 minutes }, }; /** * Create a middleware that adds authentication headers to requests * and handles token refresh when needed. * * This middleware is designed to work with the unified ITokenLifecycleManager interface. * * @param options The token manager or options object * @returns A middleware function */ export function withTokenAuth(options) { // Handle both function signatures (tokenManager or options object) let config; if ('tokenManager' in options) { config = { ...options, advanced: { ...DEFAULT_TOKEN_AUTH_OPTIONS.advanced, ...options.advanced, }, }; } else { config = { tokenManager: options, ...DEFAULT_TOKEN_AUTH_OPTIONS, }; } const tokenManager = config.tokenManager; return async (req, next) => { // Get middleware metadata const metadata = getMetadata(req); // Initialize auth metadata if not exists if (!metadata.auth) { metadata.auth = { tokenRefreshed: false }; } try { logger.debug(`[withTokenAuth] Getting access token for ${req.url}`); // Debug: Check token provider type logger.debug(`[withTokenAuth] Token manager type: ${tokenManager.constructor.name}`); // Check if token manager supports refresh const supportsRefresh = tokenManager.supportsRefresh(); logger.debug(`[withTokenAuth] Token manager supports refresh: ${supportsRefresh}`); // Use the unified getAccessToken method which handles refreshing internally // Note: We cannot pass refreshThresholdMs here as the interface doesn't accept parameters // These should be configured at the token manager level instead const accessToken = await tokenManager.getAccessToken(); // Debug: Log token status (not the actual token) logger.debug(`[withTokenAuth] Token obtained: ${accessToken ? 'Yes (length: ' + accessToken.length + ')' : 'No'}`); if (!accessToken) { logger.warn(`[withTokenAuth] No access token available from provider for request to ${req.method} ${req.url}. ` + `Provider: ${tokenManager.constructor.name}. Proceeding without authentication.`); // Continue without authentication, which will likely result in a 401 error from the API // We don't throw here to allow for public endpoints that don't require authentication return next(req); } // Create a new request with updated headers and metadata const authorizedReq = cloneRequestWithMetadata(req); authorizedReq.headers.set('Authorization', `Bearer ${accessToken}`); // Debug: Log the headers being sent (without sensitive values) logger.debug('[withTokenAuth] Request headers set:', Object.fromEntries([...authorizedReq.headers.entries()].map(([k, v]) => k.toLowerCase() === 'authorization' ? [k, 'Bearer ***'] : [k, v]))); // Execute the request logger.debug(`[withTokenAuth] Executing authenticated request to ${req.url}`); const response = await next(authorizedReq); // Debug: Log response status logger.debug(`[withTokenAuth] Response status: ${response.status} for ${req.url}`); // Add auth metadata to the response const responseMetadata = getMetadata(response); responseMetadata.auth = metadata.auth; return response; } catch (error) { // Log the error with more context for debugging logger.error('[withTokenAuth] Failed to get access token:', error); // Provide detailed context for the error const context = `Failed to retrieve authentication token for request to ${req.method} ${req.url}`; // Pass the error to the central error handler with improved context return handleApiError(error, context); } }; }