s3db.js 13.4.0 → 13.6.0

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.
Files changed (110) hide show
  1. package/README.md +25 -10
  2. package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
  3. package/dist/s3db.cjs.map +1 -0
  4. package/dist/s3db.es.js +38653 -32291
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +218 -22
  7. package/src/concerns/id.js +90 -6
  8. package/src/concerns/index.js +2 -1
  9. package/src/concerns/password-hashing.js +150 -0
  10. package/src/database.class.js +6 -2
  11. package/src/plugins/api/auth/basic-auth.js +40 -10
  12. package/src/plugins/api/auth/index.js +49 -3
  13. package/src/plugins/api/auth/oauth2-auth.js +171 -0
  14. package/src/plugins/api/auth/oidc-auth.js +789 -0
  15. package/src/plugins/api/auth/oidc-client.js +462 -0
  16. package/src/plugins/api/auth/path-auth-matcher.js +284 -0
  17. package/src/plugins/api/concerns/event-emitter.js +134 -0
  18. package/src/plugins/api/concerns/failban-manager.js +651 -0
  19. package/src/plugins/api/concerns/guards-helpers.js +402 -0
  20. package/src/plugins/api/concerns/metrics-collector.js +346 -0
  21. package/src/plugins/api/index.js +510 -57
  22. package/src/plugins/api/middlewares/failban.js +305 -0
  23. package/src/plugins/api/middlewares/rate-limit.js +301 -0
  24. package/src/plugins/api/middlewares/request-id.js +74 -0
  25. package/src/plugins/api/middlewares/security-headers.js +120 -0
  26. package/src/plugins/api/middlewares/session-tracking.js +194 -0
  27. package/src/plugins/api/routes/auth-routes.js +119 -78
  28. package/src/plugins/api/routes/resource-routes.js +73 -30
  29. package/src/plugins/api/server.js +1139 -45
  30. package/src/plugins/api/utils/custom-routes.js +102 -0
  31. package/src/plugins/api/utils/guards.js +213 -0
  32. package/src/plugins/api/utils/mime-types.js +154 -0
  33. package/src/plugins/api/utils/openapi-generator.js +91 -12
  34. package/src/plugins/api/utils/path-matcher.js +173 -0
  35. package/src/plugins/api/utils/static-filesystem.js +262 -0
  36. package/src/plugins/api/utils/static-s3.js +231 -0
  37. package/src/plugins/api/utils/template-engine.js +188 -0
  38. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
  39. package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
  40. package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
  41. package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
  42. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
  43. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
  44. package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
  45. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
  46. package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
  47. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
  48. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
  49. package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
  50. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
  51. package/src/plugins/cloud-inventory/index.js +20 -0
  52. package/src/plugins/cloud-inventory/registry.js +146 -0
  53. package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
  54. package/src/plugins/cloud-inventory.plugin.js +1333 -0
  55. package/src/plugins/concerns/plugin-dependencies.js +62 -2
  56. package/src/plugins/eventual-consistency/analytics.js +1 -0
  57. package/src/plugins/eventual-consistency/consolidation.js +2 -2
  58. package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
  59. package/src/plugins/eventual-consistency/install.js +2 -2
  60. package/src/plugins/identity/README.md +335 -0
  61. package/src/plugins/identity/concerns/mfa-manager.js +204 -0
  62. package/src/plugins/identity/concerns/password.js +138 -0
  63. package/src/plugins/identity/concerns/resource-schemas.js +273 -0
  64. package/src/plugins/identity/concerns/token-generator.js +172 -0
  65. package/src/plugins/identity/email-service.js +422 -0
  66. package/src/plugins/identity/index.js +1052 -0
  67. package/src/plugins/identity/oauth2-server.js +1033 -0
  68. package/src/plugins/identity/oidc-discovery.js +285 -0
  69. package/src/plugins/identity/rsa-keys.js +323 -0
  70. package/src/plugins/identity/server.js +500 -0
  71. package/src/plugins/identity/session-manager.js +453 -0
  72. package/src/plugins/identity/ui/layouts/base.js +251 -0
  73. package/src/plugins/identity/ui/middleware.js +135 -0
  74. package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
  75. package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
  76. package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
  77. package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
  78. package/src/plugins/identity/ui/pages/admin/users.js +263 -0
  79. package/src/plugins/identity/ui/pages/consent.js +262 -0
  80. package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
  81. package/src/plugins/identity/ui/pages/login.js +144 -0
  82. package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
  83. package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
  84. package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
  85. package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
  86. package/src/plugins/identity/ui/pages/profile.js +361 -0
  87. package/src/plugins/identity/ui/pages/register.js +226 -0
  88. package/src/plugins/identity/ui/pages/reset-password.js +128 -0
  89. package/src/plugins/identity/ui/pages/verify-email.js +172 -0
  90. package/src/plugins/identity/ui/routes.js +2541 -0
  91. package/src/plugins/identity/ui/styles/main.css +465 -0
  92. package/src/plugins/index.js +4 -1
  93. package/src/plugins/ml/base-model.class.js +65 -16
  94. package/src/plugins/ml/classification-model.class.js +1 -1
  95. package/src/plugins/ml/timeseries-model.class.js +3 -1
  96. package/src/plugins/ml.plugin.js +584 -31
  97. package/src/plugins/shared/error-handler.js +147 -0
  98. package/src/plugins/shared/index.js +9 -0
  99. package/src/plugins/shared/middlewares/compression.js +117 -0
  100. package/src/plugins/shared/middlewares/cors.js +49 -0
  101. package/src/plugins/shared/middlewares/index.js +11 -0
  102. package/src/plugins/shared/middlewares/logging.js +54 -0
  103. package/src/plugins/shared/middlewares/rate-limit.js +73 -0
  104. package/src/plugins/shared/middlewares/security.js +158 -0
  105. package/src/plugins/shared/response-formatter.js +264 -0
  106. package/src/plugins/state-machine.plugin.js +57 -2
  107. package/src/resource.class.js +140 -12
  108. package/src/schema.class.js +30 -1
  109. package/src/validator.class.js +57 -6
  110. package/dist/s3db.cjs.js.map +0 -1
@@ -0,0 +1,771 @@
1
+ import { BaseCloudDriver } from './base-driver.js';
2
+
3
+ /**
4
+ * Production-ready MongoDB Atlas inventory driver using mongodb-atlas-api-client.
5
+ *
6
+ * Covers 10+ services with 15+ resource types:
7
+ * - Compute (Clusters, Serverless Instances)
8
+ * - Storage (Backups, Snapshots, Data Lakes)
9
+ * - Security (Database Users, IP Access Lists, Custom Roles)
10
+ * - Monitoring (Alerts, Events)
11
+ * - Search (Atlas Search Indexes)
12
+ * - Projects & Organizations
13
+ *
14
+ * @see https://www.mongodb.com/docs/atlas/api/
15
+ * @see https://www.npmjs.com/package/mongodb-atlas-api-client
16
+ */
17
+ export class MongoDBAtlasInventoryDriver extends BaseCloudDriver {
18
+ constructor(options = {}) {
19
+ super({ ...options, driver: options.driver || 'mongodb-atlas' });
20
+
21
+ this._publicKey = null;
22
+ this._privateKey = null;
23
+ this._baseUrl = 'https://cloud.mongodb.com/api/atlas/v2';
24
+ this._organizationId = this.config?.organizationId || null;
25
+
26
+ // Services to collect (can be filtered via config.services)
27
+ this._services = this.config?.services || [
28
+ 'projects',
29
+ 'clusters',
30
+ 'serverless',
31
+ 'users',
32
+ 'accesslists',
33
+ 'backups',
34
+ 'alerts',
35
+ 'datalakes',
36
+ 'search',
37
+ 'customroles',
38
+ 'events'
39
+ ];
40
+
41
+ // Projects to scan (null = all accessible projects)
42
+ this._projectIds = this.config?.projectIds || null;
43
+ }
44
+
45
+ /**
46
+ * Initialize MongoDB Atlas credentials.
47
+ */
48
+ async _initializeCredentials() {
49
+ if (this._publicKey) return;
50
+
51
+ const credentials = this.credentials || {};
52
+ this._publicKey = credentials.publicKey || process.env.MONGODB_ATLAS_PUBLIC_KEY;
53
+ this._privateKey = credentials.privateKey || process.env.MONGODB_ATLAS_PRIVATE_KEY;
54
+ this._organizationId = credentials.organizationId || this._organizationId;
55
+
56
+ if (!this._publicKey || !this._privateKey) {
57
+ throw new Error('MongoDB Atlas API keys are required. Provide via credentials.publicKey/privateKey or env vars.');
58
+ }
59
+
60
+ this.logger('info', 'MongoDB Atlas credentials initialized', {
61
+ organizationId: this._organizationId || 'auto-discover',
62
+ services: this._services.length
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Create Atlas API client.
68
+ */
69
+ async _createClient() {
70
+ const AtlasClient = await import('mongodb-atlas-api-client');
71
+
72
+ return AtlasClient.default({
73
+ publicKey: this._publicKey,
74
+ privateKey: this._privateKey,
75
+ baseUrl: this._baseUrl
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Make authenticated request to Atlas API.
81
+ */
82
+ async _makeRequest(endpoint, options = {}) {
83
+ const crypto = await import('crypto');
84
+ const https = await import('https');
85
+
86
+ const url = new URL(endpoint, this._baseUrl);
87
+ const method = options.method || 'GET';
88
+
89
+ // HTTP Digest Authentication
90
+ const nonce = crypto.randomBytes(16).toString('hex');
91
+ const timestamp = new Date().toISOString();
92
+
93
+ return new Promise((resolve, reject) => {
94
+ const req = https.request({
95
+ hostname: url.hostname,
96
+ path: url.pathname + url.search,
97
+ method,
98
+ headers: {
99
+ 'Accept': 'application/vnd.atlas.2025-03-12+json',
100
+ 'Content-Type': 'application/json'
101
+ },
102
+ auth: `${this._publicKey}:${this._privateKey}`
103
+ }, (res) => {
104
+ let data = '';
105
+ res.on('data', chunk => data += chunk);
106
+ res.on('end', () => {
107
+ try {
108
+ resolve(JSON.parse(data));
109
+ } catch (err) {
110
+ resolve(data);
111
+ }
112
+ });
113
+ });
114
+
115
+ req.on('error', reject);
116
+
117
+ if (options.body) {
118
+ req.write(JSON.stringify(options.body));
119
+ }
120
+
121
+ req.end();
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Main entry point - lists all resources from configured services.
127
+ */
128
+ async *listResources(options = {}) {
129
+ await this._initializeCredentials();
130
+
131
+ const serviceCollectors = {
132
+ projects: () => this._collectProjects(),
133
+ clusters: () => this._collectClusters(),
134
+ serverless: () => this._collectServerless(),
135
+ users: () => this._collectUsers(),
136
+ accesslists: () => this._collectAccessLists(),
137
+ backups: () => this._collectBackups(),
138
+ alerts: () => this._collectAlerts(),
139
+ datalakes: () => this._collectDataLakes(),
140
+ search: () => this._collectSearchIndexes(),
141
+ customroles: () => this._collectCustomRoles(),
142
+ events: () => this._collectEvents()
143
+ };
144
+
145
+ for (const service of this._services) {
146
+ const collector = serviceCollectors[service];
147
+ if (!collector) {
148
+ this.logger('warn', `Unknown MongoDB Atlas service: ${service}`, { service });
149
+ continue;
150
+ }
151
+
152
+ try {
153
+ this.logger('info', `Collecting MongoDB Atlas ${service} resources`, { service });
154
+ yield* collector();
155
+ } catch (err) {
156
+ // Continue with next service instead of failing entire sync
157
+ this.logger('error', `MongoDB Atlas service collection failed, skipping to next service`, {
158
+ service,
159
+ error: err.message,
160
+ errorName: err.name,
161
+ stack: err.stack
162
+ });
163
+ }
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Get list of projects to iterate.
169
+ */
170
+ async _getProjects() {
171
+ if (this._projectIds) {
172
+ return this._projectIds.map(id => ({ id }));
173
+ }
174
+
175
+ try {
176
+ const response = await this._makeRequest('/groups');
177
+ return response.results || [];
178
+ } catch (err) {
179
+ this.logger('error', 'Failed to fetch projects list', { error: err.message });
180
+ return [];
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Collect Projects (Groups).
186
+ */
187
+ async *_collectProjects() {
188
+ try {
189
+ const projects = await this._getProjects();
190
+
191
+ for (const project of projects) {
192
+ yield {
193
+ provider: 'mongodb-atlas',
194
+ accountId: this._organizationId || project.orgId,
195
+ region: null, // Projects are global
196
+ service: 'projects',
197
+ resourceType: 'mongodb-atlas.project',
198
+ resourceId: project.id,
199
+ name: project.name,
200
+ tags: {},
201
+ metadata: {
202
+ orgId: project.orgId,
203
+ clusterCount: project.clusterCount
204
+ },
205
+ configuration: this._sanitize(project)
206
+ };
207
+ }
208
+
209
+ this.logger('info', `Collected ${projects.length} MongoDB Atlas projects`);
210
+ } catch (err) {
211
+ this.logger('error', 'Failed to collect MongoDB Atlas projects', {
212
+ error: err.message,
213
+ stack: err.stack
214
+ });
215
+ throw err;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Collect Clusters.
221
+ */
222
+ async *_collectClusters() {
223
+ try {
224
+ const projects = await this._getProjects();
225
+
226
+ for (const project of projects) {
227
+ try {
228
+ const response = await this._makeRequest(`/groups/${project.id}/clusters`);
229
+ const clusters = response.results || [];
230
+
231
+ for (const cluster of clusters) {
232
+ yield {
233
+ provider: 'mongodb-atlas',
234
+ accountId: this._organizationId || project.orgId,
235
+ region: cluster.providerSettings?.regionName || null,
236
+ service: 'clusters',
237
+ resourceType: 'mongodb-atlas.cluster',
238
+ resourceId: cluster.id || cluster.name,
239
+ name: cluster.name,
240
+ tags: cluster.tags || {},
241
+ metadata: {
242
+ projectId: project.id,
243
+ projectName: project.name,
244
+ tier: cluster.providerSettings?.instanceSizeName,
245
+ provider: cluster.providerSettings?.providerName,
246
+ mongoDBVersion: cluster.mongoDBVersion,
247
+ clusterType: cluster.clusterType,
248
+ state: cluster.stateName
249
+ },
250
+ configuration: this._sanitize(cluster)
251
+ };
252
+ }
253
+ } catch (projectErr) {
254
+ this.logger('warn', `Failed to collect clusters for project ${project.id}`, {
255
+ projectId: project.id,
256
+ error: projectErr.message
257
+ });
258
+ }
259
+ }
260
+
261
+ this.logger('info', `Collected MongoDB Atlas clusters`);
262
+ } catch (err) {
263
+ this.logger('error', 'Failed to collect MongoDB Atlas clusters', {
264
+ error: err.message,
265
+ stack: err.stack
266
+ });
267
+ throw err;
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Collect Serverless Instances.
273
+ */
274
+ async *_collectServerless() {
275
+ try {
276
+ const projects = await this._getProjects();
277
+
278
+ for (const project of projects) {
279
+ try {
280
+ const response = await this._makeRequest(`/groups/${project.id}/serverless`);
281
+ const instances = response.results || [];
282
+
283
+ for (const instance of instances) {
284
+ yield {
285
+ provider: 'mongodb-atlas',
286
+ accountId: this._organizationId || project.orgId,
287
+ region: instance.providerSettings?.regionName || null,
288
+ service: 'serverless',
289
+ resourceType: 'mongodb-atlas.serverless',
290
+ resourceId: instance.id || instance.name,
291
+ name: instance.name,
292
+ tags: instance.tags || {},
293
+ metadata: {
294
+ projectId: project.id,
295
+ projectName: project.name,
296
+ provider: instance.providerSettings?.providerName,
297
+ state: instance.stateName
298
+ },
299
+ configuration: this._sanitize(instance)
300
+ };
301
+ }
302
+ } catch (projectErr) {
303
+ this.logger('debug', `No serverless instances in project ${project.id}`, {
304
+ projectId: project.id,
305
+ error: projectErr.message
306
+ });
307
+ }
308
+ }
309
+
310
+ this.logger('info', `Collected MongoDB Atlas serverless instances`);
311
+ } catch (err) {
312
+ this.logger('error', 'Failed to collect MongoDB Atlas serverless', {
313
+ error: err.message,
314
+ stack: err.stack
315
+ });
316
+ throw err;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Collect Database Users.
322
+ */
323
+ async *_collectUsers() {
324
+ try {
325
+ const projects = await this._getProjects();
326
+
327
+ for (const project of projects) {
328
+ try {
329
+ const response = await this._makeRequest(`/groups/${project.id}/databaseUsers`);
330
+ const users = response.results || [];
331
+
332
+ for (const user of users) {
333
+ yield {
334
+ provider: 'mongodb-atlas',
335
+ accountId: this._organizationId || project.orgId,
336
+ region: null,
337
+ service: 'users',
338
+ resourceType: 'mongodb-atlas.user',
339
+ resourceId: `${project.id}/${user.username}`,
340
+ name: user.username,
341
+ tags: {},
342
+ metadata: {
343
+ projectId: project.id,
344
+ projectName: project.name,
345
+ databaseName: user.databaseName,
346
+ roles: user.roles?.map(r => r.roleName)
347
+ },
348
+ configuration: this._sanitize(user)
349
+ };
350
+ }
351
+ } catch (projectErr) {
352
+ this.logger('warn', `Failed to collect users for project ${project.id}`, {
353
+ projectId: project.id,
354
+ error: projectErr.message
355
+ });
356
+ }
357
+ }
358
+
359
+ this.logger('info', `Collected MongoDB Atlas database users`);
360
+ } catch (err) {
361
+ this.logger('error', 'Failed to collect MongoDB Atlas users', {
362
+ error: err.message,
363
+ stack: err.stack
364
+ });
365
+ throw err;
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Collect IP Access Lists (Whitelists).
371
+ */
372
+ async *_collectAccessLists() {
373
+ try {
374
+ const projects = await this._getProjects();
375
+
376
+ for (const project of projects) {
377
+ try {
378
+ const response = await this._makeRequest(`/groups/${project.id}/accessList`);
379
+ const entries = response.results || [];
380
+
381
+ for (const entry of entries) {
382
+ yield {
383
+ provider: 'mongodb-atlas',
384
+ accountId: this._organizationId || project.orgId,
385
+ region: null,
386
+ service: 'accesslists',
387
+ resourceType: 'mongodb-atlas.accesslist',
388
+ resourceId: `${project.id}/${entry.ipAddress || entry.cidrBlock}`,
389
+ name: entry.comment || entry.ipAddress || entry.cidrBlock,
390
+ tags: {},
391
+ metadata: {
392
+ projectId: project.id,
393
+ projectName: project.name,
394
+ ipAddress: entry.ipAddress,
395
+ cidrBlock: entry.cidrBlock
396
+ },
397
+ configuration: this._sanitize(entry)
398
+ };
399
+ }
400
+ } catch (projectErr) {
401
+ this.logger('warn', `Failed to collect access lists for project ${project.id}`, {
402
+ projectId: project.id,
403
+ error: projectErr.message
404
+ });
405
+ }
406
+ }
407
+
408
+ this.logger('info', `Collected MongoDB Atlas IP access lists`);
409
+ } catch (err) {
410
+ this.logger('error', 'Failed to collect MongoDB Atlas access lists', {
411
+ error: err.message,
412
+ stack: err.stack
413
+ });
414
+ throw err;
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Collect Cloud Backups.
420
+ */
421
+ async *_collectBackups() {
422
+ try {
423
+ const projects = await this._getProjects();
424
+
425
+ for (const project of projects) {
426
+ try {
427
+ // Get clusters first
428
+ const clustersResponse = await this._makeRequest(`/groups/${project.id}/clusters`);
429
+ const clusters = clustersResponse.results || [];
430
+
431
+ for (const cluster of clusters) {
432
+ try {
433
+ const response = await this._makeRequest(
434
+ `/groups/${project.id}/clusters/${cluster.name}/backup/snapshots`
435
+ );
436
+ const snapshots = response.results || [];
437
+
438
+ for (const snapshot of snapshots) {
439
+ yield {
440
+ provider: 'mongodb-atlas',
441
+ accountId: this._organizationId || project.orgId,
442
+ region: cluster.providerSettings?.regionName || null,
443
+ service: 'backups',
444
+ resourceType: 'mongodb-atlas.backup',
445
+ resourceId: snapshot.id,
446
+ name: `${cluster.name}-${snapshot.id}`,
447
+ tags: {},
448
+ metadata: {
449
+ projectId: project.id,
450
+ projectName: project.name,
451
+ clusterName: cluster.name,
452
+ type: snapshot.type,
453
+ status: snapshot.status
454
+ },
455
+ configuration: this._sanitize(snapshot)
456
+ };
457
+ }
458
+ } catch (clusterErr) {
459
+ this.logger('debug', `No backups for cluster ${cluster.name}`, {
460
+ clusterName: cluster.name,
461
+ error: clusterErr.message
462
+ });
463
+ }
464
+ }
465
+ } catch (projectErr) {
466
+ this.logger('warn', `Failed to collect backups for project ${project.id}`, {
467
+ projectId: project.id,
468
+ error: projectErr.message
469
+ });
470
+ }
471
+ }
472
+
473
+ this.logger('info', `Collected MongoDB Atlas backups`);
474
+ } catch (err) {
475
+ this.logger('error', 'Failed to collect MongoDB Atlas backups', {
476
+ error: err.message,
477
+ stack: err.stack
478
+ });
479
+ throw err;
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Collect Alert Configurations.
485
+ */
486
+ async *_collectAlerts() {
487
+ try {
488
+ const projects = await this._getProjects();
489
+
490
+ for (const project of projects) {
491
+ try {
492
+ const response = await this._makeRequest(`/groups/${project.id}/alertConfigs`);
493
+ const alerts = response.results || [];
494
+
495
+ for (const alert of alerts) {
496
+ yield {
497
+ provider: 'mongodb-atlas',
498
+ accountId: this._organizationId || project.orgId,
499
+ region: null,
500
+ service: 'alerts',
501
+ resourceType: 'mongodb-atlas.alert',
502
+ resourceId: alert.id,
503
+ name: alert.eventTypeName,
504
+ tags: {},
505
+ metadata: {
506
+ projectId: project.id,
507
+ projectName: project.name,
508
+ enabled: alert.enabled,
509
+ eventTypeName: alert.eventTypeName
510
+ },
511
+ configuration: this._sanitize(alert)
512
+ };
513
+ }
514
+ } catch (projectErr) {
515
+ this.logger('warn', `Failed to collect alerts for project ${project.id}`, {
516
+ projectId: project.id,
517
+ error: projectErr.message
518
+ });
519
+ }
520
+ }
521
+
522
+ this.logger('info', `Collected MongoDB Atlas alerts`);
523
+ } catch (err) {
524
+ this.logger('error', 'Failed to collect MongoDB Atlas alerts', {
525
+ error: err.message,
526
+ stack: err.stack
527
+ });
528
+ throw err;
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Collect Data Lakes (Federated Database Instances).
534
+ */
535
+ async *_collectDataLakes() {
536
+ try {
537
+ const projects = await this._getProjects();
538
+
539
+ for (const project of projects) {
540
+ try {
541
+ const response = await this._makeRequest(`/groups/${project.id}/dataLakes`);
542
+ const dataLakes = response || [];
543
+
544
+ for (const lake of dataLakes) {
545
+ yield {
546
+ provider: 'mongodb-atlas',
547
+ accountId: this._organizationId || project.orgId,
548
+ region: lake.cloudProviderConfig?.aws?.roleId ? 'aws' : null,
549
+ service: 'datalakes',
550
+ resourceType: 'mongodb-atlas.datalake',
551
+ resourceId: lake.name,
552
+ name: lake.name,
553
+ tags: {},
554
+ metadata: {
555
+ projectId: project.id,
556
+ projectName: project.name,
557
+ state: lake.state
558
+ },
559
+ configuration: this._sanitize(lake)
560
+ };
561
+ }
562
+ } catch (projectErr) {
563
+ this.logger('debug', `No data lakes in project ${project.id}`, {
564
+ projectId: project.id,
565
+ error: projectErr.message
566
+ });
567
+ }
568
+ }
569
+
570
+ this.logger('info', `Collected MongoDB Atlas data lakes`);
571
+ } catch (err) {
572
+ this.logger('error', 'Failed to collect MongoDB Atlas data lakes', {
573
+ error: err.message,
574
+ stack: err.stack
575
+ });
576
+ throw err;
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Collect Atlas Search Indexes.
582
+ */
583
+ async *_collectSearchIndexes() {
584
+ try {
585
+ const projects = await this._getProjects();
586
+
587
+ for (const project of projects) {
588
+ try {
589
+ // Get clusters first
590
+ const clustersResponse = await this._makeRequest(`/groups/${project.id}/clusters`);
591
+ const clusters = clustersResponse.results || [];
592
+
593
+ for (const cluster of clusters) {
594
+ try {
595
+ const response = await this._makeRequest(
596
+ `/groups/${project.id}/clusters/${cluster.name}/fts/indexes`
597
+ );
598
+ const indexes = response || [];
599
+
600
+ for (const index of indexes) {
601
+ yield {
602
+ provider: 'mongodb-atlas',
603
+ accountId: this._organizationId || project.orgId,
604
+ region: cluster.providerSettings?.regionName || null,
605
+ service: 'search',
606
+ resourceType: 'mongodb-atlas.search.index',
607
+ resourceId: index.indexID,
608
+ name: index.name,
609
+ tags: {},
610
+ metadata: {
611
+ projectId: project.id,
612
+ projectName: project.name,
613
+ clusterName: cluster.name,
614
+ collectionName: index.collectionName,
615
+ database: index.database,
616
+ status: index.status
617
+ },
618
+ configuration: this._sanitize(index)
619
+ };
620
+ }
621
+ } catch (clusterErr) {
622
+ this.logger('debug', `No search indexes for cluster ${cluster.name}`, {
623
+ clusterName: cluster.name,
624
+ error: clusterErr.message
625
+ });
626
+ }
627
+ }
628
+ } catch (projectErr) {
629
+ this.logger('warn', `Failed to collect search indexes for project ${project.id}`, {
630
+ projectId: project.id,
631
+ error: projectErr.message
632
+ });
633
+ }
634
+ }
635
+
636
+ this.logger('info', `Collected MongoDB Atlas search indexes`);
637
+ } catch (err) {
638
+ this.logger('error', 'Failed to collect MongoDB Atlas search indexes', {
639
+ error: err.message,
640
+ stack: err.stack
641
+ });
642
+ throw err;
643
+ }
644
+ }
645
+
646
+ /**
647
+ * Collect Custom Database Roles.
648
+ */
649
+ async *_collectCustomRoles() {
650
+ try {
651
+ const projects = await this._getProjects();
652
+
653
+ for (const project of projects) {
654
+ try {
655
+ const response = await this._makeRequest(`/groups/${project.id}/customDBRoles/roles`);
656
+ const roles = response || [];
657
+
658
+ for (const role of roles) {
659
+ yield {
660
+ provider: 'mongodb-atlas',
661
+ accountId: this._organizationId || project.orgId,
662
+ region: null,
663
+ service: 'customroles',
664
+ resourceType: 'mongodb-atlas.customrole',
665
+ resourceId: `${project.id}/${role.roleName}`,
666
+ name: role.roleName,
667
+ tags: {},
668
+ metadata: {
669
+ projectId: project.id,
670
+ projectName: project.name
671
+ },
672
+ configuration: this._sanitize(role)
673
+ };
674
+ }
675
+ } catch (projectErr) {
676
+ this.logger('debug', `No custom roles in project ${project.id}`, {
677
+ projectId: project.id,
678
+ error: projectErr.message
679
+ });
680
+ }
681
+ }
682
+
683
+ this.logger('info', `Collected MongoDB Atlas custom roles`);
684
+ } catch (err) {
685
+ this.logger('error', 'Failed to collect MongoDB Atlas custom roles', {
686
+ error: err.message,
687
+ stack: err.stack
688
+ });
689
+ throw err;
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Collect Events (audit logs).
695
+ */
696
+ async *_collectEvents() {
697
+ try {
698
+ const projects = await this._getProjects();
699
+
700
+ for (const project of projects) {
701
+ try {
702
+ // Limit to recent events (last 7 days)
703
+ const minDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
704
+ const response = await this._makeRequest(
705
+ `/groups/${project.id}/events?minDate=${minDate}&itemsPerPage=100`
706
+ );
707
+ const events = response.results || [];
708
+
709
+ for (const event of events) {
710
+ yield {
711
+ provider: 'mongodb-atlas',
712
+ accountId: this._organizationId || project.orgId,
713
+ region: null,
714
+ service: 'events',
715
+ resourceType: 'mongodb-atlas.event',
716
+ resourceId: event.id,
717
+ name: event.eventTypeName,
718
+ tags: {},
719
+ metadata: {
720
+ projectId: project.id,
721
+ projectName: project.name,
722
+ eventTypeName: event.eventTypeName,
723
+ created: event.created
724
+ },
725
+ configuration: this._sanitize(event)
726
+ };
727
+ }
728
+ } catch (projectErr) {
729
+ this.logger('debug', `No recent events in project ${project.id}`, {
730
+ projectId: project.id,
731
+ error: projectErr.message
732
+ });
733
+ }
734
+ }
735
+
736
+ this.logger('info', `Collected MongoDB Atlas events`);
737
+ } catch (err) {
738
+ this.logger('error', 'Failed to collect MongoDB Atlas events', {
739
+ error: err.message,
740
+ stack: err.stack
741
+ });
742
+ throw err;
743
+ }
744
+ }
745
+
746
+ /**
747
+ * Sanitize configuration by removing sensitive data.
748
+ */
749
+ _sanitize(config) {
750
+ if (!config || typeof config !== 'object') return config;
751
+
752
+ const sanitized = { ...config };
753
+ const sensitiveFields = [
754
+ 'password',
755
+ 'privateKey',
756
+ 'apiKey',
757
+ 'connectionStrings',
758
+ 'mongoURI',
759
+ 'mongoURIUpdated',
760
+ 'mongoURIWithOptions'
761
+ ];
762
+
763
+ for (const field of sensitiveFields) {
764
+ if (field in sanitized) {
765
+ sanitized[field] = '***REDACTED***';
766
+ }
767
+ }
768
+
769
+ return sanitized;
770
+ }
771
+ }