Skip to content
Closed
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
4 changes: 4 additions & 0 deletions src-tauri/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ pub struct ConnectionParams {
pub port: Option<u16>,
pub username: Option<String>,
pub password: Option<String>,
/// AWS region for drivers that need an explicit signing region
/// (e.g. DynamoDB). Optional so SQL drivers ignore it.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub region: Option<String>,
/// Opaque driver-specific connection URI forwarded verbatim to the driver
/// (e.g. a `mongodb+srv://` seedlist URI). Runtime only: command handlers
/// strip it before persisting a connection, because it embeds credentials.
Expand Down
58 changes: 58 additions & 0 deletions src/components/modals/NewConnectionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ import {
type EngineGroup,
type CatalogueDriver,
} from "../../utils/connectionCatalogue";
import {
AWS_REGION_CODES,
AWS_REGION_SELECT_LABELS,
regionFromDynamoDbHost,
} from "../../utils/awsRegions";

// Accent colors per data paradigm, used for driver chips in the configure header.
const PARADIGM_ACCENT: Record<string, string> = {
Expand All @@ -101,6 +106,8 @@ interface ConnectionParams {
port?: number;
username?: string;
password?: string;
/** AWS region (DynamoDB and other AWS drivers). */
region?: string;
/** Raw driver-specific connection URI, stored in the OS keychain, never in connections.json. */
connection_uri?: string;
/** True when the URI can be restored from the OS keychain. */
Expand Down Expand Up @@ -594,6 +601,8 @@ export const NewConnectionModal = ({
// Flat single-database store (e.g. Meilisearch): no database to select or name.
const singleDatabase =
activeDriver?.capabilities?.single_database === true;
const isDynamoDb =
driver === "dynamodb" || activeDriver?.engine === "dynamodb";

// ── plugin slot: connection-modal.connection_content ──
const slotRegistry = usePluginSlotRegistry();
Expand Down Expand Up @@ -1436,6 +1445,24 @@ export const NewConnectionModal = ({
setFormData((prev) => ({ ...prev, [field]: value }));
};

// Auto-fill region from a standard DynamoDB endpoint hostname when the
// region field is still empty (or still matches a previously derived value).
useEffect(() => {
if (!isDynamoDb) return;
const derived = regionFromDynamoDbHost(formData.host);
if (!derived) return;
setFormData((prev) => {
if (prev.region && prev.region !== derived) {
// User picked a different region explicitly — leave it alone unless
// the current value still matches a previously auto-derived host.
const prevHostRegion = regionFromDynamoDbHost(prev.host);
if (prev.region !== prevHostRegion) return prev;
}
if (prev.region === derived) return prev;
return { ...prev, region: derived };
});
}, [isDynamoDb, formData.host]);

// Any change to the SSH configuration invalidates a previous SSH test
// result: a stale green check must not survive an edit.
const invalidateSshTest = useCallback(() => {
Expand Down Expand Up @@ -1754,6 +1781,7 @@ export const NewConnectionModal = ({
port: drivers.find((d) => d.id === newDriver)?.default_port ?? undefined,
username: "",
password: "",
region: undefined,
database: "",
ssl_mode: "",
ssh_enabled: false,
Expand Down Expand Up @@ -2483,6 +2511,36 @@ export const NewConnectionModal = ({
/>
</div>

{/* AWS region — DynamoDB needs an explicit signing region. The
generic host/port form has no region field, so without this
the plugin falls back to us-east-1 and real AWS endpoints in
other regions fail with InvalidSignatureException. */}
{isDynamoDb && (
<div className="flex flex-col gap-1">
<label className="text-[10px] uppercase font-semibold tracking-wider text-muted">
{t("newConnection.region", { defaultValue: "AWS Region" })}
</label>
<Select
value={formData.region || null}
options={[...AWS_REGION_CODES]}
labels={AWS_REGION_SELECT_LABELS}
onChange={(val) => updateField("region", val ?? "")}
searchable
searchPlaceholder={t("common.search")}
noResultsLabel={t("common.noResults")}
placeholder={t("newConnection.regionPlaceholder", {
defaultValue: "Select region (e.g. us-west-2)",
})}
/>
<p className="text-[11px] leading-relaxed text-muted">
{t("newConnection.regionHint", {
defaultValue:
"Signing region for AWS DynamoDB. Auto-filled from a standard dynamodb.<region>.amazonaws.com host when empty.",
})}
</p>
</div>
)}

{/* User + Password */}
<div className="grid grid-cols-2 gap-3">
<FieldInput
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,9 @@
"dbType": "Database Type",
"host": "Host",
"port": "Port",
"region": "AWS Region",
"regionPlaceholder": "Select region (e.g. us-west-2)",
"regionHint": "Signing region for AWS DynamoDB. Auto-filled from a standard dynamodb.<region>.amazonaws.com host when empty.",
"username": "Username",
"password": "Password",
"passwordMissing": "Password missing or not set. Please re-enter.",
Expand Down
29 changes: 29 additions & 0 deletions src/utils/awsRegions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import {
AWS_REGION_CODES,
regionFromDynamoDbHost,
} from "./awsRegions";

describe("awsRegions", () => {
it("lists the commercial AWS regions from the AWS Regions docs", () => {
expect(AWS_REGION_CODES).toContain("us-west-2");
expect(AWS_REGION_CODES).toContain("ap-southeast-2");
expect(AWS_REGION_CODES).toContain("eu-central-1");
expect(AWS_REGION_CODES).toHaveLength(34);
});

it("parses region from a standard DynamoDB hostname", () => {
expect(regionFromDynamoDbHost("dynamodb.us-west-2.amazonaws.com")).toBe(
"us-west-2",
);
expect(
regionFromDynamoDbHost("https://dynamodb.eu-west-1.amazonaws.com:443"),
).toBe("eu-west-1");
});

it("returns null for non-AWS hosts", () => {
expect(regionFromDynamoDbHost("localhost")).toBeNull();
expect(regionFromDynamoDbHost("127.0.0.1")).toBeNull();
expect(regionFromDynamoDbHost(undefined)).toBeNull();
});
});
107 changes: 107 additions & 0 deletions src/utils/awsRegions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* AWS commercial-region codes, ordered to match the AWS Regions table:
* https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions.html
*
* GovCloud / China partitions are omitted — they require separate account
* types and hostnames (`amazonaws.com.cn`, `amazonaws-us-gov.com`).
*/
export const AWS_REGION_CODES = [
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2",
"af-south-1",
"ap-east-1",
"ap-east-2",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ap-southeast-6",
"ap-southeast-7",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"eu-south-1",
"eu-south-2",
"eu-north-1",
"il-central-1",
"mx-central-1",
"me-south-1",
"me-central-1",
"sa-east-1",
] as const;

export type AwsRegionCode = (typeof AWS_REGION_CODES)[number];

/** Human-readable names keyed by region code. */
export const AWS_REGION_LABELS: Record<AwsRegionCode, string> = {
"us-east-1": "US East (N. Virginia)",
"us-east-2": "US East (Ohio)",
"us-west-1": "US West (N. California)",
"us-west-2": "US West (Oregon)",
"af-south-1": "Africa (Cape Town)",
"ap-east-1": "Asia Pacific (Hong Kong)",
"ap-east-2": "Asia Pacific (Taipei)",
"ap-south-1": "Asia Pacific (Mumbai)",
"ap-south-2": "Asia Pacific (Hyderabad)",
"ap-southeast-1": "Asia Pacific (Singapore)",
"ap-southeast-2": "Asia Pacific (Sydney)",
"ap-southeast-3": "Asia Pacific (Jakarta)",
"ap-southeast-4": "Asia Pacific (Melbourne)",
"ap-southeast-5": "Asia Pacific (Malaysia)",
"ap-southeast-6": "Asia Pacific (New Zealand)",
"ap-southeast-7": "Asia Pacific (Thailand)",
"ap-northeast-1": "Asia Pacific (Tokyo)",
"ap-northeast-2": "Asia Pacific (Seoul)",
"ap-northeast-3": "Asia Pacific (Osaka)",
"ca-central-1": "Canada (Central)",
"ca-west-1": "Canada West (Calgary)",
"eu-central-1": "Europe (Frankfurt)",
"eu-central-2": "Europe (Zurich)",
"eu-west-1": "Europe (Ireland)",
"eu-west-2": "Europe (London)",
"eu-west-3": "Europe (Paris)",
"eu-south-1": "Europe (Milan)",
"eu-south-2": "Europe (Spain)",
"eu-north-1": "Europe (Stockholm)",
"il-central-1": "Israel (Tel Aviv)",
"mx-central-1": "Mexico (Central)",
"me-south-1": "Middle East (Bahrain)",
"me-central-1": "Middle East (UAE)",
"sa-east-1": "South America (São Paulo)",
};

/** Labels suitable for the shared `<Select>` component (`code — Name`). */
export const AWS_REGION_SELECT_LABELS: Record<string, string> = Object.fromEntries(
AWS_REGION_CODES.map((code) => [
code,
`${code} — ${AWS_REGION_LABELS[code]}`,
]),
);

/**
* Extract a region code from a standard DynamoDB endpoint hostname,
* e.g. `dynamodb.us-west-2.amazonaws.com` → `us-west-2`.
*/
export function regionFromDynamoDbHost(host: string | undefined | null): string | null {
if (!host) return null;
const bare = host
.trim()
.replace(/^https?:\/\//i, "")
.split(/[/:]/)[0]
?.toLowerCase();
if (!bare) return null;
const match = /^dynamodb\.([a-z0-9-]+)\.amazonaws\.com$/.exec(bare);
return match?.[1] ?? null;
}
2 changes: 2 additions & 0 deletions src/utils/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface ConnectionParams {
port?: number;
username?: string;
password?: string;
/** AWS region (DynamoDB and other AWS drivers). */
region?: string;
/** Raw driver-specific connection URI, forwarded verbatim to the driver.
* Never persisted in connections.json: it embeds credentials and is stored
* in the OS keychain instead. */
Expand Down
2 changes: 2 additions & 0 deletions src/utils/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ interface ConnectionParams {
port?: number;
username?: string;
password?: string;
/** AWS region (DynamoDB and other AWS drivers). */
region?: string;
/** Raw driver-specific connection URI, restored from the OS keychain by the host. */
connection_uri?: string;
/** True when the URI can be restored from the OS keychain. */
Expand Down
Loading