@toa.io/extensions.origins 0.10.0-dev.9 → 0.20.0-dev.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.origins",
3
- "version": "0.10.0-dev.9",
3
+ "version": "0.20.0-dev.2",
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": "1.1.0-dev.9",
23
- "@toa.io/generic": "0.11.0-dev.9",
24
- "@toa.io/schemas": "0.8.4-dev.9",
25
- "@toa.io/yaml": "0.7.6-dev.9",
22
+ "@toa.io/core": "0.20.0-dev.2",
23
+ "@toa.io/generic": "0.20.0-dev.2",
24
+ "@toa.io/schemas": "0.20.0-dev.2",
25
+ "@toa.io/yaml": "0.20.0-dev.2",
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": "db624e061c5017062f46561e6d5e4106b7420e28"
32
+ "gitHead": "3e767a32bf4705bacee7b2e8312ed0b0805a5260"
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
@@ -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
- schemas.annotations.validate(annotations)
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
@@ -25,8 +25,6 @@ class Factory {
25
25
 
26
26
  let properties
27
27
 
28
- // let properties
29
-
30
28
  for (const [origin, reference] of Object.entries(manifest)) {
31
29
  if (origin[0] === '.') {
32
30
  if (origin.substring(1) === protocol.id) properties = reference
@@ -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) {
@@ -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
- if (allowance !== -1) return true
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 regex = new RegExp(match.groups.expression)
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
- if (rule === true) this.#allowances.push(regex)
59
- else this.#denials.push(regex)
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 {string} */ name)) {
41
- return this.#invokeURL(
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 {import('node-fetch').RequestInit} request
61
- * @return {Promise<void>}
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 {import('node-fetch').RequestInit} request
72
+ * @param {Request} request
72
73
  * @param {toa.generic.retry.Options} [options]
73
- * @return {Promise<import('node-fetch').Response>}
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