mindoodb 0.0.64 → 0.0.65

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.
Files changed (51) hide show
  1. package/dist/browser/index.mjs +11 -0
  2. package/dist/core/BaseMindooDB.d.ts +19 -0
  3. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  4. package/dist/core/BaseMindooDB.js +607 -323
  5. package/dist/core/BaseMindooDB.js.map +1 -1
  6. package/dist/core/accesscontrol/tier1Evaluator.d.ts +59 -0
  7. package/dist/core/accesscontrol/tier1Evaluator.d.ts.map +1 -0
  8. package/dist/core/accesscontrol/tier1Evaluator.js +77 -0
  9. package/dist/core/accesscontrol/tier1Evaluator.js.map +1 -0
  10. package/dist/core/appendonlystores/network/IrohNetworkTransport.d.ts +58 -5
  11. package/dist/core/appendonlystores/network/IrohNetworkTransport.d.ts.map +1 -1
  12. package/dist/core/appendonlystores/network/IrohNetworkTransport.js +77 -8
  13. package/dist/core/appendonlystores/network/IrohNetworkTransport.js.map +1 -1
  14. package/dist/core/appendonlystores/network/IrohStreamIO.d.ts +32 -10
  15. package/dist/core/appendonlystores/network/IrohStreamIO.d.ts.map +1 -1
  16. package/dist/core/appendonlystores/network/IrohStreamIO.js +26 -10
  17. package/dist/core/appendonlystores/network/IrohStreamIO.js.map +1 -1
  18. package/dist/core/appendonlystores/network/irohLocator.d.ts +16 -0
  19. package/dist/core/appendonlystores/network/irohLocator.d.ts.map +1 -1
  20. package/dist/core/appendonlystores/network/irohLocator.js +36 -0
  21. package/dist/core/appendonlystores/network/irohLocator.js.map +1 -1
  22. package/dist/core/builtinDbInvariants.d.ts +12 -6
  23. package/dist/core/builtinDbInvariants.d.ts.map +1 -1
  24. package/dist/core/builtinDbInvariants.js +7 -1
  25. package/dist/core/builtinDbInvariants.js.map +1 -1
  26. package/dist/core/index.d.ts +6 -3
  27. package/dist/core/index.d.ts.map +1 -1
  28. package/dist/core/index.js +27 -11
  29. package/dist/core/index.js.map +1 -1
  30. package/dist/core/peerdevices/PeerDeviceDocument.d.ts +144 -0
  31. package/dist/core/peerdevices/PeerDeviceDocument.d.ts.map +1 -0
  32. package/dist/core/peerdevices/PeerDeviceDocument.js +209 -0
  33. package/dist/core/peerdevices/PeerDeviceDocument.js.map +1 -0
  34. package/dist/core/types.d.ts +15 -0
  35. package/dist/core/types.d.ts.map +1 -1
  36. package/dist/core/userkeys/fingerprint.d.ts +12 -4
  37. package/dist/core/userkeys/fingerprint.d.ts.map +1 -1
  38. package/dist/core/userkeys/fingerprint.js +14 -5
  39. package/dist/core/userkeys/fingerprint.js.map +1 -1
  40. package/dist/iroh/index.d.ts +3 -2
  41. package/dist/iroh/index.d.ts.map +1 -1
  42. package/dist/iroh/index.js +8 -1
  43. package/dist/iroh/index.js.map +1 -1
  44. package/dist/node/server/TenantManager.d.ts +5 -0
  45. package/dist/node/server/TenantManager.d.ts.map +1 -1
  46. package/dist/node/server/TenantManager.js +35 -56
  47. package/dist/node/server/TenantManager.js.map +1 -1
  48. package/dist/node/server/nativeIrohStreamIO.d.ts.map +1 -1
  49. package/dist/node/server/nativeIrohStreamIO.js +17 -4
  50. package/dist/node/server/nativeIrohStreamIO.js.map +1 -1
  51. package/package.json +1 -1
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Peer-device records: how one device advertises its Iroh endpoint id to the
3
+ * other members of a tenant.
4
+ *
5
+ * A device publishes one `dev_<fingerprint>` document into `userdirectory`,
6
+ * encrypted with the tenant `default` key. That placement is deliberate:
7
+ *
8
+ * - **`userdirectory`, not `grantaccess`.** Every member already replicates
9
+ * `userdirectory`, and a device can write its own record there without an
10
+ * administrator approving anything.
11
+ * - **`default`, not `$publicinfos`.** The hoster holds the `$publicinfos`
12
+ * key, and who talks to whom over Iroh is none of the hoster's business.
13
+ * `default` keeps the record readable for every tenant member and opaque to
14
+ * the server.
15
+ * - **`dev_` prefix.** That makes it a *personal document* (see
16
+ * {@link PERSONAL_DOC_ID_PREFIXES}), so the write invariant lets its creator
17
+ * — and only its creator — change and delete it.
18
+ *
19
+ * ## Why records must be verified on read
20
+ *
21
+ * Any tenant member can write a `dev_` document holding any endpoint id they
22
+ * like. The doc id and payload are therefore claims, not facts.
23
+ * {@link verifyPeerDeviceRecord} only accepts a record when all three hold:
24
+ *
25
+ * 1. the doc id is the fingerprint of the `signingPublicKey` in the payload,
26
+ * 2. the `doc_create` entry was signed by exactly that key, and
27
+ * 3. that key is an active (non-revoked) grant in the tenant.
28
+ *
29
+ * Together these mean a record can only be published by the device that owns
30
+ * the signing key it names, so nobody can put their own endpoint id under
31
+ * someone else's device.
32
+ *
33
+ * **Known v1 gap — id squatting.** A member can create `dev_<someone else's
34
+ * fingerprint>` before that device does. The squatted record fails step 2 and
35
+ * is discarded, so it cannot impersonate anyone; but the real device then
36
+ * cannot create its own record under that id and stays invisible to the
37
+ * picker. Detect it by the record being present yet unverifiable.
38
+ */
39
+ import type { MindooDB, MindooDoc, SigningKeyPair } from "../types";
40
+ export declare const PEER_DEVICE_DOC_ID_PREFIX = "dev_";
41
+ export declare const PEER_DEVICE_FORM = "userdirectory";
42
+ export declare const PEER_DEVICE_TYPE = "peerdevice";
43
+ export declare const PEER_DEVICE_SCHEMA_VERSION = 1;
44
+ /** Payload of a `dev_` document. Encrypted with {@link DEFAULT_TENANT_KEY_ID}. */
45
+ export interface PeerDeviceDocumentPayload {
46
+ form: typeof PEER_DEVICE_FORM;
47
+ type: typeof PEER_DEVICE_TYPE;
48
+ schemaVersion: number;
49
+ /**
50
+ * 64-char lowercase hex Iroh endpoint id of this device. Peers dial this
51
+ * directly; the pkarr address lookup resolves the current relay, so the
52
+ * value stays valid when the device changes networks.
53
+ */
54
+ irohEndpointId: string;
55
+ /**
56
+ * Signing public key (PEM) of the device that owns this record. The whole
57
+ * verification chain hangs off this field.
58
+ */
59
+ signingPublicKey: string;
60
+ /** Human label shown in the peer picker, e.g. "Laptop (office)". */
61
+ label?: string;
62
+ updatedAt: number;
63
+ }
64
+ /** A record that passed {@link verifyPeerDeviceRecord}. */
65
+ export interface PeerDeviceRecord {
66
+ docId: string;
67
+ irohEndpointId: string;
68
+ signingPublicKey: string;
69
+ /** Fingerprint of {@link signingPublicKey}, colon-separated hex. */
70
+ signingKeyFingerprint: string;
71
+ label?: string;
72
+ updatedAt: number;
73
+ }
74
+ /** The directory calls a peer-device record needs to verify a claim. */
75
+ export interface PeerDeviceDirectory {
76
+ /** True when the key belongs to an active, non-revoked grant. */
77
+ validatePublicSigningKey(publicKey: string, opts?: {
78
+ forceRefresh?: boolean;
79
+ }): Promise<boolean>;
80
+ }
81
+ /**
82
+ * Deterministic document id for a device, derived from its signing key
83
+ * fingerprint.
84
+ *
85
+ * Deterministic so a device republishing after a reload updates its record
86
+ * instead of accumulating a new one per session. Colons are stripped because
87
+ * custom ids must match `^[a-z][a-z0-9_]*$` (`CUSTOM_DOC_ID_REGEX`).
88
+ */
89
+ export declare function peerDeviceDocumentId(signingKeyFingerprint: string): string;
90
+ export declare function isPeerDeviceDocId(docId: string): boolean;
91
+ /**
92
+ * Publish (or refresh) this device's record.
93
+ *
94
+ * Idempotent: the id is derived from the device's own signing key, so calling
95
+ * this on every unlock keeps one record per device and updates the endpoint id
96
+ * and label in place.
97
+ *
98
+ * Note this does *not* pass `assumeUniqueId`. The id is deterministic rather
99
+ * than random, so two replicas creating it concurrently must share Automerge
100
+ * ancestry in order to converge instead of forking. That code path also
101
+ * forbids `initialValues`, which is why the payload is written with a
102
+ * follow-up `changeDoc` — the same call that refreshes an existing record.
103
+ */
104
+ export declare function publishPeerDeviceRecord(input: {
105
+ db: MindooDB;
106
+ /** Signing public key (PEM) of this device — must match the signing identity writing the document. */
107
+ signingPublicKey: string;
108
+ irohEndpointId: string;
109
+ label?: string;
110
+ subtle: SubtleCrypto;
111
+ signingKeyPair?: SigningKeyPair;
112
+ signingKeyPassword?: string;
113
+ }): Promise<MindooDoc>;
114
+ /** Withdraw this device's record, e.g. when signing out of the tenant. */
115
+ export declare function removePeerDeviceRecord(input: {
116
+ db: MindooDB;
117
+ signingPublicKey: string;
118
+ subtle: SubtleCrypto;
119
+ }): Promise<void>;
120
+ /**
121
+ * Check one record's claim. Returns the verified record, or `null` when any
122
+ * step of the chain described in the module docs fails.
123
+ */
124
+ export declare function verifyPeerDeviceRecord(input: {
125
+ db: MindooDB;
126
+ directory: PeerDeviceDirectory;
127
+ doc: MindooDoc;
128
+ subtle: SubtleCrypto;
129
+ }): Promise<PeerDeviceRecord | null>;
130
+ /**
131
+ * Every verified peer-device record in this `userdirectory` replica.
132
+ *
133
+ * Requires the tenant `default` key to be unlocked; records that cannot be
134
+ * decrypted are simply absent. Unverifiable records are skipped silently —
135
+ * they are the expected shape of an attack, not an error to surface.
136
+ */
137
+ export declare function listPeerDeviceRecords(input: {
138
+ db: MindooDB;
139
+ directory: PeerDeviceDirectory;
140
+ subtle: SubtleCrypto;
141
+ /** Endpoint id of the local device, excluded from the result. */
142
+ excludeEndpointId?: string;
143
+ }): Promise<PeerDeviceRecord[]>;
144
+ //# sourceMappingURL=PeerDeviceDocument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PeerDeviceDocument.d.ts","sourceRoot":"","sources":["../../../src/core/peerdevices/PeerDeviceDocument.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAGpE,eAAO,MAAM,yBAAyB,SAAS,CAAC;AAChD,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAChD,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAC7C,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,kFAAkF;AAClF,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,gBAAgB,CAAC;IAC9B,IAAI,EAAE,OAAO,gBAAgB,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,2DAA2D;AAC3D,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wEAAwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAClG;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,qBAAqB,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,EAAE,EAAE,QAAQ,CAAC;IACb,sGAAsG;IACtG,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,GAAG,OAAO,CAAC,SAAS,CAAC,CAwCrB;AAED,0EAA0E;AAC1E,wBAAsB,sBAAsB,CAAC,KAAK,EAAE;IAClD,EAAE,EAAE,QAAQ,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE;IAClD,EAAE,EAAE,QAAQ,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC;IAC/B,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAqCnC;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE;IACjD,EAAE,EAAE,QAAQ,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,iEAAiE;IACjE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAuB9B"}
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ /**
3
+ * Peer-device records: how one device advertises its Iroh endpoint id to the
4
+ * other members of a tenant.
5
+ *
6
+ * A device publishes one `dev_<fingerprint>` document into `userdirectory`,
7
+ * encrypted with the tenant `default` key. That placement is deliberate:
8
+ *
9
+ * - **`userdirectory`, not `grantaccess`.** Every member already replicates
10
+ * `userdirectory`, and a device can write its own record there without an
11
+ * administrator approving anything.
12
+ * - **`default`, not `$publicinfos`.** The hoster holds the `$publicinfos`
13
+ * key, and who talks to whom over Iroh is none of the hoster's business.
14
+ * `default` keeps the record readable for every tenant member and opaque to
15
+ * the server.
16
+ * - **`dev_` prefix.** That makes it a *personal document* (see
17
+ * {@link PERSONAL_DOC_ID_PREFIXES}), so the write invariant lets its creator
18
+ * — and only its creator — change and delete it.
19
+ *
20
+ * ## Why records must be verified on read
21
+ *
22
+ * Any tenant member can write a `dev_` document holding any endpoint id they
23
+ * like. The doc id and payload are therefore claims, not facts.
24
+ * {@link verifyPeerDeviceRecord} only accepts a record when all three hold:
25
+ *
26
+ * 1. the doc id is the fingerprint of the `signingPublicKey` in the payload,
27
+ * 2. the `doc_create` entry was signed by exactly that key, and
28
+ * 3. that key is an active (non-revoked) grant in the tenant.
29
+ *
30
+ * Together these mean a record can only be published by the device that owns
31
+ * the signing key it names, so nobody can put their own endpoint id under
32
+ * someone else's device.
33
+ *
34
+ * **Known v1 gap — id squatting.** A member can create `dev_<someone else's
35
+ * fingerprint>` before that device does. The squatted record fails step 2 and
36
+ * is discarded, so it cannot impersonate anyone; but the real device then
37
+ * cannot create its own record under that id and stays invisible to the
38
+ * picker. Detect it by the record being present yet unverifiable.
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.PEER_DEVICE_SCHEMA_VERSION = exports.PEER_DEVICE_TYPE = exports.PEER_DEVICE_FORM = exports.PEER_DEVICE_DOC_ID_PREFIX = void 0;
42
+ exports.peerDeviceDocumentId = peerDeviceDocumentId;
43
+ exports.isPeerDeviceDocId = isPeerDeviceDocId;
44
+ exports.publishPeerDeviceRecord = publishPeerDeviceRecord;
45
+ exports.removePeerDeviceRecord = removePeerDeviceRecord;
46
+ exports.verifyPeerDeviceRecord = verifyPeerDeviceRecord;
47
+ exports.listPeerDeviceRecords = listPeerDeviceRecords;
48
+ const types_1 = require("../types");
49
+ const fingerprint_1 = require("../userkeys/fingerprint");
50
+ exports.PEER_DEVICE_DOC_ID_PREFIX = "dev_";
51
+ exports.PEER_DEVICE_FORM = "userdirectory";
52
+ exports.PEER_DEVICE_TYPE = "peerdevice";
53
+ exports.PEER_DEVICE_SCHEMA_VERSION = 1;
54
+ /**
55
+ * Deterministic document id for a device, derived from its signing key
56
+ * fingerprint.
57
+ *
58
+ * Deterministic so a device republishing after a reload updates its record
59
+ * instead of accumulating a new one per session. Colons are stripped because
60
+ * custom ids must match `^[a-z][a-z0-9_]*$` (`CUSTOM_DOC_ID_REGEX`).
61
+ */
62
+ function peerDeviceDocumentId(signingKeyFingerprint) {
63
+ return `${exports.PEER_DEVICE_DOC_ID_PREFIX}${signingKeyFingerprint.replace(/:/g, "").toLowerCase()}`;
64
+ }
65
+ function isPeerDeviceDocId(docId) {
66
+ return docId.startsWith(exports.PEER_DEVICE_DOC_ID_PREFIX);
67
+ }
68
+ /**
69
+ * Publish (or refresh) this device's record.
70
+ *
71
+ * Idempotent: the id is derived from the device's own signing key, so calling
72
+ * this on every unlock keeps one record per device and updates the endpoint id
73
+ * and label in place.
74
+ *
75
+ * Note this does *not* pass `assumeUniqueId`. The id is deterministic rather
76
+ * than random, so two replicas creating it concurrently must share Automerge
77
+ * ancestry in order to converge instead of forking. That code path also
78
+ * forbids `initialValues`, which is why the payload is written with a
79
+ * follow-up `changeDoc` — the same call that refreshes an existing record.
80
+ */
81
+ async function publishPeerDeviceRecord(input) {
82
+ const fingerprint = await (0, fingerprint_1.fingerprintPublicKeyPem)(input.signingPublicKey, input.subtle);
83
+ const docId = peerDeviceDocumentId(fingerprint);
84
+ const doc = (await input.db.getDocument(docId).catch(() => null)) ??
85
+ (await input.db.createDocument({
86
+ id: docId,
87
+ decryptionKeyId: types_1.DEFAULT_TENANT_KEY_ID,
88
+ signingKeyPair: input.signingKeyPair,
89
+ signingKeyPassword: input.signingKeyPassword,
90
+ }));
91
+ const label = input.label ?? "";
92
+ const current = doc.getData();
93
+ const unchanged = current.type === exports.PEER_DEVICE_TYPE &&
94
+ current.schemaVersion === exports.PEER_DEVICE_SCHEMA_VERSION &&
95
+ current.irohEndpointId === input.irohEndpointId &&
96
+ current.signingPublicKey === input.signingPublicKey &&
97
+ (current.label ?? "") === label;
98
+ // Callers republish on every unlock and after every `userdirectory` sync.
99
+ // Rewriting `updatedAt` unconditionally would append an entry each time,
100
+ // and since that entry syncs and triggers the next refresh, the two halves
101
+ // would keep feeding each other. Only a real change is worth a write.
102
+ if (unchanged) {
103
+ return doc;
104
+ }
105
+ await input.db.changeDoc(doc, (editable) => {
106
+ const data = editable.getData();
107
+ data.form = exports.PEER_DEVICE_FORM;
108
+ data.type = exports.PEER_DEVICE_TYPE;
109
+ data.schemaVersion = exports.PEER_DEVICE_SCHEMA_VERSION;
110
+ data.irohEndpointId = input.irohEndpointId;
111
+ data.signingPublicKey = input.signingPublicKey;
112
+ data.label = label;
113
+ data.updatedAt = Date.now();
114
+ });
115
+ return doc;
116
+ }
117
+ /** Withdraw this device's record, e.g. when signing out of the tenant. */
118
+ async function removePeerDeviceRecord(input) {
119
+ const fingerprint = await (0, fingerprint_1.fingerprintPublicKeyPem)(input.signingPublicKey, input.subtle);
120
+ const docId = peerDeviceDocumentId(fingerprint);
121
+ await input.db.deleteDocument(docId).catch(() => {
122
+ // Already gone, or never published from this device.
123
+ });
124
+ }
125
+ /**
126
+ * Check one record's claim. Returns the verified record, or `null` when any
127
+ * step of the chain described in the module docs fails.
128
+ */
129
+ async function verifyPeerDeviceRecord(input) {
130
+ const docId = input.doc.getId();
131
+ const data = input.doc.getData();
132
+ const signingPublicKey = typeof data.signingPublicKey === "string" ? data.signingPublicKey : "";
133
+ const irohEndpointId = typeof data.irohEndpointId === "string" ? data.irohEndpointId.trim().toLowerCase() : "";
134
+ if (data.type !== exports.PEER_DEVICE_TYPE || !signingPublicKey || !/^[0-9a-f]{64}$/.test(irohEndpointId)) {
135
+ return null;
136
+ }
137
+ // 1. The id must be the fingerprint of the key the payload names, so a
138
+ // record cannot sit under a device id it does not belong to.
139
+ const fingerprint = await (0, fingerprint_1.fingerprintPublicKeyPem)(signingPublicKey, input.subtle);
140
+ if (peerDeviceDocumentId(fingerprint) !== docId) {
141
+ return null;
142
+ }
143
+ // 2. That key must be the one that actually created the document. Without
144
+ // this, any member could publish a record naming someone else's key.
145
+ const creatorKey = await resolveCreateSigner(input.db, docId);
146
+ if (!creatorKey || normalizePem(creatorKey) !== normalizePem(signingPublicKey)) {
147
+ return null;
148
+ }
149
+ // 3. And it must still be a live grant, so revoked devices drop out.
150
+ if (!(await input.directory.validatePublicSigningKey(signingPublicKey).catch(() => false))) {
151
+ return null;
152
+ }
153
+ return {
154
+ docId,
155
+ irohEndpointId,
156
+ signingPublicKey,
157
+ signingKeyFingerprint: fingerprint,
158
+ label: typeof data.label === "string" && data.label ? data.label : undefined,
159
+ updatedAt: typeof data.updatedAt === "number" ? data.updatedAt : 0,
160
+ };
161
+ }
162
+ /**
163
+ * Every verified peer-device record in this `userdirectory` replica.
164
+ *
165
+ * Requires the tenant `default` key to be unlocked; records that cannot be
166
+ * decrypted are simply absent. Unverifiable records are skipped silently —
167
+ * they are the expected shape of an attack, not an error to surface.
168
+ */
169
+ async function listPeerDeviceRecords(input) {
170
+ // `idPrefix` matching is boundary-aware: "dev" matches `dev_…`, not `device…`.
171
+ const docIds = await input.db.getAllDocumentIds({ idPrefix: "dev" });
172
+ const records = [];
173
+ for (const docId of docIds) {
174
+ if (!isPeerDeviceDocId(docId)) {
175
+ continue;
176
+ }
177
+ const doc = await input.db.getDocument(docId).catch(() => null);
178
+ if (!doc) {
179
+ continue;
180
+ }
181
+ const record = await verifyPeerDeviceRecord({
182
+ db: input.db,
183
+ directory: input.directory,
184
+ doc,
185
+ subtle: input.subtle,
186
+ });
187
+ if (record && record.irohEndpointId !== input.excludeEndpointId) {
188
+ records.push(record);
189
+ }
190
+ }
191
+ return records;
192
+ }
193
+ /** Signing key that wrote the `doc_create` entry, or null when it is not here yet. */
194
+ async function resolveCreateSigner(db, docId) {
195
+ try {
196
+ // History is ordered causally, so `doc_create` is on the first page.
197
+ const page = await db.getDocumentHistoryPage(docId, { limit: 10 });
198
+ const create = page.entries.find((entry) => entry.entryType === "doc_create");
199
+ return create?.changeCreatedByPublicKey ?? null;
200
+ }
201
+ catch {
202
+ return null;
203
+ }
204
+ }
205
+ /** PEM formatting varies by transport; compare the key material, not the wrapping. */
206
+ function normalizePem(pem) {
207
+ return pem.replace(/\s+/g, "");
208
+ }
209
+ //# sourceMappingURL=PeerDeviceDocument.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PeerDeviceDocument.js","sourceRoot":"","sources":["../../../src/core/peerdevices/PeerDeviceDocument.ts"],"names":[],"mappings":"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"}
@@ -4821,6 +4821,21 @@ export interface MindooDB {
4821
4821
  * @return A promise that resolves when the sync is complete.
4822
4822
  */
4823
4823
  syncStoreChanges(): Promise<void>;
4824
+ /**
4825
+ * Would the builtin `directory` / `userdirectory` invariant forbid this
4826
+ * entry? For callers that accept entries from somewhere other than a server
4827
+ * — a peer sync — and therefore have to apply the admin/owner rules
4828
+ * themselves before storing anything.
4829
+ *
4830
+ * Deliberately the same check the load path uses, so an entry admitted on
4831
+ * this answer is one that will also materialize. Returns false for databases
4832
+ * without a builtin invariant, so it can be asked unconditionally.
4833
+ */
4834
+ violatesBuiltinWriteInvariant(entry: {
4835
+ entryType?: string;
4836
+ createdByPublicKey: string;
4837
+ docId?: string;
4838
+ }): Promise<boolean>;
4824
4839
  /**
4825
4840
  * Reconcile local document visibility with the current tenant KeyBag.
4826
4841
  *