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
10 changes: 10 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ jobs:
run: npm run test
working-directory: ./keeperapi

- name: SDK - Install
run: npm ci
working-directory: ./KeeperSdk
env:
NPM_TOKEN: ""

- name: SDK - Check Formatting
run: npm run format:check
working-directory: ./KeeperSdk

- name: Examples (node) - Installation
run: npm run link-local
working-directory: ./examples/print-vault-node
Expand Down
2 changes: 2 additions & 0 deletions KeeperSdk/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package-lock.json
dist/
7 changes: 7 additions & 0 deletions KeeperSdk/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"singleQuote": true,
"semi": false,
"tabWidth": 4,
"printWidth": 120,
"trailingComma": "es5"
}
1,244 changes: 622 additions & 622 deletions KeeperSdk/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion KeeperSdk/src/auth/ConsoleLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ function unsuppressLogs(): () => void {

function unsuppressedAuthUI(): AuthUI3 {
const ui = new ConsoleAuthUI()
const wrap = <A extends unknown[], R>(fn: (...args: A) => Promise<R>) =>
const wrap =
<A extends unknown[], R>(fn: (...args: A) => Promise<R>) =>
async (...args: A): Promise<R> => {
const restore = unsuppressLogs()
try {
Expand Down
4 changes: 1 addition & 3 deletions KeeperSdk/src/auth/SessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,7 @@ export class SessionManager implements SessionStorage {
const keeperConfig = await this.loadKeeperConfig()

if (keeperConfig.users && keeperConfig.devices) {
const user = keeperConfig.users.find(
(configUser) => configUser.user?.toLowerCase() === normalizedUsername
)
const user = keeperConfig.users.find((configUser) => configUser.user?.toLowerCase() === normalizedUsername)
if (user?.last_device?.device_token) {
const deviceTokenStr = user.last_device.device_token
const device = keeperConfig.devices.find((configDevice) => configDevice.device_token === deviceTokenStr)
Expand Down
7 changes: 1 addition & 6 deletions KeeperSdk/src/enterpriseReport/EnterpriseReportManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import type { Auth } from '@keeper-security/keeperapi'
import { KeeperSdkError, ResultCodes } from '../utils'
import { runAuditReport } from './auditReport'
import { runActionReport } from './actionReport'
import type {
ActionReportOptions,
ActionReportResult,
AuditReportOptions,
AuditReportResult,
} from './reportTypes'
import type { ActionReportOptions, ActionReportResult, AuditReportOptions, AuditReportResult } from './reportTypes'
import type { AuthProvider } from './reportUtils'

export class EnterpriseReportManager {
Expand Down
66 changes: 39 additions & 27 deletions KeeperSdk/src/enterpriseReport/actionReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import { getAuditEventReportsCommand } from '@keeper-security/keeperapi'
import { KeeperSdkError, ResultCodes, extractErrorMessage } from '../utils'
import { actionUsers, UserAction } from '../users/actionUser'
import { deleteUsers } from '../users/deleteUser'
import {
DeleteUserStatus,
EnterpriseUserStatus,
formatTransferStatus,
formatUserStatus,
} from '../users/userTypes'
import { DeleteUserStatus, EnterpriseUserStatus, formatTransferStatus, formatUserStatus } from '../users/userTypes'
import {
EnterpriseDataInclude,
EnterpriseDataManager,
Expand Down Expand Up @@ -70,9 +65,7 @@ const ALLOWED_ACTIONS: Readonly<Record<TargetUserStatus, ReadonlySet<AdminAction
[TargetUserStatus.NoRecovery]: new Set([AdminAction.None]),
}

function usernameTargetConfig(
pickCandidates: (users: EnterpriseUser[]) => EnterpriseUser[]
): TargetAuditConfig {
function usernameTargetConfig(pickCandidates: (users: EnterpriseUser[]) => EnterpriseUser[]): TargetAuditConfig {
return {
auditColumn: 'username',
auditFilterField: 'username',
Expand Down Expand Up @@ -102,10 +95,7 @@ const TARGET_AUDIT_CONFIG: Record<TargetUserStatus, TargetAuditConfig> = {
),
}

export async function runActionReport(
auth: Auth,
options: ActionReportOptions = {}
): Promise<ActionReportResult> {
export async function runActionReport(auth: Auth, options: ActionReportOptions = {}): Promise<ActionReportResult> {
const target = options.target ?? TargetUserStatus.NoLogon
const daysSince = options.daysSince ?? ACTION_DEFAULT_DAYS_BY_TARGET[target] ?? ACTION_DEFAULT_DAYS
const applyAction = options.applyAction ?? AdminAction.None
Expand Down Expand Up @@ -165,9 +155,7 @@ async function generateActionReportEntries(
let candidates = config.pickCandidates(data.users || [])

if (options.nodeIds) {
candidates = candidates.filter(
(user) => user.node_id != null && options.nodeIds!.has(user.node_id)
)
candidates = candidates.filter((user) => user.node_id != null && options.nodeIds!.has(user.node_id))
}
if (candidates.length === 0) return []

Expand Down Expand Up @@ -245,11 +233,15 @@ function buildActionEntryContext(data: GetEnterpriseDataResponse): ActionEntryCo
const nodePaths = new Map(
nodes.map((node) => [
node.node_id,
EnterpriseDataManager.getNodePath(nodes, node.node_id, { omitRoot: false }),
EnterpriseDataManager.getNodePath(nodes, node.node_id, {
omitRoot: false,
}),
])
)
const teamNames = new Map((data.teams || []).map((team) => [team.team_uid, team.name]))
const roleNames = new Map((data.roles || []).map((role) => [role.role_id, role.displayName || String(role.role_id)]))
const roleNames = new Map(
(data.roles || []).map((role) => [role.role_id, role.displayName || String(role.role_id)])
)

return {
nodePaths,
Expand All @@ -274,10 +266,7 @@ function buildActionEntry(user: EnterpriseUser, context: ActionEntryContext): Ac
}
}

function buildUserTeamNames(
links: EnterpriseTeamUserLink[],
teamNames: Map<string, string>
): Map<number, string[]> {
function buildUserTeamNames(links: EnterpriseTeamUserLink[], teamNames: Map<string, string>): Map<number, string[]> {
const map = new Map<number, Set<string>>()
for (const link of links) {
if (!link.team_uid) continue
Expand Down Expand Up @@ -347,21 +336,39 @@ async function applyAdminAction(
const { applyAction, targetUser, dryRun } = options

if (applyAction === AdminAction.None) {
return { action: AdminAction.None, status: 'none', affectedCount: 0, serverMessage: 'n/a' }
return {
action: AdminAction.None,
status: 'none',
affectedCount: 0,
serverMessage: 'n/a',
}
}
if (entries.length === 0) {
return { action: applyAction, status: 'no users matched', affectedCount: 0, serverMessage: 'n/a' }
return {
action: applyAction,
status: 'no users matched',
affectedCount: 0,
serverMessage: 'n/a',
}
}
if (dryRun) {
return { action: applyAction, status: 'dry run', affectedCount: entries.length, serverMessage: 'n/a' }
return {
action: applyAction,
status: 'dry run',
affectedCount: entries.length,
serverMessage: 'n/a',
}
}

const emails = entries.map((entry) => entry.email)

try {
switch (applyAction) {
case AdminAction.Lock: {
const result = await actionUsers(auth, { action: UserAction.Lock, emails })
const result = await actionUsers(auth, {
action: UserAction.Lock,
emails,
})
return {
action: AdminAction.Lock,
status: result.success ? 'success' : 'partial',
Expand Down Expand Up @@ -392,7 +399,12 @@ async function applyAdminAction(
)
}
default:
return { action: applyAction, status: 'unsupported', affectedCount: 0, serverMessage: 'n/a' }
return {
action: applyAction,
status: 'unsupported',
affectedCount: 0,
serverMessage: 'n/a',
}
}
} catch (err) {
return {
Expand Down
41 changes: 24 additions & 17 deletions KeeperSdk/src/enterpriseReport/auditReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,15 @@ async function runDimensionReport(auth: Auth, options: AuditReportOptions): Prom
}
}

async function runRawReport(
auth: Auth,
options: AuditReportOptions,
hasAram: boolean
): Promise<AuditReportResult> {
async function runRawReport(auth: Auth, options: AuditReportOptions, hasAram: boolean): Promise<AuditReportResult> {
let filter = await resolveFilter(auth, options.filter ?? {})
let limit = options.limit
let order = options.order

if (!hasAram) {
const requested = options.limit
limit = requested != null && requested > 0 ? Math.min(requested, AUDIT_NO_ARAM_RAW_LIMIT) : AUDIT_NO_ARAM_RAW_LIMIT
limit =
requested != null && requested > 0 ? Math.min(requested, AUDIT_NO_ARAM_RAW_LIMIT) : AUDIT_NO_ARAM_RAW_LIMIT
order = order ?? AuditReportOrder.Desc
if (!filter.created) {
filter.created = 'last_30_days'
Expand Down Expand Up @@ -147,7 +144,9 @@ async function runRawReport(
}
rows.push(
fields.map((field) =>
field === 'message' ? eventMessage(event, templates) : formatFieldValue(field, getAuditEventField(event, field), 'raw')
field === 'message'
? eventMessage(event, templates)
: formatFieldValue(field, getAuditEventField(event, field), 'raw')
)
)
}
Expand Down Expand Up @@ -178,8 +177,7 @@ async function runSummaryReport(
throw new KeeperSdkError('"columns" parameter cannot be empty.', ResultCodes.AUDIT_COLUMNS_REQUIRED)
}

const aggregates =
options.aggregates?.length ? options.aggregates : [AuditAggregate.Occurrences]
const aggregates = options.aggregates?.length ? options.aggregates : [AuditAggregate.Occurrences]
const events = await fetchSummaryEvents(auth, {
reportType,
filter: await resolveFilter(auth, options.filter),
Expand Down Expand Up @@ -320,7 +318,9 @@ async function resolveFilter(auth: Auth, filter: AuditReportFilter = {}): Promis
const city = (parts.pop() || '').trim().toLowerCase()
for (const geo of await loadDimension(auth, 'geo_location')) {
if (!geo || typeof geo !== 'object') continue
const row = geo as AuditDimensionIpAddress & { ip_addresses?: string[] }
const row = geo as AuditDimensionIpAddress & {
ip_addresses?: string[]
}
if ((row.country_code || '').toLowerCase() !== country) continue
if (region && (row.region || '').toLowerCase() !== region) continue
if (city && (row.city || '').toLowerCase() !== city) continue
Expand Down Expand Up @@ -353,7 +353,9 @@ async function resolveFilter(auth: Auth, filter: AuditReportFilter = {}): Promis
}
for (const row of await loadDimension(auth, 'device_type')) {
if (!row || typeof row !== 'object') continue
const ver = row as AuditDimensionKeeperVersion & { version_ids?: number[] }
const ver = row as AuditDimensionKeeperVersion & {
version_ids?: number[]
}
if (deviceType) {
const typeName = (ver.type_name || '').toLowerCase()
const typeCategory = (ver.type_category || '').toLowerCase()
Expand Down Expand Up @@ -415,7 +417,10 @@ async function fetchDimensionRows(auth: Auth, dimension: string): Promise<AuditD
const region = ipRow.region || ''
const country = ipRow.country_code || ''
if (!city && !region && !country) return row
return { ...ipRow, geo_location: [city, region, country].filter(Boolean).join(', ') }
return {
...ipRow,
geo_location: [city, region, country].filter(Boolean).join(', '),
}
})
}

Expand All @@ -424,7 +429,9 @@ function buildVirtualDimension(dimension: string, sourceRows: AuditDimensionRow[
const geoMap = new Map<string, Record<string, unknown>>()
for (const row of sourceRows) {
if (!row || typeof row !== 'object') continue
const ipRow = row as AuditDimensionIpAddress & { ip_addresses?: string[] }
const ipRow = row as AuditDimensionIpAddress & {
ip_addresses?: string[]
}
if (!ipRow.geo_location || !ipRow.ip_address) continue
const existing = geoMap.get(ipRow.geo_location)
if (existing) (existing.ip_addresses as string[]).push(ipRow.ip_address)
Expand All @@ -445,7 +452,9 @@ function buildVirtualDimension(dimension: string, sourceRows: AuditDimensionRow[
const deviceMap = new Map<number, Record<string, unknown>>()
for (const row of sourceRows) {
if (!row || typeof row !== 'object') continue
const versionRow = row as AuditDimensionKeeperVersion & { version_ids?: number[] }
const versionRow = row as AuditDimensionKeeperVersion & {
version_ids?: number[]
}
if (!versionRow.type_id || !versionRow.version_id) continue
const existing = deviceMap.get(versionRow.type_id)
if (existing) (existing.version_ids as number[]).push(versionRow.version_id)
Expand Down Expand Up @@ -544,9 +553,7 @@ function advanceCreatedFilter(
): AuditReportFilter {
const next: AuditReportFilter = { ...(filter || {}) }
const criteria: CreatedFilterCriteria =
next.created && typeof next.created === 'object' && !Array.isArray(next.created)
? { ...next.created }
: {}
next.created && typeof next.created === 'object' && !Array.isArray(next.created) ? { ...next.created } : {}
if (order === AuditReportOrder.Asc) {
criteria.fromDate = timestamp
criteria.excludeFrom = false
Expand Down
15 changes: 6 additions & 9 deletions KeeperSdk/src/enterpriseReport/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
export {
runAuditReport,
} from './auditReport'
export {
runActionReport,
getAllowedActions,
getDefaultDaysSince,
} from './actionReport'
export { runAuditReport } from './auditReport'
export { runActionReport, getAllowedActions, getDefaultDaysSince } from './actionReport'
export {
runPasswordReport,
getPasswordStrength,
Expand All @@ -15,7 +9,10 @@ export {
buildPasswordPolicySummary,
} from './passwordReport'
export { EnterpriseReportManager } from './EnterpriseReportManager'
export { EnterpriseReportManager as AuditReportManager, EnterpriseReportManager as ActionReportManager } from './EnterpriseReportManager'
export {
EnterpriseReportManager as AuditReportManager,
EnterpriseReportManager as ActionReportManager,
} from './EnterpriseReportManager'
export type { AuthProvider } from './reportUtils'
export {
AuditReportOrder,
Expand Down
15 changes: 6 additions & 9 deletions KeeperSdk/src/enterpriseReport/passwordReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import type { DBWRecord, DRecord } from '@keeper-security/keeperapi'
import { InMemoryStorage } from '../storage/InMemoryStorage'
import { resolveSingleFolder, type VaultFolderSession } from '../folders/changeDirectory'
import { listFolder } from '../folders/listFolder'
import {
getRecordPassword,
getRecordSummary,
getRecordTitle,
} from '../records/RecordUtils'
import { getRecordPassword, getRecordSummary, getRecordTitle } from '../records/RecordUtils'
import { KeeperSdkError, ResultCodes } from '../utils'
import {
DEFAULT_TRUNCATION_LENGTH,
Expand All @@ -23,7 +19,10 @@ import {
const POLICY_FIELD_COUNT = 5
const SPECIAL_CHAR_SET = new Set(PW_SPECIAL_CHARACTERS.split(''))

const POLICY_SUMMARY_LABELS: ReadonlyArray<{ key: keyof PasswordPolicy; label: string }> = [
const POLICY_SUMMARY_LABELS: ReadonlyArray<{
key: keyof PasswordPolicy
label: string
}> = [
{ key: 'length', label: 'length' },
{ key: 'lower', label: 'lowercase' },
{ key: 'upper', label: 'uppercase' },
Expand Down Expand Up @@ -250,9 +249,7 @@ export async function runPasswordReport(
breachWatchPasswordCounts,
}

const rows = targetRecords.flatMap((record) =>
buildNonCompliantRow(record, policy, verbose, verboseContext)
)
const rows = targetRecords.flatMap((record) => buildNonCompliantRow(record, policy, verbose, verboseContext))

return {
policy,
Expand Down
8 changes: 1 addition & 7 deletions KeeperSdk/src/enterpriseReport/reportTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,7 @@ export enum AuditAggregate {
LastCreated = 'last_created',
}

export const SUMMARY_REPORT_TYPES: readonly AuditSummaryReportType[] = [
'hour',
'day',
'week',
'month',
'span',
]
export const SUMMARY_REPORT_TYPES: readonly AuditSummaryReportType[] = ['hour', 'day', 'week', 'month', 'span']

export const CREATED_PRESETS = [
'today',
Expand Down
6 changes: 1 addition & 5 deletions KeeperSdk/src/enterpriseReport/reportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ export function resolveTimezone(timezone: string | undefined): string {
return `Etc/GMT${hours >= 0 ? '+' : ''}${hours}`
}

export function assertSucceeded(
response: KeeperResponse,
fallbackMessage: string,
fallbackCode: string
): void {
export function assertSucceeded(response: KeeperResponse, fallbackMessage: string, fallbackCode: string): void {
if ((response.result || '').toLowerCase() === 'fail') {
throw new KeeperSdkError(
response.message || response.result_code || fallbackMessage,
Expand Down
Loading
Loading