Skip to content

Commit

Permalink
[SM-1086] Improve TS bindings (#398)
Browse files Browse the repository at this point in the history
## Type of change
```
- [ ] Bug fix
- [ ] New feature development
- [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
```

## Objective
Implement support for projects, accesstokenlogin and response
unwrapping. Also added a small example
  • Loading branch information
dani-garcia authored Feb 8, 2024
1 parent 400a173 commit 575dd19
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 82 deletions.
32 changes: 32 additions & 0 deletions languages/js/example/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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();
34 changes: 34 additions & 0 deletions languages/js/example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions languages/js/example/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "sdk-example",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@bitwarden/sdk-client": "../sdk-client",
"@bitwarden/sdk-wasm": "../wasm"
}
}
185 changes: 111 additions & 74 deletions languages/js/sdk-client/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,50 @@
import {
Convert,
ResponseForFingerprintResponse,
ResponseForPasswordLoginResponse,
ResponseForSecretIdentifiersResponse,
ResponseForSecretResponse,
ResponseForSecretsDeleteResponse,
ResponseForSyncResponse,
ResponseForUserAPIKeyResponse,
ProjectResponse,
ProjectsDeleteResponse,
ProjectsResponse,
SecretIdentifiersResponse,
SecretResponse,
SecretsDeleteResponse,
} from "./schemas";

interface BitwardenSDKClient {
run_command(js_input: string): Promise<any>;
}

function handleResponse<T>(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;

constructor(client: BitwardenSDKClient) {
this.client = client;
}

async login(email: string, password: string, pbkdf_iter: number): Promise<ResponseForPasswordLoginResponse> {
async accessTokenLogin(accessToken: string): Promise<void> {
const response = await this.client.run_command(
Convert.commandToJson({
passwordLogin: {
email: email,
password: password,
kdf: {
pBKDF2: {
iterations: pbkdf_iter,
}
},
accessTokenLogin: {
accessToken,
},
})
}),
);

return Convert.toResponseForPasswordLoginResponse(response);
handleResponse(Convert.toResponseForAccessTokenLoginResponse(response));
}

async getUserApiKey(
secret: string,
isOtp: boolean = false
): Promise<ResponseForUserAPIKeyResponse> {
const response = await this.client.run_command(
Convert.commandToJson({
getUserApiKey: {
masterPassword: isOtp ? null : secret,
otp: isOtp ? secret : null,
},
})
);

return Convert.toResponseForUserAPIKeyResponse(response);
secrets(): SecretsClient {
return new SecretsClient(this.client);
}

async sync(excludeSubdomains: boolean = false): Promise<ResponseForSyncResponse> {
const response = await this.client.run_command(
Convert.commandToJson({
sync: {
excludeSubdomains,
},
})
);

return Convert.toResponseForSyncResponse(response);
projects(): ProjectsClient {
return new ProjectsClient(this.client);
}

async fingerprint(fingerprintMaterial: string, publicKey: string): Promise<string> {
const response = await this.client.run_command(
Convert.commandToJson({
fingerprint: {
fingerprintMaterial: fingerprintMaterial,
publicKey: publicKey,
}
})
)

return Convert.toResponseForFingerprintResponse(response).data.fingerprint;
};
}

export class SecretsClient {
Expand All @@ -87,74 +54,144 @@ export class SecretsClient {
this.client = client;
}

async get(id: string): Promise<ResponseForSecretResponse> {
async get(id: string): Promise<SecretResponse> {
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,
projectIds: string[],
organizationId: string,
value: string
): Promise<ResponseForSecretResponse> {
): Promise<SecretResponse> {
const response = await this.client.run_command(
Convert.commandToJson({
secrets: {
create: { key, note, organizationId, value },
create: { key, value, note, projectIds, organizationId },
},
})
}),
);

return Convert.toResponseForSecretResponse(response);
return handleResponse(Convert.toResponseForSecretResponse(response));
}

async list(organizationId: string): Promise<ResponseForSecretIdentifiersResponse> {
async list(organizationId: string): Promise<SecretIdentifiersResponse> {
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,
projectIds: string[],
organizationId: string,
value: string
): Promise<ResponseForSecretResponse> {
): Promise<SecretResponse> {
const response = await this.client.run_command(
Convert.commandToJson({
secrets: {
update: { id, key, note, organizationId, value },
update: { id, key, value, note, projectIds, organizationId },
},
})
}),
);

return Convert.toResponseForSecretResponse(response);
return handleResponse(Convert.toResponseForSecretResponse(response));
}

async delete(ids: string[]): Promise<ResponseForSecretsDeleteResponse> {
async delete(ids: string[]): Promise<SecretsDeleteResponse> {
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<ProjectResponse> {
const response = await this.client.run_command(
Convert.commandToJson({
projects: {
get: { id },
},
}),
);

return handleResponse(Convert.toResponseForProjectResponse(response));
}

async create(name: string, organizationId: string): Promise<ProjectResponse> {
const response = await this.client.run_command(
Convert.commandToJson({
projects: {
create: { name, organizationId },
},
}),
);

return handleResponse(Convert.toResponseForProjectResponse(response));
}

async list(organizationId: string): Promise<ProjectsResponse> {
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<ProjectResponse> {
const response = await this.client.run_command(
Convert.commandToJson({
projects: {
update: { id, name, organizationId },
},
}),
);

return handleResponse(Convert.toResponseForProjectResponse(response));
}

async delete(ids: string[]): Promise<ProjectsDeleteResponse> {
const response = await this.client.run_command(
Convert.commandToJson({
projects: {
delete: { ids },
},
}),
);

return Convert.toResponseForSecretsDeleteResponse(response);
return handleResponse(Convert.toResponseForProjectsDeleteResponse(response));
}
}
7 changes: 0 additions & 7 deletions languages/js/sdk-client/src/logging_level.ts

This file was deleted.

2 changes: 1 addition & 1 deletion languages/js/sdk-client/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"outDir": "./dist/",
"module": "esnext",
"module": "commonjs",
"target": "es5",
"moduleResolution": "node",
"sourceMap": true,
Expand Down

0 comments on commit 575dd19

Please sign in to comment.