@forgezero/vault 0.1.23 → 0.1.25
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/dist/index.d.ts +19 -2
- package/dist/index.js +74 -6
- package/dist/providers.js +73 -6
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +2 -2
- package/dist/schema.js +73 -6
- package/package.json +5 -3
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export declare class VaultError extends Error {
|
|
|
35
35
|
readonly code: string;
|
|
36
36
|
constructor(code: string, message: string);
|
|
37
37
|
}
|
|
38
|
-
export type CredentialMode = 'managed' | 'external';
|
|
38
|
+
export type CredentialMode = 'managed' | 'external' | 'local';
|
|
39
39
|
export interface Credential {
|
|
40
40
|
mode: CredentialMode;
|
|
41
41
|
/** MANAGED only — the agent socket serving the environment's RAM replica. */
|
|
@@ -43,6 +43,11 @@ export interface Credential {
|
|
|
43
43
|
/** EXTERNAL only — a seed, never transmitted; a keypair derives from it. */
|
|
44
44
|
apiKey?: string;
|
|
45
45
|
}
|
|
46
|
+
export interface LocalVaultStorage {
|
|
47
|
+
getItem(key: string): string | null;
|
|
48
|
+
setItem(key: string, value: string): void;
|
|
49
|
+
removeItem(key: string): void;
|
|
50
|
+
}
|
|
46
51
|
export interface DiscoveryEnvironment {
|
|
47
52
|
env?: Record<string, string | undefined>;
|
|
48
53
|
socketPath?: string;
|
|
@@ -142,6 +147,10 @@ export interface VaultOptions {
|
|
|
142
147
|
runtimeRequest?: RuntimeRequest;
|
|
143
148
|
credential?: Credential;
|
|
144
149
|
discovery?: DiscoveryEnvironment;
|
|
150
|
+
/** Browser-compatible persistence used only by explicit `local` development mode. */
|
|
151
|
+
localStorage?: LocalVaultStorage;
|
|
152
|
+
/** Separates multiple local applications that intentionally share one Storage implementation. */
|
|
153
|
+
localNamespace?: string;
|
|
145
154
|
}
|
|
146
155
|
export interface EntryMeta {
|
|
147
156
|
name: string;
|
|
@@ -168,9 +177,15 @@ export declare class ForgeZero {
|
|
|
168
177
|
private readonly requestTimeoutMs;
|
|
169
178
|
private readonly project;
|
|
170
179
|
private readonly environment;
|
|
180
|
+
private readonly localStorage?;
|
|
181
|
+
private readonly localNamespace;
|
|
171
182
|
constructor(options?: VaultOptions);
|
|
172
183
|
private scope;
|
|
173
184
|
private request;
|
|
185
|
+
private localStorageKey;
|
|
186
|
+
private localValues;
|
|
187
|
+
private saveLocalValues;
|
|
188
|
+
private localRequest;
|
|
174
189
|
private managedRequest;
|
|
175
190
|
/** One value, current version unless asked otherwise. */
|
|
176
191
|
get(name: string, options?: {
|
|
@@ -258,6 +273,8 @@ export declare class ForgeZero {
|
|
|
258
273
|
* constructor that happens to do I/O-adjacent work.
|
|
259
274
|
*/
|
|
260
275
|
export declare const createVault: (options?: VaultOptions) => ForgeZero;
|
|
276
|
+
/** Explicit isolated laptop/browser development. This mode never performs HTTP. */
|
|
277
|
+
export declare const createLocalVault: (options?: Omit<VaultOptions, "credential">) => ForgeZero;
|
|
261
278
|
export declare function vaultCredentials(vault: ForgeZero): {
|
|
262
279
|
readonly name: string;
|
|
263
280
|
get(reference: string, field: string): Promise<string>;
|
|
@@ -296,4 +313,4 @@ export declare function systemdCredentials(options?: SystemdCredentialOptions):
|
|
|
296
313
|
readonly name: 'systemd';
|
|
297
314
|
get(reference: string, field: string): Promise<string>;
|
|
298
315
|
};
|
|
299
|
-
export declare const VERSION = "0.1.
|
|
316
|
+
export declare const VERSION = "0.1.25";
|
package/dist/index.js
CHANGED
|
@@ -22,8 +22,8 @@ class VaultRuntime {
|
|
|
22
22
|
this.transport = transport;
|
|
23
23
|
}
|
|
24
24
|
available() {
|
|
25
|
-
if (this.transport === "managed-read-only") {
|
|
26
|
-
throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
|
|
25
|
+
if (this.transport === "managed-read-only" || this.transport === "local-read-only") {
|
|
26
|
+
throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", this.transport === "local-read-only" ? "Local Vault mode stores development values only; master-seed runtime operations require a separately granted live API key." : "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
async create(options) {
|
|
@@ -156,7 +156,9 @@ function discover(environment = {}) {
|
|
|
156
156
|
const apiKey = env.FORGEZERO_API_KEY;
|
|
157
157
|
if (apiKey)
|
|
158
158
|
return { mode: "external", apiKey };
|
|
159
|
-
|
|
159
|
+
if (env.FORGEZERO_LOCAL_VAULT === "1")
|
|
160
|
+
return { mode: "local" };
|
|
161
|
+
throw new VaultError("NO_CREDENTIAL", `No credential found. On managed compute the agent socket at ${socketPath} provides one; ` + "elsewhere set FORGEZERO_API_KEY, or explicitly set FORGEZERO_LOCAL_VAULT=1 for isolated local development.");
|
|
160
162
|
}
|
|
161
163
|
var unbase64url = (encoded) => {
|
|
162
164
|
const padded = encoded.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - encoded.length % 4) % 4);
|
|
@@ -264,6 +266,8 @@ class ForgeZero {
|
|
|
264
266
|
requestTimeoutMs;
|
|
265
267
|
project;
|
|
266
268
|
environment;
|
|
269
|
+
localStorage;
|
|
270
|
+
localNamespace;
|
|
267
271
|
constructor(options = {}) {
|
|
268
272
|
const credential = options.credential ?? discover(options.discovery);
|
|
269
273
|
this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
|
|
@@ -277,8 +281,13 @@ class ForgeZero {
|
|
|
277
281
|
}
|
|
278
282
|
this.project = options.project ?? "default";
|
|
279
283
|
this.environment = options.environment ?? "production";
|
|
284
|
+
this.localStorage = options.localStorage ?? (this.credential.mode === "local" && typeof globalThis.localStorage !== "undefined" ? globalThis.localStorage : undefined);
|
|
285
|
+
this.localNamespace = options.localNamespace?.trim() || "fz.local.vault.v1";
|
|
286
|
+
if (this.credential.mode === "local" && !this.localStorage) {
|
|
287
|
+
throw new VaultError("LOCAL_STORAGE_UNAVAILABLE", "Local Vault mode requires browser localStorage or an injected LocalVaultStorage adapter.");
|
|
288
|
+
}
|
|
280
289
|
const runtimeRequest = options.runtimeRequest ?? ((path, init) => this.request(`/v1/vault/${this.scope()}/runtime${path}`, init));
|
|
281
|
-
this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : "managed-read-only");
|
|
290
|
+
this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : this.credential.mode === "local" ? "local-read-only" : "managed-read-only");
|
|
282
291
|
}
|
|
283
292
|
scope() {
|
|
284
293
|
return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
|
|
@@ -286,6 +295,8 @@ class ForgeZero {
|
|
|
286
295
|
async request(path, init = {}) {
|
|
287
296
|
if (this.credential.mode === "managed")
|
|
288
297
|
return this.managedRequest(path, init);
|
|
298
|
+
if (this.credential.mode === "local")
|
|
299
|
+
return this.localRequest(path, init);
|
|
289
300
|
const body = typeof init.body === "string" ? init.body : "";
|
|
290
301
|
const target = new URL(path, this.apiUrl);
|
|
291
302
|
const headers = {
|
|
@@ -326,6 +337,46 @@ class ForgeZero {
|
|
|
326
337
|
}
|
|
327
338
|
return payload;
|
|
328
339
|
}
|
|
340
|
+
localStorageKey() {
|
|
341
|
+
return `${this.localNamespace}:${encodeURIComponent(this.project)}:${encodeURIComponent(this.environment)}`;
|
|
342
|
+
}
|
|
343
|
+
localValues() {
|
|
344
|
+
const raw = this.localStorage.getItem(this.localStorageKey());
|
|
345
|
+
if (raw === null)
|
|
346
|
+
return {};
|
|
347
|
+
try {
|
|
348
|
+
const parsed = JSON.parse(raw);
|
|
349
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || Object.entries(parsed).some(([key, value]) => !key || typeof value !== "string"))
|
|
350
|
+
throw new Error("invalid");
|
|
351
|
+
return parsed;
|
|
352
|
+
} catch {
|
|
353
|
+
throw new VaultError("LOCAL_STORE_MALFORMED", "The local Vault store is malformed. Clear it before continuing.");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
saveLocalValues(values) {
|
|
357
|
+
this.localStorage.setItem(this.localStorageKey(), JSON.stringify(values));
|
|
358
|
+
}
|
|
359
|
+
async localRequest(path, init) {
|
|
360
|
+
const method = init.method ?? "GET";
|
|
361
|
+
const target = new URL(path, "https://vault.local.invalid");
|
|
362
|
+
const values = this.localValues();
|
|
363
|
+
const entry = /\/entries\/([^/]+)$/.exec(target.pathname);
|
|
364
|
+
if (method === "GET" && entry) {
|
|
365
|
+
const name = decodeURIComponent(entry[1]);
|
|
366
|
+
if (!(name in values))
|
|
367
|
+
throw new VaultError("NOT_FOUND", `No local Vault value named ${name}.`);
|
|
368
|
+
return { value: values[name] };
|
|
369
|
+
}
|
|
370
|
+
if (method === "GET" && target.pathname.endsWith("/list")) {
|
|
371
|
+
return { entries: Object.keys(values).sort().map((name) => ({ name })) };
|
|
372
|
+
}
|
|
373
|
+
if (method === "GET" && target.pathname.endsWith("/entries"))
|
|
374
|
+
return { values: { ...values } };
|
|
375
|
+
if (method === "GET" && target.pathname.endsWith("/changes")) {
|
|
376
|
+
return { version: 0, changed: [] };
|
|
377
|
+
}
|
|
378
|
+
throw new VaultError("LOCAL_OPERATION_UNSUPPORTED", "This operation is unavailable in isolated local Vault mode.");
|
|
379
|
+
}
|
|
329
380
|
async managedRequest(path, init) {
|
|
330
381
|
const socketPath = this.credential.socketPath;
|
|
331
382
|
const method = init.method ?? "GET";
|
|
@@ -406,7 +457,7 @@ class ForgeZero {
|
|
|
406
457
|
return result.value;
|
|
407
458
|
}
|
|
408
459
|
async getAll() {
|
|
409
|
-
if (this.credential.mode !== "managed") {
|
|
460
|
+
if (this.credential.mode !== "managed" && this.credential.mode !== "local") {
|
|
410
461
|
throw new VaultError("MANAGED_ONLY", "getAll is available on managed compute only. Read entries by name with an API key.");
|
|
411
462
|
}
|
|
412
463
|
const result = await this.request(`/v1/vault/${this.scope()}/entries`);
|
|
@@ -441,9 +492,24 @@ class ForgeZero {
|
|
|
441
492
|
return result.entries;
|
|
442
493
|
}
|
|
443
494
|
async set(name, value) {
|
|
495
|
+
if (this.credential.mode === "local") {
|
|
496
|
+
if (!/^[A-Za-z0-9_.:/-]{1,256}$/.test(name) || !value) {
|
|
497
|
+
throw new VaultError("LOCAL_VALUE_INVALID", "Local Vault names and values must be non-empty and bounded.");
|
|
498
|
+
}
|
|
499
|
+
const values = this.localValues();
|
|
500
|
+
values[name] = value;
|
|
501
|
+
this.saveLocalValues(values);
|
|
502
|
+
return 1;
|
|
503
|
+
}
|
|
444
504
|
throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot write secrets; use the authenticated platform vault UI.");
|
|
445
505
|
}
|
|
446
506
|
async remove(name) {
|
|
507
|
+
if (this.credential.mode === "local") {
|
|
508
|
+
const values = this.localValues();
|
|
509
|
+
delete values[name];
|
|
510
|
+
this.saveLocalValues(values);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
447
513
|
throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot delete secrets; use the authenticated platform vault UI.");
|
|
448
514
|
}
|
|
449
515
|
async* watch(options = {}) {
|
|
@@ -463,6 +529,7 @@ class ForgeZero {
|
|
|
463
529
|
}
|
|
464
530
|
}
|
|
465
531
|
var createVault = (options = {}) => new ForgeZero(options);
|
|
532
|
+
var createLocalVault = (options = {}) => new ForgeZero({ ...options, credential: { mode: "local" } });
|
|
466
533
|
function vaultCredentials(vault) {
|
|
467
534
|
return {
|
|
468
535
|
name: "vault",
|
|
@@ -505,7 +572,7 @@ function systemdCredentials(options = {}) {
|
|
|
505
572
|
}
|
|
506
573
|
};
|
|
507
574
|
}
|
|
508
|
-
var VERSION = "0.1.
|
|
575
|
+
var VERSION = "0.1.25";
|
|
509
576
|
export {
|
|
510
577
|
vaultCredentials,
|
|
511
578
|
systemdCredentials,
|
|
@@ -514,6 +581,7 @@ export {
|
|
|
514
581
|
discover,
|
|
515
582
|
directCredentials,
|
|
516
583
|
createVault,
|
|
584
|
+
createLocalVault,
|
|
517
585
|
VaultRuntime,
|
|
518
586
|
VaultError,
|
|
519
587
|
VERSION,
|
package/dist/providers.js
CHANGED
|
@@ -22,8 +22,8 @@ class VaultRuntime {
|
|
|
22
22
|
this.transport = transport;
|
|
23
23
|
}
|
|
24
24
|
available() {
|
|
25
|
-
if (this.transport === "managed-read-only") {
|
|
26
|
-
throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
|
|
25
|
+
if (this.transport === "managed-read-only" || this.transport === "local-read-only") {
|
|
26
|
+
throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", this.transport === "local-read-only" ? "Local Vault mode stores development values only; master-seed runtime operations require a separately granted live API key." : "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
async create(options) {
|
|
@@ -156,7 +156,9 @@ function discover(environment = {}) {
|
|
|
156
156
|
const apiKey = env.FORGEZERO_API_KEY;
|
|
157
157
|
if (apiKey)
|
|
158
158
|
return { mode: "external", apiKey };
|
|
159
|
-
|
|
159
|
+
if (env.FORGEZERO_LOCAL_VAULT === "1")
|
|
160
|
+
return { mode: "local" };
|
|
161
|
+
throw new VaultError("NO_CREDENTIAL", `No credential found. On managed compute the agent socket at ${socketPath} provides one; ` + "elsewhere set FORGEZERO_API_KEY, or explicitly set FORGEZERO_LOCAL_VAULT=1 for isolated local development.");
|
|
160
162
|
}
|
|
161
163
|
var unbase64url = (encoded) => {
|
|
162
164
|
const padded = encoded.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - encoded.length % 4) % 4);
|
|
@@ -264,6 +266,8 @@ class ForgeZero {
|
|
|
264
266
|
requestTimeoutMs;
|
|
265
267
|
project;
|
|
266
268
|
environment;
|
|
269
|
+
localStorage;
|
|
270
|
+
localNamespace;
|
|
267
271
|
constructor(options = {}) {
|
|
268
272
|
const credential = options.credential ?? discover(options.discovery);
|
|
269
273
|
this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
|
|
@@ -277,8 +281,13 @@ class ForgeZero {
|
|
|
277
281
|
}
|
|
278
282
|
this.project = options.project ?? "default";
|
|
279
283
|
this.environment = options.environment ?? "production";
|
|
284
|
+
this.localStorage = options.localStorage ?? (this.credential.mode === "local" && typeof globalThis.localStorage !== "undefined" ? globalThis.localStorage : undefined);
|
|
285
|
+
this.localNamespace = options.localNamespace?.trim() || "fz.local.vault.v1";
|
|
286
|
+
if (this.credential.mode === "local" && !this.localStorage) {
|
|
287
|
+
throw new VaultError("LOCAL_STORAGE_UNAVAILABLE", "Local Vault mode requires browser localStorage or an injected LocalVaultStorage adapter.");
|
|
288
|
+
}
|
|
280
289
|
const runtimeRequest = options.runtimeRequest ?? ((path, init) => this.request(`/v1/vault/${this.scope()}/runtime${path}`, init));
|
|
281
|
-
this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : "managed-read-only");
|
|
290
|
+
this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : this.credential.mode === "local" ? "local-read-only" : "managed-read-only");
|
|
282
291
|
}
|
|
283
292
|
scope() {
|
|
284
293
|
return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
|
|
@@ -286,6 +295,8 @@ class ForgeZero {
|
|
|
286
295
|
async request(path, init = {}) {
|
|
287
296
|
if (this.credential.mode === "managed")
|
|
288
297
|
return this.managedRequest(path, init);
|
|
298
|
+
if (this.credential.mode === "local")
|
|
299
|
+
return this.localRequest(path, init);
|
|
289
300
|
const body = typeof init.body === "string" ? init.body : "";
|
|
290
301
|
const target = new URL(path, this.apiUrl);
|
|
291
302
|
const headers = {
|
|
@@ -326,6 +337,46 @@ class ForgeZero {
|
|
|
326
337
|
}
|
|
327
338
|
return payload;
|
|
328
339
|
}
|
|
340
|
+
localStorageKey() {
|
|
341
|
+
return `${this.localNamespace}:${encodeURIComponent(this.project)}:${encodeURIComponent(this.environment)}`;
|
|
342
|
+
}
|
|
343
|
+
localValues() {
|
|
344
|
+
const raw = this.localStorage.getItem(this.localStorageKey());
|
|
345
|
+
if (raw === null)
|
|
346
|
+
return {};
|
|
347
|
+
try {
|
|
348
|
+
const parsed = JSON.parse(raw);
|
|
349
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || Object.entries(parsed).some(([key, value]) => !key || typeof value !== "string"))
|
|
350
|
+
throw new Error("invalid");
|
|
351
|
+
return parsed;
|
|
352
|
+
} catch {
|
|
353
|
+
throw new VaultError("LOCAL_STORE_MALFORMED", "The local Vault store is malformed. Clear it before continuing.");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
saveLocalValues(values) {
|
|
357
|
+
this.localStorage.setItem(this.localStorageKey(), JSON.stringify(values));
|
|
358
|
+
}
|
|
359
|
+
async localRequest(path, init) {
|
|
360
|
+
const method = init.method ?? "GET";
|
|
361
|
+
const target = new URL(path, "https://vault.local.invalid");
|
|
362
|
+
const values = this.localValues();
|
|
363
|
+
const entry = /\/entries\/([^/]+)$/.exec(target.pathname);
|
|
364
|
+
if (method === "GET" && entry) {
|
|
365
|
+
const name = decodeURIComponent(entry[1]);
|
|
366
|
+
if (!(name in values))
|
|
367
|
+
throw new VaultError("NOT_FOUND", `No local Vault value named ${name}.`);
|
|
368
|
+
return { value: values[name] };
|
|
369
|
+
}
|
|
370
|
+
if (method === "GET" && target.pathname.endsWith("/list")) {
|
|
371
|
+
return { entries: Object.keys(values).sort().map((name) => ({ name })) };
|
|
372
|
+
}
|
|
373
|
+
if (method === "GET" && target.pathname.endsWith("/entries"))
|
|
374
|
+
return { values: { ...values } };
|
|
375
|
+
if (method === "GET" && target.pathname.endsWith("/changes")) {
|
|
376
|
+
return { version: 0, changed: [] };
|
|
377
|
+
}
|
|
378
|
+
throw new VaultError("LOCAL_OPERATION_UNSUPPORTED", "This operation is unavailable in isolated local Vault mode.");
|
|
379
|
+
}
|
|
329
380
|
async managedRequest(path, init) {
|
|
330
381
|
const socketPath = this.credential.socketPath;
|
|
331
382
|
const method = init.method ?? "GET";
|
|
@@ -406,7 +457,7 @@ class ForgeZero {
|
|
|
406
457
|
return result.value;
|
|
407
458
|
}
|
|
408
459
|
async getAll() {
|
|
409
|
-
if (this.credential.mode !== "managed") {
|
|
460
|
+
if (this.credential.mode !== "managed" && this.credential.mode !== "local") {
|
|
410
461
|
throw new VaultError("MANAGED_ONLY", "getAll is available on managed compute only. Read entries by name with an API key.");
|
|
411
462
|
}
|
|
412
463
|
const result = await this.request(`/v1/vault/${this.scope()}/entries`);
|
|
@@ -441,9 +492,24 @@ class ForgeZero {
|
|
|
441
492
|
return result.entries;
|
|
442
493
|
}
|
|
443
494
|
async set(name, value) {
|
|
495
|
+
if (this.credential.mode === "local") {
|
|
496
|
+
if (!/^[A-Za-z0-9_.:/-]{1,256}$/.test(name) || !value) {
|
|
497
|
+
throw new VaultError("LOCAL_VALUE_INVALID", "Local Vault names and values must be non-empty and bounded.");
|
|
498
|
+
}
|
|
499
|
+
const values = this.localValues();
|
|
500
|
+
values[name] = value;
|
|
501
|
+
this.saveLocalValues(values);
|
|
502
|
+
return 1;
|
|
503
|
+
}
|
|
444
504
|
throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot write secrets; use the authenticated platform vault UI.");
|
|
445
505
|
}
|
|
446
506
|
async remove(name) {
|
|
507
|
+
if (this.credential.mode === "local") {
|
|
508
|
+
const values = this.localValues();
|
|
509
|
+
delete values[name];
|
|
510
|
+
this.saveLocalValues(values);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
447
513
|
throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot delete secrets; use the authenticated platform vault UI.");
|
|
448
514
|
}
|
|
449
515
|
async* watch(options = {}) {
|
|
@@ -463,6 +529,7 @@ class ForgeZero {
|
|
|
463
529
|
}
|
|
464
530
|
}
|
|
465
531
|
var createVault = (options = {}) => new ForgeZero(options);
|
|
532
|
+
var createLocalVault = (options = {}) => new ForgeZero({ ...options, credential: { mode: "local" } });
|
|
466
533
|
function vaultCredentials(vault) {
|
|
467
534
|
return {
|
|
468
535
|
name: "vault",
|
|
@@ -505,7 +572,7 @@ function systemdCredentials(options = {}) {
|
|
|
505
572
|
}
|
|
506
573
|
};
|
|
507
574
|
}
|
|
508
|
-
var VERSION = "0.1.
|
|
575
|
+
var VERSION = "0.1.25";
|
|
509
576
|
|
|
510
577
|
// src/providers.ts
|
|
511
578
|
var MISSING = new Set(["ENTRY_NOT_FOUND", "VERSION_NOT_FOUND"]);
|
package/dist/runtime.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export declare class VaultRuntimeClientError extends Error {
|
|
|
48
48
|
* explicit prevents membership of the local Vault-reader group from silently
|
|
49
49
|
* granting signing, decryption or certificate authority.
|
|
50
50
|
*/
|
|
51
|
-
export type VaultRuntimeTransport = 'external' | 'managed-read-only' | 'managed-delegated';
|
|
51
|
+
export type VaultRuntimeTransport = 'external' | 'managed-read-only' | 'managed-delegated' | 'local-read-only';
|
|
52
52
|
/**
|
|
53
53
|
* Explicit non-exporting cryptographic capabilities.
|
|
54
54
|
*
|
package/dist/runtime.js
CHANGED
|
@@ -22,8 +22,8 @@ class VaultRuntime {
|
|
|
22
22
|
this.transport = transport;
|
|
23
23
|
}
|
|
24
24
|
available() {
|
|
25
|
-
if (this.transport === "managed-read-only") {
|
|
26
|
-
throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
|
|
25
|
+
if (this.transport === "managed-read-only" || this.transport === "local-read-only") {
|
|
26
|
+
throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", this.transport === "local-read-only" ? "Local Vault mode stores development values only; master-seed runtime operations require a separately granted live API key." : "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
async create(options) {
|
package/dist/schema.js
CHANGED
|
@@ -22,8 +22,8 @@ class VaultRuntime {
|
|
|
22
22
|
this.transport = transport;
|
|
23
23
|
}
|
|
24
24
|
available() {
|
|
25
|
-
if (this.transport === "managed-read-only") {
|
|
26
|
-
throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
|
|
25
|
+
if (this.transport === "managed-read-only" || this.transport === "local-read-only") {
|
|
26
|
+
throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", this.transport === "local-read-only" ? "Local Vault mode stores development values only; master-seed runtime operations require a separately granted live API key." : "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
async create(options) {
|
|
@@ -156,7 +156,9 @@ function discover(environment = {}) {
|
|
|
156
156
|
const apiKey = env.FORGEZERO_API_KEY;
|
|
157
157
|
if (apiKey)
|
|
158
158
|
return { mode: "external", apiKey };
|
|
159
|
-
|
|
159
|
+
if (env.FORGEZERO_LOCAL_VAULT === "1")
|
|
160
|
+
return { mode: "local" };
|
|
161
|
+
throw new VaultError("NO_CREDENTIAL", `No credential found. On managed compute the agent socket at ${socketPath} provides one; ` + "elsewhere set FORGEZERO_API_KEY, or explicitly set FORGEZERO_LOCAL_VAULT=1 for isolated local development.");
|
|
160
162
|
}
|
|
161
163
|
var unbase64url = (encoded) => {
|
|
162
164
|
const padded = encoded.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - encoded.length % 4) % 4);
|
|
@@ -264,6 +266,8 @@ class ForgeZero {
|
|
|
264
266
|
requestTimeoutMs;
|
|
265
267
|
project;
|
|
266
268
|
environment;
|
|
269
|
+
localStorage;
|
|
270
|
+
localNamespace;
|
|
267
271
|
constructor(options = {}) {
|
|
268
272
|
const credential = options.credential ?? discover(options.discovery);
|
|
269
273
|
this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
|
|
@@ -277,8 +281,13 @@ class ForgeZero {
|
|
|
277
281
|
}
|
|
278
282
|
this.project = options.project ?? "default";
|
|
279
283
|
this.environment = options.environment ?? "production";
|
|
284
|
+
this.localStorage = options.localStorage ?? (this.credential.mode === "local" && typeof globalThis.localStorage !== "undefined" ? globalThis.localStorage : undefined);
|
|
285
|
+
this.localNamespace = options.localNamespace?.trim() || "fz.local.vault.v1";
|
|
286
|
+
if (this.credential.mode === "local" && !this.localStorage) {
|
|
287
|
+
throw new VaultError("LOCAL_STORAGE_UNAVAILABLE", "Local Vault mode requires browser localStorage or an injected LocalVaultStorage adapter.");
|
|
288
|
+
}
|
|
280
289
|
const runtimeRequest = options.runtimeRequest ?? ((path, init) => this.request(`/v1/vault/${this.scope()}/runtime${path}`, init));
|
|
281
|
-
this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : "managed-read-only");
|
|
290
|
+
this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : this.credential.mode === "local" ? "local-read-only" : "managed-read-only");
|
|
282
291
|
}
|
|
283
292
|
scope() {
|
|
284
293
|
return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
|
|
@@ -286,6 +295,8 @@ class ForgeZero {
|
|
|
286
295
|
async request(path, init = {}) {
|
|
287
296
|
if (this.credential.mode === "managed")
|
|
288
297
|
return this.managedRequest(path, init);
|
|
298
|
+
if (this.credential.mode === "local")
|
|
299
|
+
return this.localRequest(path, init);
|
|
289
300
|
const body = typeof init.body === "string" ? init.body : "";
|
|
290
301
|
const target = new URL(path, this.apiUrl);
|
|
291
302
|
const headers = {
|
|
@@ -326,6 +337,46 @@ class ForgeZero {
|
|
|
326
337
|
}
|
|
327
338
|
return payload;
|
|
328
339
|
}
|
|
340
|
+
localStorageKey() {
|
|
341
|
+
return `${this.localNamespace}:${encodeURIComponent(this.project)}:${encodeURIComponent(this.environment)}`;
|
|
342
|
+
}
|
|
343
|
+
localValues() {
|
|
344
|
+
const raw = this.localStorage.getItem(this.localStorageKey());
|
|
345
|
+
if (raw === null)
|
|
346
|
+
return {};
|
|
347
|
+
try {
|
|
348
|
+
const parsed = JSON.parse(raw);
|
|
349
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || Object.entries(parsed).some(([key, value]) => !key || typeof value !== "string"))
|
|
350
|
+
throw new Error("invalid");
|
|
351
|
+
return parsed;
|
|
352
|
+
} catch {
|
|
353
|
+
throw new VaultError("LOCAL_STORE_MALFORMED", "The local Vault store is malformed. Clear it before continuing.");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
saveLocalValues(values) {
|
|
357
|
+
this.localStorage.setItem(this.localStorageKey(), JSON.stringify(values));
|
|
358
|
+
}
|
|
359
|
+
async localRequest(path, init) {
|
|
360
|
+
const method = init.method ?? "GET";
|
|
361
|
+
const target = new URL(path, "https://vault.local.invalid");
|
|
362
|
+
const values = this.localValues();
|
|
363
|
+
const entry = /\/entries\/([^/]+)$/.exec(target.pathname);
|
|
364
|
+
if (method === "GET" && entry) {
|
|
365
|
+
const name = decodeURIComponent(entry[1]);
|
|
366
|
+
if (!(name in values))
|
|
367
|
+
throw new VaultError("NOT_FOUND", `No local Vault value named ${name}.`);
|
|
368
|
+
return { value: values[name] };
|
|
369
|
+
}
|
|
370
|
+
if (method === "GET" && target.pathname.endsWith("/list")) {
|
|
371
|
+
return { entries: Object.keys(values).sort().map((name) => ({ name })) };
|
|
372
|
+
}
|
|
373
|
+
if (method === "GET" && target.pathname.endsWith("/entries"))
|
|
374
|
+
return { values: { ...values } };
|
|
375
|
+
if (method === "GET" && target.pathname.endsWith("/changes")) {
|
|
376
|
+
return { version: 0, changed: [] };
|
|
377
|
+
}
|
|
378
|
+
throw new VaultError("LOCAL_OPERATION_UNSUPPORTED", "This operation is unavailable in isolated local Vault mode.");
|
|
379
|
+
}
|
|
329
380
|
async managedRequest(path, init) {
|
|
330
381
|
const socketPath = this.credential.socketPath;
|
|
331
382
|
const method = init.method ?? "GET";
|
|
@@ -406,7 +457,7 @@ class ForgeZero {
|
|
|
406
457
|
return result.value;
|
|
407
458
|
}
|
|
408
459
|
async getAll() {
|
|
409
|
-
if (this.credential.mode !== "managed") {
|
|
460
|
+
if (this.credential.mode !== "managed" && this.credential.mode !== "local") {
|
|
410
461
|
throw new VaultError("MANAGED_ONLY", "getAll is available on managed compute only. Read entries by name with an API key.");
|
|
411
462
|
}
|
|
412
463
|
const result = await this.request(`/v1/vault/${this.scope()}/entries`);
|
|
@@ -441,9 +492,24 @@ class ForgeZero {
|
|
|
441
492
|
return result.entries;
|
|
442
493
|
}
|
|
443
494
|
async set(name, value) {
|
|
495
|
+
if (this.credential.mode === "local") {
|
|
496
|
+
if (!/^[A-Za-z0-9_.:/-]{1,256}$/.test(name) || !value) {
|
|
497
|
+
throw new VaultError("LOCAL_VALUE_INVALID", "Local Vault names and values must be non-empty and bounded.");
|
|
498
|
+
}
|
|
499
|
+
const values = this.localValues();
|
|
500
|
+
values[name] = value;
|
|
501
|
+
this.saveLocalValues(values);
|
|
502
|
+
return 1;
|
|
503
|
+
}
|
|
444
504
|
throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot write secrets; use the authenticated platform vault UI.");
|
|
445
505
|
}
|
|
446
506
|
async remove(name) {
|
|
507
|
+
if (this.credential.mode === "local") {
|
|
508
|
+
const values = this.localValues();
|
|
509
|
+
delete values[name];
|
|
510
|
+
this.saveLocalValues(values);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
447
513
|
throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot delete secrets; use the authenticated platform vault UI.");
|
|
448
514
|
}
|
|
449
515
|
async* watch(options = {}) {
|
|
@@ -463,6 +529,7 @@ class ForgeZero {
|
|
|
463
529
|
}
|
|
464
530
|
}
|
|
465
531
|
var createVault = (options = {}) => new ForgeZero(options);
|
|
532
|
+
var createLocalVault = (options = {}) => new ForgeZero({ ...options, credential: { mode: "local" } });
|
|
466
533
|
function vaultCredentials(vault) {
|
|
467
534
|
return {
|
|
468
535
|
name: "vault",
|
|
@@ -505,7 +572,7 @@ function systemdCredentials(options = {}) {
|
|
|
505
572
|
}
|
|
506
573
|
};
|
|
507
574
|
}
|
|
508
|
-
var VERSION = "0.1.
|
|
575
|
+
var VERSION = "0.1.25";
|
|
509
576
|
|
|
510
577
|
// src/schema.ts
|
|
511
578
|
function managedSchemas(vault, options = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgezero/vault",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -46,12 +46,14 @@
|
|
|
46
46
|
"@types/bun": "latest"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@forgezero/runtime": "^0.1.
|
|
49
|
+
"@forgezero/runtime": "^0.1.19"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
+
"@noble/ciphers": "^2.2.0",
|
|
52
53
|
"@noble/curves": "^2.2.0",
|
|
53
54
|
"@noble/post-quantum": "^0.6.1",
|
|
54
|
-
"@noble/hashes": "^2.2.0"
|
|
55
|
+
"@noble/hashes": "^2.2.0",
|
|
56
|
+
"@scure/base": "^2.2.0"
|
|
55
57
|
},
|
|
56
58
|
"description": "ForgeZero vault client. Credential discovery and versioned secrets through one stable API origin.",
|
|
57
59
|
"keywords": [
|