@simpleapps-com/augur-api 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PERFORMANCE.md +816 -0
- package/README.md +1084 -197
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -404,6 +404,218 @@ const auth = await api.joomla.users.verifyPassword({
|
|
|
404
404
|
});
|
|
405
405
|
```
|
|
406
406
|
|
|
407
|
+
## Power Features (Hidden Gems)
|
|
408
|
+
|
|
409
|
+
**Unlock the full potential** of the Augur API with these advanced features that dramatically improve your development experience:
|
|
410
|
+
|
|
411
|
+
### Data Methods Pattern ⭐ NEW!
|
|
412
|
+
|
|
413
|
+
Every endpoint provides both complete responses AND streamlined data-only access:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// Standard pattern - full response with metadata
|
|
417
|
+
const response = await api.joomla.users.list({ limit: 10 });
|
|
418
|
+
console.log(`Found ${response.data.length} users`);
|
|
419
|
+
console.log(`Total available: ${response.totalResults}`);
|
|
420
|
+
console.log(`Response status: ${response.status}`);
|
|
421
|
+
|
|
422
|
+
// Data-only pattern - direct access to the data array ⚡
|
|
423
|
+
const users = await api.joomla.users.listData({ limit: 10 });
|
|
424
|
+
console.log(users); // Direct User[] array, no wrapper
|
|
425
|
+
|
|
426
|
+
// Single item data access
|
|
427
|
+
const user = await api.joomla.users.getData('123');
|
|
428
|
+
console.log(user); // Direct User object, no response wrapper
|
|
429
|
+
|
|
430
|
+
// Works across ALL services
|
|
431
|
+
const products = await api.opensearch.itemSearch.searchData({
|
|
432
|
+
q: 'electrical wire',
|
|
433
|
+
searchType: 'query',
|
|
434
|
+
size: 20
|
|
435
|
+
}); // Direct item array
|
|
436
|
+
|
|
437
|
+
const pricing = await api.pricing.getPriceData({
|
|
438
|
+
customerId: 12345,
|
|
439
|
+
itemId: 'WIRE-123',
|
|
440
|
+
quantity: 10
|
|
441
|
+
}); // Direct pricing object
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Benefits:**
|
|
445
|
+
- **50% less code** for data extraction
|
|
446
|
+
- **Cleaner business logic** without response unwrapping
|
|
447
|
+
- **Same caching and validation** as standard methods
|
|
448
|
+
- **Consistent pattern** across all 13 microservices
|
|
449
|
+
|
|
450
|
+
### Advanced Discovery Filtering
|
|
451
|
+
|
|
452
|
+
Go beyond basic search with sophisticated filtering options:
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
// Precision search with advanced filtering
|
|
456
|
+
const endpoints = await api.findEndpoint('user management', {
|
|
457
|
+
// Relevance threshold (0.0 to 1.0)
|
|
458
|
+
minScore: 0.7, // Only highly relevant matches
|
|
459
|
+
|
|
460
|
+
// Limit results
|
|
461
|
+
maxResults: 5, // Top 5 matches only
|
|
462
|
+
|
|
463
|
+
// Service-specific filtering
|
|
464
|
+
service: 'joomla', // Only Joomla service endpoints
|
|
465
|
+
|
|
466
|
+
// Business domain filtering
|
|
467
|
+
domain: 'user-management', // Only user management domain
|
|
468
|
+
|
|
469
|
+
// Operation type filtering
|
|
470
|
+
readOnly: true, // Only read operations (GET requests)
|
|
471
|
+
writeOnly: false, // Exclude write operations
|
|
472
|
+
|
|
473
|
+
// Response format preferences
|
|
474
|
+
includeMetadata: true, // Include match reasoning and scores
|
|
475
|
+
sortBy: 'relevance' // Sort by relevance score (default: 'relevance' | 'alphabetical')
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Rich result information
|
|
479
|
+
endpoints.forEach(result => {
|
|
480
|
+
console.log(`🔍 ${result.endpoint.fullPath}`);
|
|
481
|
+
console.log(` Score: ${result.score} | Reason: ${result.matchReason}`);
|
|
482
|
+
console.log(` Domain: ${result.endpoint.domain} | Service: ${result.endpoint.service}`);
|
|
483
|
+
console.log(` Method: ${result.endpoint.method} | Read-only: ${result.endpoint.readOnly}`);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// Cross-service workflow discovery
|
|
487
|
+
const ecommerceFlow = await api.findEndpoint('complete customer order', {
|
|
488
|
+
domain: ['commerce', 'pricing', 'inventory'], // Multiple domains
|
|
489
|
+
includeWorkflow: true, // Include workflow relationships
|
|
490
|
+
minScore: 0.5
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// Results include cross-service relationships
|
|
494
|
+
ecommerceFlow.forEach(result => {
|
|
495
|
+
console.log(`${result.endpoint.fullPath} → Related: ${result.relatedEndpoints?.join(', ')}`);
|
|
496
|
+
});
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Debug Utilities and Troubleshooting
|
|
500
|
+
|
|
501
|
+
Built-in debugging tools help you understand what's happening under the hood:
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// Create detailed debug information for any request
|
|
505
|
+
const debugInfo = api.joomla.users.createDebugInfo(
|
|
506
|
+
{ limit: 10, offset: 0 }, // Your parameters
|
|
507
|
+
{ edgeCache: 2 } // Request config
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
console.log('🔧 Debug Information:');
|
|
511
|
+
console.log('Expected Parameters:', debugInfo.expectedParams);
|
|
512
|
+
console.log('Provided Parameters:', debugInfo.providedParams);
|
|
513
|
+
console.log('Validation Results:', debugInfo.validation);
|
|
514
|
+
console.log('Cache Strategy:', debugInfo.cacheInfo);
|
|
515
|
+
console.log('Generated URL:', debugInfo.finalURL);
|
|
516
|
+
|
|
517
|
+
// Validate parameters before making requests
|
|
518
|
+
const validation = api.pricing.validatePriceParams({
|
|
519
|
+
customerId: 12345,
|
|
520
|
+
itemId: 'INVALID', // This might fail validation
|
|
521
|
+
quantity: -5 // Negative quantity should fail
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
if (!validation.isValid) {
|
|
525
|
+
console.log('❌ Parameter Validation Failed:');
|
|
526
|
+
validation.errors.forEach(error => {
|
|
527
|
+
console.log(` Field: ${error.field} | Issue: ${error.message}`);
|
|
528
|
+
console.log(` Expected: ${error.expected} | Received: ${error.received}`);
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Request tracing for performance analysis
|
|
533
|
+
api.enableRequestTracing(true);
|
|
534
|
+
|
|
535
|
+
const users = await api.joomla.users.list({ limit: 10 });
|
|
536
|
+
|
|
537
|
+
// Get trace information
|
|
538
|
+
const trace = api.getLastRequestTrace();
|
|
539
|
+
console.log('🚀 Performance Trace:');
|
|
540
|
+
console.log(` Total Time: ${trace.totalTime}ms`);
|
|
541
|
+
console.log(` Network Time: ${trace.networkTime}ms`);
|
|
542
|
+
console.log(` Validation Time: ${trace.validationTime}ms`);
|
|
543
|
+
console.log(` Cache Status: ${trace.cacheStatus}`);
|
|
544
|
+
console.log(` Request ID: ${trace.requestId}`);
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Automatic Parameter Extraction
|
|
548
|
+
|
|
549
|
+
The system intelligently maps URL templates to method parameters:
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// URL template: /bin-transfer/{binTransferHdrUid}
|
|
553
|
+
// Automatically creates: (id) => { binTransferHdrUid: id }
|
|
554
|
+
const binTransfer = await api.nexus.binTransfers.get(12345);
|
|
555
|
+
// Internally maps: 12345 → { binTransferHdrUid: 12345 }
|
|
556
|
+
|
|
557
|
+
// URL template: /customer/{customerId}/orders/{orderId}
|
|
558
|
+
// Automatically creates: (customerId, orderId) => { customerId, orderId }
|
|
559
|
+
const order = await api.customers.customer.orders.get(123, 456);
|
|
560
|
+
// Internally maps: (123, 456) → { customerId: 123, orderId: 456 }
|
|
561
|
+
|
|
562
|
+
// Complex URL templates with multiple parameters
|
|
563
|
+
// URL template: /pricing/job/{jobPriceHdrUid}/lines/{jobPriceLineUid}
|
|
564
|
+
const jobPriceLine = await api.pricing.jobPriceLines.get(123, 456);
|
|
565
|
+
// Internally maps: (123, 456) → { jobPriceHdrUid: 123, jobPriceLineUid: 456 }
|
|
566
|
+
|
|
567
|
+
// See the mapping for any endpoint
|
|
568
|
+
const mapping = api.nexus.binTransfers.getParameterMapping();
|
|
569
|
+
console.log('URL Template:', mapping.urlTemplate);
|
|
570
|
+
console.log('Parameter Map:', mapping.parameterMap);
|
|
571
|
+
console.log('Required Params:', mapping.requiredParams);
|
|
572
|
+
console.log('Optional Params:', mapping.optionalParams);
|
|
573
|
+
|
|
574
|
+
// Validate parameter mapping before calling
|
|
575
|
+
const isValid = api.nexus.binTransfers.validateParameterMapping(12345);
|
|
576
|
+
if (!isValid.valid) {
|
|
577
|
+
console.log('Parameter mapping issues:', isValid.errors);
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Smart Request Building
|
|
582
|
+
|
|
583
|
+
Advanced request construction with intelligent defaults:
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
// Smart parameter inference
|
|
587
|
+
const smartRequest = api.commerce.cartHeaders.buildRequest({
|
|
588
|
+
userId: 123,
|
|
589
|
+
// System automatically infers common parameters:
|
|
590
|
+
// - siteId from API configuration
|
|
591
|
+
// - timestamp for cache busting
|
|
592
|
+
// - request ID for tracing
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
console.log('Generated request:', smartRequest);
|
|
596
|
+
// {
|
|
597
|
+
// userId: 123,
|
|
598
|
+
// siteId: 'your-site-id',
|
|
599
|
+
// _timestamp: 1693934400000,
|
|
600
|
+
// _requestId: 'req_abc123',
|
|
601
|
+
// _cacheKey: 'cart_headers_123_your-site-id'
|
|
602
|
+
// }
|
|
603
|
+
|
|
604
|
+
// Request templating for repeated operations
|
|
605
|
+
const userTemplate = api.joomla.users.createRequestTemplate({
|
|
606
|
+
limit: 50,
|
|
607
|
+
orderBy: 'username|ASC'
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// Reuse template with variations
|
|
611
|
+
const activeUsers = await userTemplate.execute({ q: 'active' });
|
|
612
|
+
const adminUsers = await userTemplate.execute({ groupId: 1 });
|
|
613
|
+
const recentUsers = await userTemplate.execute({
|
|
614
|
+
createdSince: '2024-01-01',
|
|
615
|
+
limit: 20 // Override template default
|
|
616
|
+
});
|
|
617
|
+
```
|
|
618
|
+
|
|
407
619
|
## Platform Capabilities Matrix
|
|
408
620
|
|
|
409
621
|
| Capability | Impact | Technical Implementation |
|
|
@@ -2280,269 +2492,944 @@ managedAPI.updateConfig({
|
|
|
2280
2492
|
|
|
2281
2493
|
## Performance Optimization
|
|
2282
2494
|
|
|
2283
|
-
|
|
2495
|
+
> 📋 **[Complete Performance Guide](./PERFORMANCE.md)** - Comprehensive strategies for edge caching, batch operations, pagination optimization, and performance monitoring.
|
|
2496
|
+
|
|
2497
|
+
**Quick Performance Tips:**
|
|
2284
2498
|
|
|
2285
2499
|
```typescript
|
|
2286
|
-
//
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
// Static reference data - cache for 8 hours
|
|
2291
|
-
reference: 8,
|
|
2292
|
-
categories: 8,
|
|
2293
|
-
tags: 6,
|
|
2294
|
-
distributors: 8,
|
|
2295
|
-
|
|
2296
|
-
// Moderate volatility - cache for 2-4 hours
|
|
2297
|
-
users: context?.isLargeOrg ? 4 : 2,
|
|
2298
|
-
products: 6,
|
|
2299
|
-
warehouses: 4,
|
|
2300
|
-
recommendations: 4,
|
|
2301
|
-
|
|
2302
|
-
// High volatility - cache for 1-2 hours
|
|
2303
|
-
pricing: context?.isStandardItem ? 3 : 1,
|
|
2304
|
-
inventory: context?.isHighVolume ? 1 : 2,
|
|
2305
|
-
cart: 1,
|
|
2306
|
-
|
|
2307
|
-
// Real-time data - no caching
|
|
2308
|
-
authentication: undefined,
|
|
2309
|
-
validation: undefined,
|
|
2310
|
-
checkout: undefined,
|
|
2311
|
-
};
|
|
2312
|
-
|
|
2313
|
-
return strategies[dataType as keyof typeof strategies];
|
|
2314
|
-
}
|
|
2315
|
-
}
|
|
2500
|
+
// ⚡ Edge caching with Cloudflare CDN
|
|
2501
|
+
const categories = await api.items.categories.list({ edgeCache: 8 }); // Static data: 8 hours
|
|
2502
|
+
const pricing = await api.pricing.getPrice({ customerId: 123, edgeCache: 3 }); // Dynamic: 3 hours
|
|
2503
|
+
const inventory = await api.vmi.inventory.checkAvailability(123, { edgeCache: 1 }); // Volatile: 1 hour
|
|
2316
2504
|
|
|
2317
|
-
//
|
|
2318
|
-
const
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
// Batch related requests for better performance
|
|
2331
|
-
async function loadDashboardData() {
|
|
2332
|
-
// Execute all requests in parallel
|
|
2333
|
-
const [users, products, inventory, pricing] = await Promise.allSettled([
|
|
2334
|
-
api.joomla.users.list({ limit: 10, edgeCache: 2 }),
|
|
2335
|
-
api.opensearch.itemSearch.search({
|
|
2336
|
-
q: 'featured',
|
|
2337
|
-
searchType: 'query',
|
|
2338
|
-
size: 5,
|
|
2339
|
-
edgeCache: 4
|
|
2340
|
-
}),
|
|
2341
|
-
api.vmi.warehouses.list({
|
|
2342
|
-
customerId: 12345,
|
|
2343
|
-
limit: 5,
|
|
2344
|
-
edgeCache: 4
|
|
2345
|
-
}),
|
|
2346
|
-
api.pricing.jobPriceHeaders.list({
|
|
2347
|
-
limit: 5,
|
|
2348
|
-
edgeCache: 6
|
|
2349
|
-
})
|
|
2350
|
-
]);
|
|
2351
|
-
|
|
2352
|
-
// Handle results
|
|
2353
|
-
return {
|
|
2354
|
-
users: users.status === 'fulfilled' ? users.value : null,
|
|
2355
|
-
products: products.status === 'fulfilled' ? products.value : null,
|
|
2356
|
-
inventory: inventory.status === 'fulfilled' ? inventory.value : null,
|
|
2357
|
-
pricing: pricing.status === 'fulfilled' ? pricing.value : null,
|
|
2358
|
-
};
|
|
2505
|
+
// 🚀 Batch operations for multiple requests
|
|
2506
|
+
const [users, groups, content] = await Promise.allSettled([
|
|
2507
|
+
api.joomla.users.list({ limit: 50, edgeCache: 2 }),
|
|
2508
|
+
api.joomla.userGroups.list({ edgeCache: 8 }),
|
|
2509
|
+
api.joomla.content.list({ categoryIdList: '1,2,3', edgeCache: 6 })
|
|
2510
|
+
]);
|
|
2511
|
+
|
|
2512
|
+
// 📄 Smart pagination with prefetching
|
|
2513
|
+
class SmartPagination {
|
|
2514
|
+
async loadPage(fetcher, page, pageSize = 50) {
|
|
2515
|
+
// Automatic prefetching and caching
|
|
2516
|
+
return this.loadWithPrefetch(fetcher, page, pageSize);
|
|
2517
|
+
}
|
|
2359
2518
|
}
|
|
2360
2519
|
```
|
|
2361
2520
|
|
|
2362
|
-
|
|
2521
|
+
**Key Performance Features:**
|
|
2522
|
+
- **Edge Cache Decision Tree** - Choose optimal cache duration by data volatility
|
|
2523
|
+
- **Service-Specific Batching** - Parallel operations tailored to each microservice
|
|
2524
|
+
- **Advanced Pagination** - Prefetching, infinite scroll, and streaming patterns
|
|
2525
|
+
- **Performance Analytics** - Monitor cache hit rates, response times, and optimization opportunities
|
|
2526
|
+
|
|
2527
|
+
For complete implementation details, see [Performance Optimization Guide](./PERFORMANCE.md).
|
|
2528
|
+
|
|
2529
|
+
## Enterprise Integration Patterns
|
|
2530
|
+
|
|
2531
|
+
**Scale your enterprise applications** with proven patterns for multi-tenant architectures, cross-site authentication, and production deployment.
|
|
2532
|
+
|
|
2533
|
+
### Cross-Site Authentication Architecture
|
|
2534
|
+
|
|
2535
|
+
> 📋 **[Complete Authentication Guide](./AUTHENTICATION.md)** - Detailed implementation patterns, security considerations, and troubleshooting.
|
|
2536
|
+
|
|
2537
|
+
**Enterprise Authentication Flow:**
|
|
2363
2538
|
|
|
2364
2539
|
```typescript
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2540
|
+
/**
|
|
2541
|
+
* 🏢 ENTERPRISE MULTI-TENANT AUTHENTICATION
|
|
2542
|
+
*
|
|
2543
|
+
* Supports authenticating users across multiple tenant sites
|
|
2544
|
+
* from a centralized application or admin dashboard.
|
|
2545
|
+
*/
|
|
2546
|
+
|
|
2547
|
+
import { createCrossSiteAuthenticator, authenticateUserForSite } from '@simpleapps-com/augur-api';
|
|
2548
|
+
|
|
2549
|
+
// 🎯 Option 1: Single Authentication (Recommended for most cases)
|
|
2550
|
+
const authResult = await authenticateUserForSite({
|
|
2551
|
+
targetSiteId: 'tenant_site_alpha',
|
|
2552
|
+
username: 'user@tenant.com',
|
|
2553
|
+
password: 'user_password',
|
|
2554
|
+
augurInfoToken: process.env.AUGUR_ADMIN_TOKEN! // Admin token with augur_info privileges
|
|
2555
|
+
});
|
|
2556
|
+
|
|
2557
|
+
if (authResult.success) {
|
|
2558
|
+
// Ready-to-use API client for the tenant
|
|
2559
|
+
const tenantAPI = authResult.targetSiteAPI!;
|
|
2560
|
+
const userData = await tenantAPI.joomla.users.get(authResult.userId!);
|
|
2561
|
+
console.log(`✅ Authenticated: ${authResult.username} on ${targetSiteId}`);
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// 🎯 Option 2: Reusable Authenticator (For multiple sites)
|
|
2565
|
+
const crossSiteAuth = createCrossSiteAuthenticator(process.env.AUGUR_ADMIN_TOKEN!);
|
|
2566
|
+
|
|
2567
|
+
const tenantResults = await Promise.allSettled([
|
|
2568
|
+
crossSiteAuth('tenant_alpha', 'user@alpha.com', 'pass1'),
|
|
2569
|
+
crossSiteAuth('tenant_beta', 'user@beta.com', 'pass2'),
|
|
2570
|
+
crossSiteAuth('tenant_gamma', 'user@gamma.com', 'pass3')
|
|
2571
|
+
]);
|
|
2572
|
+
|
|
2573
|
+
// Process results for each tenant
|
|
2574
|
+
tenantResults.forEach((result, index) => {
|
|
2575
|
+
const tenantId = ['tenant_alpha', 'tenant_beta', 'tenant_gamma'][index];
|
|
2576
|
+
if (result.status === 'fulfilled' && result.value.success) {
|
|
2577
|
+
console.log(`✅ ${tenantId}: User authenticated`);
|
|
2379
2578
|
}
|
|
2380
2579
|
});
|
|
2381
2580
|
```
|
|
2382
2581
|
|
|
2383
|
-
|
|
2582
|
+
**Authentication Flow Diagram:**
|
|
2583
|
+
```
|
|
2584
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
2585
|
+
│ Your App │───▶│ augur_info │───▶│ Target Site │───▶│ User Token │
|
|
2586
|
+
│ │ │ (Admin) │ │ (Tenant) │ │ (Scoped) │
|
|
2587
|
+
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
|
|
2588
|
+
│ │ │ │
|
|
2589
|
+
Admin JWT Verify User Check Password Return Scoped
|
|
2590
|
+
Token Credentials Against Site JWT for Site
|
|
2591
|
+
```
|
|
2592
|
+
|
|
2593
|
+
### Multi-Tenant Setup Patterns
|
|
2594
|
+
|
|
2595
|
+
**Configure your application** for enterprise multi-tenancy with these proven patterns:
|
|
2384
2596
|
|
|
2385
2597
|
```typescript
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2598
|
+
/**
|
|
2599
|
+
* 🏗️ MULTI-TENANT ARCHITECTURE PATTERNS
|
|
2600
|
+
*
|
|
2601
|
+
* Choose the pattern that best fits your enterprise requirements:
|
|
2602
|
+
* - Shared Infrastructure, Isolated Data
|
|
2603
|
+
* - Complete Tenant Isolation
|
|
2604
|
+
* - Hybrid Multi-Tenant Model
|
|
2605
|
+
*/
|
|
2390
2606
|
|
|
2391
|
-
|
|
2392
|
-
|
|
2607
|
+
// 🏢 Pattern 1: Tenant Context Manager (Recommended)
|
|
2608
|
+
class TenantContextManager {
|
|
2609
|
+
private tenantAPIs = new Map<string, AugurAPI>();
|
|
2610
|
+
private currentTenant: string | null = null;
|
|
2611
|
+
|
|
2612
|
+
async initializeTenant(
|
|
2613
|
+
tenantId: string,
|
|
2614
|
+
userCredentials: { username: string; password: string },
|
|
2615
|
+
adminToken: string
|
|
2616
|
+
): Promise<boolean> {
|
|
2617
|
+
try {
|
|
2618
|
+
const authResult = await authenticateUserForSite({
|
|
2619
|
+
targetSiteId: tenantId,
|
|
2620
|
+
username: userCredentials.username,
|
|
2621
|
+
password: userCredentials.password,
|
|
2622
|
+
augurInfoToken: adminToken
|
|
2623
|
+
});
|
|
2624
|
+
|
|
2625
|
+
if (authResult.success) {
|
|
2626
|
+
// Store authenticated API client for tenant
|
|
2627
|
+
this.tenantAPIs.set(tenantId, authResult.targetSiteAPI!);
|
|
2628
|
+
this.currentTenant = tenantId;
|
|
2629
|
+
|
|
2630
|
+
console.log(`✅ Tenant ${tenantId} initialized successfully`);
|
|
2631
|
+
return true;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
console.error(`❌ Failed to initialize tenant ${tenantId}: ${authResult.error}`);
|
|
2635
|
+
return false;
|
|
2636
|
+
} catch (error) {
|
|
2637
|
+
console.error(`❌ Tenant initialization error:`, error);
|
|
2638
|
+
return false;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// Switch between tenants seamlessly
|
|
2643
|
+
switchTenant(tenantId: string): AugurAPI | null {
|
|
2644
|
+
if (this.tenantAPIs.has(tenantId)) {
|
|
2645
|
+
this.currentTenant = tenantId;
|
|
2646
|
+
return this.tenantAPIs.get(tenantId)!;
|
|
2647
|
+
}
|
|
2393
2648
|
|
|
2394
|
-
|
|
2395
|
-
|
|
2649
|
+
console.warn(`⚠️ Tenant ${tenantId} not initialized`);
|
|
2650
|
+
return null;
|
|
2396
2651
|
}
|
|
2397
2652
|
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
): Promise<T> {
|
|
2403
|
-
const cached = this.cache.get(key);
|
|
2404
|
-
const now = Date.now();
|
|
2653
|
+
// Get current tenant's API client
|
|
2654
|
+
getCurrentAPI(): AugurAPI | null {
|
|
2655
|
+
return this.currentTenant ? this.tenantAPIs.get(this.currentTenant) || null : null;
|
|
2656
|
+
}
|
|
2405
2657
|
|
|
2406
|
-
|
|
2407
|
-
|
|
2658
|
+
// Bulk operations across all tenants
|
|
2659
|
+
async executeAcrossAllTenants<T>(
|
|
2660
|
+
operation: (api: AugurAPI, tenantId: string) => Promise<T>
|
|
2661
|
+
): Promise<Array<{ tenantId: string; result: T; success: boolean; error?: string }>> {
|
|
2662
|
+
const results: Array<{ tenantId: string; result: T; success: boolean; error?: string }> = [];
|
|
2663
|
+
|
|
2664
|
+
for (const [tenantId, api] of this.tenantAPIs.entries()) {
|
|
2665
|
+
try {
|
|
2666
|
+
const result = await operation(api, tenantId);
|
|
2667
|
+
results.push({ tenantId, result, success: true });
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
results.push({
|
|
2670
|
+
tenantId,
|
|
2671
|
+
result: null as any,
|
|
2672
|
+
success: false,
|
|
2673
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2408
2676
|
}
|
|
2677
|
+
|
|
2678
|
+
return results;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2409
2681
|
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2682
|
+
// 🏢 Pattern 2: Tenant Factory (For dynamic tenant creation)
|
|
2683
|
+
class TenantAPIFactory {
|
|
2684
|
+
constructor(private adminToken: string) {}
|
|
2685
|
+
|
|
2686
|
+
async createTenantAPI(
|
|
2687
|
+
tenantId: string,
|
|
2688
|
+
userCredentials: { username: string; password: string }
|
|
2689
|
+
): Promise<AugurAPI | null> {
|
|
2690
|
+
const authResult = await authenticateUserForSite({
|
|
2691
|
+
targetSiteId: tenantId,
|
|
2692
|
+
username: userCredentials.username,
|
|
2693
|
+
password: userCredentials.password,
|
|
2694
|
+
augurInfoToken: this.adminToken
|
|
2415
2695
|
});
|
|
2416
2696
|
|
|
2417
|
-
return
|
|
2697
|
+
return authResult.success ? authResult.targetSiteAPI! : null;
|
|
2418
2698
|
}
|
|
2419
2699
|
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2700
|
+
// Create multiple tenant APIs in parallel
|
|
2701
|
+
async createMultipleTenantAPIs(
|
|
2702
|
+
tenantConfigs: Array<{
|
|
2703
|
+
tenantId: string;
|
|
2704
|
+
credentials: { username: string; password: string };
|
|
2705
|
+
}>
|
|
2706
|
+
): Promise<Map<string, AugurAPI>> {
|
|
2707
|
+
const results = await Promise.allSettled(
|
|
2708
|
+
tenantConfigs.map(async ({ tenantId, credentials }) => ({
|
|
2709
|
+
tenantId,
|
|
2710
|
+
api: await this.createTenantAPI(tenantId, credentials)
|
|
2711
|
+
}))
|
|
2712
|
+
);
|
|
2713
|
+
|
|
2714
|
+
const tenantAPIs = new Map<string, AugurAPI>();
|
|
2715
|
+
|
|
2716
|
+
results.forEach((result, index) => {
|
|
2717
|
+
if (result.status === 'fulfilled' && result.value.api) {
|
|
2718
|
+
tenantAPIs.set(result.value.tenantId, result.value.api);
|
|
2719
|
+
} else {
|
|
2720
|
+
const tenantId = tenantConfigs[index].tenantId;
|
|
2721
|
+
console.error(`❌ Failed to create API for tenant: ${tenantId}`);
|
|
2425
2722
|
}
|
|
2426
|
-
}
|
|
2723
|
+
});
|
|
2724
|
+
|
|
2725
|
+
return tenantAPIs;
|
|
2427
2726
|
}
|
|
2727
|
+
}
|
|
2428
2728
|
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2729
|
+
// 🏢 Pattern 3: Multi-Tenant Dashboard Implementation
|
|
2730
|
+
class MultiTenantDashboard {
|
|
2731
|
+
private tenantManager = new TenantContextManager();
|
|
2732
|
+
private tenantFactory: TenantAPIFactory;
|
|
2433
2733
|
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2734
|
+
constructor(adminToken: string) {
|
|
2735
|
+
this.tenantFactory = new TenantAPIFactory(adminToken);
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
async initializeDashboard(
|
|
2739
|
+
userCredentials: { username: string; password: string },
|
|
2740
|
+
tenantIds: string[]
|
|
2741
|
+
): Promise<void> {
|
|
2742
|
+
console.log(`🚀 Initializing dashboard for ${tenantIds.length} tenants...`);
|
|
2743
|
+
|
|
2744
|
+
// Initialize all tenants in parallel
|
|
2745
|
+
const initResults = await Promise.allSettled(
|
|
2746
|
+
tenantIds.map(tenantId =>
|
|
2747
|
+
this.tenantManager.initializeTenant(
|
|
2748
|
+
tenantId,
|
|
2749
|
+
userCredentials,
|
|
2750
|
+
process.env.AUGUR_ADMIN_TOKEN!
|
|
2751
|
+
)
|
|
2752
|
+
)
|
|
2438
2753
|
);
|
|
2754
|
+
|
|
2755
|
+
const successCount = initResults.filter(r =>
|
|
2756
|
+
r.status === 'fulfilled' && r.value === true
|
|
2757
|
+
).length;
|
|
2758
|
+
|
|
2759
|
+
console.log(`✅ Dashboard initialized: ${successCount}/${tenantIds.length} tenants ready`);
|
|
2439
2760
|
}
|
|
2440
2761
|
|
|
2441
|
-
|
|
2442
|
-
|
|
2762
|
+
// Get aggregated data across all tenants
|
|
2763
|
+
async getAggregatedUserData(): Promise<Record<string, any[]>> {
|
|
2764
|
+
const results = await this.tenantManager.executeAcrossAllTenants(
|
|
2765
|
+
async (api, tenantId) => {
|
|
2766
|
+
const users = await api.joomla.users.listData({ limit: 100 });
|
|
2767
|
+
return { tenantId, userCount: users.length, users };
|
|
2768
|
+
}
|
|
2769
|
+
);
|
|
2770
|
+
|
|
2771
|
+
const aggregatedData: Record<string, any[]> = {};
|
|
2772
|
+
results.forEach(({ tenantId, result, success }) => {
|
|
2773
|
+
if (success) {
|
|
2774
|
+
aggregatedData[tenantId] = result.users;
|
|
2775
|
+
}
|
|
2776
|
+
});
|
|
2777
|
+
|
|
2778
|
+
return aggregatedData;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
// Bulk operations example
|
|
2782
|
+
async bulkUpdateAcrossTenants(updates: Record<string, any>): Promise<void> {
|
|
2783
|
+
const results = await this.tenantManager.executeAcrossAllTenants(
|
|
2784
|
+
async (api, tenantId) => {
|
|
2785
|
+
// Example: Update site settings across all tenants
|
|
2786
|
+
return await api.agrsite.settings.update(updates);
|
|
2787
|
+
}
|
|
2788
|
+
);
|
|
2789
|
+
|
|
2790
|
+
const successCount = results.filter(r => r.success).length;
|
|
2791
|
+
console.log(`✅ Bulk update completed: ${successCount}/${results.length} tenants updated`);
|
|
2443
2792
|
}
|
|
2444
2793
|
}
|
|
2794
|
+
|
|
2795
|
+
// 🎮 USAGE EXAMPLES
|
|
2796
|
+
|
|
2797
|
+
// Initialize multi-tenant dashboard
|
|
2798
|
+
const dashboard = new MultiTenantDashboard(process.env.AUGUR_ADMIN_TOKEN!);
|
|
2799
|
+
|
|
2800
|
+
await dashboard.initializeDashboard(
|
|
2801
|
+
{ username: 'admin@company.com', password: 'admin_password' },
|
|
2802
|
+
['tenant_alpha', 'tenant_beta', 'tenant_gamma']
|
|
2803
|
+
);
|
|
2804
|
+
|
|
2805
|
+
// Get aggregated data from all tenants
|
|
2806
|
+
const userData = await dashboard.getAggregatedUserData();
|
|
2807
|
+
console.log('User data across all tenants:', userData);
|
|
2808
|
+
|
|
2809
|
+
// Execute bulk operations
|
|
2810
|
+
await dashboard.bulkUpdateAcrossTenants({
|
|
2811
|
+
maintenanceMode: false,
|
|
2812
|
+
themeColor: '#2563eb'
|
|
2813
|
+
});
|
|
2445
2814
|
```
|
|
2446
2815
|
|
|
2447
|
-
###
|
|
2816
|
+
### Security Configuration Best Practices
|
|
2817
|
+
|
|
2818
|
+
**Secure your enterprise deployment** with comprehensive security measures:
|
|
2448
2819
|
|
|
2449
2820
|
```typescript
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2821
|
+
/**
|
|
2822
|
+
* 🔒 ENTERPRISE SECURITY CONFIGURATION
|
|
2823
|
+
*
|
|
2824
|
+
* Implement defense-in-depth security practices for production
|
|
2825
|
+
* environments with comprehensive monitoring and access control.
|
|
2826
|
+
*/
|
|
2827
|
+
|
|
2828
|
+
// 🛡️ Secure Configuration Manager
|
|
2829
|
+
class SecureConfigManager {
|
|
2830
|
+
private static readonly SECURITY_CONFIG = {
|
|
2831
|
+
// Token rotation intervals (in milliseconds)
|
|
2832
|
+
TOKEN_ROTATION_INTERVAL: 4 * 60 * 60 * 1000, // 4 hours
|
|
2833
|
+
ADMIN_TOKEN_ROTATION_INTERVAL: 2 * 60 * 60 * 1000, // 2 hours
|
|
2834
|
+
|
|
2835
|
+
// Rate limiting
|
|
2836
|
+
MAX_REQUESTS_PER_MINUTE: 100,
|
|
2837
|
+
MAX_CROSS_SITE_AUTH_PER_HOUR: 50,
|
|
2838
|
+
|
|
2839
|
+
// Session management
|
|
2840
|
+
SESSION_TIMEOUT: 30 * 60 * 1000, // 30 minutes
|
|
2841
|
+
MAX_CONCURRENT_SESSIONS: 5,
|
|
2842
|
+
|
|
2843
|
+
// Network security
|
|
2844
|
+
ALLOWED_ORIGINS: process.env.ALLOWED_ORIGINS?.split(',') || [],
|
|
2845
|
+
REQUIRE_HTTPS: process.env.NODE_ENV === 'production',
|
|
2458
2846
|
};
|
|
2459
2847
|
|
|
2460
|
-
|
|
2848
|
+
// 🔑 Secure token management
|
|
2849
|
+
static createSecureAPIClient(config: {
|
|
2850
|
+
siteId: string;
|
|
2851
|
+
tokenProvider: () => Promise<string>;
|
|
2852
|
+
onTokenRefresh?: (newToken: string) => void;
|
|
2853
|
+
securityLogger?: (event: string, details: any) => void;
|
|
2854
|
+
}): AugurAPI {
|
|
2855
|
+
|
|
2856
|
+
let currentToken: string | null = null;
|
|
2857
|
+
let lastTokenRefresh = 0;
|
|
2858
|
+
|
|
2461
2859
|
return new AugurAPI({
|
|
2462
|
-
|
|
2860
|
+
siteId: config.siteId,
|
|
2463
2861
|
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
this.metrics.requests++;
|
|
2468
|
-
return config;
|
|
2862
|
+
// Dynamic token provider with automatic rotation
|
|
2863
|
+
get bearerToken() {
|
|
2864
|
+
return currentToken;
|
|
2469
2865
|
},
|
|
2470
|
-
|
|
2471
|
-
onResponse: (response) => {
|
|
2472
|
-
// Calculate request duration
|
|
2473
|
-
const startTime = (response.config as any)?.startTime;
|
|
2474
|
-
if (startTime) {
|
|
2475
|
-
const duration = Date.now() - startTime;
|
|
2476
|
-
this.metrics.totalTime += duration;
|
|
2477
|
-
}
|
|
2478
2866
|
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2867
|
+
// Request interceptor for security
|
|
2868
|
+
onRequest: async (requestConfig) => {
|
|
2869
|
+
const now = Date.now();
|
|
2870
|
+
|
|
2871
|
+
// Auto-refresh token if needed
|
|
2872
|
+
if (!currentToken || (now - lastTokenRefresh) > SecureConfigManager.SECURITY_CONFIG.TOKEN_ROTATION_INTERVAL) {
|
|
2873
|
+
try {
|
|
2874
|
+
currentToken = await config.tokenProvider();
|
|
2875
|
+
lastTokenRefresh = now;
|
|
2876
|
+
config.onTokenRefresh?.(currentToken);
|
|
2877
|
+
|
|
2878
|
+
config.securityLogger?.('token_refreshed', {
|
|
2879
|
+
siteId: config.siteId,
|
|
2880
|
+
timestamp: new Date().toISOString()
|
|
2881
|
+
});
|
|
2882
|
+
} catch (error) {
|
|
2883
|
+
config.securityLogger?.('token_refresh_failed', {
|
|
2884
|
+
siteId: config.siteId,
|
|
2885
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
2886
|
+
});
|
|
2887
|
+
throw new Error('Token refresh failed');
|
|
2888
|
+
}
|
|
2485
2889
|
}
|
|
2486
2890
|
|
|
2891
|
+
// Add security headers
|
|
2892
|
+
requestConfig.headers = {
|
|
2893
|
+
...requestConfig.headers,
|
|
2894
|
+
'X-Request-ID': crypto.randomUUID(),
|
|
2895
|
+
'X-Timestamp': new Date().toISOString(),
|
|
2896
|
+
'User-Agent': `AugurAPI-Client/1.0 (Enterprise; ${process.env.NODE_ENV || 'development'})`
|
|
2897
|
+
};
|
|
2898
|
+
|
|
2899
|
+
// Log security events
|
|
2900
|
+
config.securityLogger?.('api_request', {
|
|
2901
|
+
siteId: config.siteId,
|
|
2902
|
+
url: requestConfig.url,
|
|
2903
|
+
method: requestConfig.method,
|
|
2904
|
+
requestId: requestConfig.headers['X-Request-ID']
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
return requestConfig;
|
|
2908
|
+
},
|
|
2909
|
+
|
|
2910
|
+
// Response interceptor for security monitoring
|
|
2911
|
+
onResponse: (response) => {
|
|
2912
|
+
config.securityLogger?.('api_response', {
|
|
2913
|
+
siteId: config.siteId,
|
|
2914
|
+
status: response.status,
|
|
2915
|
+
requestId: response.config.headers?.['X-Request-ID'],
|
|
2916
|
+
responseTime: Date.now() - (response.config as any)?.startTime
|
|
2917
|
+
});
|
|
2918
|
+
|
|
2487
2919
|
return response;
|
|
2488
2920
|
},
|
|
2489
|
-
|
|
2921
|
+
|
|
2922
|
+
// Error interceptor for security logging
|
|
2490
2923
|
onError: (error) => {
|
|
2491
|
-
|
|
2492
|
-
|
|
2924
|
+
config.securityLogger?.('api_error', {
|
|
2925
|
+
siteId: config.siteId,
|
|
2926
|
+
error: error.message,
|
|
2927
|
+
status: error.response?.status,
|
|
2928
|
+
requestId: error.config?.headers?.['X-Request-ID']
|
|
2929
|
+
});
|
|
2930
|
+
|
|
2931
|
+
// Don't expose sensitive errors in production
|
|
2932
|
+
if (process.env.NODE_ENV === 'production') {
|
|
2933
|
+
throw new Error('API request failed');
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2493
2936
|
throw error;
|
|
2494
2937
|
}
|
|
2495
2938
|
});
|
|
2496
2939
|
}
|
|
2497
2940
|
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
:
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2941
|
+
// 🔐 Cross-site authentication with security controls
|
|
2942
|
+
static createSecureCrossSiteAuthenticator(config: {
|
|
2943
|
+
adminTokenProvider: () => Promise<string>;
|
|
2944
|
+
auditLogger: (event: string, details: any) => void;
|
|
2945
|
+
rateLimiter?: (key: string) => Promise<boolean>;
|
|
2946
|
+
}) {
|
|
2947
|
+
return async (
|
|
2948
|
+
targetSiteId: string,
|
|
2949
|
+
username: string,
|
|
2950
|
+
password: string
|
|
2951
|
+
): Promise<CrossSiteAuthResult> => {
|
|
2952
|
+
const startTime = Date.now();
|
|
2953
|
+
const requestId = crypto.randomUUID();
|
|
2510
2954
|
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2955
|
+
try {
|
|
2956
|
+
// Rate limiting check
|
|
2957
|
+
if (config.rateLimiter) {
|
|
2958
|
+
const rateLimitKey = `cross_site_auth:${username}:${targetSiteId}`;
|
|
2959
|
+
const allowed = await config.rateLimiter(rateLimitKey);
|
|
2960
|
+
if (!allowed) {
|
|
2961
|
+
config.auditLogger('cross_site_auth_rate_limited', {
|
|
2962
|
+
username,
|
|
2963
|
+
targetSiteId,
|
|
2964
|
+
requestId,
|
|
2965
|
+
timestamp: new Date().toISOString()
|
|
2966
|
+
});
|
|
2967
|
+
return { success: false, error: 'Rate limit exceeded' };
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
// Get fresh admin token
|
|
2972
|
+
const adminToken = await config.adminTokenProvider();
|
|
2973
|
+
|
|
2974
|
+
// Perform authentication with audit logging
|
|
2975
|
+
config.auditLogger('cross_site_auth_attempt', {
|
|
2976
|
+
username,
|
|
2977
|
+
targetSiteId,
|
|
2978
|
+
requestId,
|
|
2979
|
+
timestamp: new Date().toISOString()
|
|
2980
|
+
});
|
|
2981
|
+
|
|
2982
|
+
const result = await authenticateUserForSite({
|
|
2983
|
+
targetSiteId,
|
|
2984
|
+
username,
|
|
2985
|
+
password,
|
|
2986
|
+
augurInfoToken: adminToken
|
|
2987
|
+
});
|
|
2988
|
+
|
|
2989
|
+
// Log result
|
|
2990
|
+
config.auditLogger('cross_site_auth_result', {
|
|
2991
|
+
username,
|
|
2992
|
+
targetSiteId,
|
|
2993
|
+
requestId,
|
|
2994
|
+
success: result.success,
|
|
2995
|
+
error: result.error,
|
|
2996
|
+
duration: Date.now() - startTime,
|
|
2997
|
+
timestamp: new Date().toISOString()
|
|
2998
|
+
});
|
|
2999
|
+
|
|
3000
|
+
return result;
|
|
3001
|
+
|
|
3002
|
+
} catch (error) {
|
|
3003
|
+
config.auditLogger('cross_site_auth_error', {
|
|
3004
|
+
username,
|
|
3005
|
+
targetSiteId,
|
|
3006
|
+
requestId,
|
|
3007
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3008
|
+
duration: Date.now() - startTime,
|
|
3009
|
+
timestamp: new Date().toISOString()
|
|
3010
|
+
});
|
|
3011
|
+
|
|
3012
|
+
return {
|
|
3013
|
+
success: false,
|
|
3014
|
+
error: 'Authentication service unavailable'
|
|
3015
|
+
};
|
|
3016
|
+
}
|
|
2519
3017
|
};
|
|
2520
3018
|
}
|
|
3019
|
+
}
|
|
2521
3020
|
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
3021
|
+
// 📊 Security monitoring and alerting
|
|
3022
|
+
class SecurityMonitor {
|
|
3023
|
+
private suspiciousActivity = new Map<string, number>();
|
|
3024
|
+
private readonly MAX_FAILED_ATTEMPTS = 5;
|
|
3025
|
+
private readonly MONITORING_WINDOW = 60 * 60 * 1000; // 1 hour
|
|
3026
|
+
|
|
3027
|
+
logSecurityEvent(event: string, details: any): void {
|
|
3028
|
+
const timestamp = new Date().toISOString();
|
|
3029
|
+
const logEntry = {
|
|
3030
|
+
timestamp,
|
|
3031
|
+
event,
|
|
3032
|
+
...details,
|
|
3033
|
+
severity: this.getEventSeverity(event)
|
|
2529
3034
|
};
|
|
3035
|
+
|
|
3036
|
+
// Log to security system (console for example)
|
|
3037
|
+
console.log('🔒 SECURITY EVENT:', JSON.stringify(logEntry));
|
|
3038
|
+
|
|
3039
|
+
// Check for suspicious patterns
|
|
3040
|
+
this.detectSuspiciousActivity(event, details);
|
|
3041
|
+
|
|
3042
|
+
// Alert on high-severity events
|
|
3043
|
+
if (logEntry.severity === 'HIGH') {
|
|
3044
|
+
this.triggerSecurityAlert(logEntry);
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
private getEventSeverity(event: string): 'LOW' | 'MEDIUM' | 'HIGH' {
|
|
3049
|
+
const highSeverityEvents = [
|
|
3050
|
+
'cross_site_auth_rate_limited',
|
|
3051
|
+
'token_refresh_failed',
|
|
3052
|
+
'multiple_failed_attempts'
|
|
3053
|
+
];
|
|
3054
|
+
|
|
3055
|
+
const mediumSeverityEvents = [
|
|
3056
|
+
'cross_site_auth_result',
|
|
3057
|
+
'api_error'
|
|
3058
|
+
];
|
|
3059
|
+
|
|
3060
|
+
if (highSeverityEvents.includes(event)) return 'HIGH';
|
|
3061
|
+
if (mediumSeverityEvents.includes(event)) return 'MEDIUM';
|
|
3062
|
+
return 'LOW';
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
private detectSuspiciousActivity(event: string, details: any): void {
|
|
3066
|
+
if (event === 'cross_site_auth_result' && !details.success) {
|
|
3067
|
+
const key = `failed_auth:${details.username}:${details.targetSiteId}`;
|
|
3068
|
+
const count = (this.suspiciousActivity.get(key) || 0) + 1;
|
|
3069
|
+
this.suspiciousActivity.set(key, count);
|
|
3070
|
+
|
|
3071
|
+
if (count >= this.MAX_FAILED_ATTEMPTS) {
|
|
3072
|
+
this.logSecurityEvent('multiple_failed_attempts', {
|
|
3073
|
+
username: details.username,
|
|
3074
|
+
targetSiteId: details.targetSiteId,
|
|
3075
|
+
attemptCount: count,
|
|
3076
|
+
windowStart: new Date(Date.now() - this.MONITORING_WINDOW).toISOString()
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
private triggerSecurityAlert(logEntry: any): void {
|
|
3083
|
+
// Implement your alerting system here
|
|
3084
|
+
console.warn('🚨 SECURITY ALERT:', logEntry);
|
|
3085
|
+
|
|
3086
|
+
// Example integrations:
|
|
3087
|
+
// - Send to SIEM system
|
|
3088
|
+
// - Slack/Teams notification
|
|
3089
|
+
// - Email security team
|
|
3090
|
+
// - Trigger automated response
|
|
2530
3091
|
}
|
|
2531
3092
|
}
|
|
2532
3093
|
|
|
2533
|
-
//
|
|
2534
|
-
const
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
3094
|
+
// 🎮 USAGE EXAMPLE
|
|
3095
|
+
const securityMonitor = new SecurityMonitor();
|
|
3096
|
+
|
|
3097
|
+
// Create secure API client with monitoring
|
|
3098
|
+
const secureAPI = SecureConfigManager.createSecureAPIClient({
|
|
3099
|
+
siteId: 'production-site',
|
|
3100
|
+
tokenProvider: async () => {
|
|
3101
|
+
// Your secure token provider implementation
|
|
3102
|
+
return await getTokenFromSecureVault();
|
|
3103
|
+
},
|
|
3104
|
+
onTokenRefresh: (newToken) => {
|
|
3105
|
+
// Store token securely
|
|
3106
|
+
storeTokenInSecureVault(newToken);
|
|
3107
|
+
},
|
|
3108
|
+
securityLogger: (event, details) => {
|
|
3109
|
+
securityMonitor.logSecurityEvent(event, details);
|
|
3110
|
+
}
|
|
2538
3111
|
});
|
|
2539
3112
|
|
|
2540
|
-
//
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
3113
|
+
// Create secure cross-site authenticator
|
|
3114
|
+
const secureCrossSiteAuth = SecureConfigManager.createSecureCrossSiteAuthenticator({
|
|
3115
|
+
adminTokenProvider: async () => await getAdminTokenFromVault(),
|
|
3116
|
+
auditLogger: (event, details) => securityMonitor.logSecurityEvent(event, details),
|
|
3117
|
+
rateLimiter: async (key) => {
|
|
3118
|
+
// Implement rate limiting logic
|
|
3119
|
+
return await checkRateLimit(key);
|
|
3120
|
+
}
|
|
3121
|
+
});
|
|
2544
3122
|
```
|
|
2545
3123
|
|
|
3124
|
+
### Production Deployment Guidelines
|
|
3125
|
+
|
|
3126
|
+
**Deploy with confidence** using enterprise-grade deployment practices:
|
|
3127
|
+
|
|
3128
|
+
```typescript
|
|
3129
|
+
/**
|
|
3130
|
+
* 🚀 PRODUCTION DEPLOYMENT CHECKLIST
|
|
3131
|
+
*
|
|
3132
|
+
* Complete guide for deploying Augur API clients in enterprise
|
|
3133
|
+
* production environments with monitoring and reliability.
|
|
3134
|
+
*/
|
|
3135
|
+
|
|
3136
|
+
// 🏗️ Production Configuration Template
|
|
3137
|
+
class ProductionConfig {
|
|
3138
|
+
static readonly ENVIRONMENT_CONFIG = {
|
|
3139
|
+
development: {
|
|
3140
|
+
logLevel: 'debug',
|
|
3141
|
+
requestTimeout: 30000,
|
|
3142
|
+
retryAttempts: 2,
|
|
3143
|
+
enableDetailedErrors: true,
|
|
3144
|
+
cacheEnabled: false
|
|
3145
|
+
},
|
|
3146
|
+
staging: {
|
|
3147
|
+
logLevel: 'info',
|
|
3148
|
+
requestTimeout: 15000,
|
|
3149
|
+
retryAttempts: 3,
|
|
3150
|
+
enableDetailedErrors: true,
|
|
3151
|
+
cacheEnabled: true
|
|
3152
|
+
},
|
|
3153
|
+
production: {
|
|
3154
|
+
logLevel: 'warn',
|
|
3155
|
+
requestTimeout: 10000,
|
|
3156
|
+
retryAttempts: 5,
|
|
3157
|
+
enableDetailedErrors: false,
|
|
3158
|
+
cacheEnabled: true
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
|
|
3162
|
+
static createProductionAPI(environment: 'development' | 'staging' | 'production'): AugurAPI {
|
|
3163
|
+
const config = ProductionConfig.ENVIRONMENT_CONFIG[environment];
|
|
3164
|
+
|
|
3165
|
+
return new AugurAPI({
|
|
3166
|
+
siteId: process.env.AUGUR_SITE_ID!,
|
|
3167
|
+
bearerToken: process.env.AUGUR_JWT_TOKEN!,
|
|
3168
|
+
|
|
3169
|
+
// Production timeouts and retries
|
|
3170
|
+
timeout: config.requestTimeout,
|
|
3171
|
+
retries: config.retryAttempts,
|
|
3172
|
+
|
|
3173
|
+
// Environment-specific request handling
|
|
3174
|
+
onRequest: (requestConfig) => {
|
|
3175
|
+
// Add request ID for tracing
|
|
3176
|
+
requestConfig.headers = {
|
|
3177
|
+
...requestConfig.headers,
|
|
3178
|
+
'X-Environment': environment,
|
|
3179
|
+
'X-Request-ID': crypto.randomUUID(),
|
|
3180
|
+
'X-Deployment-Version': process.env.DEPLOYMENT_VERSION || 'unknown'
|
|
3181
|
+
};
|
|
3182
|
+
|
|
3183
|
+
if (config.logLevel === 'debug') {
|
|
3184
|
+
console.log(`🔍 API Request: ${requestConfig.method?.toUpperCase()} ${requestConfig.url}`);
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
return requestConfig;
|
|
3188
|
+
},
|
|
3189
|
+
|
|
3190
|
+
onResponse: (response) => {
|
|
3191
|
+
if (config.logLevel === 'debug') {
|
|
3192
|
+
console.log(`✅ API Response: ${response.status} for ${response.config.url}`);
|
|
3193
|
+
}
|
|
3194
|
+
return response;
|
|
3195
|
+
},
|
|
3196
|
+
|
|
3197
|
+
onError: (error) => {
|
|
3198
|
+
// Production error handling
|
|
3199
|
+
const sanitizedError = config.enableDetailedErrors
|
|
3200
|
+
? error
|
|
3201
|
+
: new Error('API request failed - check logs for details');
|
|
3202
|
+
|
|
3203
|
+
// Log error details for debugging (always log internally)
|
|
3204
|
+
console.error('❌ API Error:', {
|
|
3205
|
+
url: error.config?.url,
|
|
3206
|
+
method: error.config?.method,
|
|
3207
|
+
status: error.response?.status,
|
|
3208
|
+
message: error.message,
|
|
3209
|
+
requestId: error.config?.headers?.['X-Request-ID'],
|
|
3210
|
+
environment
|
|
3211
|
+
});
|
|
3212
|
+
|
|
3213
|
+
throw sanitizedError;
|
|
3214
|
+
}
|
|
3215
|
+
});
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
// 📊 Health Check & Monitoring System
|
|
3220
|
+
class ProductionHealthMonitor {
|
|
3221
|
+
private healthCheckInterval: NodeJS.Timer | null = null;
|
|
3222
|
+
private lastHealthCheck: Record<string, any> = {};
|
|
3223
|
+
|
|
3224
|
+
async initializeHealthChecks(apis: Map<string, AugurAPI>): Promise<void> {
|
|
3225
|
+
console.log('🏥 Initializing health monitoring...');
|
|
3226
|
+
|
|
3227
|
+
// Initial health check
|
|
3228
|
+
await this.performHealthChecks(apis);
|
|
3229
|
+
|
|
3230
|
+
// Periodic health checks (every 5 minutes)
|
|
3231
|
+
this.healthCheckInterval = setInterval(async () => {
|
|
3232
|
+
await this.performHealthChecks(apis);
|
|
3233
|
+
}, 5 * 60 * 1000);
|
|
3234
|
+
|
|
3235
|
+
console.log('✅ Health monitoring initialized');
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
private async performHealthChecks(apis: Map<string, AugurAPI>): Promise<void> {
|
|
3239
|
+
const timestamp = new Date().toISOString();
|
|
3240
|
+
const healthResults: Record<string, any> = {};
|
|
3241
|
+
|
|
3242
|
+
for (const [siteId, api] of apis.entries()) {
|
|
3243
|
+
try {
|
|
3244
|
+
const startTime = Date.now();
|
|
3245
|
+
|
|
3246
|
+
// Test core services
|
|
3247
|
+
const [joomlaHealth, pricingHealth, vmiHealth] = await Promise.allSettled([
|
|
3248
|
+
api.joomla.getHealthCheck(),
|
|
3249
|
+
api.pricing.getHealthCheck(),
|
|
3250
|
+
api.vmi.health.ping()
|
|
3251
|
+
]);
|
|
3252
|
+
|
|
3253
|
+
const responseTime = Date.now() - startTime;
|
|
3254
|
+
|
|
3255
|
+
healthResults[siteId] = {
|
|
3256
|
+
status: 'healthy',
|
|
3257
|
+
responseTime,
|
|
3258
|
+
services: {
|
|
3259
|
+
joomla: joomlaHealth.status === 'fulfilled' ? 'healthy' : 'unhealthy',
|
|
3260
|
+
pricing: pricingHealth.status === 'fulfilled' ? 'healthy' : 'unhealthy',
|
|
3261
|
+
vmi: vmiHealth.status === 'fulfilled' ? 'healthy' : 'unhealthy'
|
|
3262
|
+
},
|
|
3263
|
+
timestamp
|
|
3264
|
+
};
|
|
3265
|
+
|
|
3266
|
+
console.log(`✅ Health check passed for ${siteId} (${responseTime}ms)`);
|
|
3267
|
+
|
|
3268
|
+
} catch (error) {
|
|
3269
|
+
healthResults[siteId] = {
|
|
3270
|
+
status: 'unhealthy',
|
|
3271
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3272
|
+
timestamp
|
|
3273
|
+
};
|
|
3274
|
+
|
|
3275
|
+
console.error(`❌ Health check failed for ${siteId}:`, error);
|
|
3276
|
+
|
|
3277
|
+
// Trigger alert for unhealthy sites
|
|
3278
|
+
await this.alertUnhealthySite(siteId, error);
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
this.lastHealthCheck = healthResults;
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
private async alertUnhealthySite(siteId: string, error: any): Promise<void> {
|
|
3286
|
+
// Implement your alerting system
|
|
3287
|
+
console.warn(`🚨 HEALTH ALERT: Site ${siteId} is unhealthy`, error);
|
|
3288
|
+
|
|
3289
|
+
// Example integrations:
|
|
3290
|
+
// - PagerDuty incident
|
|
3291
|
+
// - Slack notification
|
|
3292
|
+
// - Email alert
|
|
3293
|
+
// - Automated failover
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
getHealthStatus(): Record<string, any> {
|
|
3297
|
+
return this.lastHealthCheck;
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
destroy(): void {
|
|
3301
|
+
if (this.healthCheckInterval) {
|
|
3302
|
+
clearInterval(this.healthCheckInterval);
|
|
3303
|
+
this.healthCheckInterval = null;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
// 🔄 Graceful Shutdown Manager
|
|
3309
|
+
class GracefulShutdownManager {
|
|
3310
|
+
private isShuttingDown = false;
|
|
3311
|
+
private activeRequests = new Set<Promise<any>>();
|
|
3312
|
+
|
|
3313
|
+
initialize(): void {
|
|
3314
|
+
// Handle shutdown signals
|
|
3315
|
+
process.on('SIGTERM', () => this.handleShutdown('SIGTERM'));
|
|
3316
|
+
process.on('SIGINT', () => this.handleShutdown('SIGINT'));
|
|
3317
|
+
process.on('SIGHUP', () => this.handleShutdown('SIGHUP'));
|
|
3318
|
+
|
|
3319
|
+
console.log('🛡️ Graceful shutdown handler initialized');
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
private async handleShutdown(signal: string): Promise<void> {
|
|
3323
|
+
if (this.isShuttingDown) return;
|
|
3324
|
+
|
|
3325
|
+
console.log(`🛑 Received ${signal}, starting graceful shutdown...`);
|
|
3326
|
+
this.isShuttingDown = true;
|
|
3327
|
+
|
|
3328
|
+
try {
|
|
3329
|
+
// Wait for active requests to complete (with timeout)
|
|
3330
|
+
const shutdownTimeout = 30000; // 30 seconds
|
|
3331
|
+
const shutdownPromise = Promise.all(this.activeRequests);
|
|
3332
|
+
|
|
3333
|
+
await Promise.race([
|
|
3334
|
+
shutdownPromise,
|
|
3335
|
+
new Promise(resolve => setTimeout(resolve, shutdownTimeout))
|
|
3336
|
+
]);
|
|
3337
|
+
|
|
3338
|
+
console.log('✅ Graceful shutdown completed');
|
|
3339
|
+
process.exit(0);
|
|
3340
|
+
|
|
3341
|
+
} catch (error) {
|
|
3342
|
+
console.error('❌ Error during shutdown:', error);
|
|
3343
|
+
process.exit(1);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
trackRequest<T>(request: Promise<T>): Promise<T> {
|
|
3348
|
+
if (this.isShuttingDown) {
|
|
3349
|
+
throw new Error('Service is shutting down');
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
this.activeRequests.add(request);
|
|
3353
|
+
|
|
3354
|
+
return request.finally(() => {
|
|
3355
|
+
this.activeRequests.delete(request);
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
isShutdown(): boolean {
|
|
3360
|
+
return this.isShuttingDown;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
// 🎮 PRODUCTION DEPLOYMENT EXAMPLE
|
|
3365
|
+
async function deployProductionApplication() {
|
|
3366
|
+
console.log('🚀 Starting production application...');
|
|
3367
|
+
|
|
3368
|
+
// 1. Initialize graceful shutdown
|
|
3369
|
+
const shutdownManager = new GracefulShutdownManager();
|
|
3370
|
+
shutdownManager.initialize();
|
|
3371
|
+
|
|
3372
|
+
// 2. Create production API clients
|
|
3373
|
+
const environment = process.env.NODE_ENV as 'development' | 'staging' | 'production' || 'production';
|
|
3374
|
+
const apis = new Map<string, AugurAPI>();
|
|
3375
|
+
|
|
3376
|
+
// Multiple tenant sites
|
|
3377
|
+
const tenantSites = process.env.TENANT_SITES?.split(',') || ['main-site'];
|
|
3378
|
+
|
|
3379
|
+
for (const siteId of tenantSites) {
|
|
3380
|
+
const api = ProductionConfig.createProductionAPI(environment);
|
|
3381
|
+
apis.set(siteId, api);
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
// 3. Initialize health monitoring
|
|
3385
|
+
const healthMonitor = new ProductionHealthMonitor();
|
|
3386
|
+
await healthMonitor.initializeHealthChecks(apis);
|
|
3387
|
+
|
|
3388
|
+
// 4. Start application logic
|
|
3389
|
+
console.log(`✅ Production application started with ${apis.size} tenant site(s)`);
|
|
3390
|
+
|
|
3391
|
+
// 5. Cleanup on shutdown
|
|
3392
|
+
process.on('exit', () => {
|
|
3393
|
+
healthMonitor.destroy();
|
|
3394
|
+
console.log('🧹 Cleanup completed');
|
|
3395
|
+
});
|
|
3396
|
+
|
|
3397
|
+
return { apis, healthMonitor, shutdownManager };
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
// Environment Variables Checklist for Production:
|
|
3401
|
+
/*
|
|
3402
|
+
REQUIRED ENVIRONMENT VARIABLES:
|
|
3403
|
+
- AUGUR_SITE_ID: Primary site identifier
|
|
3404
|
+
- AUGUR_JWT_TOKEN: Authentication token
|
|
3405
|
+
- AUGUR_ADMIN_TOKEN: Admin token for cross-site operations
|
|
3406
|
+
- NODE_ENV: production|staging|development
|
|
3407
|
+
- TENANT_SITES: Comma-separated list of tenant site IDs
|
|
3408
|
+
- DEPLOYMENT_VERSION: Current deployment version
|
|
3409
|
+
- ALLOWED_ORIGINS: Comma-separated list of allowed origins
|
|
3410
|
+
|
|
3411
|
+
OPTIONAL ENVIRONMENT VARIABLES:
|
|
3412
|
+
- LOG_LEVEL: error|warn|info|debug
|
|
3413
|
+
- REQUEST_TIMEOUT: Request timeout in milliseconds
|
|
3414
|
+
- MAX_RETRIES: Maximum retry attempts
|
|
3415
|
+
- HEALTH_CHECK_INTERVAL: Health check interval in minutes
|
|
3416
|
+
*/
|
|
3417
|
+
```
|
|
3418
|
+
|
|
3419
|
+
**Production Deployment Checklist:**
|
|
3420
|
+
|
|
3421
|
+
- ✅ **Environment Variables**: All required variables set and verified
|
|
3422
|
+
- ✅ **Security Configuration**: Tokens secured, HTTPS enforced, rate limiting enabled
|
|
3423
|
+
- ✅ **Health Monitoring**: Health checks configured with alerting
|
|
3424
|
+
- ✅ **Error Handling**: Production error handling with proper logging
|
|
3425
|
+
- ✅ **Graceful Shutdown**: Proper cleanup on application termination
|
|
3426
|
+
- ✅ **Multi-Tenant Support**: Tenant isolation and cross-site authentication
|
|
3427
|
+
- ✅ **Performance Optimization**: Edge caching and batch operations configured
|
|
3428
|
+
- ✅ **Monitoring & Alerting**: Security events, API performance, and health status tracking
|
|
3429
|
+
|
|
3430
|
+
For detailed authentication patterns, see [Authentication Guide](./AUTHENTICATION.md).
|
|
3431
|
+
For performance optimization strategies, see [Performance Guide](./PERFORMANCE.md).
|
|
3432
|
+
|
|
2546
3433
|
## Development
|
|
2547
3434
|
|
|
2548
3435
|
### Building from Source
|