Redact quick connection endpoints (#598)
* fix: redact quick connection endpoints * fix: redact host-like quick connection names --------- Co-authored-by: t8y2 <1156263951@qq.com>
This commit is contained in:
parent
7eca75b325
commit
87a43644de
|
|
@ -2,7 +2,12 @@
|
|||
import { useI18n } from "vue-i18n";
|
||||
import { FilePlus2, Plus, History, Upload, Database, Search, ShieldCheck, Sparkles } from "lucide-vue-next";
|
||||
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
import { connectionDriverLabel, connectionIconType, connectionOptionSubtitle } from "@/lib/connectionPresentation";
|
||||
import {
|
||||
connectionDriverLabel,
|
||||
connectionIconType,
|
||||
connectionRedactedNameLabel,
|
||||
connectionRedactedOptionSubtitle,
|
||||
} from "@/lib/connectionPresentation";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
|
||||
defineProps<{
|
||||
|
|
@ -64,9 +69,9 @@ const { t } = useI18n();
|
|||
<DatabaseIcon :db-type="connectionIconType(connection)" class="h-4 w-4" />
|
||||
<span class="h-5 w-1 rounded-full shrink-0" :style="{ backgroundColor: connection.color || '#9ca3af' }" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm font-medium">{{ connection.name }}</div>
|
||||
<div class="truncate text-sm font-medium">{{ connectionRedactedNameLabel(connection) }}</div>
|
||||
<div class="truncate text-xs text-muted-foreground">
|
||||
{{ connectionOptionSubtitle(connection) || connectionDriverLabel(connection) }}
|
||||
{{ connectionRedactedOptionSubtitle(connection) || connectionDriverLabel(connection) }}
|
||||
</div>
|
||||
</div>
|
||||
<FilePlus2 class="h-4 w-4 text-muted-foreground" />
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ type ConnectionPresentationConfig = Pick<
|
|||
ConnectionConfig,
|
||||
"db_type" | "driver_profile" | "driver_label" | "host" | "port" | "database"
|
||||
>;
|
||||
type ConnectionNamePresentationConfig = ConnectionPresentationConfig & Pick<ConnectionConfig, "name">;
|
||||
|
||||
const LOCAL_DATABASE_TYPES = new Set(["sqlite", "duckdb", "access"]);
|
||||
const REDACTED_HOST_SEGMENT = "***";
|
||||
const REDACTED_PORT = "****";
|
||||
|
||||
export function connectionIconType(connection?: Pick<ConnectionConfig, "db_type" | "driver_profile">): string {
|
||||
return connection?.driver_profile || connection?.db_type || "postgres";
|
||||
|
|
@ -24,6 +27,62 @@ export function connectionEndpointLabel(connection?: ConnectionPresentationConfi
|
|||
return connection.host || connection.database || "";
|
||||
}
|
||||
|
||||
function redactConnectionHost(host: string): string {
|
||||
const normalizedHost = host.trim();
|
||||
if (!normalizedHost) return "";
|
||||
|
||||
const unwrappedHost =
|
||||
normalizedHost.startsWith("[") && normalizedHost.endsWith("]") ? normalizedHost.slice(1, -1) : normalizedHost;
|
||||
const separator = unwrappedHost.includes(":") ? ":" : ".";
|
||||
const segments = unwrappedHost.split(separator).filter(Boolean);
|
||||
|
||||
if (segments.length >= 3) {
|
||||
return [segments[0], ...segments.slice(1, -1).map(() => REDACTED_HOST_SEGMENT), segments[segments.length - 1]].join(
|
||||
separator,
|
||||
);
|
||||
}
|
||||
|
||||
if (segments.length === 2) {
|
||||
return [segments[0], REDACTED_HOST_SEGMENT].join(separator);
|
||||
}
|
||||
|
||||
return REDACTED_HOST_SEGMENT;
|
||||
}
|
||||
|
||||
export function connectionRedactedEndpointLabel(connection?: ConnectionPresentationConfig): string {
|
||||
if (!connection) return "";
|
||||
if (LOCAL_DATABASE_TYPES.has(connection.db_type)) {
|
||||
return connectionEndpointLabel(connection);
|
||||
}
|
||||
|
||||
const redactedHost = connection.host ? redactConnectionHost(connection.host) : "";
|
||||
if (redactedHost && connection.port) {
|
||||
const endpointHost = redactedHost.includes(":") ? `[${redactedHost}]` : redactedHost;
|
||||
return `${endpointHost}:${REDACTED_PORT}`;
|
||||
}
|
||||
|
||||
return redactedHost || connection.database || "";
|
||||
}
|
||||
|
||||
export function connectionRedactedNameLabel(connection?: ConnectionNamePresentationConfig): string {
|
||||
const name = connection?.name.trim() || "";
|
||||
if (!connection || !name || LOCAL_DATABASE_TYPES.has(connection.db_type)) return name;
|
||||
|
||||
const host = connection.host.trim();
|
||||
if (!host) return name;
|
||||
|
||||
const unwrappedHost = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
||||
const hostNames = new Set([host, unwrappedHost]);
|
||||
if (connection.port) {
|
||||
hostNames.add(`${host}:${connection.port}`);
|
||||
if (unwrappedHost.includes(":")) {
|
||||
hostNames.add(`[${unwrappedHost}]:${connection.port}`);
|
||||
}
|
||||
}
|
||||
|
||||
return hostNames.has(name) ? connectionRedactedEndpointLabel(connection) : name;
|
||||
}
|
||||
|
||||
export function connectionUrlPlaceholder(dbType: DatabaseType): string {
|
||||
switch (dbType) {
|
||||
case "mysql":
|
||||
|
|
@ -90,3 +149,7 @@ export function connectionUrlPlaceholder(dbType: DatabaseType): string {
|
|||
export function connectionOptionSubtitle(connection?: ConnectionPresentationConfig): string {
|
||||
return [connectionDriverLabel(connection), connectionEndpointLabel(connection)].filter(Boolean).join(" · ");
|
||||
}
|
||||
|
||||
export function connectionRedactedOptionSubtitle(connection?: ConnectionPresentationConfig): string {
|
||||
return [connectionDriverLabel(connection), connectionRedactedEndpointLabel(connection)].filter(Boolean).join(" · ");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import type { ConnectionConfig } from "../../apps/desktop/src/types/database.ts";
|
||||
import { connectionDriverLabel, connectionEndpointLabel, connectionIconType, connectionOptionSubtitle } from "../../apps/desktop/src/lib/connectionPresentation.ts";
|
||||
import {
|
||||
connectionDriverLabel,
|
||||
connectionEndpointLabel,
|
||||
connectionIconType,
|
||||
connectionOptionSubtitle,
|
||||
connectionRedactedEndpointLabel,
|
||||
connectionRedactedNameLabel,
|
||||
connectionRedactedOptionSubtitle,
|
||||
} from "../../apps/desktop/src/lib/connectionPresentation.ts";
|
||||
|
||||
const baseConnection: ConnectionConfig = {
|
||||
id: "conn-1",
|
||||
|
|
@ -49,3 +57,76 @@ test("uses file path as endpoint for local database connections", () => {
|
|||
|
||||
assert.equal(connectionOptionSubtitle(accessConnection), "Microsoft Access · /tmp/Northwind.accdb");
|
||||
});
|
||||
|
||||
test("redacts network endpoint labels for quick connection cards", () => {
|
||||
assert.equal(
|
||||
connectionRedactedEndpointLabel({
|
||||
...baseConnection,
|
||||
host: "192.168.1.100",
|
||||
port: 3306,
|
||||
}),
|
||||
"192.***.***.100:****",
|
||||
);
|
||||
assert.equal(
|
||||
connectionRedactedOptionSubtitle({
|
||||
...baseConnection,
|
||||
host: "db.prod.example.com",
|
||||
port: 5432,
|
||||
}),
|
||||
"TiDB · db.***.***.com:****",
|
||||
);
|
||||
assert.equal(
|
||||
connectionRedactedEndpointLabel({
|
||||
...baseConnection,
|
||||
host: "2001:db8:85a3::8a2e:370:7334",
|
||||
port: 5432,
|
||||
}),
|
||||
"[2001:***:***:***:***:7334]:****",
|
||||
);
|
||||
});
|
||||
|
||||
test("redacts host-like quick connection names", () => {
|
||||
assert.equal(
|
||||
connectionRedactedNameLabel({
|
||||
...baseConnection,
|
||||
name: "db.prod.example.com",
|
||||
host: "db.prod.example.com",
|
||||
port: 5432,
|
||||
}),
|
||||
"db.***.***.com:****",
|
||||
);
|
||||
assert.equal(
|
||||
connectionRedactedNameLabel({
|
||||
...baseConnection,
|
||||
name: "[2001:db8:85a3::8a2e:370:7334]:5432",
|
||||
host: "2001:db8:85a3::8a2e:370:7334",
|
||||
port: 5432,
|
||||
}),
|
||||
"[2001:***:***:***:***:7334]:****",
|
||||
);
|
||||
});
|
||||
|
||||
test("keeps friendly quick connection names readable", () => {
|
||||
assert.equal(
|
||||
connectionRedactedNameLabel({
|
||||
...baseConnection,
|
||||
name: "Production Analytics",
|
||||
host: "db.prod.example.com",
|
||||
port: 5432,
|
||||
}),
|
||||
"Production Analytics",
|
||||
);
|
||||
});
|
||||
|
||||
test("keeps local file database endpoint labels readable when redacting", () => {
|
||||
const sqliteConnection: ConnectionConfig = {
|
||||
...baseConnection,
|
||||
db_type: "sqlite",
|
||||
driver_profile: "sqlite",
|
||||
driver_label: "SQLite",
|
||||
host: "/tmp/local.db",
|
||||
port: 0,
|
||||
};
|
||||
|
||||
assert.equal(connectionRedactedOptionSubtitle(sqliteConnection), "SQLite · /tmp/local.db");
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue