@toa.io/extensions.origins 0.10.0-dev.9 → 0.20.0-dev.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 +6 -6
- package/source/.credentials.js +14 -0
- package/source/deployment.js +14 -1
- package/source/deployment.test.js +12 -0
- package/source/factory.js +0 -2
- package/source/manifest.js +3 -0
- package/source/manifest.test.js +7 -0
- package/source/protocols/http/.aspect/permissions.js +8 -6
- package/source/protocols/http/.aspect/permissions.test.js +23 -0
- package/source/protocols/http/aspect.js +11 -10
- package/source/protocols/http/aspect.test.js +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.origins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0-dev.1",
|
|
4
4
|
"description": "Toa Origins",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@toa.io/core": "
|
|
23
|
-
"@toa.io/generic": "0.11.0-dev.
|
|
24
|
-
"@toa.io/schemas": "0.
|
|
25
|
-
"@toa.io/yaml": "0.
|
|
22
|
+
"@toa.io/core": "0.20.0-dev.1",
|
|
23
|
+
"@toa.io/generic": "0.11.0-dev.3",
|
|
24
|
+
"@toa.io/schemas": "0.20.0-dev.1",
|
|
25
|
+
"@toa.io/yaml": "0.20.0-dev.1",
|
|
26
26
|
"comq": "0.7.0",
|
|
27
27
|
"node-fetch": "2.6.7"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node-fetch": "2.6.2"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "23a9b4399af908101aac4ae03f22f7b948dfe55f"
|
|
33
33
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} reference
|
|
5
|
+
*/
|
|
6
|
+
function check (reference) {
|
|
7
|
+
if (typeof reference !== 'string') return // aspect properties object
|
|
8
|
+
|
|
9
|
+
const url = new URL(reference)
|
|
10
|
+
|
|
11
|
+
if (url.username !== '' || url.password !== '') throw new Error('Origins must not contain credentials. Please use environment secrets instead.')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.check = check
|
package/source/deployment.js
CHANGED
|
@@ -4,6 +4,7 @@ const { merge } = require('@toa.io/generic')
|
|
|
4
4
|
const schemas = require('./schemas')
|
|
5
5
|
const protocols = require('./protocols')
|
|
6
6
|
const create = require('./.deployment')
|
|
7
|
+
const credentials = require('./.credentials')
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @param {toa.norm.context.dependencies.Instance[]} instances
|
|
@@ -11,7 +12,7 @@ const create = require('./.deployment')
|
|
|
11
12
|
* @returns {toa.deployment.dependency.Declaration}
|
|
12
13
|
*/
|
|
13
14
|
function deployment (instances, annotations = {}) {
|
|
14
|
-
|
|
15
|
+
validate(annotations)
|
|
15
16
|
|
|
16
17
|
const uris = create.uris(instances, annotations)
|
|
17
18
|
const variables = { ...uris }
|
|
@@ -25,4 +26,16 @@ function deployment (instances, annotations = {}) {
|
|
|
25
26
|
return { variables }
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* @param {toa.origins.Annotations} annotations
|
|
31
|
+
* @return {void}
|
|
32
|
+
*/
|
|
33
|
+
function validate (annotations) {
|
|
34
|
+
schemas.annotations.validate(annotations)
|
|
35
|
+
|
|
36
|
+
for (const component of Object.values(annotations)) {
|
|
37
|
+
Object.values(component).forEach(credentials.check)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
28
41
|
exports.deployment = deployment
|
|
@@ -100,6 +100,18 @@ it('should create variables', () => {
|
|
|
100
100
|
expect(variable.value).toStrictEqual(base64)
|
|
101
101
|
})
|
|
102
102
|
|
|
103
|
+
it.each(['http', 'amqp'])('should throw if %s annotation contains credentials',
|
|
104
|
+
async (protocol) => {
|
|
105
|
+
/** @type {toa.origins.Annotations} */
|
|
106
|
+
const annotations = {
|
|
107
|
+
[component.locator.id]: {
|
|
108
|
+
[origin]: protocol + '://dev:sec@host-' + generate()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
expect(() => deployment(components, annotations)).toThrow('Origins must not contain credentials')
|
|
113
|
+
})
|
|
114
|
+
|
|
103
115
|
describe('amqp', () => {
|
|
104
116
|
beforeEach(() => {
|
|
105
117
|
const amqpComponents = components.filter(
|
package/source/factory.js
CHANGED
package/source/manifest.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { remap, echo, shards } = require('@toa.io/generic')
|
|
4
4
|
const schemas = require('./schemas')
|
|
5
5
|
const protocols = require('./protocols')
|
|
6
|
+
const credentials = require('./.credentials')
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {toa.origins.Manifest} manifest
|
|
@@ -29,6 +30,8 @@ function manifest (manifest) {
|
|
|
29
30
|
function validate (manifest) {
|
|
30
31
|
manifest = remap(manifest, (value) => shards(value)[0])
|
|
31
32
|
schemas.manifest.validate(manifest)
|
|
33
|
+
|
|
34
|
+
Object.values(manifest).forEach(credentials.check)
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
function supports (provider, url) {
|
package/source/manifest.test.js
CHANGED
|
@@ -73,3 +73,10 @@ it('should handle port shards', async () => {
|
|
|
73
73
|
|
|
74
74
|
expect(() => manifest(input)).not.toThrow()
|
|
75
75
|
})
|
|
76
|
+
|
|
77
|
+
it.each(['dev:sec', 'dev'])('should throw if url contains credentials (%s)',
|
|
78
|
+
async (credentials) => {
|
|
79
|
+
const input = { foo: `http://${credentials}@${generate()}:888{0-9}` }
|
|
80
|
+
|
|
81
|
+
expect(() => manifest(input)).toThrow('must not contain credentials')
|
|
82
|
+
})
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { echo } = require('@toa.io/generic')
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* @implements {toa.origins.http.Permissions}
|
|
5
7
|
*/
|
|
@@ -26,9 +28,7 @@ class Permissions {
|
|
|
26
28
|
|
|
27
29
|
const allowance = this.#allowances.findIndex((regexp) => regexp.test(url))
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return this.#default
|
|
31
|
+
return allowance !== -1
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
#parse (properties) {
|
|
@@ -44,7 +44,8 @@ class Permissions {
|
|
|
44
44
|
|
|
45
45
|
if (match === null) throw new Error(`'${key}' is not a regular expression`)
|
|
46
46
|
|
|
47
|
-
const
|
|
47
|
+
const expression = echo(match.groups.expression)
|
|
48
|
+
const regex = new RegExp(expression)
|
|
48
49
|
|
|
49
50
|
this.#addRule(regex, rule)
|
|
50
51
|
}
|
|
@@ -55,8 +56,9 @@ class Permissions {
|
|
|
55
56
|
* @param {boolean} rule
|
|
56
57
|
*/
|
|
57
58
|
#addRule (regex, rule) {
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const rules = rule ? this.#allowances : this.#denials
|
|
60
|
+
|
|
61
|
+
rules.push(regex)
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
const { Permissions } = require('./permissions')
|
|
5
|
+
|
|
6
|
+
it('should be', async () => {
|
|
7
|
+
expect(Permissions).toBeInstanceOf(Function)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('should substitute env vars', async () => {
|
|
11
|
+
const name = 'FOO_VALUE'
|
|
12
|
+
const value = generate()
|
|
13
|
+
|
|
14
|
+
process.env[name] = value
|
|
15
|
+
|
|
16
|
+
const properties = { '/http:\/\/domain.com\/${FOO_VALUE}/': true }
|
|
17
|
+
const permissions = new Permissions(properties)
|
|
18
|
+
|
|
19
|
+
expect(permissions.test('http://other.domain.com/')).toStrictEqual(false)
|
|
20
|
+
expect(permissions.test('http://domain.com/' + value)).toStrictEqual(true)
|
|
21
|
+
|
|
22
|
+
delete process.env[name]
|
|
23
|
+
})
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('node-fetch').RequestInit} Request
|
|
5
|
+
* @typedef {import('node-fetch').Response} Response
|
|
6
|
+
*/
|
|
7
|
+
|
|
3
8
|
const fetch = require('node-fetch')
|
|
4
9
|
|
|
5
10
|
const { Connector } = require('@toa.io/core')
|
|
@@ -37,12 +42,8 @@ class Aspect extends Connector {
|
|
|
37
42
|
let origin = this.#origins[name]
|
|
38
43
|
|
|
39
44
|
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`)
|
|
45
|
+
if (isAbsoluteURL(name)) return this.#invokeURL(name, /** @type {Request} */ path)
|
|
46
|
+
else throw new Error(`Origin '${name}' is not defined`)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// absolute urls are forbidden when using origins
|
|
@@ -57,8 +58,8 @@ class Aspect extends Connector {
|
|
|
57
58
|
|
|
58
59
|
/**
|
|
59
60
|
* @param {string} url
|
|
60
|
-
* @param {
|
|
61
|
-
* @return {Promise<
|
|
61
|
+
* @param {Request} request
|
|
62
|
+
* @return {Promise<Response>}
|
|
62
63
|
*/
|
|
63
64
|
async #invokeURL (url, request) {
|
|
64
65
|
if (this.#permissions.test(url) === false) throw new Error(`URL '${url}' is not allowed`)
|
|
@@ -68,9 +69,9 @@ class Aspect extends Connector {
|
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
71
|
* @param {string} url
|
|
71
|
-
* @param {
|
|
72
|
+
* @param {Request} request
|
|
72
73
|
* @param {toa.generic.retry.Options} [options]
|
|
73
|
-
* @return {Promise<
|
|
74
|
+
* @return {Promise<Response>}
|
|
74
75
|
*/
|
|
75
76
|
async #request (url, request, options) {
|
|
76
77
|
const call = () => fetch(url, request)
|
|
@@ -172,25 +172,6 @@ describe.each(protocols)('absolute URL', (protocol) => {
|
|
|
172
172
|
expect(mock.fetch).toHaveBeenCalledWith(url, request)
|
|
173
173
|
})
|
|
174
174
|
|
|
175
|
-
it('should allow if TOA_DEV=1 and no properties', async () => {
|
|
176
|
-
const dev = process.env.TOA_DEV
|
|
177
|
-
|
|
178
|
-
process.env.TOA_DEV = '1'
|
|
179
|
-
|
|
180
|
-
mock.fetch.respond(200, response)
|
|
181
|
-
|
|
182
|
-
const url = protocol + '//' + generate()
|
|
183
|
-
const request = { method: 'POST' }
|
|
184
|
-
|
|
185
|
-
aspect = create(fixtures.manifest)
|
|
186
|
-
|
|
187
|
-
await aspect.invoke(url, request)
|
|
188
|
-
|
|
189
|
-
expect(mock.fetch).toHaveBeenCalledWith(url, request)
|
|
190
|
-
|
|
191
|
-
process.env.TOA_DEV = dev
|
|
192
|
-
})
|
|
193
|
-
|
|
194
175
|
it('should throw if URL not allowed', async () => {
|
|
195
176
|
mock.fetch.respond(200, response)
|
|
196
177
|
|