@noy-db/hub 0.1.0-pre.9 → 0.2.0-pre.2

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 (288) hide show
  1. package/dist/aggregate/index.cjs +91 -36
  2. package/dist/aggregate/index.cjs.map +1 -1
  3. package/dist/aggregate/index.d.cts +2 -2
  4. package/dist/aggregate/index.d.ts +2 -2
  5. package/dist/aggregate/index.js +16 -9
  6. package/dist/aggregate/index.js.map +1 -1
  7. package/dist/attestation/index.cjs +305 -0
  8. package/dist/attestation/index.cjs.map +1 -0
  9. package/dist/attestation/index.d.cts +52 -0
  10. package/dist/attestation/index.d.ts +52 -0
  11. package/dist/attestation/index.js +36 -0
  12. package/dist/attestation/index.js.map +1 -0
  13. package/dist/blobs/index.cjs.map +1 -1
  14. package/dist/blobs/index.d.cts +7 -6
  15. package/dist/blobs/index.d.ts +7 -6
  16. package/dist/blobs/index.js +10 -8
  17. package/dist/blobs/index.js.map +1 -1
  18. package/dist/bundle/index.cjs +16923 -60
  19. package/dist/bundle/index.cjs.map +1 -1
  20. package/dist/bundle/index.d.cts +175 -6
  21. package/dist/bundle/index.d.ts +175 -6
  22. package/dist/bundle/index.js +543 -4
  23. package/dist/bundle/index.js.map +1 -1
  24. package/dist/{chunk-PTVMYYON.js → chunk-243PNUA6.js} +3 -3
  25. package/dist/{chunk-MR4424N3.js → chunk-2PAQNPE3.js} +2 -2
  26. package/dist/chunk-3QAKZ37R.js +83 -0
  27. package/dist/chunk-3QAKZ37R.js.map +1 -0
  28. package/dist/chunk-3S4BJX25.js +36 -0
  29. package/dist/chunk-3S4BJX25.js.map +1 -0
  30. package/dist/chunk-3XHOCQK4.js +118 -0
  31. package/dist/chunk-3XHOCQK4.js.map +1 -0
  32. package/dist/{chunk-AVVPZ4BC.js → chunk-3Y53S2SA.js} +4 -4
  33. package/dist/chunk-3Z2TPHC4.js +291 -0
  34. package/dist/chunk-3Z2TPHC4.js.map +1 -0
  35. package/dist/chunk-4HIL6AHQ.js +57 -0
  36. package/dist/chunk-4HIL6AHQ.js.map +1 -0
  37. package/dist/chunk-5ZGZ6HIZ.js +100 -0
  38. package/dist/chunk-5ZGZ6HIZ.js.map +1 -0
  39. package/dist/{chunk-ZFKD4QMV.js → chunk-7BRE6EUA.js} +3 -3
  40. package/dist/chunk-7BUTTVMR.js +34 -0
  41. package/dist/chunk-7BUTTVMR.js.map +1 -0
  42. package/dist/{chunk-VQBTTTUN.js → chunk-7Q5PLD5C.js} +4 -4
  43. package/dist/{chunk-VQBTTTUN.js.map → chunk-7Q5PLD5C.js.map} +1 -1
  44. package/dist/{chunk-QAVUREFT.js → chunk-7Z23ZFLV.js} +12 -6
  45. package/dist/chunk-7Z23ZFLV.js.map +1 -0
  46. package/dist/chunk-AHPFONIL.js +59 -0
  47. package/dist/chunk-AHPFONIL.js.map +1 -0
  48. package/dist/chunk-CXSCDO5T.js +51 -0
  49. package/dist/chunk-CXSCDO5T.js.map +1 -0
  50. package/dist/chunk-E535SAN4.js +8834 -0
  51. package/dist/chunk-E535SAN4.js.map +1 -0
  52. package/dist/chunk-EUYOGYGV.js +830 -0
  53. package/dist/chunk-EUYOGYGV.js.map +1 -0
  54. package/dist/chunk-FAQVNJD4.js +61 -0
  55. package/dist/chunk-FAQVNJD4.js.map +1 -0
  56. package/dist/{chunk-SCZXXXU4.js → chunk-G6FRSBKK.js} +7 -32
  57. package/dist/chunk-G6FRSBKK.js.map +1 -0
  58. package/dist/chunk-GIV6DWBG.js +79 -0
  59. package/dist/chunk-GIV6DWBG.js.map +1 -0
  60. package/dist/chunk-HXJXPZRE.js +73 -0
  61. package/dist/chunk-HXJXPZRE.js.map +1 -0
  62. package/dist/{chunk-GOUT6DND.js → chunk-J4KLMEUL.js} +173 -91
  63. package/dist/chunk-J4KLMEUL.js.map +1 -0
  64. package/dist/{chunk-2CSJGFCB.js → chunk-JYQTXEIO.js} +6 -229
  65. package/dist/chunk-JYQTXEIO.js.map +1 -0
  66. package/dist/{chunk-MDDTIZUO.js → chunk-LRAZDV5X.js} +7 -119
  67. package/dist/chunk-LRAZDV5X.js.map +1 -0
  68. package/dist/{chunk-M5INGEFC.js → chunk-MRIBLZL3.js} +3 -1
  69. package/dist/chunk-MRIBLZL3.js.map +1 -0
  70. package/dist/{chunk-USKYUS74.js → chunk-MUWOSVEP.js} +2 -2
  71. package/dist/{chunk-4PWAI7Q4.js → chunk-NWZ3I6R6.js} +5 -5
  72. package/dist/chunk-OVZDFEOR.js +124 -0
  73. package/dist/chunk-OVZDFEOR.js.map +1 -0
  74. package/dist/chunk-PEULZC6M.js +118 -0
  75. package/dist/chunk-PEULZC6M.js.map +1 -0
  76. package/dist/chunk-PFSNOPBQ.js +233 -0
  77. package/dist/chunk-PFSNOPBQ.js.map +1 -0
  78. package/dist/chunk-PLI5TV7N.js +53 -0
  79. package/dist/chunk-PLI5TV7N.js.map +1 -0
  80. package/dist/{chunk-WDM5XGGS.js → chunk-Q6W2CMEJ.js} +181 -11
  81. package/dist/chunk-Q6W2CMEJ.js.map +1 -0
  82. package/dist/{chunk-QGZRWRSL.js → chunk-QPEXPHJR.js} +4 -4
  83. package/dist/{chunk-R36SIKES.js → chunk-QXQRKXCU.js} +2 -2
  84. package/dist/chunk-RTZVQAJ7.js +82 -0
  85. package/dist/chunk-RTZVQAJ7.js.map +1 -0
  86. package/dist/chunk-TBKOGSYR.js +296 -0
  87. package/dist/chunk-TBKOGSYR.js.map +1 -0
  88. package/dist/chunk-UMLVJTYV.js +20 -0
  89. package/dist/chunk-UMLVJTYV.js.map +1 -0
  90. package/dist/chunk-UND4XIB6.js +251 -0
  91. package/dist/chunk-UND4XIB6.js.map +1 -0
  92. package/dist/chunk-VCGTOS2A.js +795 -0
  93. package/dist/chunk-VCGTOS2A.js.map +1 -0
  94. package/dist/chunk-VE6YVP32.js +19 -0
  95. package/dist/chunk-VE6YVP32.js.map +1 -0
  96. package/dist/{chunk-M62XNWRA.js → chunk-VK5EER6C.js} +2 -2
  97. package/dist/{chunk-NXFEYLVG.js → chunk-VPSUZLOJ.js} +4 -3
  98. package/dist/{chunk-NXFEYLVG.js.map → chunk-VPSUZLOJ.js.map} +1 -1
  99. package/dist/{chunk-TDR6T5CJ.js → chunk-VRBCTEKQ.js} +91 -132
  100. package/dist/chunk-VRBCTEKQ.js.map +1 -0
  101. package/dist/{chunk-ACLDOTNQ.js → chunk-W3XXT26A.js} +303 -3
  102. package/dist/chunk-W3XXT26A.js.map +1 -0
  103. package/dist/{chunk-CIMZBAZB.js → chunk-XG3PTSCD.js} +1 -1
  104. package/dist/chunk-XG3PTSCD.js.map +1 -0
  105. package/dist/chunk-Y2RKOPNC.js +145 -0
  106. package/dist/chunk-Y2RKOPNC.js.map +1 -0
  107. package/dist/{chunk-NPC4LFV5.js → chunk-YMYK7US4.js} +2 -2
  108. package/dist/{chunk-RKJ6OL7K.js → chunk-YS3POABP.js} +1 -1
  109. package/dist/chunk-YS3POABP.js.map +1 -0
  110. package/dist/chunk-YTXSFG3C.js +179 -0
  111. package/dist/chunk-YTXSFG3C.js.map +1 -0
  112. package/dist/consent/index.cjs.map +1 -1
  113. package/dist/consent/index.d.cts +7 -6
  114. package/dist/consent/index.d.ts +7 -6
  115. package/dist/consent/index.js +3 -3
  116. package/dist/{crypto-IVKU7YTT.js → crypto-5ZDIY3NG.js} +3 -3
  117. package/dist/{delegation-2DBS2EOH.js → delegation-QYXZW25W.js} +5 -4
  118. package/dist/derivations/index.cjs +351 -0
  119. package/dist/derivations/index.cjs.map +1 -0
  120. package/dist/derivations/index.d.cts +72 -0
  121. package/dist/derivations/index.d.ts +72 -0
  122. package/dist/derivations/index.js +27 -0
  123. package/dist/{dev-unlock-Da1B0TIK.d.cts → dev-unlock-DQCNDfFp.d.cts} +1 -1
  124. package/dist/{dev-unlock-BdPp68qn.d.ts → dev-unlock-utkybTKb.d.ts} +1 -1
  125. package/dist/executor-AS2IDHKZ.js +11 -0
  126. package/dist/executor-HLXFXNFM.js +8 -0
  127. package/dist/executor-HLXFXNFM.js.map +1 -0
  128. package/dist/executor-HN6YBHZ5.js +8 -0
  129. package/dist/executor-HN6YBHZ5.js.map +1 -0
  130. package/dist/fanout-sidecar-VJ52RIEY.js +51 -0
  131. package/dist/fanout-sidecar-VJ52RIEY.js.map +1 -0
  132. package/dist/guards/index.cjs +315 -0
  133. package/dist/guards/index.cjs.map +1 -0
  134. package/dist/guards/index.d.cts +31 -0
  135. package/dist/guards/index.d.ts +31 -0
  136. package/dist/guards/index.js +29 -0
  137. package/dist/guards/index.js.map +1 -0
  138. package/dist/{hash-lsoL3eEW.d.ts → hash-DcoYWfJ_.d.ts} +1 -1
  139. package/dist/{hash-BEfzPKwo.d.cts → hash-jDowCrK2.d.cts} +1 -1
  140. package/dist/history/index.cjs +8 -1
  141. package/dist/history/index.cjs.map +1 -1
  142. package/dist/history/index.d.cts +8 -7
  143. package/dist/history/index.d.ts +8 -7
  144. package/dist/history/index.js +6 -6
  145. package/dist/i18n/index.cjs +81 -0
  146. package/dist/i18n/index.cjs.map +1 -1
  147. package/dist/i18n/index.d.cts +7 -6
  148. package/dist/i18n/index.d.ts +7 -6
  149. package/dist/i18n/index.js +27 -12
  150. package/dist/i18n/index.js.map +1 -1
  151. package/dist/{index-6xNpPsxR.d.cts → index-BCKdioeh.d.ts} +331 -5
  152. package/dist/{index-DJTf9yxn.d.ts → index-BMjrzNZr.d.cts} +331 -5
  153. package/dist/index.cjs +6065 -959
  154. package/dist/index.cjs.map +1 -1
  155. package/dist/index.d.cts +208 -16
  156. package/dist/index.d.ts +208 -16
  157. package/dist/index.js +242 -7392
  158. package/dist/index.js.map +1 -1
  159. package/dist/indexing/index.cjs +2 -0
  160. package/dist/indexing/index.cjs.map +1 -1
  161. package/dist/indexing/index.d.cts +3 -3
  162. package/dist/indexing/index.d.ts +3 -3
  163. package/dist/indexing/index.js +4 -4
  164. package/dist/issue-ORP37MVW.js +12 -0
  165. package/dist/issue-ORP37MVW.js.map +1 -0
  166. package/dist/{lazy-builder-CZVLKh0Z.d.cts → lazy-builder-C-rPfWG0.d.cts} +1 -1
  167. package/dist/{lazy-builder-BwEoBQZ9.d.ts → lazy-builder-Rpd-V3jP.d.ts} +1 -1
  168. package/dist/{ledger-QZTTHQAQ.js → ledger-3IU5GMXA.js} +6 -6
  169. package/dist/ledger-3IU5GMXA.js.map +1 -0
  170. package/dist/materialized-views/index.cjs +837 -0
  171. package/dist/materialized-views/index.cjs.map +1 -0
  172. package/dist/materialized-views/index.d.cts +184 -0
  173. package/dist/materialized-views/index.d.ts +184 -0
  174. package/dist/materialized-views/index.js +45 -0
  175. package/dist/materialized-views/index.js.map +1 -0
  176. package/dist/noydb-5H3C24GG.js +34 -0
  177. package/dist/noydb-5H3C24GG.js.map +1 -0
  178. package/dist/overlay-views/index.cjs +359 -0
  179. package/dist/overlay-views/index.cjs.map +1 -0
  180. package/dist/overlay-views/index.d.cts +82 -0
  181. package/dist/overlay-views/index.d.ts +82 -0
  182. package/dist/overlay-views/index.js +25 -0
  183. package/dist/overlay-views/index.js.map +1 -0
  184. package/dist/periods/index.cjs +7 -1
  185. package/dist/periods/index.cjs.map +1 -1
  186. package/dist/periods/index.d.cts +7 -6
  187. package/dist/periods/index.d.ts +7 -6
  188. package/dist/periods/index.js +6 -6
  189. package/dist/{predicate-SBHmi6D0.d.cts → predicate-Dnu81tsS.d.cts} +25 -1
  190. package/dist/{predicate-SBHmi6D0.d.ts → predicate-Dnu81tsS.d.ts} +25 -1
  191. package/dist/{public-envelope-6JTACYJV.js → public-envelope-U3CMEOMV.js} +4 -4
  192. package/dist/public-envelope-U3CMEOMV.js.map +1 -0
  193. package/dist/query/index.cjs +302 -124
  194. package/dist/query/index.cjs.map +1 -1
  195. package/dist/query/index.d.cts +3 -3
  196. package/dist/query/index.d.ts +3 -3
  197. package/dist/query/index.js +26 -11
  198. package/dist/read-only-facade-ITU6L7BL.js +7 -0
  199. package/dist/read-only-facade-ITU6L7BL.js.map +1 -0
  200. package/dist/registry-3ALP62P6.js +10 -0
  201. package/dist/registry-3ALP62P6.js.map +1 -0
  202. package/dist/registry-7HE6VJGC.js +8 -0
  203. package/dist/registry-7HE6VJGC.js.map +1 -0
  204. package/dist/registry-PSIPG2QR.js +8 -0
  205. package/dist/registry-PSIPG2QR.js.map +1 -0
  206. package/dist/registry-RFGGMVNJ.js +7 -0
  207. package/dist/registry-RFGGMVNJ.js.map +1 -0
  208. package/dist/revoke-KY2GB4KP.js +17 -0
  209. package/dist/revoke-KY2GB4KP.js.map +1 -0
  210. package/dist/session/index.cjs +7 -1
  211. package/dist/session/index.cjs.map +1 -1
  212. package/dist/session/index.d.cts +8 -7
  213. package/dist/session/index.d.ts +8 -7
  214. package/dist/session/index.js +10 -3
  215. package/dist/session/index.js.map +1 -1
  216. package/dist/shadow/index.cjs.map +1 -1
  217. package/dist/shadow/index.d.cts +7 -6
  218. package/dist/shadow/index.d.ts +7 -6
  219. package/dist/shadow/index.js +2 -2
  220. package/dist/signer-GRI5TZKH.js +18 -0
  221. package/dist/signer-GRI5TZKH.js.map +1 -0
  222. package/dist/stale-OTOF3FH7.js +13 -0
  223. package/dist/stale-OTOF3FH7.js.map +1 -0
  224. package/dist/store/index.cjs +14 -0
  225. package/dist/store/index.cjs.map +1 -1
  226. package/dist/store/index.d.cts +7 -6
  227. package/dist/store/index.d.ts +7 -6
  228. package/dist/store/index.js +5 -2
  229. package/dist/{strategy-D-SrOLCl.d.cts → strategy-DSTrsZ8t.d.cts} +72 -19
  230. package/dist/{strategy-D-SrOLCl.d.ts → strategy-DSTrsZ8t.d.ts} +72 -19
  231. package/dist/sync/index.cjs.map +1 -1
  232. package/dist/sync/index.d.cts +6 -5
  233. package/dist/sync/index.d.ts +6 -5
  234. package/dist/sync/index.js +4 -4
  235. package/dist/team/index.cjs +1554 -2
  236. package/dist/team/index.cjs.map +1 -1
  237. package/dist/team/index.d.cts +7 -6
  238. package/dist/team/index.d.ts +7 -6
  239. package/dist/team/index.js +77 -8
  240. package/dist/tx/index.cjs +296 -44
  241. package/dist/tx/index.cjs.map +1 -1
  242. package/dist/tx/index.d.cts +7 -6
  243. package/dist/tx/index.d.ts +7 -6
  244. package/dist/tx/index.js +2 -2
  245. package/dist/{types-Bo7NSXJr.d.ts → types-BoFFiskX.d.ts} +2714 -321
  246. package/dist/{types-Bnb82f5R.d.cts → types-DJG8HG6F.d.cts} +2714 -321
  247. package/dist/{index-CywCC1qZ.d.cts → ulid-BmBgooGm.d.ts} +215 -26
  248. package/dist/{index-8QDuznDr.d.ts → ulid-C7ms9oli.d.cts} +215 -26
  249. package/dist/util/index.cjs.map +1 -1
  250. package/dist/util/index.js +1 -1
  251. package/dist/with-derivation-BKXXa8Vt.d.ts +13 -0
  252. package/dist/with-derivation-BjQ7q4NE.d.cts +13 -0
  253. package/dist/with-guard-C25yNjzd.d.ts +18 -0
  254. package/dist/with-guard-DQme5DKE.d.cts +18 -0
  255. package/dist/with-materialized-view-BbEPFIIJ.d.cts +27 -0
  256. package/dist/with-materialized-view-CqnRwI2S.d.ts +27 -0
  257. package/dist/with-overlayed-view-Ct1fSJt-.d.ts +13 -0
  258. package/dist/with-overlayed-view-bwlmmFjx.d.cts +13 -0
  259. package/package.json +65 -2
  260. package/dist/chunk-2CSJGFCB.js.map +0 -1
  261. package/dist/chunk-ACLDOTNQ.js.map +0 -1
  262. package/dist/chunk-BTDCBVJW.js +0 -160
  263. package/dist/chunk-BTDCBVJW.js.map +0 -1
  264. package/dist/chunk-CIMZBAZB.js.map +0 -1
  265. package/dist/chunk-EXHNQEV4.js +0 -392
  266. package/dist/chunk-EXHNQEV4.js.map +0 -1
  267. package/dist/chunk-GOUT6DND.js.map +0 -1
  268. package/dist/chunk-M5INGEFC.js.map +0 -1
  269. package/dist/chunk-MDDTIZUO.js.map +0 -1
  270. package/dist/chunk-QAVUREFT.js.map +0 -1
  271. package/dist/chunk-RKJ6OL7K.js.map +0 -1
  272. package/dist/chunk-SCZXXXU4.js.map +0 -1
  273. package/dist/chunk-TDR6T5CJ.js.map +0 -1
  274. package/dist/chunk-WDM5XGGS.js.map +0 -1
  275. /package/dist/{chunk-PTVMYYON.js.map → chunk-243PNUA6.js.map} +0 -0
  276. /package/dist/{chunk-MR4424N3.js.map → chunk-2PAQNPE3.js.map} +0 -0
  277. /package/dist/{chunk-AVVPZ4BC.js.map → chunk-3Y53S2SA.js.map} +0 -0
  278. /package/dist/{chunk-ZFKD4QMV.js.map → chunk-7BRE6EUA.js.map} +0 -0
  279. /package/dist/{chunk-USKYUS74.js.map → chunk-MUWOSVEP.js.map} +0 -0
  280. /package/dist/{chunk-4PWAI7Q4.js.map → chunk-NWZ3I6R6.js.map} +0 -0
  281. /package/dist/{chunk-QGZRWRSL.js.map → chunk-QPEXPHJR.js.map} +0 -0
  282. /package/dist/{chunk-R36SIKES.js.map → chunk-QXQRKXCU.js.map} +0 -0
  283. /package/dist/{chunk-M62XNWRA.js.map → chunk-VK5EER6C.js.map} +0 -0
  284. /package/dist/{chunk-NPC4LFV5.js.map → chunk-YMYK7US4.js.map} +0 -0
  285. /package/dist/{crypto-IVKU7YTT.js.map → crypto-5ZDIY3NG.js.map} +0 -0
  286. /package/dist/{delegation-2DBS2EOH.js.map → delegation-QYXZW25W.js.map} +0 -0
  287. /package/dist/{ledger-QZTTHQAQ.js.map → derivations/index.js.map} +0 -0
  288. /package/dist/{public-envelope-6JTACYJV.js.map → executor-AS2IDHKZ.js.map} +0 -0
@@ -0,0 +1,830 @@
1
+ import {
2
+ dekKey
3
+ } from "./chunk-7BUTTVMR.js";
4
+ import {
5
+ assertStrongPassphrase,
6
+ mintKeyringCanary,
7
+ persistKeyring
8
+ } from "./chunk-Q6W2CMEJ.js";
9
+ import {
10
+ NOYDB_FORMAT_VERSION,
11
+ NOYDB_KEYRING_VERSION
12
+ } from "./chunk-YS3POABP.js";
13
+ import {
14
+ base64ToBuffer,
15
+ bufferToBase64,
16
+ decrypt,
17
+ deriveKey,
18
+ encrypt,
19
+ generateSalt,
20
+ unwrapKey,
21
+ wrapKey
22
+ } from "./chunk-2PAQNPE3.js";
23
+ import {
24
+ DelegationTargetMissingError,
25
+ InvalidKeyError,
26
+ NoAccessError,
27
+ NoydbError,
28
+ PermissionDeniedError,
29
+ PrivilegeEscalationError,
30
+ ValidationError
31
+ } from "./chunk-W3XXT26A.js";
32
+
33
+ // src/team/authenticators.ts
34
+ async function enrollAuthenticator(store, vault, keyring, options) {
35
+ const existing = keyring.authenticators.find((a) => a.id === options.id);
36
+ if (existing) {
37
+ throw new ValidationError(
38
+ `enrollAuthenticator: slot id "${options.id}" already exists in vault "${vault}". Remove the slot first or pick a unique id.`
39
+ );
40
+ }
41
+ const base = {
42
+ id: options.id,
43
+ method: options.method,
44
+ enrolled_at: (/* @__PURE__ */ new Date()).toISOString(),
45
+ enrolled_via_tier: options.enrolled_via_tier ?? 1,
46
+ meta: options.meta
47
+ };
48
+ const slot = options.wrapKind === "deks" ? {
49
+ ...base,
50
+ wrapKind: "deks",
51
+ wrapped_deks: options.wrapped_deks,
52
+ iv: options.iv
53
+ } : {
54
+ ...base,
55
+ wrapped_kek: options.wrapped_kek
56
+ };
57
+ const next = appendSlot(keyring, slot);
58
+ await persistKeyring(store, vault, next);
59
+ return next;
60
+ }
61
+ async function updateAuthenticator(store, vault, keyring, slotId, options) {
62
+ if (options.meta === void 0) {
63
+ throw new ValidationError(
64
+ `updateAuthenticator: at least one of meta must be provided (slotId: "${slotId}").`
65
+ );
66
+ }
67
+ const idx = keyring.authenticators.findIndex((a) => a.id === slotId);
68
+ if (idx === -1) {
69
+ throw new NoAccessError(
70
+ `updateAuthenticator: slot "${slotId}" not found in vault "${vault}".`
71
+ );
72
+ }
73
+ const existing = keyring.authenticators[idx];
74
+ const mergedMeta = { ...existing.meta };
75
+ for (const [k, v] of Object.entries(options.meta)) {
76
+ if (v === void 0) continue;
77
+ if (v === null) {
78
+ delete mergedMeta[k];
79
+ continue;
80
+ }
81
+ mergedMeta[k] = v;
82
+ }
83
+ const next = { ...existing, meta: mergedMeta };
84
+ const nextSlots = [...keyring.authenticators];
85
+ nextSlots[idx] = next;
86
+ const nextKeyring = {
87
+ ...keyring,
88
+ authenticators: nextSlots
89
+ };
90
+ await persistKeyring(store, vault, nextKeyring);
91
+ return nextKeyring;
92
+ }
93
+ async function removeAuthenticator(store, vault, keyring, slotId) {
94
+ const filtered = keyring.authenticators.filter((a) => a.id !== slotId);
95
+ if (filtered.length === keyring.authenticators.length) {
96
+ return keyring;
97
+ }
98
+ const next = {
99
+ ...keyring,
100
+ authenticators: filtered
101
+ };
102
+ await persistKeyring(store, vault, next);
103
+ return next;
104
+ }
105
+ function findAuthenticator(keyring, slotId) {
106
+ return keyring.authenticators.find((a) => a.id === slotId);
107
+ }
108
+ function appendSlot(keyring, slot) {
109
+ return {
110
+ ...keyring,
111
+ authenticators: [...keyring.authenticators, slot]
112
+ };
113
+ }
114
+
115
+ // src/policy/errors.ts
116
+ var PolicyDeniedError = class extends NoydbError {
117
+ gate;
118
+ reason;
119
+ required;
120
+ constructor(gate, reason, required, message) {
121
+ super(
122
+ "POLICY_DENIED",
123
+ message ?? `Gate "${gate}" denied: ${reason}.`
124
+ );
125
+ this.name = "PolicyDeniedError";
126
+ this.gate = gate;
127
+ this.reason = reason;
128
+ this.required = required;
129
+ }
130
+ };
131
+ var RecoveryNotEnrolledError = class extends NoydbError {
132
+ constructor(message = 'Recovery profile not enrolled. Pass `recovery: [{ profile: "paper", codes: 10 }]` to `createNoydb()`, or set `policy.gates["recover-passphrase"].enabled = false` to opt out of recovery (passphrase loss = data loss). See docs/subsystems/session-tiers.md.') {
133
+ super("RECOVERY_NOT_ENROLLED", message);
134
+ this.name = "RecoveryNotEnrolledError";
135
+ }
136
+ };
137
+ var ManagedRecoveryNotEnrolledError = class extends NoydbError {
138
+ vault;
139
+ constructor(vault) {
140
+ super(
141
+ "MANAGED_RECOVERY_NOT_ENROLLED",
142
+ `Managed-mode vault "${vault}" requires at least one strong recovery profile (Shamir today; multi-channel / admin-mediated when they ship). Paper alone is NOT strong under managed mode \u2014 losing the paper sheet would mean losing every record permanently. Bootstrap with \`db.openVaultAndEnrollRecovery("${vault}", { recovery: [{ profile: "shamir", k: 2, n: 3 }] })\`, or call \`db.enrollRecovery(vault, { profile: "shamir", k, n })\` separately, then re-attempt \`openVault\`.`
143
+ );
144
+ this.name = "ManagedRecoveryNotEnrolledError";
145
+ this.vault = vault;
146
+ }
147
+ };
148
+ var RecoveryProfileNotImplementedError = class extends NoydbError {
149
+ profile;
150
+ tracking;
151
+ constructor(profile, tracking) {
152
+ super(
153
+ "RECOVERY_PROFILE_NOT_IMPLEMENTED",
154
+ `Recovery profile "${profile}" is not yet implemented in this hub release. Tracking: ${tracking}. Use the "paper" profile via @noy-db/on-recovery in the meantime.`
155
+ );
156
+ this.name = "RecoveryProfileNotImplementedError";
157
+ this.profile = profile;
158
+ this.tracking = tracking;
159
+ }
160
+ };
161
+
162
+ // src/team/wrapped-deks.ts
163
+ var PBKDF2_ITERATIONS = 6e5;
164
+ var SALT_BYTES = 32;
165
+ var IV_BYTES = 12;
166
+ var subtle = globalThis.crypto.subtle;
167
+ async function mintWrappedDeksBlob(deks, credential) {
168
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES));
169
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
170
+ const wrappingKey = await deriveWrappingKey(credential, salt);
171
+ const exported = {};
172
+ for (const [coll, dek] of deks) {
173
+ const raw = await subtle.exportKey("raw", dek);
174
+ exported[coll] = bytesToBase64(new Uint8Array(raw));
175
+ }
176
+ const plaintext = new TextEncoder().encode(JSON.stringify({ deks: exported }));
177
+ const ciphertext = await subtle.encrypt(
178
+ { name: "AES-GCM", iv },
179
+ wrappingKey,
180
+ plaintext
181
+ );
182
+ return {
183
+ salt: bytesToBase64(salt),
184
+ iv: bytesToBase64(iv),
185
+ wrappedDeks: bytesToBase64(new Uint8Array(ciphertext))
186
+ };
187
+ }
188
+ async function unwrapDeksFromBlob(blob, credential) {
189
+ const wrappingKey = await deriveWrappingKey(credential, base64ToBytes(blob.salt));
190
+ const plaintext = await subtle.decrypt(
191
+ { name: "AES-GCM", iv: base64ToBytes(blob.iv) },
192
+ wrappingKey,
193
+ base64ToBytes(blob.wrappedDeks)
194
+ );
195
+ const parsed = JSON.parse(new TextDecoder().decode(plaintext));
196
+ const deks = /* @__PURE__ */ new Map();
197
+ for (const [coll, b64] of Object.entries(parsed.deks)) {
198
+ const raw = base64ToBytes(b64);
199
+ const key = await subtle.importKey(
200
+ "raw",
201
+ raw,
202
+ { name: "AES-GCM", length: 256 },
203
+ true,
204
+ ["encrypt", "decrypt"]
205
+ );
206
+ deks.set(coll, key);
207
+ }
208
+ return deks;
209
+ }
210
+ async function deriveWrappingKey(credential, salt) {
211
+ const ikm = await subtle.importKey(
212
+ "raw",
213
+ new TextEncoder().encode(credential),
214
+ "PBKDF2",
215
+ false,
216
+ ["deriveKey"]
217
+ );
218
+ return subtle.deriveKey(
219
+ {
220
+ name: "PBKDF2",
221
+ salt,
222
+ iterations: PBKDF2_ITERATIONS,
223
+ hash: "SHA-256"
224
+ },
225
+ ikm,
226
+ { name: "AES-GCM", length: 256 },
227
+ false,
228
+ ["encrypt", "decrypt"]
229
+ );
230
+ }
231
+ function bytesToBase64(b) {
232
+ let s = "";
233
+ for (const x of b) s += String.fromCharCode(x);
234
+ return btoa(s);
235
+ }
236
+ function base64ToBytes(b64) {
237
+ const s = atob(b64);
238
+ const out = new Uint8Array(s.length);
239
+ for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
240
+ return out;
241
+ }
242
+
243
+ // src/team/recovery.ts
244
+ var PAPER_DOC_ID = "recovery-paper";
245
+ async function loadPaperRecoveryEntries(store, vault) {
246
+ const env = await store.get(vault, "_meta", PAPER_DOC_ID);
247
+ if (!env) return [];
248
+ try {
249
+ const doc = JSON.parse(env._data);
250
+ if (doc.profile !== "paper" || !Array.isArray(doc.entries)) return [];
251
+ return doc.entries;
252
+ } catch {
253
+ return [];
254
+ }
255
+ }
256
+ async function savePaperRecoveryEntries(store, vault, entries) {
257
+ const doc = {
258
+ _noydb_recovery: 1,
259
+ profile: "paper",
260
+ entries
261
+ };
262
+ const envelope = {
263
+ _noydb: NOYDB_FORMAT_VERSION,
264
+ _v: 1,
265
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
266
+ _iv: "",
267
+ _data: JSON.stringify(doc)
268
+ };
269
+ await store.put(vault, "_meta", PAPER_DOC_ID, envelope);
270
+ }
271
+ async function burnPaperRecoveryEntry(store, vault, codeId) {
272
+ const entries = await loadPaperRecoveryEntries(store, vault);
273
+ const remaining = entries.filter((e) => e.codeId !== codeId);
274
+ await savePaperRecoveryEntries(store, vault, remaining);
275
+ }
276
+ async function hasRecoveryEnrolled(store, vault) {
277
+ const paper = await loadPaperRecoveryEntries(store, vault);
278
+ if (paper.length > 0) return true;
279
+ const shamir = await loadShamirRecoveryEntries(store, vault);
280
+ return shamir.length > 0;
281
+ }
282
+ async function hasStrongRecoveryEnrolled(store, vault) {
283
+ const shamir = await loadShamirRecoveryEntries(store, vault);
284
+ return shamir.length > 0;
285
+ }
286
+ var SHAMIR_DOC_ID = "recovery-shamir";
287
+ async function loadShamirRecoveryEntries(store, vault) {
288
+ const env = await store.get(vault, "_meta", SHAMIR_DOC_ID);
289
+ if (!env) return [];
290
+ try {
291
+ const doc = JSON.parse(env._data);
292
+ if (doc.profile !== "shamir" || !Array.isArray(doc.entries)) return [];
293
+ return doc.entries;
294
+ } catch {
295
+ return [];
296
+ }
297
+ }
298
+ async function saveShamirRecoveryEntries(store, vault, entries) {
299
+ const doc = {
300
+ _noydb_recovery: 1,
301
+ profile: "shamir",
302
+ entries
303
+ };
304
+ const envelope = {
305
+ _noydb: NOYDB_FORMAT_VERSION,
306
+ _v: 1,
307
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
308
+ _iv: "",
309
+ _data: JSON.stringify(doc)
310
+ };
311
+ await store.put(vault, "_meta", SHAMIR_DOC_ID, envelope);
312
+ }
313
+ async function mintShamirRecoveryEntry(provider, deks, entryId, k, n, label) {
314
+ const recoverySecret = crypto.getRandomValues(new Uint8Array(32));
315
+ try {
316
+ const credential = bytesToBase642(recoverySecret);
317
+ const blob = await mintWrappedDeksBlob(deks, credential);
318
+ const shareStrings = provider.splitToShares(recoverySecret, k, n);
319
+ const entry = {
320
+ ...blob,
321
+ entryId,
322
+ k,
323
+ n,
324
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString(),
325
+ ...label !== void 0 && { label }
326
+ };
327
+ return { entry, shareStrings };
328
+ } finally {
329
+ recoverySecret.fill(0);
330
+ }
331
+ }
332
+ async function unwrapDeksFromShamirEntry(provider, entry, shareStrings) {
333
+ if (shareStrings.length < entry.k) {
334
+ throw new Error(
335
+ `Insufficient shares: this Shamir entry needs ${entry.k} of ${entry.n}, but ${shareStrings.length} were provided.`
336
+ );
337
+ }
338
+ const secret = provider.combineShares(shareStrings);
339
+ try {
340
+ return await unwrapDeksFromBlob(entry, bytesToBase642(secret));
341
+ } finally {
342
+ secret.fill(0);
343
+ }
344
+ }
345
+ function bytesToBase642(b) {
346
+ let s = "";
347
+ for (const x of b) s += String.fromCharCode(x);
348
+ return btoa(s);
349
+ }
350
+ async function mintPaperRecoveryEntry(deks, code, codeId) {
351
+ const blob = await mintWrappedDeksBlob(deks, code);
352
+ return {
353
+ ...blob,
354
+ codeId,
355
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
356
+ };
357
+ }
358
+ async function unwrapDeksFromPaperEntry(entry, code) {
359
+ return unwrapDeksFromBlob(entry, code);
360
+ }
361
+
362
+ // src/team/rotate-recover.ts
363
+ async function rotatePassphrase(store, vault, userId, input) {
364
+ if (!input.allowWeakPassphrase) {
365
+ assertStrongPassphrase(input.newPassphrase, input.passphrasePolicy);
366
+ }
367
+ const env = await store.get(vault, "_keyring", userId);
368
+ if (!env) {
369
+ throw new NoAccessError(`No keyring found for user "${userId}" in vault "${vault}".`);
370
+ }
371
+ const file = JSON.parse(env._data);
372
+ const oldSalt = base64ToBuffer(file.salt);
373
+ const oldKek = await deriveKey(input.oldPassphrase, oldSalt);
374
+ const deks = /* @__PURE__ */ new Map();
375
+ for (const [coll, wrapped] of Object.entries(file.deks)) {
376
+ deks.set(coll, await unwrapKey(wrapped, oldKek));
377
+ }
378
+ const newSalt = generateSalt();
379
+ const newKek = await deriveKey(input.newPassphrase, newSalt);
380
+ const wrappedDeks = {};
381
+ for (const [coll, dek] of deks) {
382
+ wrappedDeks[coll] = await wrapKey(dek, newKek);
383
+ }
384
+ const oldSlots = file.authenticators ?? [];
385
+ const newSlots = [];
386
+ if (input.slotCeremonies && oldSlots.length > 0) {
387
+ for (const oldSlot of oldSlots) {
388
+ const ceremony = input.slotCeremonies[oldSlot.id];
389
+ if (!ceremony) continue;
390
+ const result = await ceremony({ newKek, newDeks: deks, oldSlot });
391
+ if (result.id !== oldSlot.id) {
392
+ throw new ValidationError(
393
+ `slotCeremonies['${oldSlot.id}'] returned id="${result.id}". The id must match the rotated slot \u2014 a ceremony cannot change a slot's identity.`
394
+ );
395
+ }
396
+ if (result.method !== oldSlot.method) {
397
+ throw new ValidationError(
398
+ `slotCeremonies['${oldSlot.id}'] returned method="${result.method}", expected "${oldSlot.method}". The method must match the rotated slot \u2014 a ceremony cannot change the auth method (e.g. webauthn \u2192 password) under cover of rotation.`
399
+ );
400
+ }
401
+ const oldWrapKind = oldSlot.wrapKind ?? "kek";
402
+ const newWrapKind = result.wrapKind ?? "kek";
403
+ if (oldWrapKind !== newWrapKind) {
404
+ throw new ValidationError(
405
+ `slotCeremonies['${oldSlot.id}'] returned wrapKind="${newWrapKind}", expected "${oldWrapKind}". The wrap format must match the rotated slot \u2014 a ceremony cannot change the wrap shape (e.g. wrap-KEK \u2192 wrap-DEKs) under cover of rotation, since that would silently change the session tier produced at unlock.`
406
+ );
407
+ }
408
+ const baseFields = {
409
+ id: result.id,
410
+ method: result.method,
411
+ // Preserve original enrolled_at — rotation is rewrapping, not
412
+ // re-enrollment. The slot's enrolment timestamp tracks when
413
+ // the user originally added the slot, not when it was last
414
+ // rewrapped. Forensics consumers reading enrolled_at are
415
+ // tracking the slot's ORIGIN, not its CURRENT wrapping.
416
+ enrolled_at: oldSlot.enrolled_at,
417
+ enrolled_via_tier: result.enrolled_via_tier ?? oldSlot.enrolled_via_tier,
418
+ meta: result.meta
419
+ };
420
+ const newSlot = result.wrapKind === "deks" ? {
421
+ ...baseFields,
422
+ wrapKind: "deks",
423
+ wrapped_deks: result.wrapped_deks,
424
+ iv: result.iv
425
+ } : {
426
+ ...baseFields,
427
+ wrapped_kek: result.wrapped_kek
428
+ };
429
+ newSlots.push(newSlot);
430
+ }
431
+ }
432
+ const canary = await mintKeyringCanary(newKek);
433
+ const next = {
434
+ ...file,
435
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
436
+ deks: wrappedDeks,
437
+ salt: bufferToBase64(newSalt),
438
+ authenticators: newSlots,
439
+ canary
440
+ };
441
+ await writeKeyringFile(store, vault, userId, next);
442
+ return {
443
+ userId: file.user_id,
444
+ displayName: file.display_name,
445
+ role: file.role,
446
+ permissions: file.permissions,
447
+ deks,
448
+ kek: newKek,
449
+ salt: newSalt,
450
+ authenticators: newSlots,
451
+ ...file.export_capability !== void 0 && { exportCapability: file.export_capability },
452
+ ...file.import_capability !== void 0 && { importCapability: file.import_capability }
453
+ };
454
+ }
455
+ async function recoverPassphrase(provider, store, vault, userId, input) {
456
+ if (!input.allowWeakPassphrase) {
457
+ assertStrongPassphrase(input.newPassphrase, input.passphrasePolicy);
458
+ }
459
+ const profile = input.recoveryProof.profile;
460
+ if (profile === "paper") {
461
+ return recoverViaPaperCode(store, vault, userId, input);
462
+ }
463
+ if (profile === "shamir") {
464
+ return recoverViaShamir(provider, store, vault, userId, input);
465
+ }
466
+ throw new RecoveryProfileNotImplementedError(
467
+ profile,
468
+ "https://github.com/vLannaAi/noy-db/issues/196"
469
+ );
470
+ }
471
+ async function recoverViaPaperCode(store, vault, userId, input) {
472
+ if (input.recoveryProof.profile !== "paper") throw new Error("unreachable");
473
+ const { code } = input.recoveryProof.payload;
474
+ const env = await store.get(vault, "_keyring", userId);
475
+ if (!env) {
476
+ throw new NoAccessError(`No keyring found for user "${userId}" in vault "${vault}".`);
477
+ }
478
+ const file = JSON.parse(env._data);
479
+ const entries = await loadPaperRecoveryEntries(store, vault);
480
+ if (entries.length === 0) {
481
+ throw new NoAccessError(
482
+ `No paper-recovery entries enrolled for vault "${vault}". Enroll via \`db.enrollRecovery({ profile: "paper", entries })\` before relying on recovery.`
483
+ );
484
+ }
485
+ const normalized = normalizePaperCode(code);
486
+ let recovered;
487
+ for (const entry of entries) {
488
+ try {
489
+ const deks2 = await unwrapDeksFromPaperEntry(entry, normalized);
490
+ recovered = { deks: deks2, entry };
491
+ break;
492
+ } catch {
493
+ }
494
+ }
495
+ if (!recovered) {
496
+ throw new InvalidKeyError(
497
+ "Recovery code does not match any enrolled paper entry. The code may have been previously used (single-use) or typed incorrectly."
498
+ );
499
+ }
500
+ const deks = recovered.deks;
501
+ const newSalt = generateSalt();
502
+ const newKek = await deriveKey(input.newPassphrase, newSalt);
503
+ const wrappedDeks = {};
504
+ for (const [coll, dek] of deks) {
505
+ wrappedDeks[coll] = await wrapKey(dek, newKek);
506
+ }
507
+ const canary = await mintKeyringCanary(newKek);
508
+ const next = {
509
+ ...file,
510
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
511
+ deks: wrappedDeks,
512
+ salt: bufferToBase64(newSalt),
513
+ authenticators: [],
514
+ // tier-2 slots wrap old KEK, drop them
515
+ canary
516
+ };
517
+ await burnPaperRecoveryEntry(store, vault, recovered.entry.codeId);
518
+ await writeKeyringFile(store, vault, userId, next);
519
+ return {
520
+ userId: file.user_id,
521
+ displayName: file.display_name,
522
+ role: file.role,
523
+ permissions: file.permissions,
524
+ deks,
525
+ kek: newKek,
526
+ salt: newSalt,
527
+ authenticators: [],
528
+ ...file.export_capability !== void 0 && { exportCapability: file.export_capability },
529
+ ...file.import_capability !== void 0 && { importCapability: file.import_capability }
530
+ };
531
+ }
532
+ function normalizePaperCode(input) {
533
+ return input.toUpperCase().replace(/[\s\-_]/g, "");
534
+ }
535
+ async function recoverViaShamir(provider, store, vault, userId, input) {
536
+ if (input.recoveryProof.profile !== "shamir") throw new Error("unreachable");
537
+ const { entryId: requestedEntryId, shares: shareStrings } = input.recoveryProof.payload;
538
+ if (shareStrings.length === 0) {
539
+ throw new ValidationError(
540
+ "Shamir recovery requires at least one share; received an empty array."
541
+ );
542
+ }
543
+ const env = await store.get(vault, "_keyring", userId);
544
+ if (!env) {
545
+ throw new NoAccessError(`No keyring found for user "${userId}" in vault "${vault}".`);
546
+ }
547
+ const file = JSON.parse(env._data);
548
+ const allEntries = await loadShamirRecoveryEntries(store, vault);
549
+ if (allEntries.length === 0) {
550
+ throw new NoAccessError(
551
+ `No Shamir-recovery entries enrolled for vault "${vault}". Enroll via \`db.enrollRecovery({ profile: "shamir", k, n })\` before relying on recovery.`
552
+ );
553
+ }
554
+ if (!provider) {
555
+ throw new Error(
556
+ "shamir recovery requires a ShamirRecoveryProvider \u2014 pass shamirRecovery: shamirRecoveryProvider() from '@noy-db/on-shamir' to createNoydb()"
557
+ );
558
+ }
559
+ let candidates;
560
+ if (requestedEntryId !== void 0) {
561
+ candidates = allEntries.filter((e) => e.entryId === requestedEntryId);
562
+ if (candidates.length === 0) {
563
+ throw new NoAccessError(
564
+ `No Shamir-recovery entry with entryId="${requestedEntryId}" found in vault "${vault}". Available entries: ` + allEntries.map((e) => `"${e.entryId}"`).join(", ")
565
+ );
566
+ }
567
+ } else {
568
+ candidates = allEntries;
569
+ }
570
+ let recoveredDeks;
571
+ for (const entry of candidates) {
572
+ if (shareStrings.length < entry.k) {
573
+ continue;
574
+ }
575
+ try {
576
+ const deks = await unwrapDeksFromShamirEntry(provider, entry, shareStrings);
577
+ recoveredDeks = deks;
578
+ break;
579
+ } catch {
580
+ }
581
+ }
582
+ if (!recoveredDeks) {
583
+ const minK = Math.min(...candidates.map((e) => e.k));
584
+ if (shareStrings.length < minK) {
585
+ throw new InvalidKeyError(
586
+ `Insufficient Shamir shares to combine: the smallest enrolled threshold is ${minK}, but only ${shareStrings.length} share${shareStrings.length === 1 ? " was" : "s were"} provided.`
587
+ );
588
+ }
589
+ throw new InvalidKeyError(
590
+ "Shamir shares do not match any enrolled entry. Possible causes: shares were tampered with, came from a different enrollment, or the entry was rotated after these shares were distributed."
591
+ );
592
+ }
593
+ const newSalt = generateSalt();
594
+ const newKek = await deriveKey(input.newPassphrase, newSalt);
595
+ const wrappedDeks = {};
596
+ for (const [coll, dek] of recoveredDeks) {
597
+ wrappedDeks[coll] = await wrapKey(dek, newKek);
598
+ }
599
+ const canary = await mintKeyringCanary(newKek);
600
+ const next = {
601
+ ...file,
602
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
603
+ deks: wrappedDeks,
604
+ salt: bufferToBase64(newSalt),
605
+ authenticators: [],
606
+ // tier-2 slots wrap old KEK, drop them on recovery
607
+ canary
608
+ };
609
+ await writeKeyringFile(store, vault, userId, next);
610
+ return {
611
+ userId: file.user_id,
612
+ displayName: file.display_name,
613
+ role: file.role,
614
+ permissions: file.permissions,
615
+ deks: recoveredDeks,
616
+ kek: newKek,
617
+ salt: newSalt,
618
+ authenticators: [],
619
+ ...file.export_capability !== void 0 && { exportCapability: file.export_capability },
620
+ ...file.import_capability !== void 0 && { importCapability: file.import_capability }
621
+ };
622
+ }
623
+ async function writeKeyringFile(store, vault, userId, file) {
624
+ const envelope = {
625
+ _noydb: 1,
626
+ _v: 1,
627
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
628
+ _iv: "",
629
+ _data: JSON.stringify(file)
630
+ };
631
+ await store.put(vault, "_keyring", userId, envelope);
632
+ }
633
+
634
+ // src/team/peer-recover.ts
635
+ var ADMIN_RECOVERABLE_TARGETS = ["operator", "viewer", "client", "admin"];
636
+ function canRecover(callerRole, targetRole) {
637
+ if (callerRole === "owner") return true;
638
+ if (callerRole === "admin") return ADMIN_RECOVERABLE_TARGETS.includes(targetRole);
639
+ return false;
640
+ }
641
+ async function recoverUser(store, vault, callerKeyring, options) {
642
+ const env = await store.get(vault, "_keyring", options.userId);
643
+ if (!env) {
644
+ throw new NoAccessError(
645
+ `recoverUser: user "${options.userId}" has no keyring in vault "${vault}".`
646
+ );
647
+ }
648
+ const target = JSON.parse(env._data);
649
+ const targetRole = options.role ?? target.role;
650
+ if (!canRecover(callerKeyring.role, targetRole)) {
651
+ throw new PermissionDeniedError(
652
+ `Role "${callerKeyring.role}" cannot recover role "${targetRole}"`
653
+ );
654
+ }
655
+ if (!canRecover(callerKeyring.role, target.role)) {
656
+ throw new PermissionDeniedError(
657
+ `Role "${callerKeyring.role}" cannot recover role "${target.role}"`
658
+ );
659
+ }
660
+ for (const coll of Object.keys(target.deks)) {
661
+ if (!callerKeyring.deks.has(coll)) {
662
+ throw new PrivilegeEscalationError(coll);
663
+ }
664
+ }
665
+ if (options.validatePassphrase && !options.allowWeakPassphrase) {
666
+ assertStrongPassphrase(options.passphrase, options.passphrasePolicy);
667
+ }
668
+ const newSalt = generateSalt();
669
+ const newKek = await deriveKey(options.passphrase, newSalt);
670
+ const wrappedDeks = {};
671
+ for (const coll of Object.keys(target.deks)) {
672
+ const callerDek = callerKeyring.deks.get(coll);
673
+ if (!callerDek) {
674
+ throw new PrivilegeEscalationError(coll);
675
+ }
676
+ wrappedDeks[coll] = await wrapKey(callerDek, newKek);
677
+ }
678
+ const canary = await mintKeyringCanary(newKek);
679
+ const next = {
680
+ ...target,
681
+ _noydb_keyring: NOYDB_KEYRING_VERSION,
682
+ role: targetRole,
683
+ display_name: options.displayName ?? target.display_name,
684
+ deks: wrappedDeks,
685
+ salt: bufferToBase64(newSalt),
686
+ granted_by: callerKeyring.userId,
687
+ authenticators: [],
688
+ canary
689
+ };
690
+ const envelope = {
691
+ _noydb: 1,
692
+ _v: 1,
693
+ _ts: (/* @__PURE__ */ new Date()).toISOString(),
694
+ _iv: "",
695
+ _data: JSON.stringify(next)
696
+ };
697
+ await store.put(vault, "_keyring", options.userId, envelope);
698
+ }
699
+
700
+ // src/team/magic-link-grant.ts
701
+ var MAGIC_LINK_GRANTS_COLLECTION = "_magic_link_grants";
702
+ var MAGIC_LINK_CONTENT_INFO_PREFIX = "noydb-magic-link-content-v1:";
703
+ var MAGIC_LINK_KEK_INFO_PREFIX = "noydb-magic-link-v1:";
704
+ async function deriveMagicLinkContentKey(serverSecret, token, vault) {
705
+ const subtle2 = globalThis.crypto.subtle;
706
+ const ikmBytes = serverSecret instanceof Uint8Array ? serverSecret : new TextEncoder().encode(serverSecret);
707
+ const tokenBytes = new TextEncoder().encode(token);
708
+ const saltBuffer = await subtle2.digest("SHA-256", tokenBytes);
709
+ const info = new TextEncoder().encode(MAGIC_LINK_CONTENT_INFO_PREFIX + vault);
710
+ const ikm = await subtle2.importKey("raw", ikmBytes, "HKDF", false, ["deriveKey"]);
711
+ return subtle2.deriveKey(
712
+ { name: "HKDF", hash: "SHA-256", salt: saltBuffer, info },
713
+ ikm,
714
+ { name: "AES-GCM", length: 256 },
715
+ false,
716
+ ["encrypt", "decrypt"]
717
+ );
718
+ }
719
+ async function writeMagicLinkGrant(store, vault, grantor, contentKey, grantKek, recordId, opts) {
720
+ const collectionName = opts.collection ?? null;
721
+ const sourceKey = collectionName ? dekKey(collectionName, opts.tier) : `__any#${opts.tier}`;
722
+ const sourceDek = grantor.deks.get(sourceKey);
723
+ if (!sourceDek) {
724
+ throw new DelegationTargetMissingError(
725
+ `grantor cannot find tier ${opts.tier} DEK for ${collectionName ?? "(any)"}`
726
+ );
727
+ }
728
+ const wrappedDek = await wrapKey(sourceDek, grantKek);
729
+ const until = typeof opts.until === "string" ? opts.until : opts.until.toISOString();
730
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
731
+ const payload = {
732
+ id: recordId,
733
+ toUser: opts.toUser,
734
+ fromUser: grantor.userId,
735
+ tier: opts.tier,
736
+ collection: collectionName,
737
+ ...opts.record && { record: opts.record },
738
+ until,
739
+ wrappedDek,
740
+ createdAt,
741
+ ...opts.note && { note: opts.note }
742
+ };
743
+ const { iv, data } = await encrypt(JSON.stringify(payload), contentKey);
744
+ const envelope = {
745
+ _noydb: 1,
746
+ _v: 1,
747
+ _ts: createdAt,
748
+ _iv: iv,
749
+ _data: data,
750
+ _by: grantor.userId
751
+ };
752
+ await store.put(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId, envelope);
753
+ return { recordId, payload };
754
+ }
755
+ async function readMagicLinkGrantRecord(store, vault, contentKey, recordId) {
756
+ const env = await store.get(vault, MAGIC_LINK_GRANTS_COLLECTION, recordId);
757
+ if (!env) return null;
758
+ try {
759
+ const json = await decrypt(env._iv, env._data, contentKey);
760
+ return JSON.parse(json);
761
+ } catch {
762
+ return null;
763
+ }
764
+ }
765
+ async function listMagicLinkGrants(store, vault, contentKey, token) {
766
+ const ids = await store.list(vault, MAGIC_LINK_GRANTS_COLLECTION);
767
+ const matching = ids.filter((id) => id === token || id.startsWith(`${token}:`));
768
+ const out = [];
769
+ for (const id of matching) {
770
+ const payload = await readMagicLinkGrantRecord(store, vault, contentKey, id);
771
+ if (payload) out.push(payload);
772
+ }
773
+ return out;
774
+ }
775
+ async function unwrapMagicLinkGrant(payload, grantKek) {
776
+ return unwrapKey(payload.wrappedDek, grantKek);
777
+ }
778
+ async function revokeMagicLinkGrant(store, vault, token) {
779
+ const ids = await store.list(vault, MAGIC_LINK_GRANTS_COLLECTION);
780
+ const matching = ids.filter((id) => id === token || id.startsWith(`${token}:`));
781
+ for (const id of matching) {
782
+ await store.delete(vault, MAGIC_LINK_GRANTS_COLLECTION, id);
783
+ }
784
+ return matching.length;
785
+ }
786
+ function magicLinkGrantRecordId(token, index) {
787
+ return index === 0 ? token : `${token}:${index}`;
788
+ }
789
+ function isMagicLinkGrantExpired(payload, now = /* @__PURE__ */ new Date()) {
790
+ return payload.until <= now.toISOString();
791
+ }
792
+
793
+ export {
794
+ enrollAuthenticator,
795
+ updateAuthenticator,
796
+ removeAuthenticator,
797
+ findAuthenticator,
798
+ PolicyDeniedError,
799
+ RecoveryNotEnrolledError,
800
+ ManagedRecoveryNotEnrolledError,
801
+ RecoveryProfileNotImplementedError,
802
+ mintWrappedDeksBlob,
803
+ unwrapDeksFromBlob,
804
+ loadPaperRecoveryEntries,
805
+ savePaperRecoveryEntries,
806
+ burnPaperRecoveryEntry,
807
+ hasRecoveryEnrolled,
808
+ hasStrongRecoveryEnrolled,
809
+ loadShamirRecoveryEntries,
810
+ saveShamirRecoveryEntries,
811
+ mintShamirRecoveryEntry,
812
+ unwrapDeksFromShamirEntry,
813
+ mintPaperRecoveryEntry,
814
+ unwrapDeksFromPaperEntry,
815
+ rotatePassphrase,
816
+ recoverPassphrase,
817
+ recoverUser,
818
+ MAGIC_LINK_GRANTS_COLLECTION,
819
+ MAGIC_LINK_CONTENT_INFO_PREFIX,
820
+ MAGIC_LINK_KEK_INFO_PREFIX,
821
+ deriveMagicLinkContentKey,
822
+ writeMagicLinkGrant,
823
+ readMagicLinkGrantRecord,
824
+ listMagicLinkGrants,
825
+ unwrapMagicLinkGrant,
826
+ revokeMagicLinkGrant,
827
+ magicLinkGrantRecordId,
828
+ isMagicLinkGrantExpired
829
+ };
830
+ //# sourceMappingURL=chunk-EUYOGYGV.js.map