import axios, { AxiosInstance } from 'axios';
import { AuthCredentials, AuthToken, AuthCredentialsSchema } from './types.js';
export interface UserManagementInfo {
isKeycloak: boolean;
realm?: string;
authMethod: 'keycloak' | 'cognito';
}
export class UmbrellaDualAuth {
private baseURL: string;
private axiosInstance: AxiosInstance;
private token: string | null = null;
private tokenExpiry: number = 0;
private userManagementInfo: UserManagementInfo | null = null;
private userKey: string | null = null;
private username: string | null = null;
private availableAccounts: any[] = [];
private plainSubUsersCache: Map<string, { data: any; timestamp: number }> = new Map();
private CACHE_TTL_MS = 10 * 60 * 1000; // Cache for 10 minutes
constructor(baseURL: string) {
this.baseURL = baseURL;
this.axiosInstance = axios.create({
baseURL: this.baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
}
/**
* Set pre-authenticated tokens (for HTTPS OAuth flow)
*/
setPreAuthenticatedTokens(tokens: { Authorization: string }): void {
this.token = tokens.Authorization;
// Set token expiry to 24 hours from now (or match config)
this.tokenExpiry = Date.now() + (24 * 60 * 60 * 1000);
// Extract userKey from JWT token (Cognito token format)
try {
// The Authorization should be the raw JWT, not with Bearer prefix
let jwtToken = tokens.Authorization;
if (jwtToken.startsWith('Bearer ')) {
jwtToken = jwtToken.substring(7); // Remove 'Bearer ' prefix if present
}
// Decode the JWT payload to extract the user ID
const tokenPayload = JSON.parse(Buffer.from(jwtToken.split('.')[1], 'base64').toString());
// Extract userKey from token dynamically based on what's in the token
this.userKey = tokenPayload.sub || // Cognito uses 'sub' for user ID
tokenPayload['cognito:username'] || // Alternative Cognito field
tokenPayload['custom:apikey']?.split(':')[0] ||
tokenPayload['custom:user_key'] ||
tokenPayload.username ||
tokenPayload['custom:username'];
if (this.userKey) {
console.error(`[DUAL-AUTH] Extracted userKey from JWT: ${this.userKey}`);
} else {
console.error('[DUAL-AUTH] Warning: Could not extract userKey from JWT token');
}
// Extract username/email for UM detection
this.username = tokenPayload.email || tokenPayload.preferred_username || tokenPayload.username;
if (this.username) {
console.error(`[DUAL-AUTH] Extracted username from JWT: ${this.username}`);
// Detect user management system (Keycloak vs Cognito) asynchronously
// This is a non-blocking call that sets userManagementInfo for later use
this.detectUserManagementSystem(this.username).catch(err => {
console.error('[DUAL-AUTH] Failed to detect UM system:', err.message);
});
}
} catch (error) {
console.error('[DUAL-AUTH] Failed to extract userKey from token:', error);
}
// Don't fetch accounts here - it causes timeouts in Claude Desktop
// Accounts will be fetched lazily when buildCustomerApiKey is called
}
/**
* Fetch accounts for the authenticated user (OAuth flow)
* This also detects and sets the user management system info
*/
private async fetchAccounts(): Promise<void> {
if (!this.token || !this.userKey) {
console.error('[DUAL-AUTH] Cannot fetch accounts: missing token or userKey');
return;
}
try {
// For OAuth users, we need to detect the auth method first
// Since we don't have username, we'll default to checking both endpoints
// Try Keycloak UM 2.0 endpoint first with correct API key format: {userKey}:-1:-1
console.error('[DUAL-AUTH] 🔍 OAuth: Trying Keycloak UM 2.0 endpoint...');
try {
const keycloakApiKey = `${this.userKey}:-1:-1`;
const accountsResponse = await this.axiosInstance.get('/user-management/accounts', {
headers: {
'Authorization': this.token,
'apikey': keycloakApiKey,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (accountsResponse.status === 200 && accountsResponse.data) {
this.availableAccounts = accountsResponse.data;
this.userManagementInfo = { isKeycloak: true, authMethod: 'keycloak' };
console.error(`[DUAL-AUTH] ✅ Keycloak UM 2.0 detected - Fetched ${this.availableAccounts.length} accounts`);
console.error(`[DUAL-AUTH] ✅ userManagementInfo set:`, JSON.stringify(this.userManagementInfo));
return;
}
} catch (keycloakError: any) {
console.error('[DUAL-AUTH] ⚠️ Keycloak UM 2.0 endpoint failed, trying Cognito...');
}
// Fallback to Cognito endpoint with Old UM API key format: {userKey}:-1
console.error('[DUAL-AUTH] 🔍 OAuth: Trying Cognito Old UM endpoint...');
const cognitoApiKey = `${this.userKey}:-1`;
const cognitoResponse = await this.axiosInstance.get('/users', {
headers: {
'Authorization': this.token,
'apikey': cognitoApiKey,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (cognitoResponse.status === 200 && cognitoResponse.data) {
this.availableAccounts = cognitoResponse.data.accounts || [];
this.userManagementInfo = { isKeycloak: false, authMethod: 'cognito' };
console.error(`[DUAL-AUTH] ✅ Cognito Old UM detected - Fetched ${this.availableAccounts.length} accounts`);
console.error(`[DUAL-AUTH] ✅ userManagementInfo set:`, JSON.stringify(this.userManagementInfo));
}
// Debug: List all available accounts
if (this.availableAccounts.length > 0) {
console.error(`[DUAL-AUTH] 🔍 Available accounts:`);
this.availableAccounts.forEach((acc: any, index: number) => {
const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id || 'N/A';
const accountName = acc.accountName || acc.account_name || acc.name || 'N/A';
console.error(`[DUAL-AUTH] ${index + 1}. Key: ${accountKey}, Name: ${accountName}, Type: ${acc.cloudType || 'N/A'}`);
});
}
} catch (error: any) {
console.error(`[DUAL-AUTH] Failed to fetch accounts: ${error.message}`);
this.availableAccounts = [];
}
}
/**
* Detect user management system (Keycloak UM 2.0 vs Cognito Old UM)
*/
async detectUserManagementSystem(username: string): Promise<UserManagementInfo> {
try {
console.error(`[DUAL-AUTH] Detecting user management system for: ${username}`);
// Call the user-realm endpoint to check if user is on Keycloak
const realmResponse = await this.axiosInstance.get(`/v1/user-management/users/user-realm`, {
params: { username: username.toLowerCase() }
});
if (realmResponse.status === 200 && realmResponse.data?.realm) {
console.error(`[DUAL-AUTH] User is on Keycloak UM 2.0, realm: ${realmResponse.data.realm}`);
return {
isKeycloak: true,
realm: realmResponse.data.realm,
authMethod: 'keycloak'
};
} else {
console.error(`[DUAL-AUTH] User is on Cognito Old UM (no realm found)`);
return {
isKeycloak: false,
authMethod: 'cognito'
};
}
} catch (error: any) {
console.error(`[DUAL-AUTH] Realm detection failed, defaulting to Cognito: ${error.message}`);
// Default to old user management if detection fails
return {
isKeycloak: false,
authMethod: 'cognito'
};
}
}
async authenticate(credentials: AuthCredentials): Promise<AuthToken> {
// Validate credentials
const validatedCredentials = AuthCredentialsSchema.parse(credentials);
// First, detect which user management system this user is on
this.userManagementInfo = await this.detectUserManagementSystem(validatedCredentials.username);
try {
let authResponse;
if (this.userManagementInfo.isKeycloak) {
// Use new authentication endpoint for Keycloak users (UM 2.0)
console.error(`[DUAL-AUTH] Authenticating Keycloak user with /authentication/token/generate...`);
try {
authResponse = await this.authenticateKeycloak(validatedCredentials);
} catch (keycloakError: any) {
// Fallback: If Keycloak fails with 500, try Cognito (some migrated accounts may still use Cognito)
if (keycloakError.response?.status === 500) {
console.error(`[DUAL-AUTH] Keycloak failed with 500, trying Cognito fallback...`);
// Update the user management info to reflect we're actually using Cognito
this.userManagementInfo = {
isKeycloak: false,
authMethod: 'cognito'
};
authResponse = await this.authenticateCognito(validatedCredentials);
} else {
throw keycloakError;
}
}
} else {
// Use old signin endpoint for Cognito users (Old UM)
console.error(`[DUAL-AUTH] Authenticating Cognito user with /users/signin...`);
authResponse = await this.authenticateCognito(validatedCredentials);
}
return authResponse;
} catch (error: any) {
console.error(`[DUAL-AUTH] Authentication failed for ${this.userManagementInfo.authMethod}: ${error.message}`);
if (error.response) {
console.error(`[DUAL-AUTH] HTTP Status: ${error.response.status}`);
console.error(`[DUAL-AUTH] Response Data:`, JSON.stringify(error.response.data, null, 2));
}
throw error;
}
}
/**
* Authenticate using Keycloak (User Management 2.0)
* Uses the new /authentication/token/generate endpoint with Basic Auth
*/
private async authenticateKeycloak(credentials: AuthCredentials): Promise<AuthToken> {
// Create Basic Auth header - this is what the authentication endpoint expects!
const basicAuth = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
const response = await this.axiosInstance.post('/v1/authentication/token/generate', {
username: credentials.username,
password: credentials.password
}, {
headers: {
'Authorization': `Basic ${basicAuth}`,
'Content-Type': 'application/json'
}
});
if (response.status === 200 && response.data.Authorization && response.data.apikey) {
this.token = response.data.Authorization;
// Extract and store userKey for Keycloak users
const tempApiKey = response.data.apikey;
this.userKey = tempApiKey.split(':')[0];
this.tokenExpiry = Date.now() + (60 * 60 * 1000);
console.error(`[DUAL-AUTH] Keycloak authentication successful, userKey: ${this.userKey}`);
return {
Authorization: response.data.Authorization,
userManagementInfo: this.userManagementInfo || undefined
};
} else {
throw new Error('Invalid response from Keycloak authentication endpoint');
}
}
/**
* Authenticate using Cognito (Old User Management)
* Uses the old /users/signin endpoint
*/
private async authenticateCognito(credentials: AuthCredentials): Promise<AuthToken> {
const response = await this.axiosInstance.post('/v1/users/signin', {
username: credentials.username,
password: credentials.password
});
if (response.status === 200 && response.data.jwtToken) {
this.token = response.data.jwtToken;
// For Cognito, the response format is different
// Extract userKey from JWT token payload
const tokenPayload = JSON.parse(Buffer.from(response.data.jwtToken.split('.')[1], 'base64').toString());
this.userKey = tokenPayload.username || tokenPayload['custom:username'] || tokenPayload.sub;
this.tokenExpiry = Date.now() + (60 * 60 * 1000);
console.error(`[DUAL-AUTH] Cognito authentication successful, userKey: ${this.userKey}`);
return {
Authorization: response.data.jwtToken,
userManagementInfo: this.userManagementInfo || undefined
};
} else {
throw new Error('Invalid response from Cognito authentication endpoint');
}
}
/**
* Get current authentication headers for API requests
*/
getAuthHeaders(): AuthToken {
if (!this.token) {
throw new Error('Not authenticated. Call authenticate() first.');
}
if (Date.now() > this.tokenExpiry) {
throw new Error('Token expired. Please re-authenticate.');
}
return {
Authorization: this.token
};
}
/**
* Get user management system info
*/
getUserManagementInfo(): UserManagementInfo | null {
return this.userManagementInfo;
}
/**
* Get user key for building API keys
*/
getUserKey(): string | null {
return this.userKey;
}
/**
* Get default API key - builds a simple API key with userKey and default account
*/
getDefaultApiKey(): string | null {
if (!this.userKey) {
console.error('[DUAL-AUTH] Cannot build default API key: missing userKey');
return null;
}
// Use the first available account or a default format
if (this.availableAccounts.length > 0) {
const firstAccount = this.availableAccounts[0];
const accountKey = firstAccount.account_key || firstAccount.accountKey;
// For Keycloak UM 2.0: use empty divisionId with trailing colon
// For Cognito: use divisionId from account or 0
if (this.userManagementInfo?.isKeycloak) {
return `${this.userKey}:${accountKey}:`;
} else {
const divisionId = firstAccount.division_id || 0;
return `${this.userKey}:${accountKey}:${divisionId}`;
}
}
// Fallback to user key with placeholder values
// For Keycloak: -1:-1, For Cognito: -1:0
if (this.userManagementInfo?.isKeycloak) {
return `${this.userKey}:-1:-1`;
} else {
return `${this.userKey}:-1:0`;
}
}
/**
* Check if the current token is still valid
*/
isAuthenticated(): boolean {
return !!(this.token && Date.now() < this.tokenExpiry);
}
/**
* Build API key for specific cloud context
*/
buildCloudContextApiKey(cloudContext: string): string | null {
if (!this.userKey || this.availableAccounts.length === 0) {
console.error('[DUAL-AUTH] Cannot build cloud context API key: missing userKey or accounts');
return null;
}
const normalizedContext = cloudContext.toLowerCase();
let targetAccount = null;
console.error(`[DUAL-AUTH] 🔍 Building API key for cloud context: ${normalizedContext}`);
// Find the appropriate account based on cloud context
if (normalizedContext === 'aws') {
// AWS: Look for account 9350
targetAccount = this.availableAccounts.find((acc: any) => {
const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id;
const keyString = String(accountKey);
return keyString === '9350' || accountKey === 9350;
});
} else if (normalizedContext === 'gcp') {
// GCP: Look for MasterBilling account (contains "Master", "21112", or "59f88c")
targetAccount = this.availableAccounts.find((acc: any) => {
const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id;
const accountName = acc.accountName || acc.account_name || acc.name || '';
const keyString = String(accountKey);
return keyString === '21112' ||
accountKey === 21112 ||
accountName.toLowerCase().includes('master') ||
keyString.includes('59f88c');
});
} else if (normalizedContext === 'azure') {
// Azure: Prioritize AzureAmortized (23105) which has more recent data, then fallback to others
targetAccount = this.availableAccounts.find((acc: any) => {
const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id;
const keyString = String(accountKey);
// First priority: AzureAmortized (has 2025 data)
return keyString === '23105' || accountKey === 23105;
});
// If AzureAmortized not found, look for other Azure accounts
if (!targetAccount) {
targetAccount = this.availableAccounts.find((acc: any) => {
const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id;
const accountName = acc.accountName || acc.account_name || acc.name || '';
const keyString = String(accountKey);
// Check for other Azure accounts or "Azure" in name
return keyString === '9347' || accountKey === 9347 || // Azure-Pileus
accountName.toLowerCase().includes('azure');
});
}
}
if (!targetAccount) {
console.error(`[DUAL-AUTH] ⚠️ No ${normalizedContext.toUpperCase()} account found, using default AWS account`);
// Fallback to AWS account 9350
targetAccount = this.availableAccounts.find((acc: any) => {
const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id;
const keyString = String(accountKey);
return keyString === '9350' || accountKey === 9350;
});
}
if (!targetAccount) {
console.error(`[DUAL-AUTH] ⚠️ No suitable account found, using first available account`);
targetAccount = this.availableAccounts[0];
}
const accountKey = targetAccount.accountKey || targetAccount.account_key || targetAccount.accountId || targetAccount.account_id;
// For Keycloak UM 2.0 Direct customers: use empty divisionId with trailing colon
// For Cognito (Old UM): use divisionId 0
let divisionId: string | number = 0;
if (this.userManagementInfo?.isKeycloak) {
divisionId = ''; // Empty string for UM 2.0
console.error(`[DUAL-AUTH] Keycloak UM 2.0 detected - using empty divisionId`);
}
const cloudApiKey = `${this.userKey}:${accountKey}:${divisionId}`;
console.error(`[DUAL-AUTH] ✅ Built ${normalizedContext.toUpperCase()} API key: ${cloudApiKey}`);
return cloudApiKey;
}
async buildCustomerApiKey(customerAccountKey: string, apiPath?: string, customerDivisionId?: string): Promise<string | null> {
console.error(`[DUAL-AUTH] 🔧 buildCustomerApiKey CALLED with:`);
console.error(`[DUAL-AUTH] customerAccountKey: "${customerAccountKey}"`);
console.error(`[DUAL-AUTH] customerDivisionId: "${customerDivisionId}" (type: ${typeof customerDivisionId})`);
console.error(`[DUAL-AUTH] apiPath: "${apiPath}"`);
console.error(`[DUAL-AUTH] this.token exists: ${!!this.token}`);
console.error(`[DUAL-AUTH] this.userKey: "${this.userKey}"`);
console.error(`[DUAL-AUTH] this.userManagementInfo?.isKeycloak: ${this.userManagementInfo?.isKeycloak}`);
if (!this.userKey) {
console.error('[DUAL-AUTH] Cannot build customer API key: missing userKey');
return null;
}
// For OAuth users: userManagementInfo should already be set from the stored token
// DO NOT re-detect - use the auth method that actually succeeded during login
if (this.token && !this.userManagementInfo) {
console.error(`[DUAL-AUTH] ⚠️ OAuth user but userManagementInfo not set!`);
console.error(`[DUAL-AUTH] This should not happen - userManagementInfo should be in the token.`);
console.error(`[DUAL-AUTH] Defaulting to Cognito to be safe (most common legacy auth method).`);
// DO NOT call detectUserManagementSystem() - it's unreliable for users in migration state
// The token should already contain the correct auth method from login
this.userManagementInfo = { isKeycloak: false, authMethod: 'cognito' };
}
// For OAuth users with pre-authenticated tokens, if we have customerAccountKey and customerDivisionId,
// just build the API key directly without needing account data
const quickPathCondition = this.token && customerAccountKey && customerDivisionId !== undefined && customerDivisionId !== null;
console.error(`[DUAL-AUTH] 🔍 Quick path condition evaluation:`);
console.error(`[DUAL-AUTH] this.token: ${!!this.token}`);
console.error(`[DUAL-AUTH] customerAccountKey: "${customerAccountKey}" -> ${!!customerAccountKey}`);
console.error(`[DUAL-AUTH] customerDivisionId !== undefined: ${customerDivisionId !== undefined}`);
console.error(`[DUAL-AUTH] customerDivisionId !== null: ${customerDivisionId !== null}`);
console.error(`[DUAL-AUTH] ⚡ QUICK PATH: ${quickPathCondition ? 'YES - Taking quick path' : 'NO - Taking full lookup path'}`);
if (quickPathCondition) {
// For Keycloak UM 2.0: use empty divisionId (Direct customers don't use divisions)
// For Cognito: use numeric divisionId
let apiKey: string;
console.error(`[DUAL-AUTH] 🔍 Checking user management system: isKeycloak=${this.userManagementInfo?.isKeycloak}`);
if (this.userManagementInfo?.isKeycloak) {
apiKey = `${this.userKey}:${customerAccountKey}:`; // Empty divisionId with trailing colon
console.error(`[DUAL-AUTH] ✅ OAuth Keycloak UM 2.0 - Built API key with empty divisionId: ${apiKey}`);
console.error(`[DUAL-AUTH] IGNORING customerDivisionId parameter "${customerDivisionId}" for Keycloak user`);
} else {
const divisionId = parseInt(String(customerDivisionId), 10);
apiKey = `${this.userKey}:${customerAccountKey}:${divisionId}`;
console.error(`[DUAL-AUTH] ✅ OAuth Cognito - Built API key with divisionId: ${apiKey}`);
}
return apiKey;
}
// Fetch accounts lazily if not already fetched (OAuth flow)
if (this.token && this.availableAccounts.length === 0) {
console.error('[DUAL-AUTH] Lazy-fetching accounts for OAuth user...');
await this.fetchAccounts();
console.error(`[DUAL-AUTH] Fetched ${this.availableAccounts.length} accounts`);
}
if (this.availableAccounts.length === 0) {
console.error('[DUAL-AUTH] Cannot build customer API key: no accounts available');
return null;
}
console.error(`[DUAL-AUTH] 🔍 Building API key for customer account: ${customerAccountKey}`);
console.error(`[DUAL-AUTH] 📝 Received customerDivisionId parameter: ${customerDivisionId} (type: ${typeof customerDivisionId})`);
// Find the specific customer account
const targetAccount = this.availableAccounts.find((acc: any) => {
const accountKey = acc.accountKey || acc.account_key || acc.accountId || acc.account_id;
const keyString = String(accountKey);
return keyString === customerAccountKey || accountKey === customerAccountKey;
});
if (!targetAccount) {
console.error(`[DUAL-AUTH] ⚠️ Customer account ${customerAccountKey} not found in available accounts`);
return null;
}
const accountKey = targetAccount.accountKey || targetAccount.account_key || targetAccount.accountId || targetAccount.account_id;
const accountName = targetAccount.accountName || targetAccount.account_name || targetAccount.name || '';
// Different division lookup logic based on user management system
// For Keycloak UM 2.0 Direct customers: divisionId is empty string (with trailing colon)
// For Cognito (Old UM): divisionId is a number
let divisionId: string | number = 1; // Default fallback
// PRIORITY 1: Use provided customerDivisionId if available
if (customerDivisionId !== undefined && customerDivisionId !== null && customerDivisionId !== '') {
divisionId = parseInt(String(customerDivisionId), 10);
console.error(`[DUAL-AUTH] ✅ Using provided division ${divisionId} from parameter (overrides system-specific lookup)`);
} else if (this.userManagementInfo?.isKeycloak) {
// Keycloak UM 2.0: Use /user-management/accounts API for Direct customers (no divisions)
// For UM 2.0 Direct customers, we use empty divisionId with trailing colon
try {
// Use the special API key format for fetching accounts: {userKey}:-1:-1
const tempApiKey = `${this.userKey}:-1:-1`;
const cacheKey = `um2-accounts:${this.userKey}`;
// Check cache first
let accountsData = null;
const cached = this.plainSubUsersCache.get(cacheKey);
if (cached && (Date.now() - cached.timestamp) < this.CACHE_TTL_MS) {
console.error(`[DUAL-AUTH] 📦 Using cached user-management/accounts data (cache age: ${Math.round((Date.now() - cached.timestamp) / 1000)}s)`);
accountsData = cached.data;
} else {
console.error(`[DUAL-AUTH] 🔍 Keycloak UM 2.0 - Fetching accounts via /user-management/accounts API...`);
const accountsResponse = await this.axiosInstance.get('/user-management/accounts', {
headers: {
'Authorization': this.token,
'apikey': tempApiKey,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (accountsResponse.status === 200 && accountsResponse.data) {
accountsData = accountsResponse.data;
// Cache the response
this.plainSubUsersCache.set(cacheKey, { data: accountsData, timestamp: Date.now() });
console.error(`[DUAL-AUTH] 📦 Cached user-management/accounts response`);
}
}
if (accountsData) {
// UM 2.0 Direct customers: No divisions, so divisionId is empty string
// The accounts response contains account list without division structure
console.error(`[DUAL-AUTH] 📋 Keycloak UM 2.0 Direct customer - No divisions (using empty divisionId)`);
divisionId = ''; // Empty string for UM 2.0 Direct customers
console.error(`[DUAL-AUTH] ✅ Keycloak UM 2.0: Using empty divisionId for Direct customer account ${customerAccountKey}`);
}
} catch (error: any) {
console.error(`[DUAL-AUTH] ⚠️ Keycloak UM 2.0 accounts lookup failed: ${error.message}`);
// For UM 2.0, default to empty divisionId on error
divisionId = '';
console.error(`[DUAL-AUTH] Using empty divisionId for Keycloak UM 2.0 user (Direct customer)`);
}
} else {
// Cognito (Old UM): Use customer-specific division mapping
console.error(`[DUAL-AUTH] 🔍 Cognito user - Using customer-specific division logic for MSP customer ${customerAccountKey}`);
// Use provided customer division ID if available (from customer detection)
if (customerDivisionId !== undefined && customerDivisionId !== null) {
divisionId = parseInt(customerDivisionId, 10);
console.error(`[DUAL-AUTH] ✅ Cognito: Using division ${divisionId} from customer detection system`);
} else {
// Fallback: For other customers, use division 0 (master division)
// This should only happen when no customer is detected (generic queries)
divisionId = 0;
console.error(`[DUAL-AUTH] ⚠️ Cognito: No division ID provided, using default division 0`);
}
}
const customerApiKey = `${this.userKey}:${accountKey}:${divisionId}`;
console.error(`[DUAL-AUTH] ✅ Built customer API key for "${accountName}" with division ${divisionId}: ${customerApiKey}`);
return customerApiKey;
}
/**
* Get available accounts (for debugging)
*/
getAvailableAccounts(): any[] {
return this.availableAccounts;
}
/**
* Clear the stored authentication data
*/
clearAuth(): void {
this.token = null;
this.tokenExpiry = 0;
this.userManagementInfo = null;
this.userKey = null;
this.availableAccounts = [];
}
}