#!/usr/bin/env node
/**
* UMBRELLA API AUTHENTICATION EXPLANATION
* ========================================
* This script explains and demonstrates how Umbrella API authentication works
*/
const axios = require('axios');
const crypto = require('crypto');
// Color codes for terminal output
const colors = {
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
magenta: '\x1b[35m',
reset: '\x1b[0m',
bold: '\x1b[1m',
underline: '\x1b[4m'
};
console.log(`
${colors.cyan}${colors.bold}════════════════════════════════════════════════════════════════════════
UMBRELLA API AUTHENTICATION SYSTEM
════════════════════════════════════════════════════════════════════════${colors.reset}
`);
console.log(`${colors.bold}${colors.underline}HOW UMBRELLA API AUTHENTICATION WORKS:${colors.reset}
`);
// STEP 1: Initial Authentication
console.log(`${colors.green}${colors.bold}STEP 1: INITIAL AUTHENTICATION${colors.reset}
${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
The authentication process starts by sending credentials to the ${colors.yellow}/authentication${colors.reset} endpoint:
${colors.magenta}POST https://api.umbrellacost.io/api/v1/authentication${colors.reset}
${colors.blue}Body:${colors.reset}
{
"username": "user@example.com",
"password": "password123"
}
${colors.green}Response:${colors.reset}
{
"access_token": "Bearer xxx.yyy.zzz",
"refresh_token": "refresh_xxx",
"expires_in": 3600
}
`);
// STEP 2: Dual-Header System
console.log(`${colors.green}${colors.bold}STEP 2: DUAL-HEADER AUTHENTICATION SYSTEM${colors.reset}
${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
Umbrella API uses a ${colors.bold}DUAL-HEADER${colors.reset} authentication system:
${colors.yellow}1. Authorization Header:${colors.reset}
• Contains the Bearer token from authentication
• Format: "Bearer <access_token>"
• This identifies WHO you are
${colors.yellow}2. apikey Header:${colors.reset}
• Contains a context-specific API key
• This determines WHAT DATA you can access
• Has a hierarchy system (see Step 3)
${colors.blue}Example headers for API call:${colors.reset}
{
"Authorization": "Bearer xxx.yyy.zzz",
"apikey": "context-specific-key-here"
}
`);
// STEP 3: API Key Hierarchy
console.log(`${colors.green}${colors.bold}STEP 3: API KEY HIERARCHY${colors.reset}
${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
The ${colors.yellow}apikey${colors.reset} header follows a priority hierarchy:
${colors.bold}Priority Order (High → Low):${colors.reset}
${colors.green}1. CUSTOMER-SPECIFIC KEY${colors.reset} (Highest Priority)
• Used when accessing specific customer data (MSP scenarios)
• Built dynamically using customer account key
• Example: "customer_12345_encrypted_key"
• Provides access to that customer's data only
${colors.blue}2. CLOUD-CONTEXT KEY${colors.reset} (Medium Priority)
• Used when accessing cloud-specific data (AWS, Azure, GCP)
• Built based on cloud provider context
• Example: "aws_context_key" or "azure_context_key"
• Limits data to specific cloud provider
${colors.yellow}3. DEFAULT KEY${colors.reset} (Lowest Priority)
• The base API key from authentication
• Used when no specific context is needed
• Provides general access to your account data
`);
// STEP 4: How Keys Are Built
console.log(`${colors.green}${colors.bold}STEP 4: HOW CONTEXT-SPECIFIC KEYS ARE BUILT${colors.reset}
${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
Context-specific keys are built dynamically:
${colors.yellow}For Customer-Specific Access:${colors.reset}
1. Take the customer's account key
2. Encrypt it with AES-256-GCM
3. Encode with Base64
4. Format: "customer_<encrypted_data>"
${colors.yellow}For Cloud-Specific Access:${colors.reset}
1. Take the cloud provider name (aws/azure/gcp)
2. Append to base token
3. Format: "<base_token>_<cloud>"
${colors.blue}Code example from api-client.ts:${colors.reset}
`);
console.log(`${colors.magenta}
// Priority logic in getAuthHeaders():
let apiKey = null;
// 1. Try customer-specific key first
if (customerAccountKey) {
apiKey = await buildCustomerApiKey(customerAccountKey);
}
// 2. Try cloud-specific key if no customer key
if (!apiKey && cloudContext) {
apiKey = buildCloudContextApiKey(cloudContext);
}
// 3. Use default key as fallback
if (!apiKey) {
apiKey = this._authToken.apikey;
}
headers['apikey'] = apiKey;
${colors.reset}`);
// STEP 5: Making API Calls
console.log(`${colors.green}${colors.bold}STEP 5: MAKING AUTHENTICATED API CALLS${colors.reset}
${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
Every API call to Umbrella requires both headers:
${colors.yellow}Example: Getting AWS costs for a specific month:${colors.reset}
${colors.magenta}GET https://api.umbrellacost.io/api/v1/invoices/caui${colors.reset}
${colors.blue}Headers:${colors.reset}
{
"Authorization": "Bearer xxx.yyy.zzz",
"apikey": "aws_context_key"
}
${colors.blue}Query Parameters:${colors.reset}
{
"startDate": "2025-01-01",
"endDate": "2025-01-31",
"cloudProvider": "aws",
"periodGranLevel": "month"
}
`);
// STEP 6: MSP Customer Access
console.log(`${colors.green}${colors.bold}STEP 6: MSP (MANAGED SERVICE PROVIDER) ACCESS${colors.reset}
${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
For MSP scenarios (accessing multiple customer accounts):
${colors.yellow}1. Initial Authentication:${colors.reset}
• MSP authenticates with their credentials
• Receives token with access to all managed customers
${colors.yellow}2. Customer List Retrieval:${colors.reset}
• Call /users/plain-sub-users to get customer divisions
• Returns list of customers with their account keys
${colors.yellow}3. Customer-Specific Access:${colors.reset}
• When accessing customer data, build customer-specific apikey
• Add 'commonparams' header: {"isPpApplied": true}
• This ensures correct data isolation
${colors.blue}Example MSP API call:${colors.reset}
{
"Authorization": "Bearer msp_token",
"apikey": "encrypted_customer_key",
"commonparams": "{\\"isPpApplied\\":true}"
}
`);
// STEP 7: Token Lifecycle
console.log(`${colors.green}${colors.bold}STEP 7: TOKEN LIFECYCLE & REFRESH${colors.reset}
${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
${colors.yellow}Token Expiration:${colors.reset}
• Access tokens typically expire in 1 hour (3600 seconds)
• Must be refreshed before expiration
${colors.yellow}Refresh Process:${colors.reset}
1. Use refresh_token to get new access_token
2. Update Authorization header with new token
3. Continue making API calls
${colors.yellow}Session State:${colors.reset}
• Authentication is stateless - each request is independent
• Token contains all necessary session information
• No server-side session storage required
`);
// STEP 8: Error Handling
console.log(`${colors.green}${colors.bold}STEP 8: AUTHENTICATION ERROR HANDLING${colors.reset}
${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}
Common authentication errors and their meanings:
${colors.red}401 Unauthorized:${colors.reset}
• Invalid or expired token
• Solution: Re-authenticate or refresh token
${colors.red}403 Forbidden:${colors.reset}
• Valid authentication but no access to resource
• Wrong apikey for the requested data
• Solution: Check context and permissions
${colors.red}"Authentication failed" message:${colors.reset}
• Invalid username/password
• Account may be locked or disabled
• Solution: Verify credentials
`);
// Practical Example
console.log(`
${colors.cyan}${colors.bold}════════════════════════════════════════════════════════════════════════
PRACTICAL EXAMPLE
════════════════════════════════════════════════════════════════════════${colors.reset}
`);
// Demo authentication flow
async function demonstrateAuth() {
console.log(`${colors.yellow}Let's demonstrate the authentication flow:${colors.reset}\n`);
// Mock credentials (replace with real ones for testing)
const credentials = {
username: 'david+saola@umbrellacost.com',
password: 'Dsamsung1!'
};
console.log(`${colors.blue}1. Authenticating with Umbrella API...${colors.reset}`);
try {
const authResponse = await axios.post(
'https://api.umbrellacost.io/api/v1/authentication',
credentials
);
if (authResponse.data && authResponse.data.access_token) {
console.log(`${colors.green}✅ Authentication successful!${colors.reset}`);
console.log(` Token: ${authResponse.data.access_token.substring(0, 50)}...`);
// Now make an API call with the token
console.log(`\n${colors.blue}2. Making authenticated API call to get costs...${colors.reset}`);
const headers = {
'Authorization': `Bearer ${authResponse.data.access_token}`,
'apikey': authResponse.data.access_token
};
const costResponse = await axios.get(
'https://api.umbrellacost.io/api/v1/invoices/caui',
{
headers: headers,
params: {
startDate: '2025-01-01',
endDate: '2025-01-31',
periodGranLevel: 'month'
}
}
);
console.log(`${colors.green}✅ API call successful!${colors.reset}`);
console.log(` Response has ${Object.keys(costResponse.data).length} fields`);
}
} catch (error) {
if (error.response?.status === 401) {
console.log(`${colors.red}❌ Authentication failed (401)${colors.reset}`);
console.log(` This means the credentials are invalid`);
} else if (error.response?.status === 403) {
console.log(`${colors.red}❌ Access forbidden (403)${colors.reset}`);
console.log(` You're authenticated but don't have permission`);
} else {
console.log(`${colors.red}❌ Error: ${error.message}${colors.reset}`);
}
}
}
// SUMMARY
console.log(`
${colors.cyan}${colors.bold}════════════════════════════════════════════════════════════════════════
KEY POINTS SUMMARY
════════════════════════════════════════════════════════════════════════${colors.reset}
${colors.green}${colors.bold}Key Takeaways:${colors.reset}
${colors.yellow}1.${colors.reset} Umbrella uses ${colors.bold}dual-header authentication${colors.reset} (Authorization + apikey)
${colors.yellow}2.${colors.reset} The ${colors.bold}Authorization header${colors.reset} identifies WHO you are
${colors.yellow}3.${colors.reset} The ${colors.bold}apikey header${colors.reset} determines WHAT DATA you can access
${colors.yellow}4.${colors.reset} API keys follow a ${colors.bold}hierarchy${colors.reset}: Customer > Cloud > Default
${colors.yellow}5.${colors.reset} MSP scenarios use ${colors.bold}customer-specific keys${colors.reset} for data isolation
${colors.yellow}6.${colors.reset} All requests are ${colors.bold}stateless${colors.reset} - token contains all session info
${colors.yellow}7.${colors.reset} Tokens expire and must be ${colors.bold}refreshed${colors.reset} periodically
`);
// Run demonstration if requested
if (process.argv.includes('--demo')) {
console.log(`\n${colors.cyan}${colors.bold}Running live demonstration...${colors.reset}\n`);
demonstrateAuth();
} else {
console.log(`\n${colors.cyan}Run with --demo flag to see a live authentication example${colors.reset}`);
}