From 76b3e879af5405c71d0a37010e5e49ed8b9b3834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garci=CC=81a?= Date: Mon, 4 Dec 2023 20:12:51 +0100 Subject: [PATCH 1/4] Improve TS bindings --- languages/js/sdk-client/src/client.ts | 164 +++++++++++++++---- languages/js/sdk-client/src/logging_level.ts | 7 - 2 files changed, 131 insertions(+), 40 deletions(-) delete mode 100644 languages/js/sdk-client/src/logging_level.ts diff --git a/languages/js/sdk-client/src/client.ts b/languages/js/sdk-client/src/client.ts index f77ba1420..8ce9d761c 100644 --- a/languages/js/sdk-client/src/client.ts +++ b/languages/js/sdk-client/src/client.ts @@ -1,18 +1,28 @@ import { + AccessTokenLoginResponse, Convert, - ResponseForFingerprintResponse, - ResponseForPasswordLoginResponse, - ResponseForSecretIdentifiersResponse, - ResponseForSecretResponse, - ResponseForSecretsDeleteResponse, - ResponseForSyncResponse, - ResponseForUserAPIKeyResponse, + PasswordLoginResponse, + ProjectResponse, + ProjectsDeleteResponse, + ProjectsResponse, + SecretIdentifiersResponse, + SecretResponse, + SecretsDeleteResponse, + SyncResponse, + UserAPIKeyResponse, } from "./schemas"; interface BitwardenSDKClient { run_command(js_input: string): Promise; } +function handleResponse(response: { success: boolean; errorMessage?: string; data?: T }): T { + if (!response.success) { + throw new Error(response.errorMessage); + } + return response.data as T; +} + export class BitwardenClient { client: BitwardenSDKClient; @@ -20,7 +30,19 @@ export class BitwardenClient { this.client = client; } - async login(email: string, password: string, pbkdf_iter: number): Promise { + async accessTokenLogin(accessToken: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + accessTokenLogin: { + accessToken, + }, + }), + ); + + return handleResponse(Convert.toResponseForAccessTokenLoginResponse(response)); + } + + async login(email: string, password: string, pbkdf_iter: number): Promise { const response = await this.client.run_command( Convert.commandToJson({ passwordLogin: { @@ -35,13 +57,13 @@ export class BitwardenClient { }) ); - return Convert.toResponseForPasswordLoginResponse(response); + return handleResponse(Convert.toResponseForPasswordLoginResponse(response)); } async getUserApiKey( secret: string, isOtp: boolean = false - ): Promise { + ): Promise { const response = await this.client.run_command( Convert.commandToJson({ getUserApiKey: { @@ -51,10 +73,10 @@ export class BitwardenClient { }) ); - return Convert.toResponseForUserAPIKeyResponse(response); + return handleResponse(Convert.toResponseForUserAPIKeyResponse(response)); } - async sync(excludeSubdomains: boolean = false): Promise { + async sync(excludeSubdomains: boolean = false): Promise { const response = await this.client.run_command( Convert.commandToJson({ sync: { @@ -63,7 +85,7 @@ export class BitwardenClient { }) ); - return Convert.toResponseForSyncResponse(response); + return handleResponse(Convert.toResponseForSyncResponse(response)); } async fingerprint(fingerprintMaterial: string, publicKey: string): Promise { @@ -76,8 +98,16 @@ export class BitwardenClient { }) ) - return Convert.toResponseForFingerprintResponse(response).data.fingerprint; + return handleResponse(Convert.toResponseForFingerprintResponse(response)).fingerprint; }; + + secrets(): SecretsClient { + return new SecretsClient(this.client); + } + + projects(): ProjectsClient { + return new ProjectsClient(this.client); + } } export class SecretsClient { @@ -87,74 +117,142 @@ export class SecretsClient { this.client = client; } - async get(id: string): Promise { + async get(id: string): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { get: { id }, }, - }) + }), ); - return Convert.toResponseForSecretResponse(response); + return handleResponse(Convert.toResponseForSecretResponse(response)); } async create( key: string, + value: string, note: string, organizationId: string, - value: string - ): Promise { + ): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { - create: { key, note, organizationId, value }, + create: { key, value, note, organizationId }, }, - }) + }), ); - return Convert.toResponseForSecretResponse(response); + return handleResponse(Convert.toResponseForSecretResponse(response)); } - async list(organizationId: string): Promise { + async list(organizationId: string): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { list: { organizationId }, }, - }) + }), ); - return Convert.toResponseForSecretIdentifiersResponse(response); + return handleResponse(Convert.toResponseForSecretIdentifiersResponse(response)); } async update( id: string, key: string, + value: string, note: string, organizationId: string, - value: string - ): Promise { + ): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { - update: { id, key, note, organizationId, value }, + update: { id, key, value, note, organizationId }, }, - }) + }), ); - return Convert.toResponseForSecretResponse(response); + return handleResponse(Convert.toResponseForSecretResponse(response)); } - async delete(ids: string[]): Promise { + async delete(ids: string[]): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { delete: { ids }, }, - }) + }), + ); + + return handleResponse(Convert.toResponseForSecretsDeleteResponse(response)); + } +} + +export class ProjectsClient { + client: BitwardenSDKClient; + + constructor(client: BitwardenSDKClient) { + this.client = client; + } + + async get(id: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + get: { id }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectResponse(response)); + } + + async create(name: string, organizationId: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + create: { name, organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectResponse(response)); + } + + async list(organizationId: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + list: { organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectsResponse(response)); + } + + async update(id: string, name: string, organizationId: string): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + update: { id, name, organizationId }, + }, + }), + ); + + return handleResponse(Convert.toResponseForProjectResponse(response)); + } + + async delete(ids: string[]): Promise { + const response = await this.client.run_command( + Convert.commandToJson({ + projects: { + delete: { ids }, + }, + }), ); - return Convert.toResponseForSecretsDeleteResponse(response); + return handleResponse(Convert.toResponseForProjectsDeleteResponse(response)); } } diff --git a/languages/js/sdk-client/src/logging_level.ts b/languages/js/sdk-client/src/logging_level.ts deleted file mode 100644 index 2453b1eb6..000000000 --- a/languages/js/sdk-client/src/logging_level.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum LoggingLevel { - Trace, - Debug, - Info, - Warn, - Error, -} From 840b01ffaf02d4146ccc8ced80a51b8ba57353fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garci=CC=81a?= Date: Thu, 11 Jan 2024 12:35:10 +0100 Subject: [PATCH 2/4] Review feedback --- languages/js/sdk-client/src/client.ts | 67 +-------------------------- 1 file changed, 2 insertions(+), 65 deletions(-) diff --git a/languages/js/sdk-client/src/client.ts b/languages/js/sdk-client/src/client.ts index 8ce9d761c..2f9098596 100644 --- a/languages/js/sdk-client/src/client.ts +++ b/languages/js/sdk-client/src/client.ts @@ -1,15 +1,11 @@ import { - AccessTokenLoginResponse, Convert, - PasswordLoginResponse, ProjectResponse, ProjectsDeleteResponse, ProjectsResponse, SecretIdentifiersResponse, SecretResponse, SecretsDeleteResponse, - SyncResponse, - UserAPIKeyResponse, } from "./schemas"; interface BitwardenSDKClient { @@ -30,7 +26,7 @@ export class BitwardenClient { this.client = client; } - async accessTokenLogin(accessToken: string): Promise { + async accessTokenLogin(accessToken: string): Promise { const response = await this.client.run_command( Convert.commandToJson({ accessTokenLogin: { @@ -39,68 +35,9 @@ export class BitwardenClient { }), ); - return handleResponse(Convert.toResponseForAccessTokenLoginResponse(response)); + handleResponse(Convert.toResponseForAccessTokenLoginResponse(response)); } - async login(email: string, password: string, pbkdf_iter: number): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - passwordLogin: { - email: email, - password: password, - kdf: { - pBKDF2: { - iterations: pbkdf_iter, - } - }, - }, - }) - ); - - return handleResponse(Convert.toResponseForPasswordLoginResponse(response)); - } - - async getUserApiKey( - secret: string, - isOtp: boolean = false - ): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - getUserApiKey: { - masterPassword: isOtp ? null : secret, - otp: isOtp ? secret : null, - }, - }) - ); - - return handleResponse(Convert.toResponseForUserAPIKeyResponse(response)); - } - - async sync(excludeSubdomains: boolean = false): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - sync: { - excludeSubdomains, - }, - }) - ); - - return handleResponse(Convert.toResponseForSyncResponse(response)); - } - - async fingerprint(fingerprintMaterial: string, publicKey: string): Promise { - const response = await this.client.run_command( - Convert.commandToJson({ - fingerprint: { - fingerprintMaterial: fingerprintMaterial, - publicKey: publicKey, - } - }) - ) - - return handleResponse(Convert.toResponseForFingerprintResponse(response)).fingerprint; - }; - secrets(): SecretsClient { return new SecretsClient(this.client); } From ddaf0f9564f08d8272034f383a781225861a8aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garci=CC=81a?= Date: Fri, 26 Jan 2024 17:34:12 +0100 Subject: [PATCH 3/4] Add small JS example --- languages/js/example/index.js | 31 +++++++++++++++++++++++ languages/js/example/package-lock.json | 34 ++++++++++++++++++++++++++ languages/js/example/package.json | 12 +++++++++ languages/js/sdk-client/src/client.ts | 6 +++-- languages/js/sdk-client/tsconfig.json | 2 +- 5 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 languages/js/example/index.js create mode 100644 languages/js/example/package-lock.json create mode 100644 languages/js/example/package.json diff --git a/languages/js/example/index.js b/languages/js/example/index.js new file mode 100644 index 000000000..ca089ec72 --- /dev/null +++ b/languages/js/example/index.js @@ -0,0 +1,31 @@ + +const { BitwardenClient: BitwardenClientWasm, LogLevel } = require('@bitwarden/sdk-wasm'); +const sdk = require('@bitwarden/sdk-client'); + + +async function main() { + const settings = { + apiUrl: process.env.API_URL, + identityUrl: process.env.IDENTITY_URL + }; + + const client = new sdk.BitwardenClient(new BitwardenClientWasm(JSON.stringify(settings), LogLevel.Debug)); + + const organization_id = process.env.ORGANIZATION_ID; + await client.accessTokenLogin(process.env.ACCESS_TOKEN); + + + const project = await client.projects().create('test', organization_id); + const projects = await client.projects().list(organization_id); + console.log(projects.data); + + const secret = await client.secrets().create('test-secret', "My secret!", "This is my secret", [project.id], organization_id); + const secrets = await client.secrets().list(organization_id); + console.log(secrets.data); + + console.log(project, secret); + + await client.projects().delete([project.id]); + await client.secrets().delete([secret.id]); +} +main(); \ No newline at end of file diff --git a/languages/js/example/package-lock.json b/languages/js/example/package-lock.json new file mode 100644 index 000000000..f6cab6536 --- /dev/null +++ b/languages/js/example/package-lock.json @@ -0,0 +1,34 @@ +{ + "name": "sdk-example", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sdk-example", + "dependencies": { + "@bitwarden/sdk-client": "../sdk-client", + "@bitwarden/sdk-wasm": "../wasm" + } + }, + "../sdk-client": { + "name": "@bitwarden/sdk-client", + "devDependencies": { + "@types/node": "^18.15.11", + "rimraf": "^5.0.0", + "typescript": "^5.0.3" + } + }, + "../wasm": { + "name": "@bitwarden/sdk-wasm", + "version": "0.1.0" + }, + "node_modules/@bitwarden/sdk-client": { + "resolved": "../sdk-client", + "link": true + }, + "node_modules/@bitwarden/sdk-wasm": { + "resolved": "../wasm", + "link": true + } + } +} diff --git a/languages/js/example/package.json b/languages/js/example/package.json new file mode 100644 index 000000000..49a0cc2cb --- /dev/null +++ b/languages/js/example/package.json @@ -0,0 +1,12 @@ +{ + "name": "sdk-example", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@bitwarden/sdk-client": "../sdk-client", + "@bitwarden/sdk-wasm": "../wasm" + } + } + \ No newline at end of file diff --git a/languages/js/sdk-client/src/client.ts b/languages/js/sdk-client/src/client.ts index 2f9098596..0f06889c1 100644 --- a/languages/js/sdk-client/src/client.ts +++ b/languages/js/sdk-client/src/client.ts @@ -70,12 +70,13 @@ export class SecretsClient { key: string, value: string, note: string, + projectIds: string[], organizationId: string, ): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { - create: { key, value, note, organizationId }, + create: { key, value, note, projectIds, organizationId }, }, }), ); @@ -100,12 +101,13 @@ export class SecretsClient { key: string, value: string, note: string, + projectIds: string[], organizationId: string, ): Promise { const response = await this.client.run_command( Convert.commandToJson({ secrets: { - update: { id, key, value, note, organizationId }, + update: { id, key, value, note, projectIds, organizationId }, }, }), ); diff --git a/languages/js/sdk-client/tsconfig.json b/languages/js/sdk-client/tsconfig.json index 453623d5f..987e8d679 100644 --- a/languages/js/sdk-client/tsconfig.json +++ b/languages/js/sdk-client/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "outDir": "./dist/", - "module": "esnext", + "module": "commonjs", "target": "es5", "moduleResolution": "node", "sourceMap": true, From d6c6c9726d8ed16e9f721f4b4c2a1a2617bca61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garci=CC=81a?= Date: Mon, 29 Jan 2024 11:11:24 +0100 Subject: [PATCH 4/4] Prettier formatting --- languages/js/example/index.js | 45 ++++++++++++++++--------------- languages/js/example/package.json | 19 +++++++------ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/languages/js/example/index.js b/languages/js/example/index.js index ca089ec72..8da3fc582 100644 --- a/languages/js/example/index.js +++ b/languages/js/example/index.js @@ -1,31 +1,32 @@ - -const { BitwardenClient: BitwardenClientWasm, LogLevel } = require('@bitwarden/sdk-wasm'); -const sdk = require('@bitwarden/sdk-client'); - +const { BitwardenClient: BitwardenClientWasm, LogLevel } = require("@bitwarden/sdk-wasm"); +const sdk = require("@bitwarden/sdk-client"); async function main() { - const settings = { - apiUrl: process.env.API_URL, - identityUrl: process.env.IDENTITY_URL - }; - - const client = new sdk.BitwardenClient(new BitwardenClientWasm(JSON.stringify(settings), LogLevel.Debug)); + const settings = { + apiUrl: process.env.API_URL, + identityUrl: process.env.IDENTITY_URL, + }; - const organization_id = process.env.ORGANIZATION_ID; - await client.accessTokenLogin(process.env.ACCESS_TOKEN); + const client = new sdk.BitwardenClient( + new BitwardenClientWasm(JSON.stringify(settings), LogLevel.Debug), + ); + const organization_id = process.env.ORGANIZATION_ID; + await client.accessTokenLogin(process.env.ACCESS_TOKEN); - const project = await client.projects().create('test', organization_id); - const projects = await client.projects().list(organization_id); - console.log(projects.data); + const project = await client.projects().create("test", organization_id); + const projects = await client.projects().list(organization_id); + console.log(projects.data); - const secret = await client.secrets().create('test-secret', "My secret!", "This is my secret", [project.id], organization_id); - const secrets = await client.secrets().list(organization_id); - console.log(secrets.data); + const secret = await client + .secrets() + .create("test-secret", "My secret!", "This is my secret", [project.id], organization_id); + const secrets = await client.secrets().list(organization_id); + console.log(secrets.data); - console.log(project, secret); + console.log(project, secret); - await client.projects().delete([project.id]); - await client.secrets().delete([secret.id]); + await client.projects().delete([project.id]); + await client.secrets().delete([secret.id]); } -main(); \ No newline at end of file +main(); diff --git a/languages/js/example/package.json b/languages/js/example/package.json index 49a0cc2cb..836e53085 100644 --- a/languages/js/example/package.json +++ b/languages/js/example/package.json @@ -1,12 +1,11 @@ { - "name": "sdk-example", - "main": "index.js", - "scripts": { - "start": "node index.js" - }, - "dependencies": { - "@bitwarden/sdk-client": "../sdk-client", - "@bitwarden/sdk-wasm": "../wasm" - } + "name": "sdk-example", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@bitwarden/sdk-client": "../sdk-client", + "@bitwarden/sdk-wasm": "../wasm" } - \ No newline at end of file +}