@better-forge/vault 0.0.0-stage → 0.1.1

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -2
  3. package/dist/createVault.d.ts +70 -0
  4. package/dist/createVault.d.ts.map +1 -0
  5. package/dist/createVault.js +28 -0
  6. package/dist/createVault.js.map +1 -0
  7. package/dist/hashicorp.d.ts +92 -0
  8. package/dist/hashicorp.d.ts.map +1 -0
  9. package/dist/hashicorp.js +436 -0
  10. package/dist/hashicorp.js.map +1 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +9 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/migrations.d.ts +4 -0
  16. package/dist/migrations.d.ts.map +1 -0
  17. package/dist/migrations.js +28 -0
  18. package/dist/migrations.js.map +1 -0
  19. package/dist/module.d.ts +8 -0
  20. package/dist/module.d.ts.map +1 -0
  21. package/dist/module.js +5 -0
  22. package/dist/module.js.map +1 -0
  23. package/dist/resolveSecret.d.ts +17 -0
  24. package/dist/resolveSecret.d.ts.map +1 -0
  25. package/dist/resolveSecret.js +80 -0
  26. package/dist/resolveSecret.js.map +1 -0
  27. package/dist/sealed.d.ts +45 -0
  28. package/dist/sealed.d.ts.map +1 -0
  29. package/dist/sealed.js +20 -0
  30. package/dist/sealed.js.map +1 -0
  31. package/dist/secretBox.d.ts +17 -0
  32. package/dist/secretBox.d.ts.map +1 -0
  33. package/dist/secretBox.js +63 -0
  34. package/dist/secretBox.js.map +1 -0
  35. package/dist/secretFields.d.ts +48 -0
  36. package/dist/secretFields.d.ts.map +1 -0
  37. package/dist/secretFields.js +159 -0
  38. package/dist/secretFields.js.map +1 -0
  39. package/dist/tables.d.ts +267 -0
  40. package/dist/tables.d.ts.map +1 -0
  41. package/dist/tables.js +22 -0
  42. package/dist/tables.js.map +1 -0
  43. package/dist/vaultRoutes.d.ts +42 -0
  44. package/dist/vaultRoutes.d.ts.map +1 -0
  45. package/dist/vaultRoutes.js +408 -0
  46. package/dist/vaultRoutes.js.map +1 -0
  47. package/dist/vaultUse.d.ts +15 -0
  48. package/dist/vaultUse.d.ts.map +1 -0
  49. package/dist/vaultUse.js +39 -0
  50. package/dist/vaultUse.js.map +1 -0
  51. package/package.json +54 -4
  52. package/src/createVault.ts +45 -0
  53. package/src/hashicorp.ts +466 -0
  54. package/src/index.ts +9 -0
  55. package/src/migrations.ts +29 -0
  56. package/src/module.ts +6 -0
  57. package/src/resolveSecret.ts +96 -0
  58. package/src/sealed.ts +38 -0
  59. package/src/secretBox.ts +72 -0
  60. package/src/secretFields.ts +177 -0
  61. package/src/tables.ts +23 -0
  62. package/src/vaultRoutes.ts +422 -0
  63. package/src/vaultUse.ts +41 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Which parts of a configuration are secrets.
3
+ *
4
+ * Secrets never leave the API as stored: reads get a mask, and a write that sends the mask back means "keep the
5
+ * stored value". The same table drives the config export, so a new secret field is declared in exactly one place.
6
+ * Values read from a vault are references, not secrets, and are not listed here.
7
+ *
8
+ * The table is grouped by type for readability, but masking and restoring look at every secret field of the kind
9
+ * whatever the object's type. A config can hold leftovers of another type (the type changed, or an API client sent
10
+ * extra keys), and those must not read back in the clear.
11
+ */
12
+ export const SECRET_MASK = '••••••••';
13
+ /** Three bullets in a row are never part of a real secret; they are a mask, or a mask somebody edited by hand. */
14
+ const LOOKS_MASKED = /•{3}/;
15
+ /** Credential-looking header names. Wide on purpose: masking `X-Idempotency-Key` costs nothing, leaking `X-Auth` does. */
16
+ export const SENSITIVE_HEADER = /auth|token|secret|key|pass|pwd|credential|cookie|session|signature/i;
17
+ export const uniqueSecretFields = (fields) => {
18
+ const seen = new Set();
19
+ return fields.filter((field) => {
20
+ const id = 'mapAt' in field ? `map:${field.mapAt.join('.')}` : `path:${field.path.join('.')}`;
21
+ return seen.has(id) ? false : (seen.add(id), true);
22
+ });
23
+ };
24
+ const isObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
25
+ function valueAt(root, path) {
26
+ let current = root;
27
+ for (const segment of path) {
28
+ if (!isObject(current))
29
+ return undefined;
30
+ current = current[segment];
31
+ }
32
+ return current;
33
+ }
34
+ /** `••••••••`, plus the last 4 characters of a long token so it can be told apart from another one. */
35
+ function mask(value, tail) {
36
+ return `${SECRET_MASK}${tail && value.length >= 16 ? value.slice(-4) : ''}`;
37
+ }
38
+ export function createSecretFields(kinds) {
39
+ function slotsIn(config, kind) {
40
+ const slots = [];
41
+ for (const field of kinds[kind].fields) {
42
+ if ('mapAt' in field) {
43
+ const holder = valueAt(config, field.mapAt);
44
+ if (!isObject(holder))
45
+ continue;
46
+ for (const key of Object.keys(holder)) {
47
+ if (SENSITIVE_HEADER.test(key))
48
+ slots.push({ holder, key, label: `${field.mapAt.join('.')}.${key}`, tail: false });
49
+ }
50
+ }
51
+ else {
52
+ const holder = valueAt(config, field.path.slice(0, -1));
53
+ const key = field.path[field.path.length - 1];
54
+ if (isObject(holder) && key in holder)
55
+ slots.push({ holder, key, label: field.path.join('.'), tail: field.tail === true });
56
+ }
57
+ }
58
+ return slots;
59
+ }
60
+ /** A copy of `config` with every secret replaced by its mask, whatever its type (a number is as secret as a string). */
61
+ function maskSecrets(kind, config) {
62
+ const copy = structuredClone(config);
63
+ for (const { holder, key, tail } of slotsIn(copy, kind)) {
64
+ const value = holder[key];
65
+ if (value !== null && value !== undefined && value !== '')
66
+ holder[key] = mask(String(value), tail);
67
+ }
68
+ return copy;
69
+ }
70
+ /**
71
+ * Resolves the secrets of an incoming config against the stored one: a mask that matches the stored secret becomes
72
+ * that secret again. Refused instead of saved:
73
+ * - a mask, or something that looks like an edited mask, that matches nothing stored;
74
+ * - a secret that is not text;
75
+ * - a kept secret while the place it is sent to changed (URL, host, port), so it cannot be redirected.
76
+ * Pass `stored` as undefined for a new object (or one whose type changed), where any mask is a mistake.
77
+ */
78
+ function restoreSecrets(kind, incoming, stored) {
79
+ const copy = structuredClone(incoming);
80
+ const storedSlots = new Map(slotsIn(stored, kind).map((slot) => [slot.label, slot]));
81
+ let kept = false;
82
+ for (const { holder, key, label, tail } of slotsIn(copy, kind)) {
83
+ const value = holder[key];
84
+ if (value === null || value === undefined)
85
+ continue;
86
+ if (typeof value !== 'string')
87
+ return { error: `${label} must be text` };
88
+ if (!LOOKS_MASKED.test(value))
89
+ continue;
90
+ const previous = storedSlots.get(label);
91
+ const original = previous?.holder[previous.key];
92
+ if (typeof original === 'string' && original && value === mask(original, tail)) {
93
+ holder[key] = original;
94
+ kept = true;
95
+ }
96
+ else {
97
+ return { error: `${label} is a masked placeholder that does not match the stored value; enter the full value to replace it` };
98
+ }
99
+ }
100
+ if (kept) {
101
+ const moved = changedDestination(kind, copy, stored);
102
+ if (moved)
103
+ return { error: `${moved} changed, so the stored secrets cannot be kept; enter them again` };
104
+ }
105
+ return { config: copy };
106
+ }
107
+ /** The first place a configuration is sent to (URL, host, port) that differs between `incoming` and `stored`, or null. */
108
+ function changedDestination(kind, incoming, stored) {
109
+ for (const path of kinds[kind].destinations) {
110
+ // An empty value means the same as an absent one.
111
+ const normalised = (config) => valueAt(config, path) || null;
112
+ if (JSON.stringify(normalised(incoming)) !== JSON.stringify(normalised(stored)))
113
+ return path.join('.');
114
+ }
115
+ for (const entry of kinds[kind].loosening ?? []) {
116
+ const { path, value } = Array.isArray(entry) ? { path: entry, value: true } : entry;
117
+ if (valueAt(incoming, path) === value && valueAt(stored, path) !== value)
118
+ return path.join('.');
119
+ }
120
+ return null;
121
+ }
122
+ /** A copy of `config` with every secret that is text passed through `convert` (a vault reference or a number is not text and stays). */
123
+ function convertSecrets(kind, config, convert) {
124
+ const copy = structuredClone(config);
125
+ for (const { holder, key } of slotsIn(copy, kind)) {
126
+ const value = holder[key];
127
+ if (typeof value === 'string')
128
+ holder[key] = convert(value);
129
+ }
130
+ return copy;
131
+ }
132
+ /**
133
+ * The codec of a column that stores a configuration as JSON text: the secrets are encrypted on the way in and opened
134
+ * on the way out, so a copy of the database alone does not carry them. Text that is not JSON is passed through.
135
+ */
136
+ function configCodec(kind, sealer) {
137
+ const through = (convert) => (json) => {
138
+ let parsed;
139
+ try {
140
+ parsed = JSON.parse(json);
141
+ }
142
+ catch {
143
+ return json;
144
+ }
145
+ // A config without a secret is returned as it was written, not as JSON.stringify would write it.
146
+ let changed = false;
147
+ const converted = convertSecrets(kind, parsed, (value) => {
148
+ const result = convert(value);
149
+ if (result !== value)
150
+ changed = true;
151
+ return result;
152
+ });
153
+ return changed ? JSON.stringify(converted) : json;
154
+ };
155
+ return { seal: through(sealer.seal), open: through(sealer.open) };
156
+ }
157
+ return { maskSecrets, restoreSecrets, changedDestination, configCodec };
158
+ }
159
+ //# sourceMappingURL=secretFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretFields.js","sourceRoot":"","sources":["../src/secretFields.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,267 @@
1
+ export declare const vaults: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
2
+ name: "vaults";
3
+ schema: undefined;
4
+ columns: {
5
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
6
+ name: "id";
7
+ tableName: "vaults";
8
+ dataType: "number";
9
+ columnType: "SQLiteInteger";
10
+ data: number;
11
+ driverParam: number;
12
+ notNull: true;
13
+ hasDefault: true;
14
+ isPrimaryKey: true;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: undefined;
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {}>;
22
+ name: import("drizzle-orm/sqlite-core").SQLiteColumn<{
23
+ name: "name";
24
+ tableName: "vaults";
25
+ dataType: "string";
26
+ columnType: "SQLiteText";
27
+ data: string;
28
+ driverParam: string;
29
+ notNull: true;
30
+ hasDefault: false;
31
+ isPrimaryKey: false;
32
+ isAutoincrement: false;
33
+ hasRuntimeDefault: false;
34
+ enumValues: [string, ...string[]];
35
+ baseColumn: never;
36
+ identity: undefined;
37
+ generated: undefined;
38
+ }, {}, {
39
+ length: number | undefined;
40
+ }>;
41
+ type: import("drizzle-orm/sqlite-core").SQLiteColumn<{
42
+ name: "type";
43
+ tableName: "vaults";
44
+ dataType: "string";
45
+ columnType: "SQLiteText";
46
+ data: string;
47
+ driverParam: string;
48
+ notNull: true;
49
+ hasDefault: true;
50
+ isPrimaryKey: false;
51
+ isAutoincrement: false;
52
+ hasRuntimeDefault: false;
53
+ enumValues: [string, ...string[]];
54
+ baseColumn: never;
55
+ identity: undefined;
56
+ generated: undefined;
57
+ }, {}, {
58
+ length: number | undefined;
59
+ }>;
60
+ description: import("drizzle-orm/sqlite-core").SQLiteColumn<{
61
+ name: "description";
62
+ tableName: "vaults";
63
+ dataType: "string";
64
+ columnType: "SQLiteText";
65
+ data: string;
66
+ driverParam: string;
67
+ notNull: false;
68
+ hasDefault: false;
69
+ isPrimaryKey: false;
70
+ isAutoincrement: false;
71
+ hasRuntimeDefault: false;
72
+ enumValues: [string, ...string[]];
73
+ baseColumn: never;
74
+ identity: undefined;
75
+ generated: undefined;
76
+ }, {}, {
77
+ length: number | undefined;
78
+ }>;
79
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
80
+ name: "created_at";
81
+ tableName: "vaults";
82
+ dataType: "number";
83
+ columnType: "SQLiteInteger";
84
+ data: number;
85
+ driverParam: number;
86
+ notNull: true;
87
+ hasDefault: false;
88
+ isPrimaryKey: false;
89
+ isAutoincrement: false;
90
+ hasRuntimeDefault: false;
91
+ enumValues: undefined;
92
+ baseColumn: never;
93
+ identity: undefined;
94
+ generated: undefined;
95
+ }, {}, {}>;
96
+ updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
97
+ name: "updated_at";
98
+ tableName: "vaults";
99
+ dataType: "number";
100
+ columnType: "SQLiteInteger";
101
+ data: number;
102
+ driverParam: number;
103
+ notNull: true;
104
+ hasDefault: false;
105
+ isPrimaryKey: false;
106
+ isAutoincrement: false;
107
+ hasRuntimeDefault: false;
108
+ enumValues: undefined;
109
+ baseColumn: never;
110
+ identity: undefined;
111
+ generated: undefined;
112
+ }, {}, {}>;
113
+ connectionConfig: import("drizzle-orm/sqlite-core").SQLiteColumn<{
114
+ name: "connection_config";
115
+ tableName: "vaults";
116
+ dataType: "string";
117
+ columnType: "SQLiteText";
118
+ data: string;
119
+ driverParam: string;
120
+ notNull: false;
121
+ hasDefault: false;
122
+ isPrimaryKey: false;
123
+ isAutoincrement: false;
124
+ hasRuntimeDefault: false;
125
+ enumValues: [string, ...string[]];
126
+ baseColumn: never;
127
+ identity: undefined;
128
+ generated: undefined;
129
+ }, {}, {
130
+ length: number | undefined;
131
+ }>;
132
+ };
133
+ dialect: "sqlite";
134
+ }>;
135
+ export declare const vaultSecrets: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
136
+ name: "vault_secrets";
137
+ schema: undefined;
138
+ columns: {
139
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
140
+ name: "id";
141
+ tableName: "vault_secrets";
142
+ dataType: "number";
143
+ columnType: "SQLiteInteger";
144
+ data: number;
145
+ driverParam: number;
146
+ notNull: true;
147
+ hasDefault: true;
148
+ isPrimaryKey: true;
149
+ isAutoincrement: false;
150
+ hasRuntimeDefault: false;
151
+ enumValues: undefined;
152
+ baseColumn: never;
153
+ identity: undefined;
154
+ generated: undefined;
155
+ }, {}, {}>;
156
+ vaultId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
157
+ name: "vault_id";
158
+ tableName: "vault_secrets";
159
+ dataType: "number";
160
+ columnType: "SQLiteInteger";
161
+ data: number;
162
+ driverParam: number;
163
+ notNull: true;
164
+ hasDefault: false;
165
+ isPrimaryKey: false;
166
+ isAutoincrement: false;
167
+ hasRuntimeDefault: false;
168
+ enumValues: undefined;
169
+ baseColumn: never;
170
+ identity: undefined;
171
+ generated: undefined;
172
+ }, {}, {}>;
173
+ name: import("drizzle-orm/sqlite-core").SQLiteColumn<{
174
+ name: "name";
175
+ tableName: "vault_secrets";
176
+ dataType: "string";
177
+ columnType: "SQLiteText";
178
+ data: string;
179
+ driverParam: string;
180
+ notNull: true;
181
+ hasDefault: false;
182
+ isPrimaryKey: false;
183
+ isAutoincrement: false;
184
+ hasRuntimeDefault: false;
185
+ enumValues: [string, ...string[]];
186
+ baseColumn: never;
187
+ identity: undefined;
188
+ generated: undefined;
189
+ }, {}, {
190
+ length: number | undefined;
191
+ }>;
192
+ type: import("drizzle-orm/sqlite-core").SQLiteColumn<{
193
+ name: "type";
194
+ tableName: "vault_secrets";
195
+ dataType: "string";
196
+ columnType: "SQLiteText";
197
+ data: string;
198
+ driverParam: string;
199
+ notNull: true;
200
+ hasDefault: false;
201
+ isPrimaryKey: false;
202
+ isAutoincrement: false;
203
+ hasRuntimeDefault: false;
204
+ enumValues: [string, ...string[]];
205
+ baseColumn: never;
206
+ identity: undefined;
207
+ generated: undefined;
208
+ }, {}, {
209
+ length: number | undefined;
210
+ }>;
211
+ encryptedValue: import("drizzle-orm/sqlite-core").SQLiteColumn<{
212
+ name: "encrypted_value";
213
+ tableName: "vault_secrets";
214
+ dataType: "string";
215
+ columnType: "SQLiteText";
216
+ data: string;
217
+ driverParam: string;
218
+ notNull: true;
219
+ hasDefault: false;
220
+ isPrimaryKey: false;
221
+ isAutoincrement: false;
222
+ hasRuntimeDefault: false;
223
+ enumValues: [string, ...string[]];
224
+ baseColumn: never;
225
+ identity: undefined;
226
+ generated: undefined;
227
+ }, {}, {
228
+ length: number | undefined;
229
+ }>;
230
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
231
+ name: "created_at";
232
+ tableName: "vault_secrets";
233
+ dataType: "number";
234
+ columnType: "SQLiteInteger";
235
+ data: number;
236
+ driverParam: number;
237
+ notNull: true;
238
+ hasDefault: false;
239
+ isPrimaryKey: false;
240
+ isAutoincrement: false;
241
+ hasRuntimeDefault: false;
242
+ enumValues: undefined;
243
+ baseColumn: never;
244
+ identity: undefined;
245
+ generated: undefined;
246
+ }, {}, {}>;
247
+ updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
248
+ name: "updated_at";
249
+ tableName: "vault_secrets";
250
+ dataType: "number";
251
+ columnType: "SQLiteInteger";
252
+ data: number;
253
+ driverParam: number;
254
+ notNull: true;
255
+ hasDefault: false;
256
+ isPrimaryKey: false;
257
+ isAutoincrement: false;
258
+ hasRuntimeDefault: false;
259
+ enumValues: undefined;
260
+ baseColumn: never;
261
+ identity: undefined;
262
+ generated: undefined;
263
+ }, {}, {}>;
264
+ };
265
+ dialect: "sqlite";
266
+ }>;
267
+ //# sourceMappingURL=tables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../src/tables.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASjB,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASvB,CAAA"}
package/dist/tables.js ADDED
@@ -0,0 +1,22 @@
1
+ import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
2
+ export const vaults = sqliteTable('vaults', {
3
+ id: integer('id').primaryKey({ autoIncrement: true }),
4
+ name: text('name').notNull(),
5
+ type: text('type').notNull().default('local'), // 'local' | 'hashicorp'
6
+ description: text('description'),
7
+ createdAt: integer('created_at').notNull(),
8
+ updatedAt: integer('updated_at').notNull(),
9
+ /** Encrypted JSON connection settings of a 'hashicorp' vault; NULL for 'local'. Never sent to the browser. */
10
+ connectionConfig: text('connection_config'),
11
+ });
12
+ export const vaultSecrets = sqliteTable('vault_secrets', {
13
+ id: integer('id').primaryKey({ autoIncrement: true }),
14
+ vaultId: integer('vault_id').notNull(),
15
+ name: text('name').notNull(),
16
+ type: text('type').notNull(), // 'userpass' | 'value' | 'json'
17
+ /** Local vault: the encrypted value. HashiCorp vault: the encrypted reference `{ path, key? }`. */
18
+ encryptedValue: text('encrypted_value').notNull(),
19
+ createdAt: integer('created_at').notNull(),
20
+ updatedAt: integer('updated_at').notNull(),
21
+ });
22
+ //# sourceMappingURL=tables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tables.js","sourceRoot":"","sources":["../src/tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAEpE,MAAM,CAAC,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,EAAE;IAC1C,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;IACrD,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,wBAAwB;IACvE,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IAC1C,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IAC1C,8GAA8G;IAC9G,gBAAgB,EAAE,IAAI,CAAC,mBAAmB,CAAC;CAC5C,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC,eAAe,EAAE;IACvD,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;IACrD,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE;IACtC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,gCAAgC;IAC9D,mGAAmG;IACnG,cAAc,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE;IACjD,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IAC1C,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CAC3C,CAAC,CAAA"}
@@ -0,0 +1,42 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import type { AuditAction } from '@better-forge/contracts';
3
+ import type { SqliteDatabase } from '@better-forge/db';
4
+ import type { Hashicorp } from './hashicorp.js';
5
+ import type { SecretResolver } from './resolveSecret.js';
6
+ import type { SecretBox } from './secretBox.js';
7
+ export declare function createVaultRoutes(ctx: {
8
+ db: SqliteDatabase;
9
+ secretBox: SecretBox;
10
+ audit: VaultAudit;
11
+ /** The signed-in caller of a request (the routes sit behind the application's own guards). */
12
+ identity: (req: FastifyRequest) => {
13
+ userId: number;
14
+ email: string;
15
+ };
16
+ }, parts: {
17
+ hashicorp: Hashicorp;
18
+ resolver: SecretResolver;
19
+ }): {
20
+ vaultCatalogRoutes: (app: FastifyInstance) => Promise<void>;
21
+ vaultRoutes: (app: FastifyInstance) => Promise<void>;
22
+ };
23
+ /** What the vault routes need to write audit entries; the audit module's instance fits. */
24
+ export interface VaultAudit {
25
+ write(actor: {
26
+ userId: number;
27
+ userEmail: string;
28
+ }, action: AuditAction, entityType: string, entityId: number | string | null, entityName: string, diff?: Record<string, unknown>): Promise<void>;
29
+ actor(identity: {
30
+ userId: number;
31
+ email: string;
32
+ }): {
33
+ userId: number;
34
+ userEmail: string;
35
+ };
36
+ diffObjects(before: Record<string, unknown>, after: Record<string, unknown>): Record<string, {
37
+ from: unknown;
38
+ to: unknown;
39
+ }>;
40
+ snapshot(obj: Record<string, unknown>): Record<string, unknown>;
41
+ }
42
+ //# sourceMappingURL=vaultRoutes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vaultRoutes.d.ts","sourceRoot":"","sources":["../src/vaultRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAwB/C,wBAAgB,iBAAiB,CAAC,GAAG,EAAE;IACrC,EAAE,EAAE,cAAc,CAAA;IAClB,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE,UAAU,CAAA;IACjB,8FAA8F;IAC9F,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACrE,EAAE,KAAK,EAAE;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAA;CAAE;8BA+EnB,eAAe;uBAkBtB,eAAe;EAsRhD;AAED,2FAA2F;AAC3F,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjM,KAAK,CAAC,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IACzF,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAC5H,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChE"}