@coffre/db 0.1.14 → 0.1.16
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-CNMfHZwF.d.ts → database-Cq_52uUo.d.ts} +1 -1
- package/dist/dialect.d.ts +1 -1
- package/dist/hyperdrive.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -4
- package/dist/log.d.ts +1 -1
- package/dist/migrations/postgres/0002_service_bindings.sql +28 -0
- package/dist/migrations/postgres/0003_exchanges.sql +10 -0
- package/dist/migrations/postgres/0004_live_indexes.sql +3 -0
- package/dist/migrations/postgres/meta/0002_snapshot.json +1870 -0
- package/dist/migrations/postgres/meta/0003_snapshot.json +1917 -0
- package/dist/migrations/postgres/meta/0004_snapshot.json +2006 -0
- package/dist/migrations/postgres/meta/_journal.json +21 -0
- package/dist/migrations/sqlite/0002_service_bindings.sql +23 -0
- package/dist/migrations/sqlite/0003_exchanges.sql +7 -0
- package/dist/migrations/sqlite/0004_live_indexes.sql +3 -0
- package/dist/migrations/sqlite/meta/0002_snapshot.json +1736 -0
- package/dist/migrations/sqlite/meta/0003_snapshot.json +1781 -0
- package/dist/migrations/sqlite/meta/0004_snapshot.json +1811 -0
- package/dist/migrations/sqlite/meta/_journal.json +21 -0
- package/dist/portable.d.ts +2 -2
- package/dist/{schema-AmvM6w0D.d.ts → schema-Wt-p8AOh.d.ts} +305 -2
- package/dist/schema-sqlite.d.ts +305 -1
- package/dist/schema-sqlite.js +38 -3
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +74 -26
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +53 -3
- package/package.json +2 -2
- package/src/database.ts +15 -5
- package/src/migrations/postgres/0002_service_bindings.sql +28 -0
- package/src/migrations/postgres/0003_exchanges.sql +10 -0
- package/src/migrations/postgres/0004_live_indexes.sql +3 -0
- package/src/migrations/postgres/meta/0002_snapshot.json +1870 -0
- package/src/migrations/postgres/meta/0003_snapshot.json +1917 -0
- package/src/migrations/postgres/meta/0004_snapshot.json +2006 -0
- package/src/migrations/postgres/meta/_journal.json +21 -0
- package/src/migrations/sqlite/0002_service_bindings.sql +23 -0
- package/src/migrations/sqlite/0003_exchanges.sql +7 -0
- package/src/migrations/sqlite/0004_live_indexes.sql +3 -0
- package/src/migrations/sqlite/meta/0002_snapshot.json +1736 -0
- package/src/migrations/sqlite/meta/0003_snapshot.json +1781 -0
- package/src/migrations/sqlite/meta/0004_snapshot.json +1811 -0
- package/src/migrations/sqlite/meta/_journal.json +21 -0
- package/src/schema.sqlite.ts +61 -0
- package/src/schema.ts +74 -0
|
@@ -15,6 +15,27 @@
|
|
|
15
15
|
"when": 1791062293531,
|
|
16
16
|
"tag": "0001_remove_syncs",
|
|
17
17
|
"breakpoints": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"idx": 2,
|
|
21
|
+
"version": "6",
|
|
22
|
+
"when": 1791069305749,
|
|
23
|
+
"tag": "0002_service_bindings",
|
|
24
|
+
"breakpoints": true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"idx": 3,
|
|
28
|
+
"version": "6",
|
|
29
|
+
"when": 1791072337548,
|
|
30
|
+
"tag": "0003_exchanges",
|
|
31
|
+
"breakpoints": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"idx": 4,
|
|
35
|
+
"version": "6",
|
|
36
|
+
"when": 1791084355588,
|
|
37
|
+
"tag": "0004_live_indexes",
|
|
38
|
+
"breakpoints": true
|
|
18
39
|
}
|
|
19
40
|
]
|
|
20
41
|
}
|
package/src/schema.sqlite.ts
CHANGED
|
@@ -303,6 +303,14 @@ export const auditLog = sqliteTable(
|
|
|
303
303
|
index('audit_log_action_idx').on(table.author, table.action, table.seq),
|
|
304
304
|
index('audit_log_releases_idx').on(table.author, table.actor, table.action, table.decision, table.occurredAt),
|
|
305
305
|
index('audit_log_subject_idx').on(table.author, table.subjectPrincipal, table.seq),
|
|
306
|
+
// A binding's tombstone: the app's successful `token.unbind` naming it.
|
|
307
|
+
index('audit_log_unbind_idx')
|
|
308
|
+
.on(sql`json_extract(${table.metadata}, '$.bindingId')`)
|
|
309
|
+
.where(sql`${table.author} = 'app' AND ${table.action} = 'token.unbind' AND ${table.decision} = 'allow'`),
|
|
310
|
+
// The run a credential was issued for: its exchange's entry, by the credential's ID.
|
|
311
|
+
index('audit_log_exchange_idx')
|
|
312
|
+
.on(sql`json_extract(${table.metadata}, '$.credentialId')`)
|
|
313
|
+
.where(sql`${table.author} = 'app' AND ${table.action} = 'token.exchange' AND ${table.decision} = 'allow'`),
|
|
306
314
|
],
|
|
307
315
|
);
|
|
308
316
|
|
|
@@ -354,6 +362,54 @@ export const identities = sqliteTable(
|
|
|
354
362
|
],
|
|
355
363
|
);
|
|
356
364
|
|
|
365
|
+
export const serviceBindings = sqliteTable(
|
|
366
|
+
'service_bindings',
|
|
367
|
+
{
|
|
368
|
+
id: text().primaryKey(),
|
|
369
|
+
authMac: bytes('auth_mac').notNull(),
|
|
370
|
+
// The service it signs in as, `token:<id>`, under the generation it was bound in.
|
|
371
|
+
principal: text().notNull(),
|
|
372
|
+
generation: integer().notNull(),
|
|
373
|
+
profile: text().notNull(),
|
|
374
|
+
issuer: text().notNull(),
|
|
375
|
+
// From the issuer's discovery when the binding was made, never refetched.
|
|
376
|
+
jwksUri: text('jwks_uri').notNull(),
|
|
377
|
+
// JSON, sorted by claim name.
|
|
378
|
+
claims: text().notNull(),
|
|
379
|
+
label: text(),
|
|
380
|
+
createdAt: createdAt(),
|
|
381
|
+
createdBy: text('created_by').notNull(),
|
|
382
|
+
lastUsedAt: time('last_used_at'),
|
|
383
|
+
revokedAt: time('revoked_at'),
|
|
384
|
+
revokedBy: text('revoked_by'),
|
|
385
|
+
},
|
|
386
|
+
(table) => [
|
|
387
|
+
check('service_bindings_principal_check', sql`${table.principal} LIKE 'token:%'`),
|
|
388
|
+
check('service_bindings_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
389
|
+
check('service_bindings_claims_check', sql`json_valid(${table.claims})`),
|
|
390
|
+
foreignKey({
|
|
391
|
+
name: 'service_bindings_principal_fkey',
|
|
392
|
+
columns: [table.principal],
|
|
393
|
+
foreignColumns: [vaultMembers.principal],
|
|
394
|
+
}).onDelete('restrict'),
|
|
395
|
+
index('service_bindings_principal_idx').on(table.principal, table.issuer),
|
|
396
|
+
// What an exchange may match: a service's live bindings on an issuer, in a
|
|
397
|
+
// generation, in the order it reads them, without the retired history.
|
|
398
|
+
index('service_bindings_live_idx')
|
|
399
|
+
.on(table.principal, table.issuer, table.generation, table.createdAt, table.id)
|
|
400
|
+
.where(sql`${table.revokedAt} IS NULL`),
|
|
401
|
+
],
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
export const consumedTokens = sqliteTable(
|
|
405
|
+
'consumed_tokens',
|
|
406
|
+
{
|
|
407
|
+
hash: bytes('hash').primaryKey(),
|
|
408
|
+
consumedAt: time('consumed_at').notNull().default(now),
|
|
409
|
+
},
|
|
410
|
+
(table) => [check('consumed_tokens_hash_check', sql`octet_length(${table.hash}) = 32`)],
|
|
411
|
+
);
|
|
412
|
+
|
|
357
413
|
export const credentials = sqliteTable(
|
|
358
414
|
'credentials',
|
|
359
415
|
{
|
|
@@ -394,6 +450,11 @@ export const credentials = sqliteTable(
|
|
|
394
450
|
foreignColumns: [identities.id, identities.principal, identities.generation],
|
|
395
451
|
}).onDelete('restrict'),
|
|
396
452
|
index('credentials_principal_idx').on(table.principal, table.generation),
|
|
453
|
+
// A member's credentials still live, however many have expired: a CI run
|
|
454
|
+
// leaves one behind each time it signs in.
|
|
455
|
+
index('credentials_live_idx').on(table.principal, table.expiresAt).where(sql`${table.revokedAt} IS NULL`),
|
|
456
|
+
// What a trust binding issued lately, for its rate.
|
|
457
|
+
index('credentials_issued_by_idx').on(table.principal, table.createdBy, table.createdAt),
|
|
397
458
|
],
|
|
398
459
|
);
|
|
399
460
|
|
package/src/schema.ts
CHANGED
|
@@ -300,6 +300,14 @@ export const auditLog = pgTable(
|
|
|
300
300
|
index('audit_log_releases_idx').on(table.author, table.actor, table.action, table.decision, table.occurredAt),
|
|
301
301
|
// What the vault logged about one member, in order.
|
|
302
302
|
index('audit_log_subject_idx').on(table.author, table.subjectPrincipal, table.seq),
|
|
303
|
+
// A binding's tombstone: the app's successful `token.unbind` naming it.
|
|
304
|
+
index('audit_log_unbind_idx')
|
|
305
|
+
.on(sql`((${table.metadata})::jsonb ->> 'bindingId')`)
|
|
306
|
+
.where(sql`${table.author} = 'app' AND ${table.action} = 'token.unbind' AND ${table.decision} = 'allow'`),
|
|
307
|
+
// The run a credential was issued for: its exchange's entry, by the credential's ID.
|
|
308
|
+
index('audit_log_exchange_idx')
|
|
309
|
+
.on(sql`((${table.metadata})::jsonb ->> 'credentialId')`)
|
|
310
|
+
.where(sql`${table.author} = 'app' AND ${table.action} = 'token.exchange' AND ${table.decision} = 'allow'`),
|
|
303
311
|
],
|
|
304
312
|
);
|
|
305
313
|
|
|
@@ -361,6 +369,67 @@ export const identities = pgTable(
|
|
|
361
369
|
],
|
|
362
370
|
);
|
|
363
371
|
|
|
372
|
+
/**
|
|
373
|
+
* A trust binding: which CI runs may sign in as a service, by the ID token
|
|
374
|
+
* their platform signs (`@coffre/core/identity`'s workloads.ts). Immutable
|
|
375
|
+
* but for its label, last use and revocation: a change is a new binding, and
|
|
376
|
+
* the old one's `token.unbind` entry is its tombstone, which outlives any
|
|
377
|
+
* row put back. The MAC covers what decides (auth-rows.ts in the server).
|
|
378
|
+
*/
|
|
379
|
+
export const serviceBindings = pgTable(
|
|
380
|
+
'service_bindings',
|
|
381
|
+
{
|
|
382
|
+
id: uuid().primaryKey(),
|
|
383
|
+
authMac: bytea('auth_mac').notNull(),
|
|
384
|
+
// The service it signs in as, `token:<id>`, under the generation it was bound in.
|
|
385
|
+
principal: text().notNull(),
|
|
386
|
+
generation: integer().notNull(),
|
|
387
|
+
profile: text().notNull(),
|
|
388
|
+
issuer: text().notNull(),
|
|
389
|
+
// From the issuer's discovery when the binding was made, never refetched.
|
|
390
|
+
jwksUri: text('jwks_uri').notNull(),
|
|
391
|
+
// JSON, sorted by claim name.
|
|
392
|
+
claims: text().notNull(),
|
|
393
|
+
label: text(),
|
|
394
|
+
createdAt: createdAt(),
|
|
395
|
+
createdBy: text('created_by').notNull(),
|
|
396
|
+
lastUsedAt: timestamp('last_used_at', { withTimezone: true }),
|
|
397
|
+
revokedAt: timestamp('revoked_at', { withTimezone: true }),
|
|
398
|
+
revokedBy: text('revoked_by'),
|
|
399
|
+
},
|
|
400
|
+
(table) => [
|
|
401
|
+
check('service_bindings_principal_check', sql`${table.principal} LIKE 'token:%'`),
|
|
402
|
+
check('service_bindings_auth_mac_check', sql`octet_length(${table.authMac}) = 32`),
|
|
403
|
+
check('service_bindings_claims_check', sql`${table.claims}::jsonb IS NOT NULL`),
|
|
404
|
+
foreignKey({
|
|
405
|
+
name: 'service_bindings_principal_fkey',
|
|
406
|
+
columns: [table.principal],
|
|
407
|
+
foreignColumns: [vaultMembers.principal],
|
|
408
|
+
}).onDelete('restrict'),
|
|
409
|
+
index('service_bindings_principal_idx').on(table.principal, table.issuer),
|
|
410
|
+
// What an exchange may match: a service's live bindings on an issuer, in a
|
|
411
|
+
// generation, in the order it reads them, without the retired history.
|
|
412
|
+
index('service_bindings_live_idx')
|
|
413
|
+
.on(table.principal, table.issuer, table.generation, table.createdAt, table.id)
|
|
414
|
+
.where(sql`${table.revokedAt} IS NULL`),
|
|
415
|
+
],
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* ID tokens exchanged for a credential, each once: the SHA-256 of what its
|
|
420
|
+
* signature covers, `header.payload` as received, so that no other
|
|
421
|
+
* spelling of the same token counts as another. Kept whatever becomes of
|
|
422
|
+
* the credential it bought; the primary key decides a race.
|
|
423
|
+
*/
|
|
424
|
+
export const consumedTokens = pgTable(
|
|
425
|
+
'consumed_tokens',
|
|
426
|
+
{
|
|
427
|
+
hash: bytea('hash').primaryKey(),
|
|
428
|
+
consumedAt: timestamp('consumed_at', { withTimezone: true }).notNull().defaultNow(),
|
|
429
|
+
},
|
|
430
|
+
(table) => [check('consumed_tokens_hash_check', sql`octet_length(${table.hash}) = 32`)],
|
|
431
|
+
);
|
|
432
|
+
|
|
364
433
|
/**
|
|
365
434
|
* Bearer credentials coffre issues itself: browser sessions, CLI sessions and
|
|
366
435
|
* service tokens.
|
|
@@ -409,6 +478,11 @@ export const credentials = pgTable(
|
|
|
409
478
|
foreignColumns: [identities.id, identities.principal, identities.generation],
|
|
410
479
|
}).onDelete('restrict'),
|
|
411
480
|
index('credentials_principal_idx').on(table.principal, table.generation),
|
|
481
|
+
// A member's credentials still live, however many have expired: a CI run
|
|
482
|
+
// leaves one behind each time it signs in.
|
|
483
|
+
index('credentials_live_idx').on(table.principal, table.expiresAt).where(sql`${table.revokedAt} IS NULL`),
|
|
484
|
+
// What a trust binding issued lately, for its rate.
|
|
485
|
+
index('credentials_issued_by_idx').on(table.principal, table.createdBy, table.createdAt),
|
|
412
486
|
],
|
|
413
487
|
);
|
|
414
488
|
|