asaihost-nodejs 0.0.1 → 0.0.3
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 +76 -25
- package/dist/InterventionLog-CRP6YJVM.cjs +30 -0
- package/dist/{InterventionLog-25EHE4QR.cjs.map → InterventionLog-CRP6YJVM.cjs.map} +1 -1
- package/dist/{InterventionLog-R6CCPU2B.es.js → InterventionLog-PZT2CCN3.es.js} +5 -5
- package/dist/LogStore-7MTUQA4G.cjs +54 -0
- package/dist/{LogStore-M2AUBNGO.cjs.map → LogStore-7MTUQA4G.cjs.map} +1 -1
- package/dist/{LogStore-ECXX34WC.es.js → LogStore-Q35YFNAY.es.js} +3 -3
- package/dist/asaihost-nodejs.cjs +1304 -1447
- package/dist/asaihost-nodejs.cjs.map +1 -1
- package/dist/asaihost-nodejs.d.cts +154 -17
- package/dist/asaihost-nodejs.d.ts +154 -17
- package/dist/asaihost-nodejs.es.js +921 -1064
- package/dist/asaihost-nodejs.es.js.map +1 -1
- package/dist/{chunk-HHPGUJ75.cjs → chunk-2BCIIKRG.cjs} +13 -6
- package/dist/chunk-2BCIIKRG.cjs.map +1 -0
- package/dist/{chunk-G4WVDUBH.cjs → chunk-4E7Z5K5U.cjs} +7 -7
- package/dist/{chunk-G4WVDUBH.cjs.map → chunk-4E7Z5K5U.cjs.map} +1 -1
- package/dist/{chunk-DROOHKB3.es.js → chunk-57UQJKSL.es.js} +2 -2
- package/dist/{chunk-EOGZPRML.es.js → chunk-A5CDJGJI.es.js} +224 -39
- package/dist/chunk-A5CDJGJI.es.js.map +1 -0
- package/dist/{chunk-E7V3EBDB.es.js → chunk-A6VJPLDD.es.js} +5 -3
- package/dist/chunk-A6VJPLDD.es.js.map +1 -0
- package/dist/{chunk-X7VHW2TD.cjs → chunk-H3K33HBA.cjs} +249 -64
- package/dist/chunk-H3K33HBA.cjs.map +1 -0
- package/dist/{chunk-KOFCBB3N.cjs → chunk-K2QPSQEV.cjs} +9 -9
- package/dist/{chunk-KOFCBB3N.cjs.map → chunk-K2QPSQEV.cjs.map} +1 -1
- package/dist/{chunk-LOMSUPPU.es.js → chunk-LTEP4XB2.es.js} +6 -6
- package/dist/{chunk-QWVR2HNT.cjs → chunk-LXJMGLKW.cjs} +21 -19
- package/dist/chunk-LXJMGLKW.cjs.map +1 -0
- package/dist/{chunk-4WJDC4M4.es.js → chunk-NLZNIGNF.es.js} +2 -2
- package/dist/chunk-NLZNIGNF.es.js.map +1 -0
- package/dist/{chunk-W7DVKGX5.cjs → chunk-OCEBEEP5.cjs} +2 -2
- package/dist/{chunk-W7DVKGX5.cjs.map → chunk-OCEBEEP5.cjs.map} +1 -1
- package/dist/{chunk-NLBFIRDB.cjs → chunk-RIRKIYET.cjs} +12 -12
- package/dist/{chunk-NLBFIRDB.cjs.map → chunk-RIRKIYET.cjs.map} +1 -1
- package/dist/{chunk-XBIN4C2V.es.js → chunk-SB23ESX2.es.js} +2 -2
- package/dist/{chunk-VLMM735Q.cjs → chunk-TKJAHTPO.cjs} +26 -26
- package/dist/{chunk-VLMM735Q.cjs.map → chunk-TKJAHTPO.cjs.map} +1 -1
- package/dist/{chunk-MCMADJQR.es.js → chunk-VUZO7DCA.es.js} +4 -4
- package/dist/chunk-VUZO7DCA.es.js.map +1 -0
- package/dist/{chunk-KUZQ36IJ.es.js → chunk-ZQBHJRAO.es.js} +10 -3
- package/dist/chunk-ZQBHJRAO.es.js.map +1 -0
- package/dist/intervention-hooks-KF3SNB5Q.cjs +21 -0
- package/dist/{intervention-hooks-LE6X4C6N.cjs.map → intervention-hooks-KF3SNB5Q.cjs.map} +1 -1
- package/dist/{intervention-hooks-FI4EHOL5.es.js → intervention-hooks-TCGIC4IV.es.js} +6 -6
- package/dist/log-formats-TUXGMCCU.cjs +31 -0
- package/dist/{log-formats-FKFMQKNO.cjs.map → log-formats-TUXGMCCU.cjs.map} +1 -1
- package/dist/{log-formats-I5E32H5F.es.js → log-formats-WW3TDUAK.es.js} +2 -2
- package/dist/{password-63OY27RX.es.js → password-4BABWAG4.es.js} +4 -4
- package/dist/password-PJHJRGPE.cjs +20 -0
- package/dist/{password-NZ5EGDWG.cjs.map → password-PJHJRGPE.cjs.map} +1 -1
- package/dist/user-store-6J2PSEIN.cjs +12 -0
- package/dist/{user-store-SX4N6PQ4.cjs.map → user-store-6J2PSEIN.cjs.map} +1 -1
- package/dist/user-store-UC5URX3C.es.js +12 -0
- package/package.json +2 -13
- package/dist/InterventionLog-25EHE4QR.cjs +0 -30
- package/dist/LogStore-M2AUBNGO.cjs +0 -54
- package/dist/chunk-4WJDC4M4.es.js.map +0 -1
- package/dist/chunk-E7V3EBDB.es.js.map +0 -1
- package/dist/chunk-EOGZPRML.es.js.map +0 -1
- package/dist/chunk-HHPGUJ75.cjs.map +0 -1
- package/dist/chunk-KUZQ36IJ.es.js.map +0 -1
- package/dist/chunk-MCMADJQR.es.js.map +0 -1
- package/dist/chunk-QWVR2HNT.cjs.map +0 -1
- package/dist/chunk-X7VHW2TD.cjs.map +0 -1
- package/dist/intervention-hooks-LE6X4C6N.cjs +0 -21
- package/dist/log-formats-FKFMQKNO.cjs +0 -31
- package/dist/password-NZ5EGDWG.cjs +0 -20
- package/dist/user-store-J7LWEXSW.es.js +0 -12
- package/dist/user-store-SX4N6PQ4.cjs +0 -12
- /package/dist/{InterventionLog-R6CCPU2B.es.js.map → InterventionLog-PZT2CCN3.es.js.map} +0 -0
- /package/dist/{LogStore-ECXX34WC.es.js.map → LogStore-Q35YFNAY.es.js.map} +0 -0
- /package/dist/{chunk-DROOHKB3.es.js.map → chunk-57UQJKSL.es.js.map} +0 -0
- /package/dist/{chunk-LOMSUPPU.es.js.map → chunk-LTEP4XB2.es.js.map} +0 -0
- /package/dist/{chunk-XBIN4C2V.es.js.map → chunk-SB23ESX2.es.js.map} +0 -0
- /package/dist/{intervention-hooks-FI4EHOL5.es.js.map → intervention-hooks-TCGIC4IV.es.js.map} +0 -0
- /package/dist/{log-formats-I5E32H5F.es.js.map → log-formats-WW3TDUAK.es.js.map} +0 -0
- /package/dist/{password-63OY27RX.es.js.map → password-4BABWAG4.es.js.map} +0 -0
- /package/dist/{user-store-J7LWEXSW.es.js.map → user-store-UC5URX3C.es.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sysserver/api/login/store/user-db.ts","../src/infra/security-policy.ts","../src/sysserver/api/login/service/user-level-config.ts","../src/sysserver/api/login/service/route-defaults.ts"],"sourcesContent":["import type Idb from '../../../db/src/type';\r\nimport type { DbType } from '../../../db/db';\r\nimport {\r\n\tasRows,\r\n\tgetDataServer,\r\n\tnormalizeStoreOpt,\r\n\tqNum,\r\n\tqStr,\r\n\treadJsonBlob,\r\n\tresolveStoreConfig,\r\n\twriteJsonBlob,\r\n} from '../../../db/data-store';\r\n\r\nexport const USER_FIELDS = [\r\n\t'id',\r\n\t'us',\r\n\t'email',\r\n\t'passHash',\r\n\t'passSalt',\r\n\t'lv',\r\n\t'role',\r\n\t'status',\r\n\t'failCount',\r\n\t'lockedUntil',\r\n\t'mustChangePassword',\r\n\t'passwordChangedAt',\r\n\t'passHistory',\r\n\t'createdAt',\r\n\t'updatedAt',\r\n] as const;\r\n\r\nexport const SESSION_FIELDS = ['id', 'us', 'ip', 'createdAt', 'expiresAt', 'lastActivityAt', 'pri', 'pub'] as const;\r\n\r\nconst FILE_USERS = 'users.json';\r\nconst FILE_SESSIONS = 'sessions.json';\r\nconst TABLE_USERS = 'auth_users';\r\nconst TABLE_SESSIONS = 'auth_sessions';\r\nconst SCHEMA_PROBE_ID = '__schema_probe__';\r\n\r\nconst STORE_TYPE_LABELS: Record<string, string> = {\r\n\tfile: 'File',\r\n\tsqlite: 'SQLite',\r\n\tmysql: 'MySQL',\r\n};\r\n\r\nconst USER_TABLE_ENSURED = '_userStoreTablesEnsured';\r\nlet ensureUserTablesPromise: Promise<void> | null = null;\r\n\r\nexport interface UserStoreDbConfig {\r\n\ttype: DbType;\r\n\topt: Record<string, unknown>;\r\n}\r\n\r\nexport function getUserStoreConfig($asai: any): UserStoreDbConfig {\r\n\tconst resolved = resolveStoreConfig($asai, 'user');\r\n\tconst type = resolved.type;\r\n\treturn { type, opt: normalizeStoreOpt('user', type, resolved.opt) };\r\n}\r\n\r\n/** 解析后的用户存储元信息(供 API / 测试 / 运维展示) */\r\nexport function getUserStoreMeta($asai: any): Record<string, unknown> {\r\n\tconst { type, opt } = getUserStoreConfig($asai);\r\n\tconst meta: Record<string, unknown> = {\r\n\t\ttype,\r\n\t\tlabel: STORE_TYPE_LABELS[type] || type,\r\n\t\tmode: type === 'file' ? 'json-blob' : type,\r\n\t\tcreate: opt.create ?? 1,\r\n\t};\r\n\tif (type === 'file') {\r\n\t\tmeta.dir = opt.dir;\r\n\t\tmeta.ext = opt.ext || '.json';\r\n\t\tmeta.location = String(opt.dir || '');\r\n\t} else if (type === 'sqlite') {\r\n\t\tmeta.database = opt.database;\r\n\t\tmeta.location = String(opt.database || '');\r\n\t} else if (type === 'mysql') {\r\n\t\tmeta.host = opt.host;\r\n\t\tmeta.database = opt.database;\r\n\t\tmeta.location = `${opt.host || '127.0.0.1'}/${opt.database || 'asai'}`;\r\n\t}\r\n\treturn meta;\r\n}\r\n\r\nfunction isTableMissing(err: unknown): boolean {\r\n\tconst msg = String((err as { message?: string })?.message || err || '');\r\n\treturn msg.includes('no such table') || msg.includes(\"doesn't exist\");\r\n}\r\n\r\nfunction sqlLiteralForField(field: string, dbType: DbType, marker?: string): string {\r\n\tif (field === 'id' && marker) return qStr(marker, dbType);\r\n\tif (\r\n\t\t[\r\n\t\t\t'lv',\r\n\t\t\t'failCount',\r\n\t\t\t'mustChangePassword',\r\n\t\t\t'passwordChangedAt',\r\n\t\t\t'createdAt',\r\n\t\t\t'updatedAt',\r\n\t\t\t'lockedUntil',\r\n\t\t\t'expiresAt',\r\n\t\t\t'lastActivityAt',\r\n\t\t].includes(field)\r\n\t) {\r\n\t\treturn qNum(0);\r\n\t}\r\n\treturn qStr('', dbType);\r\n}\r\n\r\nasync function ensureSqlTable(\r\n\tdb: { sqlDb(sql: Idb): Promise<unknown> },\r\n\ttable: string,\r\n\tfields: readonly string[],\r\n\tdbType: DbType,\r\n\tmarkerField: 'id' | 'us',\r\n): Promise<void> {\r\n\ttry {\r\n\t\tawait db.sqlDb({ type: 'select', table, field: ['id'], limit: 1 });\r\n\t\treturn;\r\n\t} catch (err) {\r\n\t\tif (!isTableMissing(err)) throw err;\r\n\t}\r\n\tconst marker = SCHEMA_PROBE_ID;\r\n\tconst valueRow = fields.map((f) => sqlLiteralForField(f, dbType, f === 'id' ? marker : undefined));\r\n\tawait db.sqlDb({ type: 'insert', table, field: [...fields], value: [valueRow] });\r\n\tawait db.sqlDb({\r\n\t\ttype: 'delete',\r\n\t\ttable,\r\n\t\twhere: [[markerField, '=', qStr(marker, dbType)]],\r\n\t});\r\n}\r\n\r\n/** 确保 auth_users / auth_sessions 表存在(SQLite/MySQL 首次 SELECT 不会自动建表) */\r\nexport async function ensureUserStoreTables($asai: any): Promise<void> {\r\n\tif ($asai[USER_TABLE_ENSURED]) return;\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\t$asai[USER_TABLE_ENSURED] = true;\r\n\t\treturn;\r\n\t}\r\n\tif (!ensureUserTablesPromise) {\r\n\t\tensureUserTablesPromise = (async () => {\r\n\t\t\tconst db = getUserDataServer($asai);\r\n\t\t\tawait ensureSqlTable(db, TABLE_USERS, USER_FIELDS, cfg.type, 'id');\r\n\t\t\tawait ensureSqlTable(db, TABLE_SESSIONS, SESSION_FIELDS, cfg.type, 'id');\r\n\t\t\t$asai[USER_TABLE_ENSURED] = true;\r\n\t\t})();\r\n\t}\r\n\ttry {\r\n\t\tawait ensureUserTablesPromise;\r\n\t} catch (err) {\r\n\t\tensureUserTablesPromise = null;\r\n\t\tthrow err;\r\n\t}\r\n}\r\n\r\nasync function beforeUserStoreOp($asai: any): Promise<void> {\r\n\tif (getUserStoreConfig($asai).type !== 'file') {\r\n\t\tawait ensureUserStoreTables($asai);\r\n\t}\r\n}\r\n\r\nexport function getUserDataServer($asai: any): { sqlDb(sql: Idb): Promise<unknown> } {\r\n\treturn getDataServer($asai, 'user');\r\n}\r\n\r\nexport function isFileStore($asai: any): boolean {\r\n\treturn getUserStoreConfig($asai).type === 'file';\r\n}\r\n\r\nfunction rowToRecord<T extends Record<string, unknown>>(row: Record<string, unknown>, numericKeys: string[]): T {\r\n\tconst out: Record<string, unknown> = { ...row };\r\n\tfor (const key of numericKeys) {\r\n\t\tif (out[key] != null && out[key] !== '') out[key] = Number(out[key]);\r\n\t}\r\n\tif (out.lockedUntil === '' || out.lockedUntil == null) delete out.lockedUntil;\r\n\tif (out.email === '') delete out.email;\r\n\treturn out as T;\r\n}\r\n\r\nasync function readJsonArray(db: { sqlDb(sql: Idb): Promise<unknown> }, filename: string): Promise<Record<string, unknown>[]> {\r\n\treturn readJsonBlob(db, filename);\r\n}\r\n\r\nasync function writeJsonArray(\r\n\tdb: { sqlDb(sql: Idb): Promise<unknown> },\r\n\tfilename: string,\r\n\tdata: Record<string, unknown>[],\r\n): Promise<void> {\r\n\tawait writeJsonBlob(db, filename, data);\r\n}\r\n\r\nexport async function dbListUsers($asai: any): Promise<Record<string, unknown>[]> {\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\treturn readJsonArray(db, FILE_USERS);\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tconst res = await db.sqlDb({ type: 'select', table: TABLE_USERS, field: [...USER_FIELDS] });\r\n\treturn asRows(res).map((r) => rowToRecord(r, ['lv', 'failCount', 'lockedUntil', 'mustChangePassword', 'passwordChangedAt', 'createdAt', 'updatedAt']));\r\n}\r\n\r\nexport async function dbFindUser($asai: any, field: 'us' | 'id', value: string): Promise<Record<string, unknown> | null> {\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\tconst users = await readJsonArray(db, FILE_USERS);\r\n\t\treturn users.find((u) => u[field] === value) || null;\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tconst res = await db.sqlDb({\r\n\t\ttype: 'select',\r\n\t\ttable: TABLE_USERS,\r\n\t\tfield: [...USER_FIELDS],\r\n\t\twhere: [[field, '=', qStr(value, cfg.type)]],\r\n\t\tlimit: 1,\r\n\t});\r\n\tconst rows = asRows(res);\r\n\treturn rows.length ? rowToRecord(rows[0], ['lv', 'failCount', 'lockedUntil', 'mustChangePassword', 'passwordChangedAt', 'createdAt', 'updatedAt']) : null;\r\n}\r\n\r\nexport async function dbCreateUser($asai: any, user: Record<string, unknown>): Promise<Record<string, unknown>> {\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\tconst users = await readJsonArray(db, FILE_USERS);\r\n\t\tusers.push(user);\r\n\t\tawait writeJsonArray(db, FILE_USERS, users);\r\n\t\treturn user;\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tawait db.sqlDb({\r\n\t\ttype: 'insert',\r\n\t\ttable: TABLE_USERS,\r\n\t\tfield: [...USER_FIELDS],\r\n\t\tvalue: [\r\n\t\t\tUSER_FIELDS.map((f) => {\r\n\t\t\t\tconst v = user[f];\r\n\t\t\t\tif (typeof v === 'number') return qNum(v);\r\n\t\t\t\treturn qStr(v == null ? '' : String(v), cfg.type);\r\n\t\t\t}),\r\n\t\t],\r\n\t});\r\n\treturn user;\r\n}\r\n\r\nexport async function dbUpdateUser($asai: any, us: string, patch: Record<string, unknown>): Promise<Record<string, unknown> | null> {\r\n\tconst existing = await dbFindUser($asai, 'us', us);\r\n\tif (!existing) return null;\r\n\tconst merged = { ...existing, ...patch, updatedAt: Date.now() };\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\tconst users = await readJsonArray(db, FILE_USERS);\r\n\t\tconst idx = users.findIndex((u) => u.us === us);\r\n\t\tif (idx < 0) return null;\r\n\t\tusers[idx] = merged;\r\n\t\tawait writeJsonArray(db, FILE_USERS, users);\r\n\t\treturn merged;\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tconst sets: Idb['set'] = [];\r\n\tfor (const [key, val] of Object.entries(patch)) {\r\n\t\tif (val === undefined) continue;\r\n\t\tif (typeof val === 'number') sets!.push([key, val]);\r\n\t\telse sets!.push([key, val == null ? '' : String(val)]);\r\n\t}\r\n\tsets!.push(['updatedAt', merged.updatedAt as number]);\r\n\tawait db.sqlDb({\r\n\t\ttype: 'update',\r\n\t\ttable: TABLE_USERS,\r\n\t\tset: sets,\r\n\t\twhere: [['us', '=', qStr(us, cfg.type)]],\r\n\t});\r\n\treturn merged;\r\n}\r\n\r\nexport async function dbListSessions($asai: any): Promise<Record<string, unknown>[]> {\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\treturn readJsonArray(db, FILE_SESSIONS);\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tconst res = await db.sqlDb({ type: 'select', table: TABLE_SESSIONS, field: [...SESSION_FIELDS] });\r\n\treturn asRows(res).map((r) => rowToRecord(r, ['createdAt', 'expiresAt', 'lastActivityAt']));\r\n}\r\n\r\nexport async function dbWriteSessions($asai: any, sessions: Record<string, unknown>[]): Promise<void> {\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\tawait writeJsonArray(db, FILE_SESSIONS, sessions);\r\n\t\treturn;\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tconst existing = await dbListSessions($asai);\r\n\tfor (const s of existing) {\r\n\t\tawait db.sqlDb({\r\n\t\t\ttype: 'delete',\r\n\t\t\ttable: TABLE_SESSIONS,\r\n\t\t\twhere: [['id', '=', qStr(String(s.id), cfg.type)]],\r\n\t\t});\r\n\t}\r\n\tfor (const s of sessions) {\r\n\t\tawait db.sqlDb({\r\n\t\t\ttype: 'insert',\r\n\t\t\ttable: TABLE_SESSIONS,\r\n\t\t\tfield: [...SESSION_FIELDS],\r\n\t\t\tvalue: [\r\n\t\t\t\tSESSION_FIELDS.map((f) => {\r\n\t\t\t\t\tconst v = s[f];\r\n\t\t\t\t\tif (typeof v === 'number') return qNum(v);\r\n\t\t\t\t\treturn qStr(v == null ? '' : String(v), cfg.type);\r\n\t\t\t\t}),\r\n\t\t\t],\r\n\t\t});\r\n\t}\r\n}\r\n\r\nexport async function dbAddSession($asai: any, session: Record<string, unknown>): Promise<void> {\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\tconst sessions = await readJsonArray(db, FILE_SESSIONS);\r\n\t\tsessions.push(session);\r\n\t\tawait writeJsonArray(db, FILE_SESSIONS, sessions);\r\n\t\treturn;\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tawait db.sqlDb({\r\n\t\ttype: 'insert',\r\n\t\ttable: TABLE_SESSIONS,\r\n\t\tfield: [...SESSION_FIELDS],\r\n\t\tvalue: [\r\n\t\t\tSESSION_FIELDS.map((f) => {\r\n\t\t\t\tconst v = session[f];\r\n\t\t\t\tif (typeof v === 'number') return qNum(v);\r\n\t\t\t\treturn qStr(v == null ? '' : String(v), cfg.type);\r\n\t\t\t}),\r\n\t\t],\r\n\t});\r\n}\r\n\r\nexport async function dbRemoveSession($asai: any, sessionId: string): Promise<void> {\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\tconst sessions = await readJsonArray(db, FILE_SESSIONS);\r\n\t\tawait writeJsonArray(\r\n\t\t\tdb,\r\n\t\t\tFILE_SESSIONS,\r\n\t\t\tsessions.filter((s) => s.id !== sessionId),\r\n\t\t);\r\n\t\treturn;\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tawait db.sqlDb({\r\n\t\ttype: 'delete',\r\n\t\ttable: TABLE_SESSIONS,\r\n\t\twhere: [['id', '=', qStr(sessionId, cfg.type)]],\r\n\t});\r\n}\r\n\r\nexport async function dbRemoveSessionsByUser($asai: any, us: string): Promise<void> {\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tif (cfg.type === 'file') {\r\n\t\tconst sessions = await readJsonArray(db, FILE_SESSIONS);\r\n\t\tawait writeJsonArray(\r\n\t\t\tdb,\r\n\t\t\tFILE_SESSIONS,\r\n\t\t\tsessions.filter((s) => s.us !== us),\r\n\t\t);\r\n\t\treturn;\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tawait db.sqlDb({\r\n\t\ttype: 'delete',\r\n\t\ttable: TABLE_SESSIONS,\r\n\t\twhere: [['us', '=', qStr(us, cfg.type)]],\r\n\t});\r\n}\r\n\r\nexport async function dbDeleteUser($asai: any, us: string): Promise<boolean> {\r\n\tconst existing = await dbFindUser($asai, 'us', us);\r\n\tif (!existing) return false;\r\n\tconst db = getUserDataServer($asai);\r\n\tconst cfg = getUserStoreConfig($asai);\r\n\tawait dbRemoveSessionsByUser($asai, us);\r\n\tif (cfg.type === 'file') {\r\n\t\tconst users = await readJsonArray(db, FILE_USERS);\r\n\t\tconst next = users.filter((u) => u.us !== us);\r\n\t\tif (next.length === users.length) return false;\r\n\t\tawait writeJsonArray(db, FILE_USERS, next);\r\n\t\treturn true;\r\n\t}\r\n\tawait beforeUserStoreOp($asai);\r\n\tawait db.sqlDb({\r\n\t\ttype: 'delete',\r\n\t\ttable: TABLE_USERS,\r\n\t\twhere: [['us', '=', qStr(us, cfg.type)]],\r\n\t});\r\n\treturn true;\r\n}\r\n","/**\r\n * IEC 62443-4-2 — 可配置安全策略(CR 1.7 / 1.12 / 2.1 / 2.5)\r\n */\r\nimport { createHash } from 'node:crypto';\r\nimport { toPublicUserLevels } from '../sysserver/api/login/service/user-level-config';\r\nimport { getUserStoreMeta } from '../sysserver/api/login/store/user-db';\r\n\r\nexport interface PasswordPolicy {\r\n\tminLength: number;\r\n\trequireUpper: boolean;\r\n\trequireLower: boolean;\r\n\trequireDigit: boolean;\r\n\trequireSpecial: boolean;\r\n\tmaxAgeDays: number;\r\n\thistoryCount: number;\r\n}\r\n\r\nexport interface RoleDef {\r\n\tname: string;\r\n\tminLv: number;\r\n\tpermissions: string[];\r\n}\r\n\r\nconst DEFAULT_POLICY: PasswordPolicy = {\r\n\tminLength: 8,\r\n\trequireUpper: false,\r\n\trequireLower: true,\r\n\trequireDigit: true,\r\n\trequireSpecial: false,\r\n\tmaxAgeDays: 0,\r\n\thistoryCount: 0,\r\n};\r\n\r\nconst DEFAULT_ROLES: RoleDef[] = [\r\n\t{ name: 'viewer', minLv: 0, permissions: ['read'] },\r\n\t{ name: 'operator', minLv: 3, permissions: ['read', 'operate'] },\r\n\t{ name: 'engineer', minLv: 5, permissions: ['read', 'operate', 'audit'] },\r\n\t{ name: 'admin', minLv: 7, permissions: ['read', 'operate', 'audit', 'admin'] },\r\n\t{ name: 'developer', minLv: 9, permissions: ['read', 'operate', 'audit', 'admin', 'develop'] },\r\n\t{ name: 'superadmin', minLv: 99, permissions: ['read', 'operate', 'audit', 'admin', 'develop', 'super'] },\r\n];\r\n\r\nexport function getSecurityBlock($asai: any): Record<string, unknown> {\r\n\treturn ($asai?.hostconfig?.security as Record<string, unknown>) || {};\r\n}\r\n\r\nexport function getLoginBanner($asai: any): { enabled: boolean; text: string } {\r\n\tconst sec = getSecurityBlock($asai);\r\n\tconst banner = (sec.banner as Record<string, unknown>) || {};\r\n\treturn {\r\n\t\tenabled: banner.enabled === 1 || banner.enabled === true,\r\n\t\ttext: String(banner.text || ''),\r\n\t};\r\n}\r\n\r\nexport function getPasswordPolicy($asai: any): PasswordPolicy {\r\n\tconst sec = getSecurityBlock($asai);\r\n\tconst p = (sec.passwordPolicy as Record<string, unknown>) || {};\r\n\tconst minFromLegacy = Number(sec.passwordMinLength ?? DEFAULT_POLICY.minLength);\r\n\treturn {\r\n\t\tminLength: Number(p.minLength ?? minFromLegacy) || DEFAULT_POLICY.minLength,\r\n\t\trequireUpper: p.requireUpper === 1 || p.requireUpper === true,\r\n\t\trequireLower: p.requireLower !== 0 && p.requireLower !== false,\r\n\t\trequireDigit: p.requireDigit !== 0 && p.requireDigit !== false,\r\n\t\trequireSpecial: p.requireSpecial === 1 || p.requireSpecial === true,\r\n\t\tmaxAgeDays: Number(p.maxAgeDays ?? 0) || 0,\r\n\t\thistoryCount: Number(p.historyCount ?? 0) || 0,\r\n\t};\r\n}\r\n\r\nexport function getSessionIdleMinutes($asai: any): number {\r\n\tconst sec = getSecurityBlock($asai);\r\n\tconst v = sec.sessionIdleMinutes;\r\n\treturn v == null || v === 0 ? 0 : Number(v);\r\n}\r\n\r\nexport function getRoleDefinitions($asai: any): RoleDef[] {\r\n\tconst sec = getSecurityBlock($asai);\r\n\tconst raw = sec.roles;\r\n\tif (!Array.isArray(raw) || !raw.length) return DEFAULT_ROLES;\r\n\treturn raw.map((r: Record<string, unknown>) => ({\r\n\t\tname: String(r.name || 'role'),\r\n\t\tminLv: Number(r.minLv ?? 0),\r\n\t\tpermissions: Array.isArray(r.permissions) ? r.permissions.map(String) : [],\r\n\t}));\r\n}\r\n\r\nexport function resolveRoleForLevel($asai: any, lv: number): RoleDef {\r\n\tconst roles = getRoleDefinitions($asai);\r\n\tlet best = roles[0];\r\n\tfor (const r of roles) {\r\n\t\tif (lv >= r.minLv && r.minLv >= (best?.minLv ?? -1)) best = r;\r\n\t}\r\n\treturn best || DEFAULT_ROLES[0];\r\n}\r\n\r\nexport function hashPasswordForHistory(password: string): string {\r\n\treturn createHash('sha256').update(password).digest('hex');\r\n}\r\n\r\nexport function validatePasswordWithPolicy(password: string, policy: PasswordPolicy): string | null {\r\n\tif (!password || typeof password !== 'string') return 'PASSWORD_REQUIRED';\r\n\tif (password.length < policy.minLength) return 'PASSWORD_TOO_SHORT';\r\n\tif (policy.requireLower && !/[a-z]/.test(password)) return 'PASSWORD_WEAK';\r\n\tif (policy.requireUpper && !/[A-Z]/.test(password)) return 'PASSWORD_WEAK';\r\n\tif (policy.requireDigit && !/[0-9]/.test(password)) return 'PASSWORD_WEAK';\r\n\tif (policy.requireSpecial && !/[^a-zA-Z0-9]/.test(password)) return 'PASSWORD_WEAK';\r\n\treturn null;\r\n}\r\n\r\nexport function parsePassHistory(raw: unknown): string[] {\r\n\tif (!raw) return [];\r\n\tif (Array.isArray(raw)) return raw.map(String);\r\n\tif (typeof raw === 'string' && raw.startsWith('[')) {\r\n\t\ttry {\r\n\t\t\tconst arr = JSON.parse(raw);\r\n\t\t\treturn Array.isArray(arr) ? arr.map(String) : [];\r\n\t\t} catch {\r\n\t\t\treturn [];\r\n\t\t}\r\n\t}\r\n\treturn [];\r\n}\r\n\r\nexport function isPasswordInHistory(password: string, history: string[]): boolean {\r\n\tconst h = hashPasswordForHistory(password);\r\n\treturn history.includes(h);\r\n}\r\n\r\nexport function isPasswordExpired(passwordChangedAt: number | undefined, maxAgeDays: number): boolean {\r\n\tif (!maxAgeDays || maxAgeDays <= 0) return false;\r\n\tif (!passwordChangedAt) return false;\r\n\treturn Date.now() - passwordChangedAt > maxAgeDays * 86400000;\r\n}\r\n\r\nexport function getPublicSecurityConfig($asai: any, lang = 'zh-CN') {\r\n\tconst policy = getPasswordPolicy($asai);\r\n\tconst banner = getLoginBanner($asai);\r\n\treturn {\r\n\t\tbanner,\r\n\t\tpasswordPolicy: {\r\n\t\t\tminLength: policy.minLength,\r\n\t\t\trequireUpper: policy.requireUpper,\r\n\t\t\trequireLower: policy.requireLower,\r\n\t\t\trequireDigit: policy.requireDigit,\r\n\t\t\trequireSpecial: policy.requireSpecial,\r\n\t\t},\r\n\t\tsessionIdleMinutes: getSessionIdleMinutes($asai),\r\n\t\troles: getRoleDefinitions($asai).map((r) => ({ name: r.name, minLv: r.minLv })),\r\n\t\tuserLevels: toPublicUserLevels($asai, lang),\r\n\t\tallowRegister: getSecurityBlock($asai).allowRegister !== 0,\r\n\t\tuserStore: getUserStoreMeta($asai),\r\n\t};\r\n}\r\n","/**\r\n * 用户等级与名称 — 管理员可自定义(持久化至 user store _meta/user-levels.json)\r\n */\r\nimport fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport { validatePasswordWithPolicy, getPasswordPolicy } from '../../../../infra/security-policy';\r\nimport { getUserStoreConfig } from '../store/user-db';\r\nimport {\r\n\tloadRouteCatalogAsRights,\r\n\tmergeRouteRights,\r\n\tnormalizeRouteRight,\r\n\troutesFromLegacyRights,\r\n\ttype RouteRight,\r\n} from './route-defaults';\r\n\r\nexport type { RouteRight };\r\n\r\nexport interface UserLevelDef {\r\n\tlv: number;\r\n\tkey: string;\r\n\tname: string;\r\n\tnameEn?: string;\r\n}\r\n\r\nexport interface PermissionThresholds {\r\n\tlogRead: number;\r\n\tadminRead: number;\r\n\tadminWrite: number;\r\n}\r\n\r\nexport interface UserLevelConfig {\r\n\tlevels: UserLevelDef[];\r\n\tthresholds: PermissionThresholds;\r\n\trouteRights?: RouteRight[];\r\n\t/** 管理员重置用户密码时的默认密码(持久化,不含明文回传至公开 API) */\r\n\tdefaultResetPassword?: string;\r\n\tupdatedAt?: number;\r\n\tupdatedBy?: string;\r\n}\r\n\r\nexport const DEFAULT_RESET_PASSWORD = 'Asai@1234';\r\n\r\nexport const DEFAULT_LEVELS: UserLevelDef[] = [\r\n\t{ lv: 0, key: 'guest', name: '普通用户', nameEn: 'Guest' },\r\n\t{ lv: 3, key: 'operator', name: '操作员', nameEn: 'Operator' },\r\n\t{ lv: 5, key: 'engineer', name: '工程师', nameEn: 'Engineer' },\r\n\t{ lv: 7, key: 'admin', name: '管理员', nameEn: 'Administrator' },\r\n\t{ lv: 9, key: 'developer', name: '开发者', nameEn: 'Developer' },\r\n\t{ lv: 99, key: 'superadmin', name: '超级管理员', nameEn: 'Super Administrator' },\r\n];\r\n\r\nexport const DEFAULT_THRESHOLDS: PermissionThresholds = {\r\n\tlogRead: 3,\r\n\tadminRead: 7,\r\n\tadminWrite: 9,\r\n};\r\n\r\nconst META_FILE = '_meta/user-levels.json';\r\n\r\nfunction configPath($asai: any): string {\r\n\tconst dir = String(getUserStoreConfig($asai).opt?.dir || 'webdata/webdb/users/');\r\n\treturn path.resolve(dir, META_FILE);\r\n}\r\n\r\nfunction fromHostLevels($asai: any): UserLevelDef[] | null {\r\n\tconst raw = $asai?.hostconfig?.security?.userLevels;\r\n\tif (!Array.isArray(raw) || !raw.length) return null;\r\n\tconst levels: UserLevelDef[] = [];\r\n\tfor (const item of raw) {\r\n\t\tif (!item || typeof item !== 'object') continue;\r\n\t\tconst lv = Number((item as any).lv);\r\n\t\tconst key = String((item as any).key || '').trim();\r\n\t\tconst name = String((item as any).name || (item as any).label || '').trim();\r\n\t\tif (!Number.isFinite(lv) || !key || !name) continue;\r\n\t\tlevels.push({\r\n\t\t\tlv,\r\n\t\t\tkey,\r\n\t\t\tname,\r\n\t\t\tnameEn: (item as any).nameEn ? String((item as any).nameEn) : undefined,\r\n\t\t});\r\n\t}\r\n\treturn levels.length ? levels.sort((a, b) => a.lv - b.lv) : null;\r\n}\r\n\r\nfunction fromHostThresholds($asai: any): PermissionThresholds {\r\n\tconst t = $asai?.hostconfig?.security?.permissionThresholds;\r\n\tif (!t || typeof t !== 'object') return { ...DEFAULT_THRESHOLDS };\r\n\treturn {\r\n\t\tlogRead: Number(t.logRead ?? DEFAULT_THRESHOLDS.logRead),\r\n\t\tadminRead: Number(t.adminRead ?? DEFAULT_THRESHOLDS.adminRead),\r\n\t\tadminWrite: Number(t.adminWrite ?? DEFAULT_THRESHOLDS.adminWrite),\r\n\t};\r\n}\r\n\r\nexport function getDefaultRouteRights($asai: any): RouteRight[] {\r\n\tlet base = loadRouteCatalogAsRights($asai);\r\n\tif (!base.length) {\r\n\t\tconst legacy = $asai?.hostconfig?.security?.routerRights;\r\n\t\tif (legacy && typeof legacy === 'object') {\r\n\t\t\tbase = routesFromLegacyRights(legacy);\r\n\t\t}\r\n\t}\r\n\tconst fromHost = $asai?.hostconfig?.security?.routeRights;\r\n\tif (Array.isArray(fromHost) && fromHost.length) {\r\n\t\tconst parsed: RouteRight[] = [];\r\n\t\tfor (const item of fromHost) {\r\n\t\t\tconst row = normalizeRouteRight(item);\r\n\t\t\tif (row) parsed.push(row);\r\n\t\t}\r\n\t\tif (parsed.length) base = mergeRouteRights(base, parsed);\r\n\t}\r\n\tconst legacy = $asai?.hostconfig?.security?.routerRights;\r\n\tif (legacy && typeof legacy === 'object' && base.length) {\r\n\t\tbase = mergeRouteRights(base, routesFromLegacyRights(legacy));\r\n\t}\r\n\treturn base;\r\n}\r\n\r\nexport function getDefaultUserLevelConfig($asai: any): UserLevelConfig {\r\n\treturn {\r\n\t\tlevels: fromHostLevels($asai) || DEFAULT_LEVELS.map((l) => ({ ...l })),\r\n\t\tthresholds: fromHostThresholds($asai),\r\n\t\trouteRights: getDefaultRouteRights($asai),\r\n\t};\r\n}\r\n\r\nfunction normalizeLevel(item: unknown): UserLevelDef | null {\r\n\tif (!item || typeof item !== 'object') return null;\r\n\tconst lv = Number((item as any).lv);\r\n\tconst key = String((item as any).key || '').trim().slice(0, 32);\r\n\tconst name = String((item as any).name || '').trim().slice(0, 64);\r\n\tif (!Number.isFinite(lv) || lv < 0 || !key || !name) return null;\r\n\treturn {\r\n\t\tlv: Math.floor(lv),\r\n\t\tkey,\r\n\t\tname,\r\n\t\tnameEn: (item as any).nameEn ? String((item as any).nameEn).trim().slice(0, 64) : undefined,\r\n\t};\r\n}\r\n\r\nexport function normalizeUserLevelConfig(input: Partial<UserLevelConfig>, $asai: any): UserLevelConfig {\r\n\tconst base = getDefaultUserLevelConfig($asai);\r\n\tconst synced = getUserLevelConfigSync($asai);\r\n\tconst levelBase = synced.levels?.length ? synced : base;\r\n\tconst levels: UserLevelDef[] = [];\r\n\tconst seenLv = new Set<number>();\r\n\tconst seenKey = new Set<string>();\r\n\tif (Array.isArray(input.levels) && input.levels.length) {\r\n\t\tfor (const raw of input.levels) {\r\n\t\t\tconst row = normalizeLevel(raw);\r\n\t\t\tif (!row || seenLv.has(row.lv) || seenKey.has(row.key)) continue;\r\n\t\t\tseenLv.add(row.lv);\r\n\t\t\tseenKey.add(row.key);\r\n\t\t\tlevels.push(row);\r\n\t\t}\r\n\t} else {\r\n\t\tlevels.push(...levelBase.levels.map((l) => ({ ...l })));\r\n\t}\r\n\tif (!levels.some((l) => l.lv === 0)) {\r\n\t\tlevels.unshift({ lv: 0, key: 'guest', name: '普通用户', nameEn: 'Guest' });\r\n\t}\r\n\tlevels.sort((a, b) => a.lv - b.lv);\r\n\r\n\tconst th = input.thresholds || {};\r\n\tconst thresholds: PermissionThresholds = {\r\n\t\tlogRead: Number.isFinite(Number(th.logRead)) ? Number(th.logRead) : base.thresholds.logRead,\r\n\t\tadminRead: Number.isFinite(Number(th.adminRead)) ? Number(th.adminRead) : base.thresholds.adminRead,\r\n\t\tadminWrite: Number.isFinite(Number(th.adminWrite)) ? Number(th.adminWrite) : base.thresholds.adminWrite,\r\n\t};\r\n\r\n\tconst syncedPwd = synced.defaultResetPassword;\r\n\tconst inputPwd = typeof input.defaultResetPassword === 'string' ? input.defaultResetPassword.trim() : '';\r\n\tconst defaultResetPassword = inputPwd || syncedPwd || DEFAULT_RESET_PASSWORD;\r\n\r\n\treturn {\r\n\t\tlevels,\r\n\t\tthresholds,\r\n\t\trouteRights: normalizeRouteRightsList(input.routeRights, base.routeRights || getDefaultRouteRights($asai)),\r\n\t\tdefaultResetPassword,\r\n\t\tupdatedAt: input.updatedAt,\r\n\t\tupdatedBy: input.updatedBy,\r\n\t};\r\n}\r\n\r\nexport function getDefaultResetPassword($asai: any): string {\r\n\tconst cfg = getUserLevelConfigSync($asai);\r\n\tconst pwd = String(cfg.defaultResetPassword || '').trim();\r\n\treturn pwd || DEFAULT_RESET_PASSWORD;\r\n}\r\n\r\nfunction normalizeRouteRightsList(raw: unknown, fallback: RouteRight[]): RouteRight[] {\r\n\tif (!Array.isArray(raw) || !raw.length) return fallback.map((r) => ({ ...r }));\r\n\tconst parsed: RouteRight[] = [];\r\n\tconst seen = new Set<string>();\r\n\tfor (const item of raw) {\r\n\t\tconst row = normalizeRouteRight(item);\r\n\t\tif (!row || seen.has(row.route)) continue;\r\n\t\tseen.add(row.route);\r\n\t\tparsed.push(row);\r\n\t}\r\n\treturn parsed.length ? parsed : fallback.map((r) => ({ ...r }));\r\n}\r\n\r\n/** 检测 UTF-8 被误解析后产生的乱码(无 CJK、非纯 ASCII) */\r\nfunction looksLikeMojibake(text: string | undefined): boolean {\r\n\tif (!text) return false;\r\n\tif (/[\\u4e00-\\u9fff]/.test(text)) return false;\r\n\tif (/^[\\x20-\\x7e]+$/.test(text)) return false;\r\n\treturn true;\r\n}\r\n\r\n/** 从默认等级 / route-catalog 恢复被损坏的中文名称 */\r\nfunction repairUserLevelConfig(cfg: UserLevelConfig, $asai: any): { cfg: UserLevelConfig; repaired: boolean } {\r\n\tlet repaired = false;\r\n\tconst levels = cfg.levels.map((l) => {\r\n\t\tconst def = DEFAULT_LEVELS.find((d) => d.lv === l.lv || d.key === l.key);\r\n\t\tif (def && looksLikeMojibake(l.name)) {\r\n\t\t\trepaired = true;\r\n\t\t\treturn { ...l, name: def.name, nameEn: l.nameEn || def.nameEn };\r\n\t\t}\r\n\t\treturn l;\r\n\t});\r\n\tconst catalogMap = new Map(loadRouteCatalogAsRights($asai, true).map((r) => [r.route, r]));\r\n\tconst routeRights = (cfg.routeRights || []).map((r) => {\r\n\t\tconst cat = catalogMap.get(r.route);\r\n\t\tif (cat?.title && looksLikeMojibake(r.title)) {\r\n\t\t\trepaired = true;\r\n\t\t\treturn { ...r, title: cat.title };\r\n\t\t}\r\n\t\treturn r;\r\n\t});\r\n\tif (!repaired) return { cfg, repaired: false };\r\n\treturn { cfg: { ...cfg, levels, routeRights }, repaired: true };\r\n}\r\n\r\nexport function validateUserLevelConfig(cfg: UserLevelConfig): string | null {\r\n\tif (!cfg.levels.length) return 'LEVELS_EMPTY';\r\n\tconst keys = new Set<string>();\r\n\tconst lvs = new Set<number>();\r\n\tfor (const l of cfg.levels) {\r\n\t\tif (keys.has(l.key) || lvs.has(l.lv)) return 'LEVELS_DUPLICATE';\r\n\t\tkeys.add(l.key);\r\n\t\tlvs.add(l.lv);\r\n\t}\r\n\tif (!lvs.has(0)) return 'LEVELS_GUEST_REQUIRED';\r\n\tconst { thresholds } = cfg;\r\n\tif (thresholds.logRead > thresholds.adminRead) return 'THRESHOLD_ORDER';\r\n\tif (thresholds.adminRead > thresholds.adminWrite) return 'THRESHOLD_ORDER';\r\n\tfor (const r of cfg.routeRights || []) {\r\n\t\tif (!r.route) return 'ROUTE_INVALID';\r\n\t\tif (!Number.isFinite(r.minLv) || r.minLv < 0) return 'ROUTE_MINLV_INVALID';\r\n\t}\r\n\treturn null;\r\n}\r\n\r\nexport function getUserLevelConfigSync($asai: any): UserLevelConfig {\r\n\treturn ($asai._userLevelConfig as UserLevelConfig) || getDefaultUserLevelConfig($asai);\r\n}\r\n\r\nexport async function loadUserLevelConfig($asai: any): Promise<UserLevelConfig> {\r\n\tconst file = configPath($asai);\r\n\ttry {\r\n\t\tconst text = await fs.readFile(file, 'utf8');\r\n\t\tconst parsed = JSON.parse(text) as Partial<UserLevelConfig>;\r\n\t\tlet cfg = normalizeUserLevelConfig(parsed, $asai);\r\n\t\tconst err = validateUserLevelConfig(cfg);\r\n\t\tif (err) {\r\n\t\t\t$asai._userLevelConfig = getDefaultUserLevelConfig($asai);\r\n\t\t} else {\r\n\t\t\tconst { cfg: fixed, repaired } = repairUserLevelConfig(cfg, $asai);\r\n\t\t\tcfg = fixed;\r\n\t\t\t$asai._userLevelConfig = cfg;\r\n\t\t\tif (repaired) {\r\n\t\t\t\tawait fs.mkdir(path.dirname(file), { recursive: true });\r\n\t\t\t\tawait fs.writeFile(file, JSON.stringify(cfg, null, 2), 'utf8');\r\n\t\t\t}\r\n\t\t}\r\n\t} catch {\r\n\t\t$asai._userLevelConfig = getDefaultUserLevelConfig($asai);\r\n\t}\r\n\treturn $asai._userLevelConfig;\r\n}\r\n\r\nexport async function saveUserLevelConfig($asai: any, input: Partial<UserLevelConfig>, updatedBy?: string): Promise<UserLevelConfig> {\r\n\tconst cfg = normalizeUserLevelConfig(input, $asai);\r\n\tconst err = validateUserLevelConfig(cfg);\r\n\tif (err) throw new Error(err);\r\n\tconst pwd = String(cfg.defaultResetPassword || '').trim() || DEFAULT_RESET_PASSWORD;\r\n\tconst policyErr = validatePasswordWithPolicy(pwd, getPasswordPolicy($asai));\r\n\tif (policyErr) throw new Error(policyErr);\r\n\tcfg.defaultResetPassword = pwd;\r\n\tcfg.updatedAt = Date.now();\r\n\tcfg.updatedBy = updatedBy;\r\n\tconst file = configPath($asai);\r\n\tawait fs.mkdir(path.dirname(file), { recursive: true });\r\n\tawait fs.writeFile(file, JSON.stringify(cfg, null, 2), 'utf8');\r\n\t$asai._userLevelConfig = cfg;\r\n\treturn cfg;\r\n}\r\n\r\nexport function getPermissionThreshold($asai: any, key: keyof PermissionThresholds): number {\r\n\treturn getUserLevelConfigSync($asai).thresholds[key];\r\n}\r\n\r\nexport function getLvSteps($asai: any): number[] {\r\n\treturn getUserLevelConfigSync($asai).levels.map((l) => l.lv);\r\n}\r\n\r\nexport function getLevelDef($asai: any, lv: number): UserLevelDef | undefined {\r\n\treturn getUserLevelConfigSync($asai).levels.find((l) => l.lv === lv);\r\n}\r\n\r\nexport function isValidUserLv($asai: any, lv: number): boolean {\r\n\treturn getUserLevelConfigSync($asai).levels.some((l) => l.lv === lv);\r\n}\r\n\r\nexport function getLevelLabel($asai: any, lv: number, lang = 'zh-CN'): string {\r\n\tconst def = getLevelDef($asai, lv);\r\n\tif (!def) return `lv${lv}`;\r\n\tif (lang.startsWith('en') && def.nameEn) return def.nameEn;\r\n\treturn def.name;\r\n}\r\n\r\nexport function toPublicUserLevels($asai: any, lang = 'zh-CN') {\r\n\tconst cfg = getUserLevelConfigSync($asai);\r\n\treturn {\r\n\t\tlevels: cfg.levels.map((l) => ({\r\n\t\t\tlv: l.lv,\r\n\t\t\tkey: l.key,\r\n\t\t\tname: getLevelLabel($asai, l.lv, lang),\r\n\t\t\tnameZh: l.name,\r\n\t\t\tnameEn: l.nameEn || l.name,\r\n\t\t})),\r\n\t\tthresholds: { ...cfg.thresholds },\r\n\t\trouteRights: (cfg.routeRights || getDefaultRouteRights($asai)).map((r) => ({ ...r })),\r\n\t};\r\n}\r\n\r\nexport function getRouteRights($asai: any): RouteRight[] {\r\n\treturn getUserLevelConfigSync($asai).routeRights || getDefaultRouteRights($asai);\r\n}\r\n\r\nexport function findRouteRight($asai: any, hash: string): RouteRight | null {\r\n\tconst routeRights = getRouteRights($asai);\r\n\tconst h = String(hash || '').replace(/^#?\\/?/, '').split('?')[0];\r\n\tif (!h || h === '/') {\r\n\t\treturn routeRights.find((r) => r.route === 'testkdd/testindex') || { route: '/', minLv: 0, public: true };\r\n\t}\r\n\tlet best: RouteRight | null = null;\r\n\tlet bestLen = -1;\r\n\tfor (const r of routeRights) {\r\n\t\tif (h === r.route || h.startsWith(r.route + '/') || h.includes(r.route)) {\r\n\t\t\tif (r.route.length > bestLen) {\r\n\t\t\t\tbest = r;\r\n\t\t\t\tbestLen = r.route.length;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn best;\r\n}\r\n\r\nexport function canAccessRoute($asai: any, hash: string, userLv: number): boolean {\r\n\tconst entry = findRouteRight($asai, hash);\r\n\tif (!entry) {\r\n\t\tconst fallback = Number($asai?.hostconfig?.security?.minUserLevel ?? 0);\r\n\t\treturn userLv >= fallback;\r\n\t}\r\n\tif (entry.public || entry.minLv === 0) return true;\r\n\treturn userLv >= entry.minLv;\r\n}\r\n","import fs from 'node:fs';\r\nimport path from 'node:path';\r\n\r\n/** 路由权限条目(与 route-catalog.json 字段对齐) */\r\nexport interface RouteRight {\r\n\troute: string;\r\n\ttitle?: string;\r\n\tminLv: number;\r\n\tpublic?: boolean;\r\n}\r\n\r\nconst DEFAULT_CATALOG_REL = '../webclient/public/webmodel/sys/testkdd/route-catalog.json';\r\n\r\n/** configrouter rights.lvN → 默认 minLv 映射 */\r\nexport const LEGACY_RIGHTS_MIN_LV: Record<string, number> = {\r\n\tlv0: 0,\r\n\tlv1: 3,\r\n\tlv2: 7,\r\n\tlv3: 9,\r\n};\r\n\r\nlet cachedCatalog: { file: string; rights: RouteRight[] } | null = null;\r\n\r\nexport function resolveRouteCatalogPath($asai: any): string {\r\n\tconst custom = $asai?.hostconfig?.security?.routeCatalogPath;\r\n\tconst root = $asai?.serverRoot || process.cwd();\r\n\tif (custom) {\r\n\t\treturn path.isAbsolute(custom) ? custom : path.resolve(root, String(custom));\r\n\t}\r\n\treturn path.resolve(root, DEFAULT_CATALOG_REL);\r\n}\r\n\r\nexport function catalogItemsToRouteRights(items: unknown[]): RouteRight[] {\r\n\tconst out: RouteRight[] = [];\r\n\tfor (const item of items) {\r\n\t\tconst row = normalizeRouteRight(item);\r\n\t\tif (row) out.push(row);\r\n\t}\r\n\treturn out;\r\n}\r\n\r\n/** 从 route-catalog.json 加载默认路由权限(无硬编码清单) */\r\nexport function loadRouteCatalogAsRights($asai: any, refresh = false): RouteRight[] {\r\n\tconst file = resolveRouteCatalogPath($asai);\r\n\tif (!refresh && cachedCatalog?.file === file) {\r\n\t\treturn cachedCatalog.rights.map((r) => ({ ...r }));\r\n\t}\r\n\ttry {\r\n\t\tconst text = fs.readFileSync(file, 'utf8');\r\n\t\tconst parsed = JSON.parse(text) as { items?: unknown[] };\r\n\t\tconst rights = catalogItemsToRouteRights(Array.isArray(parsed?.items) ? parsed.items : []);\r\n\t\tcachedCatalog = { file, rights };\r\n\t\treturn rights.map((r) => ({ ...r }));\r\n\t} catch {\r\n\t\tcachedCatalog = { file, rights: [] };\r\n\t\treturn [];\r\n\t}\r\n}\r\n\r\nexport function normalizeRouteRight(item: unknown): RouteRight | null {\r\n\tif (!item || typeof item !== 'object') return null;\r\n\tconst route = String((item as any).route || '').trim().replace(/^\\/+|\\/+$/g, '');\r\n\tif (!route) return null;\r\n\tconst minLv = Number((item as any).minLv ?? 0);\r\n\treturn {\r\n\t\troute,\r\n\t\ttitle: (item as any).title ? String((item as any).title).trim().slice(0, 128) : undefined,\r\n\t\tminLv: Number.isFinite(minLv) ? Math.max(0, Math.floor(minLv)) : 0,\r\n\t\tpublic: !!(item as any).public || minLv === 0,\r\n\t};\r\n}\r\n\r\nexport function mergeRouteRights(existing: RouteRight[], incoming: RouteRight[]): RouteRight[] {\r\n\tconst map = new Map<string, RouteRight>();\r\n\tfor (const r of existing) map.set(r.route, { ...r });\r\n\tfor (const r of incoming) {\r\n\t\tconst prev = map.get(r.route);\r\n\t\tmap.set(r.route, prev ? { ...prev, ...r, route: r.route } : r);\r\n\t}\r\n\treturn [...map.values()].sort((a, b) => a.route.localeCompare(b.route));\r\n}\r\n\r\nexport function routesFromLegacyRights(rights: Record<string, string[] | undefined>, titles?: Record<string, string>): RouteRight[] {\r\n\tconst out: RouteRight[] = [];\r\n\tif (!rights || typeof rights !== 'object') return out;\r\n\tfor (const [bucket, routes] of Object.entries(rights)) {\r\n\t\tif (!Array.isArray(routes)) continue;\r\n\t\tconst minLv = LEGACY_RIGHTS_MIN_LV[bucket] ?? 0;\r\n\t\tfor (const route of routes) {\r\n\t\t\tconst r = String(route || '').trim();\r\n\t\t\tif (!r) continue;\r\n\t\t\tout.push({\r\n\t\t\t\troute: r,\r\n\t\t\t\ttitle: titles?.[r],\r\n\t\t\t\tminLv,\r\n\t\t\t\tpublic: minLv === 0,\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\treturn out;\r\n}\r\n"],"mappings":"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","names":["fs","path","path","legacy","fs"]}
|
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
dbRemoveSessionsByUser,
|
|
10
10
|
dbUpdateUser,
|
|
11
11
|
dbWriteSessions,
|
|
12
|
+
ensureUserStoreTables,
|
|
12
13
|
resolveRoleForLevel
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-A5CDJGJI.es.js";
|
|
14
15
|
import {
|
|
15
16
|
__name
|
|
16
17
|
} from "./chunk-6U2I5EON.es.js";
|
|
@@ -306,9 +307,10 @@ function toSessionRecord(row) {
|
|
|
306
307
|
}
|
|
307
308
|
__name(toSessionRecord, "toSessionRecord");
|
|
308
309
|
async function ensureBootstrapAdmin($asai) {
|
|
310
|
+
await ensureUserStoreTables($asai);
|
|
309
311
|
const users = await dbListUsers($asai);
|
|
310
312
|
if (users.length > 0) return;
|
|
311
|
-
const { hashPassword } = await import("./password-
|
|
313
|
+
const { hashPassword } = await import("./password-4BABWAG4.es.js");
|
|
312
314
|
const pwd = $asai?.hostconfig?.password;
|
|
313
315
|
if (!pwd) throw new Error("[user-store] ASAI_HOST_PASSWORD \u672A\u914D\u7F6E\uFF0C\u65E0\u6CD5\u521B\u5EFA bootstrap admin");
|
|
314
316
|
const { passHash, passSalt } = hashPassword(pwd);
|
|
@@ -432,4 +434,4 @@ export {
|
|
|
432
434
|
ensureBootstrapAdmin,
|
|
433
435
|
createUserStore
|
|
434
436
|
};
|
|
435
|
-
//# sourceMappingURL=chunk-
|
|
437
|
+
//# sourceMappingURL=chunk-A6VJPLDD.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\tensureUserStoreTables,\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\tawait ensureUserStoreTables($asai);\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":[]}
|