@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,373 @@
1
+ import type { Auth } from '@keeper-security/keeperapi'
2
+ import { isNumber, TOKEN_SEPARATOR_PATTERN } from '../utils'
3
+ import {
4
+ EnterpriseDataInclude,
5
+ EnterpriseDataManager,
6
+ type DecryptedRoleNames,
7
+ type EnterpriseDisplayNames,
8
+ type EnterpriseNode,
9
+ type EnterpriseRole,
10
+ type EnterpriseTeamRecord,
11
+ type EnterpriseTeamUserLink,
12
+ type EnterpriseUser,
13
+ type GetEnterpriseDataResponse,
14
+ } from './enterpriseData'
15
+
16
+ export enum TeamColumn {
17
+ Restricts = 'restricts',
18
+ Node = 'node',
19
+ UserCount = 'user_count',
20
+ Users = 'users',
21
+ RoleCount = 'role_count',
22
+ Roles = 'roles',
23
+ }
24
+
25
+ export type TeamColumnInput = TeamColumn | `${TeamColumn}`
26
+
27
+ export const SUPPORTED_TEAM_COLUMNS: readonly TeamColumn[] = Object.values(TeamColumn)
28
+
29
+ export const DEFAULT_TEAM_COLUMNS: readonly TeamColumn[] = [
30
+ TeamColumn.Restricts,
31
+ TeamColumn.Node,
32
+ TeamColumn.UserCount,
33
+ TeamColumn.RoleCount,
34
+ ]
35
+
36
+ const NODE_PATH_SEPARATOR = '\\'
37
+ const MIN_ASCII_COL_WIDTH = 2
38
+ const ALL_COLUMNS_WILDCARD = '*'
39
+
40
+ const HEADER_BY_COLUMN: Record<TeamColumn, string> = {
41
+ [TeamColumn.Restricts]: 'Restricts',
42
+ [TeamColumn.Node]: 'Node',
43
+ [TeamColumn.UserCount]: 'User Count',
44
+ [TeamColumn.Users]: 'Users',
45
+ [TeamColumn.RoleCount]: 'Role Count',
46
+ [TeamColumn.Roles]: 'Roles',
47
+ }
48
+
49
+ export type ListTeamsOptions = {
50
+ pattern?: string | null
51
+ columns?: TeamColumnInput[] | typeof ALL_COLUMNS_WILDCARD | string | null
52
+ }
53
+
54
+ export type ListTeamRow = {
55
+ team_uid: string
56
+ name: string
57
+ restricts?: string
58
+ node?: string
59
+ user_count?: number
60
+ users?: string[]
61
+ role_count?: number
62
+ roles?: string[]
63
+ }
64
+
65
+ export type FormattedTeamsTable = {
66
+ headers: string[]
67
+ rows: string[][]
68
+ }
69
+
70
+ export type FormatTeamsTableOptions = {
71
+ columns?: ListTeamsOptions['columns']
72
+ }
73
+
74
+ type DecorateContext = {
75
+ nodePaths: Map<number, string>
76
+ teamUsers: Map<string, Set<number>>
77
+ roleTeams: Map<string, Set<number>>
78
+ usernameById: Map<number, string>
79
+ roleNameById: Map<number, string>
80
+ }
81
+
82
+ export function formatTeamRestricts(team: EnterpriseTeamRecord): string {
83
+ const r = team.restrict_view === true ? 'R ' : ' '
84
+ const w = team.restrict_edit === true ? 'W ' : ' '
85
+ const restrictShare = team.restrict_share === true || team.restrict_sharing === true
86
+ const s = restrictShare ? 'S' : ' '
87
+ return r + w + s
88
+ }
89
+
90
+ export async function listTeams(auth: Auth, options: ListTeamsOptions = {}): Promise<ListTeamRow[]> {
91
+ const columns = resolveColumns(options.columns)
92
+ const includes = includesForColumns(columns)
93
+ const wantsDisplayNames = columns.includes(TeamColumn.Node) || columns.includes(TeamColumn.Roles)
94
+
95
+ const enterpriseData = new EnterpriseDataManager(auth)
96
+ const emptyDisplayNames: EnterpriseDisplayNames = { nodes: new Map(), roles: new Map() }
97
+ const [response, displayNames] = await Promise.all([
98
+ enterpriseData.getData(includes),
99
+ wantsDisplayNames ? enterpriseData.getDisplayNames() : Promise.resolve(emptyDisplayNames),
100
+ ])
101
+
102
+ const teams = response.teams || []
103
+ const nodes = response.nodes || []
104
+ applyDecryptedNodeNames(nodes, displayNames.nodes)
105
+
106
+ const context: DecorateContext = {
107
+ nodePaths: buildNodePathLookup(nodes),
108
+ teamUsers: buildTeamUserMap(response.team_users || []),
109
+ roleTeams: buildRoleTeamMap(response),
110
+ usernameById: buildUserUsernameMap(response.users || []),
111
+ roleNameById: buildRoleNameMap(response.roles || [], displayNames.roles),
112
+ }
113
+
114
+ const pattern = options.pattern?.trim() || null
115
+ const rows: ListTeamRow[] = []
116
+ for (const team of teams) {
117
+ const row: ListTeamRow = {
118
+ team_uid: team.team_uid,
119
+ name: teamDisplayName(team),
120
+ }
121
+ decorateRow(row, team, columns, context)
122
+ if (pattern && !rowMatchesPattern(row, pattern)) continue
123
+ rows.push(row)
124
+ }
125
+
126
+ rows.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }))
127
+ return rows
128
+ }
129
+
130
+ export function formatTeamsTable(
131
+ rows: ListTeamRow[],
132
+ options: FormatTeamsTableOptions = {}
133
+ ): FormattedTeamsTable {
134
+ const columns = resolveColumns(options.columns)
135
+ const headers: string[] = ['#', 'Team UID', 'Name', ...columns.map((column) => HEADER_BY_COLUMN[column])]
136
+
137
+ const outRows: string[][] = rows.map((row, rowIndex) => {
138
+ const cells: string[] = [String(rowIndex + 1), row.team_uid, row.name]
139
+ for (const column of columns) cells.push(formatCell(row, column))
140
+ return cells
141
+ })
142
+
143
+ return { headers, rows: outRows }
144
+ }
145
+
146
+ export function renderTeamsAsciiTable(
147
+ table: FormattedTeamsTable,
148
+ options: { minColWidth?: number } = {}
149
+ ): string {
150
+ const { minColWidth = MIN_ASCII_COL_WIDTH } = options
151
+ const { headers, rows } = table
152
+ const columnCount = headers.length
153
+
154
+ const expandedRows: string[][][] = rows.map((row) =>
155
+ row.map((cell) => (cell.includes('\n') ? cell.split('\n') : [cell]))
156
+ )
157
+
158
+ const columnWidths = new Array<number>(columnCount).fill(0)
159
+ for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
160
+ columnWidths[columnIndex] = Math.max(headers[columnIndex].length, minColWidth)
161
+ }
162
+ for (const row of expandedRows) {
163
+ for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
164
+ for (const line of row[columnIndex]) {
165
+ columnWidths[columnIndex] = Math.max(columnWidths[columnIndex], line.length, minColWidth)
166
+ }
167
+ }
168
+ }
169
+
170
+ const padCell = (cell: string, columnIndex: number): string =>
171
+ cell + ' '.repeat(columnWidths[columnIndex] - cell.length)
172
+ const formatPhysicalRow = (cells: string[]): string =>
173
+ cells.map((cell, columnIndex) => padCell(cell, columnIndex)).join(' ')
174
+
175
+ const ruleRow = formatPhysicalRow(columnWidths.map((width) => '-'.repeat(width)))
176
+ const lines: string[] = [formatPhysicalRow(headers), ruleRow]
177
+
178
+ for (const row of expandedRows) {
179
+ const physicalLineCount = Math.max(...row.map((cell) => cell.length))
180
+ for (let lineIndex = 0; lineIndex < physicalLineCount; lineIndex += 1) {
181
+ const physicalCells: string[] = row.map((cell) => cell[lineIndex] ?? '')
182
+ lines.push(formatPhysicalRow(physicalCells))
183
+ }
184
+ }
185
+ return lines.join('\n')
186
+ }
187
+
188
+ function includesForColumns(columns: readonly TeamColumn[]): EnterpriseDataInclude[] {
189
+ const set = new Set<EnterpriseDataInclude>([EnterpriseDataInclude.Teams])
190
+ for (const column of columns) {
191
+ switch (column) {
192
+ case TeamColumn.Node:
193
+ set.add(EnterpriseDataInclude.Nodes)
194
+ break
195
+ case TeamColumn.UserCount:
196
+ case TeamColumn.Users:
197
+ set.add(EnterpriseDataInclude.TeamUsers)
198
+ if (column === TeamColumn.Users) set.add(EnterpriseDataInclude.Users)
199
+ break
200
+ case TeamColumn.RoleCount:
201
+ case TeamColumn.Roles:
202
+ set.add(EnterpriseDataInclude.RoleTeams)
203
+ if (column === TeamColumn.Roles) set.add(EnterpriseDataInclude.Roles)
204
+ break
205
+ }
206
+ }
207
+ return Array.from(set)
208
+ }
209
+
210
+ function resolveColumns(input: ListTeamsOptions['columns']): TeamColumn[] {
211
+ if (input == null) return [...DEFAULT_TEAM_COLUMNS]
212
+ if (input === ALL_COLUMNS_WILDCARD) return [...SUPPORTED_TEAM_COLUMNS]
213
+
214
+ const requested = Array.isArray(input) ? input : input.split(',').map((part) => part.trim())
215
+ const allowed = new Set<string>(SUPPORTED_TEAM_COLUMNS)
216
+ const seen = new Set<TeamColumn>()
217
+ for (const column of requested) {
218
+ if (column && allowed.has(column)) seen.add(column as TeamColumn)
219
+ }
220
+ if (seen.size === 0) return [...DEFAULT_TEAM_COLUMNS]
221
+ return SUPPORTED_TEAM_COLUMNS.filter((column) => seen.has(column))
222
+ }
223
+
224
+ function teamDisplayName(team: { name?: string; team_uid: string }): string {
225
+ return (team.name || team.team_uid || '').trim() || team.team_uid
226
+ }
227
+
228
+ function tokenize(text: string): string[] {
229
+ return text.split(TOKEN_SEPARATOR_PATTERN).filter((token) => token.length > 0)
230
+ }
231
+
232
+ function rowMatchesPattern(row: ListTeamRow, pattern: string): boolean {
233
+ const lowered = pattern.toLowerCase()
234
+ const tokens: string[] = []
235
+ tokens.push(row.team_uid.toLowerCase())
236
+ tokens.push(...tokenize(row.name.toLowerCase()))
237
+ if (row.restricts) tokens.push(row.restricts.toLowerCase())
238
+ if (row.node) tokens.push(...tokenize(row.node.toLowerCase()))
239
+ if (row.user_count != null) tokens.push(String(row.user_count))
240
+ if (row.role_count != null) tokens.push(String(row.role_count))
241
+ for (const list of [row.users, row.roles]) {
242
+ if (!list) continue
243
+ for (const value of list) tokens.push(...tokenize(value.toLowerCase()))
244
+ }
245
+ return tokens.some((token) => token.includes(lowered))
246
+ }
247
+
248
+ function applyDecryptedNodeNames(nodes: EnterpriseNode[], decrypted: Map<number, string>): void {
249
+ if (decrypted.size === 0) return
250
+ for (const node of nodes) {
251
+ const display = decrypted.get(node.node_id)
252
+ if (display) node.displayName = display
253
+ }
254
+ }
255
+
256
+ function buildNodePathLookup(nodes: EnterpriseNode[]): Map<number, string> {
257
+ return new Map(
258
+ nodes.map((node) => [
259
+ node.node_id,
260
+ EnterpriseDataManager.getNodePath(nodes, node.node_id, { separator: NODE_PATH_SEPARATOR }),
261
+ ])
262
+ )
263
+ }
264
+
265
+ function buildTeamUserMap(links: EnterpriseTeamUserLink[]): Map<string, Set<number>> {
266
+ const byTeam = new Map<string, Set<number>>()
267
+ for (const link of links) {
268
+ if (!link.team_uid) continue
269
+ const set = byTeam.get(link.team_uid)
270
+ if (set) set.add(link.enterprise_user_id)
271
+ else byTeam.set(link.team_uid, new Set([link.enterprise_user_id]))
272
+ }
273
+ return byTeam
274
+ }
275
+
276
+ function buildRoleTeamMap(response: GetEnterpriseDataResponse): Map<string, Set<number>> {
277
+ const map = new Map<string, Set<number>>()
278
+ for (const link of response.role_teams || []) {
279
+ if (!link.team_uid) continue
280
+ const set = map.get(link.team_uid)
281
+ if (set) set.add(link.role_id)
282
+ else map.set(link.team_uid, new Set([link.role_id]))
283
+ }
284
+ return map
285
+ }
286
+
287
+ function buildUserUsernameMap(users: EnterpriseUser[]): Map<number, string> {
288
+ const map = new Map<number, string>()
289
+ for (const user of users) {
290
+ if (isNumber(user.enterprise_user_id ) && user.username) {
291
+ map.set(user.enterprise_user_id, user.username)
292
+ }
293
+ }
294
+ return map
295
+ }
296
+
297
+ function buildRoleNameMap(roles: EnterpriseRole[], decrypted: DecryptedRoleNames): Map<number, string> {
298
+ const map = new Map<number, string>()
299
+ for (const role of roles) {
300
+ if (isNumber(role.role_id)) continue
301
+ const display = (role.displayName || decrypted.get(role.role_id) || '').trim()
302
+ map.set(role.role_id, display || String(role.role_id))
303
+ }
304
+ return map
305
+ }
306
+
307
+ function resolveSortedNames(ids: Set<number>, nameById: Map<number, string>): string[] {
308
+ const result: string[] = []
309
+ for (const id of ids) {
310
+ const name = nameById.get(id)
311
+ if (name) result.push(name)
312
+ }
313
+ result.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
314
+ return result
315
+ }
316
+
317
+ function decorateRow(
318
+ row: ListTeamRow,
319
+ team: EnterpriseTeamRecord,
320
+ columns: readonly TeamColumn[],
321
+ context: DecorateContext
322
+ ): void {
323
+ const { nodePaths, teamUsers, roleTeams, usernameById, roleNameById } = context
324
+
325
+ for (const column of columns) {
326
+ switch (column) {
327
+ case TeamColumn.Restricts:
328
+ row.restricts = formatTeamRestricts(team)
329
+ break
330
+ case TeamColumn.Node:
331
+ row.node = nodePaths.get(team.node_id) || ''
332
+ break
333
+ case TeamColumn.UserCount:
334
+ row.user_count = teamUsers.get(team.team_uid)?.size ?? 0
335
+ break
336
+ case TeamColumn.Users: {
337
+ const ids = teamUsers.get(team.team_uid)
338
+ row.users = ids ? resolveSortedNames(ids, usernameById) : []
339
+ break
340
+ }
341
+ case TeamColumn.RoleCount:
342
+ row.role_count = roleTeams.get(team.team_uid)?.size ?? 0
343
+ break
344
+ case TeamColumn.Roles: {
345
+ const ids = roleTeams.get(team.team_uid)
346
+ row.roles = ids ? resolveSortedNames(ids, roleNameById) : []
347
+ break
348
+ }
349
+ }
350
+ }
351
+ }
352
+
353
+ function formatListCell(values: string[] | undefined): string {
354
+ if (!values || values.length === 0) return ''
355
+ return values.join('\n')
356
+ }
357
+
358
+ function formatCell(row: ListTeamRow, column: TeamColumn): string {
359
+ switch (column) {
360
+ case TeamColumn.Restricts:
361
+ return row.restricts ?? ''
362
+ case TeamColumn.Node:
363
+ return row.node ?? ''
364
+ case TeamColumn.UserCount:
365
+ return row.user_count == null ? '' : String(row.user_count)
366
+ case TeamColumn.Users:
367
+ return formatListCell(row.users)
368
+ case TeamColumn.RoleCount:
369
+ return row.role_count == null ? '' : String(row.role_count)
370
+ case TeamColumn.Roles:
371
+ return formatListCell(row.roles)
372
+ }
373
+ }
@@ -0,0 +1,248 @@
1
+ import type { Auth } from '@keeper-security/keeperapi'
2
+ import { KeeperSdkError, ResultCodes } from '../utils'
3
+ import {
4
+ EnterpriseDataInclude,
5
+ EnterpriseDataManager,
6
+ type EnterpriseDisplayNames,
7
+ type EnterpriseNode,
8
+ type EnterpriseRoleTeamLink,
9
+ type EnterpriseTeamRecord,
10
+ type EnterpriseTeamUserLink,
11
+ type EnterpriseUser,
12
+ } from './enterpriseData'
13
+
14
+ const NODE_PATH_SEPARATOR = '\\'
15
+ const VIEW_TEAM_INCLUDES: EnterpriseDataInclude[] = [
16
+ EnterpriseDataInclude.Teams,
17
+ EnterpriseDataInclude.TeamUsers,
18
+ EnterpriseDataInclude.RoleTeams,
19
+ EnterpriseDataInclude.Roles,
20
+ EnterpriseDataInclude.Users,
21
+ EnterpriseDataInclude.Nodes,
22
+ ]
23
+
24
+ export type TeamRoleInfo = {
25
+ role_id: number
26
+ role_name: string
27
+ }
28
+
29
+ export type TeamUserInfo = {
30
+ enterprise_user_id: number
31
+ username: string
32
+ }
33
+
34
+ export type TeamView = {
35
+ team_uid: string
36
+ team_name: string
37
+ node_id: number
38
+ node_name: string
39
+ restrict_view: boolean
40
+ restrict_edit: boolean
41
+ restrict_share: boolean
42
+ team_roles?: TeamRoleInfo[]
43
+ team_users?: TeamUserInfo[]
44
+ }
45
+
46
+ export type FormatTeamViewOptions = {
47
+ verbose?: boolean
48
+ }
49
+
50
+ export type TeamViewTableRow = {
51
+ label: string
52
+ value: string | string[]
53
+ id?: number | number[]
54
+ }
55
+
56
+ export type FormattedTeamViewTable = {
57
+ rows: TeamViewTableRow[]
58
+ hasIdColumn: boolean
59
+ }
60
+
61
+ export async function viewTeam(auth: Auth, identifier: string): Promise<TeamView> {
62
+ const enterpriseData = new EnterpriseDataManager(auth)
63
+ const [response, displayNames] = await Promise.all([
64
+ enterpriseData.getData(VIEW_TEAM_INCLUDES),
65
+ enterpriseData.getDisplayNames(),
66
+ ])
67
+
68
+ const team = resolveTeam(response.teams || [], identifier)
69
+ const nodePath = resolveNodePath(response.nodes || [], displayNames, team.node_id)
70
+ const teamUsers = buildTeamUsers(response.users || [], response.team_users || [], team.team_uid)
71
+ const teamRoles = buildTeamRoles(response.role_teams || [], displayNames.roles, team.team_uid)
72
+
73
+ const view: TeamView = {
74
+ team_uid: team.team_uid,
75
+ team_name: team.name,
76
+ node_id: team.node_id,
77
+ node_name: nodePath,
78
+ restrict_view: team.restrict_view === true,
79
+ restrict_edit: team.restrict_edit === true,
80
+ restrict_share: team.restrict_share === true || team.restrict_sharing === true,
81
+ }
82
+ if (teamRoles.length > 0) view.team_roles = teamRoles
83
+ if (teamUsers.length > 0) view.team_users = teamUsers
84
+ return view
85
+ }
86
+
87
+ export function formatTeamView(view: TeamView, options: FormatTeamViewOptions = {}): FormattedTeamViewTable {
88
+ const verbose = options.verbose === true
89
+ const rows: TeamViewTableRow[] = [
90
+ { label: 'Team UID', value: view.team_uid },
91
+ { label: 'Team Name', value: view.team_name },
92
+ {
93
+ label: 'Node Name',
94
+ value: view.node_name,
95
+ id: verbose ? view.node_id : undefined,
96
+ },
97
+ { label: 'Restrict Edit', value: boolText(view.restrict_edit) },
98
+ { label: 'Restrict Share', value: boolText(view.restrict_share) },
99
+ { label: 'Restrict View', value: boolText(view.restrict_view) },
100
+ ]
101
+
102
+ if (view.team_roles && view.team_roles.length > 0) {
103
+ rows.push({
104
+ label: 'Role(s)',
105
+ value: view.team_roles.map((role) => role.role_name),
106
+ id: verbose ? view.team_roles.map((role) => role.role_id) : undefined,
107
+ })
108
+ }
109
+
110
+ if (view.team_users && view.team_users.length > 0) {
111
+ rows.push({
112
+ label: 'User(s)',
113
+ value: view.team_users.map((user) => user.username),
114
+ id: verbose ? view.team_users.map((user) => user.enterprise_user_id) : undefined,
115
+ })
116
+ }
117
+
118
+ return { rows, hasIdColumn: verbose }
119
+ }
120
+
121
+ export function teamViewTable(table: FormattedTeamViewTable): string {
122
+ const labelWidth = table.rows.reduce((max, row) => Math.max(max, row.label.length), 0)
123
+ const expandedRows = table.rows.map((row) => ({
124
+ label: row.label,
125
+ valueLines: asLines(row.value),
126
+ idLines: table.hasIdColumn ? asIdLines(row.id) : [],
127
+ }))
128
+
129
+ const valueWidth = expandedRows.reduce(
130
+ (max, row) => Math.max(max, ...row.valueLines.map((line) => line.length)),
131
+ 0
132
+ )
133
+ const idWidth = table.hasIdColumn
134
+ ? expandedRows.reduce((max, row) => Math.max(max, ...row.idLines.map((line) => line.length)), 0)
135
+ : 0
136
+
137
+ const padRight = (text: string, width: number): string =>
138
+ text + ' '.repeat(Math.max(0, width - text.length))
139
+ const padLeft = (text: string, width: number): string =>
140
+ ' '.repeat(Math.max(0, width - text.length)) + text
141
+
142
+ const lines: string[] = []
143
+ for (const row of expandedRows) {
144
+ const lineCount = Math.max(row.valueLines.length, table.hasIdColumn ? row.idLines.length : 0, 1)
145
+ for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
146
+ const isFirstLine = lineIndex === 0
147
+ const labelCell = padLeft(isFirstLine ? row.label : '', labelWidth)
148
+ const valueCell = padRight(row.valueLines[lineIndex] ?? '', valueWidth)
149
+ const cells: string[] = [labelCell, valueCell]
150
+ if (table.hasIdColumn) {
151
+ const idCell = padRight(row.idLines[lineIndex] ?? '', idWidth)
152
+ cells.push(idCell)
153
+ }
154
+ lines.push(cells.join(' ').trimEnd())
155
+ }
156
+ }
157
+ return lines.join('\n')
158
+ }
159
+
160
+ function resolveTeam(teams: EnterpriseTeamRecord[], identifier: string): EnterpriseTeamRecord {
161
+ const trimmed = (identifier ?? '').trim()
162
+ if (!trimmed) {
163
+ throw new KeeperSdkError('Team name or UID is required.', ResultCodes.TEAM_REQUIRED)
164
+ }
165
+
166
+ const uidMatch = teams.find((team) => team.team_uid === trimmed)
167
+ if (uidMatch) return uidMatch
168
+
169
+ const lowered = trimmed.toLowerCase()
170
+ const nameMatches = teams.filter((team) => (team.name || '').trim().toLowerCase() === lowered)
171
+ if (nameMatches.length === 1) return nameMatches[0]
172
+ if (nameMatches.length > 1) {
173
+ throw new KeeperSdkError(
174
+ `Multiple teams match name "${trimmed}". Specify the team UID instead.`,
175
+ ResultCodes.MULTIPLE_TEAM_MATCHES
176
+ )
177
+ }
178
+ throw new KeeperSdkError(`Team "${trimmed}" does not exist.`, ResultCodes.TEAM_NOT_FOUND)
179
+ }
180
+
181
+ function resolveNodePath(
182
+ nodes: EnterpriseNode[],
183
+ displayNames: EnterpriseDisplayNames,
184
+ nodeId: number
185
+ ): string {
186
+ if (displayNames.nodes.size > 0) {
187
+ for (const node of nodes) {
188
+ const display = displayNames.nodes.get(node.node_id)
189
+ if (display) node.displayName = display
190
+ }
191
+ }
192
+ return EnterpriseDataManager.getNodePath(nodes, nodeId, { omitRoot: false, separator: NODE_PATH_SEPARATOR })
193
+ }
194
+
195
+ function buildTeamUsers(
196
+ users: EnterpriseUser[],
197
+ teamUserLinks: EnterpriseTeamUserLink[],
198
+ teamUid: string
199
+ ): TeamUserInfo[] {
200
+ const userById = new Map<number, EnterpriseUser>()
201
+ for (const user of users) userById.set(user.enterprise_user_id, user)
202
+
203
+ const memberIds = new Set<number>()
204
+ for (const link of teamUserLinks) {
205
+ if (link.team_uid === teamUid) memberIds.add(link.enterprise_user_id)
206
+ }
207
+
208
+ const result: TeamUserInfo[] = []
209
+ for (const id of memberIds) {
210
+ const user = userById.get(id)
211
+ if (user && user.username) result.push({ enterprise_user_id: id, username: user.username })
212
+ }
213
+ result.sort((a, b) => a.username.localeCompare(b.username, undefined, { sensitivity: 'base' }))
214
+ return result
215
+ }
216
+
217
+ function buildTeamRoles(
218
+ roleTeamLinks: EnterpriseRoleTeamLink[],
219
+ decryptedRoleNames: Map<number, string>,
220
+ teamUid: string
221
+ ): TeamRoleInfo[] {
222
+ const roleIds = new Set<number>()
223
+ for (const link of roleTeamLinks) {
224
+ if (link.team_uid === teamUid) roleIds.add(link.role_id)
225
+ }
226
+
227
+ const result: TeamRoleInfo[] = []
228
+ for (const id of roleIds) {
229
+ result.push({ role_id: id, role_name: decryptedRoleNames.get(id) || String(id) })
230
+ }
231
+ result.sort((a, b) => a.role_name.localeCompare(b.role_name, undefined, { sensitivity: 'base' }))
232
+ return result
233
+ }
234
+
235
+ function boolText(value: boolean): string {
236
+ return value ? 'True' : 'False'
237
+ }
238
+
239
+ function asLines(value: string | string[]): string[] {
240
+ if (Array.isArray(value)) return value.length > 0 ? value : ['']
241
+ return [value]
242
+ }
243
+
244
+ function asIdLines(id: number | number[] | undefined): string[] {
245
+ if (id == null) return []
246
+ if (Array.isArray(id)) return id.length > 0 ? id.map(String) : ['']
247
+ return [String(id)]
248
+ }
@@ -0,0 +1,71 @@
1
+ export enum LogLevel {
2
+ DEBUG = 0,
3
+ INFO = 1,
4
+ WARN = 2,
5
+ ERROR = 3,
6
+ NONE = 4,
7
+ }
8
+
9
+ export interface ILogger {
10
+ debug(...args: unknown[]): void
11
+ info(...args: unknown[]): void
12
+ warn(...args: unknown[]): void
13
+ error(...args: unknown[]): void
14
+ }
15
+
16
+ export class ConsoleLogger implements ILogger {
17
+ private level: LogLevel
18
+
19
+ constructor(level: LogLevel = LogLevel.INFO) {
20
+ this.level = level
21
+ }
22
+
23
+ public setLevel(level: LogLevel): void {
24
+ this.level = level
25
+ }
26
+
27
+ public getLevel(): LogLevel {
28
+ return this.level
29
+ }
30
+
31
+ public debug(...args: unknown[]): void {
32
+ if (this.level <= LogLevel.DEBUG) console.debug(...args)
33
+ }
34
+
35
+ public info(...args: unknown[]): void {
36
+ if (this.level <= LogLevel.INFO) console.log(...args)
37
+ }
38
+
39
+ public warn(...args: unknown[]): void {
40
+ if (this.level <= LogLevel.WARN) console.warn(...args)
41
+ }
42
+
43
+ public error(...args: unknown[]): void {
44
+ if (this.level <= LogLevel.ERROR) console.error(...args)
45
+ }
46
+ }
47
+
48
+ let globalLogger: ILogger = new ConsoleLogger()
49
+
50
+ export function setLogger(newLogger: ILogger): void {
51
+ globalLogger = newLogger
52
+ }
53
+
54
+ export function getLogger(): ILogger {
55
+ return globalLogger
56
+ }
57
+
58
+ export function resetLogger(level: LogLevel = LogLevel.INFO): ConsoleLogger {
59
+ const c = new ConsoleLogger(level)
60
+ globalLogger = c
61
+ return c
62
+ }
63
+
64
+ export const logger: ILogger = {
65
+ debug: (...args) => globalLogger.debug(...args),
66
+ info: (...args) => globalLogger.info(...args),
67
+ warn: (...args) => globalLogger.warn(...args),
68
+ error: (...args) => globalLogger.error(...args),
69
+ }
70
+
71
+ export { ConsoleLogger as Logger }