Skip to content

Commit

Permalink
Feat: Keychain app password (#110)
Browse files Browse the repository at this point in the history
This PR adds support for the macOS keychain. Also, migrates any old-inline app-password to macOS keychain.
  • Loading branch information
joacoc authored Oct 24, 2023
1 parent 1103dfc commit 2e0eefd
Show file tree
Hide file tree
Showing 13 changed files with 544 additions and 245 deletions.
24 changes: 24 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
"devDependencies": {
"@types/chai": "^4.3.5",
"@types/glob": "^8.1.0",
"@types/keychain": "^1.4.3",
"@types/mocha": "^10.0.1",
"@types/node": "20.2.5",
"@types/tar": "^6.1.6",
Expand Down Expand Up @@ -198,6 +199,7 @@
"@types/uuid": "^9.0.2",
"@vscode/webview-ui-toolkit": "^1.2.2",
"jwks-rsa": "^3.0.1",
"keychain": "^1.5.0",
"node-fetch": "^3.3.1",
"node-jsonwebtoken": "^0.0.1",
"pg": "^8.10.0",
Expand Down
2 changes: 0 additions & 2 deletions src/clients/admin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fetch from "node-fetch";
import AppPassword from "../context/appPassword";
import { JwksError } from "jwks-rsa";
import { Errors } from "../utilities/error";
const jwksClient = require("jwks-rsa");
const jwt = require("node-jsonwebtoken");
Expand Down Expand Up @@ -107,7 +106,6 @@ export default class AdminClient {
claims = JSON.parse(claims);
}

console.log(claims);
if (!claims.email) {
throw new Error(Errors.emailNotPresentInClaims);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/clients/lsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export default class LspClient {
* Stops the LSP server client.
* This is useful before installing an upgrade.
*/
private stop() {
stop() {
this.client && this.client.stop();
}
}
53 changes: 18 additions & 35 deletions src/clients/sql.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import { Pool, QueryResult } from "pg";
import { randomUUID } from "crypto";
import { NonStorableConfigProfile } from "../context/config";
import { MaterializeObject } from "../providers/schema";
import AdminClient from "./admin";
import CloudClient from "./cloud";
import * as vscode from 'vscode';
import { Context, EventType } from "../context";
import { Profile } from "../context/config";

export default class SqlClient {
private pool: Promise<Pool>;
private adminClient: AdminClient;
private cloudClient: CloudClient;
private context: Context;
private profile: NonStorableConfigProfile;
private profile: Profile;

constructor(
adminClient: AdminClient,
cloudClient: CloudClient,
profile: NonStorableConfigProfile,
profile: Profile,
context: Context,
) {
this.adminClient = adminClient;
Expand Down Expand Up @@ -60,37 +57,41 @@ export default class SqlClient {
*/
private getConnectionOptions(): string {
const connectionOptions = [];
const environment = this.context.getEnvironment();

const cluster = this.profile.cluster;
if (cluster) {
connectionOptions.push(`--cluster=${cluster}`);
};
if (environment) {
connectionOptions.push(`--cluster=${environment.cluster}`);

const schema = this.profile.schema;
if (schema) {
connectionOptions.push(`-csearch_path=${schema}`);
// When a user changes the database, the schema turns undefined.
// Each database has a different set of schemas.
// To avoid having an invalid `search_path`, the schema
// is an empty string and should be avoided its usage.
if (environment.schema) {
connectionOptions.push(`-csearch_path=${environment.schema}`);
}
}

return connectionOptions.join(" ");
}

private async buildPoolConfig() {
console.log("[Context]", "Loading host.");
console.log("[SqlClient]", "Loading host.");
const hostPromise = this.cloudClient?.getHost(this.profile.region);
console.log("[Context]", "Loading user email.");
console.log("[SqlClient]", "Loading user email.");
const emailPromise = this.adminClient?.getEmail();

const [host, email] = await Promise.all([hostPromise, emailPromise]);
const environment = this.context.getEnvironment();

return {
host: host && host.substring(0, host.length - 5),
// eslint-disable-next-line @typescript-eslint/naming-convention
application_name: "mz_vscode",
database: (this.profile.database || "materialize").toString(),
database: ((environment && environment.database) || "materialize").toString(),
port: 6875,
user: email,
options: this.getConnectionOptions(),
password: this.profile["app-password"],
password: await this.context.getAppPassword(),
// Disable SSL for tests
ssl: (host && host.startsWith("localhost")) ? false : true,
};
Expand Down Expand Up @@ -135,22 +136,4 @@ export default class SqlClient {
client.release();
}
}


async getDatabases(): Promise<Array<MaterializeObject>> {
const { rows }: QueryResult<MaterializeObject> = await this.query(`SELECT id, name, owner_id as "ownerId" FROM mz_databases;`);
return rows;
}

async getSchemas(database: MaterializeObject) {
const { rows: schemas } = await this.query(`SELECT id, name, database_id as "databaseId", owner_id as "ownerId" FROM mz_schemas WHERE database_id = $1`, [database.id]);

return schemas;
}

async getClusters() {
const { rows: clusters }: QueryResult<MaterializeObject> = await this.query(`SELECT id, name, owner_id as "ownerId" FROM mz_clusters;`);

return clusters;
}
}
3 changes: 2 additions & 1 deletion src/context/appPassword.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as uuid from "uuid";
import { Errors } from "../utilities/error";

/// App-password prefix.
const PREFIX = 'mzp_';

export default class AppPassword {
Expand Down Expand Up @@ -64,7 +65,7 @@ export default class AppPassword {
secretKey,
};
} catch (err) {
console.log("Error parsing UUID.");
console.log("[AppPassword]", "Error parsing UUID.");
throw new Error(Errors.invalidAppPassword);
}
}
Expand Down
Loading

0 comments on commit 2e0eefd

Please sign in to comment.