@coffre/vault 0.10.1 → 0.11.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/src/store.ts CHANGED
@@ -88,10 +88,10 @@ function memberColumns(db: Queryable) {
88
88
  }
89
89
 
90
90
  /** A row as stored: `role` and `scope` as their columns hold them. */
91
- type StoredMember = Omit<Member, 'role' | 'scope'> & { owner: boolean; role: string | null; scope: string | null };
91
+ export type StoredMember = Omit<Member, 'role' | 'scope'> & { owner: boolean; role: string | null; scope: string | null };
92
92
 
93
93
  /** A member as their row says (`storedRole`). */
94
- function memberOf({ owner, role, scope, ...row }: StoredMember): Member {
94
+ export function memberOf({ owner, role, scope, ...row }: StoredMember): Member {
95
95
  return { ...row, ...storedRole({ owner, role, scope }) };
96
96
  }
97
97
 
@@ -308,38 +308,6 @@ export async function everyProjectHolders(db: Queryable): Promise<string[]> {
308
308
 
309
309
  // --- the log --------------------------------------------------------------------
310
310
 
311
- /** The action of an entry that releases a key, which the bulk limit counts. */
312
- export const RELEASE = 'secret.read';
313
-
314
- /**
315
- * How many keys the vault has released to `principal` since `after`: one
316
- * for an entry about one secret, and as many as it lists for one about
317
- * several (`secrets`), a read logged once per place.
318
- */
319
- export async function releasesSince(db: Queryable, principal: string, after: number): Promise<number> {
320
- const [{ n }] = await released(db, { principal, after });
321
- return Number(n ?? 0);
322
- }
323
-
324
- const released = prepared((db) => {
325
- const { auditLog } = tablesOf(db);
326
- const listed = engineOf(db) === 'postgres'
327
- ? sql`jsonb_array_length((${auditLog.metadata})::jsonb -> 'secrets')`
328
- : sql`json_array_length(${auditLog.metadata}, '$.secrets')`;
329
- return db
330
- .select({ n: sql<string | number | null>`SUM(COALESCE(${listed}, 1))` })
331
- .from(auditLog)
332
- .where(
333
- and(
334
- eq(auditLog.author, 'vault'),
335
- eq(auditLog.actor, sql.placeholder('principal')),
336
- eq(auditLog.action, RELEASE),
337
- eq(auditLog.decision, 'allow'),
338
- gt(auditLog.occurredAt, sql.placeholder('after')),
339
- ),
340
- );
341
- });
342
-
343
311
  /** Rows of the log as the chain's codec reads them: `author` and `decision` are checked by the table. */
344
312
  function stored(rows: object[]): StoredEntry[] {
345
313
  return rows as StoredEntry[];
@@ -504,8 +472,10 @@ export async function natures(db: Queryable, secretIds: readonly string[]): Prom
504
472
 
505
473
  /**
506
474
  * For each version a rewrap would bring back into its secret, its nature
507
- * as stored when it is an old value, not the secret's current one; null
508
- * for the current one, which a mark wraps again with no value changing.
475
+ * when it is an old value, no looser than it was stored nor than its value
476
+ * was made since (the newest version of its very ciphertext), not the
477
+ * secret's current one; null for the current one, which a mark wraps again
478
+ * with no value changing.
509
479
  */
510
480
  export async function restoredNatures(
511
481
  db: Queryable,
@@ -515,7 +485,11 @@ export async function restoredNatures(
515
485
  const { secrets, secretVersions } = tablesOf(db);
516
486
  const [current, versions] = await Promise.all([
517
487
  db.select({ id: secrets.id, version: secrets.currentVersionId }).from(secrets).where(inArray(secrets.id, [...new Set(items.map((item) => item.secretId))])),
518
- db.select({ id: secretVersions.id, sensitive: secretVersions.sensitive, plain: truth(isNotNull(secretVersions.plaintext)) }).from(secretVersions)
488
+ db.select({
489
+ id: secretVersions.id,
490
+ sensitive: sql<boolean | number>`(${secretVersions.sensitive} OR (${heldAs(secretVersions, sql`same.sensitive`)}))`,
491
+ plain: sql<boolean | number>`(${secretVersions.plaintext} IS NOT NULL AND (${heldAs(secretVersions, sql`same.plaintext IS NOT NULL`)}))`,
492
+ }).from(secretVersions)
519
493
  .where(inArray(secretVersions.id, [...new Set(items.map((item) => item.from))])),
520
494
  ]);
521
495
  const currentOf = new Map(current.map((row) => [row.id, row.version]));
@@ -698,6 +672,12 @@ export type SecretVersion = {
698
672
  current: boolean;
699
673
  /** Whether its secret's newest version is sensitive: what a rewrap of it may not loosen. */
700
674
  newestSensitive: boolean;
675
+ /**
676
+ * Whether its value was made sensitive since: the newest version of its
677
+ * secret with its very ciphertext, which a mark in place or a restore wraps
678
+ * again unchanged, is. What a rewrap of it may not loosen either.
679
+ */
680
+ heldSensitive: boolean;
701
681
  };
702
682
 
703
683
  export async function versions(db: Queryable, ids: readonly string[]): Promise<SecretVersion[]> {
@@ -723,6 +703,7 @@ export async function versions(db: Queryable, ids: readonly string[]): Promise<S
723
703
  // The newest by the versions' own order: the app may write `current_version_id`, never a version.
724
704
  newest: sql<number | string>`(SELECT max(newest.version) FROM secret_versions newest WHERE newest.secret_id = ${secretVersions.secretId})`,
725
705
  newestSensitive: sql<boolean | number>`(SELECT newest.sensitive FROM secret_versions newest WHERE newest.secret_id = ${secretVersions.secretId} ORDER BY newest.version DESC LIMIT 1)`,
706
+ heldSensitive: sql<boolean | number>`(${heldAs(secretVersions, sql`same.sensitive`)})`,
726
707
  })
727
708
  .from(secretVersions)
728
709
  .innerJoin(secrets, eq(secrets.id, secretVersions.secretId))
@@ -749,5 +730,15 @@ export async function versions(db: Queryable, ids: readonly string[]): Promise<S
749
730
  current: row.currentVersionId === row.id && Number(row.newest) === row.version,
750
731
  // SQLite answers a subquery's boolean as a number.
751
732
  newestSensitive: Boolean(row.newestSensitive),
733
+ heldSensitive: Boolean(row.heldSensitive),
752
734
  }));
753
735
  }
736
+
737
+ /**
738
+ * `column` of the newest version of a version's secret with its very ciphertext: its value, as it is held now. Known
739
+ * by its IV and tag, which under one data key name one ciphertext, and which, unlike the ciphertext, no erasure empties.
740
+ */
741
+ function heldAs(secretVersions: ReturnType<typeof tablesOf>['secretVersions'], column: SQL): SQL {
742
+ return sql`SELECT ${column} FROM secret_versions same WHERE same.secret_id = ${secretVersions.secretId}
743
+ AND same.iv = ${secretVersions.iv} AND same.auth_tag = ${secretVersions.authTag} ORDER BY same.version DESC LIMIT 1`;
744
+ }