Skip to content

Commit 5740d5c

Browse files
authored
Merge pull request #62 from ric-v/main
Authentication and Password Resolution Improvements
2 parents 63a49fc + 6569ad3 commit 5740d5c

6 files changed

Lines changed: 66 additions & 57 deletions

File tree

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ dist
88
pat-open-vsx
99
.nyc_output/
1010
coverage/
11-
out_test/
11+
out_test/
12+
test_*
13+
mock_ai_server.js
14+
*.pgpass
15+
src/test/unit/PgPassSupport.test.ts

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
---
99

10+
## [0.8.6...0.8.7] - 2026-03-15
11+
12+
### Added
13+
- **.pgpass support**: Native backwards-compatible support with explicit resolvers parsing standard `.pgpass` (and Windows `%APPDATA%\postgresql\pgpass.conf`) secret files.
14+
15+
### Fixed
16+
- **Authentication Resilience**: Resolved standard connection `password authentication failed` issues that fell back to implicit OS defaults incorrectly.
17+
- **SSL Fallback Reliability**: Fixed `DatabaseTreeProvider` stripping configuration details (such as direct inline passwords and sslmode) during fallback client re-trigger calculations.
18+
- **.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.
19+
20+
---
21+
1022
## [0.8.4...0.8.5] - 2026-02-19
1123

1224
### Added

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "postgres-explorer",
33
"displayName": "PgStudio (PostgreSQL Explorer)",
4-
"version": "0.8.5",
4+
"version": "0.8.7",
55
"description": "PostgreSQL database explorer for VS Code with notebook support",
66
"publisher": "ric-v",
77
"private": false,

src/commands/connection.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,22 +83,36 @@ export async function getConnectionWithPassword(connectionId: string, databaseNa
8383
}
8484

8585
let password = await SecretStorageService.getInstance().getPassword(connectionId);
86-
if (!password && connection.username) {
86+
if (!password && connection.password) {
87+
password = connection.password;
88+
}
89+
90+
const defaultUsername = process.env.PGUSER || process.env.USER || process.env.USERNAME || require('os').userInfo().username || 'postgres';
91+
const actualUsername = connection.username || defaultUsername;
92+
93+
if (!password && actualUsername) {
8794
password = resolvePgPassPassword(
8895
connection.host,
8996
connection.port,
9097
databaseName || connection.database || 'postgres',
91-
connection.username
98+
actualUsername
9299
);
100+
if (!password && (databaseName || connection.database) !== 'postgres') {
101+
password = resolvePgPassPassword(
102+
connection.host,
103+
connection.port,
104+
'postgres',
105+
actualUsername
106+
);
107+
}
93108
}
94109

95-
if (!password) {
96-
throw new Error('Password not found in secure storage or .pgpass');
97-
}
98-
110+
// Do NOT throw if !password here, because pg library supports trust auth
111+
// without passwords, and SCRAM throws its own clean error downstream.
112+
99113
return {
100114
...connection,
101-
password
115+
password: password || undefined
102116
};
103117
}
104118

src/providers/DatabaseTreeProvider.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ export class DatabaseTreeProvider implements vscode.TreeDataProvider<DatabaseTre
197197
*/
198198
public async getDbObjectsForConnection(connection: any): Promise<Array<{ type: string, schema: string, name: string, columns?: string[] }>> {
199199
const client = await ConnectionManager.getInstance().getPooledClient({
200+
...connection,
200201
id: connection.id,
201202
host: connection.host,
202203
port: connection.port,
@@ -440,14 +441,11 @@ export class DatabaseTreeProvider implements vscode.TreeDataProvider<DatabaseTre
440441
const dbName = element.databaseName || connection.database || 'postgres';
441442

442443
client = await ConnectionManager.getInstance().getPooledClient({
443-
id: connection.id,
444-
host: connection.host,
445-
port: connection.port,
446-
username: connection.username,
444+
...connection,
447445
database: dbName,
448-
name: connection.name
449446
});
450447

448+
451449
switch (element.type) {
452450
case 'connection':
453451
const items: DatabaseTreeItem[] = [];

src/services/ConnectionManager.ts

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -351,63 +351,44 @@ export class ConnectionManager {
351351
forceDisableSSL: boolean = false,
352352
): Promise<ClientConfig> {
353353
let password: string | undefined;
354-
if (config.username) {
355-
password = await SecretStorageService.getInstance().getPassword(
356-
config.id,
357-
);
354+
355+
// 1) Primary: password stored in VSCode SecretStorage (set during connection setup)
356+
if (config.username && config.id) {
357+
password = await SecretStorageService.getInstance().getPassword(config.id);
358+
}
359+
360+
// 2) Fallback: password stored inline in settings (legacy or manually specified)
361+
if (!password && (config as any).password) {
362+
password = (config as any).password;
358363
}
359364

360-
// ── Explicit pgpass resolution ─────────────────────────────────────────
361-
// When no password is stored in SecretStorage (the user relies on a pgpass
362-
// file), the pg library's *internal* pgpass lookup can silently fail —
363-
// most commonly on Windows where the expected file path is
364-
// %APPDATA%\postgresql\pgpass.conf
365-
// rather than ~/.pgpass. If that lookup returns undefined, pg keeps the
366-
// password as null and SCRAM authentication throws:
367-
// "SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string"
368-
//
369-
// By resolving the pgpass password ourselves and passing it explicitly we
370-
// bypass pg's internal lookup entirely and maintain consistent behaviour
371-
// across all platforms.
365+
// 3) Secondary fallback: resolve from .pgpass file
366+
// Only attempt this when the user has explicitly configured a username.
367+
// We do NOT use the OS default username here, because that would silently
368+
// resolve a wrong .pgpass entry for connections that rely on trust auth.
372369
if (!password && config.username) {
373-
const targetDb = config.database || "postgres";
374-
const pgpassPassword = resolvePgPassPassword(
375-
config.host,
376-
config.port,
377-
targetDb,
378-
config.username,
370+
const targetDb = config.database || 'postgres';
371+
const pgpassPwd = resolvePgPassPassword(
372+
config.host, config.port, targetDb, config.username,
379373
);
380-
if (pgpassPassword !== undefined) {
381-
password = pgpassPassword;
382-
console.log(
383-
`[ConnectionManager] Password resolved from pgpass file for ${config.username}@${config.host}:${config.port}/${targetDb}`,
384-
);
385-
} else if (targetDb !== "postgres") {
386-
// Also try the postgres database in case the entry uses that as the
387-
// database field (e.g. when the target database doesn't exist yet).
374+
if (pgpassPwd !== undefined) {
375+
password = pgpassPwd;
376+
console.log(`[ConnectionManager] Password resolved from .pgpass for ${config.username}@${config.host}:${config.port}/${targetDb}`);
377+
} else if (targetDb !== 'postgres') {
388378
const fallback = resolvePgPassPassword(
389-
config.host,
390-
config.port,
391-
"postgres",
392-
config.username,
379+
config.host, config.port, 'postgres', config.username,
393380
);
394381
if (fallback !== undefined) {
395382
password = fallback;
396-
console.log(
397-
`[ConnectionManager] Password resolved from pgpass file (postgres fallback) for ${config.username}@${config.host}:${config.port}`,
398-
);
383+
console.log(`[ConnectionManager] Password resolved from .pgpass (postgres fallback) for ${config.username}@${config.host}:${config.port}`);
399384
}
400385
}
401-
402386
if (!password) {
403-
console.warn(
404-
`[ConnectionManager] No password found in SecretStorage or pgpass for ` +
405-
`${config.username}@${config.host}:${config.port}/${targetDb}. ` +
406-
`Expected pgpass file location: ${pgPassFileDescription()}`,
407-
);
387+
console.log(`[ConnectionManager] No password found in SecretStorage or .pgpass for ${config.username}@${config.host}. Expected: ${pgPassFileDescription()}`);
408388
}
409389
}
410390

391+
411392
let sslConfig: boolean | any = false;
412393
// Default to 'prefer' if empty/undefined.
413394
// If forceDisableSSL is true, we ignore sslmode and leave sslConfig as false.

0 commit comments

Comments
 (0)