Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KSM-489 - JavaScript SDK: Added transaction support #562

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
3 changes: 2 additions & 1 deletion sdk/javascript/packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ For more information see our official documentation page https://docs.keeper.io/

# Change Log
## 16.6.3
- KSM-489 - Added transaction support for updateSecret
- KSM-521 - Dependencies upgrade

## 16.6.2
Expand Down Expand Up @@ -38,4 +39,4 @@ For more information see our official documentation page https://docs.keeper.io/
## 16.3.3

- KSM-273 - Avoid reliance on external package for file upload with Node
- Added support to Japan `JP` and Canada `CA` regions
- Added support to Japan `JP` and Canada `CA` regions
42 changes: 38 additions & 4 deletions sdk/javascript/packages/core/src/keeper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,17 @@ export type CreateOptions = {
subFolderUid?: string
}

export enum UpdateTransactionType {
General = "general",
Rotation = "rotation"
}

type AnyPayload =
GetPayload
| DeletePayload
| DeleteFolderPayload
| UpdatePayload
| CompleteTransactionPayload
| CreatePayload
| CreateFolderPayload
| UpdateFolderPayload
Expand Down Expand Up @@ -89,6 +95,11 @@ type UpdatePayload = CommonPayload & {
recordUid: string
data: string
revision: number
transactionType?: UpdateTransactionType
}

type CompleteTransactionPayload = CommonPayload & {
recordUid: string
}

type CreatePayload = CommonPayload & {
Expand Down Expand Up @@ -251,20 +262,37 @@ const prepareGetPayload = async (storage: KeyValueStorage, queryOptions?: QueryO
return payload
}

const prepareUpdatePayload = async (storage: KeyValueStorage, record: KeeperRecord): Promise<UpdatePayload> => {
const prepareUpdatePayload = async (storage: KeyValueStorage, record: KeeperRecord, transactionType?: UpdateTransactionType): Promise<UpdatePayload> => {
const clientId = await storage.getString(KEY_CLIENT_ID)
if (!clientId) {
throw new Error('Client Id is missing from the configuration')
}
const recordBytes = platform.stringToBytes(JSON.stringify(record.data))
const encryptedRecord = await platform.encrypt(recordBytes, record.recordUid)
return {
const payload: UpdatePayload = {
clientVersion: 'ms' + packageVersion,
clientId: clientId,
recordUid: record.recordUid,
data: webSafe64FromBytes(encryptedRecord),
revision: record.revision
}
if (transactionType) {
payload.transactionType = transactionType
}
return payload
}

const prepareCompleteTransactionPayload = async (storage: KeyValueStorage, recordUid: string): Promise<CompleteTransactionPayload> => {
const clientId = await storage.getString(KEY_CLIENT_ID)
if (!clientId) {
throw new Error('Client Id is missing from the configuration')
}
const payload: CompleteTransactionPayload = {
clientVersion: 'ms' + packageVersion,
clientId: clientId,
recordUid: recordUid,
}
return payload
}

const prepareDeletePayload = async (storage: KeyValueStorage, recordUids: string[]): Promise<DeletePayload> => {
Expand Down Expand Up @@ -867,11 +895,17 @@ export const getSecretByTitle = async (options: SecretManagerOptions, recordTitl
return secrets.records.find(record => record.data.title === recordTitle)
}

export const updateSecret = async (options: SecretManagerOptions, record: KeeperRecord): Promise<void> => {
const payload = await prepareUpdatePayload(options.storage, record)
export const updateSecret = async (options: SecretManagerOptions, record: KeeperRecord, transactionType?: UpdateTransactionType): Promise<void> => {
const payload = await prepareUpdatePayload(options.storage, record, transactionType)
await postQuery(options, 'update_secret', payload)
}

export const completeTransaction = async (options: SecretManagerOptions, recordUid: string, rollback: boolean = false): Promise<void> => {
const payload = await prepareCompleteTransactionPayload(options.storage, recordUid)
const route = (rollback ? "rollback_secret_update" : "finalize_secret_update")
await postQuery(options, route, payload)
}

export const deleteSecret = async (options: SecretManagerOptions, recordUids: string[]): Promise<SecretsManagerDeleteResponse> => {
const payload = await prepareDeletePayload(options.storage, recordUids)
const responseData = await postQuery(options, 'delete_secret', payload)
Expand Down
19 changes: 9 additions & 10 deletions sdk/javascript/packages/core/src/node/nodePlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const sign = async (data: Uint8Array, keyId: string, storage: KeyValueStorage):
const sign = createSign('SHA256')
sign.update(data)
const sig = sign.sign(key)
return Promise.resolve(sig)
return sig
}

const importKey = async (keyId: string, key: Uint8Array, storage?: KeyValueStorage): Promise<void> => {
Expand All @@ -98,29 +98,28 @@ const importKey = async (keyId: string, key: Uint8Array, storage?: KeyValueStora

const encrypt = async (data: Uint8Array, keyId: string, storage?: KeyValueStorage, useCBC?: boolean): Promise<Uint8Array> => {
const key = await loadKey(keyId, storage)
return _encrypt(data, key, useCBC)
return await _encrypt(data, key, useCBC)
}

const _encrypt = (data: Uint8Array, key: Uint8Array, useCBC?: boolean): Promise<Uint8Array> => {
const _encrypt = async (data: Uint8Array, key: Uint8Array, useCBC?: boolean): Promise<Uint8Array> => {
if (useCBC) {
return _encryptCBC(data, key)
}
const iv = randomBytes(12)
const cipher = createCipheriv('aes-256-gcm', key, iv)
const encrypted = Buffer.concat([cipher.update(data), cipher.final()])
const tag = cipher.getAuthTag()
const result = Buffer.concat([iv, encrypted, tag])
return Promise.resolve(result)
return Buffer.concat([iv, encrypted, tag])
}

const _encryptCBC = async (data: Uint8Array, key: Uint8Array): Promise<Uint8Array> => {
const _encryptCBC = (data: Uint8Array, key: Uint8Array): Uint8Array => {
let iv = randomBytes(16);
let cipher = createCipheriv("aes-256-cbc", key, iv).setAutoPadding(true);
let encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
return Buffer.concat([iv, encrypted]);
}

const _decrypt = (data: Uint8Array, key: Uint8Array, useCBC?: boolean): Promise<Uint8Array> => {
const _decrypt = async (data: Uint8Array, key: Uint8Array, useCBC?: boolean): Promise<Uint8Array> => {
if (useCBC) {
return _decryptCBC(data, key)
}
Expand All @@ -129,10 +128,10 @@ const _decrypt = (data: Uint8Array, key: Uint8Array, useCBC?: boolean): Promise<
const tag = data.subarray(data.length - 16)
const cipher = createDecipheriv('aes-256-gcm', key, iv)
cipher.setAuthTag(tag)
return Promise.resolve(Buffer.concat([cipher.update(encrypted), cipher.final()]))
return Buffer.concat([cipher.update(encrypted), cipher.final()])
}

const _decryptCBC = async (data: Uint8Array, key: Uint8Array): Promise<Uint8Array> => {
const _decryptCBC = (data: Uint8Array, key: Uint8Array): Uint8Array => {
let iv = data.subarray(0, 16)
let encrypted = data.subarray(16)
let cipher = createDecipheriv("aes-256-cbc", key, iv).setAutoPadding(true)
Expand All @@ -153,7 +152,7 @@ const unwrap = async (key: Uint8Array, keyId: string, unwrappingKeyId: string, s

const decrypt = async (data: Uint8Array, keyId: string, storage?: KeyValueStorage, useCBC?: boolean): Promise<Uint8Array> => {
const key = await loadKey(keyId, storage)
return _decrypt(data, key, useCBC)
return await _decrypt(data, key, useCBC)
}

function hash(data: Uint8Array): Promise<Uint8Array> {
Expand Down