@things-factory/shell 10.1.3 → 10.1.7
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/client/themes/calendar-theme.css +60 -18
- package/client/themes/category-color.css +139 -0
- package/client/themes/grist-theme.css +7 -2
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/typeorm/encrypt-json-transform.d.ts +5 -0
- package/dist-server/typeorm/encrypt-json-transform.js +33 -0
- package/dist-server/typeorm/encrypt-json-transform.js.map +1 -1
- package/package.json +2 -2
- package/tests/category-palette-single-source.test.ts +98 -0
|
@@ -5,3 +5,8 @@ import { ValueTransformer } from 'typeorm';
|
|
|
5
5
|
* and decrypt values when retrieving them from the database.
|
|
6
6
|
*/
|
|
7
7
|
export declare const encryptJsonTransformer: ValueTransformer;
|
|
8
|
+
export declare function encryptedJsonColumn(): {
|
|
9
|
+
type: any;
|
|
10
|
+
length?: any;
|
|
11
|
+
transformer: ValueTransformer;
|
|
12
|
+
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.encryptJsonTransformer = void 0;
|
|
4
|
+
exports.encryptedJsonColumn = encryptedJsonColumn;
|
|
4
5
|
const crypto_1 = require("crypto");
|
|
5
6
|
const get_data_encryption_key_js_1 = require("./get-data-encryption-key.js");
|
|
7
|
+
const long_text_column_js_1 = require("./long-text-column.js");
|
|
6
8
|
const ALGORITHM = 'aes-256-cbc';
|
|
7
9
|
const KEY_LENGTH = 32;
|
|
8
10
|
const IV_LENGTH = 16;
|
|
@@ -92,4 +94,35 @@ function decryptJson(text) {
|
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
}
|
|
97
|
+
/*
|
|
98
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
99
|
+
* 암호화되는 JSON 칸 — **타입과 변환기를 함께** 낸다.
|
|
100
|
+
*
|
|
101
|
+
* ── 왜 함께여야 하나 (2026-09-09) ──────────────────────────────────────────
|
|
102
|
+
* 위 변환기는 문자열을 돌려준다. 그것을 `simple-json` 칸에 붙이면 TypeORM 이 그 문자열을 다시
|
|
103
|
+
* JSON 으로 감싸므로 두 번 부호화되고, 읽을 때 원래 값이 안 나온다. 그래서 칸이 **텍스트 타입**이어야
|
|
104
|
+
* 하는데 그 타입 이름이 드라이버마다 다르다(§`longTextColumn`).
|
|
105
|
+
*
|
|
106
|
+
* 둘을 따로 두면 쓰는 사람이 둘을 기억해야 하고, 하나만 기억하면 조용히 틀린다 — `integration-base`
|
|
107
|
+
* 의 `Connection.params` 가 그 상태로 남아 있다: 변환기를 붙이려면 타입도 바꿔야 한다는 것을 주석에
|
|
108
|
+
* 적어 두고 둘 다 물려 두었다(그 판단은 그 쪽 사정으로 맞다 — 배포된 DB 의 칸 타입을 바꾸면 기존
|
|
109
|
+
* 데이터를 잃는다).
|
|
110
|
+
*
|
|
111
|
+
* ── 무엇을 지키고 무엇을 못 지키나 ────────────────────────────────────────
|
|
112
|
+
* 저장된 것을 지킨다 — DB 파일 · 백업 · 복제본 · 행을 읽을 수 있는 사람.
|
|
113
|
+
*
|
|
114
|
+
* **프로세스를 돌릴 수 있는 사람에게서는 못 지킨다.** 그 사람은 키를 갖고 있다. 이 암호화는 접근
|
|
115
|
+
* 통제를 대신하지 않는다.
|
|
116
|
+
*
|
|
117
|
+
* 그리고 값이 화면으로 다시 나가는 것도 못 막는다. 그건 내보내는 쪽이 가려야 한다 — 두 가지가
|
|
118
|
+
* 갈려 있으므로 한쪽만 하면 반쪽이다.
|
|
119
|
+
*
|
|
120
|
+
* ── 기존 평문 행 ──────────────────────────────────────────────────────────
|
|
121
|
+
* 위 `from` 이 암호문 모양(`iv:cipher`)이 아닌 값을 알아보고 그대로 파싱하며 경고를 남긴다. 그래서
|
|
122
|
+
* 이 옵션을 붙여도 이미 있는 행이 안 깨진다 — 다시 저장될 때 암호화된다.
|
|
123
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
124
|
+
*/
|
|
125
|
+
function encryptedJsonColumn() {
|
|
126
|
+
return { ...(0, long_text_column_js_1.longTextColumn)(), transformer: exports.encryptJsonTransformer };
|
|
127
|
+
}
|
|
95
128
|
//# sourceMappingURL=encrypt-json-transform.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encrypt-json-transform.js","sourceRoot":"","sources":["../../server/typeorm/encrypt-json-transform.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"encrypt-json-transform.js","sourceRoot":"","sources":["../../server/typeorm/encrypt-json-transform.ts"],"names":[],"mappings":"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","sourcesContent":["import { ValueTransformer } from 'typeorm'\nimport { createCipheriv, createDecipheriv, randomBytes } from 'crypto'\n\nimport { dataEncryptionKey } from './get-data-encryption-key.js'\nimport { longTextColumn } from './long-text-column.js'\n\nconst ALGORITHM = 'aes-256-cbc'\nconst KEY_LENGTH = 32\nconst IV_LENGTH = 16\n\n/**\n * ValueTransformer object for encrypting and decrypting JSON objects.\n * Used in TypeORM entities to encrypt JSON field values before storing them in the database\n * and decrypt values when retrieving them from the database.\n */\nexport const encryptJsonTransformer: ValueTransformer = {\n /**\n * Encrypts the JSON object before storing it in the database.\n * @param {any} entityValue - JSON object to encrypt\n * @returns {string} - Encrypted string\n */\n to: (entityValue: any) => encryptJson(entityValue),\n\n /**\n * Decrypts the encrypted JSON string when retrieving it from the database.\n * @param {string} databaseValue - Encrypted JSON string\n * @returns {any} - Decrypted JSON object\n */\n from: (databaseValue: string) => decryptJson(databaseValue)\n}\n\n/**\n * Encrypts the given JSON object using the AES-256-CBC algorithm.\n * @param {any} jsonObj - JSON object to encrypt\n * @returns {string} - Encrypted string\n */\nfunction encryptJson(jsonObj: any): string {\n if (!jsonObj) {\n return null\n }\n\n try {\n // JSON 객체를 문자열로 변환\n const jsonString = JSON.stringify(jsonObj)\n\n const iv = randomBytes(IV_LENGTH)\n const cipher = createCipheriv(ALGORITHM, dataEncryptionKey, iv)\n const encrypted = Buffer.concat([cipher.update(jsonString, 'utf8'), cipher.final()])\n\n return `${iv.toString('hex')}:${encrypted.toString('hex')}`\n } catch (err) {\n console.error('Failed to encrypt JSON object:', err)\n return null\n }\n}\n\n/**\n * Decrypts the given encrypted JSON string.\n * @param {string} text - Encrypted JSON string to decrypt\n * @returns {any} - Decrypted JSON object\n */\nfunction decryptJson(text: string): any {\n if (!text) {\n return null\n }\n\n try {\n // 암호화된 데이터인지 확인 (iv:encrypted 형식)\n if (!text.includes(':')) {\n // 암호화되지 않은 기존 데이터인 경우 JSON 파싱 시도\n try {\n const parsedData = JSON.parse(text)\n console.warn('Found unencrypted data in database. This data should be encrypted for security.')\n return parsedData\n } catch (parseErr) {\n // JSON 파싱도 실패하면 원본 문자열 반환\n console.warn('Found unencrypted non-JSON data in database. This data should be encrypted for security.')\n return text\n }\n }\n\n const parts = text.split(':')\n const iv = Buffer.from(parts.shift(), 'hex')\n const encryptedText = Buffer.from(parts.join(':'), 'hex')\n const decipher = createDecipheriv(ALGORITHM, dataEncryptionKey, iv)\n\n const decryptedString = Buffer.concat([decipher.update(encryptedText), decipher.final()]).toString('utf8')\n\n // JSON 문자열을 객체로 파싱\n return JSON.parse(decryptedString)\n } catch (err) {\n // 복호화 실패 시 원본 데이터 반환 시도\n try {\n const parsedData = JSON.parse(text)\n console.warn('Failed to decrypt data, but found valid JSON. Data may be unencrypted or corrupted.')\n return parsedData\n } catch (parseErr) {\n console.warn('Failed to decrypt data and not valid JSON. Returning original text.')\n return text\n }\n }\n}\n\n/*\n * ═══════════════════════════════════════════════════════════════════════════\n * 암호화되는 JSON 칸 — **타입과 변환기를 함께** 낸다.\n *\n * ── 왜 함께여야 하나 (2026-09-09) ──────────────────────────────────────────\n * 위 변환기는 문자열을 돌려준다. 그것을 `simple-json` 칸에 붙이면 TypeORM 이 그 문자열을 다시\n * JSON 으로 감싸므로 두 번 부호화되고, 읽을 때 원래 값이 안 나온다. 그래서 칸이 **텍스트 타입**이어야\n * 하는데 그 타입 이름이 드라이버마다 다르다(§`longTextColumn`).\n *\n * 둘을 따로 두면 쓰는 사람이 둘을 기억해야 하고, 하나만 기억하면 조용히 틀린다 — `integration-base`\n * 의 `Connection.params` 가 그 상태로 남아 있다: 변환기를 붙이려면 타입도 바꿔야 한다는 것을 주석에\n * 적어 두고 둘 다 물려 두었다(그 판단은 그 쪽 사정으로 맞다 — 배포된 DB 의 칸 타입을 바꾸면 기존\n * 데이터를 잃는다).\n *\n * ── 무엇을 지키고 무엇을 못 지키나 ────────────────────────────────────────\n * 저장된 것을 지킨다 — DB 파일 · 백업 · 복제본 · 행을 읽을 수 있는 사람.\n *\n * **프로세스를 돌릴 수 있는 사람에게서는 못 지킨다.** 그 사람은 키를 갖고 있다. 이 암호화는 접근\n * 통제를 대신하지 않는다.\n *\n * 그리고 값이 화면으로 다시 나가는 것도 못 막는다. 그건 내보내는 쪽이 가려야 한다 — 두 가지가\n * 갈려 있으므로 한쪽만 하면 반쪽이다.\n *\n * ── 기존 평문 행 ──────────────────────────────────────────────────────────\n * 위 `from` 이 암호문 모양(`iv:cipher`)이 아닌 값을 알아보고 그대로 파싱하며 경고를 남긴다. 그래서\n * 이 옵션을 붙여도 이미 있는 행이 안 깨진다 — 다시 저장될 때 암호화된다.\n * ═══════════════════════════════════════════════════════════════════════════\n */\nexport function encryptedJsonColumn(): { type: any; length?: any; transformer: ValueTransformer } {\n return { ...longTextColumn(), transformer: encryptJsonTransformer }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/shell",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.7",
|
|
4
4
|
"description": "Core module for framework",
|
|
5
5
|
"bin": {
|
|
6
6
|
"things-factory": "bin/things-factory",
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"pg": "^8.7.3",
|
|
127
127
|
"sqlite3": "^5.0.8"
|
|
128
128
|
},
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "9a036f83f5751f9e4af9180e9a0018f716a4ba27"
|
|
130
130
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 분류색 팔레트(`--ui-category-*`)를 **정의하는 자리가 하나인지** 센다.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 주석으로는 못 막나 ───────────────────────────────────────────────────
|
|
5
|
+
* `category-color.css` 는 「도메인이 바꾸지 않는다」고 적어 두었지만, 테마는 **같은 이름이면 파일
|
|
6
|
+
* 단위로 대체되는** 구조다(builder 의 ThemeOverridePlugin). 앱이 자기 `category-color.css` 를 두면
|
|
7
|
+
* 팔레트가 통째로 갈리고, 그것 말고도 자기 CSS 어디서든 `--ui-category-3: ...` 한 줄이면 덮인다.
|
|
8
|
+
* 둘째 길은 파일을 옮겨도 안 막힌다.
|
|
9
|
+
*
|
|
10
|
+
* 그래서 세는 쪽으로 막는다 — 정의가 하나인지를.
|
|
11
|
+
*
|
|
12
|
+
* 분류색은 **부호**라서 이것이 지켜져야 한다. 「1번 = 휴무」를 한 번 배운 사람은 그것을 들고 다니고,
|
|
13
|
+
* 도메인마다 다르면 같은 것을 배운 사람이 다른 공장에서 잘못 읽는다. 오독은 오류보다 나쁘다 — 화면이
|
|
14
|
+
* 멀쩡해 보이기 때문이다.
|
|
15
|
+
*
|
|
16
|
+
* ── 덮어야 할 일이 생기면 ───────────────────────────────────────────────────
|
|
17
|
+
* 이 시험을 지우지 말고 **가운데(이 파일이 가리키는 그 CSS)를 고친다.** 「이 도메인 바탕에서 3번이
|
|
18
|
+
* 안 보인다」는 도메인이 덮을 일이 아니라 팔레트의 결함이다. 막기만 하고 길이 없으면 급한 사람이
|
|
19
|
+
* 우회한다.
|
|
20
|
+
*
|
|
21
|
+
* ── 몇 개를 봤는지도 주장한다 ───────────────────────────────────────────────
|
|
22
|
+
* 「아무것도 못 찾아서 초록」이 이 종류 시험의 대표적인 실패다. 경로가 바뀌거나 glob 이 빗나가면
|
|
23
|
+
* 검사 대상이 0이 되고, 0을 다 통과한 것으로 읽힌다. 그래서 훑은 CSS 파일 수의 하한을 함께 건다.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { readFileSync, readdirSync, statSync } from 'fs'
|
|
27
|
+
import path from 'path'
|
|
28
|
+
|
|
29
|
+
/** 저장소 안의 모든 CSS 를 훑는다. 여기가 팔레트를 덮을 수 있는 자리 전부다. */
|
|
30
|
+
const REPO_ROOT = path.resolve(__dirname, '../../..')
|
|
31
|
+
|
|
32
|
+
/** 실제로 훑는 자리. 산출물과 설치본은 소스의 사본이라 세면 같은 정의를 두 번 센다. */
|
|
33
|
+
const SKIP = new Set(['node_modules', 'dist', 'dist-client', 'dist-server', 'dist-shared', '.git', 'coverage'])
|
|
34
|
+
|
|
35
|
+
/** `--ui-category-3: #...` 처럼 값을 주는 줄. `var(--ui-category-3)` 로 읽기만 하는 줄은 아니다. */
|
|
36
|
+
const DEFINES = /--ui-category-\d+\s*:/
|
|
37
|
+
|
|
38
|
+
function cssFilesUnder(dir: string, found: string[] = []): string[] {
|
|
39
|
+
for (const entry of readdirSync(dir)) {
|
|
40
|
+
if (SKIP.has(entry) || entry.startsWith('.')) continue
|
|
41
|
+
|
|
42
|
+
const full = path.join(dir, entry)
|
|
43
|
+
let stat
|
|
44
|
+
try {
|
|
45
|
+
stat = statSync(full)
|
|
46
|
+
} catch {
|
|
47
|
+
continue // broken symlink
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (stat.isDirectory()) cssFilesUnder(full, found)
|
|
51
|
+
else if (entry.endsWith('.css')) found.push(full)
|
|
52
|
+
}
|
|
53
|
+
return found
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('the categorical palette has one definition site', () => {
|
|
57
|
+
const files = cssFilesUnder(REPO_ROOT)
|
|
58
|
+
const definers = files.filter(file => DEFINES.test(readFileSync(file, 'utf-8')))
|
|
59
|
+
|
|
60
|
+
it('scanned enough css to make the answer mean something', () => {
|
|
61
|
+
/*
|
|
62
|
+
* Without this, a moved path or a wrong glob scans nothing, finds no overrides, and reports
|
|
63
|
+
* green. 47 css files were in scope when this was written; the floor sits under that so
|
|
64
|
+
* deleting a stylesheet does not fail the build, but gutting the scan does.
|
|
65
|
+
*/
|
|
66
|
+
expect(files.length).toBeGreaterThanOrEqual(40)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('is defined in exactly one file', () => {
|
|
70
|
+
expect(definers.map(f => path.relative(REPO_ROOT, f))).toEqual([
|
|
71
|
+
'packages/shell/client/themes/category-color.css'
|
|
72
|
+
])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('defines all six slots there, and only six', () => {
|
|
76
|
+
const css = readFileSync(definers[0], 'utf-8')
|
|
77
|
+
const slots = new Set([...css.matchAll(/--ui-category-(\d+)\s*:/g)].map(m => m[1]))
|
|
78
|
+
|
|
79
|
+
expect([...slots].sort()).toEqual(['1', '2', '3', '4', '5', '6'])
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('keeps red for status, so a kind and an error never share a colour', () => {
|
|
83
|
+
/*
|
|
84
|
+
* Slot 1 was #c0392b, which measured dE00 2.5 against the error red the plant calendar
|
|
85
|
+
* actually renders - the same collapse this palette exists to fix. A screen showing both
|
|
86
|
+
* then has red meaning two things, and "red is bad" stops holding on that screen alone.
|
|
87
|
+
*
|
|
88
|
+
* A rule saying "do not use slot 1 where status colours appear" would have to be kept on
|
|
89
|
+
* every screen, with no signal when it is not. Giving up one colour is cheaper.
|
|
90
|
+
*/
|
|
91
|
+
const css = readFileSync(definers[0], 'utf-8')
|
|
92
|
+
const declared = [...css.matchAll(/--ui-category-\d+\s*:\s*(#[0-9a-fA-F]{6})/g)].map(m => m[1].toLowerCase())
|
|
93
|
+
|
|
94
|
+
expect(declared.length).toBeGreaterThan(0)
|
|
95
|
+
expect(declared).not.toContain('#c0392b')
|
|
96
|
+
expect(declared).not.toContain('#ff9080')
|
|
97
|
+
})
|
|
98
|
+
})
|