Skip to content
Open
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
38 changes: 37 additions & 1 deletion KeeperSdk/src/auth/SessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ 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 {
KeeperJsonConfig,
ConfigLoader,
ConfigurationUser,
ConfigurationServerConfig,
ConfigurationServer,
ConfigurationDeviceConfig,
} from './config'

Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions KeeperSdk/src/auth/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,6 +29,7 @@ export type KeeperJsonConfig = {
private_key?: string
clone_code?: string
users?: Array<ConfigurationUser>
servers?: Array<ConfigurationServer>
devices?: Array<ConfigurationDeviceConfig>
}

Expand All @@ -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<string, unknown>
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
}
31 changes: 26 additions & 5 deletions KeeperSdk/src/auth/node/FileConfigLoader.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<KeeperJsonConfig> {
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<void> {
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,
})
}
Expand Down
5 changes: 3 additions & 2 deletions KeeperSdk/src/vault/KeeperVault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
Expand Down
Loading