@toa.io/extensions.origins 0.10.0-dev.9 → 0.20.0-alpha.1
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/package.json +17 -11
- package/readme.md +70 -67
- package/schemas/annotation.cos.yaml +3 -0
- package/schemas/manifest.cos.yaml +4 -0
- package/source/Factory.ts +88 -0
- package/source/annotation.test.ts +150 -0
- package/source/annotation.ts +83 -0
- package/source/extension.test.ts +161 -0
- package/source/extension.ts +60 -0
- package/source/index.ts +2 -0
- package/source/manifest.test.ts +30 -0
- package/source/manifest.ts +11 -0
- package/source/protocols/amqp/.test/aspect.fixtures.js +1 -1
- package/source/protocols/amqp/.test/mock.comq.js +2 -2
- package/source/protocols/amqp/aspect.js +17 -24
- package/source/protocols/amqp/deployment.js +8 -2
- package/source/protocols/http/.aspect/permissions.js +20 -15
- package/source/protocols/http/aspect.js +18 -38
- package/source/protocols/index.ts +16 -0
- package/transpiled/Factory.d.ts +17 -0
- package/transpiled/Factory.js +65 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/annotation.d.ts +10 -0
- package/transpiled/annotation.js +91 -0
- package/transpiled/annotation.js.map +1 -0
- package/transpiled/constants.d.ts +1 -0
- package/transpiled/constants.js +3 -0
- package/transpiled/constants.js.map +1 -0
- package/transpiled/env.d.ts +5 -0
- package/transpiled/env.js +40 -0
- package/transpiled/env.js.map +1 -0
- package/transpiled/extension.d.ts +10 -0
- package/transpiled/extension.js +49 -0
- package/transpiled/extension.js.map +1 -0
- package/transpiled/index.d.ts +2 -0
- package/transpiled/index.js +9 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/manifest.d.ts +2 -0
- package/transpiled/manifest.js +36 -0
- package/transpiled/manifest.js.map +1 -0
- package/transpiled/protocols/amqp/aspect.d.ts +11 -0
- package/transpiled/protocols/amqp/aspect.js +53 -0
- package/transpiled/protocols/amqp/aspect.js.map +1 -0
- package/transpiled/protocols/amqp/deployment.d.ts +5 -0
- package/transpiled/protocols/amqp/deployment.js +55 -0
- package/transpiled/protocols/amqp/deployment.js.map +1 -0
- package/transpiled/protocols/amqp/id.d.ts +1 -0
- package/transpiled/protocols/amqp/id.js +3 -0
- package/transpiled/protocols/amqp/id.js.map +1 -0
- package/transpiled/protocols/amqp/index.d.ts +5 -0
- package/transpiled/protocols/amqp/index.js +10 -0
- package/transpiled/protocols/amqp/index.js.map +1 -0
- package/transpiled/protocols/amqp/protocols.d.ts +2 -0
- package/transpiled/protocols/amqp/protocols.js +3 -0
- package/transpiled/protocols/amqp/protocols.js.map +1 -0
- package/transpiled/protocols/http/.aspect/permissions.d.ts +7 -0
- package/transpiled/protocols/http/.aspect/permissions.js +52 -0
- package/transpiled/protocols/http/.aspect/permissions.js.map +1 -0
- package/transpiled/protocols/http/aspect.d.ts +11 -0
- package/transpiled/protocols/http/aspect.js +88 -0
- package/transpiled/protocols/http/aspect.js.map +1 -0
- package/transpiled/protocols/http/id.d.ts +1 -0
- package/transpiled/protocols/http/id.js +3 -0
- package/transpiled/protocols/http/id.js.map +1 -0
- package/transpiled/protocols/http/index.d.ts +4 -0
- package/transpiled/protocols/http/index.js +8 -0
- package/transpiled/protocols/http/index.js.map +1 -0
- package/transpiled/protocols/http/protocols.d.ts +2 -0
- package/transpiled/protocols/http/protocols.js +3 -0
- package/transpiled/protocols/http/protocols.js.map +1 -0
- package/transpiled/protocols/index.d.ts +9 -0
- package/transpiled/protocols/index.js +10 -0
- package/transpiled/protocols/index.js.map +1 -0
- package/transpiled/tsconfig.tsbuildinfo +1 -0
- package/tsconfig.json +12 -0
- package/source/.deployment/index.js +0 -5
- package/source/.deployment/uris.js +0 -37
- package/source/.test/constants.js +0 -3
- package/source/.test/deployment.fixtures.js +0 -20
- package/source/.test/factory.fixtures.js +0 -13
- package/source/deployment.js +0 -28
- package/source/deployment.test.js +0 -173
- package/source/factory.js +0 -46
- package/source/factory.test.js +0 -140
- package/source/index.js +0 -9
- package/source/manifest.js +0 -38
- package/source/manifest.test.js +0 -75
- package/source/protocols/amqp/aspect.test.js +0 -119
- package/source/protocols/http/aspect.test.js +0 -239
- package/source/protocols/index.js +0 -6
- package/source/schemas/annotations.cos.yaml +0 -1
- package/source/schemas/index.js +0 -8
- package/source/schemas/manifest.cos.yaml +0 -2
- package/types/amqp.d.ts +0 -9
- package/types/deployment.d.ts +0 -7
- package/types/http.d.ts +0 -28
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { generate } from 'randomstring'
|
|
2
|
+
import { encode } from 'msgpackr'
|
|
3
|
+
import { Locator } from '@toa.io/core'
|
|
4
|
+
import { deployment, type Instance } from './extension'
|
|
5
|
+
import type { Annotation, Properties } from './annotation'
|
|
6
|
+
import type { Manifest } from './manifest'
|
|
7
|
+
import type { Dependency } from '@toa.io/operations'
|
|
8
|
+
|
|
9
|
+
const locator = new Locator(generate(), generate())
|
|
10
|
+
const NAMESPACE = locator.namespace.toUpperCase()
|
|
11
|
+
const NAME = locator.name.toUpperCase()
|
|
12
|
+
|
|
13
|
+
it('should deploy pointer variables', async () => {
|
|
14
|
+
const manifest: Manifest = { queue: null }
|
|
15
|
+
const instance = { locator, manifest } as unknown as Instance
|
|
16
|
+
const url = 'amqp://host-' + generate()
|
|
17
|
+
|
|
18
|
+
const annotation: Annotation = {
|
|
19
|
+
[locator.id]: {
|
|
20
|
+
queue: url
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const deploy = deployment([instance], annotation)
|
|
25
|
+
|
|
26
|
+
const expected: Dependency = {
|
|
27
|
+
variables: {
|
|
28
|
+
[locator.label]: expect.arrayContaining([
|
|
29
|
+
{
|
|
30
|
+
name: `TOA_ORIGINS_${NAMESPACE}_${NAME}_QUEUE`,
|
|
31
|
+
value: url
|
|
32
|
+
}
|
|
33
|
+
])
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
expect(deploy)
|
|
38
|
+
.toMatchObject(expected)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should deploy default origin', async () => {
|
|
42
|
+
const example = 'http://api.example.com'
|
|
43
|
+
const manifest: Manifest = { example }
|
|
44
|
+
const instance = { locator, manifest } as unknown as Instance
|
|
45
|
+
const annotation: Annotation = {}
|
|
46
|
+
const deploy = deployment([instance], annotation)
|
|
47
|
+
|
|
48
|
+
const expected: Dependency = {
|
|
49
|
+
variables: {
|
|
50
|
+
[locator.label]: expect.arrayContaining([
|
|
51
|
+
{
|
|
52
|
+
name: `TOA_ORIGINS_${NAMESPACE}_${NAME}_EXAMPLE`,
|
|
53
|
+
value: example
|
|
54
|
+
}
|
|
55
|
+
])
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
expect(deploy)
|
|
60
|
+
.toMatchObject(expected)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should deploy properties', async () => {
|
|
64
|
+
const manifest: Manifest = {}
|
|
65
|
+
const instance = { locator, manifest } as unknown as Instance
|
|
66
|
+
const properties: Properties = {
|
|
67
|
+
'.http': {
|
|
68
|
+
'/^http:\\/\\/\\w+api.example.com/': true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const annotation: Annotation = {
|
|
73
|
+
[locator.id]: properties
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const deploy = deployment([instance], annotation)
|
|
77
|
+
const value = encode(properties).toString('base64')
|
|
78
|
+
|
|
79
|
+
const expected: Dependency = {
|
|
80
|
+
variables: {
|
|
81
|
+
[locator.label]: expect.arrayContaining([
|
|
82
|
+
{
|
|
83
|
+
name: `TOA_ORIGINS_${NAMESPACE}_${NAME}__PROPERTIES`,
|
|
84
|
+
value
|
|
85
|
+
}
|
|
86
|
+
])
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
expect(deploy)
|
|
91
|
+
.toMatchObject(expected)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should deploy properties with null manifest', async () => {
|
|
95
|
+
const manifest: Manifest = null
|
|
96
|
+
const instance = { locator, manifest } as unknown as Instance
|
|
97
|
+
const properties: Properties = {
|
|
98
|
+
'.http': {
|
|
99
|
+
'/^http:\\/\\/\\w+api.example.com/': true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const annotation: Annotation = {
|
|
104
|
+
[locator.id]: properties
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const deploy = deployment([instance], annotation)
|
|
108
|
+
const value = encode(properties).toString('base64')
|
|
109
|
+
|
|
110
|
+
const expected: Dependency = {
|
|
111
|
+
variables: {
|
|
112
|
+
[locator.label]: expect.arrayContaining([
|
|
113
|
+
{
|
|
114
|
+
name: `TOA_ORIGINS_${NAMESPACE}_${NAME}__PROPERTIES`,
|
|
115
|
+
value
|
|
116
|
+
}
|
|
117
|
+
])
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
expect(deploy)
|
|
122
|
+
.toMatchObject(expected)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should deploy credentials for amqp', async () => {
|
|
126
|
+
const manifest: Manifest = { queue: null }
|
|
127
|
+
const instance = { locator, manifest } as unknown as Instance
|
|
128
|
+
const url = 'amqp://host-' + generate()
|
|
129
|
+
|
|
130
|
+
const annotation: Annotation = {
|
|
131
|
+
[locator.id]: {
|
|
132
|
+
queue: url
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const deploy = deployment([instance], annotation)
|
|
137
|
+
|
|
138
|
+
const expected: Dependency = {
|
|
139
|
+
variables: {
|
|
140
|
+
[locator.label]: expect.arrayContaining([
|
|
141
|
+
{
|
|
142
|
+
name: `TOA_ORIGINS_${NAMESPACE}_${NAME}_QUEUE_USERNAME`,
|
|
143
|
+
secret: {
|
|
144
|
+
name: `toa-origins-${locator.label}-queue`,
|
|
145
|
+
key: 'username'
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: `TOA_ORIGINS_${NAMESPACE}_${NAME}_QUEUE_PASSWORD`,
|
|
150
|
+
secret: {
|
|
151
|
+
name: `toa-origins-${locator.label}-queue`,
|
|
152
|
+
key: 'password'
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
])
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
expect(deploy)
|
|
160
|
+
.toMatchObject(expected)
|
|
161
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { encode } from 'msgpackr'
|
|
2
|
+
import { createVariables, type Request } from '@toa.io/pointer'
|
|
3
|
+
import { merge } from '@toa.io/generic'
|
|
4
|
+
import { normalize, split, type Annotation, type Properties, type Origins } from './annotation'
|
|
5
|
+
import { type Manifest, validate } from './manifest'
|
|
6
|
+
import type { Locator } from '@toa.io/core'
|
|
7
|
+
import type { Dependency, Variables } from '@toa.io/operations'
|
|
8
|
+
import type { context } from '@toa.io/norm'
|
|
9
|
+
|
|
10
|
+
export function deployment (instances: Instance[], annotation: Annotation = {}): Dependency {
|
|
11
|
+
normalize(instances, annotation)
|
|
12
|
+
|
|
13
|
+
const variables: Variables = {}
|
|
14
|
+
|
|
15
|
+
for (const instance of instances) {
|
|
16
|
+
const component = annotation[instance.locator.id]
|
|
17
|
+
const { origins, properties } = split(component)
|
|
18
|
+
const instanceVariables = createInstanceVariables(instance, origins)
|
|
19
|
+
const propertiesVariable = createPropertiesVariable(instance.locator, properties)
|
|
20
|
+
|
|
21
|
+
merge(variables, instanceVariables)
|
|
22
|
+
merge(variables, propertiesVariable)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { variables }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function manifest (manifest: Manifest): Manifest {
|
|
29
|
+
validate(manifest)
|
|
30
|
+
|
|
31
|
+
return manifest
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createInstanceVariables (instance: Instance, origins: Origins): Variables {
|
|
35
|
+
if (instance.manifest === null) return {}
|
|
36
|
+
|
|
37
|
+
const label: string = instance.locator.label
|
|
38
|
+
const id = ID_PREFIX + label
|
|
39
|
+
const selectors = Object.keys(instance.manifest)
|
|
40
|
+
const request: Request = { group: label, selectors }
|
|
41
|
+
|
|
42
|
+
return createVariables(id, origins, [request])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createPropertiesVariable (locator: Locator, properties: Properties): Variables {
|
|
46
|
+
const name = ENV_PREFIX + locator.uppercase + PROPERTIES_SUFFIX
|
|
47
|
+
const value = encode(properties).toString('base64')
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
[locator.label]: [
|
|
51
|
+
{ name, value }
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const ID_PREFIX = 'origins-'
|
|
57
|
+
export const ENV_PREFIX = 'TOA_ORIGINS_'
|
|
58
|
+
export const PROPERTIES_SUFFIX = '__PROPERTIES'
|
|
59
|
+
|
|
60
|
+
export type Instance = context.Dependency<Manifest>
|
package/source/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Manifest, validate } from './manifest'
|
|
2
|
+
|
|
3
|
+
let manifest: Manifest
|
|
4
|
+
|
|
5
|
+
it('should not throw if valid', async () => {
|
|
6
|
+
manifest = {
|
|
7
|
+
one: 'http://localhost',
|
|
8
|
+
two: null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
expect(run).not.toThrow()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('should throw if not a uri', async () => {
|
|
15
|
+
manifest = {
|
|
16
|
+
one: 'not a URI'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
expect(run).toThrow('must match format')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should not throw on null manifest', async () => {
|
|
23
|
+
manifest = null
|
|
24
|
+
|
|
25
|
+
expect(run).not.toThrow()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
function run (): void {
|
|
29
|
+
validate(manifest)
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import * as schemas from '@toa.io/schemas'
|
|
3
|
+
|
|
4
|
+
export function validate (manifest: Manifest): void {
|
|
5
|
+
if (manifest !== null) schema.validate(manifest)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const path = resolve(__dirname, '../schemas/manifest.cos.yaml')
|
|
9
|
+
const schema = schemas.schema(path)
|
|
10
|
+
|
|
11
|
+
export type Manifest = Record<string, string | null> | null
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { generate } = require('randomstring')
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const assert = jest.fn(async () => ({
|
|
6
6
|
emit: jest.fn(async () => undefined),
|
|
7
7
|
request: jest.fn(async () => generate()),
|
|
8
8
|
reply: jest.fn(async () => undefined),
|
|
@@ -10,4 +10,4 @@ const connect = jest.fn(async () => ({
|
|
|
10
10
|
close: jest.fn(async () => undefined)
|
|
11
11
|
}))
|
|
12
12
|
|
|
13
|
-
exports.
|
|
13
|
+
exports.assert = assert
|
|
@@ -1,33 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { assert } = require('comq')
|
|
4
4
|
const { Connector } = require('@toa.io/core')
|
|
5
|
-
const
|
|
5
|
+
const protocol = require('./index')
|
|
6
6
|
|
|
7
|
-
const { id } = require('./id')
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @implements {toa.origins.amqp.Aspect}
|
|
11
|
-
*/
|
|
12
7
|
class Aspect extends Connector {
|
|
13
|
-
name = id
|
|
14
|
-
|
|
15
|
-
#
|
|
8
|
+
name = protocol.id
|
|
9
|
+
|
|
10
|
+
#resolve
|
|
16
11
|
|
|
17
12
|
/** @type {Record<string, Partial<comq.IO>>} */
|
|
18
13
|
#origins = {}
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
* @param {toa.origins.Manifest} manifest
|
|
22
|
-
*/
|
|
23
|
-
constructor (manifest) {
|
|
15
|
+
constructor (resolve) {
|
|
24
16
|
super()
|
|
25
17
|
|
|
26
|
-
this.#
|
|
18
|
+
this.#resolve = resolve
|
|
27
19
|
}
|
|
28
20
|
|
|
29
21
|
async open () {
|
|
30
|
-
const
|
|
22
|
+
const cfg = await this.#resolve()
|
|
23
|
+
const promises = Object.entries(cfg.origins).map(this.#open)
|
|
31
24
|
|
|
32
25
|
await Promise.all(promises)
|
|
33
26
|
}
|
|
@@ -39,12 +32,15 @@ class Aspect extends Connector {
|
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
async invoke (origin, method, ...args) {
|
|
35
|
+
if (this.#origins[origin]?.[method] === undefined) {
|
|
36
|
+
throw new Error(`Origin "${origin}" or method "${method}" is undefined`)
|
|
37
|
+
}
|
|
38
|
+
|
|
42
39
|
return this.#origins[origin][method](...args)
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
#open = async ([origin,
|
|
46
|
-
const
|
|
47
|
-
const io = await connect(...references)
|
|
42
|
+
#open = async ([origin, references]) => {
|
|
43
|
+
const io = await assert(...references)
|
|
48
44
|
|
|
49
45
|
this.#origins[origin] = restrict(io)
|
|
50
46
|
}
|
|
@@ -67,11 +63,8 @@ function restrict (io) {
|
|
|
67
63
|
}
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*/
|
|
73
|
-
function create (manifest) {
|
|
74
|
-
return new Aspect(manifest)
|
|
66
|
+
function create (resolve) {
|
|
67
|
+
return new Aspect(resolve)
|
|
75
68
|
}
|
|
76
69
|
|
|
77
70
|
exports.create = create
|
|
@@ -15,9 +15,13 @@ function deployment (instances) {
|
|
|
15
15
|
const secrets = []
|
|
16
16
|
|
|
17
17
|
for (const [origin, reference] of Object.entries(manifest)) {
|
|
18
|
-
|
|
18
|
+
let protocol
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
const match = reference.match(RX)
|
|
21
|
+
|
|
22
|
+
if (match !== null) protocol = match.groups.protocol
|
|
23
|
+
|
|
24
|
+
if (protocols.includes(protocol)) {
|
|
21
25
|
const originSecrets = createSecrets(locator, origin)
|
|
22
26
|
|
|
23
27
|
secrets.push(...originSecrets)
|
|
@@ -60,4 +64,6 @@ function createSecret (locator, origin, property) {
|
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
const RX = /^(?<protocol>\w{1,12}:)/
|
|
68
|
+
|
|
63
69
|
exports.deployment = deployment
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
class Permissions {
|
|
7
|
-
#default = process.env.TOA_DEV === '1'
|
|
3
|
+
const { echo } = require('@toa.io/generic')
|
|
4
|
+
const { Connector } = require('@toa.io/core')
|
|
8
5
|
|
|
6
|
+
class Permissions extends Connector {
|
|
9
7
|
/** @type {RegExp[]} */
|
|
10
8
|
#allowances = []
|
|
11
9
|
|
|
12
10
|
/** @type {RegExp[]} */
|
|
13
11
|
#denials = []
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
#resolve
|
|
14
|
+
|
|
15
|
+
constructor (resolve) {
|
|
16
|
+
super()
|
|
17
|
+
|
|
18
|
+
this.#resolve = resolve
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async open () {
|
|
22
|
+
const { properties } = await this.#resolve()
|
|
23
|
+
|
|
19
24
|
if (properties !== undefined) this.#parse(properties)
|
|
20
25
|
}
|
|
21
26
|
|
|
@@ -26,9 +31,7 @@ class Permissions {
|
|
|
26
31
|
|
|
27
32
|
const allowance = this.#allowances.findIndex((regexp) => regexp.test(url))
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return this.#default
|
|
34
|
+
return allowance !== -1
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
#parse (properties) {
|
|
@@ -44,7 +47,8 @@ class Permissions {
|
|
|
44
47
|
|
|
45
48
|
if (match === null) throw new Error(`'${key}' is not a regular expression`)
|
|
46
49
|
|
|
47
|
-
const
|
|
50
|
+
const expression = echo(match.groups.expression)
|
|
51
|
+
const regex = new RegExp(expression)
|
|
48
52
|
|
|
49
53
|
this.#addRule(regex, rule)
|
|
50
54
|
}
|
|
@@ -55,8 +59,9 @@ class Permissions {
|
|
|
55
59
|
* @param {boolean} rule
|
|
56
60
|
*/
|
|
57
61
|
#addRule (regex, rule) {
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
const rules = rule ? this.#allowances : this.#denials
|
|
63
|
+
|
|
64
|
+
rules.push(regex)
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
|
|
@@ -6,43 +6,38 @@ const { Connector } = require('@toa.io/core')
|
|
|
6
6
|
const { retry } = require('@toa.io/generic')
|
|
7
7
|
|
|
8
8
|
const { Permissions } = require('./.aspect/permissions')
|
|
9
|
-
const { id } = require('./id')
|
|
10
9
|
const protocols = require('./protocols')
|
|
10
|
+
const protocol = require('./index')
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* @implements {toa.origins.http.Aspect}
|
|
14
|
-
*/
|
|
15
12
|
class Aspect extends Connector {
|
|
16
13
|
/** @readonly */
|
|
17
|
-
name = id
|
|
14
|
+
name = protocol.id
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
#resolve
|
|
20
17
|
#origins
|
|
21
|
-
|
|
22
|
-
/** @type {toa.origins.http.Permissions} */
|
|
23
18
|
#permissions
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
* @param {toa.origins.Manifest} manifest
|
|
27
|
-
* @param {toa.origins.http.Permissions} permissions
|
|
28
|
-
*/
|
|
29
|
-
constructor (manifest, permissions) {
|
|
20
|
+
constructor (resolve, permissions) {
|
|
30
21
|
super()
|
|
31
22
|
|
|
32
|
-
this.#
|
|
23
|
+
this.#resolve = resolve
|
|
33
24
|
this.#permissions = permissions
|
|
25
|
+
|
|
26
|
+
this.depends(permissions)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async open () {
|
|
30
|
+
const { origins } = await this.#resolve()
|
|
31
|
+
|
|
32
|
+
this.#origins = origins
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
async invoke (name, path, request, options) {
|
|
37
36
|
let origin = this.#origins[name]
|
|
38
37
|
|
|
39
38
|
if (origin === undefined) {
|
|
40
|
-
if (isAbsoluteURL(/** @type {
|
|
41
|
-
|
|
42
|
-
/** @type {string} */ name,
|
|
43
|
-
/** @type {import('node-fetch').RequestInit} */ path
|
|
44
|
-
)
|
|
45
|
-
} else throw new Error(`Origin '${name}' is not defined`)
|
|
39
|
+
if (isAbsoluteURL(name)) return this.#invokeURL(name, /** @type {Request} */ path)
|
|
40
|
+
else throw new Error(`Origin '${name}' is not defined`)
|
|
46
41
|
}
|
|
47
42
|
|
|
48
43
|
// absolute urls are forbidden when using origins
|
|
@@ -55,23 +50,12 @@ class Aspect extends Connector {
|
|
|
55
50
|
return this.#request(url.href, request, options?.retry)
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
/**
|
|
59
|
-
* @param {string} url
|
|
60
|
-
* @param {import('node-fetch').RequestInit} request
|
|
61
|
-
* @return {Promise<void>}
|
|
62
|
-
*/
|
|
63
53
|
async #invokeURL (url, request) {
|
|
64
54
|
if (this.#permissions.test(url) === false) throw new Error(`URL '${url}' is not allowed`)
|
|
65
55
|
|
|
66
56
|
return this.#request(url, request)
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
/**
|
|
70
|
-
* @param {string} url
|
|
71
|
-
* @param {import('node-fetch').RequestInit} request
|
|
72
|
-
* @param {toa.generic.retry.Options} [options]
|
|
73
|
-
* @return {Promise<import('node-fetch').Response>}
|
|
74
|
-
*/
|
|
75
59
|
async #request (url, request, options) {
|
|
76
60
|
const call = () => fetch(url, request)
|
|
77
61
|
|
|
@@ -116,14 +100,10 @@ function isAbsoluteURL (path) {
|
|
|
116
100
|
|
|
117
101
|
const PLACEHOLDER = /\*/g
|
|
118
102
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
* @param {toa.origins.http.Properties} [properties]
|
|
122
|
-
*/
|
|
123
|
-
function create (manifest, properties) {
|
|
124
|
-
const permissions = new Permissions(properties)
|
|
103
|
+
function create (resolve) {
|
|
104
|
+
const permissions = new Permissions(resolve)
|
|
125
105
|
|
|
126
|
-
return new Aspect(
|
|
106
|
+
return new Aspect(resolve, permissions)
|
|
127
107
|
}
|
|
128
108
|
|
|
129
109
|
exports.create = create
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import { type Resolver } from '../Factory'
|
|
4
|
+
import amqp from './amqp'
|
|
5
|
+
import http from './http'
|
|
6
|
+
import type { extensions } from '@toa.io/core'
|
|
7
|
+
|
|
8
|
+
export const protocols: Protocol[] = [http, amqp]
|
|
9
|
+
|
|
10
|
+
export interface Protocol {
|
|
11
|
+
id: ProtocolID
|
|
12
|
+
protocols: string[]
|
|
13
|
+
create: (resolver: Resolver) => extensions.Aspect
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ProtocolID = 'http' | 'amqp'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type URIMap } from '@toa.io/pointer';
|
|
2
|
+
import type { Locator, extensions } from '@toa.io/core';
|
|
3
|
+
import type { Manifest } from './manifest';
|
|
4
|
+
export declare class Factory implements extensions.Factory {
|
|
5
|
+
aspect(locator: Locator, manifest: Manifest): extensions.Aspect[];
|
|
6
|
+
private createAspect;
|
|
7
|
+
private resolver;
|
|
8
|
+
private getURIs;
|
|
9
|
+
private filterOrigins;
|
|
10
|
+
private readOrigin;
|
|
11
|
+
private getProperties;
|
|
12
|
+
}
|
|
13
|
+
export interface Configuration {
|
|
14
|
+
origins: URIMap;
|
|
15
|
+
properties: Record<string, boolean>;
|
|
16
|
+
}
|
|
17
|
+
export type Resolver = () => Promise<Configuration>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Factory = void 0;
|
|
4
|
+
const msgpackr_1 = require("msgpackr");
|
|
5
|
+
const pointer_1 = require("@toa.io/pointer");
|
|
6
|
+
const generic_1 = require("@toa.io/generic");
|
|
7
|
+
const protocols_1 = require("./protocols");
|
|
8
|
+
const extension_1 = require("./extension");
|
|
9
|
+
class Factory {
|
|
10
|
+
aspect(locator, manifest) {
|
|
11
|
+
return protocols_1.protocols.map((protocol) => this.createAspect(locator, manifest, protocol));
|
|
12
|
+
}
|
|
13
|
+
createAspect(locator, manifest, protocol) {
|
|
14
|
+
const resolver = this.resolver(locator, manifest, protocol);
|
|
15
|
+
return protocol.create(resolver);
|
|
16
|
+
}
|
|
17
|
+
resolver(locator, manifest, protocol) {
|
|
18
|
+
return (0, generic_1.memo)(async () => {
|
|
19
|
+
const uris = await this.getURIs(locator, manifest);
|
|
20
|
+
const allProperties = this.getProperties(locator);
|
|
21
|
+
const origins = this.filterOrigins(uris, protocol.protocols);
|
|
22
|
+
const properties = allProperties['.' + protocol.id] ?? {};
|
|
23
|
+
return { origins, properties };
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async getURIs(locator, manifest) {
|
|
27
|
+
const map = {};
|
|
28
|
+
if (manifest === null)
|
|
29
|
+
return map;
|
|
30
|
+
for (const [name, value] of Object.entries(manifest))
|
|
31
|
+
try {
|
|
32
|
+
map[name] = await this.readOrigin(locator, name);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// eslint-disable-next-line max-depth
|
|
36
|
+
if (value === null)
|
|
37
|
+
throw new Error(`Origin value ${name} is not defined`);
|
|
38
|
+
map[name] = [value];
|
|
39
|
+
}
|
|
40
|
+
return map;
|
|
41
|
+
}
|
|
42
|
+
filterOrigins(uris, protocols) {
|
|
43
|
+
const filtered = {};
|
|
44
|
+
for (const [name, references] of Object.entries(uris)) {
|
|
45
|
+
const url = new URL(references[0]);
|
|
46
|
+
if (protocols.includes(url.protocol))
|
|
47
|
+
filtered[name] = references;
|
|
48
|
+
}
|
|
49
|
+
return filtered;
|
|
50
|
+
}
|
|
51
|
+
async readOrigin(locator, name) {
|
|
52
|
+
const id = extension_1.ID_PREFIX + locator.label;
|
|
53
|
+
return await (0, pointer_1.resolve)(id, name);
|
|
54
|
+
}
|
|
55
|
+
getProperties(locator) {
|
|
56
|
+
const variable = extension_1.ENV_PREFIX + locator.uppercase + extension_1.PROPERTIES_SUFFIX;
|
|
57
|
+
const value = process.env[variable];
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return {};
|
|
60
|
+
const buffer = Buffer.from(value, 'base64');
|
|
61
|
+
return (0, msgpackr_1.decode)(buffer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.Factory = Factory;
|
|
65
|
+
//# sourceMappingURL=Factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Instance } from './extension';
|
|
2
|
+
export declare function normalize(instances: Instance[], annotation: Annotation): void;
|
|
3
|
+
export declare function split(component: Component): {
|
|
4
|
+
origins: Origins;
|
|
5
|
+
properties: Properties;
|
|
6
|
+
};
|
|
7
|
+
export type Component = Origins | Properties;
|
|
8
|
+
export type Annotation = Record<string, Component>;
|
|
9
|
+
export type Properties = Partial<Record<'.http', Record<string, boolean>>>;
|
|
10
|
+
export type Origins = Record<string, string | string[]>;
|