@skillbuilder/minitool-cli-auth 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.
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ManagedCredentialStore = void 0;
4
+ const node_util_1 = require("node:util");
5
+ const minitool_cli_config_1 = require("@skillbuilder/minitool-cli-config");
6
+ const credential_store_1 = require("./credential-store");
7
+ const keyring_credential_store_1 = require("./keyring-credential-store");
8
+ function normalizeStoredCredential(credential) {
9
+ const json = JSON.stringify((0, minitool_cli_config_1.parseAuthFileConfig)(credential));
10
+ return (0, minitool_cli_config_1.parseAuthFileConfig)(JSON.parse(json));
11
+ }
12
+ /** 文件与系统凭据库之间的显式存储策略。 */
13
+ class ManagedCredentialStore {
14
+ files;
15
+ file;
16
+ keyring;
17
+ constructor(files, file = new credential_store_1.FileCredentialStore(files), keyring = new keyring_credential_store_1.KeyringCredentialStore(files.paths.auth)) {
18
+ this.files = files;
19
+ this.file = file;
20
+ this.keyring = keyring;
21
+ }
22
+ getMode() {
23
+ return this.files.readGlobal().credentialStore;
24
+ }
25
+ inspect(probeKeyring = false) {
26
+ const configuredMode = this.getMode();
27
+ const keyringStatus = probeKeyring || configuredMode === "keychain" ? this.inspectKeyring() : undefined;
28
+ return {
29
+ configuredMode,
30
+ provider: configuredMode === "keychain" ? (0, keyring_credential_store_1.getSystemCredentialStoreName)() : "auth.json (0600)",
31
+ protection: configuredMode === "keychain" ? "os_protected_store" : "restricted_plaintext_file",
32
+ ...keyringStatus,
33
+ };
34
+ }
35
+ get() {
36
+ if (this.getMode() === "file")
37
+ return this.file.get();
38
+ const credential = this.keyring.get();
39
+ this.file.delete();
40
+ return credential;
41
+ }
42
+ update(config) {
43
+ const mode = this.getMode();
44
+ this.writeVerified(this.store(mode), config, "登录态保存校验失败");
45
+ if (mode === "keychain")
46
+ this.file.delete();
47
+ }
48
+ delete() {
49
+ let mode;
50
+ try {
51
+ mode = this.getMode();
52
+ }
53
+ catch (error) {
54
+ if (!(error instanceof minitool_cli_config_1.ConfigFileContentError))
55
+ throw error;
56
+ }
57
+ this.file.delete();
58
+ try {
59
+ this.keyring.delete();
60
+ }
61
+ catch (error) {
62
+ if (mode !== "file" || !(error instanceof keyring_credential_store_1.KeyringUnavailableError))
63
+ throw error;
64
+ }
65
+ }
66
+ use(mode) {
67
+ const current = this.getMode();
68
+ if (mode === current)
69
+ return { changed: false, migrated: false, sourceRetained: false };
70
+ const source = this.store(current);
71
+ const target = this.store(mode);
72
+ let credential;
73
+ try {
74
+ credential = source.get();
75
+ }
76
+ catch (error) {
77
+ if (current === "keychain" &&
78
+ mode === "file" &&
79
+ (error instanceof keyring_credential_store_1.KeyringUnavailableError || error instanceof keyring_credential_store_1.KeyringAccessError)) {
80
+ this.setMode("file");
81
+ return { changed: true, migrated: false, sourceRetained: true };
82
+ }
83
+ throw error;
84
+ }
85
+ if (credential)
86
+ this.writeVerified(target, credential, "凭据存储切换校验失败");
87
+ else if (mode === "keychain")
88
+ this.keyring.probe();
89
+ try {
90
+ this.setMode(mode);
91
+ }
92
+ catch (error) {
93
+ if (credential)
94
+ target.delete();
95
+ throw error;
96
+ }
97
+ try {
98
+ source.delete();
99
+ }
100
+ catch (error) {
101
+ this.setMode(current);
102
+ if (credential)
103
+ target.delete();
104
+ throw error;
105
+ }
106
+ return { changed: true, migrated: credential !== null, sourceRetained: false };
107
+ }
108
+ store(mode) {
109
+ return mode === "keychain" ? this.keyring : this.file;
110
+ }
111
+ setMode(mode) {
112
+ this.files.writeGlobal({ schemaVersion: 1, credentialStore: mode });
113
+ }
114
+ inspectKeyring() {
115
+ try {
116
+ this.keyring.probe();
117
+ return { keyringAvailable: true };
118
+ }
119
+ catch (error) {
120
+ return {
121
+ keyringAvailable: false,
122
+ keyringIssue: error instanceof keyring_credential_store_1.KeyringUnavailableError ? "package_unavailable" : "access_error",
123
+ };
124
+ }
125
+ }
126
+ writeVerified(store, credential, errorMessage) {
127
+ const expected = normalizeStoredCredential(credential);
128
+ store.update(expected);
129
+ if ((0, node_util_1.isDeepStrictEqual)(store.get(), expected))
130
+ return;
131
+ store.delete();
132
+ throw new Error(errorMessage);
133
+ }
134
+ }
135
+ exports.ManagedCredentialStore = ManagedCredentialStore;
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SecretAuthService = exports.HttpSecretAuthApi = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const minitool_cli_config_1 = require("@skillbuilder/minitool-cli-config");
6
+ const auth_state_1 = require("./auth-state");
7
+ const LOGIN_SERVICE_UNAVAILABLE = "登录服务暂不可用,请稍后重试";
8
+ const INVALID_SECRET_CREDENTIAL = "Secret ID 或 Secret Key 无效,请检查后重试";
9
+ function loginServiceUnavailable(cause) {
10
+ return new Error(LOGIN_SERVICE_UNAVAILABLE, { cause });
11
+ }
12
+ class HttpSecretAuthApi {
13
+ endpointUrl;
14
+ request;
15
+ constructor(endpointUrl, request = fetch) {
16
+ this.endpointUrl = endpointUrl;
17
+ this.request = request;
18
+ }
19
+ async check(payload) {
20
+ if (!this.endpointUrl)
21
+ throw loginServiceUnavailable();
22
+ let endpoint;
23
+ try {
24
+ endpoint = new URL(this.endpointUrl);
25
+ }
26
+ catch (error) {
27
+ throw loginServiceUnavailable(error);
28
+ }
29
+ if ((endpoint.protocol !== "https:" &&
30
+ !(endpoint.protocol === "http:" && ["localhost", "127.0.0.1"].includes(endpoint.hostname))) ||
31
+ endpoint.username ||
32
+ endpoint.password ||
33
+ endpoint.search ||
34
+ endpoint.hash) {
35
+ throw loginServiceUnavailable();
36
+ }
37
+ let response;
38
+ try {
39
+ response = await this.request(endpoint, {
40
+ method: "POST",
41
+ headers: { "content-type": "application/json" },
42
+ body: JSON.stringify(payload),
43
+ signal: AbortSignal.timeout(20_000),
44
+ });
45
+ }
46
+ catch (error) {
47
+ throw loginServiceUnavailable(error);
48
+ }
49
+ if (response.status === 401 || response.status === 403) {
50
+ throw new Error(INVALID_SECRET_CREDENTIAL);
51
+ }
52
+ if (!response.ok)
53
+ throw loginServiceUnavailable();
54
+ let data;
55
+ try {
56
+ data = (await response.json());
57
+ }
58
+ catch (error) {
59
+ throw loginServiceUnavailable(error);
60
+ }
61
+ if (!data || typeof data.code !== "number")
62
+ throw loginServiceUnavailable();
63
+ return data;
64
+ }
65
+ }
66
+ exports.HttpSecretAuthApi = HttpSecretAuthApi;
67
+ function formatProofMinute(now) {
68
+ const date = new Date(now);
69
+ const pad = (value) => String(value).padStart(2, "0");
70
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}`;
71
+ }
72
+ function toCreator(userInfo) {
73
+ if (!userInfo?.user_id)
74
+ return undefined;
75
+ return {
76
+ creatorId: userInfo.user_id,
77
+ displayName: userInfo.nick ?? userInfo.nickname ?? userInfo.user_id,
78
+ avatarUrl: userInfo.avatar,
79
+ description: userInfo.desc,
80
+ };
81
+ }
82
+ function normalizeServerTimestamp(value) {
83
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)
84
+ return null;
85
+ // 兼容旧接口注释中的秒级时间戳;当前真实接口返回 13 位毫秒时间戳。
86
+ const milliseconds = value < 1_000_000_000_000 ? value * 1000 : value;
87
+ try {
88
+ new Date(milliseconds).toISOString();
89
+ return milliseconds;
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ /** Secret Key 仅用于一次签名,永不进入 CredentialStore。 */
96
+ class SecretAuthService {
97
+ store;
98
+ api;
99
+ storage;
100
+ now;
101
+ constructor(store, api, storage, now = Date.now) {
102
+ this.store = store;
103
+ this.api = api;
104
+ this.storage = storage;
105
+ this.now = now;
106
+ }
107
+ createProof(secretId, secretKey, nonce) {
108
+ return (0, node_crypto_1.createHash)("md5")
109
+ .update(`${secretId}#${secretKey}#${nonce}#${formatProofMinute(this.now())}`)
110
+ .digest("hex");
111
+ }
112
+ async loginWithSecret(secretId, secretKey) {
113
+ const normalizedSecretId = secretId.trim();
114
+ if (!normalizedSecretId || !secretKey.trim())
115
+ throw new Error("Secret ID 和 Secret Key 不能为空");
116
+ if (Buffer.byteLength(normalizedSecretId, "utf8") > minitool_cli_config_1.MAX_SECRET_ID_BYTES) {
117
+ throw new Error("Secret ID 长度超出限制");
118
+ }
119
+ if (Buffer.byteLength(secretKey, "utf8") > minitool_cli_config_1.MAX_SESSION_TOKEN_BYTES) {
120
+ throw new Error("Secret Key 长度超出限制");
121
+ }
122
+ const nonce = (0, node_crypto_1.randomBytes)(8).toString("hex");
123
+ const proof = this.createProof(normalizedSecretId, secretKey, nonce);
124
+ const response = await this.api.check({
125
+ secret_id: normalizedSecretId,
126
+ nonce,
127
+ cli_token: proof,
128
+ });
129
+ if (response.code !== 0)
130
+ throw new Error(INVALID_SECRET_CREDENTIAL);
131
+ const responseData = response.data;
132
+ const sessionToken = responseData?.cli_token;
133
+ const ttl = responseData?.expire_in_sec;
134
+ const returnedSecretId = responseData?.secret_id;
135
+ const issuedAt = normalizeServerTimestamp(responseData?.create_at);
136
+ if ((returnedSecretId !== undefined && returnedSecretId !== normalizedSecretId) ||
137
+ typeof sessionToken !== "string" ||
138
+ !sessionToken.trim() ||
139
+ Buffer.byteLength(sessionToken, "utf8") > minitool_cli_config_1.MAX_SESSION_TOKEN_BYTES ||
140
+ typeof ttl !== "number" ||
141
+ !Number.isSafeInteger(ttl) ||
142
+ ttl <= 0 ||
143
+ issuedAt === null) {
144
+ throw new Error("登录服务返回异常,本次登录态未保存,请稍后重试");
145
+ }
146
+ const expiresAtMs = issuedAt + ttl * 1000;
147
+ if (!Number.isFinite(issuedAt) || !Number.isSafeInteger(expiresAtMs)) {
148
+ throw new Error("登录服务返回异常,本次登录态未保存,请稍后重试");
149
+ }
150
+ let expiresAt;
151
+ try {
152
+ expiresAt = new Date(expiresAtMs).toISOString();
153
+ }
154
+ catch {
155
+ throw new Error("登录服务返回异常,本次登录态未保存,请稍后重试");
156
+ }
157
+ const creator = toCreator(responseData?.userInfo);
158
+ this.store.update({
159
+ schemaVersion: 1,
160
+ method: "secret",
161
+ secretId: normalizedSecretId,
162
+ sessionToken,
163
+ issuedAt: new Date(issuedAt).toISOString(),
164
+ expiresAt,
165
+ creator,
166
+ });
167
+ return { secretId: normalizedSecretId, expiresAt, creator };
168
+ }
169
+ readStoredCredential() {
170
+ const credential = this.store.get();
171
+ if (!credential || credential.method !== "secret")
172
+ return { status: "missing" };
173
+ return Date.parse(credential.expiresAt) > this.now()
174
+ ? { status: "valid", credential }
175
+ : { status: "expired", credential };
176
+ }
177
+ async getAuthState() {
178
+ let stored;
179
+ let source;
180
+ try {
181
+ stored = this.readStoredCredential();
182
+ source = this.storage.getMode() === "keychain" ? "os_keychain" : "local_file";
183
+ }
184
+ catch {
185
+ return { status: "unavailable", reason: "credential_store_error" };
186
+ }
187
+ if (stored.status === "missing")
188
+ return { status: "anonymous", reason: "not_logged_in" };
189
+ if (stored.status === "expired")
190
+ return { status: "anonymous", reason: "expired" };
191
+ const { credential } = stored;
192
+ const identitySnapshot = credential.creator?.creatorId
193
+ ? {
194
+ creatorId: credential.creator.creatorId,
195
+ displayName: credential.creator.displayName ?? credential.creator.creatorId,
196
+ avatarUrl: credential.creator.avatarUrl,
197
+ description: credential.creator.description,
198
+ }
199
+ : null;
200
+ return {
201
+ status: "authenticated",
202
+ method: "secret",
203
+ source,
204
+ identitySnapshot,
205
+ expiresAt: credential.expiresAt,
206
+ };
207
+ }
208
+ async getAccessToken() {
209
+ const stored = this.readStoredCredential();
210
+ if (stored.status === "missing")
211
+ throw new auth_state_1.AuthenticationRequiredError();
212
+ if (stored.status === "expired") {
213
+ this.store.delete();
214
+ throw new auth_state_1.AuthenticationRequiredError("登录态已过期,请重新登录。");
215
+ }
216
+ return stored.credential.sessionToken;
217
+ }
218
+ async whoAmI() {
219
+ const state = (0, auth_state_1.requireAuthenticatedAuthState)(await this.getAuthState());
220
+ if (!state.identitySnapshot)
221
+ throw new auth_state_1.CreatorIdentityUnavailableError();
222
+ return state.identitySnapshot;
223
+ }
224
+ async logout() {
225
+ this.store.delete();
226
+ }
227
+ }
228
+ exports.SecretAuthService = SecretAuthService;
@@ -0,0 +1,248 @@
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 fs = __importStar(require("node:fs"));
41
+ const os = __importStar(require("node:os"));
42
+ const path = __importStar(require("node:path"));
43
+ const node_test_1 = __importDefault(require("node:test"));
44
+ const minitool_cli_config_1 = require("@skillbuilder/minitool-cli-config");
45
+ const credential_store_1 = require("./credential-store");
46
+ const secret_auth_1 = require("./secret-auth");
47
+ const NOW = Date.parse("2026-09-22T10:00:00.000Z");
48
+ function makeService(t, api) {
49
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), "redminitool-auth-test-"));
50
+ t.after(() => fs.rmSync(home, { recursive: true, force: true }));
51
+ const files = new minitool_cli_config_1.ConfigFiles(home);
52
+ return {
53
+ files,
54
+ service: new secret_auth_1.SecretAuthService(new credential_store_1.FileCredentialStore(files), api, { getMode: () => "file" }, () => NOW),
55
+ };
56
+ }
57
+ (0, node_test_1.default)("Secret Key 只参与签名;落盘的是服务端返回的会话 token", async (t) => {
58
+ const api = {
59
+ async check(request) {
60
+ strict_1.default.equal(request.secret_id, "secret-id");
61
+ strict_1.default.match(request.nonce, /^[a-f0-9]{16}$/);
62
+ strict_1.default.notEqual(request.cli_token, "server-session");
63
+ return {
64
+ code: 0,
65
+ msg: "ok",
66
+ data: {
67
+ secret_id: "secret-id",
68
+ cli_token: "server-session",
69
+ create_at: NOW,
70
+ expire_in_sec: 3600,
71
+ userInfo: { user_id: "creator-1", nick: "Creator" },
72
+ },
73
+ };
74
+ },
75
+ };
76
+ const { files, service } = makeService(t, api);
77
+ const result = await service.loginWithSecret("secret-id", "never-persist-this-key");
78
+ strict_1.default.equal(result.creator?.creatorId, "creator-1");
79
+ strict_1.default.equal(await service.getAccessToken(), "server-session");
80
+ strict_1.default.equal((await service.getAuthState()).status, "authenticated");
81
+ strict_1.default.equal((await service.whoAmI()).creatorId, "creator-1");
82
+ strict_1.default.equal(files.readGlobal().credentialStore, "file");
83
+ strict_1.default.equal(fs.existsSync(files.paths.config), true);
84
+ strict_1.default.equal(fs.readFileSync(files.paths.auth, "utf8").includes("never-persist-this-key"), false);
85
+ strict_1.default.equal(fs.readFileSync(files.paths.config, "utf8").includes("server-session"), false);
86
+ });
87
+ (0, node_test_1.default)("服务端没有有效会话时不落盘;过期后清理凭据", async (t) => {
88
+ const { files, service } = makeService(t, {
89
+ async check() {
90
+ return {
91
+ code: 0,
92
+ msg: "ok",
93
+ data: { secret_id: "id", cli_token: "", create_at: NOW, expire_in_sec: 3600 },
94
+ };
95
+ },
96
+ });
97
+ await strict_1.default.rejects(service.loginWithSecret("id", "key"), /登录服务返回异常/);
98
+ strict_1.default.equal(files.readAuth(), null);
99
+ files.writeAuth({
100
+ schemaVersion: 1,
101
+ method: "secret",
102
+ secretId: "id",
103
+ sessionToken: "expired",
104
+ issuedAt: new Date(NOW - 7200_000).toISOString(),
105
+ expiresAt: new Date(NOW - 3600_000).toISOString(),
106
+ });
107
+ strict_1.default.deepEqual(await service.getAuthState(), { status: "anonymous", reason: "expired" });
108
+ strict_1.default.notEqual(files.readAuth(), null);
109
+ await strict_1.default.rejects(service.getAccessToken(), { code: "AUTHENTICATION_REQUIRED" });
110
+ strict_1.default.equal(files.readAuth(), null);
111
+ });
112
+ (0, node_test_1.default)("凭据存储故障与未登录分开表达", async () => {
113
+ const store = {
114
+ get() {
115
+ throw new Error("provider failed");
116
+ },
117
+ update() { },
118
+ delete() { },
119
+ };
120
+ const service = new secret_auth_1.SecretAuthService(store, { check: async () => ({ code: 0, msg: "ok", data: null }) }, { getMode: () => "file" }, () => NOW);
121
+ strict_1.default.deepEqual(await service.getAuthState(), {
122
+ status: "unavailable",
123
+ reason: "credential_store_error",
124
+ });
125
+ await strict_1.default.rejects(service.whoAmI(), { code: "CREDENTIAL_STORE_UNAVAILABLE" });
126
+ });
127
+ (0, node_test_1.default)("兼容真实 cli_auth:不回传 secret_id,确认后的签名即短期登录凭据", async (t) => {
128
+ const { files, service } = makeService(t, {
129
+ async check(request) {
130
+ return {
131
+ code: 0,
132
+ msg: "ok",
133
+ data: {
134
+ cli_token: request.cli_token,
135
+ create_at: NOW,
136
+ expire_in_sec: 7200,
137
+ },
138
+ };
139
+ },
140
+ });
141
+ const result = await service.loginWithSecret("id", "key");
142
+ strict_1.default.equal(result.secretId, "id");
143
+ const stored = files.readAuth();
144
+ strict_1.default.equal(stored?.method, "secret");
145
+ if (stored?.method !== "secret")
146
+ throw new Error("预期 Secret 凭据");
147
+ strict_1.default.equal(stored.secretId, "id");
148
+ strict_1.default.match(stored.sessionToken, /^[a-f0-9]{32}$/);
149
+ strict_1.default.equal(files.readAuth()?.issuedAt, new Date(NOW).toISOString());
150
+ strict_1.default.equal(files.readAuth()?.expiresAt, new Date(NOW + 7200_000).toISOString());
151
+ });
152
+ (0, node_test_1.default)("Secret ID 规范化后签名和保存,Secret Key 不做破坏性 trim", async (t) => {
153
+ const { files, service } = makeService(t, {
154
+ async check(request) {
155
+ strict_1.default.equal(request.secret_id, "id");
156
+ return {
157
+ code: 0,
158
+ msg: "ok",
159
+ data: {
160
+ secret_id: "id",
161
+ cli_token: "server-session",
162
+ create_at: NOW,
163
+ expire_in_sec: 3600,
164
+ },
165
+ };
166
+ },
167
+ });
168
+ const result = await service.loginWithSecret(" id ", " key-with-significant-spaces ");
169
+ strict_1.default.equal(result.secretId, "id");
170
+ const stored = files.readAuth();
171
+ strict_1.default.equal(stored?.method, "secret");
172
+ if (stored?.method !== "secret")
173
+ throw new Error("预期 Secret 凭据");
174
+ strict_1.default.equal(stored.secretId, "id");
175
+ });
176
+ (0, node_test_1.default)("拒绝服务端返回错配身份或不可表示的有效期", async (t) => {
177
+ const mismatch = makeService(t, {
178
+ async check() {
179
+ return {
180
+ code: 0,
181
+ msg: "ok",
182
+ data: {
183
+ secret_id: "another-id",
184
+ cli_token: "server-session",
185
+ create_at: NOW,
186
+ expire_in_sec: 3600,
187
+ },
188
+ };
189
+ },
190
+ });
191
+ await strict_1.default.rejects(mismatch.service.loginWithSecret("id", "key"), /登录服务返回异常/);
192
+ strict_1.default.equal(mismatch.files.readAuth(), null);
193
+ const overflow = makeService(t, {
194
+ async check(request) {
195
+ return {
196
+ code: 0,
197
+ msg: "ok",
198
+ data: {
199
+ secret_id: request.secret_id,
200
+ cli_token: "server-session",
201
+ create_at: NOW,
202
+ expire_in_sec: Number.MAX_SAFE_INTEGER,
203
+ },
204
+ };
205
+ },
206
+ });
207
+ await strict_1.default.rejects(overflow.service.loginWithSecret("id", "key"), /登录服务返回异常/);
208
+ strict_1.default.equal(overflow.files.readAuth(), null);
209
+ });
210
+ (0, node_test_1.default)("认证接口拒绝非本机 HTTP,且不向用户暴露内部地址规则", async () => {
211
+ let requested = false;
212
+ const api = new secret_auth_1.HttpSecretAuthApi("http://example.com", async () => {
213
+ requested = true;
214
+ throw new Error("不应请求");
215
+ });
216
+ await strict_1.default.rejects(api.check({ secret_id: "id", nonce: "nonce", cli_token: "proof" }), /登录服务暂不可用/);
217
+ strict_1.default.equal(requested, false);
218
+ });
219
+ (0, node_test_1.default)("未配置正式认证接口时返回对外错误,且不暴露内部配置", async () => {
220
+ let requested = false;
221
+ const api = new secret_auth_1.HttpSecretAuthApi(undefined, async () => {
222
+ requested = true;
223
+ throw new Error("不应请求");
224
+ });
225
+ const error = await api
226
+ .check({ secret_id: "id", nonce: "nonce", cli_token: "proof" })
227
+ .then(() => null)
228
+ .catch((reason) => reason);
229
+ strict_1.default.match(String(error), /登录服务暂不可用/);
230
+ strict_1.default.doesNotMatch(String(error), /配置/i);
231
+ strict_1.default.equal(requested, false);
232
+ });
233
+ (0, node_test_1.default)("网络与服务端错误不向终端泄露底层异常或 HTTP 状态", async () => {
234
+ const networkApi = new secret_auth_1.HttpSecretAuthApi("https://example.com/auth", async () => {
235
+ throw new Error("ECONNREFUSED internal-host:8443");
236
+ });
237
+ const networkError = await networkApi
238
+ .check({ secret_id: "id", nonce: "nonce", cli_token: "proof" })
239
+ .then(() => null)
240
+ .catch((reason) => reason);
241
+ strict_1.default.equal(String(networkError), "Error: 登录服务暂不可用,请稍后重试");
242
+ const serverApi = new secret_auth_1.HttpSecretAuthApi("https://example.com/auth", async () => new Response(null, { status: 503 }));
243
+ const serverError = await serverApi
244
+ .check({ secret_id: "id", nonce: "nonce", cli_token: "proof" })
245
+ .then(() => null)
246
+ .catch((reason) => reason);
247
+ strict_1.default.equal(String(serverError), "Error: 登录服务暂不可用,请稍后重试");
248
+ });
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@skillbuilder/minitool-cli-auth",
3
+ "version": "0.0.0-beta-20261027053041051",
4
+ "description": "redminitool credential storage and Secret login lifecycle",
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
+ "@skillbuilder/minitool-cli-config": "0.0.0-beta-20261027053041051"
23
+ },
24
+ "optionalDependencies": {
25
+ "@napi-rs/keyring": "2.0.0"
26
+ },
27
+ "license": "ISC"
28
+ }