@forgezero/vault 0.1.1 → 0.1.2
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 +11 -34
- package/dist/index.js +20 -52
- package/dist/providers.js +19 -49
- package/dist/schema.js +19 -49
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
* places, and the stronger posture is the one requiring less configuration
|
|
17
17
|
* rather than more.
|
|
18
18
|
*
|
|
19
|
-
* ##
|
|
19
|
+
* ## Stable API origin
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* External clients always call one API origin. Node selection and failover are
|
|
22
|
+
* edge responsibilities; exposing node hostnames here would couple every SDK
|
|
23
|
+
* to platform topology and can route a tenant to a process that does not hold
|
|
24
|
+
* its realm seed. Managed applications continue to use the local agent socket
|
|
25
|
+
* and make no remote request at all.
|
|
25
26
|
*
|
|
26
27
|
* This package must not import `@forgezero/providers` either. `vaultCredentials`
|
|
27
28
|
* returns the shape structurally, because a dependency edge in either direction
|
|
@@ -104,23 +105,9 @@ export type AgentResponse = {
|
|
|
104
105
|
};
|
|
105
106
|
};
|
|
106
107
|
export type AgentTransport = (socketPath: string, request: AgentRequest) => Promise<AgentResponse>;
|
|
107
|
-
export interface Assignment {
|
|
108
|
-
node: string;
|
|
109
|
-
ttl: number;
|
|
110
|
-
expiresAt: number;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Statuses meaning "ask for a different node", not "this failed".
|
|
114
|
-
*
|
|
115
|
-
* 530 is the one people miss: a node whose tunnel is down never reaches the
|
|
116
|
-
* application, so Cloudflare answers 1033 with 530.
|
|
117
|
-
*/
|
|
118
|
-
export declare const REASSIGN_ON: Set<number>;
|
|
119
108
|
export interface VaultOptions {
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
/** Override only for a private ForgeZero installation with its own node domain. */
|
|
123
|
-
nodeAllowed?: (hostname: string) => boolean;
|
|
109
|
+
/** Stable public API origin. Node topology is never exposed to the client. */
|
|
110
|
+
apiUrl?: string;
|
|
124
111
|
requestTimeoutMs?: number;
|
|
125
112
|
project?: string;
|
|
126
113
|
environment?: string;
|
|
@@ -129,7 +116,6 @@ export interface VaultOptions {
|
|
|
129
116
|
agentTransport?: AgentTransport;
|
|
130
117
|
credential?: Credential;
|
|
131
118
|
discovery?: DiscoveryEnvironment;
|
|
132
|
-
now?: () => number;
|
|
133
119
|
}
|
|
134
120
|
export interface EntryMeta {
|
|
135
121
|
name: string;
|
|
@@ -142,27 +128,18 @@ export interface Change {
|
|
|
142
128
|
name: string;
|
|
143
129
|
version: number;
|
|
144
130
|
}
|
|
145
|
-
export declare const
|
|
131
|
+
export declare const DEFAULT_API_URL = "https://api.forgezero.net";
|
|
146
132
|
export declare const DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
147
|
-
/** A compromised directory must not turn a signed secret read into an open relay. */
|
|
148
|
-
export declare const isForgeZeroNode: (hostname: string) => boolean;
|
|
149
133
|
export declare class ForgeZero {
|
|
150
134
|
readonly credential: Credential;
|
|
151
|
-
private
|
|
152
|
-
private readonly assignUrl;
|
|
135
|
+
private readonly apiUrl;
|
|
153
136
|
private readonly doFetch;
|
|
154
|
-
private readonly now;
|
|
155
137
|
private readonly signer?;
|
|
156
138
|
private readonly agentTransport;
|
|
157
|
-
private readonly nodeAllowed;
|
|
158
139
|
private readonly requestTimeoutMs;
|
|
159
140
|
private readonly project;
|
|
160
141
|
private readonly environment;
|
|
161
142
|
constructor(options?: VaultOptions);
|
|
162
|
-
/** The node to talk to, asking the directory only when the lease expired. */
|
|
163
|
-
node(): Promise<string>;
|
|
164
|
-
/** Drop the lease so the next call asks the directory again. */
|
|
165
|
-
reassign(): void;
|
|
166
143
|
private scope;
|
|
167
144
|
private request;
|
|
168
145
|
private managedRequest;
|
|
@@ -296,4 +273,4 @@ export declare function systemdCredentials(options?: SystemdCredentialOptions):
|
|
|
296
273
|
readonly name: 'systemd';
|
|
297
274
|
get(reference: string, field: string): Promise<string>;
|
|
298
275
|
};
|
|
299
|
-
export declare const VERSION = "0.1.
|
|
276
|
+
export declare const VERSION = "0.1.2";
|
package/dist/index.js
CHANGED
|
@@ -110,32 +110,37 @@ var requestAgent = (socketPath, request) => new Promise((resolveRequest, reject)
|
|
|
110
110
|
});
|
|
111
111
|
socket.on("error", (cause) => reject(new VaultError("AGENT_UNAVAILABLE", cause.message)));
|
|
112
112
|
});
|
|
113
|
-
var
|
|
114
|
-
var DEFAULT_ASSIGN_URL = "https://assign.forgezero.net/v1/assign";
|
|
113
|
+
var DEFAULT_API_URL = "https://api.forgezero.net";
|
|
115
114
|
var DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
116
|
-
|
|
115
|
+
function apiOrigin(value) {
|
|
116
|
+
let url;
|
|
117
|
+
try {
|
|
118
|
+
url = new URL(value);
|
|
119
|
+
} catch {
|
|
120
|
+
throw new VaultError("API_URL_INVALID", "apiUrl must be an absolute HTTPS origin.");
|
|
121
|
+
}
|
|
122
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
123
|
+
if (url.protocol !== "https:" && !(local && url.protocol === "http:") || url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "")
|
|
124
|
+
throw new VaultError("API_URL_INVALID", "apiUrl must be an HTTPS origin without credentials, path, query or fragment.");
|
|
125
|
+
return url;
|
|
126
|
+
}
|
|
117
127
|
|
|
118
128
|
class ForgeZero {
|
|
119
129
|
credential;
|
|
120
|
-
|
|
121
|
-
assignUrl;
|
|
130
|
+
apiUrl;
|
|
122
131
|
doFetch;
|
|
123
|
-
now;
|
|
124
132
|
signer;
|
|
125
133
|
agentTransport;
|
|
126
|
-
nodeAllowed;
|
|
127
134
|
requestTimeoutMs;
|
|
128
135
|
project;
|
|
129
136
|
environment;
|
|
130
137
|
constructor(options = {}) {
|
|
131
138
|
const credential = options.credential ?? discover(options.discovery);
|
|
132
139
|
this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
|
|
133
|
-
this.
|
|
140
|
+
this.apiUrl = apiOrigin(options.apiUrl ?? DEFAULT_API_URL);
|
|
134
141
|
this.doFetch = options.fetch ?? globalThis.fetch;
|
|
135
|
-
this.now = options.now ?? Date.now;
|
|
136
142
|
this.signer = options.signer ?? (this.credential.mode === "external" ? signerFromApiKey(this.credential.apiKey) : undefined);
|
|
137
143
|
this.agentTransport = options.agentTransport ?? requestAgent;
|
|
138
|
-
this.nodeAllowed = options.nodeAllowed ?? isForgeZeroNode;
|
|
139
144
|
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
140
145
|
if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs < 100 || this.requestTimeoutMs > 120000) {
|
|
141
146
|
throw new VaultError("INVALID_TIMEOUT", "requestTimeoutMs must be between 100 and 120000 milliseconds.");
|
|
@@ -143,45 +148,14 @@ class ForgeZero {
|
|
|
143
148
|
this.project = options.project ?? "default";
|
|
144
149
|
this.environment = options.environment ?? "production";
|
|
145
150
|
}
|
|
146
|
-
async node() {
|
|
147
|
-
const now = this.now();
|
|
148
|
-
if (this.assignment && this.assignment.expiresAt > now)
|
|
149
|
-
return this.assignment.node;
|
|
150
|
-
const assignmentUrl = new URL(this.assignUrl);
|
|
151
|
-
if (this.signer?.keyId && !assignmentUrl.searchParams.has("client")) {
|
|
152
|
-
assignmentUrl.searchParams.set("client", this.signer.keyId);
|
|
153
|
-
}
|
|
154
|
-
const response = await this.doFetch(assignmentUrl, {
|
|
155
|
-
method: "GET",
|
|
156
|
-
headers: { accept: "application/json" },
|
|
157
|
-
signal: AbortSignal.timeout(this.requestTimeoutMs)
|
|
158
|
-
});
|
|
159
|
-
if (!response.ok)
|
|
160
|
-
throw new VaultError("NO_NODE", "No vault node is available right now.");
|
|
161
|
-
const payload = await response.json().catch(() => null);
|
|
162
|
-
const hostname = typeof payload?.node === "string" ? payload.node : payload?.node && typeof payload.node === "object" && ("hostname" in payload.node) ? payload.node.hostname : undefined;
|
|
163
|
-
if (typeof hostname !== "string" || !this.nodeAllowed(hostname)) {
|
|
164
|
-
throw new VaultError("UNTRUSTED_NODE", "The directory returned an untrusted vault node hostname.");
|
|
165
|
-
}
|
|
166
|
-
const ttl = payload?.ttl ?? 60;
|
|
167
|
-
if (typeof ttl !== "number" || !Number.isFinite(ttl) || ttl < 1 || ttl > 300) {
|
|
168
|
-
throw new VaultError("INVALID_ASSIGNMENT", "The directory returned an invalid vault-node lease.");
|
|
169
|
-
}
|
|
170
|
-
this.assignment = { node: hostname, ttl, expiresAt: now + ttl * 1000 };
|
|
171
|
-
return hostname;
|
|
172
|
-
}
|
|
173
|
-
reassign() {
|
|
174
|
-
this.assignment = undefined;
|
|
175
|
-
}
|
|
176
151
|
scope() {
|
|
177
152
|
return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
|
|
178
153
|
}
|
|
179
|
-
async request(path, init = {}
|
|
154
|
+
async request(path, init = {}) {
|
|
180
155
|
if (this.credential.mode === "managed")
|
|
181
156
|
return this.managedRequest(path, init);
|
|
182
|
-
const node = await this.node();
|
|
183
157
|
const body = typeof init.body === "string" ? init.body : "";
|
|
184
|
-
const target = new URL(path,
|
|
158
|
+
const target = new URL(path, this.apiUrl);
|
|
185
159
|
const headers = {
|
|
186
160
|
"content-type": "application/json",
|
|
187
161
|
...init.headers
|
|
@@ -195,15 +169,11 @@ class ForgeZero {
|
|
|
195
169
|
body
|
|
196
170
|
});
|
|
197
171
|
}
|
|
198
|
-
const response = await this.doFetch(
|
|
172
|
+
const response = await this.doFetch(target, {
|
|
199
173
|
...init,
|
|
200
174
|
headers,
|
|
201
175
|
signal: init.signal ?? AbortSignal.timeout(this.requestTimeoutMs)
|
|
202
176
|
});
|
|
203
|
-
if (REASSIGN_ON.has(response.status) && !retried) {
|
|
204
|
-
this.reassign();
|
|
205
|
-
return this.request(path, init, true);
|
|
206
|
-
}
|
|
207
177
|
const payload = await response.json().catch(() => {
|
|
208
178
|
return;
|
|
209
179
|
});
|
|
@@ -362,20 +332,18 @@ function systemdCredentials(options = {}) {
|
|
|
362
332
|
}
|
|
363
333
|
};
|
|
364
334
|
}
|
|
365
|
-
var VERSION = "0.1.
|
|
335
|
+
var VERSION = "0.1.2";
|
|
366
336
|
export {
|
|
367
337
|
vaultCredentials,
|
|
368
338
|
systemdCredentials,
|
|
369
339
|
signerFromApiKey,
|
|
370
|
-
isForgeZeroNode,
|
|
371
340
|
discover,
|
|
372
341
|
directCredentials,
|
|
373
342
|
createVault,
|
|
374
343
|
VaultError,
|
|
375
344
|
VERSION,
|
|
376
|
-
REASSIGN_ON,
|
|
377
345
|
ForgeZero,
|
|
378
346
|
DEFAULT_SOCKET,
|
|
379
347
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
380
|
-
|
|
348
|
+
DEFAULT_API_URL
|
|
381
349
|
};
|
package/dist/providers.js
CHANGED
|
@@ -110,32 +110,37 @@ var requestAgent = (socketPath, request) => new Promise((resolveRequest, reject)
|
|
|
110
110
|
});
|
|
111
111
|
socket.on("error", (cause) => reject(new VaultError("AGENT_UNAVAILABLE", cause.message)));
|
|
112
112
|
});
|
|
113
|
-
var
|
|
114
|
-
var DEFAULT_ASSIGN_URL = "https://assign.forgezero.net/v1/assign";
|
|
113
|
+
var DEFAULT_API_URL = "https://api.forgezero.net";
|
|
115
114
|
var DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
116
|
-
|
|
115
|
+
function apiOrigin(value) {
|
|
116
|
+
let url;
|
|
117
|
+
try {
|
|
118
|
+
url = new URL(value);
|
|
119
|
+
} catch {
|
|
120
|
+
throw new VaultError("API_URL_INVALID", "apiUrl must be an absolute HTTPS origin.");
|
|
121
|
+
}
|
|
122
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
123
|
+
if (url.protocol !== "https:" && !(local && url.protocol === "http:") || url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "")
|
|
124
|
+
throw new VaultError("API_URL_INVALID", "apiUrl must be an HTTPS origin without credentials, path, query or fragment.");
|
|
125
|
+
return url;
|
|
126
|
+
}
|
|
117
127
|
|
|
118
128
|
class ForgeZero {
|
|
119
129
|
credential;
|
|
120
|
-
|
|
121
|
-
assignUrl;
|
|
130
|
+
apiUrl;
|
|
122
131
|
doFetch;
|
|
123
|
-
now;
|
|
124
132
|
signer;
|
|
125
133
|
agentTransport;
|
|
126
|
-
nodeAllowed;
|
|
127
134
|
requestTimeoutMs;
|
|
128
135
|
project;
|
|
129
136
|
environment;
|
|
130
137
|
constructor(options = {}) {
|
|
131
138
|
const credential = options.credential ?? discover(options.discovery);
|
|
132
139
|
this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
|
|
133
|
-
this.
|
|
140
|
+
this.apiUrl = apiOrigin(options.apiUrl ?? DEFAULT_API_URL);
|
|
134
141
|
this.doFetch = options.fetch ?? globalThis.fetch;
|
|
135
|
-
this.now = options.now ?? Date.now;
|
|
136
142
|
this.signer = options.signer ?? (this.credential.mode === "external" ? signerFromApiKey(this.credential.apiKey) : undefined);
|
|
137
143
|
this.agentTransport = options.agentTransport ?? requestAgent;
|
|
138
|
-
this.nodeAllowed = options.nodeAllowed ?? isForgeZeroNode;
|
|
139
144
|
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
140
145
|
if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs < 100 || this.requestTimeoutMs > 120000) {
|
|
141
146
|
throw new VaultError("INVALID_TIMEOUT", "requestTimeoutMs must be between 100 and 120000 milliseconds.");
|
|
@@ -143,45 +148,14 @@ class ForgeZero {
|
|
|
143
148
|
this.project = options.project ?? "default";
|
|
144
149
|
this.environment = options.environment ?? "production";
|
|
145
150
|
}
|
|
146
|
-
async node() {
|
|
147
|
-
const now = this.now();
|
|
148
|
-
if (this.assignment && this.assignment.expiresAt > now)
|
|
149
|
-
return this.assignment.node;
|
|
150
|
-
const assignmentUrl = new URL(this.assignUrl);
|
|
151
|
-
if (this.signer?.keyId && !assignmentUrl.searchParams.has("client")) {
|
|
152
|
-
assignmentUrl.searchParams.set("client", this.signer.keyId);
|
|
153
|
-
}
|
|
154
|
-
const response = await this.doFetch(assignmentUrl, {
|
|
155
|
-
method: "GET",
|
|
156
|
-
headers: { accept: "application/json" },
|
|
157
|
-
signal: AbortSignal.timeout(this.requestTimeoutMs)
|
|
158
|
-
});
|
|
159
|
-
if (!response.ok)
|
|
160
|
-
throw new VaultError("NO_NODE", "No vault node is available right now.");
|
|
161
|
-
const payload = await response.json().catch(() => null);
|
|
162
|
-
const hostname = typeof payload?.node === "string" ? payload.node : payload?.node && typeof payload.node === "object" && ("hostname" in payload.node) ? payload.node.hostname : undefined;
|
|
163
|
-
if (typeof hostname !== "string" || !this.nodeAllowed(hostname)) {
|
|
164
|
-
throw new VaultError("UNTRUSTED_NODE", "The directory returned an untrusted vault node hostname.");
|
|
165
|
-
}
|
|
166
|
-
const ttl = payload?.ttl ?? 60;
|
|
167
|
-
if (typeof ttl !== "number" || !Number.isFinite(ttl) || ttl < 1 || ttl > 300) {
|
|
168
|
-
throw new VaultError("INVALID_ASSIGNMENT", "The directory returned an invalid vault-node lease.");
|
|
169
|
-
}
|
|
170
|
-
this.assignment = { node: hostname, ttl, expiresAt: now + ttl * 1000 };
|
|
171
|
-
return hostname;
|
|
172
|
-
}
|
|
173
|
-
reassign() {
|
|
174
|
-
this.assignment = undefined;
|
|
175
|
-
}
|
|
176
151
|
scope() {
|
|
177
152
|
return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
|
|
178
153
|
}
|
|
179
|
-
async request(path, init = {}
|
|
154
|
+
async request(path, init = {}) {
|
|
180
155
|
if (this.credential.mode === "managed")
|
|
181
156
|
return this.managedRequest(path, init);
|
|
182
|
-
const node = await this.node();
|
|
183
157
|
const body = typeof init.body === "string" ? init.body : "";
|
|
184
|
-
const target = new URL(path,
|
|
158
|
+
const target = new URL(path, this.apiUrl);
|
|
185
159
|
const headers = {
|
|
186
160
|
"content-type": "application/json",
|
|
187
161
|
...init.headers
|
|
@@ -195,15 +169,11 @@ class ForgeZero {
|
|
|
195
169
|
body
|
|
196
170
|
});
|
|
197
171
|
}
|
|
198
|
-
const response = await this.doFetch(
|
|
172
|
+
const response = await this.doFetch(target, {
|
|
199
173
|
...init,
|
|
200
174
|
headers,
|
|
201
175
|
signal: init.signal ?? AbortSignal.timeout(this.requestTimeoutMs)
|
|
202
176
|
});
|
|
203
|
-
if (REASSIGN_ON.has(response.status) && !retried) {
|
|
204
|
-
this.reassign();
|
|
205
|
-
return this.request(path, init, true);
|
|
206
|
-
}
|
|
207
177
|
const payload = await response.json().catch(() => {
|
|
208
178
|
return;
|
|
209
179
|
});
|
|
@@ -362,7 +332,7 @@ function systemdCredentials(options = {}) {
|
|
|
362
332
|
}
|
|
363
333
|
};
|
|
364
334
|
}
|
|
365
|
-
var VERSION = "0.1.
|
|
335
|
+
var VERSION = "0.1.2";
|
|
366
336
|
|
|
367
337
|
// src/providers.ts
|
|
368
338
|
var MISSING = new Set(["ENTRY_NOT_FOUND", "VERSION_NOT_FOUND"]);
|
package/dist/schema.js
CHANGED
|
@@ -110,32 +110,37 @@ var requestAgent = (socketPath, request) => new Promise((resolveRequest, reject)
|
|
|
110
110
|
});
|
|
111
111
|
socket.on("error", (cause) => reject(new VaultError("AGENT_UNAVAILABLE", cause.message)));
|
|
112
112
|
});
|
|
113
|
-
var
|
|
114
|
-
var DEFAULT_ASSIGN_URL = "https://assign.forgezero.net/v1/assign";
|
|
113
|
+
var DEFAULT_API_URL = "https://api.forgezero.net";
|
|
115
114
|
var DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
116
|
-
|
|
115
|
+
function apiOrigin(value) {
|
|
116
|
+
let url;
|
|
117
|
+
try {
|
|
118
|
+
url = new URL(value);
|
|
119
|
+
} catch {
|
|
120
|
+
throw new VaultError("API_URL_INVALID", "apiUrl must be an absolute HTTPS origin.");
|
|
121
|
+
}
|
|
122
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
123
|
+
if (url.protocol !== "https:" && !(local && url.protocol === "http:") || url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "")
|
|
124
|
+
throw new VaultError("API_URL_INVALID", "apiUrl must be an HTTPS origin without credentials, path, query or fragment.");
|
|
125
|
+
return url;
|
|
126
|
+
}
|
|
117
127
|
|
|
118
128
|
class ForgeZero {
|
|
119
129
|
credential;
|
|
120
|
-
|
|
121
|
-
assignUrl;
|
|
130
|
+
apiUrl;
|
|
122
131
|
doFetch;
|
|
123
|
-
now;
|
|
124
132
|
signer;
|
|
125
133
|
agentTransport;
|
|
126
|
-
nodeAllowed;
|
|
127
134
|
requestTimeoutMs;
|
|
128
135
|
project;
|
|
129
136
|
environment;
|
|
130
137
|
constructor(options = {}) {
|
|
131
138
|
const credential = options.credential ?? discover(options.discovery);
|
|
132
139
|
this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
|
|
133
|
-
this.
|
|
140
|
+
this.apiUrl = apiOrigin(options.apiUrl ?? DEFAULT_API_URL);
|
|
134
141
|
this.doFetch = options.fetch ?? globalThis.fetch;
|
|
135
|
-
this.now = options.now ?? Date.now;
|
|
136
142
|
this.signer = options.signer ?? (this.credential.mode === "external" ? signerFromApiKey(this.credential.apiKey) : undefined);
|
|
137
143
|
this.agentTransport = options.agentTransport ?? requestAgent;
|
|
138
|
-
this.nodeAllowed = options.nodeAllowed ?? isForgeZeroNode;
|
|
139
144
|
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
140
145
|
if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs < 100 || this.requestTimeoutMs > 120000) {
|
|
141
146
|
throw new VaultError("INVALID_TIMEOUT", "requestTimeoutMs must be between 100 and 120000 milliseconds.");
|
|
@@ -143,45 +148,14 @@ class ForgeZero {
|
|
|
143
148
|
this.project = options.project ?? "default";
|
|
144
149
|
this.environment = options.environment ?? "production";
|
|
145
150
|
}
|
|
146
|
-
async node() {
|
|
147
|
-
const now = this.now();
|
|
148
|
-
if (this.assignment && this.assignment.expiresAt > now)
|
|
149
|
-
return this.assignment.node;
|
|
150
|
-
const assignmentUrl = new URL(this.assignUrl);
|
|
151
|
-
if (this.signer?.keyId && !assignmentUrl.searchParams.has("client")) {
|
|
152
|
-
assignmentUrl.searchParams.set("client", this.signer.keyId);
|
|
153
|
-
}
|
|
154
|
-
const response = await this.doFetch(assignmentUrl, {
|
|
155
|
-
method: "GET",
|
|
156
|
-
headers: { accept: "application/json" },
|
|
157
|
-
signal: AbortSignal.timeout(this.requestTimeoutMs)
|
|
158
|
-
});
|
|
159
|
-
if (!response.ok)
|
|
160
|
-
throw new VaultError("NO_NODE", "No vault node is available right now.");
|
|
161
|
-
const payload = await response.json().catch(() => null);
|
|
162
|
-
const hostname = typeof payload?.node === "string" ? payload.node : payload?.node && typeof payload.node === "object" && ("hostname" in payload.node) ? payload.node.hostname : undefined;
|
|
163
|
-
if (typeof hostname !== "string" || !this.nodeAllowed(hostname)) {
|
|
164
|
-
throw new VaultError("UNTRUSTED_NODE", "The directory returned an untrusted vault node hostname.");
|
|
165
|
-
}
|
|
166
|
-
const ttl = payload?.ttl ?? 60;
|
|
167
|
-
if (typeof ttl !== "number" || !Number.isFinite(ttl) || ttl < 1 || ttl > 300) {
|
|
168
|
-
throw new VaultError("INVALID_ASSIGNMENT", "The directory returned an invalid vault-node lease.");
|
|
169
|
-
}
|
|
170
|
-
this.assignment = { node: hostname, ttl, expiresAt: now + ttl * 1000 };
|
|
171
|
-
return hostname;
|
|
172
|
-
}
|
|
173
|
-
reassign() {
|
|
174
|
-
this.assignment = undefined;
|
|
175
|
-
}
|
|
176
151
|
scope() {
|
|
177
152
|
return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
|
|
178
153
|
}
|
|
179
|
-
async request(path, init = {}
|
|
154
|
+
async request(path, init = {}) {
|
|
180
155
|
if (this.credential.mode === "managed")
|
|
181
156
|
return this.managedRequest(path, init);
|
|
182
|
-
const node = await this.node();
|
|
183
157
|
const body = typeof init.body === "string" ? init.body : "";
|
|
184
|
-
const target = new URL(path,
|
|
158
|
+
const target = new URL(path, this.apiUrl);
|
|
185
159
|
const headers = {
|
|
186
160
|
"content-type": "application/json",
|
|
187
161
|
...init.headers
|
|
@@ -195,15 +169,11 @@ class ForgeZero {
|
|
|
195
169
|
body
|
|
196
170
|
});
|
|
197
171
|
}
|
|
198
|
-
const response = await this.doFetch(
|
|
172
|
+
const response = await this.doFetch(target, {
|
|
199
173
|
...init,
|
|
200
174
|
headers,
|
|
201
175
|
signal: init.signal ?? AbortSignal.timeout(this.requestTimeoutMs)
|
|
202
176
|
});
|
|
203
|
-
if (REASSIGN_ON.has(response.status) && !retried) {
|
|
204
|
-
this.reassign();
|
|
205
|
-
return this.request(path, init, true);
|
|
206
|
-
}
|
|
207
177
|
const payload = await response.json().catch(() => {
|
|
208
178
|
return;
|
|
209
179
|
});
|
|
@@ -362,7 +332,7 @@ function systemdCredentials(options = {}) {
|
|
|
362
332
|
}
|
|
363
333
|
};
|
|
364
334
|
}
|
|
365
|
-
var VERSION = "0.1.
|
|
335
|
+
var VERSION = "0.1.2";
|
|
366
336
|
|
|
367
337
|
// src/schema.ts
|
|
368
338
|
function managedSchemas(vault, options = {}) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
|
|
3
3
|
"name": "@forgezero/vault",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@noble/post-quantum": "^0.6.1",
|
|
50
50
|
"@noble/hashes": "^2.2.0"
|
|
51
51
|
},
|
|
52
|
-
"description": "ForgeZero vault client. Credential discovery
|
|
52
|
+
"description": "ForgeZero vault client. Credential discovery and versioned secrets through one stable API origin.",
|
|
53
53
|
"keywords": [
|
|
54
54
|
"secrets",
|
|
55
55
|
"vault",
|