dacument 1.0.1 → 1.2.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/README.md +50 -5
- package/dist/Dacument/acl.d.ts +1 -0
- package/dist/Dacument/acl.js +13 -0
- package/dist/Dacument/class.d.ts +42 -4
- package/dist/Dacument/class.js +577 -74
- package/dist/Dacument/crypto.d.ts +3 -0
- package/dist/Dacument/crypto.js +25 -1
- package/dist/Dacument/types.d.ts +43 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,10 +19,16 @@ yarn add dacument
|
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
21
|
import { generateNonce } from "bytecodec";
|
|
22
|
+
import { generateSignPair } from "zeyra";
|
|
22
23
|
import { Dacument } from "dacument";
|
|
23
24
|
|
|
24
25
|
const actorId = generateNonce(); // 256-bit base64url id
|
|
25
|
-
|
|
26
|
+
const actorKeys = await generateSignPair();
|
|
27
|
+
await Dacument.setActorInfo({
|
|
28
|
+
id: actorId,
|
|
29
|
+
privateKeyJwk: actorKeys.signingJwk,
|
|
30
|
+
publicKeyJwk: actorKeys.verificationJwk,
|
|
31
|
+
});
|
|
26
32
|
|
|
27
33
|
const schema = Dacument.schema({
|
|
28
34
|
title: Dacument.register({ jsType: "string", regex: /^[a-z ]+$/i }),
|
|
@@ -93,9 +99,11 @@ doc.acl.setRole("user-viewer", "viewer");
|
|
|
93
99
|
await doc.flush();
|
|
94
100
|
```
|
|
95
101
|
|
|
96
|
-
Before any schema/load/create, call `Dacument.
|
|
97
|
-
The actor id must be a 256-bit base64url string (e.g.
|
|
98
|
-
|
|
102
|
+
Before any schema/load/create, call `await Dacument.setActorInfo(...)` once per
|
|
103
|
+
process. The actor id must be a 256-bit base64url string (e.g.
|
|
104
|
+
`bytecodec` libarys `generateNonce()`), and the actor key pair must be ES256 (P-256).
|
|
105
|
+
Subsequent calls are ignored. On first merge, Dacument auto-attaches the
|
|
106
|
+
actor's `publicKeyJwk` to its own ACL entry (if missing).
|
|
99
107
|
|
|
100
108
|
Each actor signs with the role key they were given (owner/manager/editor). Load
|
|
101
109
|
with the highest role key you have; viewers load without a key.
|
|
@@ -123,7 +131,12 @@ without snapshotting.
|
|
|
123
131
|
To add a new replica, share a snapshot and load it:
|
|
124
132
|
|
|
125
133
|
```ts
|
|
126
|
-
|
|
134
|
+
const bobKeys = await generateSignPair();
|
|
135
|
+
await Dacument.setActorInfo({
|
|
136
|
+
id: bobId,
|
|
137
|
+
privateKeyJwk: bobKeys.signingJwk,
|
|
138
|
+
publicKeyJwk: bobKeys.verificationJwk,
|
|
139
|
+
});
|
|
127
140
|
const bob = await Dacument.load({
|
|
128
141
|
schema,
|
|
129
142
|
roleKey: bobKey.privateKey,
|
|
@@ -139,10 +152,42 @@ Snapshots do not include schema or schema ids; callers must supply the schema on
|
|
|
139
152
|
- `doc.addEventListener("merge", handler)` emits `{ actor, target, method, data }`.
|
|
140
153
|
- `doc.addEventListener("error", handler)` emits signing/verification errors.
|
|
141
154
|
- `doc.addEventListener("revoked", handler)` fires when the current actor is revoked.
|
|
155
|
+
- `doc.addEventListener("reset", handler)` emits `{ oldDocId, newDocId, ts, by, reason }`.
|
|
156
|
+
- `doc.selfRevoke()` emits a signed ACL op that revokes the current actor.
|
|
157
|
+
- `await doc.accessReset({ reason })` creates a new dacument with fresh keys and emits a reset op.
|
|
158
|
+
- `doc.getResetState()` returns reset metadata (or `null`).
|
|
142
159
|
- `await doc.flush()` waits for pending signatures so all local ops are emitted.
|
|
143
160
|
- `doc.snapshot()` returns a loadable op log (`{ docId, roleKeys, ops }`).
|
|
161
|
+
- `await doc.verifyActorIntegrity(...)` verifies per-actor signatures on demand.
|
|
144
162
|
- Revoked actors cannot snapshot; reads are masked to initial values.
|
|
145
163
|
|
|
164
|
+
## Access reset (key compromise response)
|
|
165
|
+
|
|
166
|
+
If an owner suspects role key compromise, call `accessReset()` to fork to a new
|
|
167
|
+
doc id and revoke the old one:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const { newDoc, oldDocOps, newDocSnapshot, roleKeys } =
|
|
171
|
+
await doc.accessReset({ reason: "suspected compromise" });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`accessReset()` materializes the current state into a new dacument with fresh
|
|
175
|
+
role keys, emits a signed `reset` op for the old doc, and returns the new
|
|
176
|
+
snapshot + keys. The reset is stored as a CRDT op so all replicas converge. Once
|
|
177
|
+
reset, the old doc rejects any ops after the reset stamp and throws on writes:
|
|
178
|
+
`Dacument is reset/deprecated. Use newDocId: <id>`. Snapshots and verification
|
|
179
|
+
still work so you can archive/inspect history.
|
|
180
|
+
If an attacker already has the owner key, they can also reset; this is a
|
|
181
|
+
response tool for suspected compromise, not a prevention mechanism.
|
|
182
|
+
|
|
183
|
+
## Actor identity (cold path)
|
|
184
|
+
|
|
185
|
+
Every op may include an `actorSig` (detached ES256 signature over the op token).
|
|
186
|
+
Merges ignore `actorSig` by default to keep the hot path fast. When you need
|
|
187
|
+
attribution or forensic checks, call `verifyActorIntegrity()` with a token,
|
|
188
|
+
ops list, or snapshot. It verifies `actorSig` against the actor's `publicKeyJwk`
|
|
189
|
+
from the ACL at the op stamp and returns a summary plus failures.
|
|
190
|
+
|
|
146
191
|
## Garbage collection
|
|
147
192
|
|
|
148
193
|
Dacument tracks per-actor `ack` ops and compacts tombstones once all non-revoked
|
package/dist/Dacument/acl.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare class AclLog {
|
|
|
11
11
|
roleAt(actorId: string, stamp: AclAssignment["stamp"]): Role;
|
|
12
12
|
currentRole(actorId: string): Role;
|
|
13
13
|
currentEntry(actorId: string): AclAssignment | null;
|
|
14
|
+
publicKeyAt(actorId: string, stamp: AclAssignment["stamp"]): JsonWebKey | null;
|
|
14
15
|
knownActors(): string[];
|
|
15
16
|
private insert;
|
|
16
17
|
}
|
package/dist/Dacument/acl.js
CHANGED
|
@@ -55,6 +55,19 @@ export class AclLog {
|
|
|
55
55
|
currentEntry(actorId) {
|
|
56
56
|
return this.currentByActor.get(actorId) ?? null;
|
|
57
57
|
}
|
|
58
|
+
publicKeyAt(actorId, stamp) {
|
|
59
|
+
const list = this.nodesByActor.get(actorId);
|
|
60
|
+
if (!list || list.length === 0)
|
|
61
|
+
return null;
|
|
62
|
+
for (let index = list.length - 1; index >= 0; index--) {
|
|
63
|
+
const entry = list[index];
|
|
64
|
+
if (compareHLC(entry.stamp, stamp) > 0)
|
|
65
|
+
continue;
|
|
66
|
+
if (entry.publicKeyJwk)
|
|
67
|
+
return entry.publicKeyJwk;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
58
71
|
knownActors() {
|
|
59
72
|
return [...this.currentByActor.keys()];
|
|
60
73
|
}
|
package/dist/Dacument/class.d.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { type AclAssignment, type DacumentEventMap, type DocFieldAccess, type DocSnapshot, type RoleKeys, type RolePublicKeys, type SchemaDefinition, type SchemaId, type SignedOp, type Role, array, map, record, register, set, text } from "./types.js";
|
|
1
|
+
import { type AclAssignment, type ActorInfo, type DacumentEventMap, type DocFieldAccess, type DocSnapshot, type RoleKeys, type RolePublicKeys, type SchemaDefinition, type SchemaId, type SignedOp, type VerificationResult, type VerifyActorIntegrityOptions, type Role, array, map, record, register, set, text } from "./types.js";
|
|
2
|
+
type ResetStateInfo = {
|
|
3
|
+
ts: AclAssignment["stamp"];
|
|
4
|
+
by: string;
|
|
5
|
+
newDocId: string;
|
|
6
|
+
reason?: string;
|
|
7
|
+
};
|
|
2
8
|
export declare class Dacument<S extends SchemaDefinition> {
|
|
3
|
-
private static
|
|
4
|
-
static
|
|
5
|
-
|
|
9
|
+
private static actorInfo?;
|
|
10
|
+
private static actorSigner?;
|
|
11
|
+
static setActorInfo(info: ActorInfo): Promise<void>;
|
|
12
|
+
private static requireActorInfo;
|
|
13
|
+
private static requireActorSigner;
|
|
14
|
+
private static signActorToken;
|
|
6
15
|
private static isValidActorId;
|
|
16
|
+
private static assertActorKeyJwk;
|
|
17
|
+
private static assertActorPrivateKey;
|
|
18
|
+
private static assertActorPublicKey;
|
|
7
19
|
static schema: <Schema extends SchemaDefinition>(schema: Schema) => Schema;
|
|
8
20
|
static register: typeof register;
|
|
9
21
|
static text: typeof text;
|
|
@@ -38,8 +50,11 @@ export declare class Dacument<S extends SchemaDefinition> {
|
|
|
38
50
|
private readonly opLog;
|
|
39
51
|
private readonly opTokens;
|
|
40
52
|
private readonly verifiedOps;
|
|
53
|
+
private readonly opIndexByToken;
|
|
54
|
+
private readonly actorSigByToken;
|
|
41
55
|
private readonly appliedTokens;
|
|
42
56
|
private currentRole;
|
|
57
|
+
private resetState;
|
|
43
58
|
private readonly revokedCrdtByField;
|
|
44
59
|
private readonly deleteStampsByField;
|
|
45
60
|
private readonly tombstoneStampsByField;
|
|
@@ -49,8 +64,14 @@ export declare class Dacument<S extends SchemaDefinition> {
|
|
|
49
64
|
private readonly ackByActor;
|
|
50
65
|
private suppressMerge;
|
|
51
66
|
private ackScheduled;
|
|
67
|
+
private actorKeyPublishPending;
|
|
52
68
|
private lastGcBarrier;
|
|
53
69
|
private snapshotFieldValues;
|
|
70
|
+
private resetError;
|
|
71
|
+
private assertNotReset;
|
|
72
|
+
private currentRoleFor;
|
|
73
|
+
private roleAt;
|
|
74
|
+
private recordActorSig;
|
|
54
75
|
readonly acl: {
|
|
55
76
|
setRole: (actorId: string, role: Role) => void;
|
|
56
77
|
getRole: (actorId: string) => Role;
|
|
@@ -68,11 +89,23 @@ export declare class Dacument<S extends SchemaDefinition> {
|
|
|
68
89
|
removeEventListener<K extends keyof DacumentEventMap>(type: K, listener: (event: DacumentEventMap[K]) => void): void;
|
|
69
90
|
flush(): Promise<void>;
|
|
70
91
|
snapshot(): DocSnapshot;
|
|
92
|
+
getResetState(): ResetStateInfo | null;
|
|
93
|
+
selfRevoke(): void;
|
|
94
|
+
accessReset(options?: {
|
|
95
|
+
reason?: string;
|
|
96
|
+
}): Promise<{
|
|
97
|
+
newDoc: DacumentDoc<S>;
|
|
98
|
+
oldDocOps: SignedOp[];
|
|
99
|
+
newDocSnapshot: DocSnapshot;
|
|
100
|
+
roleKeys: RoleKeys;
|
|
101
|
+
}>;
|
|
102
|
+
verifyActorIntegrity(options?: VerifyActorIntegrityOptions): Promise<VerificationResult>;
|
|
71
103
|
merge(input: SignedOp | SignedOp[] | string | string[]): Promise<{
|
|
72
104
|
accepted: SignedOp[];
|
|
73
105
|
rejected: number;
|
|
74
106
|
}>;
|
|
75
107
|
private rebuildFromVerified;
|
|
108
|
+
private maybePublishActorKey;
|
|
76
109
|
private ack;
|
|
77
110
|
private scheduleAck;
|
|
78
111
|
private computeGcBarrier;
|
|
@@ -102,6 +135,8 @@ export declare class Dacument<S extends SchemaDefinition> {
|
|
|
102
135
|
private commitRecordMutation;
|
|
103
136
|
private capturePatches;
|
|
104
137
|
private queueLocalOp;
|
|
138
|
+
private queueActorOp;
|
|
139
|
+
private applyResetPayload;
|
|
105
140
|
private applyRemotePayload;
|
|
106
141
|
private applyAclPayload;
|
|
107
142
|
private applyRegisterPayload;
|
|
@@ -125,9 +160,11 @@ export declare class Dacument<S extends SchemaDefinition> {
|
|
|
125
160
|
private recordValue;
|
|
126
161
|
private mapValue;
|
|
127
162
|
private fieldValue;
|
|
163
|
+
private materializeSchema;
|
|
128
164
|
private emitEvent;
|
|
129
165
|
private emitMerge;
|
|
130
166
|
private emitRevoked;
|
|
167
|
+
private emitReset;
|
|
131
168
|
private emitError;
|
|
132
169
|
private canWriteField;
|
|
133
170
|
private canWriteAcl;
|
|
@@ -140,3 +177,4 @@ export declare class Dacument<S extends SchemaDefinition> {
|
|
|
140
177
|
private assertSchemaKeys;
|
|
141
178
|
}
|
|
142
179
|
export type DacumentDoc<S extends SchemaDefinition> = Dacument<S> & DocFieldAccess<S>;
|
|
180
|
+
export {};
|