@veducio/keja 0.2.3 → 0.4.0
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/package.json +1 -1
- package/src/grant/grant.test.ts +189 -0
- package/src/grant/grant.ts +289 -0
- package/src/grant/settings.ts +67 -0
- package/src/http/batch.ts +307 -0
- package/src/http/create-app.test.ts +141 -0
- package/src/http/create-app.ts +25 -0
- package/src/http/openapi.ts +94 -0
- package/src/project/config.test.ts +39 -0
- package/src/project/config.ts +189 -5
- package/src/project/load.ts +6 -0
- package/src/route/define.ts +14 -0
- package/src/server/index.ts +21 -1
- package/src/vault/client.ts +92 -1
package/src/project/config.ts
CHANGED
|
@@ -63,6 +63,39 @@ export type VaultConfig =
|
|
|
63
63
|
| { enabled: false }
|
|
64
64
|
| ({ enabled: true } & VaultSettings);
|
|
65
65
|
|
|
66
|
+
export type GrantsConfigInput = {
|
|
67
|
+
enabled: boolean;
|
|
68
|
+
mode?: "transit" | "secret";
|
|
69
|
+
max_exp?: number;
|
|
70
|
+
transit_key?: string;
|
|
71
|
+
transit_mount?: string;
|
|
72
|
+
secret?: string;
|
|
73
|
+
secret_vault?: string;
|
|
74
|
+
secret_field?: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type GrantsConfig =
|
|
78
|
+
| { enabled: false }
|
|
79
|
+
| {
|
|
80
|
+
enabled: true;
|
|
81
|
+
mode: "transit";
|
|
82
|
+
max_exp: number;
|
|
83
|
+
transit_key: string;
|
|
84
|
+
transit_mount: string;
|
|
85
|
+
}
|
|
86
|
+
| {
|
|
87
|
+
enabled: true;
|
|
88
|
+
mode: "secret";
|
|
89
|
+
max_exp: number;
|
|
90
|
+
secret: string | null;
|
|
91
|
+
secret_vault: string | null;
|
|
92
|
+
secret_field: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type BatchConfigInput = boolean | { enabled: boolean };
|
|
96
|
+
|
|
97
|
+
export type BatchConfig = { enabled: boolean };
|
|
98
|
+
|
|
66
99
|
export type KejaConfigInput = {
|
|
67
100
|
name?: string;
|
|
68
101
|
versioning?: string;
|
|
@@ -71,6 +104,8 @@ export type KejaConfigInput = {
|
|
|
71
104
|
argus?: ArgusConfigInput;
|
|
72
105
|
db?: DatabaseConfigInput;
|
|
73
106
|
vault?: VaultConfigInput;
|
|
107
|
+
grants?: GrantsConfigInput;
|
|
108
|
+
batch?: BatchConfigInput;
|
|
74
109
|
};
|
|
75
110
|
|
|
76
111
|
export type KejaConfig = {
|
|
@@ -81,6 +116,8 @@ export type KejaConfig = {
|
|
|
81
116
|
argus: ArgusConfig;
|
|
82
117
|
database: DatabaseConfig;
|
|
83
118
|
vault: VaultConfig;
|
|
119
|
+
grants: GrantsConfig;
|
|
120
|
+
batch: BatchConfig;
|
|
84
121
|
};
|
|
85
122
|
|
|
86
123
|
export function defineConfig(input: KejaConfigInput = {}) {
|
|
@@ -104,6 +141,8 @@ export function defineConfig(input: KejaConfigInput = {}) {
|
|
|
104
141
|
argus: configArgus(input.argus),
|
|
105
142
|
database: configDatabase(input.db),
|
|
106
143
|
vault: configVault(input.vault),
|
|
144
|
+
grants: configGrants(input.grants),
|
|
145
|
+
batch: configBatch(input.batch),
|
|
107
146
|
};
|
|
108
147
|
return Object.assign(config, { [configBrand]: true as const });
|
|
109
148
|
}
|
|
@@ -120,7 +159,9 @@ export function isConfig(value: unknown): value is KejaConfig {
|
|
|
120
159
|
hasName(value) &&
|
|
121
160
|
hasArgus(value) &&
|
|
122
161
|
hasDatabase(value) &&
|
|
123
|
-
hasVault(value)
|
|
162
|
+
hasVault(value) &&
|
|
163
|
+
hasGrants(value) &&
|
|
164
|
+
hasBatch(value)
|
|
124
165
|
);
|
|
125
166
|
}
|
|
126
167
|
|
|
@@ -136,6 +177,8 @@ export type LoadedConfig = ProjectPaths & {
|
|
|
136
177
|
argus: ArgusConfig;
|
|
137
178
|
database: DatabaseConfig;
|
|
138
179
|
vault: VaultConfig;
|
|
180
|
+
grants: GrantsConfig;
|
|
181
|
+
batch: BatchConfig;
|
|
139
182
|
};
|
|
140
183
|
|
|
141
184
|
export function resolveProjectPaths(root: string, config: KejaConfig) {
|
|
@@ -192,6 +235,8 @@ function loadedConfig(
|
|
|
192
235
|
argus: config.argus,
|
|
193
236
|
database: resolvedDatabase(root, config.database),
|
|
194
237
|
vault: config.vault,
|
|
238
|
+
grants: config.grants,
|
|
239
|
+
batch: config.batch,
|
|
195
240
|
};
|
|
196
241
|
}
|
|
197
242
|
|
|
@@ -292,6 +337,87 @@ function configVault(value: VaultConfigInput | undefined): VaultConfig {
|
|
|
292
337
|
};
|
|
293
338
|
}
|
|
294
339
|
|
|
340
|
+
function configGrants(value: GrantsConfigInput | undefined): GrantsConfig {
|
|
341
|
+
if (value === undefined) {
|
|
342
|
+
return { enabled: false };
|
|
343
|
+
}
|
|
344
|
+
if (!isEnabledObject(value)) {
|
|
345
|
+
throw projectError(
|
|
346
|
+
"invalidConfigFile",
|
|
347
|
+
"keja.config.ts grants must be an object with enabled",
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (!value.enabled) {
|
|
351
|
+
return { enabled: false };
|
|
352
|
+
}
|
|
353
|
+
const mode = value.mode ?? "transit";
|
|
354
|
+
if (mode !== "transit" && mode !== "secret") {
|
|
355
|
+
throw projectError(
|
|
356
|
+
"invalidConfigFile",
|
|
357
|
+
"keja.config.ts grants.mode must be transit or secret",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
const max_exp = grantMaxExp(value.max_exp);
|
|
361
|
+
if (mode === "transit") {
|
|
362
|
+
return {
|
|
363
|
+
enabled: true,
|
|
364
|
+
mode: "transit",
|
|
365
|
+
max_exp,
|
|
366
|
+
transit_key: vaultName(
|
|
367
|
+
requiredText(value.transit_key, "grants.transit_key", "key name"),
|
|
368
|
+
"grants.transit_key",
|
|
369
|
+
),
|
|
370
|
+
transit_mount: vaultName(
|
|
371
|
+
value.transit_mount ?? "transit",
|
|
372
|
+
"grants.transit_mount",
|
|
373
|
+
),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const secret =
|
|
377
|
+
value.secret === undefined
|
|
378
|
+
? (configEnv("GRANT_SECRET") ?? null)
|
|
379
|
+
: requiredText(value.secret, "grants.secret", "secret");
|
|
380
|
+
const secret_vault =
|
|
381
|
+
value.secret_vault === undefined
|
|
382
|
+
? null
|
|
383
|
+
: secretVaultPath(
|
|
384
|
+
requiredText(value.secret_vault, "grants.secret_vault", "path"),
|
|
385
|
+
);
|
|
386
|
+
if (secret === null && secret_vault === null) {
|
|
387
|
+
throw projectError(
|
|
388
|
+
"invalidConfigFile",
|
|
389
|
+
"keja.config.ts grants.secret, GRANT_SECRET, or grants.secret_vault is required",
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
enabled: true,
|
|
394
|
+
mode: "secret",
|
|
395
|
+
max_exp,
|
|
396
|
+
secret,
|
|
397
|
+
secret_vault,
|
|
398
|
+
secret_field: vaultName(
|
|
399
|
+
value.secret_field ?? "secret",
|
|
400
|
+
"grants.secret_field",
|
|
401
|
+
),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function configBatch(value: BatchConfigInput | undefined): BatchConfig {
|
|
406
|
+
if (value === undefined) {
|
|
407
|
+
return { enabled: true };
|
|
408
|
+
}
|
|
409
|
+
if (typeof value === "boolean") {
|
|
410
|
+
return { enabled: value };
|
|
411
|
+
}
|
|
412
|
+
if (!isEnabledObject(value)) {
|
|
413
|
+
throw projectError(
|
|
414
|
+
"invalidConfigFile",
|
|
415
|
+
"keja.config.ts batch must be a boolean or an object with enabled",
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
return { enabled: value.enabled };
|
|
419
|
+
}
|
|
420
|
+
|
|
295
421
|
function isEnabledObject(value: unknown): value is { enabled: boolean } {
|
|
296
422
|
return (
|
|
297
423
|
typeof value === "object" &&
|
|
@@ -325,14 +451,51 @@ function absoluteHttpUrl(value: unknown, field: string) {
|
|
|
325
451
|
}
|
|
326
452
|
|
|
327
453
|
function vaultMount(value: string) {
|
|
328
|
-
|
|
329
|
-
|
|
454
|
+
return vaultName(value, "vault.mount");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function vaultName(value: string, field: string) {
|
|
458
|
+
const name = value.trim().replace(/^\/+|\/+$/g, "");
|
|
459
|
+
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
460
|
+
throw projectError(
|
|
461
|
+
"invalidConfigFile",
|
|
462
|
+
`keja.config.ts ${field} must be a name`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return name;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function secretVaultPath(value: string) {
|
|
469
|
+
const cleaned = value.trim().replace(/^\/+|\/+$/g, "");
|
|
470
|
+
const parts = cleaned.split("/");
|
|
471
|
+
if (
|
|
472
|
+
!cleaned ||
|
|
473
|
+
parts.some((part) => part.length === 0 || part === "." || part === "..")
|
|
474
|
+
) {
|
|
475
|
+
throw projectError(
|
|
476
|
+
"invalidConfigFile",
|
|
477
|
+
"keja.config.ts grants.secret_vault must be a Vault secret path",
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
return cleaned;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function grantMaxExp(value: number | undefined) {
|
|
484
|
+
if (value === undefined) {
|
|
485
|
+
return 900;
|
|
486
|
+
}
|
|
487
|
+
if (
|
|
488
|
+
typeof value !== "number" ||
|
|
489
|
+
!Number.isInteger(value) ||
|
|
490
|
+
value < 1 ||
|
|
491
|
+
value > 86400
|
|
492
|
+
) {
|
|
330
493
|
throw projectError(
|
|
331
494
|
"invalidConfigFile",
|
|
332
|
-
|
|
495
|
+
"keja.config.ts grants.max_exp must be a whole number of seconds from 1 to 86400",
|
|
333
496
|
);
|
|
334
497
|
}
|
|
335
|
-
return
|
|
498
|
+
return value;
|
|
336
499
|
}
|
|
337
500
|
|
|
338
501
|
function configEnv(name: string) {
|
|
@@ -398,6 +561,27 @@ function hasVault(value: object) {
|
|
|
398
561
|
);
|
|
399
562
|
}
|
|
400
563
|
|
|
564
|
+
function hasGrants(value: object) {
|
|
565
|
+
const grants = (value as KejaConfig).grants;
|
|
566
|
+
return (
|
|
567
|
+
typeof grants === "object" &&
|
|
568
|
+
grants !== null &&
|
|
569
|
+
(grants.enabled === false ||
|
|
570
|
+
(grants.enabled === true &&
|
|
571
|
+
(grants.mode === "transit" || grants.mode === "secret") &&
|
|
572
|
+
typeof grants.max_exp === "number"))
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function hasBatch(value: object) {
|
|
577
|
+
const batch = (value as KejaConfig).batch;
|
|
578
|
+
return (
|
|
579
|
+
typeof batch === "object" &&
|
|
580
|
+
batch !== null &&
|
|
581
|
+
typeof batch.enabled === "boolean"
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
401
585
|
function hasDatabase(value: object) {
|
|
402
586
|
const database = (value as KejaConfig).database;
|
|
403
587
|
return (
|
package/src/project/load.ts
CHANGED
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
import { isVersions, type VersionDefinition } from "../versioning/define.ts";
|
|
12
12
|
import {
|
|
13
13
|
type ArgusConfig,
|
|
14
|
+
type BatchConfig,
|
|
14
15
|
type DatabaseConfig,
|
|
16
|
+
type GrantsConfig,
|
|
15
17
|
loadConfig,
|
|
16
18
|
type ProjectPaths,
|
|
17
19
|
type VaultConfig,
|
|
@@ -23,6 +25,8 @@ export type LoadedProject = {
|
|
|
23
25
|
name: string | null;
|
|
24
26
|
argus: ArgusConfig;
|
|
25
27
|
vault: VaultConfig;
|
|
28
|
+
grants: GrantsConfig;
|
|
29
|
+
batch: BatchConfig;
|
|
26
30
|
database: DatabaseConfig;
|
|
27
31
|
versions: VersionDefinition[];
|
|
28
32
|
publicRoutes: RouteDefinition[];
|
|
@@ -58,6 +62,8 @@ export async function loadProject(
|
|
|
58
62
|
name: loaded.name,
|
|
59
63
|
argus: loaded.argus,
|
|
60
64
|
vault: loaded.vault,
|
|
65
|
+
grants: loaded.grants,
|
|
66
|
+
batch: loaded.batch,
|
|
61
67
|
database: loaded.database,
|
|
62
68
|
versions: versioning.default.versions,
|
|
63
69
|
publicRoutes: await loadRoutes(publicDirectory, generation),
|
package/src/route/define.ts
CHANGED
|
@@ -78,6 +78,7 @@ export type RouteDefinition = {
|
|
|
78
78
|
method: HttpMethod;
|
|
79
79
|
path: string;
|
|
80
80
|
version: string;
|
|
81
|
+
name: string | null;
|
|
81
82
|
request: RequestSchemas;
|
|
82
83
|
responses: RouteResponse[];
|
|
83
84
|
middleware: readonly MiddlewareDefinition<object>[];
|
|
@@ -124,6 +125,7 @@ export function defineRoute<
|
|
|
124
125
|
>(definition: {
|
|
125
126
|
method: HttpMethod;
|
|
126
127
|
path: string;
|
|
128
|
+
name?: string;
|
|
127
129
|
version: VersionId<Version> extends never
|
|
128
130
|
? "Version must use the form v1, v2, v3"
|
|
129
131
|
: Version;
|
|
@@ -150,6 +152,17 @@ export function defineRoute<
|
|
|
150
152
|
`Path ${String(definition.path)} must start with /`,
|
|
151
153
|
);
|
|
152
154
|
}
|
|
155
|
+
const name =
|
|
156
|
+
definition.name === undefined
|
|
157
|
+
? null
|
|
158
|
+
: typeof definition.name === "string" && definition.name.trim() !== ""
|
|
159
|
+
? definition.name.trim()
|
|
160
|
+
: (() => {
|
|
161
|
+
throw projectError(
|
|
162
|
+
"invalidRoute",
|
|
163
|
+
`${definition.method.toUpperCase()} ${definition.path} name must be a non-empty string`,
|
|
164
|
+
);
|
|
165
|
+
})();
|
|
153
166
|
const request = definition.request as RequestSchemas | undefined;
|
|
154
167
|
if (request) {
|
|
155
168
|
for (const key of ["params", "query", "body"] as const) {
|
|
@@ -229,6 +242,7 @@ export function defineRoute<
|
|
|
229
242
|
method: definition.method,
|
|
230
243
|
path: definition.path,
|
|
231
244
|
version: definition.version,
|
|
245
|
+
name,
|
|
232
246
|
request: definition.request ?? {},
|
|
233
247
|
responses,
|
|
234
248
|
middleware,
|
package/src/server/index.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { startArgusHeartbeat } from "../argus/heartbeat.ts";
|
|
|
4
4
|
import { argusRegistrations, publishArgus } from "../argus/register.ts";
|
|
5
5
|
import { connectDatabase } from "../db/postgres.ts";
|
|
6
6
|
import { readOpenApiServers, readRequiredEnv } from "../env/required-env.ts";
|
|
7
|
-
import { projectError } from "../errors/framework-errors.ts";
|
|
7
|
+
import { DependencyNotMet, projectError } from "../errors/framework-errors.ts";
|
|
8
|
+
import { connectGrants, setGrants } from "../grant/settings.ts";
|
|
8
9
|
import { createHttpApp } from "../http/create-app.ts";
|
|
9
10
|
import { type LoadedProject, loadProject } from "../project/load.ts";
|
|
10
11
|
import { formatSelfTest, runSelfTest } from "../self-test/run.ts";
|
|
@@ -24,6 +25,8 @@ export {
|
|
|
24
25
|
DependencyNotMet,
|
|
25
26
|
frameworkErrors,
|
|
26
27
|
} from "../errors/framework-errors.ts";
|
|
28
|
+
export type { Grant } from "../grant/grant.ts";
|
|
29
|
+
export { grant, verifyGrant } from "../grant/grant.ts";
|
|
27
30
|
export { defineConfig } from "../project/config.ts";
|
|
28
31
|
export { defineRepositories } from "../repository/define.ts";
|
|
29
32
|
export { repository } from "../repository/runtime.ts";
|
|
@@ -73,6 +76,21 @@ export async function listen(
|
|
|
73
76
|
} else {
|
|
74
77
|
setVault(null);
|
|
75
78
|
}
|
|
79
|
+
if (project.grants.enabled) {
|
|
80
|
+
if (project.grants.mode === "transit" && !project.vault.enabled) {
|
|
81
|
+
throw new DependencyNotMet("grant transit", "vault");
|
|
82
|
+
}
|
|
83
|
+
if (
|
|
84
|
+
project.grants.mode === "secret" &&
|
|
85
|
+
project.grants.secret_vault &&
|
|
86
|
+
!project.vault.enabled
|
|
87
|
+
) {
|
|
88
|
+
throw new DependencyNotMet("grant secret_vault", "vault");
|
|
89
|
+
}
|
|
90
|
+
await connectGrants(project.grants);
|
|
91
|
+
} else {
|
|
92
|
+
setGrants(null);
|
|
93
|
+
}
|
|
76
94
|
const mounted = mountProject(root, project, env, openApiServers);
|
|
77
95
|
const identity = mounted.identity;
|
|
78
96
|
if (project.argus.enabled) {
|
|
@@ -155,6 +173,7 @@ function mountProject(
|
|
|
155
173
|
middleware: project.publicMiddleware,
|
|
156
174
|
server: openApiServers.openapiServer,
|
|
157
175
|
identity,
|
|
176
|
+
batch: project.batch.enabled,
|
|
158
177
|
}),
|
|
159
178
|
serviceApp: createHttpApp({
|
|
160
179
|
title: "Service",
|
|
@@ -164,6 +183,7 @@ function mountProject(
|
|
|
164
183
|
server: openApiServers.openapiServiceServer,
|
|
165
184
|
identity,
|
|
166
185
|
postOnly: true,
|
|
186
|
+
batch: project.batch.enabled,
|
|
167
187
|
}),
|
|
168
188
|
};
|
|
169
189
|
}
|
package/src/vault/client.ts
CHANGED
|
@@ -62,6 +62,7 @@ export function createVault(options: VaultClientOptions = {}) {
|
|
|
62
62
|
options.mount ?? envValue("VAULT_KV_MOUNT") ?? "secret",
|
|
63
63
|
);
|
|
64
64
|
const fetchImpl = options.fetch ?? fetch;
|
|
65
|
+
const headers = () => vaultHeaders(token, namespace);
|
|
65
66
|
return {
|
|
66
67
|
async secret<
|
|
67
68
|
Data extends Record<string, unknown> = Record<string, unknown>,
|
|
@@ -70,7 +71,7 @@ export function createVault(options: VaultClientOptions = {}) {
|
|
|
70
71
|
const response = await fetchImpl(
|
|
71
72
|
`${address}/v1/${mount}/data/${secretPath}`,
|
|
72
73
|
{
|
|
73
|
-
headers:
|
|
74
|
+
headers: headers(),
|
|
74
75
|
},
|
|
75
76
|
);
|
|
76
77
|
if (!response.ok) {
|
|
@@ -84,9 +85,76 @@ export function createVault(options: VaultClientOptions = {}) {
|
|
|
84
85
|
}
|
|
85
86
|
return body.data.data;
|
|
86
87
|
},
|
|
88
|
+
transitKey(mountName: string, key: string) {
|
|
89
|
+
const transit = secretMount(mountName);
|
|
90
|
+
const name = secretMount(key);
|
|
91
|
+
return vaultSend<{ type?: unknown; latest_version?: unknown }>(
|
|
92
|
+
fetchImpl,
|
|
93
|
+
`${address}/v1/${transit}/keys/${name}`,
|
|
94
|
+
{ headers: headers() },
|
|
95
|
+
`${transit}/keys/${name}`,
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
transitSign(mountName: string, key: string, body: TransitSignBody) {
|
|
99
|
+
const transit = secretMount(mountName);
|
|
100
|
+
const name = secretMount(key);
|
|
101
|
+
return vaultSend<{ signature?: unknown }>(
|
|
102
|
+
fetchImpl,
|
|
103
|
+
`${address}/v1/${transit}/sign/${name}`,
|
|
104
|
+
{
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: jsonHeaders(headers()),
|
|
107
|
+
body: JSON.stringify(body),
|
|
108
|
+
},
|
|
109
|
+
`${transit}/sign/${name}`,
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
transitVerify(mountName: string, key: string, body: TransitVerifyBody) {
|
|
113
|
+
const transit = secretMount(mountName);
|
|
114
|
+
const name = secretMount(key);
|
|
115
|
+
return vaultSend<{ valid?: unknown }>(
|
|
116
|
+
fetchImpl,
|
|
117
|
+
`${address}/v1/${transit}/verify/${name}`,
|
|
118
|
+
{
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: jsonHeaders(headers()),
|
|
121
|
+
body: JSON.stringify(body),
|
|
122
|
+
},
|
|
123
|
+
`${transit}/verify/${name}`,
|
|
124
|
+
);
|
|
125
|
+
},
|
|
87
126
|
};
|
|
88
127
|
}
|
|
89
128
|
|
|
129
|
+
export function openVault(feature: string) {
|
|
130
|
+
const settings = requireVaultSettings(feature);
|
|
131
|
+
return createVault({
|
|
132
|
+
address: settings.address,
|
|
133
|
+
token: settings.token,
|
|
134
|
+
namespace: settings.namespace ?? undefined,
|
|
135
|
+
mount: settings.mount,
|
|
136
|
+
fetch: state.fetch,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function requireVaultSettings(feature: string) {
|
|
141
|
+
if (!state.active) {
|
|
142
|
+
throw new DependencyNotMet(feature, "vault");
|
|
143
|
+
}
|
|
144
|
+
return state.active;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type TransitSignBody = {
|
|
148
|
+
input: string;
|
|
149
|
+
hash_algorithm?: string;
|
|
150
|
+
marshaling_algorithm?: string;
|
|
151
|
+
signature_algorithm?: string;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
type TransitVerifyBody = TransitSignBody & {
|
|
155
|
+
signature: string;
|
|
156
|
+
};
|
|
157
|
+
|
|
90
158
|
export function vaultSecret<
|
|
91
159
|
Data extends Record<string, unknown> = Record<string, unknown>,
|
|
92
160
|
>(path: string, options?: VaultClientOptions) {
|
|
@@ -141,6 +209,29 @@ function normalizePath(path: string) {
|
|
|
141
209
|
return parts.map((part) => encodeURIComponent(part)).join("/");
|
|
142
210
|
}
|
|
143
211
|
|
|
212
|
+
async function vaultSend<Data>(
|
|
213
|
+
fetchImpl: VaultClientOptions["fetch"],
|
|
214
|
+
url: string,
|
|
215
|
+
init: RequestInit,
|
|
216
|
+
path: string,
|
|
217
|
+
) {
|
|
218
|
+
const fetch = fetchImpl ?? globalThis.fetch;
|
|
219
|
+
const response = await fetch(url, init);
|
|
220
|
+
if (!response.ok) {
|
|
221
|
+
throw await vaultFailure(response, path);
|
|
222
|
+
}
|
|
223
|
+
const body = (await response.json()) as { data?: Data };
|
|
224
|
+
if (!body.data || typeof body.data !== "object") {
|
|
225
|
+
throw new VaultError(`Vault ${path} has no data`);
|
|
226
|
+
}
|
|
227
|
+
return body.data;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function jsonHeaders(headers: Headers) {
|
|
231
|
+
headers.set("Content-Type", "application/json");
|
|
232
|
+
return headers;
|
|
233
|
+
}
|
|
234
|
+
|
|
144
235
|
function vaultHeaders(token: string, namespace: string | null) {
|
|
145
236
|
const headers = new Headers({ "X-Vault-Token": token });
|
|
146
237
|
if (namespace) {
|