diff --git a/.gitignore b/.gitignore index 01b2576..00e229f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,8 @@ dist pat-open-vsx .nyc_output/ coverage/ -out_test/ \ No newline at end of file +out_test/ +test_* +mock_ai_server.js +*.pgpass +src/test/unit/PgPassSupport.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index edaa37f..eacf7bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [0.8.6...0.8.7] - 2026-03-15 + +### Added +- **.pgpass support**: Native backwards-compatible support with explicit resolvers parsing standard `.pgpass` (and Windows `%APPDATA%\postgresql\pgpass.conf`) secret files. + +### Fixed +- **Authentication Resilience**: Resolved standard connection `password authentication failed` issues that fell back to implicit OS defaults incorrectly. +- **SSL Fallback Reliability**: Fixed `DatabaseTreeProvider` stripping configuration details (such as direct inline passwords and sslmode) during fallback client re-trigger calculations. +- **.pgpass lookup scope**: Avoided resolving implicit machine name environments by strictly isolating parsing searches for explicit username options, fixing backward compatibility for local `trust` authentications. + +--- + ## [0.8.4...0.8.5] - 2026-02-19 ### Added diff --git a/package.json b/package.json index 9c84445..44a5581 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "postgres-explorer", "displayName": "PgStudio (PostgreSQL Explorer)", - "version": "0.8.5", + "version": "0.8.7", "description": "PostgreSQL database explorer for VS Code with notebook support", "publisher": "ric-v", "private": false, diff --git a/src/commands/connection.ts b/src/commands/connection.ts index 5f2e942..e356d28 100644 --- a/src/commands/connection.ts +++ b/src/commands/connection.ts @@ -83,22 +83,36 @@ export async function getConnectionWithPassword(connectionId: string, databaseNa } let password = await SecretStorageService.getInstance().getPassword(connectionId); - if (!password && connection.username) { + if (!password && connection.password) { + password = connection.password; + } + + const defaultUsername = process.env.PGUSER || process.env.USER || process.env.USERNAME || require('os').userInfo().username || 'postgres'; + const actualUsername = connection.username || defaultUsername; + + if (!password && actualUsername) { password = resolvePgPassPassword( connection.host, connection.port, databaseName || connection.database || 'postgres', - connection.username + actualUsername ); + if (!password && (databaseName || connection.database) !== 'postgres') { + password = resolvePgPassPassword( + connection.host, + connection.port, + 'postgres', + actualUsername + ); + } } - if (!password) { - throw new Error('Password not found in secure storage or .pgpass'); - } - + // Do NOT throw if !password here, because pg library supports trust auth + // without passwords, and SCRAM throws its own clean error downstream. + return { ...connection, - password + password: password || undefined }; } diff --git a/src/providers/DatabaseTreeProvider.ts b/src/providers/DatabaseTreeProvider.ts index bd452f9..148eb9d 100644 --- a/src/providers/DatabaseTreeProvider.ts +++ b/src/providers/DatabaseTreeProvider.ts @@ -197,6 +197,7 @@ export class DatabaseTreeProvider implements vscode.TreeDataProvider> { const client = await ConnectionManager.getInstance().getPooledClient({ + ...connection, id: connection.id, host: connection.host, port: connection.port, @@ -440,14 +441,11 @@ export class DatabaseTreeProvider implements vscode.TreeDataProvider { let password: string | undefined; - if (config.username) { - password = await SecretStorageService.getInstance().getPassword( - config.id, - ); + + // 1) Primary: password stored in VSCode SecretStorage (set during connection setup) + if (config.username && config.id) { + password = await SecretStorageService.getInstance().getPassword(config.id); + } + + // 2) Fallback: password stored inline in settings (legacy or manually specified) + if (!password && (config as any).password) { + password = (config as any).password; } - // ── Explicit pgpass resolution ───────────────────────────────────────── - // When no password is stored in SecretStorage (the user relies on a pgpass - // file), the pg library's *internal* pgpass lookup can silently fail — - // most commonly on Windows where the expected file path is - // %APPDATA%\postgresql\pgpass.conf - // rather than ~/.pgpass. If that lookup returns undefined, pg keeps the - // password as null and SCRAM authentication throws: - // "SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string" - // - // By resolving the pgpass password ourselves and passing it explicitly we - // bypass pg's internal lookup entirely and maintain consistent behaviour - // across all platforms. + // 3) Secondary fallback: resolve from .pgpass file + // Only attempt this when the user has explicitly configured a username. + // We do NOT use the OS default username here, because that would silently + // resolve a wrong .pgpass entry for connections that rely on trust auth. if (!password && config.username) { - const targetDb = config.database || "postgres"; - const pgpassPassword = resolvePgPassPassword( - config.host, - config.port, - targetDb, - config.username, + const targetDb = config.database || 'postgres'; + const pgpassPwd = resolvePgPassPassword( + config.host, config.port, targetDb, config.username, ); - if (pgpassPassword !== undefined) { - password = pgpassPassword; - console.log( - `[ConnectionManager] Password resolved from pgpass file for ${config.username}@${config.host}:${config.port}/${targetDb}`, - ); - } else if (targetDb !== "postgres") { - // Also try the postgres database in case the entry uses that as the - // database field (e.g. when the target database doesn't exist yet). + if (pgpassPwd !== undefined) { + password = pgpassPwd; + console.log(`[ConnectionManager] Password resolved from .pgpass for ${config.username}@${config.host}:${config.port}/${targetDb}`); + } else if (targetDb !== 'postgres') { const fallback = resolvePgPassPassword( - config.host, - config.port, - "postgres", - config.username, + config.host, config.port, 'postgres', config.username, ); if (fallback !== undefined) { password = fallback; - console.log( - `[ConnectionManager] Password resolved from pgpass file (postgres fallback) for ${config.username}@${config.host}:${config.port}`, - ); + console.log(`[ConnectionManager] Password resolved from .pgpass (postgres fallback) for ${config.username}@${config.host}:${config.port}`); } } - if (!password) { - console.warn( - `[ConnectionManager] No password found in SecretStorage or pgpass for ` + - `${config.username}@${config.host}:${config.port}/${targetDb}. ` + - `Expected pgpass file location: ${pgPassFileDescription()}`, - ); + console.log(`[ConnectionManager] No password found in SecretStorage or .pgpass for ${config.username}@${config.host}. Expected: ${pgPassFileDescription()}`); } } + let sslConfig: boolean | any = false; // Default to 'prefer' if empty/undefined. // If forceDisableSSL is true, we ignore sslmode and leave sslConfig as false.