Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions KeeperSdk/src/account/whoamiInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type { AccountSummary } from '@keeper-security/keeperapi'
import type { VaultSummary } from '../vault/KeeperVault'
import { KEEPER_PUBLIC_HOSTS } from '../utils/constants'

export type WhoamiInfo = {
user: string
server: string
dataCenter: string
admin: boolean
accountType: string
renewalDate: string
storageCapacity: string
storageUsage: string
storageRenewalDate: string
breachWatch: boolean
reportingAndAlerts: boolean
recordsCount?: number
sharedFoldersCount?: number
teamsCount?: number
}

export type BuildWhoamiInfoInput = {
username: string
host: string
accountSummary: AccountSummary.IAccountSummaryElements
vaultSummary?: VaultSummary
}

export function normalizeServerHost(host: string): string {
return host
.toLowerCase()
.trim()
.replace(/^(qa|dev|dev2|local)\./, '')
}

export function resolveDataCenter(host: string): string {
const normalized = normalizeServerHost(host)
for (const [dataCenter, publicHost] of Object.entries(KEEPER_PUBLIC_HOSTS)) {
if (normalized === publicHost || normalized.endsWith(`.${publicHost}`)) {
return dataCenter
}
}
return 'US'
}

export function buildWhoamiInfo(input: BuildWhoamiInfoInput): WhoamiInfo {
const license = input.accountSummary.license ?? input.accountSummary.personalLicense ?? {}
const server = normalizeServerHost(input.host)

const info: WhoamiInfo = {
user: input.username,
server,
dataCenter: resolveDataCenter(input.host),
admin: !!input.accountSummary.isEnterpriseAdmin,
accountType: formatAccountType(license),
renewalDate: formatRenewalDate(license),
storageCapacity: formatStorageCapacity(license.bytesTotal),
storageUsage: formatStorageUsage(license.bytesUsed, license.bytesTotal),
storageRenewalDate: formatRenewalDateField(license.storageExpirationDate, license.storageExpiration),
breachWatch: isBreachWatchEnabled(license),
reportingAndAlerts: !!license.auditAndReportingEnabled,
}

if (input.vaultSummary) {
info.recordsCount = input.vaultSummary.recordCount
info.sharedFoldersCount = input.vaultSummary.sharedFolderCount
info.teamsCount = input.vaultSummary.teamCount
}

return info
}

function formatAccountType(license: AccountSummary.ILicense): string {
const name = (license.productTypeName ?? '').trim()
if (name) return name
if (license.accountType != null) return String(license.accountType)
return 'Unknown'
}

function formatRenewalDate(license: AccountSummary.ILicense): string {
return formatRenewalDateField(license.expirationDate, license.expiration)
}

function formatRenewalDateField(dateString?: string | null, timestamp?: number | null): string {
const trimmed = (dateString ?? '').trim()
if (trimmed) return trimmed
if (timestamp && timestamp > 0) {
return new Date(timestamp).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
}
return ''
}

function formatStorageCapacity(bytes?: number | null): string {
if (!bytes || bytes <= 0) return '0GB'
const gb = Math.round(bytes / 1024 ** 3)
return `${gb}GB`
}

function formatStorageUsage(bytesUsed?: number | null, bytesTotal?: number | null): string {
if (!bytesTotal || bytesTotal <= 0) return '0%'
const pct = Math.round(((bytesUsed ?? 0) / bytesTotal) * 100)
return `${pct}%`
}

function isBreachWatchEnabled(license: AccountSummary.ILicense): boolean {
if (license.breachWatchFeatureDisable) return false
return !!license.breachWatchEnabled
}
2 changes: 1 addition & 1 deletion KeeperSdk/src/folders/FolderManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class FolderManager {
}

public getWorkingFolderDisplayName(): string {
return getWorkingFolderDisplayName(this.storage, this.session.currentFolderUid)
return getWorkingFolderDisplayName(this.storage, this.session)
}

private requireAuth(): Auth {
Expand Down
22 changes: 19 additions & 3 deletions KeeperSdk/src/folders/addFolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ import { InMemoryStorage } from '../storage/InMemoryStorage'
import { isBoolean, KeeperSdkError, extractErrorMessage } from '../utils'
import { listFolder } from './listFolder'
import { tryResolvePath, splitPathComponents, type VaultFolderSession } from './changeDirectory'
import { FolderKind, FolderResultStatus, ParentFolderKind, validateFolderName } from './folderHelpers'
import {
ClassicFolderKind,
FolderKind,
FolderResultStatus,
ParentFolderKind,
validateFolderName,
} from './folderHelpers'

type NewFolderKind = FolderKind
type NewFolderKind = ClassicFolderKind

export type AddFolderInput = {
folderName: string
Expand All @@ -24,6 +30,7 @@ export type AddFolderInput = {
manageRecords?: boolean
canShare?: boolean
canEdit?: boolean
color?: string | null
}

export type AddFolderResult = {
Expand All @@ -40,6 +47,7 @@ export type MkdirOptions = {
manageRecords?: boolean
canShare?: boolean
canEdit?: boolean
color?: string | null
}

type ParentContext = {
Expand Down Expand Up @@ -152,11 +160,17 @@ export async function addFolder(auth: Auth, storage: InMemoryStorage, input: Add

const encryptionKey = await getEncryptionKeyForNewFolder(auth, storage, folderType, sharedScope)

const folderData: Record<string, string> = { name, title: name }
const color = input.color?.trim().toLowerCase()
if (color && color !== 'none') {
folderData.color = color
}

const request: FolderAddRequest = {
folder_uid: folderUid,
folder_type: folderType,
key: await encryptForStorage(folderKey, encryptionKey),
data: await encryptObjectForStorage({ name, title: name }, folderKey),
data: await encryptObjectForStorage(folderData, folderKey),
link: false,
}

Expand Down Expand Up @@ -190,6 +204,7 @@ export async function addFolder(auth: Auth, storage: InMemoryStorage, input: Add
message: reason,
}
}

return { folderUid, success: true }
} catch (err) {
return {
Expand Down Expand Up @@ -282,6 +297,7 @@ export async function mkdir(
manageRecords: isLastSegment && createAsSharedFolder ? manageRecords : undefined,
canShare: isLastSegment && createAsSharedFolder ? canShare : undefined,
canEdit: isLastSegment && createAsSharedFolder ? canEdit : undefined,
color: isLastSegment ? options.color : undefined,
})

if (!lastResult.success) {
Expand Down
53 changes: 47 additions & 6 deletions KeeperSdk/src/folders/changeDirectory.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import type { DSharedFolder, DSharedFolderFolder, DUserFolder } from '@keeper-security/keeperapi'
import { InMemoryStorage } from '../storage/InMemoryStorage'
import { KeeperSdkError } from '../utils'
import { getFolderDisplayName, getKeeperDriveFolder, isRootFolderUid } from '../nestedShareFolders/nsfHelpers'
import { listFolder, listRootUserFolders } from './listFolder'
import type { ListFolderFolderSimple } from './listFolder'
import { FolderKind, VaultObjectKind, sharedFolderFolderName, sharedFolderName, userFolderName } from './folderHelpers'

const VAULT_ROOT_DISPLAY_NAME = 'My Vault'
export const VAULT_ROOT_DISPLAY_NAME = 'My Vault'

const ESCAPED_SEPARATOR_PLACEHOLDER = '\x00'

export type VaultFolderSession = {
currentFolderUid: string | null
workingFolderDisplayPath?: string
}

export type ChangeDirectoryResult = {
Expand Down Expand Up @@ -47,13 +49,28 @@ function getFolderEntryByUid(storage: InMemoryStorage, uid: string): ListFolderF
folderKind: FolderKind.SharedFolderFolder,
}
}
const nestedShareFolder = getKeeperDriveFolder(storage, uid)
if (nestedShareFolder) {
return {
uid: nestedShareFolder.uid,
name: getFolderDisplayName(storage, nestedShareFolder.uid),
folderKind: FolderKind.KeeperDriveFolder,
}
}
return undefined
}

export async function findParentFolderUid(storage: InMemoryStorage, folderUid: string): Promise<string | null> {
const rootFolderUids = new Set((await listRootUserFolders(storage)).map((folder) => folder.uid))
if (rootFolderUids.has(folderUid)) return null

const nestedShareFolder = getKeeperDriveFolder(storage, folderUid)
if (nestedShareFolder) {
const parentUid = nestedShareFolder.parentUid?.trim()
if (!parentUid || isRootFolderUid(storage, parentUid)) return null
return parentUid
}

const parentKinds = [
FolderKind.UserFolder,
FolderKind.SharedFolder,
Expand Down Expand Up @@ -201,18 +218,42 @@ export async function resolveSingleFolder(
return { folderUid: entry.uid, name: entry.name }
}

export async function buildWorkingFolderDisplayPath(
storage: InMemoryStorage,
folderUid: string | null
): Promise<string> {
if (folderUid === null) return VAULT_ROOT_DISPLAY_NAME

const segments: string[] = []
let current: string | null = folderUid
while (current) {
const entry = getFolderEntryByUid(storage, current)
if (!entry) break
segments.unshift(entry.name)
current = await findParentFolderUid(storage, current)
}

if (segments.length === 0) return VAULT_ROOT_DISPLAY_NAME
return `${VAULT_ROOT_DISPLAY_NAME}/${segments.join('/')}`
}

export async function changeDirectory(
storage: InMemoryStorage,
session: VaultFolderSession,
path: string
): Promise<ChangeDirectoryResult> {
const resolved = await resolveSingleFolder(storage, session, path)
session.currentFolderUid = resolved.folderUid
return resolved
session.workingFolderDisplayPath = await buildWorkingFolderDisplayPath(storage, resolved.folderUid)
return {
folderUid: resolved.folderUid,
name: session.workingFolderDisplayPath,
}
}

export function getWorkingFolderDisplayName(storage: InMemoryStorage, currentFolderUid: string | null): string {
if (currentFolderUid === null) return VAULT_ROOT_DISPLAY_NAME
const entry = getFolderEntryByUid(storage, currentFolderUid)
return entry?.name || VAULT_ROOT_DISPLAY_NAME
export function getWorkingFolderDisplayName(storage: InMemoryStorage, session: VaultFolderSession): string {
if (session.workingFolderDisplayPath) return session.workingFolderDisplayPath
if (session.currentFolderUid === null) return VAULT_ROOT_DISPLAY_NAME
const entry = getFolderEntryByUid(storage, session.currentFolderUid)
return entry?.name ? `${VAULT_ROOT_DISPLAY_NAME}/${entry.name}` : VAULT_ROOT_DISPLAY_NAME
}
Loading
Loading