@strapi/admin 5.52.3 → 5.53.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 (109) hide show
  1. package/dist/admin/admin/src/translations/en.json.js +18 -0
  2. package/dist/admin/admin/src/translations/en.json.js.map +1 -1
  3. package/dist/admin/admin/src/translations/en.json.mjs +18 -0
  4. package/dist/admin/admin/src/translations/en.json.mjs.map +1 -1
  5. package/dist/admin/admin/src/utils/getFetchClient.js +118 -43
  6. package/dist/admin/admin/src/utils/getFetchClient.js.map +1 -1
  7. package/dist/admin/admin/src/utils/getFetchClient.mjs +118 -45
  8. package/dist/admin/admin/src/utils/getFetchClient.mjs.map +1 -1
  9. package/dist/admin/ee/admin/src/constants.js +6 -0
  10. package/dist/admin/ee/admin/src/constants.js.map +1 -1
  11. package/dist/admin/ee/admin/src/constants.mjs +6 -0
  12. package/dist/admin/ee/admin/src/constants.mjs.map +1 -1
  13. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/ListPage.js +31 -3
  14. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/ListPage.js.map +1 -1
  15. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/ListPage.mjs +33 -5
  16. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/ListPage.mjs.map +1 -1
  17. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/components/ExportCard.js +177 -0
  18. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/components/ExportCard.js.map +1 -0
  19. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/components/ExportCard.mjs +175 -0
  20. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/components/ExportCard.mjs.map +1 -0
  21. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/hooks/useExportAuditLogs.js +173 -0
  22. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/hooks/useExportAuditLogs.js.map +1 -0
  23. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/hooks/useExportAuditLogs.mjs +151 -0
  24. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/hooks/useExportAuditLogs.mjs.map +1 -0
  25. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.js +10 -1
  26. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.js.map +1 -1
  27. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.mjs +10 -1
  28. package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.mjs.map +1 -1
  29. package/dist/admin/index.js +2 -0
  30. package/dist/admin/index.js.map +1 -1
  31. package/dist/admin/index.mjs +1 -1
  32. package/dist/admin/shared/utils/audit-log-export.js +16 -0
  33. package/dist/admin/shared/utils/audit-log-export.js.map +1 -0
  34. package/dist/admin/shared/utils/audit-log-export.mjs +9 -0
  35. package/dist/admin/shared/utils/audit-log-export.mjs.map +1 -0
  36. package/dist/admin/src/utils/getFetchClient.d.ts +9 -2
  37. package/dist/admin/tests/store.d.ts +4 -0
  38. package/dist/admin/tests/utils.d.ts +4 -0
  39. package/dist/ee/admin/src/constants.d.ts +4 -0
  40. package/dist/ee/admin/src/pages/SettingsPage/pages/AuditLogs/components/ExportCard.d.ts +10 -0
  41. package/dist/ee/admin/src/pages/SettingsPage/pages/AuditLogs/hooks/useExportAuditLogs.d.ts +18 -0
  42. package/dist/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.d.ts +9 -0
  43. package/dist/ee/server/src/audit-logs/controllers/audit-logs.d.ts +1 -0
  44. package/dist/ee/server/src/audit-logs/controllers/audit-logs.d.ts.map +1 -1
  45. package/dist/ee/server/src/audit-logs/routes/audit-logs.d.ts.map +1 -1
  46. package/dist/ee/server/src/audit-logs/services/audit-logs.d.ts +24 -3
  47. package/dist/ee/server/src/audit-logs/services/audit-logs.d.ts.map +1 -1
  48. package/dist/ee/server/src/audit-logs/services/lifecycles.d.ts +8 -2
  49. package/dist/ee/server/src/audit-logs/services/lifecycles.d.ts.map +1 -1
  50. package/dist/ee/server/src/audit-logs/utils/csv.d.ts +5 -0
  51. package/dist/ee/server/src/audit-logs/utils/csv.d.ts.map +1 -0
  52. package/dist/ee/server/src/audit-logs/utils/export-token.d.ts +5 -0
  53. package/dist/ee/server/src/audit-logs/utils/export-token.d.ts.map +1 -0
  54. package/dist/ee/server/src/audit-logs/utils.d.ts +10 -0
  55. package/dist/ee/server/src/audit-logs/utils.d.ts.map +1 -0
  56. package/dist/ee/server/src/audit-logs/validation/audit-logs.d.ts +2 -0
  57. package/dist/ee/server/src/audit-logs/validation/audit-logs.d.ts.map +1 -1
  58. package/dist/ee/server/src/config/admin-actions.d.ts.map +1 -1
  59. package/dist/ee/server/src/index.d.ts +1 -0
  60. package/dist/ee/server/src/index.d.ts.map +1 -1
  61. package/dist/server/ee/server/src/audit-logs/controllers/audit-logs.js +50 -0
  62. package/dist/server/ee/server/src/audit-logs/controllers/audit-logs.js.map +1 -1
  63. package/dist/server/ee/server/src/audit-logs/controllers/audit-logs.mjs +51 -1
  64. package/dist/server/ee/server/src/audit-logs/controllers/audit-logs.mjs.map +1 -1
  65. package/dist/server/ee/server/src/audit-logs/routes/audit-logs.js +13 -4
  66. package/dist/server/ee/server/src/audit-logs/routes/audit-logs.js.map +1 -1
  67. package/dist/server/ee/server/src/audit-logs/routes/audit-logs.mjs +13 -4
  68. package/dist/server/ee/server/src/audit-logs/routes/audit-logs.mjs.map +1 -1
  69. package/dist/server/ee/server/src/audit-logs/services/audit-logs.js +201 -7
  70. package/dist/server/ee/server/src/audit-logs/services/audit-logs.js.map +1 -1
  71. package/dist/server/ee/server/src/audit-logs/services/audit-logs.mjs +201 -8
  72. package/dist/server/ee/server/src/audit-logs/services/audit-logs.mjs.map +1 -1
  73. package/dist/server/ee/server/src/audit-logs/services/lifecycles.js +119 -34
  74. package/dist/server/ee/server/src/audit-logs/services/lifecycles.js.map +1 -1
  75. package/dist/server/ee/server/src/audit-logs/services/lifecycles.mjs +119 -34
  76. package/dist/server/ee/server/src/audit-logs/services/lifecycles.mjs.map +1 -1
  77. package/dist/server/ee/server/src/audit-logs/utils/csv.js +33 -0
  78. package/dist/server/ee/server/src/audit-logs/utils/csv.js.map +1 -0
  79. package/dist/server/ee/server/src/audit-logs/utils/csv.mjs +29 -0
  80. package/dist/server/ee/server/src/audit-logs/utils/csv.mjs.map +1 -0
  81. package/dist/server/ee/server/src/audit-logs/utils/export-token.js +33 -0
  82. package/dist/server/ee/server/src/audit-logs/utils/export-token.js.map +1 -0
  83. package/dist/server/ee/server/src/audit-logs/utils/export-token.mjs +29 -0
  84. package/dist/server/ee/server/src/audit-logs/utils/export-token.mjs.map +1 -0
  85. package/dist/server/ee/server/src/audit-logs/utils.js +14 -0
  86. package/dist/server/ee/server/src/audit-logs/utils.js.map +1 -0
  87. package/dist/server/ee/server/src/audit-logs/utils.mjs +12 -0
  88. package/dist/server/ee/server/src/audit-logs/utils.mjs.map +1 -0
  89. package/dist/server/ee/server/src/audit-logs/validation/audit-logs.js +11 -0
  90. package/dist/server/ee/server/src/audit-logs/validation/audit-logs.js.map +1 -1
  91. package/dist/server/ee/server/src/audit-logs/validation/audit-logs.mjs +11 -1
  92. package/dist/server/ee/server/src/audit-logs/validation/audit-logs.mjs.map +1 -1
  93. package/dist/server/ee/server/src/config/admin-actions.js +8 -0
  94. package/dist/server/ee/server/src/config/admin-actions.js.map +1 -1
  95. package/dist/server/ee/server/src/config/admin-actions.mjs +8 -0
  96. package/dist/server/ee/server/src/config/admin-actions.mjs.map +1 -1
  97. package/dist/server/ee/server/src/index.js +10 -0
  98. package/dist/server/ee/server/src/index.js.map +1 -1
  99. package/dist/server/ee/server/src/index.mjs +10 -0
  100. package/dist/server/ee/server/src/index.mjs.map +1 -1
  101. package/dist/server/shared/utils/audit-log-export.js +22 -0
  102. package/dist/server/shared/utils/audit-log-export.js.map +1 -0
  103. package/dist/server/shared/utils/audit-log-export.mjs +12 -0
  104. package/dist/server/shared/utils/audit-log-export.mjs.map +1 -0
  105. package/dist/shared/contracts/audit-logs.d.ts +15 -1
  106. package/dist/shared/contracts/audit-logs.d.ts.map +1 -1
  107. package/dist/shared/utils/audit-log-export.d.ts +9 -0
  108. package/dist/shared/utils/audit-log-export.d.ts.map +1 -0
  109. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"audit-logs.js","sources":["../../../../../../../ee/server/src/audit-logs/routes/audit-logs.ts"],"sourcesContent":["import { enableFeatureMiddleware } from '../../routes/utils';\n\nconst getRouteConfig = () => ({\n middlewares: [enableFeatureMiddleware('audit-logs')],\n policies: [\n 'admin::isAuthenticatedAdmin',\n {\n name: 'admin::hasPermissions',\n config: {\n actions: ['admin::audit-logs.read'],\n },\n },\n ],\n});\n\nexport default {\n type: 'admin',\n routes: [\n {\n method: 'GET',\n path: '/audit-logs',\n handler: 'audit-logs.findMany',\n config: getRouteConfig(),\n },\n {\n method: 'GET',\n path: '/audit-logs/users',\n handler: 'audit-logs.findManyUsers',\n config: getRouteConfig(),\n },\n {\n method: 'GET',\n path: '/audit-logs/:id',\n handler: 'audit-logs.findOne',\n config: getRouteConfig(),\n },\n ],\n};\n"],"names":["getRouteConfig","middlewares","enableFeatureMiddleware","policies","name","config","actions","type","routes","method","path","handler"],"mappings":";;;;AAEA,MAAMA,cAAAA,GAAiB,KAAO;QAC5BC,WAAAA,EAAa;YAACC,6BAAAA,CAAwB,YAAA;AAAc,SAAA;QACpDC,QAAAA,EAAU;AACR,YAAA,6BAAA;AACA,YAAA;gBACEC,IAAAA,EAAM,uBAAA;gBACNC,MAAAA,EAAQ;oBACNC,OAAAA,EAAS;AAAC,wBAAA;AAAyB;AACrC;AACF;AACD;KACH,CAAA;AAEA,sBAAe;IACbC,IAAAA,EAAM,OAAA;IACNC,MAAAA,EAAQ;AACN,QAAA;YACEC,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,aAAA;YACNC,OAAAA,EAAS,qBAAA;YACTN,MAAAA,EAAQL,cAAAA;AACV,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,mBAAA;YACNC,OAAAA,EAAS,0BAAA;YACTN,MAAAA,EAAQL,cAAAA;AACV,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,iBAAA;YACNC,OAAAA,EAAS,oBAAA;YACTN,MAAAA,EAAQL,cAAAA;AACV;AACD;AACH,CAAA;;;;"}
1
+ {"version":3,"file":"audit-logs.js","sources":["../../../../../../../ee/server/src/audit-logs/routes/audit-logs.ts"],"sourcesContent":["import { enableFeatureMiddleware } from '../../routes/utils';\n\nconst getRouteConfig = (actions: string[] = ['admin::audit-logs.read']) => ({\n middlewares: [enableFeatureMiddleware('audit-logs')],\n policies: [\n 'admin::isAuthenticatedAdmin',\n {\n name: 'admin::hasPermissions',\n config: {\n actions,\n },\n },\n ],\n});\n\nexport default {\n type: 'admin',\n routes: [\n {\n method: 'GET',\n path: '/audit-logs',\n handler: 'audit-logs.findMany',\n config: getRouteConfig(),\n },\n {\n method: 'GET',\n path: '/audit-logs/users',\n handler: 'audit-logs.findManyUsers',\n config: getRouteConfig(),\n },\n {\n method: 'GET',\n path: '/audit-logs/export',\n handler: 'audit-logs.export',\n config: getRouteConfig(['admin::audit-logs.read', 'admin::audit-logs.export']),\n },\n {\n method: 'GET',\n path: '/audit-logs/:id',\n handler: 'audit-logs.findOne',\n config: getRouteConfig(),\n },\n ],\n};\n"],"names":["getRouteConfig","actions","middlewares","enableFeatureMiddleware","policies","name","config","type","routes","method","path","handler"],"mappings":";;;;AAEA,MAAMA,cAAAA,GAAiB,CAACC,OAAAA,GAAoB;AAAC,IAAA;AAAyB,CAAA,IAAM;QAC1EC,WAAAA,EAAa;YAACC,6BAAAA,CAAwB,YAAA;AAAc,SAAA;QACpDC,QAAAA,EAAU;AACR,YAAA,6BAAA;AACA,YAAA;gBACEC,IAAAA,EAAM,uBAAA;gBACNC,MAAAA,EAAQ;AACNL,oBAAAA;AACF;AACF;AACD;KACH,CAAA;AAEA,sBAAe;IACbM,IAAAA,EAAM,OAAA;IACNC,MAAAA,EAAQ;AACN,QAAA;YACEC,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,aAAA;YACNC,OAAAA,EAAS,qBAAA;YACTL,MAAAA,EAAQN,cAAAA;AACV,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,mBAAA;YACNC,OAAAA,EAAS,0BAAA;YACTL,MAAAA,EAAQN,cAAAA;AACV,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,oBAAA;YACNC,OAAAA,EAAS,mBAAA;AACTL,YAAAA,MAAAA,EAAQN,cAAAA,CAAe;AAAC,gBAAA,wBAAA;AAA0B,gBAAA;AAA2B,aAAA;AAC/E,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,iBAAA;YACNC,OAAAA,EAAS,oBAAA;YACTL,MAAAA,EAAQN,cAAAA;AACV;AACD;AACH,CAAA;;;;"}
@@ -1,6 +1,8 @@
1
1
  import { enableFeatureMiddleware } from '../../routes/utils.mjs';
2
2
 
3
- const getRouteConfig = ()=>({
3
+ const getRouteConfig = (actions = [
4
+ 'admin::audit-logs.read'
5
+ ])=>({
4
6
  middlewares: [
5
7
  enableFeatureMiddleware('audit-logs')
6
8
  ],
@@ -9,9 +11,7 @@ const getRouteConfig = ()=>({
9
11
  {
10
12
  name: 'admin::hasPermissions',
11
13
  config: {
12
- actions: [
13
- 'admin::audit-logs.read'
14
- ]
14
+ actions
15
15
  }
16
16
  }
17
17
  ]
@@ -31,6 +31,15 @@ var auditLogsRoutes = {
31
31
  handler: 'audit-logs.findManyUsers',
32
32
  config: getRouteConfig()
33
33
  },
34
+ {
35
+ method: 'GET',
36
+ path: '/audit-logs/export',
37
+ handler: 'audit-logs.export',
38
+ config: getRouteConfig([
39
+ 'admin::audit-logs.read',
40
+ 'admin::audit-logs.export'
41
+ ])
42
+ },
34
43
  {
35
44
  method: 'GET',
36
45
  path: '/audit-logs/:id',
@@ -1 +1 @@
1
- {"version":3,"file":"audit-logs.mjs","sources":["../../../../../../../ee/server/src/audit-logs/routes/audit-logs.ts"],"sourcesContent":["import { enableFeatureMiddleware } from '../../routes/utils';\n\nconst getRouteConfig = () => ({\n middlewares: [enableFeatureMiddleware('audit-logs')],\n policies: [\n 'admin::isAuthenticatedAdmin',\n {\n name: 'admin::hasPermissions',\n config: {\n actions: ['admin::audit-logs.read'],\n },\n },\n ],\n});\n\nexport default {\n type: 'admin',\n routes: [\n {\n method: 'GET',\n path: '/audit-logs',\n handler: 'audit-logs.findMany',\n config: getRouteConfig(),\n },\n {\n method: 'GET',\n path: '/audit-logs/users',\n handler: 'audit-logs.findManyUsers',\n config: getRouteConfig(),\n },\n {\n method: 'GET',\n path: '/audit-logs/:id',\n handler: 'audit-logs.findOne',\n config: getRouteConfig(),\n },\n ],\n};\n"],"names":["getRouteConfig","middlewares","enableFeatureMiddleware","policies","name","config","actions","type","routes","method","path","handler"],"mappings":";;AAEA,MAAMA,cAAAA,GAAiB,KAAO;QAC5BC,WAAAA,EAAa;YAACC,uBAAAA,CAAwB,YAAA;AAAc,SAAA;QACpDC,QAAAA,EAAU;AACR,YAAA,6BAAA;AACA,YAAA;gBACEC,IAAAA,EAAM,uBAAA;gBACNC,MAAAA,EAAQ;oBACNC,OAAAA,EAAS;AAAC,wBAAA;AAAyB;AACrC;AACF;AACD;KACH,CAAA;AAEA,sBAAe;IACbC,IAAAA,EAAM,OAAA;IACNC,MAAAA,EAAQ;AACN,QAAA;YACEC,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,aAAA;YACNC,OAAAA,EAAS,qBAAA;YACTN,MAAAA,EAAQL,cAAAA;AACV,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,mBAAA;YACNC,OAAAA,EAAS,0BAAA;YACTN,MAAAA,EAAQL,cAAAA;AACV,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,iBAAA;YACNC,OAAAA,EAAS,oBAAA;YACTN,MAAAA,EAAQL,cAAAA;AACV;AACD;AACH,CAAA;;;;"}
1
+ {"version":3,"file":"audit-logs.mjs","sources":["../../../../../../../ee/server/src/audit-logs/routes/audit-logs.ts"],"sourcesContent":["import { enableFeatureMiddleware } from '../../routes/utils';\n\nconst getRouteConfig = (actions: string[] = ['admin::audit-logs.read']) => ({\n middlewares: [enableFeatureMiddleware('audit-logs')],\n policies: [\n 'admin::isAuthenticatedAdmin',\n {\n name: 'admin::hasPermissions',\n config: {\n actions,\n },\n },\n ],\n});\n\nexport default {\n type: 'admin',\n routes: [\n {\n method: 'GET',\n path: '/audit-logs',\n handler: 'audit-logs.findMany',\n config: getRouteConfig(),\n },\n {\n method: 'GET',\n path: '/audit-logs/users',\n handler: 'audit-logs.findManyUsers',\n config: getRouteConfig(),\n },\n {\n method: 'GET',\n path: '/audit-logs/export',\n handler: 'audit-logs.export',\n config: getRouteConfig(['admin::audit-logs.read', 'admin::audit-logs.export']),\n },\n {\n method: 'GET',\n path: '/audit-logs/:id',\n handler: 'audit-logs.findOne',\n config: getRouteConfig(),\n },\n ],\n};\n"],"names":["getRouteConfig","actions","middlewares","enableFeatureMiddleware","policies","name","config","type","routes","method","path","handler"],"mappings":";;AAEA,MAAMA,cAAAA,GAAiB,CAACC,OAAAA,GAAoB;AAAC,IAAA;AAAyB,CAAA,IAAM;QAC1EC,WAAAA,EAAa;YAACC,uBAAAA,CAAwB,YAAA;AAAc,SAAA;QACpDC,QAAAA,EAAU;AACR,YAAA,6BAAA;AACA,YAAA;gBACEC,IAAAA,EAAM,uBAAA;gBACNC,MAAAA,EAAQ;AACNL,oBAAAA;AACF;AACF;AACD;KACH,CAAA;AAEA,sBAAe;IACbM,IAAAA,EAAM,OAAA;IACNC,MAAAA,EAAQ;AACN,QAAA;YACEC,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,aAAA;YACNC,OAAAA,EAAS,qBAAA;YACTL,MAAAA,EAAQN,cAAAA;AACV,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,mBAAA;YACNC,OAAAA,EAAS,0BAAA;YACTL,MAAAA,EAAQN,cAAAA;AACV,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,oBAAA;YACNC,OAAAA,EAAS,mBAAA;AACTL,YAAAA,MAAAA,EAAQN,cAAAA,CAAe;AAAC,gBAAA,wBAAA;AAA0B,gBAAA;AAA2B,aAAA;AAC/E,SAAA;AACA,QAAA;YACES,MAAAA,EAAQ,KAAA;YACRC,IAAAA,EAAM,iBAAA;YACNC,OAAAA,EAAS,oBAAA;YACTL,MAAAA,EAAQN,cAAAA;AACV;AACD;AACH,CAAA;;;;"}
@@ -1,18 +1,93 @@
1
1
  'use strict';
2
2
 
3
+ var stream = require('stream');
4
+ var auditLogExport = require('../../../../../shared/utils/audit-log-export.js');
5
+ var utils = require('../utils.js');
6
+ var csv = require('../utils/csv.js');
7
+ var exportToken = require('../utils/export-token.js');
8
+
9
+ const EXPORT_CSV_COLUMNS = [
10
+ 'id',
11
+ 'action',
12
+ 'date',
13
+ 'user_id',
14
+ 'user_email',
15
+ 'user_display_name',
16
+ 'origin',
17
+ 'payload'
18
+ ];
19
+ const EXPORT_USER_COLUMNS = [
20
+ 'id',
21
+ 'email',
22
+ 'username',
23
+ 'firstname',
24
+ 'lastname'
25
+ ];
26
+ const EXPORT_BATCH_SIZE = 5000;
3
27
  const getSanitizedUser = (user)=>{
4
- let displayName = user.email;
5
- if (user.username) {
6
- displayName = user.username;
7
- } else if (user.firstname && user.lastname) {
8
- displayName = `${user.firstname} ${user.lastname}`;
9
- }
10
28
  return {
11
29
  id: user.id,
12
30
  email: user.email,
13
- displayName
31
+ displayName: utils.getDisplayName(user)
14
32
  };
15
33
  };
34
+ const serializeCsvPayload = (payload)=>{
35
+ if (payload === null || payload === undefined) {
36
+ return null;
37
+ }
38
+ return typeof payload === 'string' ? payload : JSON.stringify(payload);
39
+ };
40
+ const parseCsvPayload = (payload)=>{
41
+ if (typeof payload === 'string') {
42
+ try {
43
+ return JSON.parse(payload);
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ return typeof payload === 'object' ? payload : null;
49
+ };
50
+ const getExportSecret = (strapi)=>{
51
+ const secret = strapi.config.get('admin.auth.secret');
52
+ if (typeof secret !== 'string' || secret.length === 0) {
53
+ throw new Error('The audit logs export requires admin.auth.secret to be set in config/admin.js.');
54
+ }
55
+ return secret;
56
+ };
57
+ const isTrustedExportContinuation = (strapi, query)=>{
58
+ return query.cursor !== undefined && query.until !== undefined && exportToken.verifyExportToken(getExportSecret(strapi), query.token, query.until, query.filters);
59
+ };
60
+ const buildExportWhere = (filtersWhere, untilId, afterId)=>({
61
+ $and: [
62
+ filtersWhere ?? {},
63
+ {
64
+ id: {
65
+ $lte: untilId
66
+ }
67
+ },
68
+ ...afterId > 0 ? [
69
+ {
70
+ id: {
71
+ $gt: afterId
72
+ }
73
+ }
74
+ ] : []
75
+ ]
76
+ });
77
+ const toCsvLine = (row)=>{
78
+ const user = row.user ? getSanitizedUser(row.user) : null;
79
+ const { payload } = row;
80
+ return csv.serializeCsvLine([
81
+ row.id,
82
+ row.action,
83
+ row.date instanceof Date ? row.date.toISOString() : row.date,
84
+ user?.id ?? null,
85
+ user?.email ?? null,
86
+ user?.displayName ?? null,
87
+ parseCsvPayload(payload)?.origin ?? null,
88
+ serializeCsvPayload(payload)
89
+ ]);
90
+ };
16
91
  /**
17
92
  * @description
18
93
  * Manages audit logs interaction with the database. Accessible via strapi.get('audit-logs')
@@ -72,6 +147,124 @@ const getSanitizedUser = (user)=>{
72
147
  pagination
73
148
  };
74
149
  },
150
+ async isExportTooLarge (query) {
151
+ const maxRows = strapi.config.get('admin.auditLogs.exportMaxRows', auditLogExport.AUDIT_LOGS_EXPORT_DEFAULT_MAX_ROWS);
152
+ const { where } = strapi.get('query-params').transform('admin::audit-log', {
153
+ filters: query.filters ?? undefined
154
+ });
155
+ const beyondCap = await strapi.db.query('admin::audit-log').findMany({
156
+ select: [
157
+ 'id'
158
+ ],
159
+ where: where ?? {},
160
+ orderBy: {
161
+ id: 'asc'
162
+ },
163
+ offset: maxRows,
164
+ limit: 1
165
+ });
166
+ return beyondCap.length > 0;
167
+ },
168
+ isTrustedContinuation (query) {
169
+ return isTrustedExportContinuation(strapi, query);
170
+ },
171
+ async createExportStream (query) {
172
+ getExportSecret(strapi);
173
+ const { cursor, pageSize, until, token, page: _page, sort: _sort, ...filtersQuery } = query;
174
+ const configuredPartSize = strapi.config.get('admin.auditLogs.exportPartRows', auditLogExport.AUDIT_LOGS_EXPORT_PART_ROWS);
175
+ const partSize = Math.max(1, Math.min(pageSize ?? configuredPartSize, auditLogExport.AUDIT_LOGS_EXPORT_PART_MAX_ROWS));
176
+ const { where: filtersWhere } = strapi.get('query-params').transform('admin::audit-log', filtersQuery);
177
+ const isNewExport = !isTrustedExportContinuation(strapi, query);
178
+ if (isNewExport) {
179
+ await strapi.eventHub.emit(auditLogExport.AUDIT_LOG_EXPORT_EVENT, {
180
+ filters: filtersQuery.filters ?? null
181
+ });
182
+ }
183
+ let frozenUntil = isNewExport ? undefined : until;
184
+ if (frozenUntil === undefined) {
185
+ const [lastRow] = await strapi.db.query('admin::audit-log').findMany({
186
+ select: [
187
+ 'id'
188
+ ],
189
+ orderBy: {
190
+ id: 'desc'
191
+ },
192
+ limit: 1
193
+ });
194
+ frozenUntil = lastRow?.id ?? 0;
195
+ }
196
+ const untilId = frozenUntil ?? 0;
197
+ const getWhere = (afterId)=>buildExportWhere(filtersWhere, untilId, afterId);
198
+ const boundary = await strapi.db.query('admin::audit-log').findMany({
199
+ select: [
200
+ 'id'
201
+ ],
202
+ where: getWhere(cursor ?? 0),
203
+ orderBy: {
204
+ id: 'asc'
205
+ },
206
+ offset: partSize - 1,
207
+ limit: 2
208
+ });
209
+ const nextCursor = boundary.length > 1 ? boundary[0].id : null;
210
+ const partUpperBound = nextCursor ?? untilId;
211
+ async function* csvChunks() {
212
+ if (isNewExport) {
213
+ yield csv.CSV_BOM + csv.serializeCsvLine(EXPORT_CSV_COLUMNS);
214
+ }
215
+ let lastId = cursor ?? 0;
216
+ let remaining = partSize;
217
+ while(remaining > 0){
218
+ const batchLimit = Math.min(EXPORT_BATCH_SIZE, remaining);
219
+ const rows = await strapi.db.query('admin::audit-log').findMany({
220
+ select: [
221
+ 'id',
222
+ 'action',
223
+ 'date',
224
+ 'payload'
225
+ ],
226
+ populate: {
227
+ user: {
228
+ select: EXPORT_USER_COLUMNS
229
+ }
230
+ },
231
+ where: buildExportWhere(filtersWhere, partUpperBound, lastId),
232
+ orderBy: {
233
+ id: 'asc'
234
+ },
235
+ limit: batchLimit
236
+ });
237
+ if (rows.length === 0) {
238
+ break;
239
+ }
240
+ yield rows.map(toCsvLine).join('');
241
+ lastId = rows[rows.length - 1].id;
242
+ remaining -= rows.length;
243
+ if (rows.length < batchLimit) {
244
+ break;
245
+ }
246
+ }
247
+ }
248
+ async function* loggedCsvChunks() {
249
+ try {
250
+ yield* csvChunks();
251
+ } catch (error) {
252
+ strapi.log.error(`Audit logs export failed after the response started, the CSV part is truncated: ${error}`);
253
+ throw error;
254
+ }
255
+ }
256
+ return {
257
+ stream: stream.Readable.from(loggedCsvChunks(), {
258
+ objectMode: false,
259
+ highWaterMark: 64 * 1024
260
+ }),
261
+ nextCursor,
262
+ until: untilId,
263
+ isNewExport,
264
+ partSize,
265
+ exportToken: isNewExport ? exportToken.signExportToken(getExportSecret(strapi), untilId, filtersQuery.filters) : token
266
+ };
267
+ },
75
268
  async findManyUsers (query) {
76
269
  const { page = 1, pageSize = 10 } = query;
77
270
  const userAttribute = strapi.db.metadata.get('admin::audit-log').attributes.user;
@@ -140,4 +333,5 @@ const getSanitizedUser = (user)=>{
140
333
  };
141
334
 
142
335
  exports.createAuditLogsService = createAuditLogsService;
336
+ exports.toCsvLine = toCsvLine;
143
337
  //# sourceMappingURL=audit-logs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit-logs.js","sources":["../../../../../../../ee/server/src/audit-logs/services/audit-logs.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\ninterface Event {\n action: string;\n date: Date;\n userId: string | number;\n payload: Record<string, unknown>;\n}\n\ninterface Log extends Omit<Event, 'userId'> {\n user: string | number;\n}\n\nconst getSanitizedUser = (user: any) => {\n let displayName = user.email;\n\n if (user.username) {\n displayName = user.username;\n } else if (user.firstname && user.lastname) {\n displayName = `${user.firstname} ${user.lastname}`;\n }\n\n return {\n id: user.id,\n email: user.email,\n displayName,\n };\n};\n\n/**\n * @description\n * Manages audit logs interaction with the database. Accessible via strapi.get('audit-logs')\n */\nconst createAuditLogsService = (strapi: Core.Strapi) => {\n return {\n async saveEvent(event: Event) {\n const { userId, ...rest } = event;\n\n const auditLog: Log = { ...rest, user: userId };\n\n // Save to database\n await strapi.db.query('admin::audit-log').create({ data: auditLog });\n\n return this;\n },\n\n async findMany(query: unknown) {\n // NOTE: We get the IDs first because sorting full rows runs MySQL/MariaDB out of sort memory\n // See: https://github.com/strapi/strapi/issues/27399\n const { results: logRows, pagination } = await strapi.db.query('admin::audit-log').findPage({\n ...strapi.get('query-params').transform('admin::audit-log', query),\n select: ['id'],\n });\n\n const ids = logRows.map((log) => log.id);\n const logs = ids.length\n ? await strapi.db.query('admin::audit-log').findMany({\n where: { id: { $in: ids } },\n populate: ['user'],\n select: ['action', 'date', 'payload'],\n })\n : [];\n const logsById = new Map(logs.map((log) => [log.id, log]));\n const results = ids.map((id) => logsById.get(id)).filter(Boolean);\n\n const sanitizedResults = results.map((result: any) => {\n const { user, ...rest } = result;\n return {\n ...rest,\n user: user ? getSanitizedUser(user) : null,\n };\n });\n\n return {\n results: sanitizedResults,\n pagination,\n };\n },\n\n async findManyUsers(query: { page?: number | string; pageSize?: number | string }) {\n const { page = 1, pageSize = 10 } = query;\n\n const userAttribute = strapi.db.metadata.get('admin::audit-log').attributes.user;\n\n if (\n userAttribute?.type !== 'relation' ||\n !('joinTable' in userAttribute) ||\n !userAttribute.joinTable ||\n !('inverseJoinColumn' in userAttribute.joinTable)\n ) {\n throw new Error('The audit log user relation is expected to use a join table');\n }\n\n const { joinTable } = userAttribute;\n\n const authorIds = await strapi.db\n .connection(joinTable.name)\n .distinct(joinTable.inverseJoinColumn.name)\n .pluck(joinTable.inverseJoinColumn.name);\n\n const { results, pagination } = await strapi.db.query('admin::user').findPage({\n select: ['id', 'email', 'username', 'firstname', 'lastname'],\n where: { id: { $in: authorIds } },\n orderBy: { id: 'asc' },\n page: Number(page),\n pageSize: Number(pageSize),\n });\n\n return {\n results: results.map(getSanitizedUser),\n pagination,\n };\n },\n\n async findOne(id: unknown) {\n const result: any = await strapi.db.query('admin::audit-log').findOne({\n where: { id },\n populate: ['user'],\n select: ['action', 'date', 'payload'],\n });\n\n if (!result) {\n return null;\n }\n\n const { user, ...rest } = result;\n return {\n ...rest,\n user: user ? getSanitizedUser(user) : null,\n };\n },\n\n deleteExpiredEvents(expirationDate: Date) {\n return strapi.db.query('admin::audit-log').deleteMany({\n where: {\n date: {\n $lt: expirationDate.toISOString(),\n },\n },\n });\n },\n };\n};\n\nexport { createAuditLogsService };\n"],"names":["getSanitizedUser","user","displayName","email","username","firstname","lastname","id","createAuditLogsService","strapi","saveEvent","event","userId","rest","auditLog","db","query","create","data","findMany","results","logRows","pagination","findPage","get","transform","select","ids","map","log","logs","length","where","$in","populate","logsById","Map","filter","Boolean","sanitizedResults","result","findManyUsers","page","pageSize","userAttribute","metadata","attributes","type","joinTable","Error","authorIds","connection","name","distinct","inverseJoinColumn","pluck","orderBy","Number","findOne","deleteExpiredEvents","expirationDate","deleteMany","date","$lt","toISOString"],"mappings":"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"}
1
+ {"version":3,"file":"audit-logs.js","sources":["../../../../../../../ee/server/src/audit-logs/services/audit-logs.ts"],"sourcesContent":["import { Readable } from 'stream';\n\nimport type { Core } from '@strapi/types';\n\nimport {\n AUDIT_LOG_EXPORT_EVENT,\n AUDIT_LOGS_EXPORT_DEFAULT_MAX_ROWS,\n AUDIT_LOGS_EXPORT_PART_MAX_ROWS,\n AUDIT_LOGS_EXPORT_PART_ROWS,\n} from '../../../../../shared/utils/audit-log-export';\nimport { getDisplayName } from '../utils';\nimport { CSV_BOM, serializeCsvLine } from '../utils/csv';\nimport { signExportToken, verifyExportToken } from '../utils/export-token';\n\nconst EXPORT_CSV_COLUMNS = [\n 'id',\n 'action',\n 'date',\n 'user_id',\n 'user_email',\n 'user_display_name',\n 'origin',\n 'payload',\n];\nconst EXPORT_USER_COLUMNS = ['id', 'email', 'username', 'firstname', 'lastname'];\nconst EXPORT_BATCH_SIZE = 5000;\n\ninterface Event {\n action: string;\n date: Date;\n // Null for actions no person performed, such as a scheduled job\n userId: string | number | null;\n payload: Record<string, unknown>;\n}\n\ninterface Log extends Omit<Event, 'userId'> {\n user: string | number | null;\n}\n\nconst getSanitizedUser = (user: any) => {\n return {\n id: user.id,\n email: user.email,\n displayName: getDisplayName(user),\n };\n};\n\nconst serializeCsvPayload = (payload: unknown) => {\n if (payload === null || payload === undefined) {\n return null;\n }\n\n return typeof payload === 'string' ? payload : JSON.stringify(payload);\n};\n\nconst parseCsvPayload = (payload: unknown): Record<string, unknown> | null => {\n if (typeof payload === 'string') {\n try {\n return JSON.parse(payload);\n } catch {\n return null;\n }\n }\n\n return typeof payload === 'object' ? (payload as Record<string, unknown> | null) : null;\n};\n\ninterface ExportQuery {\n cursor?: number;\n pageSize?: number;\n until?: number;\n token?: string;\n [key: string]: unknown;\n}\n\nconst getExportSecret = (strapi: Core.Strapi): string => {\n const secret = strapi.config.get('admin.auth.secret');\n\n if (typeof secret !== 'string' || secret.length === 0) {\n throw new Error(\n 'The audit logs export requires admin.auth.secret to be set in config/admin.js.'\n );\n }\n\n return secret;\n};\n\nconst isTrustedExportContinuation = (strapi: Core.Strapi, query: ExportQuery) => {\n return (\n query.cursor !== undefined &&\n query.until !== undefined &&\n verifyExportToken(getExportSecret(strapi), query.token, query.until, query.filters)\n );\n};\n\nconst buildExportWhere = (filtersWhere: unknown, untilId: number, afterId: number) => ({\n $and: [\n filtersWhere ?? {},\n { id: { $lte: untilId } },\n ...(afterId > 0 ? [{ id: { $gt: afterId } }] : []),\n ],\n});\n\nconst toCsvLine = (row: any) => {\n const user = row.user ? getSanitizedUser(row.user) : null;\n const { payload } = row;\n\n return serializeCsvLine([\n row.id,\n row.action,\n row.date instanceof Date ? row.date.toISOString() : row.date,\n user?.id ?? null,\n user?.email ?? null,\n user?.displayName ?? null,\n parseCsvPayload(payload)?.origin ?? null,\n serializeCsvPayload(payload),\n ]);\n};\n\n/**\n * @description\n * Manages audit logs interaction with the database. Accessible via strapi.get('audit-logs')\n */\nconst createAuditLogsService = (strapi: Core.Strapi) => {\n return {\n async saveEvent(event: Event) {\n const { userId, ...rest } = event;\n\n const auditLog: Log = { ...rest, user: userId };\n\n // Save to database\n await strapi.db.query('admin::audit-log').create({ data: auditLog });\n\n return this;\n },\n\n async findMany(query: unknown) {\n // NOTE: We get the IDs first because sorting full rows runs MySQL/MariaDB out of sort memory\n // See: https://github.com/strapi/strapi/issues/27399\n const { results: logRows, pagination } = await strapi.db.query('admin::audit-log').findPage({\n ...strapi.get('query-params').transform('admin::audit-log', query),\n select: ['id'],\n });\n\n const ids = logRows.map((log) => log.id);\n const logs = ids.length\n ? await strapi.db.query('admin::audit-log').findMany({\n where: { id: { $in: ids } },\n populate: ['user'],\n select: ['action', 'date', 'payload'],\n })\n : [];\n const logsById = new Map(logs.map((log) => [log.id, log]));\n const results = ids.map((id) => logsById.get(id)).filter(Boolean);\n\n const sanitizedResults = results.map((result: any) => {\n const { user, ...rest } = result;\n return {\n ...rest,\n user: user ? getSanitizedUser(user) : null,\n };\n });\n\n return {\n results: sanitizedResults,\n pagination,\n };\n },\n\n async isExportTooLarge(query: { filters?: unknown }) {\n const maxRows = strapi.config.get(\n 'admin.auditLogs.exportMaxRows',\n AUDIT_LOGS_EXPORT_DEFAULT_MAX_ROWS\n );\n const { where } = strapi\n .get('query-params')\n .transform('admin::audit-log', { filters: query.filters ?? undefined });\n\n const beyondCap = await strapi.db.query('admin::audit-log').findMany({\n select: ['id'],\n where: where ?? {},\n orderBy: { id: 'asc' },\n offset: maxRows,\n limit: 1,\n });\n\n return beyondCap.length > 0;\n },\n\n isTrustedContinuation(query: ExportQuery) {\n return isTrustedExportContinuation(strapi, query);\n },\n\n async createExportStream(query: ExportQuery) {\n getExportSecret(strapi);\n\n const { cursor, pageSize, until, token, page: _page, sort: _sort, ...filtersQuery } = query;\n\n const configuredPartSize = strapi.config.get(\n 'admin.auditLogs.exportPartRows',\n AUDIT_LOGS_EXPORT_PART_ROWS\n );\n const partSize = Math.max(\n 1,\n Math.min(pageSize ?? configuredPartSize, AUDIT_LOGS_EXPORT_PART_MAX_ROWS)\n );\n\n const { where: filtersWhere } = strapi\n .get('query-params')\n .transform('admin::audit-log', filtersQuery);\n\n const isNewExport = !isTrustedExportContinuation(strapi, query);\n if (isNewExport) {\n await strapi.eventHub.emit(AUDIT_LOG_EXPORT_EVENT, {\n filters: filtersQuery.filters ?? null,\n });\n }\n\n let frozenUntil = isNewExport ? undefined : until;\n if (frozenUntil === undefined) {\n const [lastRow] = await strapi.db.query('admin::audit-log').findMany({\n select: ['id'],\n orderBy: { id: 'desc' },\n limit: 1,\n });\n\n frozenUntil = lastRow?.id ?? 0;\n }\n\n const untilId = frozenUntil ?? 0;\n const getWhere = (afterId: number) => buildExportWhere(filtersWhere, untilId, afterId);\n\n const boundary: Array<{ id: number }> = await strapi.db.query('admin::audit-log').findMany({\n select: ['id'],\n where: getWhere(cursor ?? 0),\n orderBy: { id: 'asc' },\n offset: partSize - 1,\n limit: 2,\n });\n\n const nextCursor = boundary.length > 1 ? boundary[0].id : null;\n\n const partUpperBound = nextCursor ?? untilId;\n\n async function* csvChunks() {\n if (isNewExport) {\n yield CSV_BOM + serializeCsvLine(EXPORT_CSV_COLUMNS);\n }\n\n let lastId = cursor ?? 0;\n let remaining = partSize;\n\n while (remaining > 0) {\n const batchLimit = Math.min(EXPORT_BATCH_SIZE, remaining);\n const rows: any[] = await strapi.db.query('admin::audit-log').findMany({\n select: ['id', 'action', 'date', 'payload'],\n populate: { user: { select: EXPORT_USER_COLUMNS } },\n where: buildExportWhere(filtersWhere, partUpperBound, lastId),\n orderBy: { id: 'asc' },\n limit: batchLimit,\n });\n\n if (rows.length === 0) {\n break;\n }\n\n yield rows.map(toCsvLine).join('');\n\n lastId = rows[rows.length - 1].id;\n remaining -= rows.length;\n\n if (rows.length < batchLimit) {\n break;\n }\n }\n }\n\n async function* loggedCsvChunks() {\n try {\n yield* csvChunks();\n } catch (error) {\n strapi.log.error(\n `Audit logs export failed after the response started, the CSV part is truncated: ${error}`\n );\n throw error;\n }\n }\n\n return {\n stream: Readable.from(loggedCsvChunks(), { objectMode: false, highWaterMark: 64 * 1024 }),\n nextCursor,\n until: untilId,\n isNewExport,\n partSize,\n exportToken: isNewExport\n ? signExportToken(getExportSecret(strapi), untilId, filtersQuery.filters)\n : (token as string),\n };\n },\n\n async findManyUsers(query: { page?: number | string; pageSize?: number | string }) {\n const { page = 1, pageSize = 10 } = query;\n\n const userAttribute = strapi.db.metadata.get('admin::audit-log').attributes.user;\n\n if (\n userAttribute?.type !== 'relation' ||\n !('joinTable' in userAttribute) ||\n !userAttribute.joinTable ||\n !('inverseJoinColumn' in userAttribute.joinTable)\n ) {\n throw new Error('The audit log user relation is expected to use a join table');\n }\n\n const { joinTable } = userAttribute;\n\n const authorIds = await strapi.db\n .connection(joinTable.name)\n .distinct(joinTable.inverseJoinColumn.name)\n .pluck(joinTable.inverseJoinColumn.name);\n\n const { results, pagination } = await strapi.db.query('admin::user').findPage({\n select: ['id', 'email', 'username', 'firstname', 'lastname'],\n where: { id: { $in: authorIds } },\n orderBy: { id: 'asc' },\n page: Number(page),\n pageSize: Number(pageSize),\n });\n\n return {\n results: results.map(getSanitizedUser),\n pagination,\n };\n },\n\n async findOne(id: unknown) {\n const result: any = await strapi.db.query('admin::audit-log').findOne({\n where: { id },\n populate: ['user'],\n select: ['action', 'date', 'payload'],\n });\n\n if (!result) {\n return null;\n }\n\n const { user, ...rest } = result;\n return {\n ...rest,\n user: user ? getSanitizedUser(user) : null,\n };\n },\n\n deleteExpiredEvents(expirationDate: Date) {\n return strapi.db.query('admin::audit-log').deleteMany({\n where: {\n date: {\n $lt: expirationDate.toISOString(),\n },\n },\n });\n },\n };\n};\n\nexport { createAuditLogsService, toCsvLine };\n"],"names":["EXPORT_CSV_COLUMNS","EXPORT_USER_COLUMNS","EXPORT_BATCH_SIZE","getSanitizedUser","user","id","email","displayName","getDisplayName","serializeCsvPayload","payload","undefined","JSON","stringify","parseCsvPayload","parse","getExportSecret","strapi","secret","config","get","length","Error","isTrustedExportContinuation","query","cursor","until","verifyExportToken","token","filters","buildExportWhere","filtersWhere","untilId","afterId","$and","$lte","$gt","toCsvLine","row","serializeCsvLine","action","date","Date","toISOString","origin","createAuditLogsService","saveEvent","event","userId","rest","auditLog","db","create","data","findMany","results","logRows","pagination","findPage","transform","select","ids","map","log","logs","where","$in","populate","logsById","Map","filter","Boolean","sanitizedResults","result","isExportTooLarge","maxRows","AUDIT_LOGS_EXPORT_DEFAULT_MAX_ROWS","beyondCap","orderBy","offset","limit","isTrustedContinuation","createExportStream","pageSize","page","_page","sort","_sort","filtersQuery","configuredPartSize","AUDIT_LOGS_EXPORT_PART_ROWS","partSize","Math","max","min","AUDIT_LOGS_EXPORT_PART_MAX_ROWS","isNewExport","eventHub","emit","AUDIT_LOG_EXPORT_EVENT","frozenUntil","lastRow","getWhere","boundary","nextCursor","partUpperBound","csvChunks","CSV_BOM","lastId","remaining","batchLimit","rows","join","loggedCsvChunks","error","stream","Readable","from","objectMode","highWaterMark","exportToken","signExportToken","findManyUsers","userAttribute","metadata","attributes","type","joinTable","authorIds","connection","name","distinct","inverseJoinColumn","pluck","Number","findOne","deleteExpiredEvents","expirationDate","deleteMany","$lt"],"mappings":"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"}
@@ -1,16 +1,91 @@
1
+ import { Readable } from 'stream';
2
+ import { AUDIT_LOGS_EXPORT_PART_ROWS, AUDIT_LOG_EXPORT_EVENT, AUDIT_LOGS_EXPORT_DEFAULT_MAX_ROWS, AUDIT_LOGS_EXPORT_PART_MAX_ROWS } from '../../../../../shared/utils/audit-log-export.mjs';
3
+ import { getDisplayName } from '../utils.mjs';
4
+ import { CSV_BOM, serializeCsvLine } from '../utils/csv.mjs';
5
+ import { signExportToken, verifyExportToken } from '../utils/export-token.mjs';
6
+
7
+ const EXPORT_CSV_COLUMNS = [
8
+ 'id',
9
+ 'action',
10
+ 'date',
11
+ 'user_id',
12
+ 'user_email',
13
+ 'user_display_name',
14
+ 'origin',
15
+ 'payload'
16
+ ];
17
+ const EXPORT_USER_COLUMNS = [
18
+ 'id',
19
+ 'email',
20
+ 'username',
21
+ 'firstname',
22
+ 'lastname'
23
+ ];
24
+ const EXPORT_BATCH_SIZE = 5000;
1
25
  const getSanitizedUser = (user)=>{
2
- let displayName = user.email;
3
- if (user.username) {
4
- displayName = user.username;
5
- } else if (user.firstname && user.lastname) {
6
- displayName = `${user.firstname} ${user.lastname}`;
7
- }
8
26
  return {
9
27
  id: user.id,
10
28
  email: user.email,
11
- displayName
29
+ displayName: getDisplayName(user)
12
30
  };
13
31
  };
32
+ const serializeCsvPayload = (payload)=>{
33
+ if (payload === null || payload === undefined) {
34
+ return null;
35
+ }
36
+ return typeof payload === 'string' ? payload : JSON.stringify(payload);
37
+ };
38
+ const parseCsvPayload = (payload)=>{
39
+ if (typeof payload === 'string') {
40
+ try {
41
+ return JSON.parse(payload);
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ return typeof payload === 'object' ? payload : null;
47
+ };
48
+ const getExportSecret = (strapi)=>{
49
+ const secret = strapi.config.get('admin.auth.secret');
50
+ if (typeof secret !== 'string' || secret.length === 0) {
51
+ throw new Error('The audit logs export requires admin.auth.secret to be set in config/admin.js.');
52
+ }
53
+ return secret;
54
+ };
55
+ const isTrustedExportContinuation = (strapi, query)=>{
56
+ return query.cursor !== undefined && query.until !== undefined && verifyExportToken(getExportSecret(strapi), query.token, query.until, query.filters);
57
+ };
58
+ const buildExportWhere = (filtersWhere, untilId, afterId)=>({
59
+ $and: [
60
+ filtersWhere ?? {},
61
+ {
62
+ id: {
63
+ $lte: untilId
64
+ }
65
+ },
66
+ ...afterId > 0 ? [
67
+ {
68
+ id: {
69
+ $gt: afterId
70
+ }
71
+ }
72
+ ] : []
73
+ ]
74
+ });
75
+ const toCsvLine = (row)=>{
76
+ const user = row.user ? getSanitizedUser(row.user) : null;
77
+ const { payload } = row;
78
+ return serializeCsvLine([
79
+ row.id,
80
+ row.action,
81
+ row.date instanceof Date ? row.date.toISOString() : row.date,
82
+ user?.id ?? null,
83
+ user?.email ?? null,
84
+ user?.displayName ?? null,
85
+ parseCsvPayload(payload)?.origin ?? null,
86
+ serializeCsvPayload(payload)
87
+ ]);
88
+ };
14
89
  /**
15
90
  * @description
16
91
  * Manages audit logs interaction with the database. Accessible via strapi.get('audit-logs')
@@ -70,6 +145,124 @@ const getSanitizedUser = (user)=>{
70
145
  pagination
71
146
  };
72
147
  },
148
+ async isExportTooLarge (query) {
149
+ const maxRows = strapi.config.get('admin.auditLogs.exportMaxRows', AUDIT_LOGS_EXPORT_DEFAULT_MAX_ROWS);
150
+ const { where } = strapi.get('query-params').transform('admin::audit-log', {
151
+ filters: query.filters ?? undefined
152
+ });
153
+ const beyondCap = await strapi.db.query('admin::audit-log').findMany({
154
+ select: [
155
+ 'id'
156
+ ],
157
+ where: where ?? {},
158
+ orderBy: {
159
+ id: 'asc'
160
+ },
161
+ offset: maxRows,
162
+ limit: 1
163
+ });
164
+ return beyondCap.length > 0;
165
+ },
166
+ isTrustedContinuation (query) {
167
+ return isTrustedExportContinuation(strapi, query);
168
+ },
169
+ async createExportStream (query) {
170
+ getExportSecret(strapi);
171
+ const { cursor, pageSize, until, token, page: _page, sort: _sort, ...filtersQuery } = query;
172
+ const configuredPartSize = strapi.config.get('admin.auditLogs.exportPartRows', AUDIT_LOGS_EXPORT_PART_ROWS);
173
+ const partSize = Math.max(1, Math.min(pageSize ?? configuredPartSize, AUDIT_LOGS_EXPORT_PART_MAX_ROWS));
174
+ const { where: filtersWhere } = strapi.get('query-params').transform('admin::audit-log', filtersQuery);
175
+ const isNewExport = !isTrustedExportContinuation(strapi, query);
176
+ if (isNewExport) {
177
+ await strapi.eventHub.emit(AUDIT_LOG_EXPORT_EVENT, {
178
+ filters: filtersQuery.filters ?? null
179
+ });
180
+ }
181
+ let frozenUntil = isNewExport ? undefined : until;
182
+ if (frozenUntil === undefined) {
183
+ const [lastRow] = await strapi.db.query('admin::audit-log').findMany({
184
+ select: [
185
+ 'id'
186
+ ],
187
+ orderBy: {
188
+ id: 'desc'
189
+ },
190
+ limit: 1
191
+ });
192
+ frozenUntil = lastRow?.id ?? 0;
193
+ }
194
+ const untilId = frozenUntil ?? 0;
195
+ const getWhere = (afterId)=>buildExportWhere(filtersWhere, untilId, afterId);
196
+ const boundary = await strapi.db.query('admin::audit-log').findMany({
197
+ select: [
198
+ 'id'
199
+ ],
200
+ where: getWhere(cursor ?? 0),
201
+ orderBy: {
202
+ id: 'asc'
203
+ },
204
+ offset: partSize - 1,
205
+ limit: 2
206
+ });
207
+ const nextCursor = boundary.length > 1 ? boundary[0].id : null;
208
+ const partUpperBound = nextCursor ?? untilId;
209
+ async function* csvChunks() {
210
+ if (isNewExport) {
211
+ yield CSV_BOM + serializeCsvLine(EXPORT_CSV_COLUMNS);
212
+ }
213
+ let lastId = cursor ?? 0;
214
+ let remaining = partSize;
215
+ while(remaining > 0){
216
+ const batchLimit = Math.min(EXPORT_BATCH_SIZE, remaining);
217
+ const rows = await strapi.db.query('admin::audit-log').findMany({
218
+ select: [
219
+ 'id',
220
+ 'action',
221
+ 'date',
222
+ 'payload'
223
+ ],
224
+ populate: {
225
+ user: {
226
+ select: EXPORT_USER_COLUMNS
227
+ }
228
+ },
229
+ where: buildExportWhere(filtersWhere, partUpperBound, lastId),
230
+ orderBy: {
231
+ id: 'asc'
232
+ },
233
+ limit: batchLimit
234
+ });
235
+ if (rows.length === 0) {
236
+ break;
237
+ }
238
+ yield rows.map(toCsvLine).join('');
239
+ lastId = rows[rows.length - 1].id;
240
+ remaining -= rows.length;
241
+ if (rows.length < batchLimit) {
242
+ break;
243
+ }
244
+ }
245
+ }
246
+ async function* loggedCsvChunks() {
247
+ try {
248
+ yield* csvChunks();
249
+ } catch (error) {
250
+ strapi.log.error(`Audit logs export failed after the response started, the CSV part is truncated: ${error}`);
251
+ throw error;
252
+ }
253
+ }
254
+ return {
255
+ stream: Readable.from(loggedCsvChunks(), {
256
+ objectMode: false,
257
+ highWaterMark: 64 * 1024
258
+ }),
259
+ nextCursor,
260
+ until: untilId,
261
+ isNewExport,
262
+ partSize,
263
+ exportToken: isNewExport ? signExportToken(getExportSecret(strapi), untilId, filtersQuery.filters) : token
264
+ };
265
+ },
73
266
  async findManyUsers (query) {
74
267
  const { page = 1, pageSize = 10 } = query;
75
268
  const userAttribute = strapi.db.metadata.get('admin::audit-log').attributes.user;
@@ -137,5 +330,5 @@ const getSanitizedUser = (user)=>{
137
330
  };
138
331
  };
139
332
 
140
- export { createAuditLogsService };
333
+ export { createAuditLogsService, toCsvLine };
141
334
  //# sourceMappingURL=audit-logs.mjs.map