@things-factory/shell 10.1.227 → 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/dist-server/middlewares/domain-middleware.js +4 -11
- package/dist-server/middlewares/domain-middleware.js.map +1 -1
- package/dist-server/middlewares/ip-list-rule.d.ts +10 -0
- package/dist-server/middlewares/ip-list-rule.js +55 -0
- package/dist-server/middlewares/ip-list-rule.js.map +1 -0
- package/dist-server/service/domain/domain-mutation.js +4 -1
- package/dist-server/service/domain/domain-mutation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/tests/ip-list-rule.test.ts +87 -0
- package/translations/en.json +15 -1
- package/translations/es.json +9 -1
- package/translations/ja.json +9 -1
- package/translations/ko.json +15 -1
- package/translations/ms.json +9 -1
- package/translations/zh.json +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/shell",
|
|
3
|
-
"version": "10.1.
|
|
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": "
|
|
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
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -198,5 +198,19 @@
|
|
|
198
198
|
"button.scan-with-camera": "scan with camera",
|
|
199
199
|
"figure.label.motion": "Motion",
|
|
200
200
|
"figure.text.preview-needs-a-part": "add a part to see how it looks placed on a drawing.",
|
|
201
|
-
"error.timezone-required": "The $t(term.domain) has no timezone. Set it in $t(term.domain) management."
|
|
201
|
+
"error.timezone-required": "The $t(term.domain) has no timezone. Set it in $t(term.domain) management.",
|
|
202
|
+
"button.preview": "Preview",
|
|
203
|
+
"title.preview": "Preview",
|
|
204
|
+
"field.counterparty": "counterparty",
|
|
205
|
+
"text.tax-kind-taxable": "Taxable",
|
|
206
|
+
"text.tax-kind-zero-rated": "Zero-rated",
|
|
207
|
+
"text.tax-kind-exempt": "Exempt",
|
|
208
|
+
"button.rename": "rename",
|
|
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."
|
|
202
216
|
}
|
package/translations/es.json
CHANGED
|
@@ -198,5 +198,13 @@
|
|
|
198
198
|
"button.scan-with-camera": "escanear con la cámara",
|
|
199
199
|
"figure.label.motion": "Movimiento",
|
|
200
200
|
"figure.text.preview-needs-a-part": "añada una pieza para ver cómo se ve colocada en un plano.",
|
|
201
|
-
"error.timezone-required": "El dominio no tiene zona horaria. Configúrela en la gestión de dominios."
|
|
201
|
+
"error.timezone-required": "El dominio no tiene zona horaria. Configúrela en la gestión de dominios.",
|
|
202
|
+
"button.preview": "Vista previa",
|
|
203
|
+
"title.preview": "Vista previa",
|
|
204
|
+
"field.counterparty": "contraparte",
|
|
205
|
+
"text.tax-kind-taxable": "Gravado",
|
|
206
|
+
"text.tax-kind-zero-rated": "Tipo cero",
|
|
207
|
+
"text.tax-kind-exempt": "Exento",
|
|
208
|
+
"button.rename": "cambiar nombre",
|
|
209
|
+
"button.search": "buscar"
|
|
202
210
|
}
|
package/translations/ja.json
CHANGED
|
@@ -198,5 +198,13 @@
|
|
|
198
198
|
"button.scan-with-camera": "カメラでスキャン",
|
|
199
199
|
"figure.label.motion": "Motion",
|
|
200
200
|
"figure.text.preview-needs-a-part": "部品を追加すると、図面に置いたときの姿が表示されます。",
|
|
201
|
-
"error.timezone-required": "事業所のタイムゾーンが設定されていません。事業所管理でタイムゾーンを設定してください。"
|
|
201
|
+
"error.timezone-required": "事業所のタイムゾーンが設定されていません。事業所管理でタイムゾーンを設定してください。",
|
|
202
|
+
"button.preview": "プレビュー",
|
|
203
|
+
"title.preview": "プレビュー",
|
|
204
|
+
"field.counterparty": "取引先",
|
|
205
|
+
"text.tax-kind-taxable": "課税",
|
|
206
|
+
"text.tax-kind-zero-rated": "ゼロ税率",
|
|
207
|
+
"text.tax-kind-exempt": "免税",
|
|
208
|
+
"button.rename": "名前変更",
|
|
209
|
+
"button.search": "検索"
|
|
202
210
|
}
|
package/translations/ko.json
CHANGED
|
@@ -198,5 +198,19 @@
|
|
|
198
198
|
"button.scan-with-camera": "카메라로 스캔",
|
|
199
199
|
"figure.label.motion": "움직임",
|
|
200
200
|
"figure.text.preview-needs-a-part": "부품을 추가하면 도면에 놓았을 때의 모습이 표시됩니다.",
|
|
201
|
-
"error.timezone-required": "$t(term.domain) 시간대가 정해지지 않았습니다. $t(term.domain) 관리에서 시간대를 정하세요."
|
|
201
|
+
"error.timezone-required": "$t(term.domain) 시간대가 정해지지 않았습니다. $t(term.domain) 관리에서 시간대를 정하세요.",
|
|
202
|
+
"button.preview": "미리 보기",
|
|
203
|
+
"title.preview": "미리 보기",
|
|
204
|
+
"field.counterparty": "거래처",
|
|
205
|
+
"text.tax-kind-taxable": "과세",
|
|
206
|
+
"text.tax-kind-zero-rated": "영세율",
|
|
207
|
+
"text.tax-kind-exempt": "면세",
|
|
208
|
+
"button.rename": "이름변경",
|
|
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": "보호 목록에 빈 항목이 있습니다. 쉼표 사이를 비우지 마세요."
|
|
202
216
|
}
|
package/translations/ms.json
CHANGED
|
@@ -198,5 +198,13 @@
|
|
|
198
198
|
"button.scan-with-camera": "imbas dengan kamera",
|
|
199
199
|
"figure.label.motion": "Motion",
|
|
200
200
|
"figure.text.preview-needs-a-part": "tambah bahagian untuk melihat rupanya apabila diletakkan pada lukisan.",
|
|
201
|
-
"error.timezone-required": "Domain tiada zon waktu. Tetapkannya dalam pengurusan domain."
|
|
201
|
+
"error.timezone-required": "Domain tiada zon waktu. Tetapkannya dalam pengurusan domain.",
|
|
202
|
+
"button.preview": "Pratonton",
|
|
203
|
+
"title.preview": "Pratonton",
|
|
204
|
+
"field.counterparty": "rakan niaga",
|
|
205
|
+
"text.tax-kind-taxable": "Bercukai",
|
|
206
|
+
"text.tax-kind-zero-rated": "Kadar sifar",
|
|
207
|
+
"text.tax-kind-exempt": "Dikecualikan",
|
|
208
|
+
"button.rename": "Namakan Semula",
|
|
209
|
+
"button.search": "cari"
|
|
202
210
|
}
|
package/translations/zh.json
CHANGED
|
@@ -198,5 +198,13 @@
|
|
|
198
198
|
"button.scan-with-camera": "用摄像头扫描",
|
|
199
199
|
"figure.label.motion": "Motion",
|
|
200
200
|
"figure.text.preview-needs-a-part": "添加部件后会显示放置到图面上的效果。",
|
|
201
|
-
"error.timezone-required": "尚未设置单位时区。请在单位管理中设置时区。"
|
|
201
|
+
"error.timezone-required": "尚未设置单位时区。请在单位管理中设置时区。",
|
|
202
|
+
"button.preview": "预览",
|
|
203
|
+
"title.preview": "预览",
|
|
204
|
+
"field.counterparty": "往来单位",
|
|
205
|
+
"text.tax-kind-taxable": "应税",
|
|
206
|
+
"text.tax-kind-zero-rated": "零税率",
|
|
207
|
+
"text.tax-kind-exempt": "免税",
|
|
208
|
+
"button.rename": "名称变更",
|
|
209
|
+
"button.search": "搜索"
|
|
202
210
|
}
|