toolcraft-openapi 0.0.160 → 0.0.161

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.
@@ -1,6 +1,6 @@
1
1
  import { defineCommand, defineGroup, S, UserError } from "toolcraft";
2
2
  import { isCancel, password } from "toolcraft-design";
3
- import { createSecretStore } from "auth-store";
3
+ import { createSecretStore } from "toolcraft/auth-store";
4
4
  import { requestJson } from "../http.js";
5
5
  const DEFAULT_COMMAND_PREFIX = "auth";
6
6
  const loginParams = S.Object({
@@ -6,11 +6,6 @@
6
6
  "version": "0.0.1",
7
7
  "license": "MIT"
8
8
  },
9
- {
10
- "name": "auth-store",
11
- "version": "0.0.1",
12
- "license": "MIT"
13
- },
14
9
  {
15
10
  "name": "fast-string-truncated-width",
16
11
  "version": "3.0.3",
@@ -38,12 +33,12 @@
38
33
  },
39
34
  {
40
35
  "name": "toolcraft-openapi",
41
- "version": "0.0.160",
36
+ "version": "0.0.161",
42
37
  "license": "MIT"
43
38
  },
44
39
  {
45
40
  "name": "toolcraft-schema",
46
- "version": "0.0.160",
41
+ "version": "0.0.161",
47
42
  "license": "MIT"
48
43
  },
49
44
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.160",
3
+ "version": "0.0.161",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-openapi",
3
- "version": "0.0.160",
3
+ "version": "0.0.161",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -19,8 +19,8 @@
19
19
  "build": "rm -rf dist && tsc",
20
20
  "test": "cd ../.. && vitest run packages/toolcraft-openapi/src",
21
21
  "test:unit": "cd ../.. && vitest run packages/toolcraft-openapi/src",
22
- "prepack": "node ../../scripts/set-bin-executable.mjs && node ../../scripts/manage-bundled-workspace-deps.mjs prepare . toolcraft-schema toolcraft-design @poe-code/frontmatter auth-store fast-string-width fast-wrap-ansi sisteransi yaml",
23
- "postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . toolcraft-schema toolcraft-design @poe-code/frontmatter auth-store fast-string-width fast-wrap-ansi sisteransi yaml"
22
+ "prepack": "node ../../scripts/set-bin-executable.mjs && node ../../scripts/manage-bundled-workspace-deps.mjs prepare . toolcraft-schema toolcraft-design @poe-code/frontmatter fast-string-width fast-wrap-ansi sisteransi yaml",
23
+ "postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . toolcraft-schema toolcraft-design @poe-code/frontmatter fast-string-width fast-wrap-ansi sisteransi yaml"
24
24
  },
25
25
  "files": [
26
26
  "LICENSE",
@@ -30,7 +30,7 @@
30
30
  "toolcraft-openapi-generate": "dist/bin/generate.js"
31
31
  },
32
32
  "dependencies": {
33
- "toolcraft": "0.0.160",
33
+ "toolcraft": "0.0.161",
34
34
  "fast-string-width": "^3.0.2",
35
35
  "fast-wrap-ansi": "^0.2.0",
36
36
  "sisteransi": "^1.0.5",
@@ -45,8 +45,7 @@
45
45
  "directory": "packages/toolcraft-openapi"
46
46
  },
47
47
  "optionalDependencies": {
48
- "auth-store": "*",
49
- "toolcraft-schema": "0.0.160",
48
+ "toolcraft-schema": "0.0.161",
50
49
  "toolcraft-design": "*",
51
50
  "@poe-code/frontmatter": "*"
52
51
  },
@@ -54,7 +53,6 @@
54
53
  "toolcraft-schema",
55
54
  "toolcraft-design",
56
55
  "@poe-code/frontmatter",
57
- "auth-store",
58
56
  "fast-string-width",
59
57
  "fast-wrap-ansi",
60
58
  "sisteransi",
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Poe Platform
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,62 +0,0 @@
1
- # auth-store
2
-
3
- Generic encrypted secret storage with platform-aware backends.
4
-
5
- ## Usage
6
-
7
- ```ts
8
- import { createSecretStore } from "auth-store";
9
-
10
- const { store, backend } = createSecretStore({
11
- backendEnvVar: "MY_AUTH_BACKEND",
12
- fileStore: {
13
- salt: "my-app:encrypted-store:v1",
14
- defaultDirectory: ".my-app",
15
- defaultFileName: "credentials.enc"
16
- },
17
- keychainStore: {
18
- service: "my-app",
19
- account: "api-key"
20
- }
21
- });
22
-
23
- await store.set("secret-value");
24
- const value = await store.get(); // "secret-value"
25
- await store.delete();
26
- ```
27
-
28
- ## Backends
29
-
30
- | `backendEnvVar` value | Platform | Backend |
31
- | --------------------- | -------- | -------------- |
32
- | _(unset)_ | any | Encrypted file |
33
- | `file` | any | Encrypted file |
34
- | `keychain` | macOS | macOS Keychain |
35
- | `keychain` | other | Error |
36
-
37
- ### Encrypted file
38
-
39
- - AES-256-GCM with machine-derived key (hostname + username via scrypt)
40
- - Configurable salt, directory, and file name
41
- - File permissions: `0600`
42
- - Random IV per write
43
-
44
- ### macOS Keychain
45
-
46
- - Uses the `security` CLI (`add-generic-password`, `find-generic-password`, `delete-generic-password`)
47
- - Configurable service and account names
48
-
49
- ## Configuration Options
50
-
51
- `createSecretStore()` accepts:
52
-
53
- - `backendEnvVar`: optional env var name that selects `file` or `keychain`.
54
- - `fileStore.salt`: application-specific encryption salt.
55
- - `fileStore.defaultDirectory`: default credential directory.
56
- - `fileStore.defaultFileName`: encrypted file name.
57
- - `keychainStore.service`: macOS Keychain service name.
58
- - `keychainStore.account`: macOS Keychain account name.
59
-
60
- ## Environment Variables
61
-
62
- This package reads only the caller-selected `backendEnvVar`. It does not define a fixed public env var.
@@ -1,2 +0,0 @@
1
- import type { CreateSecretStoreInput, CreateSecretStoreResult } from "./types.js";
2
- export declare function createSecretStore(input: CreateSecretStoreInput): CreateSecretStoreResult;
@@ -1,44 +0,0 @@
1
- import { EncryptedFileStore } from "./encrypted-file-store.js";
2
- import { KeychainStore } from "./keychain-store.js";
3
- const DEFAULT_BACKEND_ENV_VAR = "AUTH_BACKEND";
4
- const MACOS_PLATFORM = "darwin";
5
- const storeFactories = {
6
- file: (input) => {
7
- if (!input.fileStore) {
8
- throw new Error("fileStore configuration is required for file backend");
9
- }
10
- return new EncryptedFileStore(input.fileStore);
11
- },
12
- keychain: (input) => {
13
- if (!input.keychainStore) {
14
- throw new Error("keychainStore configuration is required for keychain backend");
15
- }
16
- return new KeychainStore(input.keychainStore);
17
- }
18
- };
19
- export function createSecretStore(input) {
20
- const backend = resolveBackend(input);
21
- const platform = input.platform ?? process.platform;
22
- if (backend === "keychain" && platform !== MACOS_PLATFORM) {
23
- throw new Error(`Keychain backend is only supported on macOS. Current platform: ${platform}`);
24
- }
25
- const store = storeFactories[backend](input);
26
- return { backend, store };
27
- }
28
- function resolveBackend(input) {
29
- const envVar = input.backendEnvVar ?? DEFAULT_BACKEND_ENV_VAR;
30
- const configuredBackend = input.backend ?? getOwnEnvValue(input.env, envVar) ?? getOwnEnvValue(process.env, envVar);
31
- const backend = configuredBackend?.trim();
32
- if (backend === "keychain") {
33
- return "keychain";
34
- }
35
- if (backend === undefined || backend === "file") {
36
- return "file";
37
- }
38
- throw new Error(`Unsupported auth store backend: ${backend}`);
39
- }
40
- function getOwnEnvValue(env, key) {
41
- return env !== undefined && Object.prototype.hasOwnProperty.call(env, key)
42
- ? env[key]
43
- : undefined;
44
- }
@@ -1,47 +0,0 @@
1
- import type { SecretStore } from "./types.js";
2
- export interface MachineIdentity {
3
- hostname: string;
4
- username: string;
5
- }
6
- export interface EncryptedFileStoreFileSystem {
7
- readFile(path: string, encoding: BufferEncoding): Promise<string>;
8
- writeFile(path: string, data: string | NodeJS.ArrayBufferView, options?: {
9
- encoding?: BufferEncoding;
10
- flag?: string;
11
- mode?: number;
12
- }): Promise<void>;
13
- mkdir(path: string, options?: {
14
- recursive?: boolean;
15
- }): Promise<void | string | undefined>;
16
- rename(oldPath: string, newPath: string): Promise<void>;
17
- lstat(path: string): Promise<{
18
- isSymbolicLink(): boolean;
19
- }>;
20
- unlink(path: string): Promise<void>;
21
- chmod(path: string, mode: number): Promise<void>;
22
- }
23
- export interface EncryptedFileStoreInput {
24
- fs?: EncryptedFileStoreFileSystem;
25
- filePath?: string;
26
- salt: string;
27
- defaultDirectory?: string;
28
- defaultFileName?: string;
29
- getMachineIdentity?: () => MachineIdentity | Promise<MachineIdentity>;
30
- getHomeDirectory?: () => string;
31
- getRandomBytes?: (size: number) => Buffer;
32
- }
33
- export declare class EncryptedFileStore implements SecretStore {
34
- private readonly fs;
35
- private readonly filePath;
36
- private readonly symbolicLinkCheckStartPath;
37
- private readonly salt;
38
- private readonly getMachineIdentity;
39
- private readonly getRandomBytes;
40
- private keyPromise;
41
- constructor(input: EncryptedFileStoreInput);
42
- get(): Promise<string | null>;
43
- set(value: string): Promise<void>;
44
- delete(): Promise<void>;
45
- private assertCredentialPathHasNoSymbolicLinks;
46
- private getEncryptionKey;
47
- }
@@ -1,303 +0,0 @@
1
- import { createCipheriv, createDecipheriv, randomBytes, randomUUID, scrypt } from "node:crypto";
2
- import { promises as fs } from "node:fs";
3
- import { homedir, hostname, userInfo } from "node:os";
4
- import path from "node:path";
5
- import { hasOwnErrorCode } from "./error-codes.js";
6
- const derivedKeyCache = new Map();
7
- const ENCRYPTION_ALGORITHM = "aes-256-gcm";
8
- const ENCRYPTION_VERSION = 1;
9
- const ENCRYPTION_KEY_BYTES = 32;
10
- const ENCRYPTION_IV_BYTES = 12;
11
- const ENCRYPTION_AUTH_TAG_BYTES = 16;
12
- const ENCRYPTION_FILE_MODE = 0o600;
13
- export class EncryptedFileStore {
14
- fs;
15
- filePath;
16
- symbolicLinkCheckStartPath;
17
- salt;
18
- getMachineIdentity;
19
- getRandomBytes;
20
- keyPromise = null;
21
- constructor(input) {
22
- this.fs = input.fs ?? fs;
23
- this.salt = input.salt;
24
- if (input.filePath === undefined) {
25
- const homeDirectory = (input.getHomeDirectory ?? homedir)();
26
- const defaultDirectory = input.defaultDirectory ?? ".auth-store";
27
- const defaultFileName = input.defaultFileName ?? "credentials.enc";
28
- assertSafeDefaultDirectory(defaultDirectory);
29
- assertSafeDefaultFileName(defaultFileName);
30
- this.filePath = path.join(homeDirectory, defaultDirectory, defaultFileName);
31
- this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
32
- }
33
- else {
34
- this.filePath = input.filePath;
35
- this.symbolicLinkCheckStartPath = null;
36
- }
37
- this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
38
- this.getRandomBytes = input.getRandomBytes ?? randomBytes;
39
- }
40
- async get() {
41
- await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
42
- let rawDocument;
43
- try {
44
- rawDocument = await this.fs.readFile(this.filePath, "utf8");
45
- }
46
- catch (error) {
47
- if (isNotFoundError(error)) {
48
- return null;
49
- }
50
- throw error;
51
- }
52
- const document = parseEncryptedDocument(rawDocument);
53
- if (!document) {
54
- return null;
55
- }
56
- const key = await this.getEncryptionKey();
57
- try {
58
- const iv = Buffer.from(document.iv, "base64");
59
- const authTag = Buffer.from(document.authTag, "base64");
60
- const ciphertext = Buffer.from(document.ciphertext, "base64");
61
- if (iv.byteLength !== ENCRYPTION_IV_BYTES ||
62
- authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
63
- return null;
64
- }
65
- const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
66
- decipher.setAuthTag(authTag);
67
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
68
- return plaintext.toString("utf8");
69
- }
70
- catch {
71
- return null;
72
- }
73
- }
74
- async set(value) {
75
- await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
76
- const key = await this.getEncryptionKey();
77
- const iv = this.getRandomBytes(ENCRYPTION_IV_BYTES);
78
- const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
79
- const ciphertext = Buffer.concat([
80
- cipher.update(value, "utf8"),
81
- cipher.final()
82
- ]);
83
- const authTag = cipher.getAuthTag();
84
- const document = {
85
- version: ENCRYPTION_VERSION,
86
- iv: iv.toString("base64"),
87
- authTag: authTag.toString("base64"),
88
- ciphertext: ciphertext.toString("base64")
89
- };
90
- await this.fs.mkdir(path.dirname(this.filePath), { recursive: true });
91
- await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
92
- const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`;
93
- let temporaryCreated = false;
94
- try {
95
- await this.assertCredentialPathHasNoSymbolicLinks(temporaryPath);
96
- await this.fs.writeFile(temporaryPath, JSON.stringify(document), {
97
- encoding: "utf8",
98
- flag: "wx",
99
- mode: ENCRYPTION_FILE_MODE
100
- });
101
- temporaryCreated = true;
102
- await this.fs.chmod(temporaryPath, ENCRYPTION_FILE_MODE);
103
- await this.fs.rename(temporaryPath, this.filePath);
104
- }
105
- catch (error) {
106
- if (temporaryCreated || !isAlreadyExistsError(error)) {
107
- await removeIfPresent(this.fs, temporaryPath).catch(() => undefined);
108
- }
109
- throw error;
110
- }
111
- }
112
- async delete() {
113
- await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
114
- try {
115
- await this.fs.unlink(this.filePath);
116
- }
117
- catch (error) {
118
- if (!isNotFoundError(error)) {
119
- throw error;
120
- }
121
- }
122
- }
123
- async assertCredentialPathHasNoSymbolicLinks(targetPath) {
124
- const resolvedPath = path.resolve(targetPath);
125
- const protectedPaths = getProtectedCredentialPaths(resolvedPath, this.symbolicLinkCheckStartPath);
126
- for (const currentPath of protectedPaths) {
127
- try {
128
- const stats = await this.fs.lstat(currentPath);
129
- if (stats.isSymbolicLink()) {
130
- throw new Error(`Refusing to use encrypted credential path through symbolic link: ${currentPath}`);
131
- }
132
- }
133
- catch (error) {
134
- if (isNotFoundError(error)) {
135
- return;
136
- }
137
- throw error;
138
- }
139
- }
140
- }
141
- getEncryptionKey() {
142
- if (!this.keyPromise) {
143
- const retryableKeyPromise = deriveEncryptionKey(this.getMachineIdentity, this.salt).catch((error) => {
144
- if (this.keyPromise === retryableKeyPromise) {
145
- this.keyPromise = null;
146
- }
147
- throw error;
148
- });
149
- this.keyPromise = retryableKeyPromise;
150
- }
151
- return this.keyPromise;
152
- }
153
- }
154
- function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
155
- const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
156
- return path.resolve(homeDirectory, firstSegment ?? ".");
157
- }
158
- function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
159
- if (symbolicLinkCheckStartPath === null) {
160
- return getExplicitProtectedCredentialPaths(resolvedPath);
161
- }
162
- const resolvedStartPath = path.resolve(symbolicLinkCheckStartPath);
163
- if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
164
- return [path.dirname(resolvedPath), resolvedPath];
165
- }
166
- const protectedPaths = [resolvedStartPath];
167
- let currentPath = resolvedStartPath;
168
- for (const segment of path.relative(resolvedStartPath, resolvedPath).split(path.sep).filter(Boolean)) {
169
- currentPath = path.join(currentPath, segment);
170
- protectedPaths.push(currentPath);
171
- }
172
- return protectedPaths;
173
- }
174
- function assertSafeDefaultDirectory(defaultDirectory) {
175
- if (path.isAbsolute(defaultDirectory) || path.win32.isAbsolute(defaultDirectory)) {
176
- throw new Error("defaultDirectory must be a relative path inside the home directory");
177
- }
178
- for (const segment of splitPathSegments(defaultDirectory)) {
179
- if (segment === "..") {
180
- throw new Error("defaultDirectory must be a relative path inside the home directory");
181
- }
182
- }
183
- }
184
- function assertSafeDefaultFileName(defaultFileName) {
185
- if (defaultFileName.trim().length === 0 ||
186
- defaultFileName === "." ||
187
- defaultFileName === ".." ||
188
- splitPathSegments(defaultFileName).length !== 1) {
189
- throw new Error("defaultFileName must be a file name without path separators");
190
- }
191
- }
192
- function splitPathSegments(value) {
193
- return value
194
- .split("/")
195
- .flatMap((segment) => segment.split("\\"))
196
- .filter((segment) => segment.length > 0);
197
- }
198
- function getExplicitProtectedCredentialPaths(resolvedPath) {
199
- const parsed = path.parse(resolvedPath);
200
- const segments = resolvedPath
201
- .slice(parsed.root.length)
202
- .split(path.sep)
203
- .filter((segment) => segment.length > 0);
204
- if (segments.length <= 1) {
205
- return [resolvedPath];
206
- }
207
- const protectedPaths = [];
208
- let currentPath = parsed.root;
209
- for (const [index, segment] of segments.entries()) {
210
- currentPath = path.join(currentPath, segment);
211
- if (index === 0) {
212
- continue;
213
- }
214
- protectedPaths.push(currentPath);
215
- }
216
- return protectedPaths;
217
- }
218
- function isPathInsideOrEqual(childPath, parentPath) {
219
- const relativePath = path.relative(parentPath, childPath);
220
- return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
221
- }
222
- async function removeIfPresent(fileSystem, filePath) {
223
- try {
224
- await fileSystem.unlink(filePath);
225
- }
226
- catch (error) {
227
- if (!isNotFoundError(error)) {
228
- throw error;
229
- }
230
- }
231
- }
232
- function defaultMachineIdentity() {
233
- return {
234
- hostname: hostname(),
235
- username: userInfo().username
236
- };
237
- }
238
- async function deriveEncryptionKey(getMachineIdentity, salt) {
239
- const machineIdentity = await getMachineIdentity();
240
- const secret = `${machineIdentity.hostname}:${machineIdentity.username}`;
241
- const cacheKey = JSON.stringify([machineIdentity.hostname, machineIdentity.username, salt]);
242
- const cached = derivedKeyCache.get(cacheKey);
243
- if (cached) {
244
- return cached;
245
- }
246
- const keyPromise = new Promise((resolve, reject) => {
247
- scrypt(secret, salt, ENCRYPTION_KEY_BYTES, (error, derivedKey) => {
248
- if (error) {
249
- reject(error);
250
- return;
251
- }
252
- resolve(Buffer.from(derivedKey));
253
- });
254
- });
255
- derivedKeyCache.set(cacheKey, keyPromise);
256
- return keyPromise.catch((error) => {
257
- if (derivedKeyCache.get(cacheKey) === keyPromise) {
258
- derivedKeyCache.delete(cacheKey);
259
- }
260
- throw error;
261
- });
262
- }
263
- function parseEncryptedDocument(raw) {
264
- try {
265
- const parsed = JSON.parse(raw);
266
- if (!isRecord(parsed)) {
267
- return null;
268
- }
269
- const version = getOwnEntry(parsed, "version");
270
- const iv = getOwnEntry(parsed, "iv");
271
- const authTag = getOwnEntry(parsed, "authTag");
272
- const ciphertext = getOwnEntry(parsed, "ciphertext");
273
- if (version !== ENCRYPTION_VERSION) {
274
- return null;
275
- }
276
- if (typeof iv !== "string" ||
277
- typeof authTag !== "string" ||
278
- typeof ciphertext !== "string") {
279
- return null;
280
- }
281
- return {
282
- version,
283
- iv,
284
- authTag,
285
- ciphertext
286
- };
287
- }
288
- catch {
289
- return null;
290
- }
291
- }
292
- function isRecord(value) {
293
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
294
- }
295
- function getOwnEntry(record, key) {
296
- return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;
297
- }
298
- function isNotFoundError(error) {
299
- return hasOwnErrorCode(error, "ENOENT");
300
- }
301
- function isAlreadyExistsError(error) {
302
- return hasOwnErrorCode(error, "EEXIST");
303
- }
@@ -1 +0,0 @@
1
- export declare function hasOwnErrorCode(error: unknown, code: string): boolean;
@@ -1,5 +0,0 @@
1
- export function hasOwnErrorCode(error, code) {
2
- return (error instanceof Error &&
3
- Object.prototype.hasOwnProperty.call(error, "code") &&
4
- error.code === code);
5
- }
@@ -1,7 +0,0 @@
1
- export { createSecretStore } from "./create-secret-store.js";
2
- export { EncryptedFileStore } from "./encrypted-file-store.js";
3
- export { KeychainStore } from "./keychain-store.js";
4
- export { key, MigratingSecretStore } from "./provider-store.js";
5
- export type { SecretStore, StoreBackend, CreateSecretStoreInput, CreateSecretStoreResult } from "./types.js";
6
- export type { MachineIdentity, EncryptedFileStoreInput, EncryptedFileStoreFileSystem } from "./encrypted-file-store.js";
7
- export type { KeychainStoreInput, KeychainCommandRunner, KeychainCommandResult, KeychainCommandOptions } from "./keychain-store.js";
@@ -1,4 +0,0 @@
1
- export { createSecretStore } from "./create-secret-store.js";
2
- export { EncryptedFileStore } from "./encrypted-file-store.js";
3
- export { KeychainStore } from "./keychain-store.js";
4
- export { key, MigratingSecretStore } from "./provider-store.js";
@@ -1,25 +0,0 @@
1
- import type { SecretStore } from "./types.js";
2
- export interface KeychainCommandResult {
3
- stdout: string;
4
- stderr: string;
5
- exitCode: number;
6
- }
7
- export interface KeychainCommandOptions {
8
- stdin?: string;
9
- }
10
- export type KeychainCommandRunner = (command: string, args: string[], options?: KeychainCommandOptions) => Promise<KeychainCommandResult>;
11
- export interface KeychainStoreInput {
12
- runCommand?: KeychainCommandRunner;
13
- service: string;
14
- account: string;
15
- }
16
- export declare class KeychainStore implements SecretStore {
17
- private readonly runCommand;
18
- private readonly service;
19
- private readonly account;
20
- constructor(input: KeychainStoreInput);
21
- get(): Promise<string | null>;
22
- set(value: string): Promise<void>;
23
- delete(): Promise<void>;
24
- private executeSecurityCommand;
25
- }
@@ -1,154 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- const SECURITY_CLI = "security";
3
- const KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
4
- export class KeychainStore {
5
- runCommand;
6
- service;
7
- account;
8
- constructor(input) {
9
- this.runCommand = input.runCommand ?? runSecurityCommand;
10
- this.service = input.service.trim();
11
- this.account = input.account.trim();
12
- if (this.service.length === 0) {
13
- throw new Error("Keychain service must not be empty");
14
- }
15
- if (this.account.length === 0) {
16
- throw new Error("Keychain account must not be empty");
17
- }
18
- }
19
- async get() {
20
- const result = await this.executeSecurityCommand(["find-generic-password", "-s", this.service, "-a", this.account, "-w"], "read secret from macOS Keychain");
21
- if (getCommandExitCode(result) === 0) {
22
- return stripTrailingLineBreak(getCommandOutput(result, "stdout"));
23
- }
24
- if (isKeychainEntryNotFound(result)) {
25
- return null;
26
- }
27
- throw createSecurityCliFailure("read secret from macOS Keychain", result);
28
- }
29
- async set(value) {
30
- if (value.includes("\n") || value.includes("\r")) {
31
- throw new Error("Keychain secrets cannot contain line breaks");
32
- }
33
- const result = await this.executeSecurityCommand([
34
- "add-generic-password",
35
- "-s",
36
- this.service,
37
- "-a",
38
- this.account,
39
- "-U",
40
- "-w",
41
- value
42
- ], "store secret in macOS Keychain");
43
- if (getCommandExitCode(result) !== 0) {
44
- throw createSecurityCliFailure("store secret in macOS Keychain", result);
45
- }
46
- }
47
- async delete() {
48
- const result = await this.executeSecurityCommand(["delete-generic-password", "-s", this.service, "-a", this.account], "delete secret from macOS Keychain");
49
- if (getCommandExitCode(result) === 0 || isKeychainEntryNotFound(result)) {
50
- return;
51
- }
52
- throw createSecurityCliFailure("delete secret from macOS Keychain", result);
53
- }
54
- async executeSecurityCommand(args, operation, options) {
55
- try {
56
- if (options === undefined) {
57
- return await this.runCommand(SECURITY_CLI, args);
58
- }
59
- return await this.runCommand(SECURITY_CLI, args, options);
60
- }
61
- catch (error) {
62
- const message = error instanceof Error ? error.message : String(error);
63
- throw new Error(`Failed to ${operation}: ${message}`);
64
- }
65
- }
66
- }
67
- function runSecurityCommand(command, args, options) {
68
- return new Promise((resolve) => {
69
- const child = spawn(command, args, {
70
- stdio: [options?.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
71
- });
72
- let stdout = "";
73
- let stderr = "";
74
- let stdinErrorMessage;
75
- const appendStderr = (message) => {
76
- stderr = stderr.length === 0
77
- ? message
78
- : `${stderr}${stderr.endsWith("\n") ? "" : "\n"}${message}`;
79
- };
80
- const appendStdinError = () => {
81
- if (stdinErrorMessage === undefined) {
82
- return;
83
- }
84
- appendStderr(stdinErrorMessage);
85
- stdinErrorMessage = undefined;
86
- };
87
- child.stdout?.setEncoding("utf8");
88
- child.stdout?.on("data", (chunk) => {
89
- stdout += chunk.toString();
90
- });
91
- child.stderr?.setEncoding("utf8");
92
- child.stderr?.on("data", (chunk) => {
93
- stderr += chunk.toString();
94
- });
95
- if (options?.stdin !== undefined) {
96
- child.stdin?.once("error", (error) => {
97
- stdinErrorMessage = error instanceof Error ? error.message : String(error);
98
- });
99
- child.stdin?.end(options.stdin);
100
- }
101
- child.on("error", (error) => {
102
- const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
103
- appendStdinError();
104
- appendStderr(message);
105
- resolve({
106
- stdout,
107
- stderr,
108
- exitCode: 127
109
- });
110
- });
111
- child.on("close", (code) => {
112
- appendStdinError();
113
- resolve({
114
- stdout,
115
- stderr,
116
- exitCode: code ?? 1
117
- });
118
- });
119
- });
120
- }
121
- function stripTrailingLineBreak(value) {
122
- if (value.endsWith("\r\n")) {
123
- return value.slice(0, -2);
124
- }
125
- if (value.endsWith("\n") || value.endsWith("\r")) {
126
- return value.slice(0, -1);
127
- }
128
- return value;
129
- }
130
- function isKeychainEntryNotFound(result) {
131
- return getCommandExitCode(result) === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE;
132
- }
133
- function createSecurityCliFailure(operation, result) {
134
- const exitCode = getCommandExitCode(result);
135
- const details = getCommandOutput(result, "stderr").trim()
136
- || getCommandOutput(result, "stdout").trim();
137
- if (details) {
138
- return new Error(`Failed to ${operation}: security exited with code ${exitCode}: ${details}`);
139
- }
140
- return new Error(`Failed to ${operation}: security exited with code ${exitCode}`);
141
- }
142
- function getCommandExitCode(result) {
143
- const value = getOwnEntry(result, "exitCode");
144
- return typeof value === "number" && Number.isInteger(value) ? value : 1;
145
- }
146
- function getCommandOutput(result, key) {
147
- const value = getOwnEntry(result, key);
148
- return typeof value === "string" ? value : "";
149
- }
150
- function getOwnEntry(record, key) {
151
- return Object.prototype.hasOwnProperty.call(record, key)
152
- ? record[key]
153
- : undefined;
154
- }
@@ -1,14 +0,0 @@
1
- import type { SecretStore } from "./types.js";
2
- export declare function key(providerId: string): string;
3
- export declare class MigratingSecretStore implements SecretStore {
4
- private readonly store;
5
- private readonly legacyStore;
6
- private pendingMutation;
7
- constructor(store: SecretStore, legacyStore?: SecretStore | null);
8
- get(options?: {
9
- readOnly?: boolean;
10
- }): Promise<string | null>;
11
- set(value: string): Promise<void>;
12
- delete(): Promise<void>;
13
- private mutate;
14
- }
@@ -1,78 +0,0 @@
1
- export function key(providerId) {
2
- return `provider:${providerId}`;
3
- }
4
- export class MigratingSecretStore {
5
- store;
6
- legacyStore;
7
- pendingMutation = Promise.resolve();
8
- constructor(store, legacyStore = null) {
9
- this.store = store;
10
- this.legacyStore = legacyStore;
11
- }
12
- async get(options = {}) {
13
- const value = await this.store.get();
14
- if (value !== null || !this.legacyStore) {
15
- return value;
16
- }
17
- const legacyValue = await this.legacyStore.get();
18
- if (legacyValue !== null && !options.readOnly) {
19
- await this.mutate(async () => {
20
- if (await this.store.get() === null) {
21
- try {
22
- await this.store.set(legacyValue);
23
- }
24
- catch {
25
- return;
26
- }
27
- }
28
- });
29
- }
30
- return legacyValue;
31
- }
32
- async set(value) {
33
- await this.mutate(async () => {
34
- const previousValue = await this.store.get();
35
- const previousLegacyValue = await this.legacyStore?.get() ?? null;
36
- await this.store.set(value);
37
- try {
38
- await this.legacyStore?.set(value);
39
- }
40
- catch (error) {
41
- await restore(this.store, previousValue);
42
- if (this.legacyStore) {
43
- await restore(this.legacyStore, previousLegacyValue);
44
- }
45
- throw error;
46
- }
47
- });
48
- }
49
- async delete() {
50
- await this.mutate(async () => {
51
- const previousValue = await this.store.get();
52
- const previousLegacyValue = await this.legacyStore?.get() ?? null;
53
- await this.store.delete();
54
- try {
55
- await this.legacyStore?.delete();
56
- }
57
- catch (error) {
58
- await restore(this.store, previousValue);
59
- if (this.legacyStore) {
60
- await restore(this.legacyStore, previousLegacyValue);
61
- }
62
- throw error;
63
- }
64
- });
65
- }
66
- async mutate(action) {
67
- const operation = this.pendingMutation.then(action, action);
68
- this.pendingMutation = operation.catch(() => undefined);
69
- await operation;
70
- }
71
- }
72
- async function restore(store, value) {
73
- if (value === null) {
74
- await store.delete();
75
- return;
76
- }
77
- await store.set(value);
78
- }
@@ -1,22 +0,0 @@
1
- import type { EncryptedFileStoreInput } from "./encrypted-file-store.js";
2
- import type { KeychainStoreInput } from "./keychain-store.js";
3
- export interface SecretStore {
4
- get(options?: {
5
- readOnly?: boolean;
6
- }): Promise<string | null>;
7
- set(value: string): Promise<void>;
8
- delete(): Promise<void>;
9
- }
10
- export type StoreBackend = "file" | "keychain";
11
- export interface CreateSecretStoreInput {
12
- backend?: StoreBackend;
13
- env?: NodeJS.ProcessEnv;
14
- platform?: NodeJS.Platform;
15
- backendEnvVar?: string;
16
- fileStore?: EncryptedFileStoreInput;
17
- keychainStore?: KeychainStoreInput;
18
- }
19
- export interface CreateSecretStoreResult {
20
- store: SecretStore;
21
- backend: StoreBackend;
22
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,25 +0,0 @@
1
- {
2
- "private": true,
3
- "license": "MIT",
4
- "type": "module",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "types": "./dist/index.d.ts",
10
- "import": "./dist/index.js"
11
- }
12
- },
13
- "scripts": {
14
- "build": "node ../../scripts/guard-package-dist.mjs && tsc"
15
- },
16
- "files": [
17
- "dist",
18
- "LICENSE"
19
- ],
20
- "repository": {
21
- "type": "git",
22
- "url": "git+https://github.com/poe-platform/poe-code.git",
23
- "directory": "packages/auth-store"
24
- }
25
- }