@keeper-security/keeper-sdk-javascript 0.1.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 (173) hide show
  1. package/dist/auth/ConsoleAuthUI.d.ts +10 -0
  2. package/dist/auth/ConsoleAuthUI.js +152 -0
  3. package/dist/auth/ConsoleAuthUI.js.map +1 -0
  4. package/dist/auth/ConsoleLogin.d.ts +8 -0
  5. package/dist/auth/ConsoleLogin.js +266 -0
  6. package/dist/auth/ConsoleLogin.js.map +1 -0
  7. package/dist/auth/SessionManager.d.ts +66 -0
  8. package/dist/auth/SessionManager.js +211 -0
  9. package/dist/auth/SessionManager.js.map +1 -0
  10. package/dist/index.d.ts +17 -0
  11. package/dist/index.js +61 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/records/RecordOperations.d.ts +79 -0
  14. package/dist/records/RecordOperations.js +346 -0
  15. package/dist/records/RecordOperations.js.map +1 -0
  16. package/dist/records/RecordUtils.d.ts +36 -0
  17. package/dist/records/RecordUtils.js +224 -0
  18. package/dist/records/RecordUtils.js.map +1 -0
  19. package/dist/sharing/Sharing.d.ts +27 -0
  20. package/dist/sharing/Sharing.js +125 -0
  21. package/dist/sharing/Sharing.js.map +1 -0
  22. package/dist/src/auth/ConsoleAuthUI.d.ts +10 -0
  23. package/dist/src/auth/ConsoleAuthUI.js +161 -0
  24. package/dist/src/auth/ConsoleAuthUI.js.map +1 -0
  25. package/dist/src/auth/ConsoleLogin.d.ts +8 -0
  26. package/dist/src/auth/ConsoleLogin.js +311 -0
  27. package/dist/src/auth/ConsoleLogin.js.map +1 -0
  28. package/dist/src/auth/SessionManager.d.ts +67 -0
  29. package/dist/src/auth/SessionManager.js +212 -0
  30. package/dist/src/auth/SessionManager.js.map +1 -0
  31. package/dist/src/folders/FolderManager.d.ts +57 -0
  32. package/dist/src/folders/FolderManager.js +108 -0
  33. package/dist/src/folders/FolderManager.js.map +1 -0
  34. package/dist/src/folders/addFolder.d.ts +32 -0
  35. package/dist/src/folders/addFolder.js +207 -0
  36. package/dist/src/folders/addFolder.js.map +1 -0
  37. package/dist/src/folders/changeDirectory.d.ts +19 -0
  38. package/dist/src/folders/changeDirectory.js +171 -0
  39. package/dist/src/folders/changeDirectory.js.map +1 -0
  40. package/dist/src/folders/deleteFolder.d.ts +17 -0
  41. package/dist/src/folders/deleteFolder.js +237 -0
  42. package/dist/src/folders/deleteFolder.js.map +1 -0
  43. package/dist/src/folders/folderHelpers.d.ts +48 -0
  44. package/dist/src/folders/folderHelpers.js +100 -0
  45. package/dist/src/folders/folderHelpers.js.map +1 -0
  46. package/dist/src/folders/folderTree.d.ts +29 -0
  47. package/dist/src/folders/folderTree.js +250 -0
  48. package/dist/src/folders/folderTree.js.map +1 -0
  49. package/dist/src/folders/getFolder.d.ts +56 -0
  50. package/dist/src/folders/getFolder.js +143 -0
  51. package/dist/src/folders/getFolder.js.map +1 -0
  52. package/dist/src/folders/listFolder.d.ts +48 -0
  53. package/dist/src/folders/listFolder.js +276 -0
  54. package/dist/src/folders/listFolder.js.map +1 -0
  55. package/dist/src/folders/updateFolder.d.ts +31 -0
  56. package/dist/src/folders/updateFolder.js +137 -0
  57. package/dist/src/folders/updateFolder.js.map +1 -0
  58. package/dist/src/index.d.ts +49 -0
  59. package/dist/src/index.js +151 -0
  60. package/dist/src/index.js.map +1 -0
  61. package/dist/src/records/RecordOperations.d.ts +80 -0
  62. package/dist/src/records/RecordOperations.js +356 -0
  63. package/dist/src/records/RecordOperations.js.map +1 -0
  64. package/dist/src/records/RecordUtils.d.ts +37 -0
  65. package/dist/src/records/RecordUtils.js +263 -0
  66. package/dist/src/records/RecordUtils.js.map +1 -0
  67. package/dist/src/records/Totp.d.ts +14 -0
  68. package/dist/src/records/Totp.js +111 -0
  69. package/dist/src/records/Totp.js.map +1 -0
  70. package/dist/src/sharedFolders/SharedFolderManager.d.ts +20 -0
  71. package/dist/src/sharedFolders/SharedFolderManager.js +33 -0
  72. package/dist/src/sharedFolders/SharedFolderManager.js.map +1 -0
  73. package/dist/src/sharedFolders/listSharedFolders.d.ts +29 -0
  74. package/dist/src/sharedFolders/listSharedFolders.js +127 -0
  75. package/dist/src/sharedFolders/listSharedFolders.js.map +1 -0
  76. package/dist/src/sharedFolders/shareFolder.d.ts +36 -0
  77. package/dist/src/sharedFolders/shareFolder.js +352 -0
  78. package/dist/src/sharedFolders/shareFolder.js.map +1 -0
  79. package/dist/src/sharing/Sharing.d.ts +50 -0
  80. package/dist/src/sharing/Sharing.js +195 -0
  81. package/dist/src/sharing/Sharing.js.map +1 -0
  82. package/dist/src/storage/InMemoryStorage.d.ts +24 -0
  83. package/dist/src/storage/InMemoryStorage.js +139 -0
  84. package/dist/src/storage/InMemoryStorage.js.map +1 -0
  85. package/dist/src/teams/TeamManager.d.ts +17 -0
  86. package/dist/src/teams/TeamManager.js +38 -0
  87. package/dist/src/teams/TeamManager.js.map +1 -0
  88. package/dist/src/teams/enterpriseData.d.ts +106 -0
  89. package/dist/src/teams/enterpriseData.js +319 -0
  90. package/dist/src/teams/enterpriseData.js.map +1 -0
  91. package/dist/src/teams/listTeams.d.ts +42 -0
  92. package/dist/src/teams/listTeams.js +308 -0
  93. package/dist/src/teams/listTeams.js.map +1 -0
  94. package/dist/src/teams/viewTeam.d.ts +35 -0
  95. package/dist/src/teams/viewTeam.js +177 -0
  96. package/dist/src/teams/viewTeam.js.map +1 -0
  97. package/dist/src/utils/Logger.d.ts +28 -0
  98. package/dist/src/utils/Logger.js +62 -0
  99. package/dist/src/utils/Logger.js.map +1 -0
  100. package/dist/src/utils/constants.d.ts +50 -0
  101. package/dist/src/utils/constants.js +64 -0
  102. package/dist/src/utils/constants.js.map +1 -0
  103. package/dist/src/utils/errors.d.ts +10 -0
  104. package/dist/src/utils/errors.js +117 -0
  105. package/dist/src/utils/errors.js.map +1 -0
  106. package/dist/src/utils/guards.d.ts +7 -0
  107. package/dist/src/utils/guards.js +29 -0
  108. package/dist/src/utils/guards.js.map +1 -0
  109. package/dist/src/utils/index.d.ts +7 -0
  110. package/dist/src/utils/index.js +39 -0
  111. package/dist/src/utils/index.js.map +1 -0
  112. package/dist/src/utils/patterns.d.ts +9 -0
  113. package/dist/src/utils/patterns.js +20 -0
  114. package/dist/src/utils/patterns.js.map +1 -0
  115. package/dist/src/utils/types.d.ts +12 -0
  116. package/dist/src/utils/types.js +3 -0
  117. package/dist/src/utils/types.js.map +1 -0
  118. package/dist/src/vault/KeeperVault.d.ts +116 -0
  119. package/dist/src/vault/KeeperVault.js +443 -0
  120. package/dist/src/vault/KeeperVault.js.map +1 -0
  121. package/dist/storage/InMemoryStorage.d.ts +24 -0
  122. package/dist/storage/InMemoryStorage.js +132 -0
  123. package/dist/storage/InMemoryStorage.js.map +1 -0
  124. package/dist/utils/Logger.d.ts +28 -0
  125. package/dist/utils/Logger.js +62 -0
  126. package/dist/utils/Logger.js.map +1 -0
  127. package/dist/utils/constants.d.ts +26 -0
  128. package/dist/utils/constants.js +37 -0
  129. package/dist/utils/constants.js.map +1 -0
  130. package/dist/utils/errors.d.ts +10 -0
  131. package/dist/utils/errors.js +117 -0
  132. package/dist/utils/errors.js.map +1 -0
  133. package/dist/utils/index.d.ts +4 -0
  134. package/dist/utils/index.js +22 -0
  135. package/dist/utils/index.js.map +1 -0
  136. package/dist/vault/KeeperVault.d.ts +72 -0
  137. package/dist/vault/KeeperVault.js +338 -0
  138. package/dist/vault/KeeperVault.js.map +1 -0
  139. package/package.json +32 -0
  140. package/src/auth/ConsoleAuthUI.ts +169 -0
  141. package/src/auth/ConsoleLogin.ts +351 -0
  142. package/src/auth/SessionManager.ts +293 -0
  143. package/src/folders/FolderManager.ts +174 -0
  144. package/src/folders/addFolder.ts +294 -0
  145. package/src/folders/changeDirectory.ts +217 -0
  146. package/src/folders/deleteFolder.ts +293 -0
  147. package/src/folders/folderHelpers.ts +99 -0
  148. package/src/folders/folderTree.ts +321 -0
  149. package/src/folders/getFolder.ts +234 -0
  150. package/src/folders/listFolder.ts +358 -0
  151. package/src/folders/updateFolder.ts +210 -0
  152. package/src/index.ts +242 -0
  153. package/src/records/RecordOperations.ts +549 -0
  154. package/src/records/RecordUtils.ts +282 -0
  155. package/src/records/Totp.ts +119 -0
  156. package/src/sharedFolders/SharedFolderManager.ts +57 -0
  157. package/src/sharedFolders/listSharedFolders.ts +173 -0
  158. package/src/sharedFolders/shareFolder.ts +457 -0
  159. package/src/sharing/Sharing.ts +282 -0
  160. package/src/storage/InMemoryStorage.ts +163 -0
  161. package/src/teams/TeamManager.ts +61 -0
  162. package/src/teams/enterpriseData.ts +453 -0
  163. package/src/teams/listTeams.ts +373 -0
  164. package/src/teams/viewTeam.ts +248 -0
  165. package/src/utils/Logger.ts +71 -0
  166. package/src/utils/constants.ts +63 -0
  167. package/src/utils/errors.ts +108 -0
  168. package/src/utils/guards.ts +24 -0
  169. package/src/utils/index.ts +22 -0
  170. package/src/utils/patterns.ts +20 -0
  171. package/src/utils/types.ts +11 -0
  172. package/src/vault/KeeperVault.ts +612 -0
  173. package/tsconfig.json +16 -0
@@ -0,0 +1,457 @@
1
+ import type {
2
+ Auth,
3
+ Authentication,
4
+ DSharedFolder,
5
+ DSharedFolderFolder,
6
+ DSharedFolderUser,
7
+ DUserFolder,
8
+ } from '@keeper-security/keeperapi'
9
+ import {
10
+ Folder,
11
+ getPublicKeysMessage,
12
+ normal64Bytes,
13
+ platform,
14
+ sharedFolderUpdateV3Message,
15
+ } from '@keeper-security/keeperapi'
16
+ import { InMemoryStorage } from '../storage/InMemoryStorage'
17
+ import { extractErrorMessage, isBoolean, isObject, isValidEmail, KeeperSdkError } from '../utils'
18
+ import { FolderKind, FolderResultStatus, VaultObjectKind } from '../folders/folderHelpers'
19
+
20
+ export enum ShareFolderAction {
21
+ Grant = 'grant',
22
+ Remove = 'remove',
23
+ }
24
+
25
+ export type ShareFolderActionInput = ShareFolderAction | `${ShareFolderAction}`
26
+
27
+ export enum ShareFolderUserResultStatus {
28
+ Success = 'success',
29
+ Invited = 'invited',
30
+ MissingPublicKey = 'missing_public_key',
31
+ Unknown = 'unknown',
32
+ }
33
+
34
+ export type ShareFolderInput = {
35
+ folder: string
36
+ emails: string[]
37
+ action?: ShareFolderActionInput
38
+ manageRecords?: boolean
39
+ manageUsers?: boolean
40
+ }
41
+
42
+ export type ShareFolderUserStatus = {
43
+ email: string
44
+ success: boolean
45
+ status: string
46
+ message?: string
47
+ }
48
+
49
+ export type ShareFolderResult = {
50
+ success: boolean
51
+ message?: string
52
+ folderUid: string
53
+ folderKind: FolderKind.SharedFolder
54
+ sharedFolderUid: string
55
+ results: ShareFolderUserStatus[]
56
+ }
57
+
58
+ type ResolvedFolder =
59
+ | {
60
+ kind: FolderKind.SharedFolder
61
+ folderUid: string
62
+ sharedFolderUid: string
63
+ displayName: string
64
+ }
65
+ | {
66
+ kind: FolderKind.SharedFolderFolder
67
+ folderUid: string
68
+ sharedFolderUid: string
69
+ displayName: string
70
+ }
71
+ | { kind: FolderKind.UserFolder; folderUid: string; displayName: string }
72
+
73
+ type UserPublicKeys = {
74
+ rsaPublicKey: Uint8Array | null
75
+ eccPublicKey: Uint8Array | null
76
+ errorCode?: string
77
+ message?: string
78
+ username: string
79
+ }
80
+
81
+ function toSetBoolean(value: boolean | undefined): Folder.SetBooleanValue {
82
+ if (value === true) return Folder.SetBooleanValue.BOOLEAN_TRUE
83
+ if (value === false) return Folder.SetBooleanValue.BOOLEAN_FALSE
84
+ return Folder.SetBooleanValue.BOOLEAN_NO_CHANGE
85
+ }
86
+
87
+ function dataName(data: unknown): string {
88
+ if (isObject(data)) {
89
+ const { title, name } = data as { title?: string; name?: string }
90
+ return (title || name || '').trim()
91
+ }
92
+ return ''
93
+ }
94
+
95
+ function resolveFolder(storage: InMemoryStorage, nameOrUid: string): ResolvedFolder | undefined {
96
+ const trimmedNameOrUid = nameOrUid.trim()
97
+ if (!trimmedNameOrUid) return undefined
98
+
99
+ const sharedFolder = storage.getByUid<DSharedFolder>(FolderKind.SharedFolder, trimmedNameOrUid)
100
+ if (sharedFolder) {
101
+ return {
102
+ kind: FolderKind.SharedFolder,
103
+ folderUid: sharedFolder.uid,
104
+ sharedFolderUid: sharedFolder.uid,
105
+ displayName: (sharedFolder.name || sharedFolder.uid).trim() || sharedFolder.uid,
106
+ }
107
+ }
108
+ const sharedFolderFolder = storage.getByUid<DSharedFolderFolder>(FolderKind.SharedFolderFolder, trimmedNameOrUid)
109
+ if (sharedFolderFolder) {
110
+ return {
111
+ kind: FolderKind.SharedFolderFolder,
112
+ folderUid: sharedFolderFolder.uid,
113
+ sharedFolderUid: sharedFolderFolder.sharedFolderUid,
114
+ displayName: dataName(sharedFolderFolder.data) || sharedFolderFolder.uid,
115
+ }
116
+ }
117
+ const userFolder = storage.getByUid<DUserFolder>(FolderKind.UserFolder, trimmedNameOrUid)
118
+ if (userFolder) {
119
+ return {
120
+ kind: FolderKind.UserFolder,
121
+ folderUid: userFolder.uid,
122
+ displayName: dataName(userFolder.data) || userFolder.uid,
123
+ }
124
+ }
125
+
126
+ const lowerNameOrUid = trimmedNameOrUid.toLowerCase()
127
+ for (const candidateSharedFolder of storage.getAll<DSharedFolder>(FolderKind.SharedFolder)) {
128
+ if ((candidateSharedFolder.name || '').trim().toLowerCase() === lowerNameOrUid) {
129
+ return {
130
+ kind: FolderKind.SharedFolder,
131
+ folderUid: candidateSharedFolder.uid,
132
+ sharedFolderUid: candidateSharedFolder.uid,
133
+ displayName: candidateSharedFolder.name || candidateSharedFolder.uid,
134
+ }
135
+ }
136
+ }
137
+ for (const candidateSharedFolderFolder of storage.getAll<DSharedFolderFolder>(FolderKind.SharedFolderFolder)) {
138
+ const candidateName = dataName(candidateSharedFolderFolder.data)
139
+ if (candidateName && candidateName.toLowerCase() === lowerNameOrUid) {
140
+ return {
141
+ kind: FolderKind.SharedFolderFolder,
142
+ folderUid: candidateSharedFolderFolder.uid,
143
+ sharedFolderUid: candidateSharedFolderFolder.sharedFolderUid,
144
+ displayName: candidateName,
145
+ }
146
+ }
147
+ }
148
+ for (const candidateUserFolder of storage.getAll<DUserFolder>(FolderKind.UserFolder)) {
149
+ const candidateName = dataName(candidateUserFolder.data)
150
+ if (candidateName && candidateName.toLowerCase() === lowerNameOrUid) {
151
+ return {
152
+ kind: FolderKind.UserFolder,
153
+ folderUid: candidateUserFolder.uid,
154
+ displayName: candidateName,
155
+ }
156
+ }
157
+ }
158
+
159
+ return undefined
160
+ }
161
+
162
+ async function fetchUserPublicKeys(auth: Auth, emails: string[]): Promise<Map<string, UserPublicKeys>> {
163
+ const usernameToKeys = new Map<string, UserPublicKeys>()
164
+ if (emails.length === 0) return usernameToKeys
165
+
166
+ const keysRequest = getPublicKeysMessage({ usernames: emails })
167
+ let response: Authentication.IGetPublicKeysResponse
168
+ try {
169
+ response = await auth.executeRest(keysRequest)
170
+ } catch (err) {
171
+ throw new KeeperSdkError(`Failed to fetch public keys: ${extractErrorMessage(err)}`)
172
+ }
173
+ for (const entry of response.keyResponses || []) {
174
+ const username = (entry.username || '').toLowerCase()
175
+ if (!username) continue
176
+ usernameToKeys.set(username, {
177
+ username: entry.username || '',
178
+ rsaPublicKey: entry.publicKey && entry.publicKey.length > 0 ? (entry.publicKey as Uint8Array) : null,
179
+ eccPublicKey:
180
+ entry.publicEccKey && entry.publicEccKey.length > 0 ? (entry.publicEccKey as Uint8Array) : null,
181
+ errorCode: entry.errorCode || undefined,
182
+ message: entry.message || undefined,
183
+ })
184
+ }
185
+ return usernameToKeys
186
+ }
187
+
188
+ function dedupeEmails(emails: string[]): string[] {
189
+ const seen = new Set<string>()
190
+ const dedupedEmails: string[] = []
191
+ for (const rawEmail of emails) {
192
+ const normalized = (rawEmail || '').trim().toLowerCase()
193
+ if (!normalized) continue
194
+ if (seen.has(normalized)) continue
195
+ seen.add(normalized)
196
+ dedupedEmails.push(normalized)
197
+ }
198
+ return dedupedEmails
199
+ }
200
+
201
+ async function removeFromSharedFolder(
202
+ auth: Auth,
203
+ sharedFolder: DSharedFolder,
204
+ resolved: Extract<ResolvedFolder, { kind: FolderKind.SharedFolder | FolderKind.SharedFolderFolder }>,
205
+ emails: string[]
206
+ ): Promise<ShareFolderResult> {
207
+ const updateRequest: Folder.ISharedFolderUpdateV3Request = {
208
+ sharedFolderUid: normal64Bytes(sharedFolder.uid),
209
+ revision: sharedFolder.revision,
210
+ forceUpdate: false,
211
+ sharedFolderRemoveUser: emails,
212
+ }
213
+
214
+ let response: Folder.ISharedFolderUpdateV3ResponseV2
215
+ try {
216
+ response = await auth.executeRest(sharedFolderUpdateV3Message({ sharedFoldersUpdateV3: [updateRequest] }))
217
+ } catch (err) {
218
+ return {
219
+ success: false,
220
+ folderUid: resolved.folderUid,
221
+ sharedFolderUid: sharedFolder.uid,
222
+ folderKind: FolderKind.SharedFolder,
223
+ message: `shared_folder_update_v3 (remove) failed for "${resolved.displayName}" (uid=${sharedFolder.uid}): ${extractErrorMessage(err)}`,
224
+ results: [],
225
+ }
226
+ }
227
+
228
+ const innerResponse = (response.sharedFoldersUpdateV3Response || [])[0]
229
+ const requestOk = !innerResponse?.status || innerResponse.status === FolderResultStatus.Success
230
+
231
+ const userResults: ShareFolderUserStatus[] = []
232
+ for (const userStatus of innerResponse?.sharedFolderRemoveUserStatus || []) {
233
+ const status = userStatus.status || ShareFolderUserResultStatus.Unknown
234
+ userResults.push({
235
+ email: userStatus.username || '',
236
+ success: status === FolderResultStatus.Success,
237
+ status,
238
+ })
239
+ }
240
+
241
+ const allUsersOk = userResults.length > 0 && userResults.every((userResult) => userResult.success)
242
+
243
+ const failureReason = !requestOk
244
+ ? innerResponse?.status ||
245
+ `shared_folder_update_v3 (remove) failed for "${resolved.displayName}" (uid=${sharedFolder.uid}): server returned no status`
246
+ : undefined
247
+
248
+ return {
249
+ success: requestOk && allUsersOk,
250
+ folderUid: resolved.folderUid,
251
+ sharedFolderUid: sharedFolder.uid,
252
+ folderKind: FolderKind.SharedFolder,
253
+ message: failureReason,
254
+ results: userResults,
255
+ }
256
+ }
257
+
258
+ async function shareWithSharedFolder(
259
+ auth: Auth,
260
+ storage: InMemoryStorage,
261
+ resolved: Extract<ResolvedFolder, { kind: FolderKind.SharedFolder | FolderKind.SharedFolderFolder }>,
262
+ input: ShareFolderInput
263
+ ): Promise<ShareFolderResult> {
264
+ const sharedFolder = storage.getByUid<DSharedFolder>(FolderKind.SharedFolder, resolved.sharedFolderUid)
265
+ if (!sharedFolder) {
266
+ throw new KeeperSdkError(`Shared folder "${resolved.sharedFolderUid}" not found.`, 'shared_folder_not_found')
267
+ }
268
+ const sharedFolderKey = await storage.getKeyBytes(sharedFolder.uid)
269
+ if (!sharedFolderKey) {
270
+ throw new KeeperSdkError(
271
+ 'Shared folder encryption key not available. Sync the vault and try again.',
272
+ 'shared_folder_key_missing'
273
+ )
274
+ }
275
+
276
+ const emails = dedupeEmails(input.emails)
277
+ if (emails.length === 0) {
278
+ throw new KeeperSdkError('Provide at least one user email.', 'no_emails')
279
+ }
280
+
281
+ const invalidEmails = emails.filter((email) => !isValidEmail(email))
282
+ if (invalidEmails.length > 0) {
283
+ throw new KeeperSdkError(`Invalid email(s): ${invalidEmails.join(', ')}`, 'invalid_email')
284
+ }
285
+
286
+ if (input.action === ShareFolderAction.Remove) {
287
+ return removeFromSharedFolder(auth, sharedFolder, resolved, emails)
288
+ }
289
+
290
+ const existingMembers = new Set<string>()
291
+ for (const sharedFolderUser of storage.getAll<DSharedFolderUser>(VaultObjectKind.SharedFolderUser)) {
292
+ if (sharedFolderUser.sharedFolderUid === sharedFolder.uid && sharedFolderUser.accountUsername) {
293
+ existingMembers.add(sharedFolderUser.accountUsername.toLowerCase())
294
+ }
295
+ }
296
+
297
+ const newEmails = emails.filter((email) => !existingMembers.has(email))
298
+ const usernameToKeys = await fetchUserPublicKeys(auth, newEmails)
299
+
300
+ const usersToAdd: Folder.ISharedFolderUpdateUser[] = []
301
+ const usersToUpdate: Folder.ISharedFolderUpdateUser[] = []
302
+ const userResults: ShareFolderUserStatus[] = []
303
+
304
+ const newUserManageRecords = isBoolean(input.manageRecords)
305
+ ? input.manageRecords
306
+ : sharedFolder.defaultManageRecords
307
+ const newUserManageUsers = isBoolean(input.manageUsers)
308
+ ? input.manageUsers
309
+ : sharedFolder.defaultManageUsers
310
+
311
+ for (const email of emails) {
312
+ if (existingMembers.has(email)) {
313
+ usersToUpdate.push({
314
+ username: email,
315
+ manageRecords: toSetBoolean(input.manageRecords),
316
+ manageUsers: toSetBoolean(input.manageUsers),
317
+ })
318
+ continue
319
+ }
320
+
321
+ const publicKeys = usernameToKeys.get(email)
322
+ if (!publicKeys) {
323
+ userResults.push({
324
+ email,
325
+ success: false,
326
+ status: ShareFolderUserResultStatus.MissingPublicKey,
327
+ message: `No public key returned for user "${email}" (folder="${resolved.displayName}")`,
328
+ })
329
+ continue
330
+ }
331
+ if (publicKeys.errorCode) {
332
+ userResults.push({
333
+ email,
334
+ success: false,
335
+ status: publicKeys.errorCode,
336
+ message: publicKeys.message || publicKeys.errorCode,
337
+ })
338
+ continue
339
+ }
340
+
341
+ let encryptedKey: Uint8Array
342
+ let encryptedKeyType: Folder.EncryptedKeyType
343
+ if (publicKeys.rsaPublicKey) {
344
+ const rsaPublicKeyBase64 = platform.bytesToBase64(publicKeys.rsaPublicKey)
345
+ encryptedKey = platform.publicEncrypt(sharedFolderKey, rsaPublicKeyBase64)
346
+ encryptedKeyType = Folder.EncryptedKeyType.encrypted_by_public_key
347
+ } else if (publicKeys.eccPublicKey) {
348
+ encryptedKey = await platform.publicEncryptEC(sharedFolderKey, publicKeys.eccPublicKey)
349
+ encryptedKeyType = Folder.EncryptedKeyType.encrypted_by_public_key_ecc
350
+ } else {
351
+ userResults.push({
352
+ email,
353
+ success: false,
354
+ status: ShareFolderUserResultStatus.MissingPublicKey,
355
+ message: `No usable public key for user "${email}" (folder="${resolved.displayName}")`,
356
+ })
357
+ continue
358
+ }
359
+
360
+ usersToAdd.push({
361
+ username: email,
362
+ manageRecords: toSetBoolean(newUserManageRecords),
363
+ manageUsers: toSetBoolean(newUserManageUsers),
364
+ typedSharedFolderKey: { encryptedKey, encryptedKeyType },
365
+ })
366
+ }
367
+
368
+ if (usersToAdd.length === 0 && usersToUpdate.length === 0) {
369
+ return {
370
+ success: false,
371
+ folderUid: resolved.folderUid,
372
+ sharedFolderUid: sharedFolder.uid,
373
+ folderKind: FolderKind.SharedFolder,
374
+ message: `No users could be processed for shared folder "${resolved.displayName}" (uid=${sharedFolder.uid}).`,
375
+ results: userResults,
376
+ }
377
+ }
378
+
379
+ const updateRequest: Folder.ISharedFolderUpdateV3Request = {
380
+ sharedFolderUid: normal64Bytes(sharedFolder.uid),
381
+ revision: sharedFolder.revision,
382
+ forceUpdate: false,
383
+ }
384
+ if (usersToAdd.length > 0) updateRequest.sharedFolderAddUser = usersToAdd
385
+ if (usersToUpdate.length > 0) updateRequest.sharedFolderUpdateUser = usersToUpdate
386
+
387
+ let response: Folder.ISharedFolderUpdateV3ResponseV2
388
+ try {
389
+ response = await auth.executeRest(sharedFolderUpdateV3Message({ sharedFoldersUpdateV3: [updateRequest] }))
390
+ } catch (err) {
391
+ return {
392
+ success: false,
393
+ folderUid: resolved.folderUid,
394
+ sharedFolderUid: sharedFolder.uid,
395
+ folderKind: FolderKind.SharedFolder,
396
+ message: `shared_folder_update_v3 (grant) failed for "${resolved.displayName}" (uid=${sharedFolder.uid}): ${extractErrorMessage(err)}`,
397
+ results: userResults,
398
+ }
399
+ }
400
+
401
+ const innerResponse = (response.sharedFoldersUpdateV3Response || [])[0]
402
+ const requestOk = !innerResponse?.status || innerResponse.status === FolderResultStatus.Success
403
+
404
+ for (const addUserStatus of innerResponse?.sharedFolderAddUserStatus || []) {
405
+ const status = addUserStatus.status || ShareFolderUserResultStatus.Unknown
406
+ const success = status === FolderResultStatus.Success || status === FolderResultStatus.Invited
407
+ userResults.push({
408
+ email: addUserStatus.username || '',
409
+ success,
410
+ status,
411
+ })
412
+ }
413
+ for (const updateUserStatus of innerResponse?.sharedFolderUpdateUserStatus || []) {
414
+ const status = updateUserStatus.status || ShareFolderUserResultStatus.Unknown
415
+ userResults.push({
416
+ email: updateUserStatus.username || '',
417
+ success: status === FolderResultStatus.Success,
418
+ status,
419
+ })
420
+ }
421
+
422
+ const allUsersOk = userResults.length > 0 && userResults.every((userResult) => userResult.success)
423
+
424
+ const failureReason = !requestOk
425
+ ? innerResponse?.status ||
426
+ `shared_folder_update_v3 (grant) failed for "${resolved.displayName}" (uid=${sharedFolder.uid}): server returned no status`
427
+ : undefined
428
+
429
+ return {
430
+ success: requestOk && allUsersOk,
431
+ folderUid: resolved.folderUid,
432
+ sharedFolderUid: sharedFolder.uid,
433
+ folderKind: FolderKind.SharedFolder,
434
+ message: failureReason,
435
+ results: userResults,
436
+ }
437
+ }
438
+
439
+ export async function shareFolder(
440
+ auth: Auth,
441
+ storage: InMemoryStorage,
442
+ input: ShareFolderInput
443
+ ): Promise<ShareFolderResult> {
444
+ const resolved = resolveFolder(storage, input.folder)
445
+ if (!resolved) {
446
+ throw new KeeperSdkError(`Folder "${input.folder}" was not found.`, 'folder_not_found')
447
+ }
448
+
449
+ if (resolved.kind === FolderKind.UserFolder) {
450
+ throw new KeeperSdkError(
451
+ `"${resolved.displayName}" is a personal folder. Only shared folders can be shared.`,
452
+ 'not_a_shared_folder'
453
+ )
454
+ }
455
+
456
+ return shareWithSharedFolder(auth, storage, resolved, input)
457
+ }