@platformatic/runtime 4.0.0-new-config.2 → 4.0.0-new-config.5
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/config.d.ts +594 -596
- package/index.d.ts +11 -5
- package/index.js +161 -85
- package/lib/config.js +47 -251
- package/lib/errors.js +8 -0
- package/lib/generator.js +199 -35
- package/lib/logger.js +3 -3
- package/lib/management-api.js +4 -60
- package/lib/policies.js +27 -0
- package/lib/runtime.js +315 -239
- package/lib/schema.js +76 -0
- package/lib/worker/controller.js +54 -49
- package/lib/worker/interceptors.js +34 -34
- package/lib/worker/itc.js +49 -2
- package/lib/worker/main.js +28 -53
- package/lib/worker/symbols.js +1 -1
- package/package.json +18 -18
- package/schema.json +87 -44
package/lib/generator.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
+
import { createEnvFileTool, defaultPackageManager, generateDashedName, safeRemove } from '@platformatic/foundation'
|
|
2
|
+
import { LegacyConfigurationFileError, serializeConfiguration } from '@platformatic/foundation/lib/v4/index.js'
|
|
1
3
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from '@platformatic/
|
|
11
|
-
import { BaseGenerator, envObjectToString, getApplicationTemplateFromSchemaUrl } from '@platformatic/generators'
|
|
4
|
+
BaseGenerator,
|
|
5
|
+
appendApplications,
|
|
6
|
+
envObjectToString,
|
|
7
|
+
findAnyConfigurationFile,
|
|
8
|
+
listedApplications,
|
|
9
|
+
rawSource,
|
|
10
|
+
readEnvFile,
|
|
11
|
+
getApplicationTemplateFromSchemaUrl
|
|
12
|
+
} from '@platformatic/generators'
|
|
12
13
|
import { existsSync } from 'node:fs'
|
|
13
14
|
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
14
15
|
import { createRequire } from 'node:module'
|
|
15
|
-
import { basename, join } from 'node:path'
|
|
16
|
+
import { basename, join, resolve, sep } from 'node:path'
|
|
16
17
|
import { pathToFileURL } from 'node:url'
|
|
17
|
-
import { transform } from './config.js'
|
|
18
|
-
import { schema } from './schema.js'
|
|
19
18
|
import { getArrayDifference } from './utils.js'
|
|
20
19
|
|
|
21
20
|
const engines = {
|
|
@@ -38,6 +37,11 @@ function getRuntimeBaseEnvVars (config) {
|
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
|
|
40
|
+
/*
|
|
41
|
+
The one thing an import changes about a root: which applications it lists. Applied to the module
|
|
42
|
+
the user has rather than to a re-rendering of it, so their formatting, their comments and the
|
|
43
|
+
expressions their values are written as all survive the edit.
|
|
44
|
+
*/
|
|
41
45
|
export class RuntimeGenerator extends BaseGenerator {
|
|
42
46
|
constructor (opts) {
|
|
43
47
|
super({
|
|
@@ -120,17 +124,46 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
120
124
|
return
|
|
121
125
|
}
|
|
122
126
|
this._hasCheckedForExistingConfig = true
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
this
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
ignoreProcessEnv: true
|
|
129
|
-
})
|
|
127
|
+
/*
|
|
128
|
+
Whichever dialect the project is in: a v4 configuration is a module and is invisible to the
|
|
129
|
+
legacy lookup, so without this the wizard treats a project it scaffolded itself as a new one.
|
|
130
|
+
*/
|
|
131
|
+
const existingConfigFile = await findAnyConfigurationFile(this.targetDirectory)
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
this.
|
|
133
|
-
|
|
133
|
+
if (existingConfigFile && existsSync(join(this.targetDirectory, existingConfigFile))) {
|
|
134
|
+
const existingConfigPath = join(this.targetDirectory, existingConfigFile)
|
|
135
|
+
|
|
136
|
+
this.existingConfigRaw = await this.readConfigurationFile(existingConfigPath, 'root', this.targetDirectory)
|
|
137
|
+
/*
|
|
138
|
+
The file as written, kept so that editing it can be an edit. Re-emitting from the evaluated
|
|
139
|
+
configuration would bake this machine's environment into the user's root -- a reference to
|
|
140
|
+
PLT_SERVER_LOGGER_LEVEL becomes whatever it happens to resolve to here -- and take every
|
|
141
|
+
comment with it.
|
|
142
|
+
*/
|
|
143
|
+
this.existingConfigFile = existingConfigFile
|
|
144
|
+
this.existingConfigSource = await readFile(existingConfigPath, 'utf-8')
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
A legacy root is refused with the hint every other v4 entry point gives, rather than loaded
|
|
148
|
+
and rewritten: continuing wrote the v4 module form over the .json file, and migrate is the
|
|
149
|
+
tool whose whole job is that conversion -- with the refusals and divergence reports the
|
|
150
|
+
wizard has no way to make.
|
|
151
|
+
*/
|
|
152
|
+
if (existingConfigFile.endsWith('.json')) {
|
|
153
|
+
throw new LegacyConfigurationFileError(existingConfigPath)
|
|
154
|
+
} else {
|
|
155
|
+
/*
|
|
156
|
+
Derived from the root rather than loaded through it. A full load evaluates every
|
|
157
|
+
application's configuration, and those import their capabilities -- so the wizard would
|
|
158
|
+
need the project's dependencies installed before it could read the project, which is
|
|
159
|
+
exactly the state it is called in to fix.
|
|
160
|
+
*/
|
|
161
|
+
const { PLT_ROOT, ...existingEnvironment } = await readEnvFile(this.targetDirectory)
|
|
162
|
+
|
|
163
|
+
this.config.env = existingEnvironment
|
|
164
|
+
this.existingConfig = this.existingConfigRaw
|
|
165
|
+
this.existingApplications = await this.#listExistingApplications(this.existingConfigRaw)
|
|
166
|
+
}
|
|
134
167
|
|
|
135
168
|
this.updateRuntimeConfig(this.existingConfigRaw)
|
|
136
169
|
this.updateRuntimeEnv(await readFile(join(this.targetDirectory, '.env'), 'utf-8'))
|
|
@@ -171,6 +204,30 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
171
204
|
}
|
|
172
205
|
}
|
|
173
206
|
|
|
207
|
+
/*
|
|
208
|
+
The root is the one v4 configuration that is not a capability factory call: it imports
|
|
209
|
+
`defineConfig` from wattpm, whose whole job is to type its argument.
|
|
210
|
+
*/
|
|
211
|
+
/*
|
|
212
|
+
The stamped plain-object form rather than an imported `defineConfig`, which is the machine-writer
|
|
213
|
+
case the `$schema` marker exists for: the wizard writes this file before anything is installed
|
|
214
|
+
and reads it back on a later run, so a root that imported `wattpm` could not be read in the
|
|
215
|
+
state that produced it. A person editing the file afterwards can switch to the imported form.
|
|
216
|
+
*/
|
|
217
|
+
serializeConfigFile (config) {
|
|
218
|
+
/*
|
|
219
|
+
Unstamped while this line is still 3.x. The marker is a version declaration, and the loader
|
|
220
|
+
refuses a v3 one outright -- correctly, since that is how it catches a configuration nobody
|
|
221
|
+
migrated. It becomes writable, and worth writing, at 4.0.0.
|
|
222
|
+
*/
|
|
223
|
+
const { $schema, module: _module, ...rest } = config
|
|
224
|
+
// Resolved like every other scaffolded value: the root's placeholders are the same kind of
|
|
225
|
+
// thing, and leaving them would write the placeholder's own text where an expression belongs.
|
|
226
|
+
const resolved = this.resolveScaffoldedPlaceholders(rest)
|
|
227
|
+
|
|
228
|
+
return `export default ${serializeConfiguration(resolved)}\n`
|
|
229
|
+
}
|
|
230
|
+
|
|
174
231
|
async _getConfigFileContents () {
|
|
175
232
|
const config = {
|
|
176
233
|
$schema: `https://schemas.platformatic.dev/wattpm/${this.platformaticVersion}.json`,
|
|
@@ -236,7 +293,11 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
236
293
|
}
|
|
237
294
|
let basePath
|
|
238
295
|
if (this.existingConfig) {
|
|
239
|
-
|
|
296
|
+
/*
|
|
297
|
+
Resolved against the project, because the configuration says it relative to itself. The v3
|
|
298
|
+
loader handed back an absolute path; reading the file directly hands back what it says.
|
|
299
|
+
*/
|
|
300
|
+
basePath = resolve(this.targetDirectory, this.existingConfig.autoload.path)
|
|
240
301
|
} else {
|
|
241
302
|
basePath = join(this.targetDirectory, this.config.autoload || this.applicationsFolder)
|
|
242
303
|
}
|
|
@@ -287,11 +348,38 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
287
348
|
return (await import(pathToFileURL(fileToImport))).Generator
|
|
288
349
|
}
|
|
289
350
|
|
|
351
|
+
/*
|
|
352
|
+
Which applications a root already describes: the entries it lists, and the directories its
|
|
353
|
+
autoload would discover. Neither needs an application's own configuration, which is the point.
|
|
354
|
+
*/
|
|
355
|
+
async #listExistingApplications (config) {
|
|
356
|
+
const ids = (config.applications ?? config.services ?? config.web ?? []).map(entry => entry.id).filter(Boolean)
|
|
357
|
+
|
|
358
|
+
if (!config.autoload?.path) {
|
|
359
|
+
return ids
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const exclude = config.autoload.exclude ?? []
|
|
363
|
+
const directory = join(this.targetDirectory, config.autoload.path)
|
|
364
|
+
|
|
365
|
+
for (const entry of await readdir(directory, { withFileTypes: true }).catch(() => [])) {
|
|
366
|
+
if (entry.isDirectory() && !exclude.includes(entry.name) && !ids.includes(entry.name)) {
|
|
367
|
+
ids.push(entry.name)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return ids
|
|
372
|
+
}
|
|
373
|
+
|
|
290
374
|
async loadFromDir () {
|
|
291
375
|
const output = {
|
|
292
376
|
applications: []
|
|
293
377
|
}
|
|
294
|
-
const runtimePkgConfigFileData =
|
|
378
|
+
const runtimePkgConfigFileData = await this.readConfigurationFile(
|
|
379
|
+
join(this.targetDirectory, await findAnyConfigurationFile(this.targetDirectory)),
|
|
380
|
+
'root',
|
|
381
|
+
this.targetDirectory
|
|
382
|
+
)
|
|
295
383
|
const applicationsPath = join(this.targetDirectory, runtimePkgConfigFileData.autoload.path)
|
|
296
384
|
|
|
297
385
|
// load all applications
|
|
@@ -302,8 +390,14 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
302
390
|
const dirStat = await stat(currentApplicationPath)
|
|
303
391
|
if (dirStat.isDirectory()) {
|
|
304
392
|
// load the application config
|
|
305
|
-
const configFile = await
|
|
306
|
-
|
|
393
|
+
const configFile = await findAnyConfigurationFile(currentApplicationPath)
|
|
394
|
+
// The environment is the runtime's: an application's expressions read the project's .env,
|
|
395
|
+
// which lives at the root beside the configuration that autoloads it.
|
|
396
|
+
const applicationPltJson = await this.readConfigurationFile(
|
|
397
|
+
join(currentApplicationPath, configFile),
|
|
398
|
+
'application',
|
|
399
|
+
this.targetDirectory
|
|
400
|
+
)
|
|
307
401
|
// get module to load
|
|
308
402
|
const template = applicationPltJson.module || getApplicationTemplateFromSchemaUrl(applicationPltJson.$schema)
|
|
309
403
|
const Generator = await this._getGeneratorForTemplate(currentApplicationPath, template)
|
|
@@ -350,8 +444,12 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
350
444
|
|
|
351
445
|
// delete dependencies
|
|
352
446
|
const applicationPath = join(this.targetDirectory, this.applicationsFolder, s.name)
|
|
353
|
-
const configFile = await
|
|
354
|
-
const applicationPackageJson =
|
|
447
|
+
const configFile = await findAnyConfigurationFile(applicationPath)
|
|
448
|
+
const applicationPackageJson = await this.readConfigurationFile(
|
|
449
|
+
join(applicationPath, configFile),
|
|
450
|
+
'application',
|
|
451
|
+
this.targetDirectory
|
|
452
|
+
)
|
|
355
453
|
if (applicationPackageJson.plugins && applicationPackageJson.plugins.packages) {
|
|
356
454
|
applicationPackageJson.plugins.packages.forEach(p => {
|
|
357
455
|
delete currrentPackageJson.dependencies[p.name]
|
|
@@ -468,14 +566,67 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
468
566
|
}
|
|
469
567
|
|
|
470
568
|
updateRuntimeConfig (config) {
|
|
569
|
+
this.generatedConfig = config
|
|
570
|
+
|
|
571
|
+
// Editing what is there, rather than replacing it with a rendering of what it evaluated to.
|
|
572
|
+
if (this.existingConfigSource && !this.existingConfigFile.endsWith('.json')) {
|
|
573
|
+
this.addFile({
|
|
574
|
+
path: '',
|
|
575
|
+
file: this.existingConfigFile,
|
|
576
|
+
contents: this.#editExistingRoot(config),
|
|
577
|
+
tags: ['runtime-config']
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
return
|
|
581
|
+
}
|
|
582
|
+
|
|
471
583
|
this.addFile({
|
|
472
584
|
path: '',
|
|
473
|
-
file: this.
|
|
474
|
-
contents:
|
|
585
|
+
file: this.existingConfigFile ?? this.configurationFileName(),
|
|
586
|
+
contents: this.serializeConfigFile(config),
|
|
475
587
|
tags: ['runtime-config']
|
|
476
588
|
})
|
|
477
589
|
}
|
|
478
590
|
|
|
591
|
+
/*
|
|
592
|
+
An existing root is edited rather than rewritten, so that everything it says survives -- see
|
|
593
|
+
`appendApplications`. A shape it cannot edit is reported here rather than papered over by
|
|
594
|
+
writing the file from the loaded configuration, which would be a silent rewrite of every
|
|
595
|
+
reference the root contains.
|
|
596
|
+
*/
|
|
597
|
+
#editExistingRoot (config) {
|
|
598
|
+
/*
|
|
599
|
+
The evaluated configuration arrives with autoload already expanded, so its application list
|
|
600
|
+
holds every discovered directory as an explicit entry with an absolute machine path.
|
|
601
|
+
Appending those would bake this machine's layout into the user's root -- and duplicate what
|
|
602
|
+
autoload will discover again on the next boot. Only entries autoload does not cover belong in
|
|
603
|
+
the file: the ones this run scaffolds outside the autoload directory, or a root with no
|
|
604
|
+
autoload at all.
|
|
605
|
+
*/
|
|
606
|
+
const autoloadPath = config.autoload?.path
|
|
607
|
+
? resolve(this.targetDirectory, config.autoload.path) + sep
|
|
608
|
+
: null
|
|
609
|
+
const additions = listedApplications(config).filter(entry => {
|
|
610
|
+
if (!autoloadPath || typeof entry.path !== 'string') {
|
|
611
|
+
return true
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return !resolve(this.targetDirectory, entry.path).startsWith(autoloadPath)
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
const edited = appendApplications(this.existingConfigSource, additions, entry =>
|
|
618
|
+
this.resolveScaffoldedPlaceholders(entry, rawSource)
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
if (edited === null) {
|
|
622
|
+
throw new Error(
|
|
623
|
+
`Cannot add applications to ${this.existingConfigFile}: its default export is not a configuration object this tool can edit. Add them by hand.`
|
|
624
|
+
)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return edited
|
|
628
|
+
}
|
|
629
|
+
|
|
479
630
|
updateRuntimeEnv (contents) {
|
|
480
631
|
this.addFile({
|
|
481
632
|
path: '',
|
|
@@ -551,15 +702,28 @@ export class WrappedGenerator extends BaseGenerator {
|
|
|
551
702
|
}
|
|
552
703
|
|
|
553
704
|
async #createConfigFile () {
|
|
705
|
+
/*
|
|
706
|
+
The wrapped single-app root. v3 spelled the runtime settings under a `runtime` key inside the
|
|
707
|
+
application's own configuration; v4 has no such block, so they are the root's own and the
|
|
708
|
+
application is the singular shorthand.
|
|
709
|
+
*/
|
|
710
|
+
/*
|
|
711
|
+
The application is the root's own, named by the singular shorthand. Without it the root
|
|
712
|
+
describes a runtime with nothing in it -- it would load, and start none of the code it was
|
|
713
|
+
wrapped around.
|
|
714
|
+
|
|
715
|
+
Its capability is spelled as a plain object rather than a factory call because this file is
|
|
716
|
+
read before anything is installed, and an import cannot be resolved in that state.
|
|
717
|
+
*/
|
|
554
718
|
const config = {
|
|
555
|
-
|
|
556
|
-
|
|
719
|
+
...this.resolveScaffoldedPlaceholders(getRuntimeWrappableProperties()),
|
|
720
|
+
application: { config: { module: this.module } }
|
|
557
721
|
}
|
|
558
722
|
|
|
559
723
|
this.addFile({
|
|
560
724
|
path: '',
|
|
561
|
-
file:
|
|
562
|
-
contents:
|
|
725
|
+
file: this.configurationFileName(),
|
|
726
|
+
contents: `export default ${serializeConfiguration(config)}\n`
|
|
563
727
|
})
|
|
564
728
|
}
|
|
565
729
|
|
package/lib/logger.js
CHANGED
|
@@ -191,13 +191,13 @@ export async function createLogger (config) {
|
|
|
191
191
|
|
|
192
192
|
const multiStream = pino.multistream([{ stream: cliStream, level: loggerConfig.level }])
|
|
193
193
|
|
|
194
|
-
if (config.
|
|
194
|
+
if (config.tracing && config.logger.openTelemetryExporter) {
|
|
195
195
|
const openTelemetryTransport = pino.transport({
|
|
196
196
|
target: 'pino-opentelemetry-transport',
|
|
197
197
|
options: {
|
|
198
198
|
resourceAttributes: {
|
|
199
|
-
'service.name': config.
|
|
200
|
-
'service.version': config.
|
|
199
|
+
'service.name': config.tracing.applicationName,
|
|
200
|
+
'service.version': config.tracing.version
|
|
201
201
|
},
|
|
202
202
|
logRecordProcessorOptions: [
|
|
203
203
|
{
|
package/lib/management-api.js
CHANGED
|
@@ -99,18 +99,6 @@ export async function managementApiPlugin (app, opts) {
|
|
|
99
99
|
return runtime.getRuntimeMetadata()
|
|
100
100
|
})
|
|
101
101
|
|
|
102
|
-
app.get('/config', quiet, async request => {
|
|
103
|
-
const metadata = request.query.metadata === 'true'
|
|
104
|
-
const rawConfig = await runtime.getRuntimeConfig(metadata)
|
|
105
|
-
|
|
106
|
-
if (metadata) {
|
|
107
|
-
const { [kMetadata]: __metadata, ...config } = rawConfig
|
|
108
|
-
return { ...config, __metadata }
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return rawConfig
|
|
112
|
-
})
|
|
113
|
-
|
|
114
102
|
app.get('/env', quiet, async () => {
|
|
115
103
|
return { ...process.env, ...runtime.getRuntimeEnv() }
|
|
116
104
|
})
|
|
@@ -301,7 +289,10 @@ export async function managementApiPlugin (app, opts) {
|
|
|
301
289
|
})
|
|
302
290
|
|
|
303
291
|
app.get('/metrics', quiet, async (req, reply) => {
|
|
304
|
-
|
|
292
|
+
// The live read: this needs one boolean, and the public getter builds a frozen snapshot of the
|
|
293
|
+
// whole configuration -- which is the right shape to hand a consumer and the wrong one to pay
|
|
294
|
+
// for on a scrape.
|
|
295
|
+
const config = runtime.getRuntimeConfig(true)
|
|
305
296
|
|
|
306
297
|
if (config.metrics?.enabled === false) {
|
|
307
298
|
reply.code(501)
|
|
@@ -324,53 +315,6 @@ export async function managementApiPlugin (app, opts) {
|
|
|
324
315
|
return metrics
|
|
325
316
|
})
|
|
326
317
|
|
|
327
|
-
// TODO: Remove in next major version - deprecated endpoint
|
|
328
|
-
app.get('/metrics/live', { ...quiet, websocket: true }, async socket => {
|
|
329
|
-
const config = await runtime.getRuntimeConfig()
|
|
330
|
-
|
|
331
|
-
if (config.metrics?.enabled === false) {
|
|
332
|
-
socket.send(
|
|
333
|
-
JSON.stringify({
|
|
334
|
-
statusCode: 501,
|
|
335
|
-
error: 'Not Implemented',
|
|
336
|
-
message: 'Metrics are disabled.'
|
|
337
|
-
}),
|
|
338
|
-
() => {
|
|
339
|
-
socket.close()
|
|
340
|
-
}
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
return
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const pollAndSendMetrics = async () => {
|
|
347
|
-
try {
|
|
348
|
-
const metrics = await runtime.getFormattedMetrics()
|
|
349
|
-
if (metrics) {
|
|
350
|
-
const serializedMetrics = JSON.stringify(metrics)
|
|
351
|
-
socket.send(serializedMetrics + '\n')
|
|
352
|
-
}
|
|
353
|
-
} catch (error) {
|
|
354
|
-
// If there's an error, stop polling and close the connection
|
|
355
|
-
clearInterval(pollingInterval)
|
|
356
|
-
socket.close()
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Poll every second
|
|
361
|
-
const pollingInterval = setInterval(pollAndSendMetrics, 1000)
|
|
362
|
-
|
|
363
|
-
// Send initial metrics immediately
|
|
364
|
-
await pollAndSendMetrics()
|
|
365
|
-
|
|
366
|
-
const cleanup = () => {
|
|
367
|
-
clearInterval(pollingInterval)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
socket.on('error', cleanup)
|
|
371
|
-
socket.on('close', cleanup)
|
|
372
|
-
})
|
|
373
|
-
|
|
374
318
|
app.get('/logs/live', { ...quiet, websocket: true }, async socket => {
|
|
375
319
|
runtime.addLoggerDestination(createWebSocketStream(socket))
|
|
376
320
|
})
|
package/lib/policies.js
CHANGED
|
@@ -21,3 +21,30 @@ export function createChannelCreationHook (config) {
|
|
|
21
21
|
return !forbidden.has(`${first.toLowerCase()}:${second.toLowerCase()}`)
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
export function createTargetPermissionHook (config) {
|
|
26
|
+
const denyList = config.policies?.deny
|
|
27
|
+
|
|
28
|
+
if (typeof denyList === 'undefined') {
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const forbidden = new Set()
|
|
33
|
+
|
|
34
|
+
for (let [first, unalloweds] of Object.entries(denyList)) {
|
|
35
|
+
for (let second of unalloweds) {
|
|
36
|
+
first = first.toLowerCase()
|
|
37
|
+
second = second.toLowerCase()
|
|
38
|
+
|
|
39
|
+
forbidden.add(`${first}:${second}`)
|
|
40
|
+
forbidden.add(`${second}:${first}`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return function targetPermissionHook (req, target) {
|
|
45
|
+
const source = config.applicationId ?? req?.headers?.['x-plt-application-id']
|
|
46
|
+
const destination = target?.metadata?.applicationId
|
|
47
|
+
|
|
48
|
+
return !source || !destination || !forbidden.has(`${source.toLowerCase()}:${destination.toLowerCase()}`)
|
|
49
|
+
}
|
|
50
|
+
}
|