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
22 changes: 22 additions & 0 deletions lib/core/database/database_error_mapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import 'package:querya_desktop/core/security/ssh_tunnel_manager.dart';
import 'package:querya_desktop/core/storage/connection_secrets_store.dart';

/// Which engine produced an error; only used to tailor the wording.
/// The hint of the *server requires SSL* error (#1754); the PostgreSQL form
/// looks for it to offer *Enable SSL and retry*.
const postgresEnableSslHint = 'Enable SSL in the connection settings and try again';

enum DatabaseDriver { postgres, mysql, sqlite, mongodb, redis, extension }

/// Maps a driver / OS error to a [QueryaDatabaseException].
Expand Down Expand Up @@ -96,6 +100,24 @@ QueryaDatabaseException mapDatabaseError(
originalError: error,
stackTrace: stackTrace,
);
} else if (lower.contains('pg_hba.conf rejects connection') &&
lower.contains('no encryption')) {
// `hostssl` only in pg_hba.conf: reads like a wrong password or a blocked
// IP, but the fix is the SSL switch (#1754). The server's own line stays in
// the message.
final line = RegExp(r'pg_hba\.conf rejects connection[^\n]*',
caseSensitive: false)
.firstMatch(raw)
?.group(0)
?.trim();
mapped = TlsFailureException(
'The server only accepts encrypted connections'
'${line == null ? '' : ' ($line)'}',
detailedExplanation: raw,
remediationHint: postgresEnableSslHint,
originalError: error,
stackTrace: stackTrace,
);
} else if (_hasAny(lower, const [
'password authentication failed',
'access denied for user',
Expand Down
13 changes: 13 additions & 0 deletions lib/features/postgresql/postgresql_connection_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,19 @@ class _PostgresConnectionFormContentState
),
).small(),
),
if (!_useSSL &&
_testResult!.contains(postgresEnableSslHint)) ...[
QueryaButton.secondary(
label: 'Enable SSL and retry',
size: QueryaControlSize.sm,
onPressed: () {
_dismissResult();
setState(() => _useSSL = true);
unawaited(_testConnection());
},
),
const Gap(8),
],
QueryaIconButton(
icon: const material.Icon(material.Icons.close),
tooltip: 'Dismiss',
Expand Down
35 changes: 35 additions & 0 deletions test/core/database/database_error_mapper_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,39 @@ void main() {
);
});
});

group('a server that wants SSL (#1754)', () {
const raw = '28000: pg_hba.conf rejects connection for host "10.10.0.129", '
'user "app", database "demo", no encryption';

test('is told as an SSL problem, with the server line kept', () {
final mapped =
mapDatabaseError(Exception(raw), driver: DatabaseDriver.postgres);

expect(mapped, isA<TlsFailureException>());
expect(mapped.message, contains('only accepts encrypted connections'));
expect(mapped.message, contains('pg_hba.conf rejects connection'));
expect(mapped.message, contains('no encryption'));
expect(mapped.remediationHint, postgresEnableSslHint);
expect(describeDatabaseError(Exception(raw)), contains('Enable SSL'));
});

test('is found inside the wrapped connect error too', () {
final wrapped = PostgresConnectionException(
'Failed to connect to PostgreSQL (demo): ${describeDatabaseError(Exception(raw))}',
cause: Exception(raw),
);
expect(wrapped.message, contains(postgresEnableSslHint));
});

test('a rejection that is not about missing encryption gets no SSL hint', () {
final mapped = mapDatabaseError(
Exception('28000: pg_hba.conf rejects connection for host "10.0.0.1", '
'user "app", database "demo", SSL encryption'),
driver: DatabaseDriver.postgres,
);
expect(mapped, isNot(isA<TlsFailureException>()));
expect(mapped.remediationHint, isNot(postgresEnableSslHint));
});
});
}
Loading