diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index ff2247727..b233d52bf 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -182,6 +182,10 @@ pub struct ConnectionParams { pub port: Option, pub username: Option, pub password: Option, + /// 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, /// 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. diff --git a/src/components/modals/NewConnectionModal.tsx b/src/components/modals/NewConnectionModal.tsx index ecac6d3a0..93cf4d402 100644 --- a/src/components/modals/NewConnectionModal.tsx +++ b/src/components/modals/NewConnectionModal.tsx @@ -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 = { @@ -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. */ @@ -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(); @@ -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(() => { @@ -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, @@ -2483,6 +2511,36 @@ export const NewConnectionModal = ({ /> + {/* 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 && ( +
+ + ` component (`code — Name`). */ +export const AWS_REGION_SELECT_LABELS: Record = 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; +} diff --git a/src/utils/connections.ts b/src/utils/connections.ts index 259e783a4..80ee4308f 100644 --- a/src/utils/connections.ts +++ b/src/utils/connections.ts @@ -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. */ diff --git a/src/utils/credentials.ts b/src/utils/credentials.ts index cd1079ee0..f5c2faffa 100644 --- a/src/utils/credentials.ts +++ b/src/utils/credentials.ts @@ -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. */