From 4d4377ac25eb03a4c11e2309643c60f7911111e7 Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Tue, 29 Sep 2026 11:02:30 +0530 Subject: [PATCH 1/9] config create and resume session implementation --- KeeperSdk/src/auth/SessionManager.ts | 38 ++++++++++++++++++++- KeeperSdk/src/auth/config.ts | 7 ++++ KeeperSdk/src/auth/node/FileConfigLoader.ts | 31 ++++++++++++++--- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/KeeperSdk/src/auth/SessionManager.ts b/KeeperSdk/src/auth/SessionManager.ts index 3770e86f..27fd38d8 100644 --- a/KeeperSdk/src/auth/SessionManager.ts +++ b/KeeperSdk/src/auth/SessionManager.ts @@ -8,7 +8,13 @@ import { import { getSdkPlatform } from '../platform' import { logger, extractErrorMessage } from '../utils' import type { Nullable } from '../utils' -import type { ConfigLoader, KeeperJsonConfig, ConfigurationServerConfig, ConfigurationUser } from './config' +import type { + ConfigLoader, + KeeperJsonConfig, + ConfigurationServerConfig, + ConfigurationUser, + ConfigurationServer, +} from './config' import { isValidKeeperConfig } from './config' export type { @@ -16,6 +22,7 @@ export type { ConfigLoader, ConfigurationUser, ConfigurationServerConfig, + ConfigurationServer, ConfigurationDeviceConfig, } from './config' @@ -97,11 +104,40 @@ export class SessionManager implements SessionStorage { const parsed = await this.configLoader.load() const config: KeeperJsonConfig = parsed && Object.keys(parsed).length > 0 ? parsed : {} + config.last_login = username + config.last_server = host config.device_token = Buffer.from(deviceConfig.deviceToken).toString('base64url') config.private_key = Buffer.from(deviceConfig.privateKey).toString('base64url') config.user = username config.server = host + const users = config.users || [] + let user = users.find((entry) => entry.user?.toLowerCase() === username.toLowerCase()) + if (!user) { + user = { user: username } + users.push(user) + } + user.server = host + user.last_device = { device_token: config.device_token } + config.users = users + + const devices = config.devices || [] + let device = devices.find((entry) => entry.device_token === config.device_token) + if (!device) { + device = { device_token: config.device_token } + devices.push(device) + } + device.private_key = config.private_key + device.server_info = device.server_info || [] + config.devices = devices + + const servers = config.servers || [] + if (!servers.some((entry) => entry.server === host)) { + const server: ConfigurationServer = { server: host } + servers.push(server) + } + config.servers = servers + await this.configLoader.save(config) this._keeperConfig = null this._deviceCache = null diff --git a/KeeperSdk/src/auth/config.ts b/KeeperSdk/src/auth/config.ts index af3d2dd7..154933df 100644 --- a/KeeperSdk/src/auth/config.ts +++ b/KeeperSdk/src/auth/config.ts @@ -9,6 +9,11 @@ export type ConfigurationServerConfig = { clone_code?: string } +export type ConfigurationServer = { + server?: string + server_key_id?: number +} + export type ConfigurationDeviceConfig = { device_token?: string private_key?: string @@ -24,6 +29,7 @@ export type KeeperJsonConfig = { private_key?: string clone_code?: string users?: Array + servers?: Array devices?: Array } @@ -37,6 +43,7 @@ export function isValidKeeperConfig(value: unknown): value is KeeperJsonConfig { if (typeof value !== 'object' || value === null) return false const obj = value as Record if (obj.users !== undefined && !Array.isArray(obj.users)) return false + if (obj.servers !== undefined && !Array.isArray(obj.servers)) return false if (obj.devices !== undefined && !Array.isArray(obj.devices)) return false return true } diff --git a/KeeperSdk/src/auth/node/FileConfigLoader.ts b/KeeperSdk/src/auth/node/FileConfigLoader.ts index f80f7a0d..2a12ea92 100644 --- a/KeeperSdk/src/auth/node/FileConfigLoader.ts +++ b/KeeperSdk/src/auth/node/FileConfigLoader.ts @@ -1,4 +1,5 @@ import fs from 'fs/promises' +import { existsSync } from 'fs' import path from 'path' import os from 'os' import type { ConfigLoader, KeeperJsonConfig } from '../config' @@ -8,29 +9,49 @@ import { logger, extractErrorMessage, SdkDefaults } from '../../utils' /** CAUTION: This is a Node-only class. */ export class FileConfigLoader implements ConfigLoader { public readonly configDir: string + private readonly configPath: string constructor(configDir?: string) { - this.configDir = configDir || path.join(os.homedir(), SdkDefaults.CONFIG_DIR) + const configuredFile = process.env.KEEPER_CONFIG_FILE + if (configDir) { + this.configDir = configDir + this.configPath = path.join(configDir, 'config.json') + } else if (configuredFile) { + this.configPath = path.resolve(configuredFile) + this.configDir = path.dirname(this.configPath) + } else { + const currentDirectoryConfig = path.join(process.cwd(), 'config.json') + this.configPath = existsSync(currentDirectoryConfig) + ? currentDirectoryConfig + : path.join(os.homedir(), SdkDefaults.CONFIG_DIR, 'config.json') + this.configDir = path.dirname(this.configPath) + } } async load(): Promise { - const configPath = path.join(this.configDir, 'config.json') try { - const content = await fs.readFile(configPath, 'utf-8') + const content = await fs.readFile(this.configPath, 'utf-8') const parsed: unknown = JSON.parse(content) if (isValidKeeperConfig(parsed)) { return parsed } } catch (err) { + if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') { + try { + await this.save({}) + } catch (saveErr) { + logger.debug('Failed to create keeper config:', extractErrorMessage(saveErr)) + } + return {} + } logger.debug('Failed to load keeper config:', extractErrorMessage(err)) } return {} } async save(config: KeeperJsonConfig): Promise { - const configPath = path.join(this.configDir, 'config.json') await fs.mkdir(this.configDir, { recursive: true, mode: 0o700 }) - await fs.writeFile(configPath, JSON.stringify(config, null, 2), { + await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), { mode: 0o600, }) } From 83afa05ea9a4010f256435121d83c0413e4129d8 Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Tue, 29 Sep 2026 15:04:42 +0530 Subject: [PATCH 2/9] Fix persistent login by forwarding username in resumeSession() --- KeeperSdk/src/vault/KeeperVault.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/KeeperSdk/src/vault/KeeperVault.ts b/KeeperSdk/src/vault/KeeperVault.ts index f1a2f728..0e76ebb5 100644 --- a/KeeperSdk/src/vault/KeeperVault.ts +++ b/KeeperSdk/src/vault/KeeperVault.ts @@ -581,14 +581,15 @@ export class KeeperVault { this.auth = await this.createAuth({ useSessionResumption: true }) - await this.auth.loginV3({ + const loginResult = await this.auth.loginV3({ + username, loginType: Authentication.LoginType.NORMAL, resumeSessionOnly: true, }) if (!this.auth.sessionToken) { throw new KeeperSdkError( - 'Persistent login failed — clone code may be expired or persistent login not enabled. Perform a normal login.', + `Persistent login failed (${loginResult?.result || 'unknown result'}) — clone code may be expired or persistent login not enabled. Perform a normal login.`, ResultCodes.PERSISTENT_LOGIN_FAILED ) } From 36474e0ebaaf0ce92f6359e4525a4afda79f4f2f Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Thu, 8 Oct 2026 01:06:29 +0530 Subject: [PATCH 3/9] Persistent login implementation --- KeeperSdk/src/auth/PersistentLogin.ts | 70 ++++++++++++ KeeperSdk/src/auth/SessionManager.ts | 16 ++- KeeperSdk/src/auth/config.ts | 1 + KeeperSdk/src/index.ts | 1 + KeeperSdk/src/vault/KeeperVault.ts | 9 +- examples/sdk_example/README.md | 1 + examples/sdk_example/package.json | 1 + .../src/auth/set_persistent_login.ts | 107 ++++++++++++++++++ 8 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 KeeperSdk/src/auth/PersistentLogin.ts create mode 100644 examples/sdk_example/src/auth/set_persistent_login.ts diff --git a/KeeperSdk/src/auth/PersistentLogin.ts b/KeeperSdk/src/auth/PersistentLogin.ts new file mode 100644 index 00000000..fddec391 --- /dev/null +++ b/KeeperSdk/src/auth/PersistentLogin.ts @@ -0,0 +1,70 @@ +import { + platform, + registerEncryptedDataKeyForDeviceMessage, + setUserSettingMessage, + updateDeviceMessage, + type Auth, +} from '@keeper-security/keeperapi' +import { extractResultCode } from '../utils' + +export const DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES = 30 * 24 * 60 + +function ensureDeviceConfig(auth: Auth) { + const deviceConfig = auth.options.deviceConfig + if (!deviceConfig.deviceToken) throw new Error('Device token is missing') + return deviceConfig +} + +async function ensureDevicePublicKey(auth: Auth): Promise { + const deviceConfig = ensureDeviceConfig(auth) + if (deviceConfig.publicKey) return + + const keyPair = await platform.generateECKeyPair() + deviceConfig.privateKey = keyPair.privateKey + deviceConfig.publicKey = keyPair.publicKey + + await auth.executeRestAction( + updateDeviceMessage({ + encryptedDeviceToken: deviceConfig.deviceToken, + clientVersion: auth.clientVersion, + deviceName: deviceConfig.deviceName, + devicePublicKey: deviceConfig.publicKey, + }) + ) + + await auth.options.onDeviceConfig?.(deviceConfig, auth.options.host) +} + +async function registerDataKeyForDevice(auth: Auth): Promise { + if (!auth.dataKey) throw new Error('Data Key is missing') + const deviceConfig = ensureDeviceConfig(auth) + if (!deviceConfig.publicKey) throw new Error('Device public key is missing') + + const encryptedDeviceDataKey = await platform.publicEncryptEC(auth.dataKey, deviceConfig.publicKey) + try { + await auth.executeRestAction( + registerEncryptedDataKeyForDeviceMessage({ + encryptedDeviceToken: deviceConfig.deviceToken, + encryptedDeviceDataKey, + }) + ) + } catch (error) { + if (extractResultCode(error) !== 'device_data_key_exists') throw error + } +} + +export async function setPersistentLogin( + auth: Auth, + enabled: boolean, + logoutTimerMinutes = DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES +): Promise { + if (enabled) await ensureDevicePublicKey(auth) + + await auth.executeRestAction(setUserSettingMessage({ setting: 'persistent_login', value: enabled ? '1' : '0' })) + if (enabled) { + await registerDataKeyForDevice(auth) + await auth.executeRestAction( + setUserSettingMessage({ setting: 'logout_timer', value: String(logoutTimerMinutes) }) + ) + } +} diff --git a/KeeperSdk/src/auth/SessionManager.ts b/KeeperSdk/src/auth/SessionManager.ts index 27fd38d8..9279e8ca 100644 --- a/KeeperSdk/src/auth/SessionManager.ts +++ b/KeeperSdk/src/auth/SessionManager.ts @@ -29,6 +29,7 @@ export type { type ResolvedDevice = { deviceToken: Uint8Array privateKey: Uint8Array + publicKey?: Uint8Array serverInfo: Array> } @@ -45,6 +46,7 @@ export class SessionManager implements SessionStorage { private _deviceCache: Nullable = null private sessionDevices = new Map() private sessionCloneCodes = new Map() + private cloneCodeWrite: Promise = Promise.resolve() constructor(configDir?: string) constructor(loader: ConfigLoader) @@ -80,6 +82,7 @@ export class SessionManager implements SessionStorage { return { deviceToken: device.deviceToken, privateKey: device.privateKey, + publicKey: device.publicKey, } } } @@ -128,6 +131,9 @@ export class SessionManager implements SessionStorage { devices.push(device) } device.private_key = config.private_key + if (deviceConfig.publicKey) { + device.public_key = Buffer.from(deviceConfig.publicKey).toString('base64url') + } device.server_info = device.server_info || [] config.devices = devices @@ -171,7 +177,14 @@ export class SessionManager implements SessionStorage { public async saveCloneCode(host: KeeperHost, username: string, cloneCode: Uint8Array): Promise { const key = this.cloneCodeKey(host, username) this.sessionCloneCodes.set(key, cloneCode) - await this.updateKeeperConfigCloneCode(String(host), username, cloneCode) + this.cloneCodeWrite = this.cloneCodeWrite.then(() => + this.updateKeeperConfigCloneCode(String(host), username, cloneCode) + ) + await this.cloneCodeWrite + } + + public async flush(): Promise { + await this.cloneCodeWrite } private async updateKeeperConfigCloneCode(host: string, username: string, cloneCode: Uint8Array): Promise { @@ -261,6 +274,7 @@ export class SessionManager implements SessionStorage { return { deviceToken: normal64Bytes(deviceTokenStr), privateKey: normal64Bytes(device.private_key), + publicKey: device.public_key ? normal64Bytes(device.public_key) : undefined, serverInfo: (device.server_info || []).filter( (entry): entry is Required => !!entry.server && !!entry.clone_code diff --git a/KeeperSdk/src/auth/config.ts b/KeeperSdk/src/auth/config.ts index 154933df..4d4d900a 100644 --- a/KeeperSdk/src/auth/config.ts +++ b/KeeperSdk/src/auth/config.ts @@ -17,6 +17,7 @@ export type ConfigurationServer = { export type ConfigurationDeviceConfig = { device_token?: string private_key?: string + public_key?: string server_info?: Array } diff --git a/KeeperSdk/src/index.ts b/KeeperSdk/src/index.ts index 9540c2a2..4ee6d5bf 100644 --- a/KeeperSdk/src/index.ts +++ b/KeeperSdk/src/index.ts @@ -9,6 +9,7 @@ export { ConsoleAuthUI } from './auth/ConsoleAuthUI' export { UnavailableAuthUI } from './auth/UnavailableAuthUI' export { FileConfigLoader } from './auth/node/FileConfigLoader' export { SessionManager } from './auth/SessionManager' +export { setPersistentLogin, DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES } from './auth/PersistentLogin' export type { KeeperJsonConfig, ConfigLoader, diff --git a/KeeperSdk/src/vault/KeeperVault.ts b/KeeperSdk/src/vault/KeeperVault.ts index 0e76ebb5..3cd4ec2a 100644 --- a/KeeperSdk/src/vault/KeeperVault.ts +++ b/KeeperSdk/src/vault/KeeperVault.ts @@ -14,6 +14,7 @@ import { import type { SyncResult, SyncLogFormat, VaultStorage, SessionStorage, AuthUI3 } from '@keeper-security/keeperapi' import { InMemoryStorage } from '../storage/InMemoryStorage' import { SessionManager } from '../auth/SessionManager' +import { setPersistentLogin } from '../auth/PersistentLogin' import { getSdkPlatform } from '../platform' import { toSessionParams, type SessionRestoreInput } from '../auth/sessionRestore' import { searchRecords, formatRecord, getRecordTitle, getRecordType } from '../records/RecordUtils' @@ -478,6 +479,12 @@ export class KeeperVault { return this.auth?.sessionToken || undefined } + /** Enables or disables persistent login for the current authenticated session. */ + public async setPersistentLogin(enabled: boolean, logoutTimerMinutes = 30 * 24 * 60): Promise { + const auth = this.getAuthOrThrow() + await setPersistentLogin(auth, enabled, logoutTimerMinutes) + } + /** * Resume a session from extension-exported {@link SessionRestoreInput}. * Verifies the token with a lightweight server call so an expired session @@ -582,10 +589,10 @@ export class KeeperVault { this.auth = await this.createAuth({ useSessionResumption: true }) const loginResult = await this.auth.loginV3({ - username, loginType: Authentication.LoginType.NORMAL, resumeSessionOnly: true, }) + await this.sessionManager.flush() if (!this.auth.sessionToken) { throw new KeeperSdkError( diff --git a/examples/sdk_example/README.md b/examples/sdk_example/README.md index 7a7477c3..ecdda234 100644 --- a/examples/sdk_example/README.md +++ b/examples/sdk_example/README.md @@ -31,6 +31,7 @@ Examples use `~/.keeper/config.json` for saved credentials and persistent login. | Command | Description | |---|---| | `npm run auth:login` | Master password login with retry logic, masked input, and vault sync. Automatically attempts persistent login (via clone code from `~/.keeper/config.json`) before falling back to the password prompt. | +| `npm run auth:set-persistent-login` | Logs in, asks whether to enable or disable persistent login, applies the setting, then closes the current session. | | `npm run auth:session-token` | Login using an existing session token for pre-authenticated workflows. Prompts for username, host, and session token. | ### Records diff --git a/examples/sdk_example/package.json b/examples/sdk_example/package.json index aba405bf..c53114d3 100644 --- a/examples/sdk_example/package.json +++ b/examples/sdk_example/package.json @@ -4,6 +4,7 @@ "description": "Example usage of the KeeperSdk wrapper", "scripts": { "auth:login": "ts-node src/auth/login.ts", + "auth:set-persistent-login": "ts-node src/auth/set_persistent_login.ts", "auth:session-token": "ts-node src/auth/session_token_login.ts", "auth:register-device": "ts-node src/auth/register_device.ts", "records:list": "ts-node src/records/list_records.ts", diff --git a/examples/sdk_example/src/auth/set_persistent_login.ts b/examples/sdk_example/src/auth/set_persistent_login.ts new file mode 100644 index 00000000..c42ba64f --- /dev/null +++ b/examples/sdk_example/src/auth/set_persistent_login.ts @@ -0,0 +1,107 @@ +import { + KeeperVault, + KeeperSdkError, + loadKeeperConfig, + resolveServer, + prompt, + suppressLogs, + cleanup, + logger, + extractResultCode, + SdkDefaults, + ResultCodes, + setPersistentLogin as applyPersistentLogin, + DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES, +} from "@keeper-security/keeper-sdk-javascript"; +import { runExample } from "../utils/runner"; + +const MAX_ATTEMPTS = 5; +async function setPersistentLogin() { + const config = await loadKeeperConfig(); + const defaultUsername = config.last_login || config.user || ""; + + let username: string; + if (defaultUsername) { + logger.info(`Enter master password for ${defaultUsername}`); + username = defaultUsername; + } else { + username = await prompt("Username (email): "); + if (!username) + throw new KeeperSdkError( + "Username is required.", + ResultCodes.MISSING_USERNAME, + ); + } + + const host = await resolveServer(username); + const vault = new KeeperVault({ + host, + clientVersion: SdkDefaults.CLIENT_VERSION, + }); + + try { + for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { + const password = await prompt("Password: ", true); + if (!password) + throw new KeeperSdkError( + "Password is required.", + ResultCodes.MISSING_PASSWORD, + ); + + const restore = suppressLogs(); + try { + await vault.login(username, password); + restore(); + break; + } catch (err) { + restore(); + const resultCode = extractResultCode(err); + if (resultCode === ResultCodes.INVALID_CREDENTIALS) { + const remaining = MAX_ATTEMPTS - attempt; + if (remaining > 0) { + logger.warn( + `Incorrect password (${remaining} attempt${remaining === 1 ? "" : "s"} remaining)`, + ); + continue; + } + throw new KeeperSdkError( + `Maximum login attempts (${MAX_ATTEMPTS}) exceeded.`, + ResultCodes.MAX_ATTEMPTS_EXCEEDED, + ); + } + throw KeeperSdkError.from(err); + } + } + + const choice = ( + await prompt("Persistent login: enable or disable? [e/d]: ") + ) + .trim() + .toLowerCase(); + if ( + choice !== "e" && + choice !== "enable" && + choice !== "d" && + choice !== "disable" + ) { + throw new KeeperSdkError( + "Please choose enable or disable.", + ResultCodes.USER_CANCELLED, + ); + } + + const enabled = choice === "e" || choice === "enable"; + await applyPersistentLogin( + vault.getAuth(), + enabled, + DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES, + ); + logger.info( + `Persistent login ${enabled ? "enabled" : "disabled"} successfully.`, + ); + } finally { + cleanup(vault); + } +} + +runExample(setPersistentLogin); From 128934fc1577335e2277edbf87c397d0892c42fc Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Thu, 8 Oct 2026 01:26:36 +0530 Subject: [PATCH 4/9] PR suggestion improvements --- KeeperSdk/src/auth/config.ts | 46 ++++++++++++++++++--- KeeperSdk/src/auth/node/FileConfigLoader.ts | 11 +---- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/KeeperSdk/src/auth/config.ts b/KeeperSdk/src/auth/config.ts index 4d4d900a..5c9be0e6 100644 --- a/KeeperSdk/src/auth/config.ts +++ b/KeeperSdk/src/auth/config.ts @@ -40,11 +40,45 @@ export interface ConfigLoader { readonly configDir: string } +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +function hasOptionalStringFields(value: Record, fields: string[]): boolean { + return fields.every((field) => value[field] === undefined || typeof value[field] === 'string') +} + +function isValidUser(value: unknown): value is ConfigurationUser { + if (!isRecord(value) || !hasOptionalStringFields(value, ['user', 'server'])) return false + return value.last_device === undefined || + (isRecord(value.last_device) && hasOptionalStringFields(value.last_device, ['device_token'])) +} + +function isValidServer(value: unknown): value is ConfigurationServer { + return isRecord(value) && + hasOptionalStringFields(value, ['server']) && + (value.server_key_id === undefined || + (typeof value.server_key_id === 'number' && Number.isFinite(value.server_key_id))) +} + +function isValidServerConfig(value: unknown): value is ConfigurationServerConfig { + return isRecord(value) && hasOptionalStringFields(value, ['server', 'clone_code']) +} + +function isValidDevice(value: unknown): value is ConfigurationDeviceConfig { + if (!isRecord(value) || !hasOptionalStringFields(value, ['device_token', 'private_key', 'public_key'])) { + return false + } + return value.server_info === undefined || + (Array.isArray(value.server_info) && value.server_info.every(isValidServerConfig)) +} + export function isValidKeeperConfig(value: unknown): value is KeeperJsonConfig { - if (typeof value !== 'object' || value === null) return false - const obj = value as Record - if (obj.users !== undefined && !Array.isArray(obj.users)) return false - if (obj.servers !== undefined && !Array.isArray(obj.servers)) return false - if (obj.devices !== undefined && !Array.isArray(obj.devices)) return false - return true + if (!isRecord(value) || !hasOptionalStringFields(value, ['last_login', 'last_server', 'user', 'server'])) { + return false + } + if (!hasOptionalStringFields(value, ['device_token', 'private_key', 'clone_code'])) return false + return (value.users === undefined || (Array.isArray(value.users) && value.users.every(isValidUser))) && + (value.servers === undefined || (Array.isArray(value.servers) && value.servers.every(isValidServer))) && + (value.devices === undefined || (Array.isArray(value.devices) && value.devices.every(isValidDevice))) } diff --git a/KeeperSdk/src/auth/node/FileConfigLoader.ts b/KeeperSdk/src/auth/node/FileConfigLoader.ts index 2a12ea92..b5daa8c4 100644 --- a/KeeperSdk/src/auth/node/FileConfigLoader.ts +++ b/KeeperSdk/src/auth/node/FileConfigLoader.ts @@ -1,5 +1,4 @@ import fs from 'fs/promises' -import { existsSync } from 'fs' import path from 'path' import os from 'os' import type { ConfigLoader, KeeperJsonConfig } from '../config' @@ -20,10 +19,7 @@ export class FileConfigLoader implements ConfigLoader { this.configPath = path.resolve(configuredFile) this.configDir = path.dirname(this.configPath) } else { - const currentDirectoryConfig = path.join(process.cwd(), 'config.json') - this.configPath = existsSync(currentDirectoryConfig) - ? currentDirectoryConfig - : path.join(os.homedir(), SdkDefaults.CONFIG_DIR, 'config.json') + this.configPath = path.join(os.homedir(), SdkDefaults.CONFIG_DIR, 'config.json') this.configDir = path.dirname(this.configPath) } } @@ -37,11 +33,6 @@ export class FileConfigLoader implements ConfigLoader { } } catch (err) { if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') { - try { - await this.save({}) - } catch (saveErr) { - logger.debug('Failed to create keeper config:', extractErrorMessage(saveErr)) - } return {} } logger.debug('Failed to load keeper config:', extractErrorMessage(err)) From a65876a070061c40a78a7ed18c288b58db425a6e Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Thu, 8 Oct 2026 01:50:52 +0530 Subject: [PATCH 5/9] Type improvements --- KeeperSdk/src/auth/PersistentLogin.ts | 11 ++++++----- KeeperSdk/src/auth/SessionManager.ts | 15 ++++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/KeeperSdk/src/auth/PersistentLogin.ts b/KeeperSdk/src/auth/PersistentLogin.ts index fddec391..1e2e458a 100644 --- a/KeeperSdk/src/auth/PersistentLogin.ts +++ b/KeeperSdk/src/auth/PersistentLogin.ts @@ -4,13 +4,14 @@ import { setUserSettingMessage, updateDeviceMessage, type Auth, + type DeviceConfig, } from '@keeper-security/keeperapi' import { extractResultCode } from '../utils' -export const DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES = 30 * 24 * 60 +export const DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES: number = 30 * 24 * 60 -function ensureDeviceConfig(auth: Auth) { - const deviceConfig = auth.options.deviceConfig +function ensureDeviceConfig(auth: Auth): DeviceConfig { + const deviceConfig: DeviceConfig = auth.options.deviceConfig if (!deviceConfig.deviceToken) throw new Error('Device token is missing') return deviceConfig } @@ -19,7 +20,7 @@ async function ensureDevicePublicKey(auth: Auth): Promise { const deviceConfig = ensureDeviceConfig(auth) if (deviceConfig.publicKey) return - const keyPair = await platform.generateECKeyPair() + const keyPair: { privateKey: Uint8Array; publicKey: Uint8Array } = await platform.generateECKeyPair() deviceConfig.privateKey = keyPair.privateKey deviceConfig.publicKey = keyPair.publicKey @@ -40,7 +41,7 @@ async function registerDataKeyForDevice(auth: Auth): Promise { const deviceConfig = ensureDeviceConfig(auth) if (!deviceConfig.publicKey) throw new Error('Device public key is missing') - const encryptedDeviceDataKey = await platform.publicEncryptEC(auth.dataKey, deviceConfig.publicKey) + const encryptedDeviceDataKey: Uint8Array = await platform.publicEncryptEC(auth.dataKey, deviceConfig.publicKey) try { await auth.executeRestAction( registerEncryptedDataKeyForDeviceMessage({ diff --git a/KeeperSdk/src/auth/SessionManager.ts b/KeeperSdk/src/auth/SessionManager.ts index 9279e8ca..c5da0c7d 100644 --- a/KeeperSdk/src/auth/SessionManager.ts +++ b/KeeperSdk/src/auth/SessionManager.ts @@ -11,6 +11,7 @@ import type { Nullable } from '../utils' import type { ConfigLoader, KeeperJsonConfig, + ConfigurationDeviceConfig, ConfigurationServerConfig, ConfigurationUser, ConfigurationServer, @@ -114,8 +115,10 @@ export class SessionManager implements SessionStorage { config.user = username config.server = host - const users = config.users || [] - let user = users.find((entry) => entry.user?.toLowerCase() === username.toLowerCase()) + const users: Array = config.users || [] + let user: ConfigurationUser | undefined = users.find( + (entry: ConfigurationUser) => entry.user?.toLowerCase() === username.toLowerCase() + ) if (!user) { user = { user: username } users.push(user) @@ -124,8 +127,10 @@ export class SessionManager implements SessionStorage { user.last_device = { device_token: config.device_token } config.users = users - const devices = config.devices || [] - let device = devices.find((entry) => entry.device_token === config.device_token) + const devices: Array = config.devices || [] + let device: ConfigurationDeviceConfig | undefined = devices.find( + (entry: ConfigurationDeviceConfig) => entry.device_token === config.device_token + ) if (!device) { device = { device_token: config.device_token } devices.push(device) @@ -137,7 +142,7 @@ export class SessionManager implements SessionStorage { device.server_info = device.server_info || [] config.devices = devices - const servers = config.servers || [] + const servers: Array = config.servers || [] if (!servers.some((entry) => entry.server === host)) { const server: ConfigurationServer = { server: host } servers.push(server) From 07cd41ff82c1b83b5ce4bfbc92585fc91fd3c9cc Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Thu, 8 Oct 2026 12:53:15 +0530 Subject: [PATCH 6/9] Format fix --- KeeperSdk/src/auth/config.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/KeeperSdk/src/auth/config.ts b/KeeperSdk/src/auth/config.ts index 5c9be0e6..3c8e2eca 100644 --- a/KeeperSdk/src/auth/config.ts +++ b/KeeperSdk/src/auth/config.ts @@ -50,15 +50,19 @@ function hasOptionalStringFields(value: Record, fields: string[ function isValidUser(value: unknown): value is ConfigurationUser { if (!isRecord(value) || !hasOptionalStringFields(value, ['user', 'server'])) return false - return value.last_device === undefined || + return ( + value.last_device === undefined || (isRecord(value.last_device) && hasOptionalStringFields(value.last_device, ['device_token'])) + ) } function isValidServer(value: unknown): value is ConfigurationServer { - return isRecord(value) && + return ( + isRecord(value) && hasOptionalStringFields(value, ['server']) && (value.server_key_id === undefined || (typeof value.server_key_id === 'number' && Number.isFinite(value.server_key_id))) + ) } function isValidServerConfig(value: unknown): value is ConfigurationServerConfig { @@ -69,8 +73,10 @@ function isValidDevice(value: unknown): value is ConfigurationDeviceConfig { if (!isRecord(value) || !hasOptionalStringFields(value, ['device_token', 'private_key', 'public_key'])) { return false } - return value.server_info === undefined || + return ( + value.server_info === undefined || (Array.isArray(value.server_info) && value.server_info.every(isValidServerConfig)) + ) } export function isValidKeeperConfig(value: unknown): value is KeeperJsonConfig { @@ -78,7 +84,9 @@ export function isValidKeeperConfig(value: unknown): value is KeeperJsonConfig { return false } if (!hasOptionalStringFields(value, ['device_token', 'private_key', 'clone_code'])) return false - return (value.users === undefined || (Array.isArray(value.users) && value.users.every(isValidUser))) && + return ( + (value.users === undefined || (Array.isArray(value.users) && value.users.every(isValidUser))) && (value.servers === undefined || (Array.isArray(value.servers) && value.servers.every(isValidServer))) && (value.devices === undefined || (Array.isArray(value.devices) && value.devices.every(isValidDevice))) + ) } From ced2994d702d8042725ec593f73ea4471c8d4582 Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Mon, 12 Oct 2026 10:50:24 +0530 Subject: [PATCH 7/9] 2fa fix --- KeeperSdk/src/auth/ConsoleLogin.ts | 2 +- examples/sdk_example/src/auth/set_persistent_login.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/KeeperSdk/src/auth/ConsoleLogin.ts b/KeeperSdk/src/auth/ConsoleLogin.ts index 0d57fc57..e0eba8dc 100644 --- a/KeeperSdk/src/auth/ConsoleLogin.ts +++ b/KeeperSdk/src/auth/ConsoleLogin.ts @@ -169,7 +169,7 @@ export async function resolveServer(username?: string, preloadedConfig?: KeeperJ entries.forEach(([region, host], i) => { logger.info(` ${i + 1}. ${region} (${host})`) }) - logger.info(` Or enter a hostname directly (e.g. dev.keepersecurity.com)`) + logger.info(` Or enter server details directly (e.g. dev.keepersecurity.com)`) const choice = await prompt(`Region [1 = ${DEFAULT_REGION}]: `) if (!choice) return KEEPER_PUBLIC_HOSTS[DEFAULT_REGION] diff --git a/examples/sdk_example/src/auth/set_persistent_login.ts b/examples/sdk_example/src/auth/set_persistent_login.ts index c42ba64f..4007b787 100644 --- a/examples/sdk_example/src/auth/set_persistent_login.ts +++ b/examples/sdk_example/src/auth/set_persistent_login.ts @@ -4,7 +4,6 @@ import { loadKeeperConfig, resolveServer, prompt, - suppressLogs, cleanup, logger, extractResultCode, @@ -48,13 +47,10 @@ async function setPersistentLogin() { ResultCodes.MISSING_PASSWORD, ); - const restore = suppressLogs(); try { await vault.login(username, password); - restore(); break; } catch (err) { - restore(); const resultCode = extractResultCode(err); if (resultCode === ResultCodes.INVALID_CREDENTIALS) { const remaining = MAX_ATTEMPTS - attempt; From 3f992a243cc32fe51c7c9c29b9fd4e8b51c429d3 Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Wed, 14 Oct 2026 11:19:15 +0530 Subject: [PATCH 8/9] Change default persistent login to 1 day --- KeeperSdk/src/auth/PersistentLogin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KeeperSdk/src/auth/PersistentLogin.ts b/KeeperSdk/src/auth/PersistentLogin.ts index 1e2e458a..4e4bb08a 100644 --- a/KeeperSdk/src/auth/PersistentLogin.ts +++ b/KeeperSdk/src/auth/PersistentLogin.ts @@ -8,7 +8,7 @@ import { } from '@keeper-security/keeperapi' import { extractResultCode } from '../utils' -export const DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES: number = 30 * 24 * 60 +export const DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES: number = 24 * 60 function ensureDeviceConfig(auth: Auth): DeviceConfig { const deviceConfig: DeviceConfig = auth.options.deviceConfig From daacd617ee2a40c0192659062a34c381565ab43b Mon Sep 17 00:00:00 2001 From: ukumar-ks Date: Wed, 14 Oct 2026 11:37:42 +0530 Subject: [PATCH 9/9] change default persistent login timeout to 1 hour --- KeeperSdk/src/auth/PersistentLogin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KeeperSdk/src/auth/PersistentLogin.ts b/KeeperSdk/src/auth/PersistentLogin.ts index 4e4bb08a..87b4bf94 100644 --- a/KeeperSdk/src/auth/PersistentLogin.ts +++ b/KeeperSdk/src/auth/PersistentLogin.ts @@ -8,7 +8,7 @@ import { } from '@keeper-security/keeperapi' import { extractResultCode } from '../utils' -export const DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES: number = 24 * 60 +export const DEFAULT_PERSISTENT_LOGIN_TIMEOUT_MINUTES: number = 60 function ensureDeviceConfig(auth: Auth): DeviceConfig { const deviceConfig: DeviceConfig = auth.options.deviceConfig