From 75f8e034fb906d105f438d46e6190cdcc58b77ed Mon Sep 17 00:00:00 2001 From: Roshan Patil Date: Sat, 2 Mar 2024 20:30:06 +0100 Subject: [PATCH] Command Line Support for Password Retrieval --- src/commands/addConnection.ts | 51 ++++++++++++++++++++++++++++++----- src/common/IConnection.ts | 1 + src/common/database.ts | 20 ++++++++++++-- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/commands/addConnection.ts b/src/commands/addConnection.ts index 536a28f..3b9f010 100644 --- a/src/commands/addConnection.ts +++ b/src/commands/addConnection.ts @@ -15,6 +15,10 @@ interface SSLQuickPickItem extends vscode.QuickPickItem { ssl: boolean } +interface PasswordTypeQuickPickItem extends vscode.QuickPickItem { + passwordType: string +} + interface DatabaseQuickPickItem extends vscode.QuickPickItem { dbname?: string } @@ -23,11 +27,15 @@ const sslOptions: SSLQuickPickItem[] = [ {label: 'Use Secure Connection', ssl: true}, {label: 'Standard Connection', ssl: false} ] +const passwordTypeOptions: PasswordTypeQuickPickItem[] = [ + {label: 'Plain text', passwordType: "PLAIN"}, + {label: 'Password from command line', passwordType: "CMD"} +] export class addConnectionCommand extends BaseCommand { readonly TITLE: string = 'Add Database Connection'; - readonly TotalSteps: number = 7; + readonly TotalSteps: number = 8; async run() { const state = {port: 5432} as Partial; @@ -49,7 +57,8 @@ export class addConnectionCommand extends BaseCommand { user: state.user, port: state.port, ssl: state.secure, - database: state.database + database: state.database, + passwordGenerationCommand: state.passwordGenerationCommand }; connections[id].hasPassword = !!state.password; @@ -87,7 +96,20 @@ export class addConnectionCommand extends BaseCommand { value: (typeof state.user === 'string') ? state.user : '', validate: async (value) => (!value || !value.trim()) ? 'Username is required' : '' }); - return (input: MultiStepInput) => this.setPassword(input, state); + return (input: MultiStepInput) => this.passwordType(input, state); + } + + async passwordType(input: MultiStepInput, state: Partial) { + let passwordType = await input.showQuickPick({ + title: this.TITLE, + step: input.CurrentStepNumber, + totalSteps: this.TotalSteps, + placeholder: 'Select Passward Type', + ignoreFocusOut: true, + items: passwordTypeOptions, + convert: async (value: PasswordTypeQuickPickItem) => value.passwordType + }); + return (input: MultiStepInput) => passwordType === "PLAIN"? this.setPassword(input,state): this.setPasswordGeneratorCommand(input, state) ; } async setPassword(input: MultiStepInput, state: Partial) { @@ -102,6 +124,20 @@ export class addConnectionCommand extends BaseCommand { value: (typeof state.password === 'string') ? state.password : '', validate: async (value) => '' }); + return (input: MultiStepInput) => this.setPasswordGeneratorCommand(input, state); + } + + async setPasswordGeneratorCommand(input: MultiStepInput, state: Partial) { + state.passwordGenerationCommand = await input.showInputBox({ + title: this.TITLE, + step: input.CurrentStepNumber, + totalSteps: this.TotalSteps, + prompt: 'If the password is generated by a command, enter the command here. The command should output the password to stdout', + placeholder: 'ex. cat /path/to/password.txt', + ignoreFocusOut: true, + value: (typeof state.passwordGenerationCommand === 'string') ? state.passwordGenerationCommand : '', + validate: async (value) => '' + }); return (input: MultiStepInput) => this.setPort(input, state); } @@ -153,7 +189,8 @@ export class addConnectionCommand extends BaseCommand { user: state.user, password: state.password, port: state.port, - ssl: state.secure + ssl: state.secure, + passwordGenerationCommand: state.passwordGenerationCommand }, 'postgres'); const res = await connection.query('SELECT datname FROM pg_database WHERE datistemplate = false;'); databases = res.rows.map(database => ({label: database.datname, dbname: database.datname})); @@ -194,7 +231,8 @@ export class addConnectionCommand extends BaseCommand { user: state.user, password: state.password, port: state.port, - ssl: state.secure + ssl: state.secure, + passwordGenerationCommand: state.passwordGenerationCommand }, databaseToTry); connectionOK = true; } catch(err) { @@ -253,5 +291,6 @@ interface ConnectionState { password: string; port: number; database: string; - secure: boolean + secure: boolean; + passwordGenerationCommand: string; } \ No newline at end of file diff --git a/src/common/IConnection.ts b/src/common/IConnection.ts index 1478f98..43eda43 100644 --- a/src/common/IConnection.ts +++ b/src/common/IConnection.ts @@ -9,4 +9,5 @@ export interface IConnection { multipleStatements?: boolean; readonly certPath?: string; ssl?: any; + readonly passwordGenerationCommand?: string; } \ No newline at end of file diff --git a/src/common/database.ts b/src/common/database.ts index f237c32..ef27dba 100644 --- a/src/common/database.ts +++ b/src/common/database.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; import * as path from 'path'; +import {execSync} from 'child_process'; // import { Pool, Client, types, ClientConfig } from 'pg'; import { PgClient } from './connection'; import { IConnection } from "./IConnection"; @@ -65,7 +66,8 @@ export class Database { port: connection.port, database: dbname, multipleStatements: connection.multipleStatements, - certPath: connection.certPath + certPath: connection.certPath, + passwordGenerationCommand: connection.passwordGenerationCommand, }; } @@ -84,7 +86,21 @@ export class Database { } let client = new PgClient(connectionOptions); - await client.connect(); + try { + await client.connect(); + } catch (err) { + if (connection.passwordGenerationCommand) { + try { + const password = execSync(connection.passwordGenerationCommand, { encoding: 'utf-8' }); + connectionOptions.password = password.trim(); + client = new PgClient(connectionOptions); + await client.connect(); + } catch (error) { + throw error + } + } + + } const versionRes = await client.query(`SELECT current_setting('server_version_num') as ver_num;`); /* return res.rows.map(column => {