@things-factory/shell 10.1.228 → 10.1.234

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": "@things-factory/shell",
3
- "version": "10.1.228",
3
+ "version": "10.1.234",
4
4
  "description": "Core module for framework",
5
5
  "bin": {
6
6
  "things-factory": "bin/things-factory",
@@ -142,5 +142,5 @@
142
142
  "pg": "^8.7.3",
143
143
  "sqlite3": "^5.0.8"
144
144
  },
145
- "gitHead": "fc4ede44617418df8701e83b83d177db4142a8f3"
145
+ "gitHead": "92f29853680fff527a5f1d167ab3ad9b27837ba9"
146
146
  }
@@ -0,0 +1,87 @@
1
+ /* The domain is given in state, so the URL lookup is never reached - and importing it drags in every entity. */
2
+ jest.mock('../server/utils/get-domain', () => ({ getDomainFromURL: jest.fn() }))
3
+
4
+ import { domainMiddleware, REFUSAL_HEADER, REFUSED_ADDRESS } from '../server/middlewares/domain-middleware'
5
+ import { refuseBadIPList } from '../server/middlewares/ip-list-rule'
6
+
7
+ /**
8
+ * One IP list entry that is not a regular expression used to throw on every request of the domain - nobody got
9
+ * in, with a server error. Saving refuses it by list and entry; one already stored is passed over.
10
+ */
11
+
12
+ function run(iplist: any, ip: string) {
13
+ const headers: Record<string, string> = {}
14
+ const context: any = {
15
+ state: { domain: { id: 'd1', iplist } },
16
+ req: { headers: { 'x-client-ip': ip }, connection: {}, socket: {} },
17
+ status: 200,
18
+ set: (name: string, value: string) => (headers[name.toLowerCase()] = value)
19
+ }
20
+ let passed = false
21
+ return domainMiddleware(context, async () => {
22
+ passed = true
23
+ }).then(() => ({ status: context.status, headers, passed, unsafeIP: context.state.unsafeIP }))
24
+ }
25
+
26
+ function refusalOf(iplist: any) {
27
+ try {
28
+ refuseBadIPList(iplist)
29
+ } catch (e: any) {
30
+ return { code: e.code, params: e.params }
31
+ }
32
+ }
33
+
34
+ describe('a stored entry that is not a regular expression', () => {
35
+ it('★ does not break the request: the other whitelist entries still let the address in', async () => {
36
+ expect(await run({ whitelist: ['10.0.0.[', '^10\\.'] }, '10.0.0.1')).toMatchObject({ status: 200, passed: true })
37
+ })
38
+
39
+ it('★ matches nothing: an address outside the rest of the whitelist is refused, not a server error', async () => {
40
+ expect(await run({ whitelist: ['10.0.0.['] }, '10.0.0.1')).toMatchObject({
41
+ status: 403,
42
+ headers: { [REFUSAL_HEADER]: REFUSED_ADDRESS },
43
+ passed: false
44
+ })
45
+ })
46
+
47
+ it('★ in the blacklist and the protected list it is passed over as well', async () => {
48
+ expect(await run({ blacklist: ['('] }, '10.0.0.1')).toMatchObject({ status: 200, passed: true })
49
+ expect(await run({ protectedlist: ['(', '^10\\.'] }, '10.0.0.1')).toMatchObject({ passed: true, unsafeIP: false })
50
+ })
51
+ })
52
+
53
+ describe('saving the IP lists', () => {
54
+ it('★ refuses an entry that is not a regular expression, naming the list and the entry', () => {
55
+ expect(refusalOf({ whitelist: ['^10\\.', '10.0.0.['] })).toEqual({ code: 'ip-whitelist-entry-invalid', params: { entry: '10.0.0.[' } })
56
+ expect(refusalOf({ blacklist: ['('] })).toEqual({ code: 'ip-blacklist-entry-invalid', params: { entry: '(' } })
57
+ expect(refusalOf({ protectedlist: [10] })).toEqual({ code: 'ip-protectedlist-entry-invalid', params: { entry: '10' } })
58
+ })
59
+
60
+ it('★ refuses an empty entry - an empty pattern matches every address', () => {
61
+ expect(refusalOf({ whitelist: ['^10\\.', ' '] })).toEqual({ code: 'ip-whitelist-entry-empty', params: {} })
62
+ })
63
+
64
+ it('takes lists of regular expressions, and lists left out', () => {
65
+ expect(refusalOf({ whitelist: ['^10\\.'], blacklist: [], privileges: [{ category: 'board', privilege: 'mutation' }] })).toBeUndefined()
66
+ expect(refusalOf({})).toBeUndefined()
67
+ expect(refusalOf(null)).toBeUndefined()
68
+ })
69
+
70
+ it('every refusal has its sentence in Korean and English', () => {
71
+ for (const locale of ['ko', 'en']) {
72
+ const sentences = require(`../translations/${locale}.json`)
73
+ for (const list of ['whitelist', 'blacklist', 'protectedlist']) {
74
+ for (const kind of ['invalid', 'empty']) {
75
+ expect([locale, typeof sentences[`error.ip-${list}-entry-${kind}`]]).toEqual([locale, 'string'])
76
+ }
77
+ }
78
+ }
79
+ })
80
+
81
+ it('the save door checks before it writes', () => {
82
+ const source = require('fs').readFileSync(require('path').resolve(__dirname, '../server/service/domain/domain-mutation.ts'), 'utf8')
83
+ const door = source.slice(source.indexOf('async updateSecureIPList('))
84
+ expect(door.indexOf('refuseBadIPList(iplist)')).toBeGreaterThan(0)
85
+ expect(door.indexOf('refuseBadIPList(iplist)')).toBeLessThan(door.indexOf('repository.save'))
86
+ })
87
+ })
@@ -206,5 +206,11 @@
206
206
  "text.tax-kind-zero-rated": "Zero-rated",
207
207
  "text.tax-kind-exempt": "Exempt",
208
208
  "button.rename": "rename",
209
- "button.search": "search"
209
+ "button.search": "search",
210
+ "error.ip-whitelist-entry-invalid": "'{entry}' in the allow list is not an IP address regular expression. For example: ^10\\.0\\.",
211
+ "error.ip-blacklist-entry-invalid": "'{entry}' in the block list is not an IP address regular expression. For example: ^10\\.0\\.",
212
+ "error.ip-protectedlist-entry-invalid": "'{entry}' in the protected list is not an IP address regular expression. For example: ^10\\.0\\.",
213
+ "error.ip-whitelist-entry-empty": "The allow list has an empty entry. Leave nothing empty between commas.",
214
+ "error.ip-blacklist-entry-empty": "The block list has an empty entry. Leave nothing empty between commas.",
215
+ "error.ip-protectedlist-entry-empty": "The protected list has an empty entry. Leave nothing empty between commas."
210
216
  }
@@ -206,5 +206,11 @@
206
206
  "text.tax-kind-zero-rated": "영세율",
207
207
  "text.tax-kind-exempt": "면세",
208
208
  "button.rename": "이름변경",
209
- "button.search": "찾기"
209
+ "button.search": "찾기",
210
+ "error.ip-whitelist-entry-invalid": "허용 목록의 '{entry}'는 IP 주소 정규식이 아닙니다. 예: ^10\\.0\\.",
211
+ "error.ip-blacklist-entry-invalid": "차단 목록의 '{entry}'는 IP 주소 정규식이 아닙니다. 예: ^10\\.0\\.",
212
+ "error.ip-protectedlist-entry-invalid": "보호 목록의 '{entry}'는 IP 주소 정규식이 아닙니다. 예: ^10\\.0\\.",
213
+ "error.ip-whitelist-entry-empty": "허용 목록에 빈 항목이 있습니다. 쉼표 사이를 비우지 마세요.",
214
+ "error.ip-blacklist-entry-empty": "차단 목록에 빈 항목이 있습니다. 쉼표 사이를 비우지 마세요.",
215
+ "error.ip-protectedlist-entry-empty": "보호 목록에 빈 항목이 있습니다. 쉼표 사이를 비우지 마세요."
210
216
  }