@aws/aurora-dsql-postgresjs-connector
Version:
An AWS Aurora DSQL connector with IAM authentication for Postgres.js
103 lines • 4.16 kB
JavaScript
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import postgres from "postgres";
import { DsqlSigner } from "@aws-sdk/dsql-signer";
const ADMIN = "admin";
const DEFAULT_DATABASE = "postgres";
const DEFAULT_EXPIRY = 30; // Based on default Postgres.js connect_timeout
// String components of a DSQL hostname, <Cluster ID>.dsql.<region>.on.aws
const PRE_REGION_HOST_PATTERN = ".dsql.";
const POST_REGION_HOST_PATTERN = ".on.aws";
export function auroraDSQLPostgres(urlOrOptions, options) {
/* eslint-enable @typescript-eslint/no-explicit-any */
let opts;
let host;
let username;
let database;
let ssl;
if (typeof urlOrOptions === 'string') {
// Called with (url, options)
let parsedOptions = parseConnectionString(urlOrOptions);
host = options?.hostname || options?.host || parsedOptions.host || process.env.PGHOST;
username = options?.username || options?.user || parsedOptions.username || process.env.PGUSERNAME || process.env.USER || ADMIN;
database = options?.database || options?.db || parsedOptions.database || process.env.PGDATABASE;
ssl = options?.ssl || parsedOptions.ssl;
opts = { ...options };
}
else {
// Called with (options) only
host = urlOrOptions?.hostname || urlOrOptions?.host || process.env.PGHOST;
username = urlOrOptions?.username || urlOrOptions?.user || process.env.PGUSERNAME || process.env.USER || ADMIN;
database = urlOrOptions?.database || urlOrOptions?.db || process.env.PGDATABASE;
ssl = urlOrOptions?.ssl;
opts = { ...urlOrOptions };
}
if (Array.isArray(host)) {
throw new Error('Multi-host configurations are not supported for Aurora DSQL');
}
let region = opts.region || parseRegionFromHost(host);
if (isClusterID(host)) {
host = buildHostnameFromIdAndRegion(host, region);
opts.host = host;
}
let signerConfig = {
hostname: host,
region: opts.region || parseRegionFromHost(host),
expiresIn: opts.tokenDurationSecs ?? opts.connect_timeout ?? (process.env.PGCONNECT_TIMEOUT ? parseInt(process.env.PGCONNECT_TIMEOUT) : undefined) ?? DEFAULT_EXPIRY,
profile: opts.profile || process.env.AWS_PROFILE || "default",
};
if (opts.customCredentialsProvider) {
signerConfig.credentials = opts.customCredentialsProvider;
}
let signer = new DsqlSigner(signerConfig);
if (!database)
opts.database = DEFAULT_DATABASE;
if (!ssl)
opts.ssl = true;
const postgresOpts = {
...opts,
pass: () => getToken(signer, username),
};
return typeof urlOrOptions === 'string' ? postgres(urlOrOptions, postgresOpts) : postgres(postgresOpts);
}
function parseConnectionString(url) {
let decodedUrl = decodeURI(url);
const parsed = new URL(decodedUrl);
// Check for multi-host
if (parsed.hostname?.includes(',')) {
throw new Error('Multi-host connection strings are not supported for Aurora DSQL');
}
return {
username: parsed.username,
host: parsed.hostname,
database: parsed.pathname?.slice(1),
ssl: parsed.searchParams.get("ssl") || parsed.searchParams.get("sslmode") || undefined,
};
}
function parseRegionFromHost(host) {
if (!host) {
throw new Error("Hostname is required to parse region");
}
const match = host.match(/^(?<instance>[^.]+)\.(?<dns>dsql(?:-[^.]+)?)\.(?<domain>(?<region>[a-zA-Z0-9-]+)\.on\.aws\.?)$/i);
if (match?.groups) {
return match.groups.region;
}
throw new Error(`Unable to parse region from hostname: ${host}`);
}
function isClusterID(host) {
return !host.includes(".");
}
function buildHostnameFromIdAndRegion(host, region) {
return host + PRE_REGION_HOST_PATTERN + region + POST_REGION_HOST_PATTERN;
}
async function getToken(signer, username) {
if (username === ADMIN) {
return await signer.getDbConnectAdminAuthToken();
}
else {
return await signer.getDbConnectAuthToken();
}
}
//# sourceMappingURL=client.js.map