Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ dist
pat-open-vsx
.nyc_output/
coverage/
out_test/
out_test/
test_*
mock_ai_server.js
*.pgpass
src/test/unit/PgPassSupport.test.ts
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
28 changes: 21 additions & 7 deletions src/commands/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
}

Expand Down
8 changes: 3 additions & 5 deletions src/providers/DatabaseTreeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export class DatabaseTreeProvider implements vscode.TreeDataProvider<DatabaseTre
*/
public async getDbObjectsForConnection(connection: any): Promise<Array<{ type: string, schema: string, name: string, columns?: string[] }>> {
const client = await ConnectionManager.getInstance().getPooledClient({
...connection,
id: connection.id,
host: connection.host,
port: connection.port,
Expand Down Expand Up @@ -440,14 +441,11 @@ export class DatabaseTreeProvider implements vscode.TreeDataProvider<DatabaseTre
const dbName = element.databaseName || connection.database || 'postgres';

client = await ConnectionManager.getInstance().getPooledClient({
id: connection.id,
host: connection.host,
port: connection.port,
username: connection.username,
...connection,
database: dbName,
name: connection.name
});


switch (element.type) {
case 'connection':
const items: DatabaseTreeItem[] = [];
Expand Down
67 changes: 24 additions & 43 deletions src/services/ConnectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,63 +351,44 @@ export class ConnectionManager {
forceDisableSSL: boolean = false,
): Promise<ClientConfig> {
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.
Expand Down
Loading