@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.
- package/dist/auth/ConsoleAuthUI.d.ts +10 -0
- package/dist/auth/ConsoleAuthUI.js +152 -0
- package/dist/auth/ConsoleAuthUI.js.map +1 -0
- package/dist/auth/ConsoleLogin.d.ts +8 -0
- package/dist/auth/ConsoleLogin.js +266 -0
- package/dist/auth/ConsoleLogin.js.map +1 -0
- package/dist/auth/SessionManager.d.ts +66 -0
- package/dist/auth/SessionManager.js +211 -0
- package/dist/auth/SessionManager.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/records/RecordOperations.d.ts +79 -0
- package/dist/records/RecordOperations.js +346 -0
- package/dist/records/RecordOperations.js.map +1 -0
- package/dist/records/RecordUtils.d.ts +36 -0
- package/dist/records/RecordUtils.js +224 -0
- package/dist/records/RecordUtils.js.map +1 -0
- package/dist/sharing/Sharing.d.ts +27 -0
- package/dist/sharing/Sharing.js +125 -0
- package/dist/sharing/Sharing.js.map +1 -0
- package/dist/src/auth/ConsoleAuthUI.d.ts +10 -0
- package/dist/src/auth/ConsoleAuthUI.js +161 -0
- package/dist/src/auth/ConsoleAuthUI.js.map +1 -0
- package/dist/src/auth/ConsoleLogin.d.ts +8 -0
- package/dist/src/auth/ConsoleLogin.js +311 -0
- package/dist/src/auth/ConsoleLogin.js.map +1 -0
- package/dist/src/auth/SessionManager.d.ts +67 -0
- package/dist/src/auth/SessionManager.js +212 -0
- package/dist/src/auth/SessionManager.js.map +1 -0
- package/dist/src/folders/FolderManager.d.ts +57 -0
- package/dist/src/folders/FolderManager.js +108 -0
- package/dist/src/folders/FolderManager.js.map +1 -0
- package/dist/src/folders/addFolder.d.ts +32 -0
- package/dist/src/folders/addFolder.js +207 -0
- package/dist/src/folders/addFolder.js.map +1 -0
- package/dist/src/folders/changeDirectory.d.ts +19 -0
- package/dist/src/folders/changeDirectory.js +171 -0
- package/dist/src/folders/changeDirectory.js.map +1 -0
- package/dist/src/folders/deleteFolder.d.ts +17 -0
- package/dist/src/folders/deleteFolder.js +237 -0
- package/dist/src/folders/deleteFolder.js.map +1 -0
- package/dist/src/folders/folderHelpers.d.ts +48 -0
- package/dist/src/folders/folderHelpers.js +100 -0
- package/dist/src/folders/folderHelpers.js.map +1 -0
- package/dist/src/folders/folderTree.d.ts +29 -0
- package/dist/src/folders/folderTree.js +250 -0
- package/dist/src/folders/folderTree.js.map +1 -0
- package/dist/src/folders/getFolder.d.ts +56 -0
- package/dist/src/folders/getFolder.js +143 -0
- package/dist/src/folders/getFolder.js.map +1 -0
- package/dist/src/folders/listFolder.d.ts +48 -0
- package/dist/src/folders/listFolder.js +276 -0
- package/dist/src/folders/listFolder.js.map +1 -0
- package/dist/src/folders/updateFolder.d.ts +31 -0
- package/dist/src/folders/updateFolder.js +137 -0
- package/dist/src/folders/updateFolder.js.map +1 -0
- package/dist/src/index.d.ts +49 -0
- package/dist/src/index.js +151 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/records/RecordOperations.d.ts +80 -0
- package/dist/src/records/RecordOperations.js +356 -0
- package/dist/src/records/RecordOperations.js.map +1 -0
- package/dist/src/records/RecordUtils.d.ts +37 -0
- package/dist/src/records/RecordUtils.js +263 -0
- package/dist/src/records/RecordUtils.js.map +1 -0
- package/dist/src/records/Totp.d.ts +14 -0
- package/dist/src/records/Totp.js +111 -0
- package/dist/src/records/Totp.js.map +1 -0
- package/dist/src/sharedFolders/SharedFolderManager.d.ts +20 -0
- package/dist/src/sharedFolders/SharedFolderManager.js +33 -0
- package/dist/src/sharedFolders/SharedFolderManager.js.map +1 -0
- package/dist/src/sharedFolders/listSharedFolders.d.ts +29 -0
- package/dist/src/sharedFolders/listSharedFolders.js +127 -0
- package/dist/src/sharedFolders/listSharedFolders.js.map +1 -0
- package/dist/src/sharedFolders/shareFolder.d.ts +36 -0
- package/dist/src/sharedFolders/shareFolder.js +352 -0
- package/dist/src/sharedFolders/shareFolder.js.map +1 -0
- package/dist/src/sharing/Sharing.d.ts +50 -0
- package/dist/src/sharing/Sharing.js +195 -0
- package/dist/src/sharing/Sharing.js.map +1 -0
- package/dist/src/storage/InMemoryStorage.d.ts +24 -0
- package/dist/src/storage/InMemoryStorage.js +139 -0
- package/dist/src/storage/InMemoryStorage.js.map +1 -0
- package/dist/src/teams/TeamManager.d.ts +17 -0
- package/dist/src/teams/TeamManager.js +38 -0
- package/dist/src/teams/TeamManager.js.map +1 -0
- package/dist/src/teams/enterpriseData.d.ts +106 -0
- package/dist/src/teams/enterpriseData.js +319 -0
- package/dist/src/teams/enterpriseData.js.map +1 -0
- package/dist/src/teams/listTeams.d.ts +42 -0
- package/dist/src/teams/listTeams.js +308 -0
- package/dist/src/teams/listTeams.js.map +1 -0
- package/dist/src/teams/viewTeam.d.ts +35 -0
- package/dist/src/teams/viewTeam.js +177 -0
- package/dist/src/teams/viewTeam.js.map +1 -0
- package/dist/src/utils/Logger.d.ts +28 -0
- package/dist/src/utils/Logger.js +62 -0
- package/dist/src/utils/Logger.js.map +1 -0
- package/dist/src/utils/constants.d.ts +50 -0
- package/dist/src/utils/constants.js +64 -0
- package/dist/src/utils/constants.js.map +1 -0
- package/dist/src/utils/errors.d.ts +10 -0
- package/dist/src/utils/errors.js +117 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/guards.d.ts +7 -0
- package/dist/src/utils/guards.js +29 -0
- package/dist/src/utils/guards.js.map +1 -0
- package/dist/src/utils/index.d.ts +7 -0
- package/dist/src/utils/index.js +39 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/patterns.d.ts +9 -0
- package/dist/src/utils/patterns.js +20 -0
- package/dist/src/utils/patterns.js.map +1 -0
- package/dist/src/utils/types.d.ts +12 -0
- package/dist/src/utils/types.js +3 -0
- package/dist/src/utils/types.js.map +1 -0
- package/dist/src/vault/KeeperVault.d.ts +116 -0
- package/dist/src/vault/KeeperVault.js +443 -0
- package/dist/src/vault/KeeperVault.js.map +1 -0
- package/dist/storage/InMemoryStorage.d.ts +24 -0
- package/dist/storage/InMemoryStorage.js +132 -0
- package/dist/storage/InMemoryStorage.js.map +1 -0
- package/dist/utils/Logger.d.ts +28 -0
- package/dist/utils/Logger.js +62 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/constants.d.ts +26 -0
- package/dist/utils/constants.js +37 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/errors.d.ts +10 -0
- package/dist/utils/errors.js +117 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +22 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/vault/KeeperVault.d.ts +72 -0
- package/dist/vault/KeeperVault.js +338 -0
- package/dist/vault/KeeperVault.js.map +1 -0
- package/package.json +32 -0
- package/src/auth/ConsoleAuthUI.ts +169 -0
- package/src/auth/ConsoleLogin.ts +351 -0
- package/src/auth/SessionManager.ts +293 -0
- package/src/folders/FolderManager.ts +174 -0
- package/src/folders/addFolder.ts +294 -0
- package/src/folders/changeDirectory.ts +217 -0
- package/src/folders/deleteFolder.ts +293 -0
- package/src/folders/folderHelpers.ts +99 -0
- package/src/folders/folderTree.ts +321 -0
- package/src/folders/getFolder.ts +234 -0
- package/src/folders/listFolder.ts +358 -0
- package/src/folders/updateFolder.ts +210 -0
- package/src/index.ts +242 -0
- package/src/records/RecordOperations.ts +549 -0
- package/src/records/RecordUtils.ts +282 -0
- package/src/records/Totp.ts +119 -0
- package/src/sharedFolders/SharedFolderManager.ts +57 -0
- package/src/sharedFolders/listSharedFolders.ts +173 -0
- package/src/sharedFolders/shareFolder.ts +457 -0
- package/src/sharing/Sharing.ts +282 -0
- package/src/storage/InMemoryStorage.ts +163 -0
- package/src/teams/TeamManager.ts +61 -0
- package/src/teams/enterpriseData.ts +453 -0
- package/src/teams/listTeams.ts +373 -0
- package/src/teams/viewTeam.ts +248 -0
- package/src/utils/Logger.ts +71 -0
- package/src/utils/constants.ts +63 -0
- package/src/utils/errors.ts +108 -0
- package/src/utils/guards.ts +24 -0
- package/src/utils/index.ts +22 -0
- package/src/utils/patterns.ts +20 -0
- package/src/utils/types.ts +11 -0
- package/src/vault/KeeperVault.ts +612 -0
- 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
|
+
}
|