asaihost-nodejs 0.0.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/README.md +518 -0
- package/dist/InterventionLog-25EHE4QR.cjs +30 -0
- package/dist/InterventionLog-25EHE4QR.cjs.map +1 -0
- package/dist/InterventionLog-R6CCPU2B.es.js +30 -0
- package/dist/InterventionLog-R6CCPU2B.es.js.map +1 -0
- package/dist/LogStore-ECXX34WC.es.js +54 -0
- package/dist/LogStore-ECXX34WC.es.js.map +1 -0
- package/dist/LogStore-M2AUBNGO.cjs +54 -0
- package/dist/LogStore-M2AUBNGO.cjs.map +1 -0
- package/dist/asaihost-nodejs.cjs +6761 -0
- package/dist/asaihost-nodejs.cjs.map +1 -0
- package/dist/asaihost-nodejs.d.cts +406 -0
- package/dist/asaihost-nodejs.d.ts +406 -0
- package/dist/asaihost-nodejs.es.js +6761 -0
- package/dist/asaihost-nodejs.es.js.map +1 -0
- package/dist/chunk-4WJDC4M4.es.js +311 -0
- package/dist/chunk-4WJDC4M4.es.js.map +1 -0
- package/dist/chunk-6U2I5EON.es.js +12 -0
- package/dist/chunk-6U2I5EON.es.js.map +1 -0
- package/dist/chunk-DROOHKB3.es.js +87 -0
- package/dist/chunk-DROOHKB3.es.js.map +1 -0
- package/dist/chunk-E7V3EBDB.es.js +435 -0
- package/dist/chunk-E7V3EBDB.es.js.map +1 -0
- package/dist/chunk-EOGZPRML.es.js +740 -0
- package/dist/chunk-EOGZPRML.es.js.map +1 -0
- package/dist/chunk-G4WVDUBH.cjs +463 -0
- package/dist/chunk-G4WVDUBH.cjs.map +1 -0
- package/dist/chunk-HHPGUJ75.cjs +162 -0
- package/dist/chunk-HHPGUJ75.cjs.map +1 -0
- package/dist/chunk-KOFCBB3N.cjs +87 -0
- package/dist/chunk-KOFCBB3N.cjs.map +1 -0
- package/dist/chunk-KUZQ36IJ.es.js +162 -0
- package/dist/chunk-KUZQ36IJ.es.js.map +1 -0
- package/dist/chunk-LOMSUPPU.es.js +1202 -0
- package/dist/chunk-LOMSUPPU.es.js.map +1 -0
- package/dist/chunk-MCMADJQR.es.js +463 -0
- package/dist/chunk-MCMADJQR.es.js.map +1 -0
- package/dist/chunk-NLBFIRDB.cjs +105 -0
- package/dist/chunk-NLBFIRDB.cjs.map +1 -0
- package/dist/chunk-O6LVLSNB.cjs +40 -0
- package/dist/chunk-O6LVLSNB.cjs.map +1 -0
- package/dist/chunk-QWVR2HNT.cjs +435 -0
- package/dist/chunk-QWVR2HNT.cjs.map +1 -0
- package/dist/chunk-SMVZ3ZDJ.cjs +12 -0
- package/dist/chunk-SMVZ3ZDJ.cjs.map +1 -0
- package/dist/chunk-UTKVJE7N.es.js +40 -0
- package/dist/chunk-UTKVJE7N.es.js.map +1 -0
- package/dist/chunk-VLMM735Q.cjs +1202 -0
- package/dist/chunk-VLMM735Q.cjs.map +1 -0
- package/dist/chunk-W7DVKGX5.cjs +311 -0
- package/dist/chunk-W7DVKGX5.cjs.map +1 -0
- package/dist/chunk-X7VHW2TD.cjs +740 -0
- package/dist/chunk-X7VHW2TD.cjs.map +1 -0
- package/dist/chunk-XBIN4C2V.es.js +105 -0
- package/dist/chunk-XBIN4C2V.es.js.map +1 -0
- package/dist/intervention-hooks-FI4EHOL5.es.js +21 -0
- package/dist/intervention-hooks-FI4EHOL5.es.js.map +1 -0
- package/dist/intervention-hooks-LE6X4C6N.cjs +21 -0
- package/dist/intervention-hooks-LE6X4C6N.cjs.map +1 -0
- package/dist/log-alert-GLCULMQP.es.js +51 -0
- package/dist/log-alert-GLCULMQP.es.js.map +1 -0
- package/dist/log-alert-LAJ6VSAG.cjs +51 -0
- package/dist/log-alert-LAJ6VSAG.cjs.map +1 -0
- package/dist/log-formats-FKFMQKNO.cjs +31 -0
- package/dist/log-formats-FKFMQKNO.cjs.map +1 -0
- package/dist/log-formats-I5E32H5F.es.js +31 -0
- package/dist/log-formats-I5E32H5F.es.js.map +1 -0
- package/dist/password-63OY27RX.es.js +20 -0
- package/dist/password-63OY27RX.es.js.map +1 -0
- package/dist/password-NZ5EGDWG.cjs +20 -0
- package/dist/password-NZ5EGDWG.cjs.map +1 -0
- package/dist/user-store-J7LWEXSW.es.js +12 -0
- package/dist/user-store-J7LWEXSW.es.js.map +1 -0
- package/dist/user-store-SX4N6PQ4.cjs +12 -0
- package/dist/user-store-SX4N6PQ4.cjs.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dbAddSession,
|
|
3
|
+
dbCreateUser,
|
|
4
|
+
dbDeleteUser,
|
|
5
|
+
dbFindUser,
|
|
6
|
+
dbListSessions,
|
|
7
|
+
dbListUsers,
|
|
8
|
+
dbRemoveSession,
|
|
9
|
+
dbRemoveSessionsByUser,
|
|
10
|
+
dbUpdateUser,
|
|
11
|
+
dbWriteSessions,
|
|
12
|
+
resolveRoleForLevel
|
|
13
|
+
} from "./chunk-EOGZPRML.es.js";
|
|
14
|
+
import {
|
|
15
|
+
__name
|
|
16
|
+
} from "./chunk-6U2I5EON.es.js";
|
|
17
|
+
|
|
18
|
+
// src/sysserver/api/login/store/user-store.ts
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
20
|
+
|
|
21
|
+
// src/config/secrets.ts
|
|
22
|
+
import fs from "fs/promises";
|
|
23
|
+
import path from "path";
|
|
24
|
+
var ENV_MAP = {
|
|
25
|
+
password: "ASAI_HOST_PASSWORD",
|
|
26
|
+
asaisn: "ASAI_ASAISN",
|
|
27
|
+
aesKeyBase64: "ASAI_AES_KEYBASE64",
|
|
28
|
+
sessionSecret: "ASAI_SESSION_SECRET",
|
|
29
|
+
httpsKey: "ASAI_HTTPS_KEY",
|
|
30
|
+
httpsCert: "ASAI_HTTPS_CERT",
|
|
31
|
+
httpsKeyPath: "ASAI_HTTPS_KEY_PATH",
|
|
32
|
+
httpsCertPath: "ASAI_HTTPS_CERT_PATH",
|
|
33
|
+
wssecToken: "ASAI_WSSEC_TOKEN",
|
|
34
|
+
siemToken: "ASAI_SIEM_TOKEN"
|
|
35
|
+
};
|
|
36
|
+
function isNonEmptyString(v) {
|
|
37
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
38
|
+
}
|
|
39
|
+
__name(isNonEmptyString, "isNonEmptyString");
|
|
40
|
+
async function loadDotEnvFile(envPath) {
|
|
41
|
+
const out = {};
|
|
42
|
+
try {
|
|
43
|
+
const raw = await fs.readFile(envPath, "utf8");
|
|
44
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
45
|
+
const trimmed = line.trim();
|
|
46
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
47
|
+
const eq = trimmed.indexOf("=");
|
|
48
|
+
if (eq <= 0) continue;
|
|
49
|
+
const key = trimmed.slice(0, eq).trim();
|
|
50
|
+
let val = trimmed.slice(eq + 1).trim();
|
|
51
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
52
|
+
val = val.slice(1, -1);
|
|
53
|
+
}
|
|
54
|
+
if (key) out[key] = val;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
__name(loadDotEnvFile, "loadDotEnvFile");
|
|
61
|
+
function envGet(name, dotenv) {
|
|
62
|
+
const fromProcess = process.env[name];
|
|
63
|
+
if (isNonEmptyString(fromProcess)) return fromProcess.trim();
|
|
64
|
+
const fromFile = dotenv[name];
|
|
65
|
+
if (isNonEmptyString(fromFile)) return fromFile.trim();
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
__name(envGet, "envGet");
|
|
69
|
+
async function readJsonFile(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
72
|
+
const data = JSON.parse(raw);
|
|
73
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
__name(readJsonFile, "readJsonFile");
|
|
79
|
+
async function readPemFile(serverRoot, filePath) {
|
|
80
|
+
const abs = path.isAbsolute(filePath) ? filePath : path.join(serverRoot, filePath);
|
|
81
|
+
try {
|
|
82
|
+
return (await fs.readFile(abs, "utf8")).trim();
|
|
83
|
+
} catch {
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
__name(readPemFile, "readPemFile");
|
|
88
|
+
function stripForbiddenSecretsFromHostConfig(cfg) {
|
|
89
|
+
const stripped = [];
|
|
90
|
+
if (isNonEmptyString(cfg.password)) {
|
|
91
|
+
stripped.push("password");
|
|
92
|
+
delete cfg.password;
|
|
93
|
+
}
|
|
94
|
+
if (isNonEmptyString(cfg.asaisn)) {
|
|
95
|
+
stripped.push("asaisn");
|
|
96
|
+
delete cfg.asaisn;
|
|
97
|
+
}
|
|
98
|
+
if (cfg.aes && typeof cfg.aes === "object" && isNonEmptyString(cfg.aes.keybase64)) {
|
|
99
|
+
stripped.push("aes.keybase64");
|
|
100
|
+
delete cfg.aes.keybase64;
|
|
101
|
+
}
|
|
102
|
+
if (cfg.security && typeof cfg.security === "object" && isNonEmptyString(cfg.security.sessionSecret)) {
|
|
103
|
+
stripped.push("security.sessionSecret");
|
|
104
|
+
delete cfg.security.sessionSecret;
|
|
105
|
+
}
|
|
106
|
+
if (cfg.httpscert && typeof cfg.httpscert === "object") {
|
|
107
|
+
if (isNonEmptyString(cfg.httpscert.key)) {
|
|
108
|
+
stripped.push("httpscert.key");
|
|
109
|
+
delete cfg.httpscert.key;
|
|
110
|
+
}
|
|
111
|
+
if (isNonEmptyString(cfg.httpscert.cert)) {
|
|
112
|
+
stripped.push("httpscert.cert");
|
|
113
|
+
delete cfg.httpscert.cert;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(cfg.wssec) && isNonEmptyString(cfg.wssec[0])) {
|
|
117
|
+
stripped.push("wssec[0]");
|
|
118
|
+
cfg.wssec = [cfg.wssec[0] ? "" : cfg.wssec[0], cfg.wssec[1] ?? "", cfg.wssec[2] ?? "-"];
|
|
119
|
+
cfg.wssec[0] = "";
|
|
120
|
+
}
|
|
121
|
+
return stripped;
|
|
122
|
+
}
|
|
123
|
+
__name(stripForbiddenSecretsFromHostConfig, "stripForbiddenSecretsFromHostConfig");
|
|
124
|
+
function pickFromLocalJson(local) {
|
|
125
|
+
if (!local) return {};
|
|
126
|
+
return {
|
|
127
|
+
password: isNonEmptyString(local.password) ? local.password : void 0,
|
|
128
|
+
asaisn: isNonEmptyString(local.asaisn) ? local.asaisn : void 0,
|
|
129
|
+
aesKeyBase64: isNonEmptyString(local.aesKeyBase64) ? local.aesKeyBase64 : isNonEmptyString(local.aes?.keybase64) ? local.aes.keybase64 : void 0,
|
|
130
|
+
sessionSecret: isNonEmptyString(local.sessionSecret) ? local.sessionSecret : void 0,
|
|
131
|
+
httpsKey: isNonEmptyString(local.httpsKey) ? local.httpsKey : void 0,
|
|
132
|
+
httpsCert: isNonEmptyString(local.httpsCert) ? local.httpsCert : void 0,
|
|
133
|
+
wssecToken: isNonEmptyString(local.wssecToken) ? local.wssecToken : void 0
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
__name(pickFromLocalJson, "pickFromLocalJson");
|
|
137
|
+
async function loadAsaiSecrets(serverRoot) {
|
|
138
|
+
const sources = [];
|
|
139
|
+
const dotenv = await loadDotEnvFile(path.join(serverRoot, ".env"));
|
|
140
|
+
if (Object.keys(dotenv).length) sources.push("webserver/.env");
|
|
141
|
+
const localPath = path.join(serverRoot, "websys/sys/asaihost.secrets.local.json");
|
|
142
|
+
const localJson = await readJsonFile(localPath);
|
|
143
|
+
if (localJson) sources.push("websys/sys/asaihost.secrets.local.json");
|
|
144
|
+
const fromLocal = pickFromLocalJson(localJson);
|
|
145
|
+
const password = envGet(ENV_MAP.password, dotenv) ?? fromLocal.password;
|
|
146
|
+
const asaisn = envGet(ENV_MAP.asaisn, dotenv) ?? fromLocal.asaisn;
|
|
147
|
+
const aesKeyBase64 = envGet(ENV_MAP.aesKeyBase64, dotenv) ?? fromLocal.aesKeyBase64;
|
|
148
|
+
let sessionSecret = envGet(ENV_MAP.sessionSecret, dotenv) ?? fromLocal.sessionSecret;
|
|
149
|
+
let httpsKey = envGet(ENV_MAP.httpsKey, dotenv) ?? fromLocal.httpsKey;
|
|
150
|
+
let httpsCert = envGet(ENV_MAP.httpsCert, dotenv) ?? fromLocal.httpsCert;
|
|
151
|
+
const wssecToken = envGet(ENV_MAP.wssecToken, dotenv) ?? fromLocal.wssecToken;
|
|
152
|
+
const siemToken = envGet(ENV_MAP.siemToken, dotenv) ?? fromLocal.siemToken;
|
|
153
|
+
if (envGet(ENV_MAP.password, dotenv)) sources.push("env:ASAI_HOST_PASSWORD");
|
|
154
|
+
if (envGet(ENV_MAP.asaisn, dotenv)) sources.push("env:ASAI_ASAISN");
|
|
155
|
+
if (envGet(ENV_MAP.aesKeyBase64, dotenv)) sources.push("env:ASAI_AES_KEYBASE64");
|
|
156
|
+
if (envGet(ENV_MAP.sessionSecret, dotenv)) sources.push("env:ASAI_SESSION_SECRET");
|
|
157
|
+
const keyPath = envGet(ENV_MAP.httpsKeyPath, dotenv);
|
|
158
|
+
const certPath = envGet(ENV_MAP.httpsCertPath, dotenv);
|
|
159
|
+
if (!httpsKey && keyPath) {
|
|
160
|
+
httpsKey = await readPemFile(serverRoot, keyPath);
|
|
161
|
+
if (httpsKey) sources.push("env:ASAI_HTTPS_KEY_PATH");
|
|
162
|
+
}
|
|
163
|
+
if (!httpsCert && certPath) {
|
|
164
|
+
httpsCert = await readPemFile(serverRoot, certPath);
|
|
165
|
+
if (httpsCert) sources.push("env:ASAI_HTTPS_CERT_PATH");
|
|
166
|
+
}
|
|
167
|
+
if (!sessionSecret && asaisn) {
|
|
168
|
+
sessionSecret = asaisn;
|
|
169
|
+
sources.push("derived:sessionSecret<=asaisn");
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
secrets: {
|
|
173
|
+
password: password ?? "",
|
|
174
|
+
asaisn: asaisn ?? "",
|
|
175
|
+
aesKeyBase64: aesKeyBase64 ?? "",
|
|
176
|
+
sessionSecret: sessionSecret ?? "",
|
|
177
|
+
httpsKey,
|
|
178
|
+
httpsCert,
|
|
179
|
+
wssecToken,
|
|
180
|
+
siemToken
|
|
181
|
+
},
|
|
182
|
+
strippedFromJson: [],
|
|
183
|
+
sources: [...new Set(sources)]
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
__name(loadAsaiSecrets, "loadAsaiSecrets");
|
|
187
|
+
function applySecretsToHostConfig(cfg, secrets) {
|
|
188
|
+
if (secrets.password) cfg.password = secrets.password;
|
|
189
|
+
if (secrets.asaisn) cfg.asaisn = secrets.asaisn;
|
|
190
|
+
if (secrets.aesKeyBase64) {
|
|
191
|
+
if (!cfg.aes || typeof cfg.aes !== "object") cfg.aes = {};
|
|
192
|
+
cfg.aes.keybase64 = secrets.aesKeyBase64;
|
|
193
|
+
}
|
|
194
|
+
if (secrets.sessionSecret) {
|
|
195
|
+
if (!cfg.security || typeof cfg.security !== "object") cfg.security = {};
|
|
196
|
+
cfg.security.sessionSecret = secrets.sessionSecret;
|
|
197
|
+
}
|
|
198
|
+
if (secrets.httpsKey && secrets.httpsCert) {
|
|
199
|
+
if (!cfg.httpscert || typeof cfg.httpscert !== "object") cfg.httpscert = {};
|
|
200
|
+
cfg.httpscert.key = secrets.httpsKey;
|
|
201
|
+
cfg.httpscert.cert = secrets.httpsCert;
|
|
202
|
+
} else if (cfg.httpscert && typeof cfg.httpscert === "object") {
|
|
203
|
+
const hc = cfg.httpscert;
|
|
204
|
+
if (!secrets.httpsKey && isNonEmptyString(hc.keyPath)) {
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (secrets.wssecToken) {
|
|
208
|
+
cfg.wssec = [secrets.wssecToken, "", "-"];
|
|
209
|
+
}
|
|
210
|
+
if (secrets.siemToken) {
|
|
211
|
+
if (!cfg.logger || typeof cfg.logger !== "object") cfg.logger = {};
|
|
212
|
+
if (!cfg.logger.siem || typeof cfg.logger.siem !== "object") cfg.logger.siem = {};
|
|
213
|
+
cfg.logger.siem.token = secrets.siemToken;
|
|
214
|
+
cfg.logger.siem.enabled = 1;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
__name(applySecretsToHostConfig, "applySecretsToHostConfig");
|
|
218
|
+
async function resolveHttpsCertFromPaths(serverRoot, cfg) {
|
|
219
|
+
const hc = cfg?.httpscert;
|
|
220
|
+
if (!hc || typeof hc !== "object") return;
|
|
221
|
+
if (hc.key && hc.cert) return;
|
|
222
|
+
const keyPath = hc.keyPath;
|
|
223
|
+
const certPath = hc.certPath;
|
|
224
|
+
if (!isNonEmptyString(keyPath) || !isNonEmptyString(certPath)) return;
|
|
225
|
+
const key = await readPemFile(serverRoot, keyPath);
|
|
226
|
+
const cert = await readPemFile(serverRoot, certPath);
|
|
227
|
+
if (key && cert) {
|
|
228
|
+
hc.key = key;
|
|
229
|
+
hc.cert = cert;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
__name(resolveHttpsCertFromPaths, "resolveHttpsCertFromPaths");
|
|
233
|
+
function validateAsaiSecrets(secrets, opts) {
|
|
234
|
+
const errors = [];
|
|
235
|
+
const prod = opts?.isProduction ?? process.env.NODE_ENV === "production";
|
|
236
|
+
if (!isNonEmptyString(secrets.password)) {
|
|
237
|
+
errors.push("\u7F3A\u5C11 ASAI_HOST_PASSWORD\uFF08\u6216 websys/sys/asaihost.secrets.local.json \u4E2D\u7684 password\uFF09");
|
|
238
|
+
}
|
|
239
|
+
if (!isNonEmptyString(secrets.asaisn)) {
|
|
240
|
+
errors.push("\u7F3A\u5C11 ASAI_ASAISN\uFF08Userinfo \u7F16\u89E3\u7801\u76D0\uFF0C\u7981\u6B62\u5199\u5165 asaihost.json\uFF09");
|
|
241
|
+
}
|
|
242
|
+
if (!isNonEmptyString(secrets.aesKeyBase64)) {
|
|
243
|
+
errors.push("\u7F3A\u5C11 ASAI_AES_KEYBASE64\uFF08AES \u5BC6\u94A5\uFF0C\u7981\u6B62\u5199\u5165 asaihost.json\uFF09");
|
|
244
|
+
}
|
|
245
|
+
if (!isNonEmptyString(secrets.sessionSecret)) {
|
|
246
|
+
errors.push("\u7F3A\u5C11 ASAI_SESSION_SECRET\uFF08\u6216 ASAI_ASAISN \u4F5C\u4E3A\u6D3E\u751F\u6E90\uFF09");
|
|
247
|
+
}
|
|
248
|
+
if (prod) {
|
|
249
|
+
const weak = ["asai", "password", "123456", "admin", "changeme"];
|
|
250
|
+
if (weak.includes(secrets.password.toLowerCase())) {
|
|
251
|
+
errors.push("\u751F\u4EA7\u73AF\u5883\u7981\u6B62\u4F7F\u7528\u5F31\u9ED8\u8BA4\u5BC6\u7801\uFF08IEC 62443-4-2 CR 1.7\uFF09");
|
|
252
|
+
}
|
|
253
|
+
if (secrets.password.length < 12) {
|
|
254
|
+
errors.push("\u751F\u4EA7\u73AF\u5883 ASAI_HOST_PASSWORD \u957F\u5EA6\u5E94 \u2265 12");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return errors;
|
|
258
|
+
}
|
|
259
|
+
__name(validateAsaiSecrets, "validateAsaiSecrets");
|
|
260
|
+
function isDevDefaultPassword(password) {
|
|
261
|
+
return password === "asai" || password === "AsaiDevOnly1";
|
|
262
|
+
}
|
|
263
|
+
__name(isDevDefaultPassword, "isDevDefaultPassword");
|
|
264
|
+
|
|
265
|
+
// src/sysserver/api/login/service/user-levels.ts
|
|
266
|
+
var USER_LV = {
|
|
267
|
+
OPERATOR: 3,
|
|
268
|
+
ENGINEER: 5,
|
|
269
|
+
ADMIN: 7,
|
|
270
|
+
DEVELOPER: 9,
|
|
271
|
+
SUPER: 99
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// src/sysserver/api/login/store/user-store.ts
|
|
275
|
+
function toUserRecord(row) {
|
|
276
|
+
return {
|
|
277
|
+
id: String(row.id),
|
|
278
|
+
us: String(row.us),
|
|
279
|
+
email: row.email ? String(row.email) : void 0,
|
|
280
|
+
passHash: String(row.passHash),
|
|
281
|
+
passSalt: String(row.passSalt),
|
|
282
|
+
lv: Number(row.lv ?? 0),
|
|
283
|
+
role: row.role ? String(row.role) : void 0,
|
|
284
|
+
status: row.status || "active",
|
|
285
|
+
failCount: Number(row.failCount ?? 0),
|
|
286
|
+
lockedUntil: row.lockedUntil != null ? Number(row.lockedUntil) : void 0,
|
|
287
|
+
mustChangePassword: row.mustChangePassword != null ? Number(row.mustChangePassword) : void 0,
|
|
288
|
+
passwordChangedAt: row.passwordChangedAt != null ? Number(row.passwordChangedAt) : void 0,
|
|
289
|
+
passHistory: row.passHistory ? String(row.passHistory) : void 0,
|
|
290
|
+
createdAt: Number(row.createdAt),
|
|
291
|
+
updatedAt: Number(row.updatedAt)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
__name(toUserRecord, "toUserRecord");
|
|
295
|
+
function toSessionRecord(row) {
|
|
296
|
+
return {
|
|
297
|
+
id: String(row.id),
|
|
298
|
+
us: String(row.us),
|
|
299
|
+
ip: String(row.ip),
|
|
300
|
+
createdAt: Number(row.createdAt),
|
|
301
|
+
expiresAt: Number(row.expiresAt),
|
|
302
|
+
lastActivityAt: row.lastActivityAt != null ? Number(row.lastActivityAt) : void 0,
|
|
303
|
+
pri: String(row.pri),
|
|
304
|
+
pub: String(row.pub)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
__name(toSessionRecord, "toSessionRecord");
|
|
308
|
+
async function ensureBootstrapAdmin($asai) {
|
|
309
|
+
const users = await dbListUsers($asai);
|
|
310
|
+
if (users.length > 0) return;
|
|
311
|
+
const { hashPassword } = await import("./password-63OY27RX.es.js");
|
|
312
|
+
const pwd = $asai?.hostconfig?.password;
|
|
313
|
+
if (!pwd) throw new Error("[user-store] ASAI_HOST_PASSWORD \u672A\u914D\u7F6E\uFF0C\u65E0\u6CD5\u521B\u5EFA bootstrap admin");
|
|
314
|
+
const { passHash, passSalt } = hashPassword(pwd);
|
|
315
|
+
const now = Date.now();
|
|
316
|
+
const role = resolveRoleForLevel($asai, USER_LV.SUPER).name;
|
|
317
|
+
await dbCreateUser($asai, {
|
|
318
|
+
id: randomUUID(),
|
|
319
|
+
us: "admin",
|
|
320
|
+
email: "",
|
|
321
|
+
passHash,
|
|
322
|
+
passSalt,
|
|
323
|
+
lv: USER_LV.SUPER,
|
|
324
|
+
role,
|
|
325
|
+
status: "active",
|
|
326
|
+
failCount: 0,
|
|
327
|
+
lockedUntil: "",
|
|
328
|
+
mustChangePassword: isDevDefaultPassword(pwd) ? 1 : 0,
|
|
329
|
+
passwordChangedAt: now,
|
|
330
|
+
passHistory: "[]",
|
|
331
|
+
createdAt: now,
|
|
332
|
+
updatedAt: now
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
__name(ensureBootstrapAdmin, "ensureBootstrapAdmin");
|
|
336
|
+
function createUserStore($asai) {
|
|
337
|
+
return {
|
|
338
|
+
async findByUsername(us) {
|
|
339
|
+
const row = await dbFindUser($asai, "us", us);
|
|
340
|
+
return row ? toUserRecord(row) : null;
|
|
341
|
+
},
|
|
342
|
+
async findById(id) {
|
|
343
|
+
const row = await dbFindUser($asai, "id", id);
|
|
344
|
+
return row ? toUserRecord(row) : null;
|
|
345
|
+
},
|
|
346
|
+
async listUsers() {
|
|
347
|
+
const rows = await dbListUsers($asai);
|
|
348
|
+
return rows.map(toUserRecord);
|
|
349
|
+
},
|
|
350
|
+
async createUser(data) {
|
|
351
|
+
const existing = await dbFindUser($asai, "us", data.us);
|
|
352
|
+
if (existing) throw new Error("Username already exists");
|
|
353
|
+
const now = Date.now();
|
|
354
|
+
const user = {
|
|
355
|
+
id: randomUUID(),
|
|
356
|
+
us: data.us,
|
|
357
|
+
email: data.email,
|
|
358
|
+
passHash: data.passHash,
|
|
359
|
+
passSalt: data.passSalt,
|
|
360
|
+
lv: data.lv ?? 0,
|
|
361
|
+
role: data.role || resolveRoleForLevel($asai, data.lv ?? 0).name,
|
|
362
|
+
status: data.status ?? "active",
|
|
363
|
+
failCount: 0,
|
|
364
|
+
mustChangePassword: data.mustChangePassword,
|
|
365
|
+
passwordChangedAt: data.passwordChangedAt ?? Date.now(),
|
|
366
|
+
passHistory: data.passHistory || "[]",
|
|
367
|
+
createdAt: now,
|
|
368
|
+
updatedAt: now
|
|
369
|
+
};
|
|
370
|
+
await dbCreateUser($asai, {
|
|
371
|
+
...user,
|
|
372
|
+
email: user.email || "",
|
|
373
|
+
lockedUntil: user.lockedUntil ?? "",
|
|
374
|
+
mustChangePassword: user.mustChangePassword ?? 0,
|
|
375
|
+
passwordChangedAt: user.passwordChangedAt ?? now,
|
|
376
|
+
passHistory: user.passHistory || "[]",
|
|
377
|
+
role: user.role || ""
|
|
378
|
+
});
|
|
379
|
+
return user;
|
|
380
|
+
},
|
|
381
|
+
async updateUser(us, patch) {
|
|
382
|
+
const dbPatch = { ...patch };
|
|
383
|
+
if (patch.lockedUntil === void 0 && "lockedUntil" in patch) {
|
|
384
|
+
dbPatch.lockedUntil = "";
|
|
385
|
+
}
|
|
386
|
+
const row = await dbUpdateUser($asai, us, dbPatch);
|
|
387
|
+
return row ? toUserRecord(row) : null;
|
|
388
|
+
},
|
|
389
|
+
async getSessions() {
|
|
390
|
+
const sessions = (await dbListSessions($asai)).map(toSessionRecord);
|
|
391
|
+
const now = Date.now();
|
|
392
|
+
const valid = sessions.filter((s) => s.expiresAt > now);
|
|
393
|
+
if (valid.length !== sessions.length) {
|
|
394
|
+
await dbWriteSessions(
|
|
395
|
+
$asai,
|
|
396
|
+
valid.map((s) => ({ ...s }))
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return valid;
|
|
400
|
+
},
|
|
401
|
+
async addSession(session) {
|
|
402
|
+
await dbAddSession($asai, { ...session });
|
|
403
|
+
},
|
|
404
|
+
async removeSession(sessionId) {
|
|
405
|
+
await dbRemoveSession($asai, sessionId);
|
|
406
|
+
},
|
|
407
|
+
async removeSessionsByUser(us) {
|
|
408
|
+
await dbRemoveSessionsByUser($asai, us);
|
|
409
|
+
},
|
|
410
|
+
async countSessions() {
|
|
411
|
+
return (await this.getSessions()).length;
|
|
412
|
+
},
|
|
413
|
+
async countSessionsByUser(us) {
|
|
414
|
+
return (await this.getSessions()).filter((s) => s.us === us).length;
|
|
415
|
+
},
|
|
416
|
+
async deleteUser(us) {
|
|
417
|
+
return dbDeleteUser($asai, us);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
__name(createUserStore, "createUserStore");
|
|
422
|
+
|
|
423
|
+
export {
|
|
424
|
+
loadDotEnvFile,
|
|
425
|
+
stripForbiddenSecretsFromHostConfig,
|
|
426
|
+
loadAsaiSecrets,
|
|
427
|
+
applySecretsToHostConfig,
|
|
428
|
+
resolveHttpsCertFromPaths,
|
|
429
|
+
validateAsaiSecrets,
|
|
430
|
+
isDevDefaultPassword,
|
|
431
|
+
USER_LV,
|
|
432
|
+
ensureBootstrapAdmin,
|
|
433
|
+
createUserStore
|
|
434
|
+
};
|
|
435
|
+
//# sourceMappingURL=chunk-E7V3EBDB.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sysserver/api/login/store/user-store.ts","../src/config/secrets.ts","../src/sysserver/api/login/service/user-levels.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\r\nimport type { IUserStore, SessionRecord, UserRecord } from './types';\r\nimport { isDevDefaultPassword } from '../../../../config/secrets';\r\nimport { resolveRoleForLevel } from '../../../../infra/security-policy';\r\nimport { USER_LV } from '../service/user-levels';\r\nimport {\r\n\tdbAddSession,\r\n\tdbCreateUser,\r\n\tdbFindUser,\r\n\tdbDeleteUser,\r\n\tdbListSessions,\r\n\tdbListUsers,\r\n\tdbRemoveSession,\r\n\tdbRemoveSessionsByUser,\r\n\tdbUpdateUser,\r\n\tdbWriteSessions,\r\n} from './user-db';\r\n\r\nfunction toUserRecord(row: Record<string, unknown>): UserRecord {\r\n\treturn {\r\n\t\tid: String(row.id),\r\n\t\tus: String(row.us),\r\n\t\temail: row.email ? String(row.email) : undefined,\r\n\t\tpassHash: String(row.passHash),\r\n\t\tpassSalt: String(row.passSalt),\r\n\t\tlv: Number(row.lv ?? 0),\r\n\t\trole: row.role ? String(row.role) : undefined,\r\n\t\tstatus: (row.status as UserRecord['status']) || 'active',\r\n\t\tfailCount: Number(row.failCount ?? 0),\r\n\t\tlockedUntil: row.lockedUntil != null ? Number(row.lockedUntil) : undefined,\r\n\t\tmustChangePassword: row.mustChangePassword != null ? Number(row.mustChangePassword) : undefined,\r\n\t\tpasswordChangedAt: row.passwordChangedAt != null ? Number(row.passwordChangedAt) : undefined,\r\n\t\tpassHistory: row.passHistory ? String(row.passHistory) : undefined,\r\n\t\tcreatedAt: Number(row.createdAt),\r\n\t\tupdatedAt: Number(row.updatedAt),\r\n\t};\r\n}\r\n\r\nfunction toSessionRecord(row: Record<string, unknown>): SessionRecord {\r\n\treturn {\r\n\t\tid: String(row.id),\r\n\t\tus: String(row.us),\r\n\t\tip: String(row.ip),\r\n\t\tcreatedAt: Number(row.createdAt),\r\n\t\texpiresAt: Number(row.expiresAt),\r\n\t\tlastActivityAt: row.lastActivityAt != null ? Number(row.lastActivityAt) : undefined,\r\n\t\tpri: String(row.pri),\r\n\t\tpub: String(row.pub),\r\n\t};\r\n}\r\n\r\nexport async function ensureBootstrapAdmin($asai: any): Promise<void> {\r\n\tconst users = await dbListUsers($asai);\r\n\tif (users.length > 0) return;\r\n\tconst { hashPassword } = await import('../service/password');\r\n\tconst pwd = $asai?.hostconfig?.password;\r\n\tif (!pwd) throw new Error('[user-store] ASAI_HOST_PASSWORD 未配置,无法创建 bootstrap admin');\r\n\tconst { passHash, passSalt } = hashPassword(pwd);\r\n\tconst now = Date.now();\r\n\tconst role = resolveRoleForLevel($asai, USER_LV.SUPER).name;\r\n\tawait dbCreateUser($asai, {\r\n\t\tid: randomUUID(),\r\n\t\tus: 'admin',\r\n\t\temail: '',\r\n\t\tpassHash,\r\n\t\tpassSalt,\r\n\t\tlv: USER_LV.SUPER,\r\n\t\trole,\r\n\t\tstatus: 'active',\r\n\t\tfailCount: 0,\r\n\t\tlockedUntil: '',\r\n\t\tmustChangePassword: isDevDefaultPassword(pwd) ? 1 : 0,\r\n\t\tpasswordChangedAt: now,\r\n\t\tpassHistory: '[]',\r\n\t\tcreatedAt: now,\r\n\t\tupdatedAt: now,\r\n\t});\r\n}\r\n\r\nexport function createUserStore($asai: any): IUserStore {\r\n\treturn {\r\n\t\tasync findByUsername(us) {\r\n\t\t\tconst row = await dbFindUser($asai, 'us', us);\r\n\t\t\treturn row ? toUserRecord(row) : null;\r\n\t\t},\r\n\t\tasync findById(id) {\r\n\t\t\tconst row = await dbFindUser($asai, 'id', id);\r\n\t\t\treturn row ? toUserRecord(row) : null;\r\n\t\t},\r\n\t\tasync listUsers() {\r\n\t\t\tconst rows = await dbListUsers($asai);\r\n\t\t\treturn rows.map(toUserRecord);\r\n\t\t},\r\n\t\tasync createUser(data) {\r\n\t\t\tconst existing = await dbFindUser($asai, 'us', data.us);\r\n\t\t\tif (existing) throw new Error('Username already exists');\r\n\t\t\tconst now = Date.now();\r\n\t\t\tconst user: UserRecord = {\r\n\t\t\t\tid: randomUUID(),\r\n\t\t\t\tus: data.us,\r\n\t\t\t\temail: data.email,\r\n\t\t\t\tpassHash: data.passHash,\r\n\t\t\t\tpassSalt: data.passSalt,\r\n\t\t\t\tlv: data.lv ?? 0,\r\n\t\t\t\trole: data.role || resolveRoleForLevel($asai, data.lv ?? 0).name,\r\n\t\t\t\tstatus: data.status ?? 'active',\r\n\t\t\t\tfailCount: 0,\r\n\t\t\t\tmustChangePassword: data.mustChangePassword,\r\n\t\t\t\tpasswordChangedAt: data.passwordChangedAt ?? Date.now(),\r\n\t\t\t\tpassHistory: data.passHistory || '[]',\r\n\t\t\t\tcreatedAt: now,\r\n\t\t\t\tupdatedAt: now,\r\n\t\t\t};\r\n\t\t\tawait dbCreateUser($asai, {\r\n\t\t\t\t...user,\r\n\t\t\t\temail: user.email || '',\r\n\t\t\t\tlockedUntil: user.lockedUntil ?? '',\r\n\t\t\t\tmustChangePassword: user.mustChangePassword ?? 0,\r\n\t\t\t\tpasswordChangedAt: user.passwordChangedAt ?? now,\r\n\t\t\t\tpassHistory: user.passHistory || '[]',\r\n\t\t\t\trole: user.role || '',\r\n\t\t\t});\r\n\t\t\treturn user;\r\n\t\t},\r\n\t\tasync updateUser(us, patch) {\r\n\t\t\tconst dbPatch: Record<string, unknown> = { ...patch };\r\n\t\t\tif (patch.lockedUntil === undefined && 'lockedUntil' in patch) {\r\n\t\t\t\tdbPatch.lockedUntil = '';\r\n\t\t\t}\r\n\t\t\tconst row = await dbUpdateUser($asai, us, dbPatch);\r\n\t\t\treturn row ? toUserRecord(row) : null;\r\n\t\t},\r\n\t\tasync getSessions() {\r\n\t\t\tconst sessions = (await dbListSessions($asai)).map(toSessionRecord);\r\n\t\t\tconst now = Date.now();\r\n\t\t\tconst valid = sessions.filter((s) => s.expiresAt > now);\r\n\t\t\tif (valid.length !== sessions.length) {\r\n\t\t\t\tawait dbWriteSessions(\r\n\t\t\t\t\t$asai,\r\n\t\t\t\t\tvalid.map((s) => ({ ...s })),\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\treturn valid;\r\n\t\t},\r\n\t\tasync addSession(session) {\r\n\t\t\tawait dbAddSession($asai, { ...session });\r\n\t\t},\r\n\t\tasync removeSession(sessionId) {\r\n\t\t\tawait dbRemoveSession($asai, sessionId);\r\n\t\t},\r\n\t\tasync removeSessionsByUser(us) {\r\n\t\t\tawait dbRemoveSessionsByUser($asai, us);\r\n\t\t},\r\n\t\tasync countSessions() {\r\n\t\t\treturn (await this.getSessions()).length;\r\n\t\t},\r\n\t\tasync countSessionsByUser(us) {\r\n\t\t\treturn (await this.getSessions()).filter((s) => s.us === us).length;\r\n\t\t},\r\n\t\tasync deleteUser(us) {\r\n\t\t\treturn dbDeleteUser($asai, us);\r\n\t\t},\r\n\t};\r\n}\r\n","/**\r\n * IEC 62443-4-2 CR 4.1 / CR 1.7 — 密钥与凭据不得写入版本库内的 asaihost.json。\r\n * 加载顺序:process.env > webserver/.env > asaihost.secrets.local.json\r\n */\r\nimport fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\n\r\n/** 禁止出现在 asaihost.json 中的敏感字段(检测到则剥离并审计) */\r\nexport const FORBIDDEN_HOSTCONFIG_KEYS = [\r\n\t'password',\r\n\t'asaisn',\r\n] as const;\r\n\r\nexport interface AsaiSecrets {\r\n\tpassword: string;\r\n\tasaisn: string;\r\n\taesKeyBase64: string;\r\n\tsessionSecret: string;\r\n\thttpsKey?: string;\r\n\thttpsCert?: string;\r\n\twssecToken?: string;\r\n\tsiemToken?: string;\r\n}\r\n\r\nexport interface SecretsLoadResult {\r\n\tsecrets: AsaiSecrets;\r\n\tstrippedFromJson: string[];\r\n\tsources: string[];\r\n}\r\n\r\nconst ENV_MAP = {\r\n\tpassword: 'ASAI_HOST_PASSWORD',\r\n\tasaisn: 'ASAI_ASAISN',\r\n\taesKeyBase64: 'ASAI_AES_KEYBASE64',\r\n\tsessionSecret: 'ASAI_SESSION_SECRET',\r\n\thttpsKey: 'ASAI_HTTPS_KEY',\r\n\thttpsCert: 'ASAI_HTTPS_CERT',\r\n\thttpsKeyPath: 'ASAI_HTTPS_KEY_PATH',\r\n\thttpsCertPath: 'ASAI_HTTPS_CERT_PATH',\r\n\twssecToken: 'ASAI_WSSEC_TOKEN',\r\n\tsiemToken: 'ASAI_SIEM_TOKEN',\r\n} as const;\r\n\r\nfunction isNonEmptyString(v: unknown): v is string {\r\n\treturn typeof v === 'string' && v.trim().length > 0;\r\n}\r\n\r\n/** 轻量 .env 解析(不引入 dotenv 依赖) */\r\nexport async function loadDotEnvFile(envPath: string): Promise<Record<string, string>> {\r\n\tconst out: Record<string, string> = {};\r\n\ttry {\r\n\t\tconst raw = await fs.readFile(envPath, 'utf8');\r\n\t\tfor (const line of raw.split(/\\r?\\n/)) {\r\n\t\t\tconst trimmed = line.trim();\r\n\t\t\tif (!trimmed || trimmed.startsWith('#')) continue;\r\n\t\t\tconst eq = trimmed.indexOf('=');\r\n\t\t\tif (eq <= 0) continue;\r\n\t\t\tconst key = trimmed.slice(0, eq).trim();\r\n\t\t\tlet val = trimmed.slice(eq + 1).trim();\r\n\t\t\tif ((val.startsWith('\"') && val.endsWith('\"')) || (val.startsWith(\"'\") && val.endsWith(\"'\"))) {\r\n\t\t\t\tval = val.slice(1, -1);\r\n\t\t\t}\r\n\t\t\tif (key) out[key] = val;\r\n\t\t}\r\n\t} catch {\r\n\t\t/* optional file */\r\n\t}\r\n\treturn out;\r\n}\r\n\r\nfunction envGet(name: string, dotenv: Record<string, string>): string | undefined {\r\n\tconst fromProcess = process.env[name];\r\n\tif (isNonEmptyString(fromProcess)) return fromProcess.trim();\r\n\tconst fromFile = dotenv[name];\r\n\tif (isNonEmptyString(fromFile)) return fromFile.trim();\r\n\treturn undefined;\r\n}\r\n\r\nasync function readJsonFile(filePath: string): Promise<Record<string, unknown> | null> {\r\n\ttry {\r\n\t\tconst raw = await fs.readFile(filePath, 'utf8');\r\n\t\tconst data = JSON.parse(raw);\r\n\t\treturn data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : null;\r\n\t} catch {\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nasync function readPemFile(serverRoot: string, filePath: string): Promise<string | undefined> {\r\n\tconst abs = path.isAbsolute(filePath) ? filePath : path.join(serverRoot, filePath);\r\n\ttry {\r\n\t\treturn (await fs.readFile(abs, 'utf8')).trim();\r\n\t} catch {\r\n\t\treturn undefined;\r\n\t}\r\n}\r\n\r\n/**\r\n * 从 asaihost.json 剥离敏感字段(禁止落盘/进仓库)。\r\n * 返回被剥离的字段路径列表,供安全审计。\r\n */\r\nexport function stripForbiddenSecretsFromHostConfig(cfg: Record<string, any>): string[] {\r\n\tconst stripped: string[] = [];\r\n\r\n\tif (isNonEmptyString(cfg.password)) {\r\n\t\tstripped.push('password');\r\n\t\tdelete cfg.password;\r\n\t}\r\n\tif (isNonEmptyString(cfg.asaisn)) {\r\n\t\tstripped.push('asaisn');\r\n\t\tdelete cfg.asaisn;\r\n\t}\r\n\tif (cfg.aes && typeof cfg.aes === 'object' && isNonEmptyString(cfg.aes.keybase64)) {\r\n\t\tstripped.push('aes.keybase64');\r\n\t\tdelete cfg.aes.keybase64;\r\n\t}\r\n\tif (cfg.security && typeof cfg.security === 'object' && isNonEmptyString(cfg.security.sessionSecret)) {\r\n\t\tstripped.push('security.sessionSecret');\r\n\t\tdelete cfg.security.sessionSecret;\r\n\t}\r\n\tif (cfg.httpscert && typeof cfg.httpscert === 'object') {\r\n\t\tif (isNonEmptyString(cfg.httpscert.key)) {\r\n\t\t\tstripped.push('httpscert.key');\r\n\t\t\tdelete cfg.httpscert.key;\r\n\t\t}\r\n\t\tif (isNonEmptyString(cfg.httpscert.cert)) {\r\n\t\t\tstripped.push('httpscert.cert');\r\n\t\t\tdelete cfg.httpscert.cert;\r\n\t\t}\r\n\t}\r\n\tif (Array.isArray(cfg.wssec) && isNonEmptyString(cfg.wssec[0])) {\r\n\t\tstripped.push('wssec[0]');\r\n\t\tcfg.wssec = [cfg.wssec[0] ? '' : cfg.wssec[0], cfg.wssec[1] ?? '', cfg.wssec[2] ?? '-'];\r\n\t\tcfg.wssec[0] = '';\r\n\t}\r\n\r\n\treturn stripped;\r\n}\r\n\r\nfunction pickFromLocalJson(local: Record<string, unknown> | null): Partial<AsaiSecrets> {\r\n\tif (!local) return {};\r\n\treturn {\r\n\t\tpassword: isNonEmptyString(local.password) ? local.password : undefined,\r\n\t\tasaisn: isNonEmptyString(local.asaisn) ? local.asaisn : undefined,\r\n\t\taesKeyBase64: isNonEmptyString(local.aesKeyBase64)\r\n\t\t\t? local.aesKeyBase64\r\n\t\t\t: isNonEmptyString((local.aes as any)?.keybase64)\r\n\t\t\t\t? (local.aes as any).keybase64\r\n\t\t\t\t: undefined,\r\n\t\tsessionSecret: isNonEmptyString(local.sessionSecret) ? local.sessionSecret : undefined,\r\n\t\thttpsKey: isNonEmptyString(local.httpsKey) ? local.httpsKey : undefined,\r\n\t\thttpsCert: isNonEmptyString(local.httpsCert) ? local.httpsCert : undefined,\r\n\t\twssecToken: isNonEmptyString(local.wssecToken) ? local.wssecToken : undefined,\r\n\t};\r\n}\r\n\r\nexport async function loadAsaiSecrets(serverRoot: string): Promise<SecretsLoadResult> {\r\n\tconst sources: string[] = [];\r\n\tconst dotenv = await loadDotEnvFile(path.join(serverRoot, '.env'));\r\n\tif (Object.keys(dotenv).length) sources.push('webserver/.env');\r\n\r\n\tconst localPath = path.join(serverRoot, 'websys/sys/asaihost.secrets.local.json');\r\n\tconst localJson = await readJsonFile(localPath);\r\n\tif (localJson) sources.push('websys/sys/asaihost.secrets.local.json');\r\n\r\n\tconst fromLocal = pickFromLocalJson(localJson);\r\n\r\n\tconst password =\r\n\t\tenvGet(ENV_MAP.password, dotenv) ?? fromLocal.password;\r\n\tconst asaisn =\r\n\t\tenvGet(ENV_MAP.asaisn, dotenv) ?? fromLocal.asaisn;\r\n\tconst aesKeyBase64 =\r\n\t\tenvGet(ENV_MAP.aesKeyBase64, dotenv) ?? fromLocal.aesKeyBase64;\r\n\tlet sessionSecret =\r\n\t\tenvGet(ENV_MAP.sessionSecret, dotenv) ?? fromLocal.sessionSecret;\r\n\tlet httpsKey =\r\n\t\tenvGet(ENV_MAP.httpsKey, dotenv) ?? fromLocal.httpsKey;\r\n\tlet httpsCert =\r\n\t\tenvGet(ENV_MAP.httpsCert, dotenv) ?? fromLocal.httpsCert;\r\n\tconst wssecToken =\r\n\t\tenvGet(ENV_MAP.wssecToken, dotenv) ?? fromLocal.wssecToken;\r\n\tconst siemToken =\r\n\t\tenvGet(ENV_MAP.siemToken, dotenv) ?? (fromLocal as any).siemToken;\r\n\r\n\tif (envGet(ENV_MAP.password, dotenv)) sources.push('env:ASAI_HOST_PASSWORD');\r\n\tif (envGet(ENV_MAP.asaisn, dotenv)) sources.push('env:ASAI_ASAISN');\r\n\tif (envGet(ENV_MAP.aesKeyBase64, dotenv)) sources.push('env:ASAI_AES_KEYBASE64');\r\n\tif (envGet(ENV_MAP.sessionSecret, dotenv)) sources.push('env:ASAI_SESSION_SECRET');\r\n\r\n\tconst keyPath = envGet(ENV_MAP.httpsKeyPath, dotenv);\r\n\tconst certPath = envGet(ENV_MAP.httpsCertPath, dotenv);\r\n\tif (!httpsKey && keyPath) {\r\n\t\thttpsKey = await readPemFile(serverRoot, keyPath);\r\n\t\tif (httpsKey) sources.push('env:ASAI_HTTPS_KEY_PATH');\r\n\t}\r\n\tif (!httpsCert && certPath) {\r\n\t\thttpsCert = await readPemFile(serverRoot, certPath);\r\n\t\tif (httpsCert) sources.push('env:ASAI_HTTPS_CERT_PATH');\r\n\t}\r\n\r\n\tif (!sessionSecret && asaisn) {\r\n\t\tsessionSecret = asaisn;\r\n\t\tsources.push('derived:sessionSecret<=asaisn');\r\n\t}\r\n\r\n\treturn {\r\n\t\tsecrets: {\r\n\t\t\tpassword: password ?? '',\r\n\t\t\tasaisn: asaisn ?? '',\r\n\t\t\taesKeyBase64: aesKeyBase64 ?? '',\r\n\t\t\tsessionSecret: sessionSecret ?? '',\r\n\t\t\thttpsKey,\r\n\t\t\thttpsCert,\r\n\t\t\twssecToken,\r\n\t\t\tsiemToken,\r\n\t\t},\r\n\t\tstrippedFromJson: [],\r\n\t\tsources: [...new Set(sources)],\r\n\t};\r\n}\r\n\r\n/** 将已加载的密钥合并进运行时 hostconfig(仅内存,不写回 JSON) */\r\nexport function applySecretsToHostConfig(cfg: Record<string, any>, secrets: AsaiSecrets): void {\r\n\tif (secrets.password) cfg.password = secrets.password;\r\n\tif (secrets.asaisn) cfg.asaisn = secrets.asaisn;\r\n\tif (secrets.aesKeyBase64) {\r\n\t\tif (!cfg.aes || typeof cfg.aes !== 'object') cfg.aes = {};\r\n\t\tcfg.aes.keybase64 = secrets.aesKeyBase64;\r\n\t}\r\n\tif (secrets.sessionSecret) {\r\n\t\tif (!cfg.security || typeof cfg.security !== 'object') cfg.security = {};\r\n\t\tcfg.security.sessionSecret = secrets.sessionSecret;\r\n\t}\r\n\tif (secrets.httpsKey && secrets.httpsCert) {\r\n\t\tif (!cfg.httpscert || typeof cfg.httpscert !== 'object') cfg.httpscert = {};\r\n\t\tcfg.httpscert.key = secrets.httpsKey;\r\n\t\tcfg.httpscert.cert = secrets.httpsCert;\r\n\t} else if (cfg.httpscert && typeof cfg.httpscert === 'object') {\r\n\t\tconst hc = cfg.httpscert;\r\n\t\tif (!secrets.httpsKey && isNonEmptyString(hc.keyPath)) {\r\n\t\t\t/* keyPath resolved at apply time by caller if needed */\r\n\t\t}\r\n\t}\r\n\tif (secrets.wssecToken) {\r\n\t\tcfg.wssec = [secrets.wssecToken, '', '-'];\r\n\t}\r\n\tif (secrets.siemToken) {\r\n\t\tif (!cfg.logger || typeof cfg.logger !== 'object') cfg.logger = {};\r\n\t\tif (!cfg.logger.siem || typeof cfg.logger.siem !== 'object') cfg.logger.siem = {};\r\n\t\tcfg.logger.siem.token = secrets.siemToken;\r\n\t\tcfg.logger.siem.enabled = 1;\r\n\t}\r\n}\r\n\r\nexport async function resolveHttpsCertFromPaths(serverRoot: string, cfg: Record<string, any>): Promise<void> {\r\n\tconst hc = cfg?.httpscert;\r\n\tif (!hc || typeof hc !== 'object') return;\r\n\tif (hc.key && hc.cert) return;\r\n\tconst keyPath = hc.keyPath;\r\n\tconst certPath = hc.certPath;\r\n\tif (!isNonEmptyString(keyPath) || !isNonEmptyString(certPath)) return;\r\n\tconst key = await readPemFile(serverRoot, keyPath);\r\n\tconst cert = await readPemFile(serverRoot, certPath);\r\n\tif (key && cert) {\r\n\t\thc.key = key;\r\n\t\thc.cert = cert;\r\n\t}\r\n}\r\n\r\nexport function validateAsaiSecrets(secrets: AsaiSecrets, opts?: { isProduction?: boolean }): string[] {\r\n\tconst errors: string[] = [];\r\n\tconst prod = opts?.isProduction ?? process.env.NODE_ENV === 'production';\r\n\r\n\tif (!isNonEmptyString(secrets.password)) {\r\n\t\terrors.push('缺少 ASAI_HOST_PASSWORD(或 websys/sys/asaihost.secrets.local.json 中的 password)');\r\n\t}\r\n\tif (!isNonEmptyString(secrets.asaisn)) {\r\n\t\terrors.push('缺少 ASAI_ASAISN(Userinfo 编解码盐,禁止写入 asaihost.json)');\r\n\t}\r\n\tif (!isNonEmptyString(secrets.aesKeyBase64)) {\r\n\t\terrors.push('缺少 ASAI_AES_KEYBASE64(AES 密钥,禁止写入 asaihost.json)');\r\n\t}\r\n\tif (!isNonEmptyString(secrets.sessionSecret)) {\r\n\t\terrors.push('缺少 ASAI_SESSION_SECRET(或 ASAI_ASAISN 作为派生源)');\r\n\t}\r\n\r\n\tif (prod) {\r\n\t\tconst weak = ['asai', 'password', '123456', 'admin', 'changeme'];\r\n\t\tif (weak.includes(secrets.password.toLowerCase())) {\r\n\t\t\terrors.push('生产环境禁止使用弱默认密码(IEC 62443-4-2 CR 1.7)');\r\n\t\t}\r\n\t\tif (secrets.password.length < 12) {\r\n\t\t\terrors.push('生产环境 ASAI_HOST_PASSWORD 长度应 ≥ 12');\r\n\t\t}\r\n\t}\r\n\r\n\treturn errors;\r\n}\r\n\r\nexport function isDevDefaultPassword(password: string): boolean {\r\n\treturn password === 'asai' || password === 'AsaiDevOnly1';\r\n}\r\n","/** 内置等级常量(默认阈值;运行时应优先读 user-level-config) */\r\nexport const USER_LV = {\r\n\tOPERATOR: 3,\r\n\tENGINEER: 5,\r\n\tADMIN: 7,\r\n\tDEVELOPER: 9,\r\n\tSUPER: 99,\r\n} as const;\r\n\r\nexport { getPermissionThreshold, getLvSteps, getLevelLabel, isValidUserLv } from './user-level-config';\r\n"],"mappings":"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","names":[]}
|