@jsreport/jsreport-core 3.11.4 → 4.0.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.
package/README.md CHANGED
@@ -282,6 +282,18 @@ jsreport.documentStore.collection('templates')
282
282
 
283
283
  ## Changelog
284
284
 
285
+ ### 4.0.0
286
+
287
+ - remove old migration options `migrateXlsxTemplatesToAssets`, `migrateResourcesToAssets`
288
+ - sandbox now uses SES instead of vm2 for evaluating user code
289
+ - internal changes to support multi admin users
290
+
291
+ ### 3.12.0
292
+
293
+ - update vm2 to fix security issues
294
+ - render requests are now rotated across not busy workers but considering its last usage too
295
+ - require in sandbox now uses a custom require implementation that takes care of isolate module resolving and that uses our own cache (different to built in require.cache) to avoid using a lot of memory when there are a lot of requests. if module isolation is not needed (because user can trust the templates) then it can be disabled by using `sandbox.isolateModules: false`
296
+
285
297
  ### 3.11.4
286
298
 
287
299
  - update unset-value to fix security issue
@@ -1,270 +1,263 @@
1
- const { getDefaultTempDirectory, getDefaultLoadConfig } = require('./defaults')
2
- // we use deepmerge instead of node.extend here because it supports
3
- // concatenating arrays instead of replacing them, something necessary
4
- // to support extending some values in schemas like the "enum"
5
- const deepMerge = require('deepmerge')
6
-
7
- module.exports.getRootSchemaOptions = () => ({
8
- type: 'object',
9
- properties: {
10
- rootDirectory: {
11
- type: 'string',
12
- description: 'specifies where is the application root and where jsreport searches for extensions'
13
- },
14
- appDirectory: {
15
- type: 'string',
16
- description: 'specifies directory of the script that was used to start node.js, this value is mostly metadata that is useful for your own code inside jsreport scripts'
17
- },
18
- tempDirectory: {
19
- type: 'string',
20
- default: getDefaultTempDirectory(),
21
- description: 'specifies where jsreport stores temporary files used by the conversion pipeline'
22
- },
23
- loadConfig: {
24
- type: 'boolean',
25
- default: getDefaultLoadConfig(),
26
- description: 'specifies if jsreport should load configuration values from external sources (cli args, env vars, configuration files) or not'
27
- },
28
- autoTempCleanup: {
29
- type: 'boolean',
30
- default: true,
31
- description: 'specifies if after some interval jsreport should automatically clean up temporary files generated while rendering reports'
32
- },
33
- discover: {
34
- type: 'boolean',
35
- defaultNotInitialized: true,
36
- description: 'specifies if jsreport should discover/search installed extensions in project and use them automatically'
37
- },
38
- useExtensionsLocationCache: {
39
- type: 'boolean',
40
- default: true,
41
- description: 'whether if jsreport should read list of extensions from a previous generated cache or if it should crawl and try to search extensions again, set it to false when you want to always force crawling node_modules when searching for extensions while starting jsreport'
42
- },
43
- logger: {
44
- type: 'object',
45
- properties: {
46
- silent: { type: 'boolean' }
47
- }
48
- },
49
- reportTimeout: {
50
- type: ['string', 'number'],
51
- '$jsreport-acceptsDuration': true,
52
- description: 'global single timeout that controls how much a report generation should wait before it times out',
53
- default: 60000
54
- },
55
- reportTimeoutMargin: {
56
- type: ['string', 'number'],
57
- description: 'the time to wait before the worker thread is forcibly killed after timeout',
58
- '$jsreport-acceptsDuration': true,
59
- default: '2s'
60
- },
61
- enableRequestReportTimeout: { type: 'boolean', default: false, description: 'option that enables passing a custom report timeout per request using req.options.timeout. this enables that the caller of the report generation control the report timeout so enable it only when you trust the caller' },
62
- trustUserCode: { type: 'boolean', default: false, description: 'option that control whether code sandboxing is enabled or not, code sandboxing has an impact on performance when rendering large reports. when true code sandboxing will be disabled meaning that users can potentially penetrate the local system if you allow code from external users to be part of your reports' },
63
- allowLocalFilesAccess: { type: 'boolean', default: false },
64
- encryption: {
65
- type: 'object',
66
- default: {},
67
- properties: {
68
- secretKey: {
69
- type: 'string',
70
- minLength: 16,
71
- maxLength: 16
72
- },
73
- enabled: {
74
- type: 'boolean',
75
- default: true
76
- }
77
- }
78
- },
79
- sandbox: {
80
- type: 'object',
81
- default: {},
82
- properties: {
83
- allowedModules: {
84
- anyOf: [{
85
- type: 'string',
86
- '$jsreport-constantOrArray': ['*']
87
- }, {
88
- type: 'array',
89
- items: { type: 'string' }
90
- }]
91
- },
92
- cache: {
93
- type: 'object',
94
- default: {},
95
- properties: {
96
- max: { type: 'number', default: 100 },
97
- enabled: { type: 'boolean', default: true }
98
- }
99
- }
100
- }
101
- },
102
- workers: {
103
- type: 'object',
104
- default: {},
105
- properties: {
106
- numberOfWorkers: {
107
- type: 'number',
108
- default: 2,
109
- description: 'Number of workers allocated. Every worker can process a single request in parallel. This means increasing numberOfWorkers will increase the parallelization.'
110
- },
111
- initTimeout: {
112
- type: ['string', 'number'],
113
- '$jsreport-acceptsDuration': true,
114
- description: 'Timeout for initializing a worker thread. This should be increased only when running at very slow HW environment.',
115
- default: '30s'
116
- },
117
- resourceLimits: {
118
- type: 'object',
119
- description: 'Limits for the individual workers. See https://nodejs.org/api/worker_threads.html#worker_threads_worker_resourcelimits',
120
- properties: {
121
- maxOldGenerationSizeMb: { type: 'number' },
122
- maxYoungGenerationSizeMb: { type: 'number' },
123
- codeRangeSizeMb: { type: 'number' },
124
- stackSizeMb: { type: 'number' }
125
- }
126
- }
127
- }
128
- },
129
- store: {
130
- type: 'object',
131
- properties: {
132
- provider: { type: 'string', enum: ['memory'] },
133
- transactions: {
134
- type: 'object',
135
- properties: {
136
- enabled: { type: 'boolean', default: true }
137
- }
138
- }
139
- }
140
- },
141
- blobStorage: {
142
- type: 'object',
143
- properties: {
144
- provider: { type: 'string', enum: ['memory'] }
145
- }
146
- },
147
- extensions: {
148
- type: 'object',
149
- properties: {}
150
- },
151
- extensionsList: {
152
- anyOf: [
153
- {
154
- type: 'string',
155
- '$jsreport-constantOrArray': []
156
- },
157
- {
158
- type: 'array',
159
- items: { type: 'string' }
160
- }
161
- ]
162
- },
163
- migrateXlsxTemplatesToAssets: {
164
- type: 'boolean',
165
- default: true
166
- },
167
- migrateResourcesToAssets: {
168
- type: 'boolean',
169
- default: true
170
- },
171
- profiler: {
172
- type: 'object',
173
- default: {},
174
- properties: {
175
- defaultMode: {
176
- type: 'string',
177
- default: 'standard'
178
- },
179
- fullModeDurationCheckInterval: {
180
- type: ['string', 'number'],
181
- '$jsreport-acceptsDuration': true,
182
- default: '10m'
183
- },
184
- fullModeDuration: {
185
- type: ['string', 'number'],
186
- '$jsreport-acceptsDuration': true,
187
- default: '4h'
188
- },
189
- maxProfilesHistory: {
190
- type: 'number',
191
- default: 1000
192
- },
193
- cleanupInterval: {
194
- type: ['string', 'number'],
195
- '$jsreport-acceptsDuration': true,
196
- default: '1m'
197
- },
198
- maxUnallocatedProfileAge: {
199
- type: ['string', 'number'],
200
- '$jsreport-acceptsDuration': true,
201
- default: '24h'
202
- },
203
- maxDiffSize: {
204
- type: ['string', 'number'],
205
- '$jsreport-acceptsSize': true,
206
- default: '50mb'
207
- }
208
- }
209
- }
210
- }
211
- })
212
-
213
- module.exports.extendRootSchemaOptions = (rootSchema, schema) => {
214
- const schemasToApply = Array.isArray(schema) ? schema : [schema]
215
-
216
- rootSchema.properties = rootSchema.properties || {}
217
-
218
- rootSchema.properties.extensions = rootSchema.properties.extensions || {
219
- type: 'object',
220
- properties: {}
221
- }
222
-
223
- schemasToApply.forEach((sch) => {
224
- if (sch == null) {
225
- return
226
- }
227
-
228
- if (sch.schema == null && sch.name != null) {
229
- rootSchema.properties.extensions.properties[sch.name] = {
230
- type: 'object',
231
- properties: {
232
- enabled: { type: 'boolean' }
233
- }
234
- }
235
- return
236
- } else if (sch.schema == null) {
237
- return
238
- }
239
-
240
- Object.keys(sch.schema).forEach((key) => {
241
- const current = sch.schema[key]
242
-
243
- if (key === 'extensions') {
244
- if (current == null) {
245
- return
246
- }
247
-
248
- Object.keys(current).forEach(s => {
249
- rootSchema.properties.extensions.properties[s] = deepMerge(rootSchema.properties.extensions.properties[s] || {}, current[s])
250
-
251
- if (
252
- rootSchema.properties.extensions.properties[s].properties &&
253
- rootSchema.properties.extensions.properties[s].properties.enabled == null
254
- ) {
255
- rootSchema.properties.extensions.properties[s].properties.enabled = { type: 'boolean' }
256
- }
257
- })
258
- } else if (current != null) {
259
- rootSchema.properties[key] = deepMerge(rootSchema.properties[key] || {}, current)
260
- }
261
- })
262
- })
263
-
264
- return rootSchema
265
- }
266
-
267
- module.exports.ignoreInitialSchemaProperties = [
268
- 'properties.store.properties.provider',
269
- 'properties.blobStorage.properties.provider'
270
- ]
1
+ const { getDefaultTempDirectory, getDefaultLoadConfig } = require('./defaults')
2
+ // we use deepmerge instead of node.extend here because it supports
3
+ // concatenating arrays instead of replacing them, something necessary
4
+ // to support extending some values in schemas like the "enum"
5
+ const deepMerge = require('deepmerge')
6
+
7
+ module.exports.getRootSchemaOptions = () => ({
8
+ type: 'object',
9
+ properties: {
10
+ rootDirectory: {
11
+ type: 'string',
12
+ description: 'specifies where is the application root and where jsreport searches for extensions'
13
+ },
14
+ appDirectory: {
15
+ type: 'string',
16
+ description: 'specifies directory of the script that was used to start node.js, this value is mostly metadata that is useful for your own code inside jsreport scripts'
17
+ },
18
+ tempDirectory: {
19
+ type: 'string',
20
+ default: getDefaultTempDirectory(),
21
+ description: 'specifies where jsreport stores temporary files used by the conversion pipeline'
22
+ },
23
+ loadConfig: {
24
+ type: 'boolean',
25
+ default: getDefaultLoadConfig(),
26
+ description: 'specifies if jsreport should load configuration values from external sources (cli args, env vars, configuration files) or not'
27
+ },
28
+ autoTempCleanup: {
29
+ type: 'boolean',
30
+ default: true,
31
+ description: 'specifies if after some interval jsreport should automatically clean up temporary files generated while rendering reports'
32
+ },
33
+ discover: {
34
+ type: 'boolean',
35
+ defaultNotInitialized: true,
36
+ description: 'specifies if jsreport should discover/search installed extensions in project and use them automatically'
37
+ },
38
+ useExtensionsLocationCache: {
39
+ type: 'boolean',
40
+ default: true,
41
+ description: 'whether if jsreport should read list of extensions from a previous generated cache or if it should crawl and try to search extensions again, set it to false when you want to always force crawling node_modules when searching for extensions while starting jsreport'
42
+ },
43
+ logger: {
44
+ type: 'object',
45
+ properties: {
46
+ silent: { type: 'boolean' }
47
+ }
48
+ },
49
+ reportTimeout: {
50
+ type: ['string', 'number'],
51
+ '$jsreport-acceptsDuration': true,
52
+ description: 'global single timeout that controls how much a report generation should wait before it times out',
53
+ default: 60000
54
+ },
55
+ reportTimeoutMargin: {
56
+ type: ['string', 'number'],
57
+ description: 'the time to wait before the worker thread is forcibly killed after timeout',
58
+ '$jsreport-acceptsDuration': true,
59
+ default: '2s'
60
+ },
61
+ enableRequestReportTimeout: { type: 'boolean', default: false, description: 'option that enables passing a custom report timeout per request using req.options.timeout. this enables that the caller of the report generation control the report timeout so enable it only when you trust the caller' },
62
+ trustUserCode: { type: 'boolean', default: false, description: 'option that control whether code sandboxing is enabled or not, code sandboxing has an impact on performance when rendering large reports. when true code sandboxing will be disabled meaning that users can potentially penetrate the local system if you allow code from external users to be part of your reports' },
63
+ allowLocalFilesAccess: { type: 'boolean', default: false },
64
+ encryption: {
65
+ type: 'object',
66
+ default: {},
67
+ properties: {
68
+ secretKey: {
69
+ type: 'string',
70
+ minLength: 16,
71
+ maxLength: 16
72
+ },
73
+ enabled: {
74
+ type: 'boolean',
75
+ default: true
76
+ }
77
+ }
78
+ },
79
+ sandbox: {
80
+ type: 'object',
81
+ default: {},
82
+ properties: {
83
+ isolateModules: { type: 'boolean', default: true, description: 'option that control whether require/import of modules during rendering are isolated from other renders or not. when this is false the require/import of modules will behave like normal require, which means that module is evaluated only once and next require/import are resolved from a cache' },
84
+ allowedModules: {
85
+ anyOf: [{
86
+ type: 'string',
87
+ '$jsreport-constantOrArray': ['*']
88
+ }, {
89
+ type: 'array',
90
+ items: { type: 'string' }
91
+ }]
92
+ },
93
+ cache: {
94
+ type: 'object',
95
+ default: {},
96
+ properties: {
97
+ max: { type: 'number', default: 100 },
98
+ enabled: { type: 'boolean', default: true }
99
+ }
100
+ }
101
+ }
102
+ },
103
+ workers: {
104
+ type: 'object',
105
+ default: {},
106
+ properties: {
107
+ numberOfWorkers: {
108
+ type: 'number',
109
+ default: 2,
110
+ description: 'Number of workers allocated. Every worker can process a single request in parallel. This means increasing numberOfWorkers will increase the parallelization.'
111
+ },
112
+ initTimeout: {
113
+ type: ['string', 'number'],
114
+ '$jsreport-acceptsDuration': true,
115
+ description: 'Timeout for initializing a worker thread. This should be increased only when running at very slow HW environment.',
116
+ default: '30s'
117
+ },
118
+ resourceLimits: {
119
+ type: 'object',
120
+ description: 'Limits for the individual workers. See https://nodejs.org/api/worker_threads.html#worker_threads_worker_resourcelimits',
121
+ properties: {
122
+ maxOldGenerationSizeMb: { type: 'number' },
123
+ maxYoungGenerationSizeMb: { type: 'number' },
124
+ codeRangeSizeMb: { type: 'number' },
125
+ stackSizeMb: { type: 'number' }
126
+ }
127
+ }
128
+ }
129
+ },
130
+ store: {
131
+ type: 'object',
132
+ properties: {
133
+ provider: { type: 'string', enum: ['memory'] },
134
+ transactions: {
135
+ type: 'object',
136
+ properties: {
137
+ enabled: { type: 'boolean', default: true }
138
+ }
139
+ }
140
+ }
141
+ },
142
+ blobStorage: {
143
+ type: 'object',
144
+ properties: {
145
+ provider: { type: 'string', enum: ['memory'] }
146
+ }
147
+ },
148
+ extensions: {
149
+ type: 'object',
150
+ properties: {}
151
+ },
152
+ extensionsList: {
153
+ anyOf: [
154
+ {
155
+ type: 'string',
156
+ '$jsreport-constantOrArray': []
157
+ },
158
+ {
159
+ type: 'array',
160
+ items: { type: 'string' }
161
+ }
162
+ ]
163
+ },
164
+ profiler: {
165
+ type: 'object',
166
+ default: {},
167
+ properties: {
168
+ defaultMode: {
169
+ type: 'string',
170
+ default: 'standard'
171
+ },
172
+ fullModeDurationCheckInterval: {
173
+ type: ['string', 'number'],
174
+ '$jsreport-acceptsDuration': true,
175
+ default: '10m'
176
+ },
177
+ fullModeDuration: {
178
+ type: ['string', 'number'],
179
+ '$jsreport-acceptsDuration': true,
180
+ default: '4h'
181
+ },
182
+ maxProfilesHistory: {
183
+ type: 'number',
184
+ default: 1000
185
+ },
186
+ cleanupInterval: {
187
+ type: ['string', 'number'],
188
+ '$jsreport-acceptsDuration': true,
189
+ default: '1m'
190
+ },
191
+ maxUnallocatedProfileAge: {
192
+ type: ['string', 'number'],
193
+ '$jsreport-acceptsDuration': true,
194
+ default: '24h'
195
+ },
196
+ maxDiffSize: {
197
+ type: ['string', 'number'],
198
+ '$jsreport-acceptsSize': true,
199
+ default: '50mb'
200
+ }
201
+ }
202
+ }
203
+ }
204
+ })
205
+
206
+ module.exports.extendRootSchemaOptions = (rootSchema, schema) => {
207
+ const schemasToApply = Array.isArray(schema) ? schema : [schema]
208
+
209
+ rootSchema.properties = rootSchema.properties || {}
210
+
211
+ rootSchema.properties.extensions = rootSchema.properties.extensions || {
212
+ type: 'object',
213
+ properties: {}
214
+ }
215
+
216
+ schemasToApply.forEach((sch) => {
217
+ if (sch == null) {
218
+ return
219
+ }
220
+
221
+ if (sch.schema == null && sch.name != null) {
222
+ rootSchema.properties.extensions.properties[sch.name] = {
223
+ type: 'object',
224
+ properties: {
225
+ enabled: { type: 'boolean' }
226
+ }
227
+ }
228
+ return
229
+ } else if (sch.schema == null) {
230
+ return
231
+ }
232
+
233
+ Object.keys(sch.schema).forEach((key) => {
234
+ const current = sch.schema[key]
235
+
236
+ if (key === 'extensions') {
237
+ if (current == null) {
238
+ return
239
+ }
240
+
241
+ Object.keys(current).forEach(s => {
242
+ rootSchema.properties.extensions.properties[s] = deepMerge(rootSchema.properties.extensions.properties[s] || {}, current[s])
243
+
244
+ if (
245
+ rootSchema.properties.extensions.properties[s].properties &&
246
+ rootSchema.properties.extensions.properties[s].properties.enabled == null
247
+ ) {
248
+ rootSchema.properties.extensions.properties[s].properties.enabled = { type: 'boolean' }
249
+ }
250
+ })
251
+ } else if (current != null) {
252
+ rootSchema.properties[key] = deepMerge(rootSchema.properties[key] || {}, current)
253
+ }
254
+ })
255
+ })
256
+
257
+ return rootSchema
258
+ }
259
+
260
+ module.exports.ignoreInitialSchemaProperties = [
261
+ 'properties.store.properties.provider',
262
+ 'properties.blobStorage.properties.provider'
263
+ ]
@@ -6,6 +6,7 @@
6
6
  const path = require('path')
7
7
  const { Readable } = require('stream')
8
8
  const Reaper = require('@jsreport/reap')
9
+ const pkg = require('../../package.json')
9
10
  const optionsLoad = require('./optionsLoad')
10
11
  const { createLogger, configureLogger, silentLogs } = require('./logger')
11
12
  const checkEntityName = require('./validateEntityName')
@@ -28,8 +29,6 @@ const Reporter = require('../shared/reporter')
28
29
  const Request = require('./request')
29
30
  const generateRequestId = require('../shared/generateRequestId')
30
31
  const Profiler = require('./profiler')
31
- const migrateXlsxTemplatesToAssets = require('./migration/xlsxTemplatesToAssets')
32
- const migrateResourcesToAssets = require('./migration/resourcesToAssets')
33
32
  const semver = require('semver')
34
33
  let reportCounter = 0
35
34
 
@@ -37,8 +36,8 @@ class MainReporter extends Reporter {
37
36
  constructor (options, defaults) {
38
37
  super(options)
39
38
 
40
- if (!semver.satisfies(process.versions.node, '>=16.11.0')) {
41
- throw this.createError('jsreport needs at least node 16.11.0 to run.')
39
+ if (!semver.satisfies(process.versions.node, pkg.engines.node)) {
40
+ throw this.createError(`jsreport needs at least node ${pkg.engines.node} to run.`)
42
41
  }
43
42
 
44
43
  this.defaults = defaults || {}
@@ -172,14 +171,6 @@ class MainReporter extends Reporter {
172
171
 
173
172
  this._initializing = true
174
173
 
175
- if (this.compilation) {
176
- this.compilation.resource('vm2-events.js', require.resolve('vm2/lib/events.js'))
177
- this.compilation.resource('vm2-resolver-compat.js', require.resolve('vm2/lib/resolver-compat.js'))
178
- this.compilation.resource('vm2-resolver.js', require.resolve('vm2/lib/resolver.js'))
179
- this.compilation.resource('vm2-setup-node-sandbox.js', require.resolve('vm2/lib/setup-node-sandbox.js'))
180
- this.compilation.resource('vm2-setup-sandbox.js', require.resolve('vm2/lib/setup-sandbox.js'))
181
- }
182
-
183
174
  try {
184
175
  this._registerLogMainAction()
185
176
 
@@ -219,6 +210,10 @@ class MainReporter extends Reporter {
219
210
  this.logger.info('Code sandboxing is disabled, users can potentially penetrate the local system if you allow code from external users to be part of your reports')
220
211
  }
221
212
 
213
+ if (!this.options.sandbox.isolateModules) {
214
+ this.logger.info('Modules isolation is disabled, require of modules during rendering will be shared across all renders')
215
+ }
216
+
222
217
  if (explicitOptions.trustUserCode == null && explicitOptions.allowLocalFilesAccess != null) {
223
218
  this.logger.warn('options.allowLocalFilesAccess is deprecated, use options.trustUserCode instead')
224
219
  }
@@ -233,7 +228,11 @@ class MainReporter extends Reporter {
233
228
 
234
229
  await this.documentStore.init()
235
230
  await this.blobStorage.init()
236
- await this.settings.init(this.documentStore, this.authorization)
231
+
232
+ await this.settings.init(this.documentStore, {
233
+ authentication: this.authentication,
234
+ authorization: this.authorization
235
+ })
237
236
 
238
237
  const extensionsForWorkers = this.extensionsManager.extensions.filter(e => e.worker)
239
238
 
@@ -263,9 +262,6 @@ class MainReporter extends Reporter {
263
262
  setupValidateId(this)
264
263
  setupValidateShortid(this)
265
264
 
266
- this.initializeListeners.insert(0, 'core-resources-migration', () => migrateResourcesToAssets(this))
267
- this.initializeListeners.insert(0, 'core-xlsxTemplates-migration', () => migrateXlsxTemplatesToAssets(this))
268
-
269
265
  await this.initializeListeners.fire()
270
266
 
271
267
  this._workersManager = this._workersManagerFactory