@skillbuilder/minitool-cli-config 0.0.0-beta-20261027053041051
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 +25 -0
- package/lib/file-store.js +316 -0
- package/lib/file-store.test.js +219 -0
- package/lib/index.js +10 -0
- package/lib/schemas.js +68 -0
- package/package.json +25 -0
- package/types/file-store.d.ts +35 -0
- package/types/file-store.test.d.ts +1 -0
- package/types/index.d.ts +3 -0
- package/types/schemas.d.ts +96 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @skillbuilder/minitool-cli-config
|
|
2
|
+
|
|
3
|
+
`redminitool` 的纯本地配置包:定义 `config.json` / `auth.json` schema 与路径,执行安全读写和文件权限
|
|
4
|
+
控制。它不发网络请求,也不判断登录或资源权限。
|
|
5
|
+
|
|
6
|
+
`config.json` 的 `credentialStore` 允许 `file` / `keychain`;本包只校验与保存选择,不访问系统凭据库。
|
|
7
|
+
配置和凭据由 Zod 4 schema 定义,TypeScript 类型直接从 schema 推导,
|
|
8
|
+
不再手写多套字段判断和类型声明。包根入口仅导出宿主实际需要的 `ConfigFiles`、
|
|
9
|
+
`ConfigFileContentError`、`parseAuthFileConfig`、凭据长度策略和相关公共类型;路径、文件名、
|
|
10
|
+
文件上限和内部 schema 不对外开放,包的 `exports` 也禁止 deep import。
|
|
11
|
+
|
|
12
|
+
当前文件位于 `~/.redminitool/`;`ConfigFiles` 可注入其他 home 目录供测试或宿主隔离。CLI 尚未发布,
|
|
13
|
+
因此首版直接采用双文件:`config.json` 与 `auth.json` 各自从 `schemaVersion: 1` 开始,不包含旧格式兼容
|
|
14
|
+
或 v1→v2 迁移逻辑。`auth.json` 以 `method` 区分 Secret 与设备码 OAuth 凭据;OAuth 存储 access/refresh token、有效期、可选 openId、scope 和 API origin,复用相同文件与 Keyring 策略。设备码等待状态放在受限的 `pending-auth.json`,沿用相同安全 I/O。`ConfigFiles(homeDir, "beta")` 使用 `~/.redminitool/beta/` 并同时校验父配置目录。
|
|
15
|
+
|
|
16
|
+
`config.json` 只保存 `schemaVersion` 和 `credentialStore`,凭据仅进入 `auth.json`
|
|
17
|
+
或系统 Keyring。目录和文件除拒绝符号链接外,还会校验 POSIX owner;凭据文件必须是普通文件且拒绝
|
|
18
|
+
多硬链接后再执行权限修正或读写。写入采用同目录临时文件、文件 `fsync`、原子 `rename` 和尽力执行的
|
|
19
|
+
目录 `fsync`;读取兼容 UTF-8 BOM,并以 1 MiB 上限阻止异常文件耗尽内存。多个进程并发写入不会产生
|
|
20
|
+
半截 JSON,冲突语义为最后一次原子替换生效。内容损坏不会阻止 logout 清理凭据;成功清理后会重置为
|
|
21
|
+
无凭据的首版文件配置。符号链接、owner 异常等路径安全错误不会被吞掉或覆盖。
|
|
22
|
+
|
|
23
|
+
`conf` 等通用配置库虽提供 JSON Schema、迁移和原子写入,但不覆盖本包要求的
|
|
24
|
+
`O_NOFOLLOW`、POSIX owner、多硬链拒绝、固定 `~/.redminitool` 双文件布局和 Keyring 存储切换语义,
|
|
25
|
+
因此本期只用 Zod 替换 schema 校验,保留已验证的安全文件 I/O。
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ConfigFiles = exports.ConfigFileContentError = void 0;
|
|
37
|
+
exports.parseAuthFileConfig = parseAuthFileConfig;
|
|
38
|
+
const crypto = __importStar(require("node:crypto"));
|
|
39
|
+
const fs = __importStar(require("node:fs"));
|
|
40
|
+
const os = __importStar(require("node:os"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const schemas_1 = require("./schemas");
|
|
43
|
+
const CONFIG_DIRNAME = ".redminitool";
|
|
44
|
+
const CONFIG_FILENAME = "config.json";
|
|
45
|
+
const AUTH_FILENAME = "auth.json";
|
|
46
|
+
const MAX_CONFIG_FILE_BYTES = 1024 * 1024;
|
|
47
|
+
/** JSON/schema/大小等内容错误;与符号链接、所有者异常等路径安全错误区分。 */
|
|
48
|
+
class ConfigFileContentError extends Error {
|
|
49
|
+
constructor(message, options) {
|
|
50
|
+
super(message, options);
|
|
51
|
+
this.name = "ConfigFileContentError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.ConfigFileContentError = ConfigFileContentError;
|
|
55
|
+
function getConfigPaths(homeDir, environment) {
|
|
56
|
+
const root = path.join(homeDir, CONFIG_DIRNAME);
|
|
57
|
+
const directory = environment === "beta" ? path.join(root, "beta") : root;
|
|
58
|
+
return {
|
|
59
|
+
directory,
|
|
60
|
+
config: path.join(directory, CONFIG_FILENAME),
|
|
61
|
+
auth: path.join(directory, AUTH_FILENAME),
|
|
62
|
+
pendingAuth: path.join(directory, "pending-auth.json"),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function parseSchema(schema, value, message) {
|
|
66
|
+
const result = schema.safeParse(value);
|
|
67
|
+
if (!result.success)
|
|
68
|
+
throw new ConfigFileContentError(message, { cause: result.error });
|
|
69
|
+
return result.data;
|
|
70
|
+
}
|
|
71
|
+
function parseAuthFileConfig(value) {
|
|
72
|
+
return parseSchema(schemas_1.authFileConfigSchema, value, "无效的 redminitool 凭据文件");
|
|
73
|
+
}
|
|
74
|
+
function parseGlobalConfig(value) {
|
|
75
|
+
return parseSchema(schemas_1.globalConfigSchema, value, "不支持的 redminitool 全局配置版本或凭据存储方式");
|
|
76
|
+
}
|
|
77
|
+
function isENOENT(error) {
|
|
78
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
79
|
+
}
|
|
80
|
+
/** 无网络、无认证流程;只负责路径、schema 与安全文件 I/O。 */
|
|
81
|
+
class ConfigFiles {
|
|
82
|
+
paths;
|
|
83
|
+
rootDirectory;
|
|
84
|
+
constructor(homeDir = os.homedir(), environment = "prod") {
|
|
85
|
+
this.rootDirectory = path.join(homeDir, CONFIG_DIRNAME);
|
|
86
|
+
this.paths = getConfigPaths(homeDir, environment);
|
|
87
|
+
}
|
|
88
|
+
readGlobal() {
|
|
89
|
+
const value = this.readJson(this.paths.config);
|
|
90
|
+
return value === null
|
|
91
|
+
? { schemaVersion: 1, credentialStore: "file" }
|
|
92
|
+
: parseGlobalConfig(value);
|
|
93
|
+
}
|
|
94
|
+
writeGlobal(config) {
|
|
95
|
+
this.writeJson(this.paths.config, parseGlobalConfig(config));
|
|
96
|
+
}
|
|
97
|
+
readAuth() {
|
|
98
|
+
const value = this.readJson(this.paths.auth);
|
|
99
|
+
return value === null ? null : parseAuthFileConfig(value);
|
|
100
|
+
}
|
|
101
|
+
writeAuth(auth) {
|
|
102
|
+
this.writeJson(this.paths.auth, parseAuthFileConfig(auth));
|
|
103
|
+
}
|
|
104
|
+
readPendingAuth() {
|
|
105
|
+
const value = this.readJson(this.paths.pendingAuth);
|
|
106
|
+
return value === null ? null : parseSchema(schemas_1.pendingAuthSchema, value, "无效的扫码登录等待状态");
|
|
107
|
+
}
|
|
108
|
+
writePendingAuth(value) {
|
|
109
|
+
this.writeJson(this.paths.pendingAuth, parseSchema(schemas_1.pendingAuthSchema, value, "无效的扫码登录等待状态"));
|
|
110
|
+
}
|
|
111
|
+
clearPendingAuth() {
|
|
112
|
+
this.assertSafeDirectory(false);
|
|
113
|
+
try {
|
|
114
|
+
this.assertSafeFile(this.paths.pendingAuth);
|
|
115
|
+
fs.unlinkSync(this.paths.pendingAuth);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (!isENOENT(error))
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
clearAuth() {
|
|
123
|
+
// logout 要能清理损坏的凭据文件;非敏感配置有效时继续保留。
|
|
124
|
+
let resetGlobal = false;
|
|
125
|
+
try {
|
|
126
|
+
const rawGlobal = this.readJson(this.paths.config);
|
|
127
|
+
if (rawGlobal !== null)
|
|
128
|
+
parseGlobalConfig(rawGlobal);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
// 配置内容损坏不应阻止删除凭据;路径、所有者等安全错误仍必须暴露。
|
|
132
|
+
if (!(error instanceof ConfigFileContentError))
|
|
133
|
+
throw error;
|
|
134
|
+
resetGlobal = true;
|
|
135
|
+
}
|
|
136
|
+
this.assertSafeDirectory(false);
|
|
137
|
+
try {
|
|
138
|
+
this.assertSafeFile(this.paths.auth);
|
|
139
|
+
fs.unlinkSync(this.paths.auth);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (!isENOENT(error))
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
if (resetGlobal) {
|
|
146
|
+
// 成功 logout 后将损坏配置覆盖为不含凭据的干净首版配置。
|
|
147
|
+
this.writeJson(this.paths.config, {
|
|
148
|
+
schemaVersion: 1,
|
|
149
|
+
credentialStore: "file",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
assertSafeDirectory(create) {
|
|
154
|
+
this.assertSafeDirectoryPath(this.rootDirectory, create);
|
|
155
|
+
if (this.paths.directory !== this.rootDirectory)
|
|
156
|
+
this.assertSafeDirectoryPath(this.paths.directory, create);
|
|
157
|
+
}
|
|
158
|
+
assertSafeDirectoryPath(directory, create) {
|
|
159
|
+
try {
|
|
160
|
+
const stat = fs.lstatSync(directory);
|
|
161
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
162
|
+
throw new Error("redminitool 配置目录不安全");
|
|
163
|
+
const fd = fs.openSync(directory, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
164
|
+
try {
|
|
165
|
+
const opened = fs.fstatSync(fd);
|
|
166
|
+
if (!opened.isDirectory())
|
|
167
|
+
throw new Error("redminitool 配置目录不安全");
|
|
168
|
+
this.assertCurrentUserOwner(opened, "配置目录");
|
|
169
|
+
fs.fchmodSync(fd, 0o700);
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
fs.closeSync(fd);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (!isENOENT(error))
|
|
177
|
+
throw error;
|
|
178
|
+
if (create) {
|
|
179
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
180
|
+
this.assertSafeDirectoryPath(directory, false);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
assertSafeFile(filePath) {
|
|
185
|
+
const fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
186
|
+
try {
|
|
187
|
+
const opened = fs.fstatSync(fd);
|
|
188
|
+
this.assertSafeOpenedFile(opened);
|
|
189
|
+
fs.fchmodSync(fd, 0o600);
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
fs.closeSync(fd);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
readJson(filePath) {
|
|
196
|
+
this.assertSafeDirectory(false);
|
|
197
|
+
let fd;
|
|
198
|
+
try {
|
|
199
|
+
// 不跟随符号链接打开,之后只对这个 fd 做校验、收紧权限和读取:
|
|
200
|
+
// 确认是当前用户的普通文件且没有硬链接,超过 1MB 按内容错误拒绝,
|
|
201
|
+
// 权限收到仅所有者可读写,避免按路径二次打开时被换成链接或并发替换。
|
|
202
|
+
fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
203
|
+
const stat = fs.fstatSync(fd);
|
|
204
|
+
this.assertSafeOpenedFile(stat);
|
|
205
|
+
if (stat.size > MAX_CONFIG_FILE_BYTES) {
|
|
206
|
+
throw new ConfigFileContentError(`redminitool 配置文件超过 ${MAX_CONFIG_FILE_BYTES} 字节上限`);
|
|
207
|
+
}
|
|
208
|
+
// 校验通过后再改权限、再读:fchmod 作用在已打开的 inode 上,把模式收到 0o600;
|
|
209
|
+
// 读也走同一个 fd,不再按路径打开一次。权限改失败就不会把内容读出来。
|
|
210
|
+
fs.fchmodSync(fd, 0o600);
|
|
211
|
+
const source = fs.readFileSync(fd, "utf8");
|
|
212
|
+
if (Buffer.byteLength(source, "utf8") > MAX_CONFIG_FILE_BYTES) {
|
|
213
|
+
throw new ConfigFileContentError(`redminitool 配置文件超过 ${MAX_CONFIG_FILE_BYTES} 字节上限`);
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
return JSON.parse(source.charCodeAt(0) === 0xfeff ? source.slice(1) : source);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (error instanceof SyntaxError) {
|
|
220
|
+
throw new ConfigFileContentError("redminitool 配置文件不是有效 JSON", {
|
|
221
|
+
cause: error,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (isENOENT(error))
|
|
229
|
+
return null;
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
if (fd !== undefined)
|
|
234
|
+
fs.closeSync(fd);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
writeJson(filePath, value) {
|
|
238
|
+
this.assertSafeDirectory(true);
|
|
239
|
+
try {
|
|
240
|
+
this.assertSafeFile(filePath);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (!isENOENT(error))
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
const temp = path.join(this.paths.directory, `.${path.basename(filePath)}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`);
|
|
247
|
+
const source = `${JSON.stringify(value, null, 2)}\n`;
|
|
248
|
+
if (Buffer.byteLength(source, "utf8") > MAX_CONFIG_FILE_BYTES) {
|
|
249
|
+
throw new ConfigFileContentError(`redminitool 配置文件超过 ${MAX_CONFIG_FILE_BYTES} 字节上限`);
|
|
250
|
+
}
|
|
251
|
+
let tempFd;
|
|
252
|
+
try {
|
|
253
|
+
tempFd = fs.openSync(temp, fs.constants.O_WRONLY |
|
|
254
|
+
fs.constants.O_CREAT |
|
|
255
|
+
fs.constants.O_EXCL |
|
|
256
|
+
fs.constants.O_NOFOLLOW, 0o600);
|
|
257
|
+
fs.writeFileSync(tempFd, source, "utf8");
|
|
258
|
+
fs.fsyncSync(tempFd);
|
|
259
|
+
fs.closeSync(tempFd);
|
|
260
|
+
tempFd = undefined;
|
|
261
|
+
fs.renameSync(temp, filePath);
|
|
262
|
+
fs.chmodSync(filePath, 0o600);
|
|
263
|
+
this.syncDirectoryBestEffort();
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
if (tempFd !== undefined) {
|
|
267
|
+
try {
|
|
268
|
+
fs.closeSync(tempFd);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
/* best effort */
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
fs.unlinkSync(temp);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
/* best effort */
|
|
279
|
+
}
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
syncDirectoryBestEffort() {
|
|
284
|
+
let fd;
|
|
285
|
+
try {
|
|
286
|
+
fd = fs.openSync(this.paths.directory, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
287
|
+
fs.fsyncSync(fd);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Windows/部分文件系统不支持目录 fsync;文件本身已经完成 fsync + 原子 rename。
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
if (fd !== undefined) {
|
|
294
|
+
try {
|
|
295
|
+
fs.closeSync(fd);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
/* best effort */
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
assertSafeOpenedFile(stat) {
|
|
304
|
+
// 并发原子替换后,已打开的旧 inode 可能变为 nlink=0;它仍是完整且安全的快照。
|
|
305
|
+
if (!stat.isFile() || stat.nlink > 1)
|
|
306
|
+
throw new Error("redminitool 配置文件不安全");
|
|
307
|
+
this.assertCurrentUserOwner(stat, "配置文件");
|
|
308
|
+
}
|
|
309
|
+
assertCurrentUserOwner(stat, target) {
|
|
310
|
+
const uid = process.getuid?.();
|
|
311
|
+
if (uid !== undefined && stat.uid !== uid) {
|
|
312
|
+
throw new Error(`redminitool ${target}不属于当前用户`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
exports.ConfigFiles = ConfigFiles;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
40
|
+
const node_child_process_1 = require("node:child_process");
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const os = __importStar(require("node:os"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
45
|
+
const index_1 = require("./index");
|
|
46
|
+
const MAX_CONFIG_FILE_BYTES = 1024 * 1024;
|
|
47
|
+
function temporaryHome(t) {
|
|
48
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), "redminitool-config-test-"));
|
|
49
|
+
t.after(() => fs.rmSync(home, { recursive: true, force: true }));
|
|
50
|
+
return home;
|
|
51
|
+
}
|
|
52
|
+
(0, node_test_1.default)("拒绝符号链接配置目录", (t) => {
|
|
53
|
+
const home = temporaryHome(t);
|
|
54
|
+
fs.symlinkSync(os.tmpdir(), path.join(home, ".redminitool"));
|
|
55
|
+
strict_1.default.throws(() => new index_1.ConfigFiles(home).readGlobal(), /配置目录不安全/);
|
|
56
|
+
});
|
|
57
|
+
(0, node_test_1.default)("拒绝符号链接凭据文件", (t) => {
|
|
58
|
+
const home = temporaryHome(t);
|
|
59
|
+
const files = new index_1.ConfigFiles(home);
|
|
60
|
+
fs.mkdirSync(files.paths.directory, { mode: 0o700 });
|
|
61
|
+
const outside = path.join(home, "outside.json");
|
|
62
|
+
fs.writeFileSync(outside, "{}");
|
|
63
|
+
fs.symlinkSync(outside, files.paths.auth);
|
|
64
|
+
strict_1.default.throws(() => files.readAuth());
|
|
65
|
+
strict_1.default.equal(fs.readFileSync(outside, "utf8"), "{}");
|
|
66
|
+
});
|
|
67
|
+
(0, node_test_1.default)("拒绝硬链接凭据文件,避免权限修正或写入影响其他路径", (t) => {
|
|
68
|
+
const home = temporaryHome(t);
|
|
69
|
+
const files = new index_1.ConfigFiles(home);
|
|
70
|
+
fs.mkdirSync(files.paths.directory, { mode: 0o700 });
|
|
71
|
+
const outside = path.join(home, "outside.json");
|
|
72
|
+
fs.writeFileSync(outside, "{}");
|
|
73
|
+
fs.linkSync(outside, files.paths.auth);
|
|
74
|
+
strict_1.default.throws(() => files.readAuth(), /配置文件不安全/);
|
|
75
|
+
strict_1.default.equal(fs.readFileSync(outside, "utf8"), "{}");
|
|
76
|
+
});
|
|
77
|
+
(0, node_test_1.default)("logout 能删除损坏的 auth.json,并保留非敏感配置", (t) => {
|
|
78
|
+
const files = new index_1.ConfigFiles(temporaryHome(t));
|
|
79
|
+
fs.mkdirSync(files.paths.directory, { mode: 0o700 });
|
|
80
|
+
fs.writeFileSync(files.paths.config, JSON.stringify({ schemaVersion: 1, credentialStore: "file" }));
|
|
81
|
+
fs.writeFileSync(files.paths.auth, "{broken-json");
|
|
82
|
+
files.clearAuth();
|
|
83
|
+
strict_1.default.equal(fs.existsSync(files.paths.auth), false);
|
|
84
|
+
strict_1.default.equal(files.readGlobal().credentialStore, "file");
|
|
85
|
+
});
|
|
86
|
+
(0, node_test_1.default)("config.json 损坏时 logout 删除 auth.json,并重置为无凭据配置", (t) => {
|
|
87
|
+
const files = new index_1.ConfigFiles(temporaryHome(t));
|
|
88
|
+
fs.mkdirSync(files.paths.directory, { mode: 0o700 });
|
|
89
|
+
fs.writeFileSync(files.paths.config, "{broken-config");
|
|
90
|
+
fs.writeFileSync(files.paths.auth, "{broken-auth");
|
|
91
|
+
files.clearAuth();
|
|
92
|
+
strict_1.default.equal(fs.existsSync(files.paths.auth), false);
|
|
93
|
+
strict_1.default.deepEqual(JSON.parse(fs.readFileSync(files.paths.config, "utf8")), {
|
|
94
|
+
schemaVersion: 1,
|
|
95
|
+
credentialStore: "file",
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
(0, node_test_1.default)("config.json 超限时 logout 仍删除 auth.json", (t) => {
|
|
99
|
+
const files = new index_1.ConfigFiles(temporaryHome(t));
|
|
100
|
+
fs.mkdirSync(files.paths.directory, { mode: 0o700 });
|
|
101
|
+
fs.writeFileSync(files.paths.config, Buffer.alloc(MAX_CONFIG_FILE_BYTES + 1, 0x20));
|
|
102
|
+
fs.writeFileSync(files.paths.auth, "{broken-auth");
|
|
103
|
+
files.clearAuth();
|
|
104
|
+
strict_1.default.equal(fs.existsSync(files.paths.auth), false);
|
|
105
|
+
strict_1.default.deepEqual(files.readGlobal(), { schemaVersion: 1, credentialStore: "file" });
|
|
106
|
+
});
|
|
107
|
+
(0, node_test_1.default)("兼容带 UTF-8 BOM 的配置,并修复目录和文件权限", (t) => {
|
|
108
|
+
const files = new index_1.ConfigFiles(temporaryHome(t));
|
|
109
|
+
fs.mkdirSync(files.paths.directory, { mode: 0o777 });
|
|
110
|
+
fs.writeFileSync(files.paths.config, `\ufeff${JSON.stringify({ schemaVersion: 1, credentialStore: "file" })}`, { mode: 0o666 });
|
|
111
|
+
strict_1.default.equal(files.readGlobal().credentialStore, "file");
|
|
112
|
+
strict_1.default.equal(fs.statSync(files.paths.directory).mode & 0o777, 0o700);
|
|
113
|
+
strict_1.default.equal(fs.statSync(files.paths.config).mode & 0o777, 0o600);
|
|
114
|
+
});
|
|
115
|
+
(0, node_test_1.default)("凭据 schema 拒绝超限与非法时间字段,并清理未知字段", (t) => {
|
|
116
|
+
const files = new index_1.ConfigFiles(temporaryHome(t));
|
|
117
|
+
fs.mkdirSync(files.paths.directory, { mode: 0o700 });
|
|
118
|
+
fs.writeFileSync(files.paths.config, Buffer.alloc(MAX_CONFIG_FILE_BYTES + 1, 0x20));
|
|
119
|
+
strict_1.default.throws(() => files.readGlobal(), /超过 .* 字节上限/);
|
|
120
|
+
strict_1.default.throws(() => (0, index_1.parseAuthFileConfig)({
|
|
121
|
+
schemaVersion: 1,
|
|
122
|
+
method: "secret",
|
|
123
|
+
secretId: "x".repeat(index_1.MAX_SECRET_ID_BYTES + 1),
|
|
124
|
+
sessionToken: "session",
|
|
125
|
+
issuedAt: "2026-01-01T00:00:00.000Z",
|
|
126
|
+
expiresAt: "2027-01-01T00:00:00.000Z",
|
|
127
|
+
}), /无效的 redminitool 凭据文件/);
|
|
128
|
+
strict_1.default.throws(() => (0, index_1.parseAuthFileConfig)({
|
|
129
|
+
schemaVersion: 1,
|
|
130
|
+
method: "secret",
|
|
131
|
+
secretId: "id",
|
|
132
|
+
sessionToken: "session",
|
|
133
|
+
issuedAt: "not-an-iso-time",
|
|
134
|
+
expiresAt: "2027-01-01T00:00:00.000Z",
|
|
135
|
+
}), /无效的 redminitool 凭据文件/);
|
|
136
|
+
const normalized = (0, index_1.parseAuthFileConfig)({
|
|
137
|
+
schemaVersion: 1,
|
|
138
|
+
method: "secret",
|
|
139
|
+
secretId: "id",
|
|
140
|
+
sessionToken: "session",
|
|
141
|
+
issuedAt: "2026-01-01T00:00:00.000Z",
|
|
142
|
+
expiresAt: "2027-01-01T00:00:00.000Z",
|
|
143
|
+
ignored: "field",
|
|
144
|
+
});
|
|
145
|
+
strict_1.default.equal("ignored" in normalized, false);
|
|
146
|
+
});
|
|
147
|
+
(0, node_test_1.default)("多个进程并发写配置不会产生截断 JSON 或临时文件残留", async (t) => {
|
|
148
|
+
const home = temporaryHome(t);
|
|
149
|
+
const modulePath = path.join(__dirname, "index.js");
|
|
150
|
+
const worker = `
|
|
151
|
+
const { ConfigFiles } = require(process.argv[1]);
|
|
152
|
+
const files = new ConfigFiles(process.argv[2]);
|
|
153
|
+
for (let index = 0; index < 20; index += 1) {
|
|
154
|
+
files.writeGlobal({ schemaVersion: 1, credentialStore: index % 2 ? "file" : "keychain" });
|
|
155
|
+
}
|
|
156
|
+
`;
|
|
157
|
+
const exits = await Promise.all(Array.from({ length: 4 }, () => new Promise((resolve, reject) => {
|
|
158
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, ["-e", worker, modulePath, home], {
|
|
159
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
160
|
+
});
|
|
161
|
+
let stderr = "";
|
|
162
|
+
child.stderr.setEncoding("utf8");
|
|
163
|
+
child.stderr.on("data", (chunk) => {
|
|
164
|
+
stderr += chunk;
|
|
165
|
+
});
|
|
166
|
+
child.on("error", reject);
|
|
167
|
+
child.on("close", (code) => {
|
|
168
|
+
if (code !== 0)
|
|
169
|
+
reject(new Error(stderr));
|
|
170
|
+
else
|
|
171
|
+
resolve(code);
|
|
172
|
+
});
|
|
173
|
+
})));
|
|
174
|
+
strict_1.default.deepEqual(exits, [0, 0, 0, 0]);
|
|
175
|
+
strict_1.default.equal(new index_1.ConfigFiles(home).readGlobal().schemaVersion, 1);
|
|
176
|
+
strict_1.default.deepEqual(fs.readdirSync(path.join(home, ".redminitool")).filter((name) => name.endsWith(".tmp")), []);
|
|
177
|
+
});
|
|
178
|
+
(0, node_test_1.default)("扫码凭据、等待状态和 beta 环境沿用安全存储且互相隔离", () => {
|
|
179
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), "redminitool-device-config-"));
|
|
180
|
+
try {
|
|
181
|
+
const prod = new index_1.ConfigFiles(home);
|
|
182
|
+
const beta = new index_1.ConfigFiles(home, "beta");
|
|
183
|
+
const credential = {
|
|
184
|
+
schemaVersion: 1,
|
|
185
|
+
method: "device",
|
|
186
|
+
apiBaseUrl: "https://auth.example",
|
|
187
|
+
accessToken: "private-access",
|
|
188
|
+
refreshToken: "private-refresh",
|
|
189
|
+
openId: "creator",
|
|
190
|
+
scope: "base_info,skill_publish",
|
|
191
|
+
issuedAt: "2026-10-26T00:00:00Z",
|
|
192
|
+
expiresAt: "2026-10-26T02:00:00Z",
|
|
193
|
+
refreshExpiresAt: "2026-11-26T00:00:00Z",
|
|
194
|
+
};
|
|
195
|
+
prod.writeAuth(credential);
|
|
196
|
+
strict_1.default.deepEqual(prod.readAuth(), credential);
|
|
197
|
+
strict_1.default.equal(beta.readAuth(), null);
|
|
198
|
+
beta.writePendingAuth({
|
|
199
|
+
apiBaseUrl: "https://auth.example",
|
|
200
|
+
deviceCode: "private-device",
|
|
201
|
+
codeVerifier: "v".repeat(64),
|
|
202
|
+
authorizeCommonUrl: "https://auth.example/scan",
|
|
203
|
+
authorizeH5Url: "https://auth.example/web",
|
|
204
|
+
userCode: "CODE",
|
|
205
|
+
expiresAt: "2026-10-26T01:00:00Z",
|
|
206
|
+
pollIntervalSeconds: 5,
|
|
207
|
+
});
|
|
208
|
+
strict_1.default.equal(prod.readPendingAuth(), null);
|
|
209
|
+
strict_1.default.equal(beta.readPendingAuth()?.deviceCode, "private-device");
|
|
210
|
+
if (process.platform !== "win32")
|
|
211
|
+
strict_1.default.equal(fs.statSync(beta.paths.pendingAuth).mode & 0o777, 0o600);
|
|
212
|
+
beta.clearPendingAuth();
|
|
213
|
+
strict_1.default.equal(beta.readPendingAuth(), null);
|
|
214
|
+
strict_1.default.throws(() => (0, index_1.parseAuthFileConfig)({ ...credential, refreshExpiresAt: credential.issuedAt }));
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_SESSION_TOKEN_BYTES = exports.MAX_SECRET_ID_BYTES = exports.parseAuthFileConfig = exports.ConfigFiles = exports.ConfigFileContentError = void 0;
|
|
4
|
+
var file_store_1 = require("./file-store");
|
|
5
|
+
Object.defineProperty(exports, "ConfigFileContentError", { enumerable: true, get: function () { return file_store_1.ConfigFileContentError; } });
|
|
6
|
+
Object.defineProperty(exports, "ConfigFiles", { enumerable: true, get: function () { return file_store_1.ConfigFiles; } });
|
|
7
|
+
Object.defineProperty(exports, "parseAuthFileConfig", { enumerable: true, get: function () { return file_store_1.parseAuthFileConfig; } });
|
|
8
|
+
var schemas_1 = require("./schemas");
|
|
9
|
+
Object.defineProperty(exports, "MAX_SECRET_ID_BYTES", { enumerable: true, get: function () { return schemas_1.MAX_SECRET_ID_BYTES; } });
|
|
10
|
+
Object.defineProperty(exports, "MAX_SESSION_TOKEN_BYTES", { enumerable: true, get: function () { return schemas_1.MAX_SESSION_TOKEN_BYTES; } });
|
package/lib/schemas.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.globalConfigSchema = exports.pendingAuthSchema = exports.authFileConfigSchema = exports.creatorCacheSchema = exports.MAX_SESSION_TOKEN_BYTES = exports.MAX_SECRET_ID_BYTES = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
exports.MAX_SECRET_ID_BYTES = 4 * 1024;
|
|
6
|
+
exports.MAX_SESSION_TOKEN_BYTES = 64 * 1024;
|
|
7
|
+
const MAX_CREATOR_TEXT_BYTES = 16 * 1024;
|
|
8
|
+
function utf8String(maxBytes) {
|
|
9
|
+
return zod_1.z.string().refine((value) => Buffer.byteLength(value, "utf8") <= maxBytes);
|
|
10
|
+
}
|
|
11
|
+
const optionalCreatorText = utf8String(MAX_CREATOR_TEXT_BYTES).optional().catch(undefined);
|
|
12
|
+
exports.creatorCacheSchema = zod_1.z.object({
|
|
13
|
+
creatorId: optionalCreatorText,
|
|
14
|
+
displayName: optionalCreatorText,
|
|
15
|
+
avatarUrl: optionalCreatorText,
|
|
16
|
+
description: optionalCreatorText,
|
|
17
|
+
});
|
|
18
|
+
const authPayloadSchema = zod_1.z.object({
|
|
19
|
+
method: zod_1.z.literal("secret"),
|
|
20
|
+
secretId: utf8String(exports.MAX_SECRET_ID_BYTES).refine((value) => value.trim().length > 0),
|
|
21
|
+
sessionToken: utf8String(exports.MAX_SESSION_TOKEN_BYTES).refine((value) => value.trim().length > 0),
|
|
22
|
+
issuedAt: zod_1.z.iso.datetime({ offset: true }),
|
|
23
|
+
expiresAt: zod_1.z.iso.datetime({ offset: true }),
|
|
24
|
+
creator: exports.creatorCacheSchema.optional().catch(undefined),
|
|
25
|
+
});
|
|
26
|
+
function expiresAfterIssue(value) {
|
|
27
|
+
return Date.parse(value.expiresAt) > Date.parse(value.issuedAt);
|
|
28
|
+
}
|
|
29
|
+
const secretAuthFileConfigSchema = authPayloadSchema
|
|
30
|
+
.extend({ schemaVersion: zod_1.z.literal(1) })
|
|
31
|
+
.refine(expiresAfterIssue, { path: ["expiresAt"] });
|
|
32
|
+
const token = utf8String(exports.MAX_SESSION_TOKEN_BYTES).refine((value) => value.trim().length > 0);
|
|
33
|
+
const deviceAuthFileConfigSchema = zod_1.z
|
|
34
|
+
.object({
|
|
35
|
+
schemaVersion: zod_1.z.literal(1),
|
|
36
|
+
method: zod_1.z.literal("device"),
|
|
37
|
+
apiBaseUrl: zod_1.z.url(),
|
|
38
|
+
accessToken: token,
|
|
39
|
+
refreshToken: token,
|
|
40
|
+
issuedAt: zod_1.z.iso.datetime({ offset: true }),
|
|
41
|
+
expiresAt: zod_1.z.iso.datetime({ offset: true }),
|
|
42
|
+
refreshExpiresAt: zod_1.z.iso.datetime({ offset: true }),
|
|
43
|
+
openId: utf8String(MAX_CREATOR_TEXT_BYTES).min(1).optional(),
|
|
44
|
+
scope: utf8String(MAX_CREATOR_TEXT_BYTES),
|
|
45
|
+
creator: exports.creatorCacheSchema.optional().catch(undefined),
|
|
46
|
+
})
|
|
47
|
+
.refine(expiresAfterIssue, { path: ["expiresAt"] })
|
|
48
|
+
.refine((value) => Date.parse(value.refreshExpiresAt) > Date.parse(value.issuedAt), {
|
|
49
|
+
path: ["refreshExpiresAt"],
|
|
50
|
+
});
|
|
51
|
+
exports.authFileConfigSchema = zod_1.z.discriminatedUnion("method", [
|
|
52
|
+
secretAuthFileConfigSchema,
|
|
53
|
+
deviceAuthFileConfigSchema,
|
|
54
|
+
]);
|
|
55
|
+
exports.pendingAuthSchema = zod_1.z.object({
|
|
56
|
+
apiBaseUrl: zod_1.z.url(),
|
|
57
|
+
deviceCode: token,
|
|
58
|
+
codeVerifier: zod_1.z.string().regex(/^[A-Za-z0-9._~-]{43,128}$/),
|
|
59
|
+
authorizeCommonUrl: zod_1.z.url(),
|
|
60
|
+
authorizeH5Url: zod_1.z.url(),
|
|
61
|
+
userCode: utf8String(MAX_CREATOR_TEXT_BYTES),
|
|
62
|
+
expiresAt: zod_1.z.iso.datetime({ offset: true }),
|
|
63
|
+
pollIntervalSeconds: zod_1.z.number().int().min(1).max(60),
|
|
64
|
+
});
|
|
65
|
+
exports.globalConfigSchema = zod_1.z.object({
|
|
66
|
+
schemaVersion: zod_1.z.literal(1),
|
|
67
|
+
credentialStore: zod_1.z.enum(["file", "keychain"]),
|
|
68
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skillbuilder/minitool-cli-config",
|
|
3
|
+
"version": "0.0.0-beta-20261027053041051",
|
|
4
|
+
"description": "redminitool configuration schemas, paths and safe file IO",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "types/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./types/index.d.ts",
|
|
10
|
+
"require": "./lib/index.js",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"lib",
|
|
16
|
+
"types"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20.0.0"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"zod": "4.3.5"
|
|
23
|
+
},
|
|
24
|
+
"license": "ISC"
|
|
25
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type AuthFileConfig, type GlobalConfig, type PendingAuth } from "./schemas";
|
|
2
|
+
/** JSON/schema/大小等内容错误;与符号链接、所有者异常等路径安全错误区分。 */
|
|
3
|
+
export declare class ConfigFileContentError extends Error {
|
|
4
|
+
constructor(message: string, options?: ErrorOptions);
|
|
5
|
+
}
|
|
6
|
+
interface ConfigPaths {
|
|
7
|
+
directory: string;
|
|
8
|
+
config: string;
|
|
9
|
+
auth: string;
|
|
10
|
+
pendingAuth: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function parseAuthFileConfig(value: unknown): AuthFileConfig;
|
|
13
|
+
/** 无网络、无认证流程;只负责路径、schema 与安全文件 I/O。 */
|
|
14
|
+
export declare class ConfigFiles {
|
|
15
|
+
readonly paths: ConfigPaths;
|
|
16
|
+
private readonly rootDirectory;
|
|
17
|
+
constructor(homeDir?: string, environment?: "prod" | "beta");
|
|
18
|
+
readGlobal(): GlobalConfig;
|
|
19
|
+
writeGlobal(config: GlobalConfig): void;
|
|
20
|
+
readAuth(): AuthFileConfig | null;
|
|
21
|
+
writeAuth(auth: AuthFileConfig): void;
|
|
22
|
+
readPendingAuth(): PendingAuth | null;
|
|
23
|
+
writePendingAuth(value: PendingAuth): void;
|
|
24
|
+
clearPendingAuth(): void;
|
|
25
|
+
clearAuth(): void;
|
|
26
|
+
private assertSafeDirectory;
|
|
27
|
+
private assertSafeDirectoryPath;
|
|
28
|
+
private assertSafeFile;
|
|
29
|
+
private readJson;
|
|
30
|
+
private writeJson;
|
|
31
|
+
private syncDirectoryBestEffort;
|
|
32
|
+
private assertSafeOpenedFile;
|
|
33
|
+
private assertCurrentUserOwner;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ConfigFileContentError, ConfigFiles, parseAuthFileConfig } from "./file-store";
|
|
2
|
+
export { MAX_SECRET_ID_BYTES, MAX_SESSION_TOKEN_BYTES } from "./schemas";
|
|
3
|
+
export type { AuthFileConfig, CreatorCache, GlobalConfig, SecretAuthFileConfig, DeviceAuthFileConfig, PendingAuth, } from "./schemas";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const MAX_SECRET_ID_BYTES: number;
|
|
3
|
+
export declare const MAX_SESSION_TOKEN_BYTES: number;
|
|
4
|
+
export declare const creatorCacheSchema: z.ZodObject<{
|
|
5
|
+
creatorId: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
6
|
+
displayName: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
7
|
+
avatarUrl: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
8
|
+
description: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
declare const secretAuthFileConfigSchema: z.ZodObject<{
|
|
11
|
+
method: z.ZodLiteral<"secret">;
|
|
12
|
+
secretId: z.ZodString;
|
|
13
|
+
sessionToken: z.ZodString;
|
|
14
|
+
issuedAt: z.ZodISODateTime;
|
|
15
|
+
expiresAt: z.ZodISODateTime;
|
|
16
|
+
creator: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
17
|
+
creatorId: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
18
|
+
displayName: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
19
|
+
avatarUrl: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
20
|
+
description: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
21
|
+
}, z.core.$strip>>>;
|
|
22
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
declare const deviceAuthFileConfigSchema: z.ZodObject<{
|
|
25
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
26
|
+
method: z.ZodLiteral<"device">;
|
|
27
|
+
apiBaseUrl: z.ZodURL;
|
|
28
|
+
accessToken: z.ZodString;
|
|
29
|
+
refreshToken: z.ZodString;
|
|
30
|
+
issuedAt: z.ZodISODateTime;
|
|
31
|
+
expiresAt: z.ZodISODateTime;
|
|
32
|
+
refreshExpiresAt: z.ZodISODateTime;
|
|
33
|
+
openId: z.ZodOptional<z.ZodString>;
|
|
34
|
+
scope: z.ZodString;
|
|
35
|
+
creator: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
36
|
+
creatorId: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
37
|
+
displayName: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
38
|
+
avatarUrl: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
39
|
+
description: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
40
|
+
}, z.core.$strip>>>;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
export declare const authFileConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
43
|
+
method: z.ZodLiteral<"secret">;
|
|
44
|
+
secretId: z.ZodString;
|
|
45
|
+
sessionToken: z.ZodString;
|
|
46
|
+
issuedAt: z.ZodISODateTime;
|
|
47
|
+
expiresAt: z.ZodISODateTime;
|
|
48
|
+
creator: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
49
|
+
creatorId: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
50
|
+
displayName: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
51
|
+
avatarUrl: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
52
|
+
description: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
53
|
+
}, z.core.$strip>>>;
|
|
54
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
55
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
56
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
57
|
+
method: z.ZodLiteral<"device">;
|
|
58
|
+
apiBaseUrl: z.ZodURL;
|
|
59
|
+
accessToken: z.ZodString;
|
|
60
|
+
refreshToken: z.ZodString;
|
|
61
|
+
issuedAt: z.ZodISODateTime;
|
|
62
|
+
expiresAt: z.ZodISODateTime;
|
|
63
|
+
refreshExpiresAt: z.ZodISODateTime;
|
|
64
|
+
openId: z.ZodOptional<z.ZodString>;
|
|
65
|
+
scope: z.ZodString;
|
|
66
|
+
creator: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
67
|
+
creatorId: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
68
|
+
displayName: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
69
|
+
avatarUrl: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
70
|
+
description: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
71
|
+
}, z.core.$strip>>>;
|
|
72
|
+
}, z.core.$strip>], "method">;
|
|
73
|
+
export declare const pendingAuthSchema: z.ZodObject<{
|
|
74
|
+
apiBaseUrl: z.ZodURL;
|
|
75
|
+
deviceCode: z.ZodString;
|
|
76
|
+
codeVerifier: z.ZodString;
|
|
77
|
+
authorizeCommonUrl: z.ZodURL;
|
|
78
|
+
authorizeH5Url: z.ZodURL;
|
|
79
|
+
userCode: z.ZodString;
|
|
80
|
+
expiresAt: z.ZodISODateTime;
|
|
81
|
+
pollIntervalSeconds: z.ZodNumber;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
export type SecretAuthFileConfig = z.infer<typeof secretAuthFileConfigSchema>;
|
|
84
|
+
export type DeviceAuthFileConfig = z.infer<typeof deviceAuthFileConfigSchema>;
|
|
85
|
+
export type PendingAuth = z.infer<typeof pendingAuthSchema>;
|
|
86
|
+
export declare const globalConfigSchema: z.ZodObject<{
|
|
87
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
88
|
+
credentialStore: z.ZodEnum<{
|
|
89
|
+
file: "file";
|
|
90
|
+
keychain: "keychain";
|
|
91
|
+
}>;
|
|
92
|
+
}, z.core.$strip>;
|
|
93
|
+
export type GlobalConfig = z.infer<typeof globalConfigSchema>;
|
|
94
|
+
export type CreatorCache = z.infer<typeof creatorCacheSchema>;
|
|
95
|
+
export type AuthFileConfig = z.infer<typeof authFileConfigSchema>;
|
|
96
|
+
export {};
|