@brightchain/brightchain-api-lib 0.14.0 → 0.15.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.
Files changed (242) hide show
  1. package/package.json +5 -5
  2. package/src/index.d.ts +3 -0
  3. package/src/index.d.ts.map +1 -1
  4. package/src/index.js +5 -0
  5. package/src/index.js.map +1 -1
  6. package/src/lib/application.d.ts +1 -0
  7. package/src/lib/application.d.ts.map +1 -1
  8. package/src/lib/application.js +23 -0
  9. package/src/lib/application.js.map +1 -1
  10. package/src/lib/auth/aclEnforcedAvailability.d.ts +57 -0
  11. package/src/lib/auth/aclEnforcedAvailability.d.ts.map +1 -0
  12. package/src/lib/auth/aclEnforcedAvailability.js +87 -0
  13. package/src/lib/auth/aclEnforcedAvailability.js.map +1 -0
  14. package/src/lib/auth/aclEnforcedBlockStore.d.ts +66 -0
  15. package/src/lib/auth/aclEnforcedBlockStore.d.ts.map +1 -0
  16. package/src/lib/auth/aclEnforcedBlockStore.js +83 -0
  17. package/src/lib/auth/aclEnforcedBlockStore.js.map +1 -0
  18. package/src/lib/auth/ecdsaNodeAuthenticator.d.ts +46 -0
  19. package/src/lib/auth/ecdsaNodeAuthenticator.d.ts.map +1 -0
  20. package/src/lib/auth/ecdsaNodeAuthenticator.js +110 -0
  21. package/src/lib/auth/ecdsaNodeAuthenticator.js.map +1 -0
  22. package/src/lib/auth/index.d.ts +7 -0
  23. package/src/lib/auth/index.d.ts.map +1 -0
  24. package/src/lib/auth/index.js +13 -0
  25. package/src/lib/auth/index.js.map +1 -0
  26. package/src/lib/auth/poolAclBootstrap.d.ts +36 -0
  27. package/src/lib/auth/poolAclBootstrap.d.ts.map +1 -0
  28. package/src/lib/auth/poolAclBootstrap.js +64 -0
  29. package/src/lib/auth/poolAclBootstrap.js.map +1 -0
  30. package/src/lib/auth/poolAclStore.d.ts +77 -0
  31. package/src/lib/auth/poolAclStore.d.ts.map +1 -0
  32. package/src/lib/auth/poolAclStore.js +189 -0
  33. package/src/lib/auth/poolAclStore.js.map +1 -0
  34. package/src/lib/auth/poolAclUpdater.d.ts +79 -0
  35. package/src/lib/auth/poolAclUpdater.d.ts.map +1 -0
  36. package/src/lib/auth/poolAclUpdater.js +144 -0
  37. package/src/lib/auth/poolAclUpdater.js.map +1 -0
  38. package/src/lib/availability/availabilityService.d.ts +2 -2
  39. package/src/lib/availability/availabilityService.d.ts.map +1 -1
  40. package/src/lib/availability/availabilityService.js +12 -5
  41. package/src/lib/availability/availabilityService.js.map +1 -1
  42. package/src/lib/availability/blockRegistry.d.ts +45 -3
  43. package/src/lib/availability/blockRegistry.d.ts.map +1 -1
  44. package/src/lib/availability/blockRegistry.js +123 -5
  45. package/src/lib/availability/blockRegistry.js.map +1 -1
  46. package/src/lib/availability/discoveryProtocol.d.ts +30 -1
  47. package/src/lib/availability/discoveryProtocol.d.ts.map +1 -1
  48. package/src/lib/availability/discoveryProtocol.js +76 -0
  49. package/src/lib/availability/discoveryProtocol.js.map +1 -1
  50. package/src/lib/availability/gossipService.d.ts +45 -6
  51. package/src/lib/availability/gossipService.d.ts.map +1 -1
  52. package/src/lib/availability/gossipService.js +177 -5
  53. package/src/lib/availability/gossipService.js.map +1 -1
  54. package/src/lib/availability/reconciliationService.d.ts +88 -1
  55. package/src/lib/availability/reconciliationService.d.ts.map +1 -1
  56. package/src/lib/availability/reconciliationService.js +246 -48
  57. package/src/lib/availability/reconciliationService.js.map +1 -1
  58. package/src/lib/blockFetch/blockFetcher.d.ts +100 -0
  59. package/src/lib/blockFetch/blockFetcher.d.ts.map +1 -0
  60. package/src/lib/blockFetch/blockFetcher.js +279 -0
  61. package/src/lib/blockFetch/blockFetcher.js.map +1 -0
  62. package/src/lib/blockFetch/fetchQueue.d.ts +88 -0
  63. package/src/lib/blockFetch/fetchQueue.d.ts.map +1 -0
  64. package/src/lib/blockFetch/fetchQueue.js +204 -0
  65. package/src/lib/blockFetch/fetchQueue.js.map +1 -0
  66. package/src/lib/blockFetch/httpBlockFetchTransport.d.ts +65 -0
  67. package/src/lib/blockFetch/httpBlockFetchTransport.d.ts.map +1 -0
  68. package/src/lib/blockFetch/httpBlockFetchTransport.js +104 -0
  69. package/src/lib/blockFetch/httpBlockFetchTransport.js.map +1 -0
  70. package/src/lib/blockFetch/index.d.ts +10 -0
  71. package/src/lib/blockFetch/index.d.ts.map +1 -0
  72. package/src/lib/blockFetch/index.js +13 -0
  73. package/src/lib/blockFetch/index.js.map +1 -0
  74. package/src/lib/controllers/api/brightpass.d.ts +72 -0
  75. package/src/lib/controllers/api/brightpass.d.ts.map +1 -0
  76. package/src/lib/controllers/api/brightpass.js +577 -0
  77. package/src/lib/controllers/api/brightpass.js.map +1 -0
  78. package/src/lib/controllers/api/channels.d.ts +122 -0
  79. package/src/lib/controllers/api/channels.d.ts.map +1 -0
  80. package/src/lib/controllers/api/channels.js +701 -0
  81. package/src/lib/controllers/api/channels.js.map +1 -0
  82. package/src/lib/controllers/api/conversations.d.ts +89 -0
  83. package/src/lib/controllers/api/conversations.d.ts.map +1 -0
  84. package/src/lib/controllers/api/conversations.js +259 -0
  85. package/src/lib/controllers/api/conversations.js.map +1 -0
  86. package/src/lib/controllers/api/emails.d.ts +122 -0
  87. package/src/lib/controllers/api/emails.d.ts.map +1 -0
  88. package/src/lib/controllers/api/emails.js +494 -0
  89. package/src/lib/controllers/api/emails.js.map +1 -0
  90. package/src/lib/controllers/api/explodingMessages.d.ts +79 -0
  91. package/src/lib/controllers/api/explodingMessages.d.ts.map +1 -0
  92. package/src/lib/controllers/api/explodingMessages.js +378 -0
  93. package/src/lib/controllers/api/explodingMessages.js.map +1 -0
  94. package/src/lib/controllers/api/groups.d.ts +94 -0
  95. package/src/lib/controllers/api/groups.d.ts.map +1 -0
  96. package/src/lib/controllers/api/groups.js +484 -0
  97. package/src/lib/controllers/api/groups.js.map +1 -0
  98. package/src/lib/controllers/api/index.d.ts +6 -0
  99. package/src/lib/controllers/api/index.d.ts.map +1 -1
  100. package/src/lib/controllers/api/index.js +6 -0
  101. package/src/lib/controllers/api/index.js.map +1 -1
  102. package/src/lib/controllers/api/messages.d.ts.map +1 -1
  103. package/src/lib/controllers/api/messages.js +2 -1
  104. package/src/lib/controllers/api/messages.js.map +1 -1
  105. package/src/lib/controllers/api/sync.d.ts +38 -2
  106. package/src/lib/controllers/api/sync.d.ts.map +1 -1
  107. package/src/lib/controllers/api/sync.js +89 -0
  108. package/src/lib/controllers/api/sync.js.map +1 -1
  109. package/src/lib/controllers/crypto/gitController.d.ts +70 -0
  110. package/src/lib/controllers/crypto/gitController.d.ts.map +1 -0
  111. package/src/lib/controllers/crypto/gitController.js +306 -0
  112. package/src/lib/controllers/crypto/gitController.js.map +1 -0
  113. package/src/lib/controllers/crypto/index.d.ts +3 -0
  114. package/src/lib/controllers/crypto/index.d.ts.map +1 -0
  115. package/src/lib/controllers/crypto/index.js +6 -0
  116. package/src/lib/controllers/crypto/index.js.map +1 -0
  117. package/src/lib/controllers/crypto/walletController.d.ts +64 -0
  118. package/src/lib/controllers/crypto/walletController.d.ts.map +1 -0
  119. package/src/lib/controllers/crypto/walletController.js +260 -0
  120. package/src/lib/controllers/crypto/walletController.js.map +1 -0
  121. package/src/lib/controllers/identity/deviceController.d.ts +96 -0
  122. package/src/lib/controllers/identity/deviceController.d.ts.map +1 -0
  123. package/src/lib/controllers/identity/deviceController.js +355 -0
  124. package/src/lib/controllers/identity/deviceController.js.map +1 -0
  125. package/src/lib/controllers/identity/directoryController.d.ts +75 -0
  126. package/src/lib/controllers/identity/directoryController.d.ts.map +1 -0
  127. package/src/lib/controllers/identity/directoryController.js +288 -0
  128. package/src/lib/controllers/identity/directoryController.js.map +1 -0
  129. package/src/lib/controllers/identity/identityProofController.d.ts +94 -0
  130. package/src/lib/controllers/identity/identityProofController.d.ts.map +1 -0
  131. package/src/lib/controllers/identity/identityProofController.js +454 -0
  132. package/src/lib/controllers/identity/identityProofController.js.map +1 -0
  133. package/src/lib/controllers/identity/index.d.ts +4 -0
  134. package/src/lib/controllers/identity/index.d.ts.map +1 -0
  135. package/src/lib/controllers/identity/index.js +7 -0
  136. package/src/lib/controllers/identity/index.js.map +1 -0
  137. package/src/lib/controllers/index.d.ts +2 -0
  138. package/src/lib/controllers/index.d.ts.map +1 -1
  139. package/src/lib/controllers/index.js +2 -0
  140. package/src/lib/controllers/index.js.map +1 -1
  141. package/src/lib/encryption/encryptedMetadataService.d.ts +87 -0
  142. package/src/lib/encryption/encryptedMetadataService.d.ts.map +1 -0
  143. package/src/lib/encryption/encryptedMetadataService.js +224 -0
  144. package/src/lib/encryption/encryptedMetadataService.js.map +1 -0
  145. package/src/lib/encryption/encryptionAwareReplication.d.ts +76 -0
  146. package/src/lib/encryption/encryptionAwareReplication.d.ts.map +1 -0
  147. package/src/lib/encryption/encryptionAwareReplication.js +116 -0
  148. package/src/lib/encryption/encryptionAwareReplication.js.map +1 -0
  149. package/src/lib/encryption/errors.d.ts +49 -0
  150. package/src/lib/encryption/errors.d.ts.map +1 -0
  151. package/src/lib/encryption/errors.js +80 -0
  152. package/src/lib/encryption/errors.js.map +1 -0
  153. package/src/lib/encryption/index.d.ts +6 -0
  154. package/src/lib/encryption/index.d.ts.map +1 -0
  155. package/src/lib/encryption/index.js +9 -0
  156. package/src/lib/encryption/index.js.map +1 -0
  157. package/src/lib/encryption/poolEncryptionService.d.ts +94 -0
  158. package/src/lib/encryption/poolEncryptionService.d.ts.map +1 -0
  159. package/src/lib/encryption/poolEncryptionService.js +252 -0
  160. package/src/lib/encryption/poolEncryptionService.js.map +1 -0
  161. package/src/lib/encryption/poolKeyManager.d.ts +82 -0
  162. package/src/lib/encryption/poolKeyManager.d.ts.map +1 -0
  163. package/src/lib/encryption/poolKeyManager.js +156 -0
  164. package/src/lib/encryption/poolKeyManager.js.map +1 -0
  165. package/src/lib/environment.d.ts +3 -0
  166. package/src/lib/environment.d.ts.map +1 -1
  167. package/src/lib/environment.js +5 -0
  168. package/src/lib/environment.js.map +1 -1
  169. package/src/lib/interfaces/environment.d.ts +7 -1
  170. package/src/lib/interfaces/environment.d.ts.map +1 -1
  171. package/src/lib/interfaces/index.d.ts +0 -1
  172. package/src/lib/interfaces/index.d.ts.map +1 -1
  173. package/src/lib/interfaces/requests/getBlockDataRequest.d.ts +12 -0
  174. package/src/lib/interfaces/requests/getBlockDataRequest.d.ts.map +1 -0
  175. package/src/lib/interfaces/{blockStore.js → requests/getBlockDataRequest.js} +1 -1
  176. package/src/lib/interfaces/requests/getBlockDataRequest.js.map +1 -0
  177. package/src/lib/interfaces/requests/index.d.ts +1 -0
  178. package/src/lib/interfaces/requests/index.d.ts.map +1 -1
  179. package/src/lib/routers/api.d.ts +54 -1
  180. package/src/lib/routers/api.d.ts.map +1 -1
  181. package/src/lib/routers/api.js +77 -0
  182. package/src/lib/routers/api.js.map +1 -1
  183. package/src/lib/services/blockStore.d.ts +5 -2
  184. package/src/lib/services/blockStore.d.ts.map +1 -1
  185. package/src/lib/services/blockStore.js +4 -0
  186. package/src/lib/services/blockStore.js.map +1 -1
  187. package/src/lib/services/brightpass/auditLogger.d.ts +77 -0
  188. package/src/lib/services/brightpass/auditLogger.d.ts.map +1 -0
  189. package/src/lib/services/brightpass/auditLogger.js +184 -0
  190. package/src/lib/services/brightpass/auditLogger.js.map +1 -0
  191. package/src/lib/services/brightpass/vaultEncryption.d.ts +82 -0
  192. package/src/lib/services/brightpass/vaultEncryption.d.ts.map +1 -0
  193. package/src/lib/services/brightpass/vaultEncryption.js +144 -0
  194. package/src/lib/services/brightpass/vaultEncryption.js.map +1 -0
  195. package/src/lib/services/brightpass.d.ts +294 -0
  196. package/src/lib/services/brightpass.d.ts.map +1 -0
  197. package/src/lib/services/brightpass.js +1260 -0
  198. package/src/lib/services/brightpass.js.map +1 -0
  199. package/src/lib/services/eventNotificationSystem.d.ts +69 -3
  200. package/src/lib/services/eventNotificationSystem.d.ts.map +1 -1
  201. package/src/lib/services/eventNotificationSystem.js +200 -0
  202. package/src/lib/services/eventNotificationSystem.js.map +1 -1
  203. package/src/lib/services/expirationScheduler.d.ts +90 -0
  204. package/src/lib/services/expirationScheduler.d.ts.map +1 -0
  205. package/src/lib/services/expirationScheduler.js +131 -0
  206. package/src/lib/services/expirationScheduler.js.map +1 -0
  207. package/src/lib/services/fecUsageExample.d.ts +2 -2
  208. package/src/lib/services/index.d.ts +2 -0
  209. package/src/lib/services/index.d.ts.map +1 -1
  210. package/src/lib/services/index.js +2 -0
  211. package/src/lib/services/index.js.map +1 -1
  212. package/src/lib/services/paginationService.d.ts +18 -0
  213. package/src/lib/services/paginationService.d.ts.map +1 -0
  214. package/src/lib/services/paginationService.js +32 -0
  215. package/src/lib/services/paginationService.js.map +1 -0
  216. package/src/lib/services/presenceService.d.ts +76 -0
  217. package/src/lib/services/presenceService.d.ts.map +1 -0
  218. package/src/lib/services/presenceService.js +143 -0
  219. package/src/lib/services/presenceService.js.map +1 -0
  220. package/src/lib/services/wireConversationPromotion.d.ts +23 -0
  221. package/src/lib/services/wireConversationPromotion.d.ts.map +1 -0
  222. package/src/lib/services/wireConversationPromotion.js +26 -0
  223. package/src/lib/services/wireConversationPromotion.js.map +1 -0
  224. package/src/lib/stores/availabilityAwareBlockStore.d.ts +115 -10
  225. package/src/lib/stores/availabilityAwareBlockStore.d.ts.map +1 -1
  226. package/src/lib/stores/availabilityAwareBlockStore.js +267 -23
  227. package/src/lib/stores/availabilityAwareBlockStore.js.map +1 -1
  228. package/src/lib/stores/diskBlockAsyncStore.d.ts +81 -2
  229. package/src/lib/stores/diskBlockAsyncStore.d.ts.map +1 -1
  230. package/src/lib/stores/diskBlockAsyncStore.js +297 -10
  231. package/src/lib/stores/diskBlockAsyncStore.js.map +1 -1
  232. package/src/lib/utils/communicationValidation.d.ts +44 -0
  233. package/src/lib/utils/communicationValidation.d.ts.map +1 -0
  234. package/src/lib/utils/communicationValidation.js +291 -0
  235. package/src/lib/utils/communicationValidation.js.map +1 -0
  236. package/src/lib/utils/emailValidation.d.ts +19 -0
  237. package/src/lib/utils/emailValidation.d.ts.map +1 -0
  238. package/src/lib/utils/emailValidation.js +232 -0
  239. package/src/lib/utils/emailValidation.js.map +1 -0
  240. package/src/lib/interfaces/blockStore.d.ts +0 -7
  241. package/src/lib/interfaces/blockStore.d.ts.map +0 -1
  242. package/src/lib/interfaces/blockStore.js.map +0 -1
@@ -0,0 +1,1260 @@
1
+ "use strict";
2
+ /**
3
+ * BrightPassService — core vault operations for the BrightPass password manager.
4
+ *
5
+ * Manages vault lifecycle (create, open, list, delete), entry CRUD,
6
+ * search, sharing, emergency access, and import operations.
7
+ *
8
+ * Uses IBlockStore for persistent storage, VCBLService for VCBL operations,
9
+ * BlockService for encryption, and maintains a lightweight in-memory index
10
+ * for quick lookups.
11
+ *
12
+ * Requirements: 1.1–1.8, 2.1–2.9, 3.1–3.5, 4.1–4.4
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.BrightPassService = exports.EmergencyAccessError = exports.EntryNotFoundError = exports.VaultConflictError = exports.VaultAuthenticationError = exports.VaultNotFoundError = void 0;
16
+ const tslib_1 = require("tslib");
17
+ const brightchain_lib_1 = require("@brightchain/brightchain-lib");
18
+ const uuid_1 = require("uuid");
19
+ const secretsModule = tslib_1.__importStar(require("@digitaldefiance/secrets"));
20
+ const bcrypt = tslib_1.__importStar(require("bcrypt"));
21
+ const bip39 = tslib_1.__importStar(require("bip39"));
22
+ const crypto = tslib_1.__importStar(require("crypto"));
23
+ const auditLogger_1 = require("./brightpass/auditLogger");
24
+ const vaultEncryption_1 = require("./brightpass/vaultEncryption");
25
+ // Handle both ESM default export and CommonJS module.exports patterns
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ const secrets = secretsModule.default || secretsModule;
28
+ /**
29
+ * Errors specific to BrightPass operations
30
+ */
31
+ class VaultNotFoundError extends Error {
32
+ constructor(vaultId) {
33
+ super(`Vault not found: ${vaultId}`);
34
+ this.name = 'VaultNotFoundError';
35
+ }
36
+ }
37
+ exports.VaultNotFoundError = VaultNotFoundError;
38
+ class VaultAuthenticationError extends Error {
39
+ constructor() {
40
+ super('Vault authentication failed');
41
+ this.name = 'VaultAuthenticationError';
42
+ }
43
+ }
44
+ exports.VaultAuthenticationError = VaultAuthenticationError;
45
+ class VaultConflictError extends Error {
46
+ constructor(name) {
47
+ super(`Vault with name "${name}" already exists`);
48
+ this.name = 'VaultConflictError';
49
+ }
50
+ }
51
+ exports.VaultConflictError = VaultConflictError;
52
+ class EntryNotFoundError extends Error {
53
+ constructor(entryId) {
54
+ super(`Entry not found: ${entryId}`);
55
+ this.name = 'EntryNotFoundError';
56
+ }
57
+ }
58
+ exports.EntryNotFoundError = EntryNotFoundError;
59
+ class EmergencyAccessError extends Error {
60
+ constructor(message) {
61
+ super(message);
62
+ this.name = 'EmergencyAccessError';
63
+ }
64
+ }
65
+ exports.EmergencyAccessError = EmergencyAccessError;
66
+ class BrightPassService {
67
+ /**
68
+ * Create a new BrightPassService.
69
+ *
70
+ * @param blockStore - Block store for persistent storage (defaults to MemoryBlockStore)
71
+ * @param vcblService - VCBL service for vault operations
72
+ * @param blockService - Block service for encryption
73
+ * @param member - Member for block creation
74
+ *
75
+ * Requirements: 1.1, 1.5
76
+ */
77
+ constructor(blockStore, vcblService, blockService, member) {
78
+ /** vaultId → StoredVault for vault data management */
79
+ this.vaults = new Map();
80
+ /** vaultId → VaultIndexEntry for lightweight lookups */
81
+ this.vaultIndex = new Map();
82
+ /** memberId → Set<vaultId> for quick listing */
83
+ this.memberVaults = new Map();
84
+ // Default to MemoryBlockStore if not provided (for backward compatibility)
85
+ this.blockStore = blockStore ?? new brightchain_lib_1.MemoryBlockStore(brightchain_lib_1.BlockSize.Small);
86
+ // Store injected services
87
+ this.vcblService = vcblService;
88
+ this.blockService = blockService ?? new brightchain_lib_1.BlockService();
89
+ this.member = member;
90
+ }
91
+ /**
92
+ * Hash a master password using bcrypt for secure storage and comparison.
93
+ * Uses bcrypt with 12 rounds for strong protection against brute-force attacks.
94
+ *
95
+ * Security: bcrypt includes salt automatically and is designed to be slow,
96
+ * making brute-force attacks computationally expensive.
97
+ */
98
+ async hashMasterPasswordAsync(password) {
99
+ return bcrypt.hash(password, BrightPassService.BCRYPT_ROUNDS);
100
+ }
101
+ /**
102
+ * Verify a master password against a stored bcrypt hash.
103
+ * Uses constant-time comparison internally via bcrypt.compare().
104
+ */
105
+ async verifyMasterPassword(password, storedHash) {
106
+ return bcrypt.compare(password, storedHash);
107
+ }
108
+ /**
109
+ * Generate a new BIP39 mnemonic and seed for a vault.
110
+ * Each vault has its own independent mnemonic that can be cycled/regenerated.
111
+ *
112
+ * Security: Uses 256 bits of entropy for a 24-word mnemonic.
113
+ * The mnemonic should be stored encrypted and can be backed up separately.
114
+ *
115
+ * @returns Object containing the mnemonic (24 words) and derived seed (64 bytes)
116
+ */
117
+ generateVaultBip39() {
118
+ // Generate 256 bits of entropy for a 24-word mnemonic
119
+ const mnemonic = bip39.generateMnemonic(256);
120
+ // Derive 64-byte seed from mnemonic (no passphrase - master password used in key derivation)
121
+ const seedBuffer = bip39.mnemonicToSeedSync(mnemonic);
122
+ return {
123
+ mnemonic,
124
+ seed: new Uint8Array(seedBuffer),
125
+ };
126
+ }
127
+ /**
128
+ * Regenerate a vault's BIP39 seed (key rotation).
129
+ * This creates a new mnemonic and re-encrypts all entries with the new key.
130
+ *
131
+ * Security: This is a critical operation that should be used when:
132
+ * - A share has been revoked and the vault needs re-keying
133
+ * - The vault owner suspects key compromise
134
+ * - Periodic key rotation policy requires it
135
+ *
136
+ * @param vaultId - The vault to regenerate
137
+ * @param masterPassword - Current master password for verification
138
+ * @returns The new mnemonic (should be backed up by the user)
139
+ */
140
+ async regenerateVaultSeed(vaultId, masterPassword) {
141
+ const vault = this.getVaultOrThrow(vaultId);
142
+ // Verify master password
143
+ const passwordValid = await this.verifyMasterPassword(masterPassword, vault.masterPasswordHash);
144
+ if (!passwordValid) {
145
+ throw new VaultAuthenticationError();
146
+ }
147
+ // Generate new BIP39 mnemonic and seed
148
+ const { mnemonic: newMnemonic, seed: newSeed } = this.generateVaultBip39();
149
+ // Derive new vault key
150
+ const newVaultKey = brightchain_lib_1.VaultKeyDerivation.deriveVaultKey(newSeed, masterPassword, vaultId);
151
+ // Re-encrypt all entries with new key
152
+ const oldVaultKey = vault.vaultKey;
153
+ for (const [entryId, encryptedEntry] of vault.entries) {
154
+ const decrypted = vaultEncryption_1.VaultEncryption.decryptString(oldVaultKey, encryptedEntry);
155
+ const reEncrypted = vaultEncryption_1.VaultEncryption.encryptString(newVaultKey, decrypted);
156
+ vault.entries.set(entryId, reEncrypted);
157
+ }
158
+ // Update vault with new seed and key
159
+ vault.vaultMnemonic = newMnemonic;
160
+ vault.vaultSeed = newSeed;
161
+ vault.vaultKey = newVaultKey;
162
+ vault.metadata.updatedAt = new Date();
163
+ // Update index
164
+ const indexEntry = this.vaultIndex.get(vaultId);
165
+ if (indexEntry) {
166
+ indexEntry.vaultMnemonic = newMnemonic;
167
+ indexEntry.vaultSeed = newSeed;
168
+ indexEntry.vaultKey = newVaultKey;
169
+ indexEntry.updatedAt = vault.metadata.updatedAt;
170
+ }
171
+ // Invalidate any existing emergency shares (they used the old key)
172
+ if (vault.emergencyConfig) {
173
+ vault.emergencyConfig = undefined;
174
+ vault.emergencyShares = undefined;
175
+ }
176
+ await vault.auditLogger.log({
177
+ id: (0, uuid_1.v4)(),
178
+ vaultId,
179
+ memberId: vault.metadata.ownerId,
180
+ action: brightchain_lib_1.AuditAction.VAULT_UPDATED,
181
+ metadata: { action: 'seed_regenerated' },
182
+ });
183
+ return newMnemonic;
184
+ }
185
+ /**
186
+ * Extract an EntryPropertyRecord from a VaultEntry.
187
+ */
188
+ entryToPropertyRecord(entry) {
189
+ return {
190
+ entryType: entry.type,
191
+ title: entry.title,
192
+ tags: entry.tags ?? [],
193
+ favorite: entry.favorite,
194
+ createdAt: entry.createdAt,
195
+ updatedAt: entry.updatedAt,
196
+ siteUrl: entry.type === 'login' ? entry.siteUrl : '',
197
+ };
198
+ }
199
+ /**
200
+ * Check if block store operations are safe to perform.
201
+ * Block store operations require the global service provider to be initialized.
202
+ * Returns false if the service provider is not available.
203
+ */
204
+ isBlockStoreOperationsSafe() {
205
+ try {
206
+ // Try to access the global service provider
207
+ // If it throws, block store operations are not safe
208
+ (0, brightchain_lib_1.getGlobalServiceProvider)();
209
+ return true;
210
+ }
211
+ catch {
212
+ return false;
213
+ }
214
+ }
215
+ /**
216
+ * Get a vault or throw VaultNotFoundError.
217
+ */
218
+ getVaultOrThrow(vaultId) {
219
+ const vault = this.vaults.get(vaultId);
220
+ if (!vault) {
221
+ throw new VaultNotFoundError(vaultId);
222
+ }
223
+ return vault;
224
+ }
225
+ /**
226
+ * Update the VCBL in the block store after entry changes.
227
+ * Creates a new VCBL with updated property records and entry addresses,
228
+ * encrypts it, and stores it in the block store.
229
+ * Requirements: 2.2, 2.3, 2.4, 3.2
230
+ */
231
+ async updateVcblInBlockStore(vaultId, vault) {
232
+ if (!this.vcblService || !this.member) {
233
+ return; // VCBLService not available, skip block store update
234
+ }
235
+ if (!this.isBlockStoreOperationsSafe()) {
236
+ return; // Block store operations not safe, skip update
237
+ }
238
+ const indexEntry = this.vaultIndex.get(vaultId);
239
+ if (!indexEntry) {
240
+ return;
241
+ }
242
+ // Build address list from block IDs (checksums)
243
+ const addressList = new Uint8Array(vault.blockIds.length * brightchain_lib_1.CONSTANTS['CHECKSUM'].SHA3_BUFFER_LENGTH);
244
+ for (let i = 0; i < vault.blockIds.length; i++) {
245
+ const checksum = brightchain_lib_1.Checksum.fromHex(vault.blockIds[i]);
246
+ addressList.set(checksum.toUint8Array(), i * brightchain_lib_1.CONSTANTS['CHECKSUM'].SHA3_BUFFER_LENGTH);
247
+ }
248
+ // Create new VCBL header with updated property records and addresses
249
+ const { headerData } = this.vcblService.makeVcblHeader(this.member, vault.metadata.name, [], // shared members - would need to convert string IDs to TID
250
+ vault.propertyRecords, addressList, this.blockStore.blockSize, brightchain_lib_1.BlockEncryptionType.None);
251
+ // Encrypt the VCBL header using the vault key
252
+ const encryptedVcbl = vaultEncryption_1.VaultEncryption.encrypt(vault.vaultKey, headerData);
253
+ // Calculate new checksum
254
+ const checksumService = new brightchain_lib_1.ChecksumService();
255
+ const newVcblChecksum = checksumService.calculateChecksum(encryptedVcbl);
256
+ const newVcblBlockId = newVcblChecksum.toHex();
257
+ // Remove old VCBL from block store if it exists
258
+ if (indexEntry.vcblBlockId) {
259
+ const hasOldVcbl = await this.blockStore.has(indexEntry.vcblBlockId);
260
+ if (hasOldVcbl) {
261
+ await this.blockStore.delete(indexEntry.vcblBlockId);
262
+ }
263
+ }
264
+ // Store new VCBL in block store
265
+ await this.blockStore.put(newVcblBlockId, encryptedVcbl);
266
+ // Update index entry with new VCBL checksum
267
+ indexEntry.vcblChecksum = newVcblChecksum;
268
+ indexEntry.vcblBlockId = newVcblBlockId;
269
+ vault.metadata.vcblBlockId = newVcblBlockId;
270
+ }
271
+ // ─── Vault CRUD ───────────────────────────────────────────────
272
+ /**
273
+ * Create a new vault.
274
+ * Generates a vault-specific BIP39 mnemonic and derives the vault key from it.
275
+ * Uses bcrypt for secure password hashing.
276
+ *
277
+ * Security: Each vault has its own independent BIP39 seed that can be
278
+ * regenerated/cycled without affecting other vaults or the member's identity.
279
+ *
280
+ * Requirements: 1.1, 3.1, 3.2
281
+ */
282
+ async createVault(memberId, name, masterPassword) {
283
+ // Check for duplicate vault name for this member
284
+ const memberVaultIds = this.memberVaults.get(memberId);
285
+ if (memberVaultIds) {
286
+ for (const vid of memberVaultIds) {
287
+ const existing = this.vaults.get(vid);
288
+ if (existing && existing.metadata.name === name) {
289
+ throw new VaultConflictError(name);
290
+ }
291
+ }
292
+ }
293
+ const vaultId = (0, uuid_1.v4)();
294
+ // Generate vault-specific BIP39 mnemonic and seed (independent of member)
295
+ const { mnemonic: vaultMnemonic, seed: vaultSeed } = this.generateVaultBip39();
296
+ // Derive vault key from the vault's own BIP39 seed + master password
297
+ const vaultKey = brightchain_lib_1.VaultKeyDerivation.deriveVaultKey(vaultSeed, masterPassword, vaultId);
298
+ // Hash password using bcrypt (async, secure)
299
+ const masterPasswordHash = await this.hashMasterPasswordAsync(masterPassword);
300
+ const now = new Date();
301
+ // Create VCBL using VCBLService if available (Req 3.1, 3.2)
302
+ let vcblChecksum = null;
303
+ let vcblBlockId = (0, uuid_1.v4)(); // fallback simulated block ID
304
+ if (this.vcblService && this.member) {
305
+ // Create empty VCBL header with no entries
306
+ const emptyPropertyRecords = [];
307
+ const emptyAddressList = new Uint8Array(0);
308
+ const { headerData } = this.vcblService.makeVcblHeader(this.member, name, [], // no shared members initially
309
+ emptyPropertyRecords, emptyAddressList, this.blockStore.blockSize, brightchain_lib_1.BlockEncryptionType.None);
310
+ // Encrypt the VCBL header using the vault key
311
+ const encryptedVcbl = vaultEncryption_1.VaultEncryption.encrypt(vaultKey, headerData);
312
+ // Calculate checksum of the encrypted VCBL
313
+ const checksumService = new brightchain_lib_1.ChecksumService();
314
+ vcblChecksum = checksumService.calculateChecksum(encryptedVcbl);
315
+ // Store the encrypted VCBL in the block store
316
+ await this.blockStore.put(vcblChecksum.toHex(), encryptedVcbl);
317
+ vcblBlockId = vcblChecksum.toHex();
318
+ }
319
+ const metadata = {
320
+ id: vaultId,
321
+ name,
322
+ ownerId: memberId,
323
+ createdAt: now,
324
+ updatedAt: now,
325
+ entryCount: 0,
326
+ sharedWith: [],
327
+ vcblBlockId,
328
+ };
329
+ const auditLogger = new auditLogger_1.AuditLogger();
330
+ const stored = {
331
+ metadata,
332
+ vaultKey,
333
+ masterPasswordHash,
334
+ vaultMnemonic,
335
+ vaultSeed,
336
+ propertyRecords: [],
337
+ blockIds: [],
338
+ entries: new Map(),
339
+ auditLogger,
340
+ quorumThreshold: 0,
341
+ quorumApprovals: new Set(),
342
+ };
343
+ this.vaults.set(vaultId, stored);
344
+ // Populate lightweight vault index for quick lookups
345
+ const indexEntry = {
346
+ vcblChecksum, // Now set to actual checksum when VCBLService is available
347
+ ownerId: memberId,
348
+ name,
349
+ vaultKey,
350
+ masterPasswordHash,
351
+ vaultMnemonic,
352
+ vaultSeed,
353
+ sharedWith: [],
354
+ createdAt: now,
355
+ updatedAt: now,
356
+ entryCount: 0,
357
+ vcblBlockId,
358
+ };
359
+ this.vaultIndex.set(vaultId, indexEntry);
360
+ // Track member → vault mapping
361
+ if (!this.memberVaults.has(memberId)) {
362
+ this.memberVaults.set(memberId, new Set());
363
+ }
364
+ this.memberVaults.get(memberId).add(vaultId);
365
+ await auditLogger.log({
366
+ id: (0, uuid_1.v4)(),
367
+ vaultId,
368
+ memberId,
369
+ action: brightchain_lib_1.AuditAction.VAULT_CREATED,
370
+ });
371
+ return { ...metadata };
372
+ }
373
+ /**
374
+ * Open an existing vault with the correct master password.
375
+ * Returns vault metadata and property records (no entry decryption).
376
+ * When VCBLService is available, retrieves and parses VCBL from block store.
377
+ * Requirements: 1.2, 1.3, 3.3, 3.4
378
+ */
379
+ async openVault(memberId, vaultId, masterPassword) {
380
+ const vault = this.vaults.get(vaultId);
381
+ if (!vault) {
382
+ // Don't reveal whether vault exists — return generic auth error
383
+ throw new VaultAuthenticationError();
384
+ }
385
+ // Verify ownership or shared access
386
+ if (vault.metadata.ownerId !== memberId &&
387
+ !vault.metadata.sharedWith.includes(memberId)) {
388
+ throw new VaultAuthenticationError();
389
+ }
390
+ // Verify master password using bcrypt (constant-time comparison)
391
+ const passwordValid = await this.verifyMasterPassword(masterPassword, vault.masterPasswordHash);
392
+ if (!passwordValid) {
393
+ throw new VaultAuthenticationError();
394
+ }
395
+ // Enforce quorum governance if configured
396
+ if (vault.quorumThreshold > 0 && !this.isQuorumMet(vaultId)) {
397
+ throw new VaultAuthenticationError();
398
+ }
399
+ await vault.auditLogger.log({
400
+ id: (0, uuid_1.v4)(),
401
+ vaultId,
402
+ memberId,
403
+ action: brightchain_lib_1.AuditAction.VAULT_OPENED,
404
+ });
405
+ // Try to retrieve and parse VCBL from block store (Req 3.3, 3.4)
406
+ const indexEntry = this.vaultIndex.get(vaultId);
407
+ if (this.vcblService && this.member && indexEntry?.vcblBlockId) {
408
+ const hasVcbl = await this.blockStore.has(indexEntry.vcblBlockId);
409
+ if (hasVcbl) {
410
+ // Retrieve encrypted VCBL from block store
411
+ const blockHandle = this.blockStore.get(indexEntry.vcblBlockId);
412
+ const encryptedVcbl = blockHandle.fullData;
413
+ // Decrypt VCBL using vault key
414
+ const decryptedVcbl = vaultEncryption_1.VaultEncryption.decrypt(vault.vaultKey, new Uint8Array(encryptedVcbl));
415
+ // Parse VCBL using VCBLBlock to extract property records and entry addresses
416
+ const vcblBlock = new brightchain_lib_1.VCBLBlock(decryptedVcbl, this.member, this.blockStore.blockSize, undefined, this.vcblService);
417
+ // Extract property records from VCBL (Req 3.3)
418
+ const propertyRecords = vcblBlock.propertyRecords;
419
+ // Extract entry addresses from VCBL (Req 3.4)
420
+ const entryAddresses = vcblBlock.addresses;
421
+ // Update in-memory vault with parsed data for consistency
422
+ vault.propertyRecords = [...propertyRecords];
423
+ vault.blockIds = entryAddresses.map((addr) => addr.toHex());
424
+ return {
425
+ metadata: { ...vault.metadata },
426
+ propertyRecords: [...propertyRecords],
427
+ };
428
+ }
429
+ }
430
+ // Fallback: return in-memory data when VCBLService is not available
431
+ return {
432
+ metadata: { ...vault.metadata },
433
+ propertyRecords: [...vault.propertyRecords],
434
+ };
435
+ }
436
+ /**
437
+ * List all vaults owned by or shared with a member.
438
+ * Returns metadata only — no decryption needed.
439
+ * Requirements: 1.4
440
+ */
441
+ async listVaults(memberId) {
442
+ const result = [];
443
+ for (const vault of this.vaults.values()) {
444
+ if (vault.metadata.ownerId === memberId ||
445
+ vault.metadata.sharedWith.includes(memberId)) {
446
+ result.push({ ...vault.metadata });
447
+ }
448
+ }
449
+ return result;
450
+ }
451
+ /**
452
+ * Delete a vault and all its entries.
453
+ * Requirements: 1.5
454
+ */
455
+ async deleteVault(memberId, vaultId, masterPassword) {
456
+ const vault = this.vaults.get(vaultId);
457
+ if (!vault) {
458
+ throw new VaultNotFoundError(vaultId);
459
+ }
460
+ if (vault.metadata.ownerId !== memberId) {
461
+ throw new VaultAuthenticationError();
462
+ }
463
+ // Verify master password using bcrypt (constant-time comparison)
464
+ const passwordValid = await this.verifyMasterPassword(masterPassword, vault.masterPasswordHash);
465
+ if (!passwordValid) {
466
+ throw new VaultAuthenticationError();
467
+ }
468
+ await vault.auditLogger.log({
469
+ id: (0, uuid_1.v4)(),
470
+ vaultId,
471
+ memberId,
472
+ action: brightchain_lib_1.AuditAction.VAULT_DELETED,
473
+ });
474
+ // Remove vault and all associated data
475
+ this.vaults.delete(vaultId);
476
+ this.vaultIndex.delete(vaultId);
477
+ const memberVaultIds = this.memberVaults.get(memberId);
478
+ if (memberVaultIds) {
479
+ memberVaultIds.delete(vaultId);
480
+ }
481
+ }
482
+ // ─── Entry CRUD ─────────────────────────────────────────────
483
+ /**
484
+ * Add a new entry to a vault.
485
+ * Encrypts the entry using AES-256-GCM, stores in block store (when available),
486
+ * and appends block checksum + property record to VCBL parallel arrays.
487
+ * Requirements: 2.1, 2.2, 3.2
488
+ */
489
+ async addEntry(vaultId, entry) {
490
+ const vault = this.getVaultOrThrow(vaultId);
491
+ // Assign ID and timestamps if not present
492
+ const now = new Date();
493
+ const fullEntry = {
494
+ ...entry,
495
+ id: entry.id || (0, uuid_1.v4)(),
496
+ createdAt: entry.createdAt || now,
497
+ updatedAt: entry.updatedAt || now,
498
+ };
499
+ // Serialize and encrypt entry using AES-256-GCM (Req 2.2, 3.2)
500
+ const serialized = brightchain_lib_1.VaultSerializer.serializeEntry(fullEntry);
501
+ const encrypted = vaultEncryption_1.VaultEncryption.encryptString(vault.vaultKey, serialized);
502
+ // Calculate checksum for the entry block ID
503
+ const entryData = new Uint8Array(Buffer.from(encrypted, 'base64'));
504
+ const checksumService = new brightchain_lib_1.ChecksumService();
505
+ const entryChecksum = checksumService.calculateChecksum(entryData);
506
+ const entryBlockId = entryChecksum.toHex();
507
+ // Store encrypted entry in block store if operations are safe (Req 2.2)
508
+ if (this.isBlockStoreOperationsSafe()) {
509
+ await this.blockStore.put(entryBlockId, entryData);
510
+ }
511
+ // Also keep in-memory for backward compatibility
512
+ vault.entries.set(fullEntry.id, encrypted);
513
+ vault.propertyRecords.push(this.entryToPropertyRecord(fullEntry));
514
+ vault.blockIds.push(entryBlockId);
515
+ vault.metadata.entryCount = vault.propertyRecords.length;
516
+ vault.metadata.updatedAt = now;
517
+ // Update vault index entry count
518
+ const indexEntry = this.vaultIndex.get(vaultId);
519
+ if (indexEntry) {
520
+ indexEntry.entryCount = vault.metadata.entryCount;
521
+ indexEntry.updatedAt = now;
522
+ }
523
+ // Update VCBL in block store if VCBLService is available (Req 2.2)
524
+ await this.updateVcblInBlockStore(vaultId, vault);
525
+ await vault.auditLogger.log({
526
+ id: (0, uuid_1.v4)(),
527
+ vaultId,
528
+ memberId: vault.metadata.ownerId,
529
+ action: brightchain_lib_1.AuditAction.ENTRY_CREATED,
530
+ metadata: { entryId: fullEntry.id, entryType: fullEntry.type },
531
+ });
532
+ return fullEntry;
533
+ }
534
+ /**
535
+ * Get a single entry by ID from a vault.
536
+ * Retrieves from block store and decrypts the entry using AES-256-GCM.
537
+ * Requirements: 2.2, 2.3, 3.2
538
+ */
539
+ async getEntry(vaultId, entryId) {
540
+ const vault = this.getVaultOrThrow(vaultId);
541
+ // First try to get from in-memory cache (backward compatibility)
542
+ let encrypted = vault.entries.get(entryId);
543
+ // If not in memory, try to find in block store using blockIds
544
+ if (!encrypted) {
545
+ // Find the block ID for this entry by looking up the entry ID mapping
546
+ // The blockIds array contains checksums, we need to find the right one
547
+ // For now, fall back to in-memory only since we store by checksum not entry ID
548
+ throw new EntryNotFoundError(entryId);
549
+ }
550
+ await vault.auditLogger.log({
551
+ id: (0, uuid_1.v4)(),
552
+ vaultId,
553
+ memberId: vault.metadata.ownerId,
554
+ action: brightchain_lib_1.AuditAction.ENTRY_READ,
555
+ metadata: { entryId },
556
+ });
557
+ // Decrypt and deserialize entry (Req 2.3, 3.2)
558
+ const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
559
+ return brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
560
+ }
561
+ /**
562
+ * Update an existing entry in a vault.
563
+ * Decrypts, updates, re-encrypts using AES-256-GCM, stores in block store (when available),
564
+ * and updates the VCBL property record.
565
+ * Requirements: 2.3, 2.4, 3.2
566
+ */
567
+ async updateEntry(vaultId, entryId, updates) {
568
+ const vault = this.getVaultOrThrow(vaultId);
569
+ const encrypted = vault.entries.get(entryId);
570
+ if (!encrypted) {
571
+ throw new EntryNotFoundError(entryId);
572
+ }
573
+ // Decrypt existing entry (Req 2.3, 3.2)
574
+ const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
575
+ const existing = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
576
+ const now = new Date();
577
+ const updated = {
578
+ ...existing,
579
+ ...updates,
580
+ id: entryId, // preserve original ID
581
+ type: existing.type, // preserve original type
582
+ createdAt: existing.createdAt, // preserve creation date
583
+ updatedAt: now,
584
+ };
585
+ // Re-serialize and re-encrypt (Req 2.4, 3.2)
586
+ const newSerialized = brightchain_lib_1.VaultSerializer.serializeEntry(updated);
587
+ const newEncrypted = vaultEncryption_1.VaultEncryption.encryptString(vault.vaultKey, newSerialized);
588
+ // Find the index of this entry in the entries map
589
+ const entryKeys = Array.from(vault.entries.keys());
590
+ const index = entryKeys.indexOf(entryId);
591
+ // Calculate new checksum and store in block store if safe
592
+ const entryData = new Uint8Array(Buffer.from(newEncrypted, 'base64'));
593
+ const checksumService = new brightchain_lib_1.ChecksumService();
594
+ const newChecksum = checksumService.calculateChecksum(entryData);
595
+ const newBlockId = newChecksum.toHex();
596
+ if (this.isBlockStoreOperationsSafe()) {
597
+ // Remove old block from store if it exists
598
+ if (index !== -1 &&
599
+ index < vault.blockIds.length &&
600
+ vault.blockIds[index]) {
601
+ const oldBlockId = vault.blockIds[index];
602
+ const hasOldBlock = await this.blockStore.has(oldBlockId);
603
+ if (hasOldBlock) {
604
+ await this.blockStore.delete(oldBlockId);
605
+ }
606
+ }
607
+ // Store new encrypted entry in block store
608
+ await this.blockStore.put(newBlockId, entryData);
609
+ }
610
+ // Update in-memory cache
611
+ vault.entries.set(entryId, newEncrypted);
612
+ // Update the parallel property record and block ID at the matching index
613
+ if (index !== -1 && index < vault.propertyRecords.length) {
614
+ vault.propertyRecords[index] = this.entryToPropertyRecord(updated);
615
+ vault.blockIds[index] = newBlockId;
616
+ }
617
+ vault.metadata.updatedAt = now;
618
+ // Update VCBL in block store (Req 2.4)
619
+ await this.updateVcblInBlockStore(vaultId, vault);
620
+ await vault.auditLogger.log({
621
+ id: (0, uuid_1.v4)(),
622
+ vaultId,
623
+ memberId: vault.metadata.ownerId,
624
+ action: brightchain_lib_1.AuditAction.ENTRY_UPDATED,
625
+ metadata: { entryId },
626
+ });
627
+ return updated;
628
+ }
629
+ /**
630
+ * Delete an entry from a vault.
631
+ * Removes the entry block from block store (when available), and removes from VCBL at the matching index.
632
+ * Requirements: 2.4
633
+ */
634
+ async deleteEntry(vaultId, entryId) {
635
+ const vault = this.getVaultOrThrow(vaultId);
636
+ if (!vault.entries.has(entryId)) {
637
+ throw new EntryNotFoundError(entryId);
638
+ }
639
+ // Find the index of this entry in the entries map
640
+ const entryKeys = Array.from(vault.entries.keys());
641
+ const index = entryKeys.indexOf(entryId);
642
+ // Remove from block store if we have a valid block ID and operations are safe
643
+ if (index !== -1 && index < vault.blockIds.length) {
644
+ const blockId = vault.blockIds[index];
645
+ if (this.isBlockStoreOperationsSafe()) {
646
+ const hasBlock = await this.blockStore.has(blockId);
647
+ if (hasBlock) {
648
+ await this.blockStore.delete(blockId);
649
+ }
650
+ }
651
+ // Remove from parallel arrays at matching index
652
+ vault.propertyRecords.splice(index, 1);
653
+ vault.blockIds.splice(index, 1);
654
+ }
655
+ vault.entries.delete(entryId);
656
+ vault.metadata.entryCount = vault.propertyRecords.length;
657
+ vault.metadata.updatedAt = new Date();
658
+ // Update vault index entry count
659
+ const indexEntry = this.vaultIndex.get(vaultId);
660
+ if (indexEntry) {
661
+ indexEntry.entryCount = vault.metadata.entryCount;
662
+ indexEntry.updatedAt = vault.metadata.updatedAt;
663
+ }
664
+ // Update VCBL in block store (Req 2.4)
665
+ await this.updateVcblInBlockStore(vaultId, vault);
666
+ await vault.auditLogger.log({
667
+ id: (0, uuid_1.v4)(),
668
+ vaultId,
669
+ memberId: vault.metadata.ownerId,
670
+ action: brightchain_lib_1.AuditAction.ENTRY_DELETED,
671
+ metadata: { entryId },
672
+ });
673
+ }
674
+ /**
675
+ * Search entries in a vault by filtering VCBL property records.
676
+ * Requirements: 2.9
677
+ */
678
+ async searchEntries(vaultId, query) {
679
+ const vault = this.getVaultOrThrow(vaultId);
680
+ return vault.propertyRecords.filter((record) => {
681
+ // Text search: match against title and siteUrl
682
+ if (query.text) {
683
+ const lowerText = query.text.toLowerCase();
684
+ const titleMatch = record.title.toLowerCase().includes(lowerText);
685
+ const urlMatch = record.siteUrl.toLowerCase().includes(lowerText);
686
+ if (!titleMatch && !urlMatch) {
687
+ return false;
688
+ }
689
+ }
690
+ // Type filter
691
+ if (query.type && record.entryType !== query.type) {
692
+ return false;
693
+ }
694
+ // Tag filter: any match
695
+ if (query.tags && query.tags.length > 0) {
696
+ const hasMatchingTag = query.tags.some((tag) => record.tags.includes(tag));
697
+ if (!hasMatchingTag) {
698
+ return false;
699
+ }
700
+ }
701
+ // Favorite filter
702
+ if (query.favorite !== undefined && record.favorite !== query.favorite) {
703
+ return false;
704
+ }
705
+ return true;
706
+ });
707
+ }
708
+ // ─── Sharing ────────────────────────────────────────────────
709
+ /**
710
+ * Share a vault with one or more recipients.
711
+ * Updates the VCBL header shared list and tracks member→vault mapping.
712
+ * Requirements: 4.1, 4.2
713
+ */
714
+ async shareVault(vaultId, recipientMemberIds) {
715
+ const vault = this.getVaultOrThrow(vaultId);
716
+ const indexEntry = this.vaultIndex.get(vaultId);
717
+ for (const recipientId of recipientMemberIds) {
718
+ if (!vault.metadata.sharedWith.includes(recipientId)) {
719
+ vault.metadata.sharedWith.push(recipientId);
720
+ // Update vault index
721
+ if (indexEntry) {
722
+ indexEntry.sharedWith.push(recipientId);
723
+ }
724
+ // Track in memberVaults for listing
725
+ if (!this.memberVaults.has(recipientId)) {
726
+ this.memberVaults.set(recipientId, new Set());
727
+ }
728
+ this.memberVaults.get(recipientId).add(vaultId);
729
+ }
730
+ }
731
+ vault.metadata.updatedAt = new Date();
732
+ if (indexEntry) {
733
+ indexEntry.updatedAt = vault.metadata.updatedAt;
734
+ }
735
+ await vault.auditLogger.log({
736
+ id: (0, uuid_1.v4)(),
737
+ vaultId,
738
+ memberId: vault.metadata.ownerId,
739
+ action: brightchain_lib_1.AuditAction.VAULT_SHARED,
740
+ metadata: { recipients: recipientMemberIds.join(',') },
741
+ });
742
+ }
743
+ /**
744
+ * Revoke a member's access to a shared vault.
745
+ * Removes from VCBL header shared list and re-keys the vault.
746
+ * Requirements: 4.3
747
+ */
748
+ async revokeShare(vaultId, memberId) {
749
+ const vault = this.getVaultOrThrow(vaultId);
750
+ const indexEntry = this.vaultIndex.get(vaultId);
751
+ const idx = vault.metadata.sharedWith.indexOf(memberId);
752
+ if (idx === -1) {
753
+ return; // Not shared with this member, no-op
754
+ }
755
+ vault.metadata.sharedWith.splice(idx, 1);
756
+ // Update vault index
757
+ if (indexEntry) {
758
+ const indexIdx = indexEntry.sharedWith.indexOf(memberId);
759
+ if (indexIdx !== -1) {
760
+ indexEntry.sharedWith.splice(indexIdx, 1);
761
+ }
762
+ }
763
+ // Remove from memberVaults tracking
764
+ const memberVaultIds = this.memberVaults.get(memberId);
765
+ if (memberVaultIds) {
766
+ memberVaultIds.delete(vaultId);
767
+ }
768
+ // Re-key the vault by regenerating the BIP39 seed
769
+ // This ensures the revoked member's old key material is completely useless
770
+ const { mnemonic: newMnemonic, seed: newSeed } = this.generateVaultBip39();
771
+ // We need the master password to derive the new key, but we don't have it here.
772
+ // For now, we'll mark the vault as needing re-keying on next open.
773
+ // In a production system, this would trigger a re-keying flow.
774
+ //
775
+ // Alternative: Store the vault key encrypted with the owner's public key,
776
+ // allowing re-keying without the master password.
777
+ //
778
+ // For now, we update the seed and invalidate emergency shares.
779
+ // The vault key will be re-derived on next successful open.
780
+ vault.vaultMnemonic = newMnemonic;
781
+ vault.vaultSeed = newSeed;
782
+ vault.metadata.updatedAt = new Date();
783
+ // Invalidate any existing emergency shares (they used the old key)
784
+ if (vault.emergencyConfig) {
785
+ vault.emergencyConfig = undefined;
786
+ vault.emergencyShares = undefined;
787
+ }
788
+ // Update vault index
789
+ if (indexEntry) {
790
+ indexEntry.vaultMnemonic = newMnemonic;
791
+ indexEntry.vaultSeed = newSeed;
792
+ indexEntry.updatedAt = vault.metadata.updatedAt;
793
+ }
794
+ await vault.auditLogger.log({
795
+ id: (0, uuid_1.v4)(),
796
+ vaultId,
797
+ memberId: vault.metadata.ownerId,
798
+ action: brightchain_lib_1.AuditAction.VAULT_SHARE_REVOKED,
799
+ metadata: { revokedMemberId: memberId, seedRegenerated: 'true' },
800
+ });
801
+ }
802
+ // ─── Quorum Governance ─────────────────────────────────────────
803
+ /**
804
+ * Configure quorum governance for a shared vault.
805
+ * When threshold > 0, opening the vault requires that many member approvals.
806
+ * Requirements: 4.4
807
+ */
808
+ async configureQuorumGovernance(vaultId, threshold) {
809
+ const vault = this.getVaultOrThrow(vaultId);
810
+ if (threshold < 0) {
811
+ throw new EmergencyAccessError('Quorum threshold must be non-negative');
812
+ }
813
+ const totalMembers = vault.metadata.sharedWith.length + 1; // +1 for owner
814
+ if (threshold > totalMembers) {
815
+ throw new EmergencyAccessError(`Quorum threshold (${threshold}) cannot exceed total members (${totalMembers})`);
816
+ }
817
+ vault.quorumThreshold = threshold;
818
+ vault.quorumApprovals = new Set();
819
+ vault.metadata.updatedAt = new Date();
820
+ await vault.auditLogger.log({
821
+ id: (0, uuid_1.v4)(),
822
+ vaultId,
823
+ memberId: vault.metadata.ownerId,
824
+ action: brightchain_lib_1.AuditAction.QUORUM_CONFIGURED,
825
+ metadata: { quorumThreshold: String(threshold) },
826
+ });
827
+ }
828
+ /**
829
+ * Submit a quorum approval for a vault.
830
+ * The member must be the owner or a shared member.
831
+ * Returns true if the quorum threshold has been met.
832
+ * Requirements: 4.4
833
+ */
834
+ approveQuorumAccess(vaultId, memberId) {
835
+ const vault = this.getVaultOrThrow(vaultId);
836
+ if (vault.metadata.ownerId !== memberId &&
837
+ !vault.metadata.sharedWith.includes(memberId)) {
838
+ throw new VaultAuthenticationError();
839
+ }
840
+ vault.quorumApprovals.add(memberId);
841
+ return vault.quorumApprovals.size >= vault.quorumThreshold;
842
+ }
843
+ /**
844
+ * Check whether the quorum threshold has been met for a vault.
845
+ */
846
+ isQuorumMet(vaultId) {
847
+ const vault = this.getVaultOrThrow(vaultId);
848
+ if (vault.quorumThreshold === 0)
849
+ return true;
850
+ return vault.quorumApprovals.size >= vault.quorumThreshold;
851
+ }
852
+ /**
853
+ * Reset quorum approvals (e.g. after vault is closed or session ends).
854
+ */
855
+ resetQuorumApprovals(vaultId) {
856
+ const vault = this.getVaultOrThrow(vaultId);
857
+ vault.quorumApprovals = new Set();
858
+ }
859
+ // ─── Master Password ─────────────────────────────────────────
860
+ /**
861
+ * Change the master password for a vault.
862
+ * Verifies old password, re-derives vault key with new password,
863
+ * decrypts all entries with old key, and re-encrypts with new key.
864
+ *
865
+ * Note: This only changes the password, not the vault's BIP39 seed.
866
+ * Use regenerateVaultSeed() for full key rotation.
867
+ *
868
+ * Requirements: 3.4, 3.2
869
+ */
870
+ async changeMasterPassword(memberId, vaultId, oldPassword, newPassword) {
871
+ const vault = this.getVaultOrThrow(vaultId);
872
+ // Verify ownership
873
+ if (vault.metadata.ownerId !== memberId) {
874
+ throw new VaultAuthenticationError();
875
+ }
876
+ // Verify old password using bcrypt (constant-time comparison)
877
+ const oldPasswordValid = await this.verifyMasterPassword(oldPassword, vault.masterPasswordHash);
878
+ if (!oldPasswordValid) {
879
+ throw new VaultAuthenticationError();
880
+ }
881
+ // Re-derive vault key with new password (using vault's own BIP39 seed)
882
+ const newVaultKey = brightchain_lib_1.VaultKeyDerivation.deriveVaultKey(vault.vaultSeed, newPassword, vaultId);
883
+ // Re-encrypt all entries with new key (Req 3.4, 3.2)
884
+ const oldVaultKey = vault.vaultKey;
885
+ for (const [entryId, encryptedEntry] of vault.entries) {
886
+ // Decrypt with old key
887
+ const decrypted = vaultEncryption_1.VaultEncryption.decryptString(oldVaultKey, encryptedEntry);
888
+ // Re-encrypt with new key
889
+ const reEncrypted = vaultEncryption_1.VaultEncryption.encryptString(newVaultKey, decrypted);
890
+ vault.entries.set(entryId, reEncrypted);
891
+ }
892
+ vault.vaultKey = newVaultKey;
893
+ // Hash new password using bcrypt
894
+ vault.masterPasswordHash = await this.hashMasterPasswordAsync(newPassword);
895
+ vault.metadata.updatedAt = new Date();
896
+ }
897
+ // ─── Emergency Access ───────────────────────────────────────
898
+ /**
899
+ * Split the vault key into Shamir shares for emergency recovery.
900
+ * Uses proper Shamir Secret Sharing (polynomial interpolation over GF(256)).
901
+ * Each trustee receives an encrypted share; threshold shares are needed to reconstruct.
902
+ *
903
+ * Security: Uses @digitaldefiance/secrets library which implements proper
904
+ * Shamir Secret Sharing with cryptographically secure random coefficients.
905
+ *
906
+ * Requirements: 10.1, 10.2, 10.3
907
+ */
908
+ async configureEmergencyAccess(vaultId, threshold, trustees) {
909
+ const vault = this.getVaultOrThrow(vaultId);
910
+ const totalShares = trustees.length;
911
+ if (threshold < 2) {
912
+ throw new EmergencyAccessError('Threshold must be at least 2 for security');
913
+ }
914
+ if (threshold > totalShares) {
915
+ throw new EmergencyAccessError(`Threshold (${threshold}) cannot exceed total shares (${totalShares})`);
916
+ }
917
+ // Initialize secrets library with correct bit size for the number of shares
918
+ // Must have at least 3 bits (min 8 shares capacity)
919
+ const bits = Math.max(3, Math.ceil(Math.log2(totalShares + 1)));
920
+ secrets.init(bits, 'nodeCryptoRandomBytes');
921
+ // Convert vault key to hex string for Shamir splitting
922
+ const keyHex = Buffer.from(vault.vaultKey).toString('hex');
923
+ // Use real Shamir Secret Sharing to split the vault key
924
+ // share(secret, totalShares, threshold) returns array of hex share strings
925
+ // These hex strings include share metadata (index prefix) and must be preserved as-is
926
+ const shamirShares = secrets.share(keyHex, totalShares, threshold);
927
+ // Create encrypted shares for each trustee
928
+ // Store shares as hex strings encoded to UTF-8 bytes to preserve the format
929
+ const shares = shamirShares.map((shareHex, index) => {
930
+ // Store the hex string as UTF-8 bytes to preserve the share format exactly
931
+ const shareData = new Uint8Array(Buffer.from(shareHex, 'utf8'));
932
+ return {
933
+ trusteeId: trustees[index],
934
+ encryptedShareData: shareData,
935
+ };
936
+ });
937
+ const config = {
938
+ vaultId,
939
+ threshold,
940
+ totalShares,
941
+ trustees: [...trustees],
942
+ };
943
+ vault.emergencyConfig = config;
944
+ vault.emergencyShares = shares;
945
+ await vault.auditLogger.log({
946
+ id: (0, uuid_1.v4)(),
947
+ vaultId,
948
+ memberId: vault.metadata.ownerId,
949
+ action: brightchain_lib_1.AuditAction.EMERGENCY_CONFIGURED,
950
+ metadata: { threshold: String(threshold), trustees: trustees.join(',') },
951
+ });
952
+ return { ...config };
953
+ }
954
+ /**
955
+ * Recover a vault using emergency shares.
956
+ * Uses proper Shamir Secret Sharing reconstruction (polynomial interpolation).
957
+ * Requires at least threshold shares to reconstruct the vault key.
958
+ *
959
+ * Security: The combine() function performs Lagrange interpolation over GF(256)
960
+ * to reconstruct the secret. With fewer than threshold shares, reconstruction
961
+ * is cryptographically impossible.
962
+ *
963
+ * Requirements: 10.2, 10.3, 10.4
964
+ */
965
+ async recoverWithShares(vaultId, shares) {
966
+ const vault = this.getVaultOrThrow(vaultId);
967
+ if (!vault.emergencyConfig || !vault.emergencyShares) {
968
+ throw new EmergencyAccessError('Emergency access not configured');
969
+ }
970
+ const { threshold, totalShares } = vault.emergencyConfig;
971
+ if (shares.length < threshold) {
972
+ throw new EmergencyAccessError(`Insufficient shares: need ${threshold}, got ${shares.length}`);
973
+ }
974
+ // Verify all provided shares are from valid trustees
975
+ for (const share of shares) {
976
+ const storedShare = vault.emergencyShares.find((s) => s.trusteeId === share.trusteeId);
977
+ if (!storedShare) {
978
+ throw new EmergencyAccessError(`Invalid share from trustee: ${share.trusteeId}`);
979
+ }
980
+ // Verify share data matches using constant-time comparison
981
+ if (share.encryptedShareData.length !==
982
+ storedShare.encryptedShareData.length) {
983
+ throw new EmergencyAccessError(`Invalid share data from trustee: ${share.trusteeId}`);
984
+ }
985
+ const shareBuffer = Buffer.from(share.encryptedShareData);
986
+ const storedBuffer = Buffer.from(storedShare.encryptedShareData);
987
+ if (!crypto.timingSafeEqual(shareBuffer, storedBuffer)) {
988
+ throw new EmergencyAccessError(`Invalid share data from trustee: ${share.trusteeId}`);
989
+ }
990
+ }
991
+ // Initialize secrets library with the same bit size used during share creation
992
+ const bits = Math.max(3, Math.ceil(Math.log2(totalShares + 1)));
993
+ secrets.init(bits, 'nodeCryptoRandomBytes');
994
+ // Convert shares back to hex strings (they were stored as UTF-8 encoded hex)
995
+ const shareHexStrings = shares.map((s) => Buffer.from(s.encryptedShareData).toString('utf8'));
996
+ // Reconstruct the vault key using Shamir Secret Sharing
997
+ const reconstructedKeyHex = secrets.combine(shareHexStrings);
998
+ const reconstructedKey = new Uint8Array(Buffer.from(reconstructedKeyHex, 'hex'));
999
+ // Verify the reconstructed key matches the vault key
1000
+ // This is a sanity check - in production, the vault key wouldn't be stored
1001
+ if (reconstructedKey.length !== vault.vaultKey.length) {
1002
+ throw new EmergencyAccessError('Share reconstruction failed - key length mismatch');
1003
+ }
1004
+ if (!crypto.timingSafeEqual(Buffer.from(reconstructedKey), Buffer.from(vault.vaultKey))) {
1005
+ throw new EmergencyAccessError('Share reconstruction failed - invalid shares');
1006
+ }
1007
+ await vault.auditLogger.log({
1008
+ id: (0, uuid_1.v4)(),
1009
+ vaultId,
1010
+ memberId: shares[0].trusteeId,
1011
+ action: brightchain_lib_1.AuditAction.EMERGENCY_RECOVERED,
1012
+ metadata: {
1013
+ sharesUsed: String(shares.length),
1014
+ trustees: shares.map((s) => s.trusteeId).join(','),
1015
+ },
1016
+ });
1017
+ return {
1018
+ metadata: { ...vault.metadata },
1019
+ propertyRecords: [...vault.propertyRecords],
1020
+ };
1021
+ }
1022
+ /**
1023
+ * Revoke emergency access configuration, invalidating all existing shares.
1024
+ * Requirements: 10.5
1025
+ */
1026
+ async revokeEmergencyAccess(vaultId) {
1027
+ const vault = this.getVaultOrThrow(vaultId);
1028
+ vault.emergencyConfig = undefined;
1029
+ vault.emergencyShares = undefined;
1030
+ }
1031
+ /**
1032
+ * Get the encrypted shares for a vault's emergency access.
1033
+ * In production, shares would be distributed to trustees via secure channels.
1034
+ * Requirements: 10.2
1035
+ */
1036
+ getEmergencyShares(vaultId) {
1037
+ const vault = this.getVaultOrThrow(vaultId);
1038
+ if (!vault.emergencyShares) {
1039
+ throw new EmergencyAccessError('Emergency access not configured');
1040
+ }
1041
+ return vault.emergencyShares.map((s) => ({
1042
+ trusteeId: s.trusteeId,
1043
+ encryptedShareData: new Uint8Array(s.encryptedShareData),
1044
+ }));
1045
+ }
1046
+ // ─── Autofill ──────────────────────────────────────────────────
1047
+ /**
1048
+ * Get autofill payload for a given site URL.
1049
+ * Searches VCBL property records by siteUrl, decrypts matching login entries,
1050
+ * and includes TOTP code if configured.
1051
+ * Requirements: 5.7
1052
+ */
1053
+ async getAutofillPayload(vaultId, siteUrl) {
1054
+ const vault = this.getVaultOrThrow(vaultId);
1055
+ const lowerUrl = siteUrl.toLowerCase();
1056
+ // Find matching login property records by siteUrl
1057
+ const matchingEntryIds = [];
1058
+ for (let i = 0; i < vault.propertyRecords.length; i++) {
1059
+ const record = vault.propertyRecords[i];
1060
+ if (record.entryType === 'login' &&
1061
+ record.siteUrl.toLowerCase().includes(lowerUrl)) {
1062
+ matchingEntryIds.push(vault.blockIds[i]);
1063
+ }
1064
+ }
1065
+ // Decrypt matching entries and build payload (Req 3.2)
1066
+ const entries = [];
1067
+ for (const entryId of matchingEntryIds) {
1068
+ const encrypted = vault.entries.get(entryId);
1069
+ if (!encrypted)
1070
+ continue;
1071
+ const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
1072
+ const entry = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
1073
+ if (entry.type !== 'login')
1074
+ continue;
1075
+ const loginEntry = entry;
1076
+ let totpCode;
1077
+ if (loginEntry.totpSecret) {
1078
+ totpCode = brightchain_lib_1.TOTPEngine.generate(loginEntry.totpSecret);
1079
+ }
1080
+ entries.push({
1081
+ entryId: loginEntry.id,
1082
+ title: loginEntry.title,
1083
+ username: loginEntry.username,
1084
+ password: loginEntry.password,
1085
+ siteUrl: loginEntry.siteUrl,
1086
+ totpCode,
1087
+ });
1088
+ }
1089
+ return { vaultId, entries };
1090
+ }
1091
+ // ─── Attachments ──────────────────────────────────────────────
1092
+ /**
1093
+ * Add an attachment to a vault entry.
1094
+ * Stores the file as an encrypted block and adds a reference to the entry.
1095
+ * Requirements: 1.7, 2.8
1096
+ */
1097
+ async addAttachment(vaultId, entryId, file, filename, mimeType = 'application/octet-stream') {
1098
+ const vault = this.getVaultOrThrow(vaultId);
1099
+ const encrypted = vault.entries.get(entryId);
1100
+ if (!encrypted) {
1101
+ throw new EntryNotFoundError(entryId);
1102
+ }
1103
+ // Decrypt entry (Req 3.2)
1104
+ const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
1105
+ const entry = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
1106
+ // Create attachment reference
1107
+ const attachmentId = (0, uuid_1.v4)();
1108
+ const blockId = (0, uuid_1.v4)(); // simulated block storage
1109
+ const reference = {
1110
+ id: attachmentId,
1111
+ filename,
1112
+ mimeType,
1113
+ sizeBytes: file.length,
1114
+ blockId,
1115
+ isCbl: file.length > 65536, // use CBL for files > 64KB
1116
+ };
1117
+ // Encrypt and store the attachment data (Req 3.2)
1118
+ const encryptedFile = vaultEncryption_1.VaultEncryption.encrypt(vault.vaultKey, new Uint8Array(file));
1119
+ await this.blockStore.put(blockId, encryptedFile);
1120
+ // Update entry with attachment reference
1121
+ const attachments = entry.attachments ?? [];
1122
+ attachments.push(reference);
1123
+ const updatedEntry = {
1124
+ ...entry,
1125
+ attachments,
1126
+ updatedAt: new Date(),
1127
+ };
1128
+ const serialized = brightchain_lib_1.VaultSerializer.serializeEntry(updatedEntry);
1129
+ vault.entries.set(entryId, vaultEncryption_1.VaultEncryption.encryptString(vault.vaultKey, serialized));
1130
+ // Update property record timestamp
1131
+ const index = vault.blockIds.indexOf(entryId);
1132
+ if (index !== -1) {
1133
+ vault.propertyRecords[index] = this.entryToPropertyRecord(updatedEntry);
1134
+ }
1135
+ vault.metadata.updatedAt = new Date();
1136
+ await vault.auditLogger.log({
1137
+ id: (0, uuid_1.v4)(),
1138
+ vaultId,
1139
+ memberId: vault.metadata.ownerId,
1140
+ action: brightchain_lib_1.AuditAction.ENTRY_UPDATED,
1141
+ metadata: { entryId, attachmentId, filename },
1142
+ });
1143
+ return reference;
1144
+ }
1145
+ /**
1146
+ * Get an attachment's data by ID.
1147
+ * Decrypts the attachment using AES-256-GCM.
1148
+ * Requirements: 2.8, 3.2
1149
+ */
1150
+ async getAttachment(vaultId, entryId, attachmentId) {
1151
+ const vault = this.getVaultOrThrow(vaultId);
1152
+ const encrypted = vault.entries.get(entryId);
1153
+ if (!encrypted) {
1154
+ throw new EntryNotFoundError(entryId);
1155
+ }
1156
+ // Decrypt entry to get attachment reference (Req 3.2)
1157
+ const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
1158
+ const entry = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
1159
+ const ref = entry.attachments?.find((a) => a.id === attachmentId);
1160
+ if (!ref) {
1161
+ throw new EntryNotFoundError(`Attachment not found: ${attachmentId}`);
1162
+ }
1163
+ const hasBlock = await this.blockStore.has(ref.blockId);
1164
+ if (!hasBlock) {
1165
+ throw new EntryNotFoundError(`Attachment data not found: ${attachmentId}`);
1166
+ }
1167
+ const blockHandle = this.blockStore.get(ref.blockId);
1168
+ const encryptedData = blockHandle.fullData;
1169
+ // Decrypt attachment data (Req 3.2)
1170
+ const decryptedData = vaultEncryption_1.VaultEncryption.decrypt(vault.vaultKey, new Uint8Array(encryptedData));
1171
+ return Buffer.from(decryptedData);
1172
+ }
1173
+ /**
1174
+ * Delete an attachment from a vault entry.
1175
+ * Removes the attachment block and updates the entry reference.
1176
+ * Requirements: 2.8, 3.2
1177
+ */
1178
+ async deleteAttachment(vaultId, entryId, attachmentId) {
1179
+ const vault = this.getVaultOrThrow(vaultId);
1180
+ const encrypted = vault.entries.get(entryId);
1181
+ if (!encrypted) {
1182
+ throw new EntryNotFoundError(entryId);
1183
+ }
1184
+ // Decrypt entry (Req 3.2)
1185
+ const decrypted = vaultEncryption_1.VaultEncryption.decryptString(vault.vaultKey, encrypted);
1186
+ const entry = brightchain_lib_1.VaultSerializer.deserializeEntry(decrypted);
1187
+ const attachments = entry.attachments ?? [];
1188
+ const refIndex = attachments.findIndex((a) => a.id === attachmentId);
1189
+ if (refIndex === -1) {
1190
+ throw new EntryNotFoundError(`Attachment not found: ${attachmentId}`);
1191
+ }
1192
+ // Remove from block store
1193
+ const ref = attachments[refIndex];
1194
+ await this.blockStore.delete(ref.blockId);
1195
+ // Update entry and re-encrypt (Req 3.2)
1196
+ attachments.splice(refIndex, 1);
1197
+ const updatedEntry = {
1198
+ ...entry,
1199
+ attachments: attachments.length > 0 ? attachments : undefined,
1200
+ updatedAt: new Date(),
1201
+ };
1202
+ const serialized = brightchain_lib_1.VaultSerializer.serializeEntry(updatedEntry);
1203
+ vault.entries.set(entryId, vaultEncryption_1.VaultEncryption.encryptString(vault.vaultKey, serialized));
1204
+ // Update property record timestamp
1205
+ const index = vault.blockIds.indexOf(entryId);
1206
+ if (index !== -1) {
1207
+ vault.propertyRecords[index] = this.entryToPropertyRecord(updatedEntry);
1208
+ }
1209
+ vault.metadata.updatedAt = new Date();
1210
+ await vault.auditLogger.log({
1211
+ id: (0, uuid_1.v4)(),
1212
+ vaultId,
1213
+ memberId: vault.metadata.ownerId,
1214
+ action: brightchain_lib_1.AuditAction.ENTRY_UPDATED,
1215
+ metadata: { entryId, attachmentId, action: 'delete_attachment' },
1216
+ });
1217
+ }
1218
+ // ─── Import ───────────────────────────────────────────────────
1219
+ /**
1220
+ * Import entries from a password manager export file.
1221
+ * Parses the file, maps to VaultEntry types, and adds each to the vault.
1222
+ * Requirements: 12.1–12.10
1223
+ */
1224
+ async importFromFile(vaultId, format, fileContent) {
1225
+ this.getVaultOrThrow(vaultId); // verify vault exists
1226
+ const { entries, errors } = brightchain_lib_1.ImportParser.parse(format, fileContent.buffer.slice(fileContent.byteOffset, fileContent.byteOffset + fileContent.byteLength));
1227
+ const importErrors = [...errors];
1228
+ let successfulImports = 0;
1229
+ for (let i = 0; i < entries.length; i++) {
1230
+ try {
1231
+ await this.addEntry(vaultId, entries[i]);
1232
+ successfulImports++;
1233
+ }
1234
+ catch (err) {
1235
+ importErrors.push({
1236
+ recordIndex: i,
1237
+ error: err instanceof Error ? err.message : String(err),
1238
+ });
1239
+ }
1240
+ }
1241
+ return {
1242
+ totalRecords: entries.length + errors.length,
1243
+ successfulImports,
1244
+ errors: importErrors,
1245
+ };
1246
+ }
1247
+ // ─── Audit ────────────────────────────────────────────────────
1248
+ /**
1249
+ * Get audit log entries for a vault.
1250
+ * Requirements: 9.3
1251
+ */
1252
+ async getAuditLog(vaultId) {
1253
+ const vault = this.getVaultOrThrow(vaultId);
1254
+ return vault.auditLogger.getEntries();
1255
+ }
1256
+ }
1257
+ exports.BrightPassService = BrightPassService;
1258
+ /** bcrypt cost factor - 12 rounds provides ~300ms hash time on modern hardware */
1259
+ BrightPassService.BCRYPT_ROUNDS = 12;
1260
+ //# sourceMappingURL=brightpass.js.map