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
6 changes: 6 additions & 0 deletions examples/sdk_example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
"nsf:record-add": "ts-node src/nestedShareFolders/add_nsf_record.ts",
"nsf:mkdir": "ts-node src/nestedShareFolders/mkdir_nsf.ts",
"nsf:rmdir": "ts-node src/nestedShareFolders/rmdir_nsf.ts",
"nsf:share-folder": "ts-node src/nestedShareFolders/share_nsf_folder.ts",
"nsf:share-record": "ts-node src/nestedShareFolders/share_nsf_record.ts",
"nsf:record-permission": "ts-node src/nestedShareFolders/nsf_record_permission.ts",
"nsf:shortcut": "ts-node src/nestedShareFolders/shortcut_nsf.ts",
"nsf:transfer-record": "ts-node src/nestedShareFolders/transfer_nsf_record.ts",
"nsf:rndir": "ts-node src/nestedShareFolders/rndir_nsf.ts",
"teams:list": "ts-node src/teams/list_teams.ts",
"teams:view": "ts-node src/teams/view_team.ts",
"teams:add": "ts-node src/teams/add_team.ts",
Expand Down
133 changes: 133 additions & 0 deletions examples/sdk_example/src/nestedShareFolders/nsf_record_permission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {
cleanup,
extractErrorMessage,
login,
logger,
NsfRecordPermissionAction,
NSF_RECORD_PERMISSION_ROLES,
prompt,
suppressLogs,
type UpdateNsfRecordPermissionInput,
type UpdateNsfRecordPermissionResult,
} from '@keeper-security/keeper-sdk-javascript'
import { runExample } from '../utils/runner'
import { isYes } from '../utils/format'

type Vault = Awaited<ReturnType<typeof login>>

const ACTION_BY_INPUT: Record<string, NsfRecordPermissionAction> = {
'': NsfRecordPermissionAction.Grant,
'1': NsfRecordPermissionAction.Grant,
'2': NsfRecordPermissionAction.Revoke,
grant: NsfRecordPermissionAction.Grant,
revoke: NsfRecordPermissionAction.Revoke,
}

function parseAction(input: string): NsfRecordPermissionAction {
return ACTION_BY_INPUT[input.trim().toLowerCase()] ?? NsfRecordPermissionAction.Grant
}

async function runPermissionUpdate(
vault: Vault,
input: UpdateNsfRecordPermissionInput
): Promise<UpdateNsfRecordPermissionResult> {
const restore = suppressLogs()
try {
return await vault.updateNestedShareRecordPermissions(input)
} finally {
restore()
}
}

function printPlan(vault: Vault, result: UpdateNsfRecordPermissionResult): void {
logger.info('')
logger.info(vault.formatNsfRecordPermissionPlan(result))
logger.info('')
}

function printFailures(vault: Vault, result: UpdateNsfRecordPermissionResult): void {
const grantFailures = vault.formatNsfRecordPermissionFailures(result.grantFailures, 'GRANT')
const revokeFailures = vault.formatNsfRecordPermissionFailures(result.revokeFailures, 'REVOKE')
if (grantFailures) {
logger.info('')
logger.info(grantFailures)
logger.info('')
}
if (revokeFailures) {
logger.info('')
logger.info(revokeFailures)
logger.info('')
}
}

async function nsfRecordPermission() {
const vault = await login()

try {
logger.info('Action: 1) grant 2) revoke')
const action = parseAction(await prompt('Choose [1]: '))
const folder = (await prompt('Folder path or UID (Enter for root-level records): ')).trim()
const recursive = isYes(await prompt('Include sub-folders? [y/N]: '))

let role: string | undefined
if (action === NsfRecordPermissionAction.Grant) {
logger.info(`Roles: ${NSF_RECORD_PERMISSION_ROLES.join(', ')}`)
role = (await prompt('Role to grant: ')).trim()
if (!role) {
logger.info('Role is required for grant action.')
return
}
} else {
role = (await prompt('Role filter for revoke (Enter for all roles): ')).trim() || undefined
}

const dryRun = isYes(await prompt('Dry run (preview only)? [y/N]: '))
const force = dryRun ? false : isYes(await prompt('Force confirm without prompt? [y/N]: '))

const baseInput: UpdateNsfRecordPermissionInput = {
folder: folder || undefined,
action,
role,
recursive,
}

if (dryRun) {
const result = await runPermissionUpdate(vault, { ...baseInput, dryRun: true })
printPlan(vault, result)
if (result.message) logger.info(result.message)
logger.info('[Dry-run] No permission changes were applied.')
return
}

if (force) {
const result = await runPermissionUpdate(vault, { ...baseInput, force: true })
printPlan(vault, result)
printFailures(vault, result)
if (result.message) logger.info(result.message)
return
}

const preview = await runPermissionUpdate(vault, { ...baseInput, force: false })
printPlan(vault, preview)
if (preview.message && preview.plan.grants.length + preview.plan.revokes.length === 0) {
logger.info(preview.message)
return
}

if (!isYes(await prompt('Do you want to proceed with these permission changes? [y/n]: '))) {
logger.info('Permission update cancelled.')
return
}

const result = await runPermissionUpdate(vault, { ...baseInput, force: true })
printFailures(vault, result)
if (result.message) logger.info(result.message)
} catch (err) {
logger.error(`Record permission update failed: ${extractErrorMessage(err)}`)
process.exitCode = 1
} finally {
cleanup(vault)
}
}

runExample(nsfRecordPermission)
52 changes: 52 additions & 0 deletions examples/sdk_example/src/nestedShareFolders/rndir_nsf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
NSF_FOLDER_COLORS,
cleanup,
extractErrorMessage,
login,
logger,
prompt,
type NsfFolderColorInput,
} from '@keeper-security/keeper-sdk-javascript'
import { runExample } from '../utils/runner'
import { isYes, withSuppressedLogs } from '../utils/format'

async function rndirNsf() {
const vault = await login()

try {
const folder = (await prompt('Folder path or UID: ')).trim()
if (!folder) {
logger.info('Enter the path or UID of an existing folder.')
return
}

const name = (await prompt('New name (optional): ')).trim()
const colorInput = (await prompt(`New color (${NSF_FOLDER_COLORS.join(', ')}, optional): `)).trim()
const quiet = isYes(await prompt('Quiet mode? (y/N): '))

if (!name && !colorInput) {
logger.info('New folder name and/or color parameters are required.')
return
}

const result = await withSuppressedLogs(() =>
vault.updateNestedShareFolder({
folder,
name: name || undefined,
color: colorInput ? (colorInput as NsfFolderColorInput) : undefined,
quiet,
})
)

if (result.message) {
logger.info(result.message)
}
} catch (err) {
logger.error(`Folder update failed: ${extractErrorMessage(err)}`)
process.exitCode = 1
} finally {
cleanup(vault)
}
}

runExample(rndirNsf)
99 changes: 99 additions & 0 deletions examples/sdk_example/src/nestedShareFolders/share_nsf_folder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
cleanup,
extractErrorMessage,
login,
logger,
NsfFolderShareAction,
NSF_RECORD_PERMISSION_ROLES,
prompt,
suppressLogs,
type ShareNestedShareFolderInput,
type ShareNestedShareFolderResult,
} from '@keeper-security/keeper-sdk-javascript'
import { runExample } from '../utils/runner'
import { promptShareExpiration, splitCommaSeparated } from '../utils/format'

type Vault = Awaited<ReturnType<typeof login>>

const ACTION_BY_INPUT: Record<string, NsfFolderShareAction> = {
'': NsfFolderShareAction.Grant,
'1': NsfFolderShareAction.Grant,
'2': NsfFolderShareAction.Remove,
grant: NsfFolderShareAction.Grant,
remove: NsfFolderShareAction.Remove,
}

function parseAction(input: string): NsfFolderShareAction {
return ACTION_BY_INPUT[input.trim().toLowerCase()] ?? NsfFolderShareAction.Grant
}

async function runFolderShare(
vault: Vault,
input: ShareNestedShareFolderInput
): Promise<ShareNestedShareFolderResult> {
const restore = suppressLogs()
try {
return await vault.shareNestedShareFolder(input)
} finally {
restore()
}
}

function printResults(vault: Vault, result: ShareNestedShareFolderResult): void {
logger.info('')
logger.info(vault.formatNsfFolderShareResults(result.results))
logger.info('')
}

async function shareNsfFolder() {
const vault = await login()

try {
const folders = splitCommaSeparated(
await prompt('Folder path(s) or UID(s), comma-separated: ')
)
if (folders.length === 0) {
logger.info('Folder path or UID is required.')
return
}

const recipients = splitCommaSeparated(
await prompt('Recipient(s) — email, team name/UID, or @existing, comma-separated: ')
)
if (recipients.length === 0) {
logger.info('At least one recipient is required.')
return
}

logger.info('Action: 1) grant 2) remove')
const action = parseAction(await prompt('Choose [1]: '))

const role =
action === NsfFolderShareAction.Grant
? (await prompt(`Role (${NSF_RECORD_PERMISSION_ROLES.join(', ')}) [viewer]: `)).trim() ||
'viewer'
: undefined

const expiration =
action === NsfFolderShareAction.Grant
? await promptShareExpiration('nsf-share-folder')
: {}

const result = await runFolderShare(vault, {
folders,
recipients,
action,
role,
...expiration,
})

printResults(vault, result)
} catch (err) {
logger.error(`Folder share failed: ${extractErrorMessage(err)}`)
process.exitCode = 1
} finally {
cleanup(vault)
}
}

runExample(shareNsfFolder)
Loading
Loading