@coffre/db 0.4.6 → 0.5.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.
- package/dist/connect.d.ts +1 -1
- package/dist/{database-edkMkG02.d.ts → database-C63WjCrC.d.ts} +1 -1
- package/dist/dialect.d.ts +1 -1
- package/dist/grants.d.ts +105 -10
- package/dist/grants.js +207 -7
- package/dist/hyperdrive.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/log.d.ts +392 -2
- package/dist/log.js +53 -1
- package/dist/migrate.d.ts +41 -4
- package/dist/migrate.js +95 -8
- package/dist/migrations/postgres/0002_instance_roles.sql +4 -0
- package/dist/migrations/postgres/0003_credential_scopes.sql +4 -0
- package/dist/migrations/postgres/meta/0002_snapshot.json +2860 -0
- package/dist/migrations/postgres/meta/0003_snapshot.json +2872 -0
- package/dist/migrations/postgres/meta/_journal.json +14 -0
- package/dist/migrations/sqlite/0002_instance_roles.sql +2 -0
- package/dist/migrations/sqlite/0003_credential_scopes.sql +2 -0
- package/dist/migrations/sqlite/meta/0002_snapshot.json +2594 -0
- package/dist/migrations/sqlite/meta/0003_snapshot.json +2608 -0
- package/dist/migrations/sqlite/meta/_journal.json +14 -0
- package/dist/portable.d.ts +2 -2
- package/dist/{schema-BqCB3XFm.d.ts → schema-a7PkFgea.d.ts} +68 -0
- package/dist/schema-sqlite.d.ts +76 -0
- package/dist/schema-sqlite.js +4 -0
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +60 -26
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +4 -0
- package/package.json +2 -2
- package/src/grants.ts +272 -11
- package/src/log.ts +66 -2
- package/src/migrate.ts +111 -6
- package/src/migrations/postgres/0002_instance_roles.sql +4 -0
- package/src/migrations/postgres/0003_credential_scopes.sql +4 -0
- package/src/migrations/postgres/meta/0002_snapshot.json +2860 -0
- package/src/migrations/postgres/meta/0003_snapshot.json +2872 -0
- package/src/migrations/postgres/meta/_journal.json +14 -0
- package/src/migrations/sqlite/0002_instance_roles.sql +2 -0
- package/src/migrations/sqlite/0003_credential_scopes.sql +2 -0
- package/src/migrations/sqlite/meta/0002_snapshot.json +2594 -0
- package/src/migrations/sqlite/meta/0003_snapshot.json +2608 -0
- package/src/migrations/sqlite/meta/_journal.json +14 -0
- package/src/schema.sqlite.ts +6 -0
- package/src/schema.ts +11 -1
package/dist/log.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as Transaction } from "./database-
|
|
2
|
-
import { LogKey } from "@coffre/core/audit";
|
|
1
|
+
import { a as Transaction, i as Queryable } from "./database-C63WjCrC.js";
|
|
2
|
+
import { LogKey, StoredEntry } from "@coffre/core/audit";
|
|
3
3
|
//#region src/log.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Appending to the audit log, the one table both the app and the vault
|
|
@@ -75,4 +75,394 @@ export declare function lockLogHead(tx: Transaction): Promise<{
|
|
|
75
75
|
* commits. It throws to refuse.
|
|
76
76
|
*/
|
|
77
77
|
export declare function appendEntries(tx: Transaction, key: LogKey, entries: readonly NewEntry[], check?: (tx: Transaction) => Promise<void>): Promise<Appended>;
|
|
78
|
+
/** An entry's columns, as the chain's codec reads them (`StoredEntry`). */
|
|
79
|
+
export declare function entryColumns(db: Queryable): {
|
|
80
|
+
seq: import("drizzle-orm/pg-core").PgColumn<{
|
|
81
|
+
name: "seq";
|
|
82
|
+
tableName: "audit_log";
|
|
83
|
+
dataType: "bigint";
|
|
84
|
+
columnType: "PgBigInt64";
|
|
85
|
+
data: bigint;
|
|
86
|
+
driverParam: string;
|
|
87
|
+
notNull: true;
|
|
88
|
+
hasDefault: false;
|
|
89
|
+
isPrimaryKey: true;
|
|
90
|
+
isAutoincrement: false;
|
|
91
|
+
hasRuntimeDefault: false;
|
|
92
|
+
enumValues: undefined;
|
|
93
|
+
baseColumn: never;
|
|
94
|
+
identity: undefined;
|
|
95
|
+
generated: undefined;
|
|
96
|
+
}, {}, {}>;
|
|
97
|
+
author: import("drizzle-orm/pg-core").PgColumn<{
|
|
98
|
+
name: "author";
|
|
99
|
+
tableName: "audit_log";
|
|
100
|
+
dataType: "string";
|
|
101
|
+
columnType: "PgText";
|
|
102
|
+
data: string;
|
|
103
|
+
driverParam: string;
|
|
104
|
+
notNull: true;
|
|
105
|
+
hasDefault: false;
|
|
106
|
+
isPrimaryKey: false;
|
|
107
|
+
isAutoincrement: false;
|
|
108
|
+
hasRuntimeDefault: false;
|
|
109
|
+
enumValues: [string, ...string[]];
|
|
110
|
+
baseColumn: never;
|
|
111
|
+
identity: undefined;
|
|
112
|
+
generated: undefined;
|
|
113
|
+
}, {}, {}>;
|
|
114
|
+
keyId: import("drizzle-orm/pg-core").PgColumn<{
|
|
115
|
+
name: "key_id";
|
|
116
|
+
tableName: "audit_log";
|
|
117
|
+
dataType: "string";
|
|
118
|
+
columnType: "PgText";
|
|
119
|
+
data: string;
|
|
120
|
+
driverParam: string;
|
|
121
|
+
notNull: true;
|
|
122
|
+
hasDefault: false;
|
|
123
|
+
isPrimaryKey: false;
|
|
124
|
+
isAutoincrement: false;
|
|
125
|
+
hasRuntimeDefault: false;
|
|
126
|
+
enumValues: [string, ...string[]];
|
|
127
|
+
baseColumn: never;
|
|
128
|
+
identity: undefined;
|
|
129
|
+
generated: undefined;
|
|
130
|
+
}, {}, {}>;
|
|
131
|
+
occurredAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
132
|
+
name: "occurred_at";
|
|
133
|
+
tableName: "audit_log";
|
|
134
|
+
dataType: "number";
|
|
135
|
+
columnType: "PgBigInt53";
|
|
136
|
+
data: number;
|
|
137
|
+
driverParam: string | number;
|
|
138
|
+
notNull: true;
|
|
139
|
+
hasDefault: false;
|
|
140
|
+
isPrimaryKey: false;
|
|
141
|
+
isAutoincrement: false;
|
|
142
|
+
hasRuntimeDefault: false;
|
|
143
|
+
enumValues: undefined;
|
|
144
|
+
baseColumn: never;
|
|
145
|
+
identity: undefined;
|
|
146
|
+
generated: undefined;
|
|
147
|
+
}, {}, {}>;
|
|
148
|
+
actor: import("drizzle-orm/pg-core").PgColumn<{
|
|
149
|
+
name: "actor";
|
|
150
|
+
tableName: "audit_log";
|
|
151
|
+
dataType: "string";
|
|
152
|
+
columnType: "PgText";
|
|
153
|
+
data: string;
|
|
154
|
+
driverParam: string;
|
|
155
|
+
notNull: true;
|
|
156
|
+
hasDefault: false;
|
|
157
|
+
isPrimaryKey: false;
|
|
158
|
+
isAutoincrement: false;
|
|
159
|
+
hasRuntimeDefault: false;
|
|
160
|
+
enumValues: [string, ...string[]];
|
|
161
|
+
baseColumn: never;
|
|
162
|
+
identity: undefined;
|
|
163
|
+
generated: undefined;
|
|
164
|
+
}, {}, {}>;
|
|
165
|
+
action: import("drizzle-orm/pg-core").PgColumn<{
|
|
166
|
+
name: "action";
|
|
167
|
+
tableName: "audit_log";
|
|
168
|
+
dataType: "string";
|
|
169
|
+
columnType: "PgText";
|
|
170
|
+
data: string;
|
|
171
|
+
driverParam: string;
|
|
172
|
+
notNull: true;
|
|
173
|
+
hasDefault: false;
|
|
174
|
+
isPrimaryKey: false;
|
|
175
|
+
isAutoincrement: false;
|
|
176
|
+
hasRuntimeDefault: false;
|
|
177
|
+
enumValues: [string, ...string[]];
|
|
178
|
+
baseColumn: never;
|
|
179
|
+
identity: undefined;
|
|
180
|
+
generated: undefined;
|
|
181
|
+
}, {}, {}>;
|
|
182
|
+
decision: import("drizzle-orm/pg-core").PgColumn<{
|
|
183
|
+
name: "decision";
|
|
184
|
+
tableName: "audit_log";
|
|
185
|
+
dataType: "string";
|
|
186
|
+
columnType: "PgText";
|
|
187
|
+
data: string;
|
|
188
|
+
driverParam: string;
|
|
189
|
+
notNull: true;
|
|
190
|
+
hasDefault: false;
|
|
191
|
+
isPrimaryKey: false;
|
|
192
|
+
isAutoincrement: false;
|
|
193
|
+
hasRuntimeDefault: false;
|
|
194
|
+
enumValues: [string, ...string[]];
|
|
195
|
+
baseColumn: never;
|
|
196
|
+
identity: undefined;
|
|
197
|
+
generated: undefined;
|
|
198
|
+
}, {}, {}>;
|
|
199
|
+
code: import("drizzle-orm/pg-core").PgColumn<{
|
|
200
|
+
name: "code";
|
|
201
|
+
tableName: "audit_log";
|
|
202
|
+
dataType: "string";
|
|
203
|
+
columnType: "PgText";
|
|
204
|
+
data: string;
|
|
205
|
+
driverParam: string;
|
|
206
|
+
notNull: false;
|
|
207
|
+
hasDefault: false;
|
|
208
|
+
isPrimaryKey: false;
|
|
209
|
+
isAutoincrement: false;
|
|
210
|
+
hasRuntimeDefault: false;
|
|
211
|
+
enumValues: [string, ...string[]];
|
|
212
|
+
baseColumn: never;
|
|
213
|
+
identity: undefined;
|
|
214
|
+
generated: undefined;
|
|
215
|
+
}, {}, {}>;
|
|
216
|
+
subjectPrincipal: import("drizzle-orm/pg-core").PgColumn<{
|
|
217
|
+
name: "subject_principal";
|
|
218
|
+
tableName: "audit_log";
|
|
219
|
+
dataType: "string";
|
|
220
|
+
columnType: "PgText";
|
|
221
|
+
data: string;
|
|
222
|
+
driverParam: string;
|
|
223
|
+
notNull: false;
|
|
224
|
+
hasDefault: false;
|
|
225
|
+
isPrimaryKey: false;
|
|
226
|
+
isAutoincrement: false;
|
|
227
|
+
hasRuntimeDefault: false;
|
|
228
|
+
enumValues: [string, ...string[]];
|
|
229
|
+
baseColumn: never;
|
|
230
|
+
identity: undefined;
|
|
231
|
+
generated: undefined;
|
|
232
|
+
}, {}, {}>;
|
|
233
|
+
projectId: import("drizzle-orm/pg-core").PgColumn<{
|
|
234
|
+
name: "project_id";
|
|
235
|
+
tableName: "audit_log";
|
|
236
|
+
dataType: "string";
|
|
237
|
+
columnType: "PgUUID";
|
|
238
|
+
data: string;
|
|
239
|
+
driverParam: string;
|
|
240
|
+
notNull: false;
|
|
241
|
+
hasDefault: false;
|
|
242
|
+
isPrimaryKey: false;
|
|
243
|
+
isAutoincrement: false;
|
|
244
|
+
hasRuntimeDefault: false;
|
|
245
|
+
enumValues: undefined;
|
|
246
|
+
baseColumn: never;
|
|
247
|
+
identity: undefined;
|
|
248
|
+
generated: undefined;
|
|
249
|
+
}, {}, {}>;
|
|
250
|
+
environmentId: import("drizzle-orm/pg-core").PgColumn<{
|
|
251
|
+
name: "environment_id";
|
|
252
|
+
tableName: "audit_log";
|
|
253
|
+
dataType: "string";
|
|
254
|
+
columnType: "PgUUID";
|
|
255
|
+
data: string;
|
|
256
|
+
driverParam: string;
|
|
257
|
+
notNull: false;
|
|
258
|
+
hasDefault: false;
|
|
259
|
+
isPrimaryKey: false;
|
|
260
|
+
isAutoincrement: false;
|
|
261
|
+
hasRuntimeDefault: false;
|
|
262
|
+
enumValues: undefined;
|
|
263
|
+
baseColumn: never;
|
|
264
|
+
identity: undefined;
|
|
265
|
+
generated: undefined;
|
|
266
|
+
}, {}, {}>;
|
|
267
|
+
secretId: import("drizzle-orm/pg-core").PgColumn<{
|
|
268
|
+
name: "secret_id";
|
|
269
|
+
tableName: "audit_log";
|
|
270
|
+
dataType: "string";
|
|
271
|
+
columnType: "PgUUID";
|
|
272
|
+
data: string;
|
|
273
|
+
driverParam: string;
|
|
274
|
+
notNull: false;
|
|
275
|
+
hasDefault: false;
|
|
276
|
+
isPrimaryKey: false;
|
|
277
|
+
isAutoincrement: false;
|
|
278
|
+
hasRuntimeDefault: false;
|
|
279
|
+
enumValues: undefined;
|
|
280
|
+
baseColumn: never;
|
|
281
|
+
identity: undefined;
|
|
282
|
+
generated: undefined;
|
|
283
|
+
}, {}, {}>;
|
|
284
|
+
secretVersionId: import("drizzle-orm/pg-core").PgColumn<{
|
|
285
|
+
name: "secret_version_id";
|
|
286
|
+
tableName: "audit_log";
|
|
287
|
+
dataType: "string";
|
|
288
|
+
columnType: "PgUUID";
|
|
289
|
+
data: string;
|
|
290
|
+
driverParam: string;
|
|
291
|
+
notNull: false;
|
|
292
|
+
hasDefault: false;
|
|
293
|
+
isPrimaryKey: false;
|
|
294
|
+
isAutoincrement: false;
|
|
295
|
+
hasRuntimeDefault: false;
|
|
296
|
+
enumValues: undefined;
|
|
297
|
+
baseColumn: never;
|
|
298
|
+
identity: undefined;
|
|
299
|
+
generated: undefined;
|
|
300
|
+
}, {}, {}>;
|
|
301
|
+
operationId: import("drizzle-orm/pg-core").PgColumn<{
|
|
302
|
+
name: "operation_id";
|
|
303
|
+
tableName: "audit_log";
|
|
304
|
+
dataType: "string";
|
|
305
|
+
columnType: "PgUUID";
|
|
306
|
+
data: string;
|
|
307
|
+
driverParam: string;
|
|
308
|
+
notNull: false;
|
|
309
|
+
hasDefault: false;
|
|
310
|
+
isPrimaryKey: false;
|
|
311
|
+
isAutoincrement: false;
|
|
312
|
+
hasRuntimeDefault: false;
|
|
313
|
+
enumValues: undefined;
|
|
314
|
+
baseColumn: never;
|
|
315
|
+
identity: undefined;
|
|
316
|
+
generated: undefined;
|
|
317
|
+
}, {}, {}>;
|
|
318
|
+
requestId: import("drizzle-orm/pg-core").PgColumn<{
|
|
319
|
+
name: "request_id";
|
|
320
|
+
tableName: "audit_log";
|
|
321
|
+
dataType: "string";
|
|
322
|
+
columnType: "PgText";
|
|
323
|
+
data: string;
|
|
324
|
+
driverParam: string;
|
|
325
|
+
notNull: false;
|
|
326
|
+
hasDefault: false;
|
|
327
|
+
isPrimaryKey: false;
|
|
328
|
+
isAutoincrement: false;
|
|
329
|
+
hasRuntimeDefault: false;
|
|
330
|
+
enumValues: [string, ...string[]];
|
|
331
|
+
baseColumn: never;
|
|
332
|
+
identity: undefined;
|
|
333
|
+
generated: undefined;
|
|
334
|
+
}, {}, {}>;
|
|
335
|
+
sourceIp: import("drizzle-orm/pg-core").PgColumn<{
|
|
336
|
+
name: "source_ip";
|
|
337
|
+
tableName: "audit_log";
|
|
338
|
+
dataType: "string";
|
|
339
|
+
columnType: "PgText";
|
|
340
|
+
data: string;
|
|
341
|
+
driverParam: string;
|
|
342
|
+
notNull: false;
|
|
343
|
+
hasDefault: false;
|
|
344
|
+
isPrimaryKey: false;
|
|
345
|
+
isAutoincrement: false;
|
|
346
|
+
hasRuntimeDefault: false;
|
|
347
|
+
enumValues: [string, ...string[]];
|
|
348
|
+
baseColumn: never;
|
|
349
|
+
identity: undefined;
|
|
350
|
+
generated: undefined;
|
|
351
|
+
}, {}, {}>;
|
|
352
|
+
relatedSeq: import("drizzle-orm/pg-core").PgColumn<{
|
|
353
|
+
name: "related_seq";
|
|
354
|
+
tableName: "audit_log";
|
|
355
|
+
dataType: "bigint";
|
|
356
|
+
columnType: "PgBigInt64";
|
|
357
|
+
data: bigint;
|
|
358
|
+
driverParam: string;
|
|
359
|
+
notNull: false;
|
|
360
|
+
hasDefault: false;
|
|
361
|
+
isPrimaryKey: false;
|
|
362
|
+
isAutoincrement: false;
|
|
363
|
+
hasRuntimeDefault: false;
|
|
364
|
+
enumValues: undefined;
|
|
365
|
+
baseColumn: never;
|
|
366
|
+
identity: undefined;
|
|
367
|
+
generated: undefined;
|
|
368
|
+
}, {}, {}>;
|
|
369
|
+
metadata: import("drizzle-orm/pg-core").PgColumn<{
|
|
370
|
+
name: "metadata";
|
|
371
|
+
tableName: "audit_log";
|
|
372
|
+
dataType: "string";
|
|
373
|
+
columnType: "PgText";
|
|
374
|
+
data: string;
|
|
375
|
+
driverParam: string;
|
|
376
|
+
notNull: true;
|
|
377
|
+
hasDefault: true;
|
|
378
|
+
isPrimaryKey: false;
|
|
379
|
+
isAutoincrement: false;
|
|
380
|
+
hasRuntimeDefault: false;
|
|
381
|
+
enumValues: [string, ...string[]];
|
|
382
|
+
baseColumn: never;
|
|
383
|
+
identity: undefined;
|
|
384
|
+
generated: undefined;
|
|
385
|
+
}, {}, {}>;
|
|
386
|
+
prevHash: import("drizzle-orm/pg-core").PgColumn<{
|
|
387
|
+
name: "prev_hash";
|
|
388
|
+
tableName: "audit_log";
|
|
389
|
+
dataType: "custom";
|
|
390
|
+
columnType: "PgCustomColumn";
|
|
391
|
+
data: Buffer<ArrayBufferLike>;
|
|
392
|
+
driverParam: unknown;
|
|
393
|
+
notNull: true;
|
|
394
|
+
hasDefault: false;
|
|
395
|
+
isPrimaryKey: false;
|
|
396
|
+
isAutoincrement: false;
|
|
397
|
+
hasRuntimeDefault: false;
|
|
398
|
+
enumValues: undefined;
|
|
399
|
+
baseColumn: never;
|
|
400
|
+
identity: undefined;
|
|
401
|
+
generated: undefined;
|
|
402
|
+
}, {}, {
|
|
403
|
+
pgColumnBuilderBrand: "PgCustomColumnBuilderBrand";
|
|
404
|
+
}>;
|
|
405
|
+
mac: import("drizzle-orm/pg-core").PgColumn<{
|
|
406
|
+
name: "mac";
|
|
407
|
+
tableName: "audit_log";
|
|
408
|
+
dataType: "custom";
|
|
409
|
+
columnType: "PgCustomColumn";
|
|
410
|
+
data: Buffer<ArrayBufferLike>;
|
|
411
|
+
driverParam: unknown;
|
|
412
|
+
notNull: true;
|
|
413
|
+
hasDefault: false;
|
|
414
|
+
isPrimaryKey: false;
|
|
415
|
+
isAutoincrement: false;
|
|
416
|
+
hasRuntimeDefault: false;
|
|
417
|
+
enumValues: undefined;
|
|
418
|
+
baseColumn: never;
|
|
419
|
+
identity: undefined;
|
|
420
|
+
generated: undefined;
|
|
421
|
+
}, {}, {
|
|
422
|
+
pgColumnBuilderBrand: "PgCustomColumnBuilderBrand";
|
|
423
|
+
}>;
|
|
424
|
+
hash: import("drizzle-orm/pg-core").PgColumn<{
|
|
425
|
+
name: "hash";
|
|
426
|
+
tableName: "audit_log";
|
|
427
|
+
dataType: "custom";
|
|
428
|
+
columnType: "PgCustomColumn";
|
|
429
|
+
data: Buffer<ArrayBufferLike>;
|
|
430
|
+
driverParam: unknown;
|
|
431
|
+
notNull: true;
|
|
432
|
+
hasDefault: false;
|
|
433
|
+
isPrimaryKey: false;
|
|
434
|
+
isAutoincrement: false;
|
|
435
|
+
hasRuntimeDefault: false;
|
|
436
|
+
enumValues: undefined;
|
|
437
|
+
baseColumn: never;
|
|
438
|
+
identity: undefined;
|
|
439
|
+
generated: undefined;
|
|
440
|
+
}, {}, {
|
|
441
|
+
pgColumnBuilderBrand: "PgCustomColumnBuilderBrand";
|
|
442
|
+
}>;
|
|
443
|
+
};
|
|
444
|
+
/**
|
|
445
|
+
* How a reader tells the vault's own entries from those forged in its name:
|
|
446
|
+
* by their MAC, which only the vault holds the key to check. Each it finds
|
|
447
|
+
* forged goes to `forged`.
|
|
448
|
+
*/
|
|
449
|
+
export type Authenticity = {
|
|
450
|
+
authentic: (entry: StoredEntry) => boolean;
|
|
451
|
+
forged: (entry: StoredEntry) => void;
|
|
452
|
+
};
|
|
453
|
+
/** How many entries in its name the vault reads back, a page at a time, for its own newest of a kind (`newestAuthentic`). */
|
|
454
|
+
export declare const LOOK_BACK: {
|
|
455
|
+
page: number;
|
|
456
|
+
pages: number;
|
|
457
|
+
};
|
|
458
|
+
/** What `newestAuthentic` finds: the entry, or why there is none. */
|
|
459
|
+
export type Newest = StoredEntry | 'none' | 'forged' | 'unreached';
|
|
460
|
+
/**
|
|
461
|
+
* The newest of the entries `page` reads, newest first and before a seq,
|
|
462
|
+
* that `check` finds authentic, read back a page at a time: 'none' when
|
|
463
|
+
* there are none at all, 'forged' when every one is in the vault's name but
|
|
464
|
+
* not its own, and 'unreached' once it has read LOOK_BACK such entries
|
|
465
|
+
* (1024), whether or not more are left.
|
|
466
|
+
*/
|
|
467
|
+
export declare function newestAuthentic(page: (before: bigint | undefined) => Promise<StoredEntry[]>, check: Authenticity): Promise<Newest>;
|
|
78
468
|
//#endregion
|
package/dist/log.js
CHANGED
|
@@ -136,5 +136,57 @@ async function appendEntries(tx, key, entries, check) {
|
|
|
136
136
|
occurredAt: now
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
+
/** An entry's columns, as the chain's codec reads them (`StoredEntry`). */
|
|
140
|
+
function entryColumns(db) {
|
|
141
|
+
const { auditLog } = tablesOf(db);
|
|
142
|
+
return {
|
|
143
|
+
seq: auditLog.seq,
|
|
144
|
+
author: auditLog.author,
|
|
145
|
+
keyId: auditLog.keyId,
|
|
146
|
+
occurredAt: auditLog.occurredAt,
|
|
147
|
+
actor: auditLog.actor,
|
|
148
|
+
action: auditLog.action,
|
|
149
|
+
decision: auditLog.decision,
|
|
150
|
+
code: auditLog.code,
|
|
151
|
+
subjectPrincipal: auditLog.subjectPrincipal,
|
|
152
|
+
projectId: auditLog.projectId,
|
|
153
|
+
environmentId: auditLog.environmentId,
|
|
154
|
+
secretId: auditLog.secretId,
|
|
155
|
+
secretVersionId: auditLog.secretVersionId,
|
|
156
|
+
operationId: auditLog.operationId,
|
|
157
|
+
requestId: auditLog.requestId,
|
|
158
|
+
sourceIp: auditLog.sourceIp,
|
|
159
|
+
relatedSeq: auditLog.relatedSeq,
|
|
160
|
+
metadata: auditLog.metadata,
|
|
161
|
+
prevHash: auditLog.prevHash,
|
|
162
|
+
mac: auditLog.mac,
|
|
163
|
+
hash: auditLog.hash
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** How many entries in its name the vault reads back, a page at a time, for its own newest of a kind (`newestAuthentic`). */
|
|
167
|
+
const LOOK_BACK = {
|
|
168
|
+
page: 32,
|
|
169
|
+
pages: 32
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* The newest of the entries `page` reads, newest first and before a seq,
|
|
173
|
+
* that `check` finds authentic, read back a page at a time: 'none' when
|
|
174
|
+
* there are none at all, 'forged' when every one is in the vault's name but
|
|
175
|
+
* not its own, and 'unreached' once it has read LOOK_BACK such entries
|
|
176
|
+
* (1024), whether or not more are left.
|
|
177
|
+
*/
|
|
178
|
+
async function newestAuthentic(page, check) {
|
|
179
|
+
let before;
|
|
180
|
+
for (let read = 0; read < LOOK_BACK.pages; read += 1) {
|
|
181
|
+
const entries = await page(before);
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
if (check.authentic(entry)) return entry;
|
|
184
|
+
check.forged(entry);
|
|
185
|
+
}
|
|
186
|
+
if (entries.length < LOOK_BACK.page) return before === void 0 && entries.length === 0 ? "none" : "forged";
|
|
187
|
+
before = entries.at(-1).seq;
|
|
188
|
+
}
|
|
189
|
+
return "unreached";
|
|
190
|
+
}
|
|
139
191
|
//#endregion
|
|
140
|
-
export { LogHeadMismatch, LogRewound, appendEntries, forgetLogHeads, lockLogHead };
|
|
192
|
+
export { LOOK_BACK, LogHeadMismatch, LogRewound, appendEntries, entryColumns, forgetLogHeads, lockLogHead, newestAuthentic };
|
package/dist/migrate.d.ts
CHANGED
|
@@ -3,6 +3,40 @@ import { PoolClient } from "pg";
|
|
|
3
3
|
//#region src/migrate.d.ts
|
|
4
4
|
/** Reassert database privileges that pg_dump and CREATE DATABASE TEMPLATE do not carry. */
|
|
5
5
|
export declare function restrictDatabase(client: Pick<PoolClient, 'query'>): Promise<void>;
|
|
6
|
+
/** An owner of coffre's objects whose privileges the login lacks: not a member of it, or one that does not inherit them. */
|
|
7
|
+
export type ForeignOwner = {
|
|
8
|
+
name: string;
|
|
9
|
+
member: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The login cannot change coffre's objects, the ledger's schema, the tables
|
|
13
|
+
* and the functions: their owners are roles whose privileges it does not
|
|
14
|
+
* have, as neither one of them nor a member of one that inherits them.
|
|
15
|
+
* Postgres would refuse the first migration that touches one, "permission
|
|
16
|
+
* denied for schema drizzle"; this says whose login migrates, or what makes
|
|
17
|
+
* this one able to: on Postgres 16 and later, a membership that inherits,
|
|
18
|
+
* which each one's GRANT says; before, a membership, and a login that
|
|
19
|
+
* inherits (`ALTER ROLE … INHERIT`).
|
|
20
|
+
*/
|
|
21
|
+
export declare class NotOwner extends Error {
|
|
22
|
+
readonly login: string;
|
|
23
|
+
readonly owners: ForeignOwner[];
|
|
24
|
+
constructor(login: string, owners: ForeignOwner[], { grantsInherit, inherits }: {
|
|
25
|
+
grantsInherit: boolean;
|
|
26
|
+
inherits: boolean;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** What coffre's migrations make and so change later: relations and functions, by name, in public. */
|
|
30
|
+
type Managed = {
|
|
31
|
+
relations: string[];
|
|
32
|
+
functions: string[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The names a migration's SQL makes: CREATE TABLE, VIEW, MATERIALIZED VIEW
|
|
36
|
+
* and SEQUENCE, and CREATE FUNCTION, in public. Unquoted, a name is folded
|
|
37
|
+
* to lower case, as Postgres folds it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function managedObjects(sqlTexts: readonly string[]): Managed;
|
|
6
40
|
/**
|
|
7
41
|
* Beside this module: src/migrations in the workspace, and dist/migrations
|
|
8
42
|
* in the built package, where the build copies them.
|
|
@@ -35,17 +69,20 @@ export type MigrationPlan = {
|
|
|
35
69
|
};
|
|
36
70
|
/**
|
|
37
71
|
* Which of this version's migrations a database has applied, and which it
|
|
38
|
-
* has not, without changing it or waiting on a migration under way
|
|
39
|
-
*
|
|
40
|
-
*
|
|
72
|
+
* has not, without changing it or waiting on a migration under way; and
|
|
73
|
+
* whether this login could apply them (`notOwner`), as `migrateDatabase`
|
|
74
|
+
* requires: thrown, when it cannot read which. A history that is not a
|
|
75
|
+
* prefix of this version's is refused, as `migrateDatabase` refuses it.
|
|
41
76
|
*/
|
|
42
77
|
export declare function migrationStatus(url: string): Promise<{
|
|
43
78
|
applied: string[];
|
|
44
79
|
pending: string[];
|
|
80
|
+
notOwner: NotOwner | null;
|
|
45
81
|
}>;
|
|
46
82
|
/**
|
|
47
83
|
* Apply missing migrations, check their history and reassert database
|
|
48
|
-
* privileges
|
|
84
|
+
* privileges, as a login that can change coffre's objects: another is
|
|
85
|
+
* refused, NotOwner, before anything changes. `onPlan` hears what there is to apply before it is, under
|
|
49
86
|
* the migration lock, and may stop the run by throwing: all of it then goes
|
|
50
87
|
* in one call, with no word of each.
|
|
51
88
|
*/
|
package/dist/migrate.js
CHANGED
|
@@ -25,6 +25,71 @@ async function restrictDatabase(client) {
|
|
|
25
25
|
END $$`);
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
+
* The login cannot change coffre's objects, the ledger's schema, the tables
|
|
29
|
+
* and the functions: their owners are roles whose privileges it does not
|
|
30
|
+
* have, as neither one of them nor a member of one that inherits them.
|
|
31
|
+
* Postgres would refuse the first migration that touches one, "permission
|
|
32
|
+
* denied for schema drizzle"; this says whose login migrates, or what makes
|
|
33
|
+
* this one able to: on Postgres 16 and later, a membership that inherits,
|
|
34
|
+
* which each one's GRANT says; before, a membership, and a login that
|
|
35
|
+
* inherits (`ALTER ROLE … INHERIT`).
|
|
36
|
+
*/
|
|
37
|
+
var NotOwner = class extends Error {
|
|
38
|
+
login;
|
|
39
|
+
owners;
|
|
40
|
+
constructor(login, owners, { grantsInherit, inherits }) {
|
|
41
|
+
const one = owners.length === 1;
|
|
42
|
+
const members = owners.every((owner) => owner.member);
|
|
43
|
+
const fixes = [...owners.filter((owner) => grantsInherit || !owner.member).map((owner) => `GRANT ${identifier(owner.name)} TO ${identifier(login)}${grantsInherit ? " WITH INHERIT TRUE" : ""}`), ...grantsInherit || inherits ? [] : [`ALTER ROLE ${identifier(login)} INHERIT`]];
|
|
44
|
+
super(`coffre's tables are owned by ${owners.map((owner) => owner.name).join(" and ")}, and this login, ${login}, ` + (members ? `is a member of ${one ? "it" : "them"} but does not inherit ${one ? "its" : "their"} privileges: ` : `is neither ${one ? "it nor a member of it" : "one nor a member of one"}: `) + `migrate with ${one ? `${owners[0].name}'s` : "their"} connection string, or, as a role that administers ${one ? "it" : "them"}, run ${fixes.join("; ")}`);
|
|
45
|
+
this.name = "NotOwner";
|
|
46
|
+
this.login = login;
|
|
47
|
+
this.owners = owners;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/** A role's name as SQL takes it: always quoted, as a keyword such as `user` must be. */
|
|
51
|
+
function identifier(name) {
|
|
52
|
+
return `"${name.replace(/"/g, "\"\"")}"`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The names a migration's SQL makes: CREATE TABLE, VIEW, MATERIALIZED VIEW
|
|
56
|
+
* and SEQUENCE, and CREATE FUNCTION, in public. Unquoted, a name is folded
|
|
57
|
+
* to lower case, as Postgres folds it.
|
|
58
|
+
*/
|
|
59
|
+
function managedObjects(sqlTexts) {
|
|
60
|
+
const name = String.raw`(?:"?public"?\.)?(?:"((?:[^"]|"")+)"|([A-Za-z_][A-Za-z0-9_$]*))`;
|
|
61
|
+
const relation = new RegExp(String.raw`\bCREATE\s+(?:OR\s+REPLACE\s+)?(?:UNLOGGED\s+)?(?:TABLE|VIEW|MATERIALIZED\s+VIEW|SEQUENCE)\s+(?:IF\s+NOT\s+EXISTS\s+)?${name}`, "gi");
|
|
62
|
+
const fn = new RegExp(String.raw`\bCREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+${name}\s*\(`, "gi");
|
|
63
|
+
const names = (pattern) => [...new Set(sqlTexts.flatMap((text) => [...text.matchAll(pattern)].map(([, quoted, bare]) => quoted !== void 0 ? quoted.replace(/""/g, "\"") : bare.toLowerCase())))];
|
|
64
|
+
return {
|
|
65
|
+
relations: names(relation),
|
|
66
|
+
functions: names(fn)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The owners of what migrations change, whose privileges the login lacks:
|
|
71
|
+
* the drizzle schema and everything in it, and in public the relations and
|
|
72
|
+
* functions coffre's migrations make ($1, $2), and nothing else of public,
|
|
73
|
+
* which may hold a database's other tables. A superuser has every role's.
|
|
74
|
+
*/
|
|
75
|
+
const FOREIGN_OWNERS = `
|
|
76
|
+
WITH owned AS (
|
|
77
|
+
SELECT nspowner AS owner FROM pg_namespace WHERE nspname = 'drizzle'
|
|
78
|
+
UNION ALL
|
|
79
|
+
SELECT c.relowner FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
80
|
+
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'S', 'f')
|
|
81
|
+
AND (n.nspname = 'drizzle' OR (n.nspname = 'public' AND c.relname = ANY($1::text[])))
|
|
82
|
+
UNION ALL
|
|
83
|
+
SELECT p.proowner FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
84
|
+
WHERE n.nspname = 'public' AND p.proname = ANY($2::text[])
|
|
85
|
+
)
|
|
86
|
+
SELECT DISTINCT r.rolname AS owner, pg_has_role(owned.owner, 'MEMBER') AS member, current_user AS login,
|
|
87
|
+
current_setting('server_version_num')::int >= 160000 AS grants_inherit,
|
|
88
|
+
(SELECT rolinherit FROM pg_roles WHERE rolname = current_user) AS inherits
|
|
89
|
+
FROM owned JOIN pg_roles r ON r.oid = owned.owner
|
|
90
|
+
WHERE NOT pg_has_role(owned.owner, 'USAGE')
|
|
91
|
+
ORDER BY r.rolname`;
|
|
92
|
+
/**
|
|
28
93
|
* Beside this module: src/migrations in the workspace, and dist/migrations
|
|
29
94
|
* in the built package, where the build copies them.
|
|
30
95
|
*/
|
|
@@ -41,7 +106,8 @@ async function expectedMigrations(engine) {
|
|
|
41
106
|
return {
|
|
42
107
|
createdAt: String(entry.when),
|
|
43
108
|
hash: createHash("sha256").update(sqlText).digest("hex"),
|
|
44
|
-
tag: entry.tag
|
|
109
|
+
tag: entry.tag,
|
|
110
|
+
sql: sqlText
|
|
45
111
|
};
|
|
46
112
|
}));
|
|
47
113
|
for (let index = 1; index < expected.length; index += 1) if (BigInt(expected[index - 1].createdAt) >= BigInt(expected[index].createdAt)) throw new Error("Drizzle migration journal timestamps must be strictly increasing");
|
|
@@ -106,6 +172,18 @@ async function postgresMigrator(url) {
|
|
|
106
172
|
},
|
|
107
173
|
migrate: (migrationsFolder) => migrate(drizzle(client), { migrationsFolder }),
|
|
108
174
|
restrict: () => restrictDatabase(client),
|
|
175
|
+
async notOwner({ relations, functions }) {
|
|
176
|
+
const { rows } = await client.query(FOREIGN_OWNERS, [relations, functions]);
|
|
177
|
+
if (rows.length === 0) return null;
|
|
178
|
+
const [{ login, grants_inherit: grantsInherit, inherits }] = rows;
|
|
179
|
+
return new NotOwner(login, rows.map(({ owner, member }) => ({
|
|
180
|
+
name: owner,
|
|
181
|
+
member
|
|
182
|
+
})), {
|
|
183
|
+
grantsInherit,
|
|
184
|
+
inherits
|
|
185
|
+
});
|
|
186
|
+
},
|
|
109
187
|
async close() {
|
|
110
188
|
client.release();
|
|
111
189
|
await pool.end();
|
|
@@ -125,14 +203,16 @@ async function sqliteMigrator(url) {
|
|
|
125
203
|
async unlock() {},
|
|
126
204
|
migrate: (migrationsFolder) => migrate(sqlite, { migrationsFolder }),
|
|
127
205
|
async restrict() {},
|
|
206
|
+
notOwner: async () => null,
|
|
128
207
|
close
|
|
129
208
|
};
|
|
130
209
|
}
|
|
131
210
|
/**
|
|
132
211
|
* Which of this version's migrations a database has applied, and which it
|
|
133
|
-
* has not, without changing it or waiting on a migration under way
|
|
134
|
-
*
|
|
135
|
-
*
|
|
212
|
+
* has not, without changing it or waiting on a migration under way; and
|
|
213
|
+
* whether this login could apply them (`notOwner`), as `migrateDatabase`
|
|
214
|
+
* requires: thrown, when it cannot read which. A history that is not a
|
|
215
|
+
* prefix of this version's is refused, as `migrateDatabase` refuses it.
|
|
136
216
|
*/
|
|
137
217
|
async function migrationStatus(url) {
|
|
138
218
|
const engine = engineOfUrl(url);
|
|
@@ -142,12 +222,16 @@ async function migrationStatus(url) {
|
|
|
142
222
|
sqlite: sqliteMigrator
|
|
143
223
|
}[engine](url);
|
|
144
224
|
try {
|
|
145
|
-
const
|
|
225
|
+
const notOwner = await migrator.notOwner(managedObjects(expected.map((entry) => entry.sql)));
|
|
226
|
+
const history = await migrator.applied().catch((error) => {
|
|
227
|
+
throw notOwner ?? error;
|
|
228
|
+
});
|
|
146
229
|
verifyHistory(expected, history, false);
|
|
147
230
|
const applied = history?.length ?? 0;
|
|
148
231
|
return {
|
|
149
232
|
applied: expected.slice(0, applied).map((entry) => entry.tag),
|
|
150
|
-
pending: expected.slice(applied).map((entry) => entry.tag)
|
|
233
|
+
pending: expected.slice(applied).map((entry) => entry.tag),
|
|
234
|
+
notOwner
|
|
151
235
|
};
|
|
152
236
|
} finally {
|
|
153
237
|
await migrator.close();
|
|
@@ -155,7 +239,8 @@ async function migrationStatus(url) {
|
|
|
155
239
|
}
|
|
156
240
|
/**
|
|
157
241
|
* Apply missing migrations, check their history and reassert database
|
|
158
|
-
* privileges
|
|
242
|
+
* privileges, as a login that can change coffre's objects: another is
|
|
243
|
+
* refused, NotOwner, before anything changes. `onPlan` hears what there is to apply before it is, under
|
|
159
244
|
* the migration lock, and may stop the run by throwing: all of it then goes
|
|
160
245
|
* in one call, with no word of each.
|
|
161
246
|
*/
|
|
@@ -170,6 +255,8 @@ async function migrateDatabase(url, onPlan) {
|
|
|
170
255
|
try {
|
|
171
256
|
await migrator.lock();
|
|
172
257
|
locked = true;
|
|
258
|
+
const notOwner = await migrator.notOwner(managedObjects(expected.map((entry) => entry.sql)));
|
|
259
|
+
if (notOwner !== null) throw notOwner;
|
|
173
260
|
const applied = await migrator.applied();
|
|
174
261
|
verifyHistory(expected, applied, false);
|
|
175
262
|
const done = applied?.length ?? 0;
|
|
@@ -190,4 +277,4 @@ async function migrateDatabase(url, onPlan) {
|
|
|
190
277
|
}
|
|
191
278
|
}
|
|
192
279
|
//#endregion
|
|
193
|
-
export { DatabaseAhead, migrateDatabase, migrationStatus, migrationsFolder, restrictDatabase };
|
|
280
|
+
export { DatabaseAhead, NotOwner, managedObjects, migrateDatabase, migrationStatus, migrationsFolder, restrictDatabase };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
ALTER TABLE "vault_members" ADD COLUMN "role" text;--> statement-breakpoint
|
|
2
|
+
ALTER TABLE "vault_members" ADD COLUMN "scope" text;--> statement-breakpoint
|
|
3
|
+
-- The vault sets a person's instance role and its scope, as it sets the rest of their row.
|
|
4
|
+
GRANT UPDATE (role, scope) ON vault_members TO coffre_vault;
|