sf-data-extractor 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -18,6 +18,9 @@
18
18
 
19
19
  - ![sf-data-extract](https://raw.githubusercontent.com/mchinnappan100/npmjs-images/main/sf-data-extract/sf-data-export-1.png)
20
20
 
21
+ ## Security check load plan
22
+ - [Load Plan Security](https://raw.githubusercontent.com/mchinnappan100/npmjs-images/main/sf-data-extract/load-plan-security.json))
23
+
21
24
  ## 📋 Prerequisites
22
25
 
23
26
  - Node.js >= 14.0.0
package/index.js CHANGED
@@ -59,6 +59,7 @@ async function main() {
59
59
 
60
60
  results.push({
61
61
  object: obj.object,
62
+ description: obj.description || '',
62
63
  query: obj.query,
63
64
  recordCount: recordCount,
64
65
  csvPath: csvPath,
@@ -71,6 +72,7 @@ async function main() {
71
72
  console.error(` ❌ Error extracting ${obj.object}:`, error.message);
72
73
  results.push({
73
74
  object: obj.object,
75
+ description: obj.description || '',
74
76
  query: obj.query,
75
77
  recordCount: 0,
76
78
  error: error.message,
@@ -158,6 +160,7 @@ async function createZipArchive(results, outputDir, zipPath) {
158
160
  function generateHTML(results, orgUsername) {
159
161
  const dataJson = JSON.stringify(results.map(r => ({
160
162
  object: r.object,
163
+ description: r.description,
161
164
  query: r.query,
162
165
  recordCount: r.recordCount,
163
166
  status: r.status,
@@ -269,6 +272,7 @@ function generateHTML(results, orgUsername) {
269
272
  const list = document.getElementById('objectList');
270
273
  list.innerHTML = objects.map((obj, idx) => {
271
274
  const statusColor = obj.status === 'success' ? 'bg-green-500' : 'bg-red-500';
275
+ const description = obj.description ? \`<div class="text-xs text-gray-500 mt-1 line-clamp-2" title="\${escapeHtml(obj.description)}">\${escapeHtml(obj.description)}</div>\` : '';
272
276
  return \`
273
277
  <div
274
278
  class="p-3 mb-2 bg-dark-bg hover:bg-slate-700 rounded-lg cursor-pointer transition-colors border border-dark-border hover:border-blue-500"
@@ -277,9 +281,10 @@ function generateHTML(results, orgUsername) {
277
281
  >
278
282
  <div class="flex items-start justify-between mb-1">
279
283
  <span class="font-medium text-sm text-white">\${obj.object}</span>
280
- <span class="w-2 h-2 rounded-full \${statusColor} mt-1"></span>
284
+ <span class="w-2 h-2 rounded-full \${statusColor} mt-1 flex-shrink-0"></span>
281
285
  </div>
282
- <div class="text-xs text-gray-400">
286
+ \${description}
287
+ <div class="text-xs text-gray-400 mt-1">
283
288
  \${obj.status === 'success' ? \`\${obj.recordCount} records\` : 'Error'}
284
289
  </div>
285
290
  </div>
@@ -303,14 +308,19 @@ function generateHTML(results, orgUsername) {
303
308
  const header = document.getElementById('objectHeader');
304
309
  const content = document.getElementById('contentArea');
305
310
 
306
- // Reset pagination
311
+ // Reset pagination and filtered data
307
312
  currentPage = 1;
308
313
  filteredData = obj.data || [];
309
314
 
315
+ const descriptionHTML = obj.description ? \`
316
+ <p class="text-sm text-gray-400 mt-2">\${escapeHtml(obj.description)}</p>
317
+ \` : '';
318
+
310
319
  header.innerHTML = \`
311
320
  <div class="flex items-center justify-between">
312
321
  <div>
313
322
  <h2 class="text-2xl font-bold text-white">\${obj.object}</h2>
323
+ \${descriptionHTML}
314
324
  <p class="text-sm text-gray-400 mt-1">\${obj.recordCount} records extracted</p>
315
325
  </div>
316
326
  <button
@@ -368,22 +378,20 @@ function generateHTML(results, orgUsername) {
368
378
  />
369
379
  </div>
370
380
  </div>
371
- <div class="overflow-x-auto">
372
- \${renderDataTable(obj.data)}
381
+ <div class="overflow-x-auto" id="tableContainer">
382
+ \${renderDataTable()}
373
383
  </div>
374
- <div id="paginationContainer"></div>
375
384
  </div>
376
385
  </div>
377
386
  \`;
378
387
  }
379
388
 
380
- function renderDataTable(data) {
381
- if (!data || data.length === 0) {
389
+ function renderDataTable() {
390
+ if (!filteredData || filteredData.length === 0) {
382
391
  return '<p class="text-gray-500 text-center py-8">No data available</p>';
383
392
  }
384
393
 
385
- filteredData = data;
386
- const headers = Object.keys(data[0]);
394
+ const headers = Object.keys(filteredData[0]);
387
395
  const totalPages = rowsPerPage === -1 ? 1 : Math.ceil(filteredData.length / rowsPerPage);
388
396
  const startIdx = rowsPerPage === -1 ? 0 : (currentPage - 1) * rowsPerPage;
389
397
  const endIdx = rowsPerPage === -1 ? filteredData.length : startIdx + rowsPerPage;
@@ -413,6 +421,7 @@ function generateHTML(results, orgUsername) {
413
421
  \`).join('')}
414
422
  </tbody>
415
423
  </table>
424
+ <div id="paginationContainer"></div>
416
425
  \`;
417
426
 
418
427
  // Render pagination after table
@@ -496,18 +505,24 @@ function generateHTML(results, orgUsername) {
496
505
  if (page < 1 || page > totalPages) return;
497
506
 
498
507
  currentPage = page;
499
- renderObjectDetails(currentObject);
508
+ const container = document.getElementById('tableContainer');
509
+ if (container) {
510
+ container.innerHTML = renderDataTable();
511
+ }
500
512
  }
501
513
 
502
514
  function changeRowsPerPage() {
503
515
  const select = document.getElementById('rowsPerPageSelect');
504
516
  rowsPerPage = parseInt(select.value);
505
517
  currentPage = 1;
506
- renderObjectDetails(currentObject);
518
+ const container = document.getElementById('tableContainer');
519
+ if (container) {
520
+ container.innerHTML = renderDataTable();
521
+ }
507
522
  }
508
523
 
509
524
  function sortTable(column) {
510
- if (!currentObject || !currentObject.data) return;
525
+ if (!currentObject || !filteredData) return;
511
526
 
512
527
  if (currentSort.column === column) {
513
528
  currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
@@ -516,7 +531,7 @@ function generateHTML(results, orgUsername) {
516
531
  currentSort.direction = 'asc';
517
532
  }
518
533
 
519
- currentObject.data.sort((a, b) => {
534
+ filteredData.sort((a, b) => {
520
535
  const aVal = a[column] || '';
521
536
  const bVal = b[column] || '';
522
537
 
@@ -524,7 +539,11 @@ function generateHTML(results, orgUsername) {
524
539
  return currentSort.direction === 'asc' ? compare : -compare;
525
540
  });
526
541
 
527
- renderObjectDetails(currentObject);
542
+ currentPage = 1;
543
+ const container = document.getElementById('tableContainer');
544
+ if (container) {
545
+ container.innerHTML = renderDataTable();
546
+ }
528
547
  }
529
548
 
530
549
  function filterTable() {
@@ -541,7 +560,10 @@ function generateHTML(results, orgUsername) {
541
560
  }
542
561
 
543
562
  currentPage = 1;
544
- renderObjectDetails(currentObject);
563
+ const container = document.getElementById('tableContainer');
564
+ if (container) {
565
+ container.innerHTML = renderDataTable();
566
+ }
545
567
  }
546
568
 
547
569
  function exportCSV() {
@@ -571,7 +593,8 @@ function generateHTML(results, orgUsername) {
571
593
  document.getElementById('objectSearch').addEventListener('keyup', (e) => {
572
594
  const search = e.target.value.toLowerCase();
573
595
  const filtered = data.filter(obj =>
574
- obj.object.toLowerCase().includes(search)
596
+ obj.object.toLowerCase().includes(search) ||
597
+ (obj.description && obj.description.toLowerCase().includes(search))
575
598
  );
576
599
  renderObjectList(filtered);
577
600
  });
@@ -0,0 +1,212 @@
1
+ [
2
+ {
3
+ "object": "EntityDefinition",
4
+ "compositeKeys": ["DurableId"],
5
+ "description": "Identifies sharing model settings for all customizable objects to detect overly permissive configurations",
6
+ "query": "SELECT DurableId, QualifiedApiName, InternalSharingModel, ExternalSharingModel FROM EntityDefinition WHERE IsCustomizable = true",
7
+ "fieldMappings": {
8
+ "DurableId": "DurableId",
9
+ "QualifiedApiName": "QualifiedApiName",
10
+ "InternalSharingModel": "InternalSharingModel",
11
+ "ExternalSharingModel": "ExternalSharingModel"
12
+ }
13
+ },
14
+ {
15
+ "object": "Group",
16
+ "compositeKeys": ["Id"],
17
+ "description": "Retrieves organization-wide default settings to review baseline sharing rules",
18
+ "query": "SELECT Id, Name, Type FROM Group WHERE Type = 'Organization' LIMIT 1",
19
+ "fieldMappings": {
20
+ "Id": "Id",
21
+ "Name": "Name",
22
+ "Type": "Type"
23
+ }
24
+ },
25
+ {
26
+ "object": "PermissionSet",
27
+ "compositeKeys": ["Id"],
28
+ "description": "Lists all custom permission sets including those with elevated system permissions like Modify All Data and View All Data",
29
+ "query": "SELECT Id, Name, Label, IsOwnedByProfile, PermissionsModifyAllData, PermissionsViewAllData, PermissionsManageUsers FROM PermissionSet WHERE IsOwnedByProfile = false",
30
+ "fieldMappings": {
31
+ "Id": "Id",
32
+ "Name": "Name",
33
+ "Label": "Label",
34
+ "IsOwnedByProfile": "IsOwnedByProfile",
35
+ "PermissionsModifyAllData": "PermissionsModifyAllData",
36
+ "PermissionsViewAllData": "PermissionsViewAllData",
37
+ "PermissionsManageUsers": "PermissionsManageUsers"
38
+ }
39
+ },
40
+ {
41
+ "object": "FieldPermissions",
42
+ "compositeKeys": ["ParentId", "Field"],
43
+ "description": "Identifies field-level edit permissions on sensitive security objects (User, Profile, PermissionSet) that could enable privilege escalation",
44
+ "query": "SELECT ParentId, Field, PermissionsEdit, PermissionsRead, SobjectType FROM FieldPermissions WHERE PermissionsEdit = true AND SobjectType IN ('User','Profile','PermissionSet')",
45
+ "fieldMappings": {
46
+ "ParentId": "ParentId",
47
+ "Field": "Field",
48
+ "PermissionsEdit": "PermissionsEdit",
49
+ "PermissionsRead": "PermissionsRead",
50
+ "SobjectType": "SobjectType"
51
+ }
52
+ },
53
+ {
54
+ "object": "Profile",
55
+ "compositeKeys": ["Id"],
56
+ "description": "Lists all standard user profiles with their system-level permissions to identify overly privileged profiles",
57
+ "query": "SELECT Id, Name, UserLicense.Name, PermissionsModifyAllData, PermissionsViewAllData, PermissionsManageUsers, PermissionsApiEnabled FROM Profile WHERE UserType = 'Standard'",
58
+ "fieldMappings": {
59
+ "Id": "Id",
60
+ "Name": "Name",
61
+ "UserLicenseName": "UserLicense.Name",
62
+ "PermissionsModifyAllData": "PermissionsModifyAllData",
63
+ "PermissionsViewAllData": "PermissionsViewAllData",
64
+ "PermissionsManageUsers": "PermissionsManageUsers",
65
+ "PermissionsApiEnabled": "PermissionsApiEnabled"
66
+ }
67
+ },
68
+ {
69
+ "object": "User",
70
+ "compositeKeys": ["Username"],
71
+ "description": "Identifies active users who haven't logged in for 90+ days, indicating potential stale accounts that should be deactivated",
72
+ "query": "SELECT Id, Username, Email, ProfileId, IsActive, LastLoginDate, CreatedDate FROM User WHERE IsActive = true AND LastLoginDate < LAST_N_DAYS:90",
73
+ "fieldMappings": {
74
+ "Id": "Id",
75
+ "Username": "Username",
76
+ "Email": "Email",
77
+ "ProfileId": "ProfileId",
78
+ "IsActive": "IsActive",
79
+ "LastLoginDate": "LastLoginDate",
80
+ "CreatedDate": "CreatedDate"
81
+ }
82
+ },
83
+ {
84
+ "object": "PermissionSetAssignment",
85
+ "compositeKeys": ["Id"],
86
+ "description": "Tracks users assigned permission sets with Modify All Data or View All Data permissions for privilege monitoring",
87
+ "query": "SELECT Id, PermissionSetId, AssigneeId, PermissionSet.Name, Assignee.Username FROM PermissionSetAssignment WHERE PermissionSet.PermissionsModifyAllData = true OR PermissionSet.PermissionsViewAllData = true",
88
+ "fieldMappings": {
89
+ "Id": "Id",
90
+ "PermissionSetId": "PermissionSetId",
91
+ "AssigneeId": "AssigneeId",
92
+ "PermissionSetName": "PermissionSet.Name",
93
+ "AssigneeUsername": "Assignee.Username"
94
+ }
95
+ },
96
+ {
97
+ "object": "LoginHistory",
98
+ "compositeKeys": ["Id"],
99
+ "description": "Captures all login attempts from the last 30 days to detect potential brute force attacks or unauthorized access attempts",
100
+ "query": "SELECT Id, UserId, LoginTime, Status, SourceIp, Application FROM LoginHistory WHERE LoginTime = LAST_N_DAYS:30 ORDER BY LoginTime DESC LIMIT 10000",
101
+ "fieldMappings": {
102
+ "Id": "Id",
103
+ "UserId": "UserId",
104
+ "LoginTime": "LoginTime",
105
+ "Status": "Status",
106
+ "SourceIp": "SourceIp",
107
+ "Application": "Application"
108
+ }
109
+ },
110
+ {
111
+ "object": "SetupAuditTrail",
112
+ "compositeKeys": ["Id"],
113
+ "description": "Monitors all configuration changes in the last 90 days to track security-related modifications and user management activities",
114
+ "query": "SELECT Id, Action, Section, CreatedById, CreatedBy.Username, CreatedDate FROM SetupAuditTrail WHERE CreatedDate = LAST_N_DAYS:90 ORDER BY CreatedDate DESC",
115
+ "fieldMappings": {
116
+ "Id": "Id",
117
+ "Action": "Action",
118
+ "Section": "Section",
119
+ "CreatedById": "CreatedById",
120
+ "CreatedByUsername": "CreatedBy.Username",
121
+ "CreatedDate": "CreatedDate"
122
+ }
123
+ },
124
+ {
125
+ "object": "ObjectPermissions",
126
+ "compositeKeys": ["ParentId", "SobjectType"],
127
+ "description": "Identifies profiles and permission sets with Modify All Records permission which bypasses sharing rules",
128
+ "query": "SELECT ParentId, SobjectType, PermissionsCreate, PermissionsRead, PermissionsEdit, PermissionsDelete, PermissionsViewAllRecords, PermissionsModifyAllRecords FROM ObjectPermissions WHERE PermissionsModifyAllRecords = true",
129
+ "fieldMappings": {
130
+ "ParentId": "ParentId",
131
+ "SobjectType": "SobjectType",
132
+ "PermissionsCreate": "PermissionsCreate",
133
+ "PermissionsRead": "PermissionsRead",
134
+ "PermissionsEdit": "PermissionsEdit",
135
+ "PermissionsDelete": "PermissionsDelete",
136
+ "PermissionsViewAllRecords": "PermissionsViewAllRecords",
137
+ "PermissionsModifyAllRecords": "PermissionsModifyAllRecords"
138
+ }
139
+ },
140
+ {
141
+ "object": "UserRole",
142
+ "compositeKeys": ["Id"],
143
+ "description": "Maps the role hierarchy to understand data visibility through role-based sharing",
144
+ "query": "SELECT Id, Name, ParentRoleId FROM UserRole",
145
+ "fieldMappings": {
146
+ "Id": "Id",
147
+ "Name": "Name",
148
+ "ParentRoleId": "ParentRoleId"
149
+ }
150
+ },
151
+ {
152
+ "object": "User",
153
+ "compositeKeys": ["Username"],
154
+ "description": "Lists all users with elevated system permissions (Modify All Data or View All Data) for privileged access review",
155
+ "query": "SELECT Id, Username, Email, ProfileId, Profile.Name, UserRoleId, IsActive FROM User WHERE Profile.PermissionsModifyAllData = true OR Profile.PermissionsViewAllData = true",
156
+ "fieldMappings": {
157
+ "Id": "Id",
158
+ "Username": "Username",
159
+ "Email": "Email",
160
+ "ProfileId": "ProfileId",
161
+ "ProfileName": "Profile.Name",
162
+ "UserRoleId": "UserRoleId",
163
+ "IsActive": "IsActive"
164
+ }
165
+ },
166
+ {
167
+ "object": "NetworkMemberGroup",
168
+ "compositeKeys": ["Id"],
169
+ "description": "Reviews Experience Cloud community member groups for proper external user access controls",
170
+ "query": "SELECT Id, NetworkId, ParentId FROM NetworkMemberGroup",
171
+ "fieldMappings": {
172
+ "Id": "Id",
173
+ "NetworkId": "NetworkId",
174
+ "ParentId": "ParentId"
175
+ }
176
+ },
177
+ {
178
+ "object": "AuthSession",
179
+ "compositeKeys": ["Id"],
180
+ "description": "Monitors active authentication sessions from the last 7 days to detect suspicious login patterns or session anomalies",
181
+ "query": "SELECT Id, UsersId, LoginType, SourceIp, LastModifiedDate FROM AuthSession WHERE LastModifiedDate = LAST_N_DAYS:7",
182
+ "fieldMappings": {
183
+ "Id": "Id",
184
+ "UsersId": "UsersId",
185
+ "LoginType": "LoginType",
186
+ "SourceIp": "SourceIp",
187
+ "LastModifiedDate": "LastModifiedDate"
188
+ }
189
+ },
190
+
191
+ {
192
+ "object": "User",
193
+ "compositeKeys": ["Id"],
194
+ "description": "Lists all active users assigned the System Administrator profile",
195
+ "query": "SELECT Id, Name, Username, Email, Profile.Name, IsActive FROM User WHERE IsActive = true AND Profile.Name = 'System Administrator'",
196
+ "fieldMappings": {
197
+ "Id": "Id",
198
+ "Name": "Name",
199
+ "Username": "Username",
200
+ "Email": "Email",
201
+ "IsActive": "IsActive",
202
+ "ProfileId": {
203
+ "lookup": {
204
+ "object": "Profile",
205
+ "key": "Name",
206
+ "field": "Profile.Name"
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sf-data-extractor",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Extract Salesforce data based on load plan and generate beautiful HTML reports",
5
5
  "main": "index.js",
6
6
  "bin": {