@coffre/core 0.1.11 → 0.1.13

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/access.d.ts CHANGED
@@ -85,16 +85,6 @@ export type Holdings = {
85
85
  * `market/prod` does not let anyone rename `market`.
86
86
  */
87
87
  export declare function allows(holder: Holdings, permission: Permission, place: Place): boolean;
88
- /** A sync reads as a principal of its own, `sync:<id>`. */
89
- export declare function isSyncPrincipal(principal: string): boolean;
90
- /**
91
- * Whether `actor` may set `subject`'s role at `place`, or take it away
92
- * (`role` null). `grant.manage` on the project decides, with one addition:
93
- * whoever may manage a project's environments may let a sync read an
94
- * environment they can read themselves, and take that away again, since
95
- * adding and removing syncs is theirs to do.
96
- */
97
- export declare function mayManageAccess(actor: Holdings, subject: string, change: Place & {
98
- role: Role | null;
99
- }): boolean;
88
+ /** Whether the actor may manage grants on this project. */
89
+ export declare function mayManageAccess(actor: Holdings, place: Place): boolean;
100
90
  //#endregion
package/dist/access.js CHANGED
@@ -88,22 +88,9 @@ function allows(holder, permission, place) {
88
88
  if (environmentId === null && holder.isOwner && PROJECT_ONLY_PERMISSIONS.includes(permission)) return true;
89
89
  return holder.grants.some((grant) => grant.projectId === place.projectId && (grant.environmentId === null || grant.environmentId === environmentId) && roleGrants(grant.role, permission));
90
90
  }
91
- /** A sync reads as a principal of its own, `sync:<id>`. */
92
- function isSyncPrincipal(principal) {
93
- return principal.startsWith("sync:");
94
- }
95
- /**
96
- * Whether `actor` may set `subject`'s role at `place`, or take it away
97
- * (`role` null). `grant.manage` on the project decides, with one addition:
98
- * whoever may manage a project's environments may let a sync read an
99
- * environment they can read themselves, and take that away again, since
100
- * adding and removing syncs is theirs to do.
101
- */
102
- function mayManageAccess(actor, subject, change) {
103
- if (allows(actor, "grant.manage", { projectId: change.projectId })) return true;
104
- if (!isSyncPrincipal(subject) || !allows(actor, "environment.manage", { projectId: change.projectId })) return false;
105
- if (change.role === null) return true;
106
- return change.role === "viewer" && (change.environmentId ?? null) !== null && allows(actor, "secret.read", change);
91
+ /** Whether the actor may manage grants on this project. */
92
+ function mayManageAccess(actor, place) {
93
+ return allows(actor, "grant.manage", { projectId: place.projectId });
107
94
  }
108
95
  //#endregion
109
- export { PERMISSIONS, PROJECT_ONLY_PERMISSIONS, ROLES, ROLE_NAMES, allows, assignableToEnvironment, isRole, isSyncPrincipal, mayManageAccess, roleGrants };
96
+ export { PERMISSIONS, PROJECT_ONLY_PERMISSIONS, ROLES, ROLE_NAMES, allows, assignableToEnvironment, isRole, mayManageAccess, roleGrants };
package/dist/schemas.d.ts CHANGED
@@ -9,10 +9,9 @@ export declare const principalType: z.ZodEnum<{
9
9
  }>;
10
10
  export declare const principalId: z.ZodString;
11
11
  export declare const instanceRole: z.ZodEnum<{
12
- user: "user";
13
12
  owner: "owner";
13
+ user: "user";
14
14
  }>;
15
15
  export declare const grantId: z.ZodString;
16
16
  export declare const emailAddress: z.ZodString;
17
- export declare const syncId: z.ZodString;
18
17
  //#endregion
package/dist/schemas.js CHANGED
@@ -8,6 +8,5 @@ const principalId = z.string().trim().min(1).max(320);
8
8
  const instanceRole = z.enum(["user", "owner"]);
9
9
  const grantId = z.string().uuid();
10
10
  const emailAddress = z.string().email().max(320);
11
- const syncId = z.string().uuid();
12
11
  //#endregion
13
- export { displayName, emailAddress, grantId, instanceRole, principalId, principalType, secretKey, slug, syncId };
12
+ export { displayName, emailAddress, grantId, instanceRole, principalId, principalType, secretKey, slug };
package/dist/vault.d.ts CHANGED
@@ -67,6 +67,12 @@ export declare function describeAccessFault(fault: AccessFault, names?: FaultNam
67
67
  export declare function checkpointMessage(checkpoint: Omit<Checkpoint, 'signature' | 'keyId'>): Uint8Array<ArrayBuffer>;
68
68
  /** Whether `checkpoint` was signed by the key whose raw public half is `publicKey`. */
69
69
  export declare function verifyCheckpoint(checkpoint: Checkpoint, publicKey: string): Promise<boolean>;
70
+ /**
71
+ * `verifyCheckpoint` for many checkpoints signed by one key, the key
72
+ * imported once: a full verification checks every checkpoint, one every
73
+ * five minutes.
74
+ */
75
+ export declare function checkpointVerifier(publicKey: string): Promise<(checkpoint: Checkpoint) => Promise<boolean>>;
70
76
  //#endregion
71
77
  //#region src/vault.d.ts
72
78
  /**
@@ -162,11 +168,13 @@ export interface Vault {
162
168
  */
163
169
  keyChecks(): Promise<KeyChecks>;
164
170
  /**
165
- * Check the whole log: rehash it from the first entry, the vault's
166
- * entries by their MACs, every checkpoint against the prefix it signed,
171
+ * Check the whole log: the vault's entries by their MACs, every
172
+ * checkpoint against the prefix it signed, the key batches accounted for,
167
173
  * and replay it to see that who is a member, and what they hold, follow
168
- * from it. A verdict and nothing else, so anyone may ask; the app asks
169
- * for owners.
174
+ * from it. The links and hashes of the chain are the caller's to
175
+ * recompute through `upTo`, as the app does first; with none, the vault
176
+ * recomputes them all itself. A verdict and nothing else, so anyone may
177
+ * ask; the app asks for owners.
170
178
  */
171
179
  verifyLog(input: VerifyLogInput): Promise<LogVerification>;
172
180
  }
@@ -256,7 +264,7 @@ export type KeyChecks = {
256
264
  })[];
257
265
  };
258
266
  /** Why someone reads: shown in the log, and the same rules apply to each. */
259
- export type Purpose = 'reveal' | 'run' | 'compare' | 'sync';
267
+ export type Purpose = 'reveal' | 'run' | 'compare';
260
268
  /** Ties the vault's entries to the app's request and audit rows. */
261
269
  type Correlation = {
262
270
  requestId?: string | null;
@@ -340,15 +348,6 @@ export type AdmitInput = Correlation & {
340
348
  export type RemoveInput = Correlation & {
341
349
  actor: string;
342
350
  principal: string;
343
- /**
344
- * For a sync: the environment it pushes from. Whoever may manage it there
345
- * may stop the sync, also once its grant there is revoked and only the
346
- * one on its credential is left.
347
- */
348
- source?: {
349
- projectId: string;
350
- environmentId: string;
351
- };
352
351
  };
353
352
  /**
354
353
  * A prefix of the log the vault signed: every entry up to `seq`, the last
@@ -381,9 +380,11 @@ export type LogHead = {
381
380
  };
382
381
  export type VerifyLogInput = {
383
382
  /**
384
- * The last entry the app verified, its own entries by its key: the vault
385
- * checks the log still holds it, so between them both authors' entries
386
- * are authenticated over the same prefix.
383
+ * The last entry the app verified, every link and hash up to it
384
+ * recomputed and its own entries checked by its key: the vault checks the
385
+ * log still holds it, so between them both authors' entries are
386
+ * authenticated over the same prefix, and the vault leaves the links to
387
+ * the app.
387
388
  */
388
389
  upTo?: LogHead | null;
389
390
  };
package/dist/vault.js CHANGED
@@ -6,8 +6,16 @@ function checkpointMessage(checkpoint) {
6
6
  }
7
7
  /** Whether `checkpoint` was signed by the key whose raw public half is `publicKey`. */
8
8
  async function verifyCheckpoint(checkpoint, publicKey) {
9
+ return (await checkpointVerifier(publicKey))(checkpoint);
10
+ }
11
+ /**
12
+ * `verifyCheckpoint` for many checkpoints signed by one key, the key
13
+ * imported once: a full verification checks every checkpoint, one every
14
+ * five minutes.
15
+ */
16
+ async function checkpointVerifier(publicKey) {
9
17
  const key = await crypto.subtle.importKey("raw", new Uint8Array(Buffer.from(publicKey, "base64")), { name: "Ed25519" }, false, ["verify"]);
10
- return crypto.subtle.verify("Ed25519", key, new Uint8Array(Buffer.from(checkpoint.signature, "base64")), checkpointMessage(checkpoint));
18
+ return (checkpoint) => crypto.subtle.verify("Ed25519", key, new Uint8Array(Buffer.from(checkpoint.signature, "base64")), checkpointMessage(checkpoint));
11
19
  }
12
20
  //#endregion
13
21
  //#region src/access-fault.ts
@@ -42,4 +50,4 @@ const ACCESS_ACTIONS = [
42
50
  "access.revoke"
43
51
  ];
44
52
  //#endregion
45
- export { ACCESS_ACTIONS, checkpointMessage, describeAccessFault, verifyCheckpoint };
53
+ export { ACCESS_ACTIONS, checkpointMessage, checkpointVerifier, describeAccessFault, verifyCheckpoint };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/core",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "What coffre's packages share: roles, envelopes, identity, the audit chain and the vault's contract.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -69,7 +69,7 @@
69
69
  "@types/node": "26.1.1",
70
70
  "tsdown": "0.23.0",
71
71
  "typescript": "5.9.3",
72
- "@coffre/conformance": "0.1.11"
72
+ "@coffre/conformance": "0.1.13"
73
73
  },
74
74
  "scripts": {
75
75
  "build": "tsdown",
package/src/access.ts CHANGED
@@ -126,27 +126,7 @@ export function allows(holder: Holdings, permission: Permission, place: Place):
126
126
  );
127
127
  }
128
128
 
129
- /** A sync reads as a principal of its own, `sync:<id>`. */
130
- export function isSyncPrincipal(principal: string): boolean {
131
- return principal.startsWith('sync:');
132
- }
133
-
134
- /**
135
- * Whether `actor` may set `subject`'s role at `place`, or take it away
136
- * (`role` null). `grant.manage` on the project decides, with one addition:
137
- * whoever may manage a project's environments may let a sync read an
138
- * environment they can read themselves, and take that away again, since
139
- * adding and removing syncs is theirs to do.
140
- */
141
- export function mayManageAccess(
142
- actor: Holdings,
143
- subject: string,
144
- change: Place & { role: Role | null },
145
- ): boolean {
146
- if (allows(actor, 'grant.manage', { projectId: change.projectId })) return true;
147
- if (!isSyncPrincipal(subject) || !allows(actor, 'environment.manage', { projectId: change.projectId })) {
148
- return false;
149
- }
150
- if (change.role === null) return true;
151
- return change.role === 'viewer' && (change.environmentId ?? null) !== null && allows(actor, 'secret.read', change);
129
+ /** Whether the actor may manage grants on this project. */
130
+ export function mayManageAccess(actor: Holdings, place: Place): boolean {
131
+ return allows(actor, 'grant.manage', { projectId: place.projectId });
152
132
  }
package/src/checkpoint.ts CHANGED
@@ -8,6 +8,15 @@ export function checkpointMessage(checkpoint: Omit<Checkpoint, 'signature' | 'ke
8
8
 
9
9
  /** Whether `checkpoint` was signed by the key whose raw public half is `publicKey`. */
10
10
  export async function verifyCheckpoint(checkpoint: Checkpoint, publicKey: string): Promise<boolean> {
11
+ return (await checkpointVerifier(publicKey))(checkpoint);
12
+ }
13
+
14
+ /**
15
+ * `verifyCheckpoint` for many checkpoints signed by one key, the key
16
+ * imported once: a full verification checks every checkpoint, one every
17
+ * five minutes.
18
+ */
19
+ export async function checkpointVerifier(publicKey: string): Promise<(checkpoint: Checkpoint) => Promise<boolean>> {
11
20
  const key = await crypto.subtle.importKey('raw', new Uint8Array(Buffer.from(publicKey, 'base64')), { name: 'Ed25519' }, false, ['verify']);
12
- return crypto.subtle.verify('Ed25519', key, new Uint8Array(Buffer.from(checkpoint.signature, 'base64')), checkpointMessage(checkpoint));
21
+ return (checkpoint) => crypto.subtle.verify('Ed25519', key, new Uint8Array(Buffer.from(checkpoint.signature, 'base64')), checkpointMessage(checkpoint));
13
22
  }
package/src/schemas.ts CHANGED
@@ -8,4 +8,3 @@ export const principalId = z.string().trim().min(1).max(320);
8
8
  export const instanceRole = z.enum(['user', 'owner']);
9
9
  export const grantId = z.string().uuid();
10
10
  export const emailAddress = z.string().email().max(320);
11
- export const syncId = z.string().uuid();
package/src/vault.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { AccessFault } from './access-fault.ts';
2
2
  import type { Role } from './access.ts';
3
3
 
4
- export { checkpointMessage, verifyCheckpoint } from './checkpoint.ts';
4
+ export { checkpointMessage, checkpointVerifier, verifyCheckpoint } from './checkpoint.ts';
5
5
  export { describeAccessFault, type AccessFault, type FaultGrant, type FaultNames } from './access-fault.ts';
6
6
 
7
7
  /**
@@ -77,11 +77,13 @@ export interface Vault {
77
77
  */
78
78
  keyChecks(): Promise<KeyChecks>;
79
79
  /**
80
- * Check the whole log: rehash it from the first entry, the vault's
81
- * entries by their MACs, every checkpoint against the prefix it signed,
80
+ * Check the whole log: the vault's entries by their MACs, every
81
+ * checkpoint against the prefix it signed, the key batches accounted for,
82
82
  * and replay it to see that who is a member, and what they hold, follow
83
- * from it. A verdict and nothing else, so anyone may ask; the app asks
84
- * for owners.
83
+ * from it. The links and hashes of the chain are the caller's to
84
+ * recompute through `upTo`, as the app does first; with none, the vault
85
+ * recomputes them all itself. A verdict and nothing else, so anyone may
86
+ * ask; the app asks for owners.
85
87
  */
86
88
  verifyLog(input: VerifyLogInput): Promise<LogVerification>;
87
89
  }
@@ -158,7 +160,7 @@ export type WrappedKey = { kekProvider: string; kekId: string; kekVersion: strin
158
160
  export type KeyChecks = { current: { kekProvider: string; kekId: string }; checks: (WrappedKey & { seq: number })[] };
159
161
 
160
162
  /** Why someone reads: shown in the log, and the same rules apply to each. */
161
- export type Purpose = 'reveal' | 'run' | 'compare' | 'sync';
163
+ export type Purpose = 'reveal' | 'run' | 'compare';
162
164
 
163
165
  /** Ties the vault's entries to the app's request and audit rows. */
164
166
  type Correlation = {
@@ -245,12 +247,7 @@ export type AdmitInput = Correlation & {
245
247
  export type RemoveInput = Correlation & {
246
248
  actor: string;
247
249
  principal: string;
248
- /**
249
- * For a sync: the environment it pushes from. Whoever may manage it there
250
- * may stop the sync, also once its grant there is revoked and only the
251
- * one on its credential is left.
252
- */
253
- source?: { projectId: string; environmentId: string };
250
+
254
251
  };
255
252
 
256
253
  /**
@@ -281,9 +278,11 @@ export type LogHead = { seq: number; hash: string };
281
278
 
282
279
  export type VerifyLogInput = {
283
280
  /**
284
- * The last entry the app verified, its own entries by its key: the vault
285
- * checks the log still holds it, so between them both authors' entries
286
- * are authenticated over the same prefix.
281
+ * The last entry the app verified, every link and hash up to it
282
+ * recomputed and its own entries checked by its key: the vault checks the
283
+ * log still holds it, so between them both authors' entries are
284
+ * authenticated over the same prefix, and the vault leaves the links to
285
+ * the app.
287
286
  */
288
287
  upTo?: LogHead | null;
289
288
  };