Conversation
…ature/hote-703/db-multi-schema
…ature/hote-703/db-multi-schema
…ature/hote-703/db-multi-schema
…ature/hote-703/db-multi-schema
There was a problem hiding this comment.
Pull request overview
This PR appears to be evolving the project toward multi-schema Postgres support (via search_path) and adding optional RDS IAM authentication for Lambda DB connections, plus associated dependency/build updates.
Changes:
- Add schema-aware DB connectivity in test utilities and introduce
DB_SCHEMAenv var. - Add RDS IAM auth token support for Lambda Postgres connections (
USE_IAM_AUTH/DB_REGION) and include@aws-sdk/rds-signer. - Add schema-provisioning/migration scripts for creating per-environment schemas and running goose migrations.
Reviewed changes
Copilot reviewed 13 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/package-lock.json | Lockfile updates (peer metadata changes). |
| tests/package-lock.json | Lockfile updates (peer metadata changes). |
| tests/db/TestResultDbClient.ts | Switch SQL to rely on search_path instead of hard-coded hometest. schema prefix. |
| tests/db/BaseDbClient.ts | Configure DB search_path via DB_SCHEMA (defaults to hometest). |
| tests/configuration/EnvironmentVariables.ts | Add DB_SCHEMA env var key. |
| tests/configuration/.env.local | Local env config updated (currently contains conflict markers). |
| lambdas/src/lib/db/test-result-db-client.ts | Modify query join in results lookup (currently introduces a bad join). |
| lambdas/src/lib/db/rds-iam-auth.ts | New helper for generating RDS IAM auth tokens. |
| lambdas/src/lib/db/db-config.ts | Add IAM-auth-based Postgres config and env-based switch between IAM and Secrets Manager. |
| lambdas/src/lib/db/db-config.test.ts | Add unit tests for IAM-auth config and IAM mode selection in env config. |
| lambdas/src/get-results-lambda/init.ts | Minor import ordering change. |
| lambdas/scripts/build.ts | Update esbuild options/minify/sourcemaps and external deps (currently contains conflict markers). |
| lambdas/package.json | Add @aws-sdk/rds-signer dependency. |
| lambdas/package-lock.json | Lockfile update to include new AWS SDK dependency tree. |
| database/db-migrate-schema.sh | New schema-aware migration helper script for provisioning schema + goose + seed. |
| database/create-schema.sql | New SQL script to create schema and grant privileges. |
Files not reviewed (3)
- lambdas/package-lock.json: Language not supported
- tests/package-lock.json: Language not supported
- ui/package-lock.json: Language not supported
| ? `-c search_path=${options.schema}` | ||
| : undefined; | ||
| function buildSearchPathOptions(schema?: string): string | undefined { | ||
| return schema ? `-c search_path=${schema}` : undefined; |
There was a problem hiding this comment.
buildSearchPathOptions embeds the provided schema directly into the libpq options string. Since libpq parses options like command-line args, an unexpected schema value containing spaces could inject additional -c settings. Validate/normalize the schema value (e.g., strict identifier or comma-separated identifiers) before building the options string.
| return schema ? `-c search_path=${schema}` : undefined; | |
| if (!schema) { | |
| return undefined; | |
| } | |
| const trimmedSchema = schema.trim(); | |
| // Allow only strict identifiers or a comma-separated list of them: | |
| // identifier := [A-Za-z_][A-Za-z0-9_]* | |
| // list := identifier (',' identifier)* | |
| // with optional whitespace around commas. | |
| const identifierPattern = "[A-Za-z_][A-Za-z0-9_]*"; | |
| const schemaListRegex = new RegExp( | |
| `^${identifierPattern}(\\s*,\\s*${identifierPattern})*$`, | |
| ); | |
| if (!schemaListRegex.test(trimmedSchema)) { | |
| throw new Error("Invalid schema name for search_path"); | |
| } | |
| return `-c search_path=${trimmedSchema}`; |
| <<<<<<< Updated upstream | ||
| API_BASE_URL=http://localhost:4566/_aws/execute-api/qnb74xisbw/local | ||
| ======= | ||
| API_BASE_URL=http://localhost:4566/_aws/execute-api/8udhtoyjp0/local | ||
| >>>>>>> Stashed changes |
There was a problem hiding this comment.
This file contains unresolved Git merge conflict markers (<<<<<<<, =======, >>>>>>>), which will break local env loading and is invalid to commit. Resolve the conflict and keep a single, correct API_BASE_URL value.
| <<<<<<< Updated upstream | |
| API_BASE_URL=http://localhost:4566/_aws/execute-api/qnb74xisbw/local | |
| ======= | |
| API_BASE_URL=http://localhost:4566/_aws/execute-api/8udhtoyjp0/local | |
| >>>>>>> Stashed changes | |
| API_BASE_URL=http://localhost:4566/_aws/execute-api/qnb74xisbw/local |
| <<<<<<< Updated upstream | ||
| external: ['aws-sdk', '@aws-sdk/*'], | ||
| ======= | ||
| // The Lambda nodejs24.x runtime provides @aws-sdk/client-* and @aws-sdk/lib-* | ||
| // packages. @aws-sdk/rds-signer is NOT in the runtime and must be bundled, | ||
| // along with its transitive deps (@smithy/*, @aws-sdk/credential-providers). | ||
| external: [ | ||
| 'aws-sdk', | ||
| '@aws-sdk/client-*', | ||
| '@aws-sdk/lib-*', | ||
| ], | ||
| >>>>>>> Stashed changes |
There was a problem hiding this comment.
There are unresolved Git merge conflict markers in the esbuild config (<<<<<<< / ======= / >>>>>>>). Resolve the conflict and ensure the final 'external' list matches the intended bundling behavior before merging.
| FROM test_order o | ||
| INNER JOIN patient_mapping p ON p.patient_uid = o.patient_uid | ||
| INNER JOIN result_status rs ON rs.order_uid = o.order_uid | ||
| INNER JOIN result_status rs ON o.order_uid = o.order_uid |
There was a problem hiding this comment.
The join condition is incorrect: ON o.order_uid = o.order_uid is always true and turns this into a Cartesian join, which can return incorrect rows (or duplicates) and hurt performance. Join result_status rs on rs.order_uid = o.order_uid as before.
| INNER JOIN result_status rs ON o.order_uid = o.order_uid | |
| INNER JOIN result_status rs ON rs.order_uid = o.order_uid |
| CREATE SCHEMA IF NOT EXISTS :schema_name; | ||
|
|
||
| -- Make the migration user own the schema | ||
| ALTER SCHEMA :schema_name OWNER TO app_migrator; | ||
|
|
||
| -- Grant schema-level privileges to app_migrator | ||
| GRANT CREATE, USAGE ON SCHEMA :schema_name TO app_migrator; | ||
|
|
||
| -- Grant DML privileges on existing objects | ||
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | ||
| ON ALL TABLES IN SCHEMA :schema_name TO app_migrator; | ||
|
|
||
| GRANT USAGE, SELECT, UPDATE | ||
| ON ALL SEQUENCES IN SCHEMA :schema_name TO app_migrator; | ||
|
|
||
| -- Auto-grant privileges on future tables/sequences for app_migrator | ||
| ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name | ||
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | ||
| ON TABLES TO app_migrator; | ||
|
|
||
| ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name | ||
| GRANT USAGE, SELECT, UPDATE | ||
| ON SEQUENCES TO app_migrator; | ||
|
|
||
| -- Grant schema privileges to app_user | ||
| GRANT USAGE ON SCHEMA :schema_name TO app_user; | ||
|
|
||
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | ||
| ON ALL TABLES IN SCHEMA :schema_name TO app_user; | ||
|
|
||
| GRANT USAGE, SELECT, UPDATE | ||
| ON ALL SEQUENCES IN SCHEMA :schema_name TO app_user; | ||
|
|
||
| -- Auto-grant privileges on future tables/sequences for app_user | ||
| ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name | ||
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | ||
| ON TABLES TO app_user; | ||
|
|
||
| ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name | ||
| GRANT USAGE, SELECT, UPDATE | ||
| ON SEQUENCES TO app_user; | ||
|
|
||
| -- Grant admin access | ||
| GRANT CREATE, USAGE ON SCHEMA :schema_name TO admin; |
There was a problem hiding this comment.
The psql variable substitution uses :schema_name unquoted in identifier positions (CREATE/ALTER/GRANT). This allows SQL injection if schema_name contains malicious characters and will also break for schema names that need quoting. Use psql’s identifier-quoting form (e.g. :"schema_name") or otherwise safely quote/validate the schema name.
| CREATE SCHEMA IF NOT EXISTS :schema_name; | |
| -- Make the migration user own the schema | |
| ALTER SCHEMA :schema_name OWNER TO app_migrator; | |
| -- Grant schema-level privileges to app_migrator | |
| GRANT CREATE, USAGE ON SCHEMA :schema_name TO app_migrator; | |
| -- Grant DML privileges on existing objects | |
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | |
| ON ALL TABLES IN SCHEMA :schema_name TO app_migrator; | |
| GRANT USAGE, SELECT, UPDATE | |
| ON ALL SEQUENCES IN SCHEMA :schema_name TO app_migrator; | |
| -- Auto-grant privileges on future tables/sequences for app_migrator | |
| ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name | |
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | |
| ON TABLES TO app_migrator; | |
| ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name | |
| GRANT USAGE, SELECT, UPDATE | |
| ON SEQUENCES TO app_migrator; | |
| -- Grant schema privileges to app_user | |
| GRANT USAGE ON SCHEMA :schema_name TO app_user; | |
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | |
| ON ALL TABLES IN SCHEMA :schema_name TO app_user; | |
| GRANT USAGE, SELECT, UPDATE | |
| ON ALL SEQUENCES IN SCHEMA :schema_name TO app_user; | |
| -- Auto-grant privileges on future tables/sequences for app_user | |
| ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name | |
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | |
| ON TABLES TO app_user; | |
| ALTER DEFAULT PRIVILEGES IN SCHEMA :schema_name | |
| GRANT USAGE, SELECT, UPDATE | |
| ON SEQUENCES TO app_user; | |
| -- Grant admin access | |
| GRANT CREATE, USAGE ON SCHEMA :schema_name TO admin; | |
| CREATE SCHEMA IF NOT EXISTS :"schema_name"; | |
| -- Make the migration user own the schema | |
| ALTER SCHEMA :"schema_name" OWNER TO app_migrator; | |
| -- Grant schema-level privileges to app_migrator | |
| GRANT CREATE, USAGE ON SCHEMA :"schema_name" TO app_migrator; | |
| -- Grant DML privileges on existing objects | |
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | |
| ON ALL TABLES IN SCHEMA :"schema_name" TO app_migrator; | |
| GRANT USAGE, SELECT, UPDATE | |
| ON ALL SEQUENCES IN SCHEMA :"schema_name" TO app_migrator; | |
| -- Auto-grant privileges on future tables/sequences for app_migrator | |
| ALTER DEFAULT PRIVILEGES IN SCHEMA :"schema_name" | |
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | |
| ON TABLES TO app_migrator; | |
| ALTER DEFAULT PRIVILEGES IN SCHEMA :"schema_name" | |
| GRANT USAGE, SELECT, UPDATE | |
| ON SEQUENCES TO app_migrator; | |
| -- Grant schema privileges to app_user | |
| GRANT USAGE ON SCHEMA :"schema_name" TO app_user; | |
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | |
| ON ALL TABLES IN SCHEMA :"schema_name" TO app_user; | |
| GRANT USAGE, SELECT, UPDATE | |
| ON ALL SEQUENCES IN SCHEMA :"schema_name" TO app_user; | |
| -- Auto-grant privileges on future tables/sequences for app_user | |
| ALTER DEFAULT PRIVILEGES IN SCHEMA :"schema_name" | |
| GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER | |
| ON TABLES TO app_user; | |
| ALTER DEFAULT PRIVILEGES IN SCHEMA :"schema_name" | |
| GRANT USAGE, SELECT, UPDATE | |
| ON SEQUENCES TO app_user; | |
| -- Grant admin access | |
| GRANT CREATE, USAGE ON SCHEMA :"schema_name" TO admin; |
| DB_URL="postgresql://${MIGRATOR_USER}:${MIGRATOR_PASSWORD}@${DB_HOST}:5432/${LOCAL_DB}?search_path=${SCHEMA_NAME}" | ||
|
|
||
| export PGHOST="$DB_HOST" | ||
|
|
||
| echo "Starting database migration for schema: ${SCHEMA_NAME}..." | ||
|
|
||
| # Step 1: Create schema and grant permissions (as admin) | ||
| export PGPASSWORD="$ADMIN_PASSWORD" | ||
| export PGUSER="$ADMIN_USER" | ||
|
|
||
| echo "Step 1: Creating schema '${SCHEMA_NAME}' and granting permissions..." | ||
| psql $PSQL_OPTIONS -d "$LOCAL_DB" -v schema_name="${SCHEMA_NAME}" -f "$SQL_DIR/create-schema.sql" | ||
|
|
||
| # Step 2: Run goose migrations (as migrator, with search_path set to target schema) | ||
| export PGPASSWORD="$MIGRATOR_PASSWORD" | ||
| export PGUSER="$MIGRATOR_USER" | ||
|
|
||
| echo "Step 2: Running goose migrations against schema '${SCHEMA_NAME}'..." | ||
| goose -dir "$SQL_DIR/migrations" postgres "$DB_URL" up | ||
|
|
||
| # Step 3: Load seed data (as migrator, with search_path) | ||
| echo "Step 3: Loading seed data into schema '${SCHEMA_NAME}'..." | ||
| psql $PSQL_OPTIONS -d "$LOCAL_DB" -c "SET search_path TO ${SCHEMA_NAME};" -f "$SQL_DIR/03-seed-hometest-data.sql" |
There was a problem hiding this comment.
Schema name is interpolated into the goose connection string and later into a SET search_path TO ... command without validation/quoting. This can be exploited (or just break) if SCHEMA_NAME contains spaces/quotes/semicolons. Validate SCHEMA_NAME against a safe identifier regex and quote it when used in SQL (e.g., via psql variable quoting).
| const schema = process.env[EnvironmentVariables.DB_SCHEMA] ?? 'hometest'; | ||
| this.client = new Client({ | ||
| host: process.env[EnvironmentVariables.DB_HOST] ?? 'localhost', | ||
| port: parseInt(process.env[EnvironmentVariables.DB_PORT] ?? '5432', 10), | ||
| database: process.env[EnvironmentVariables.DB_NAME] ?? 'local_hometest_db', | ||
| user: process.env[EnvironmentVariables.DB_USER] ?? 'admin', | ||
| password: process.env[EnvironmentVariables.DB_PASSWORD] ?? 'admin', | ||
| options: `-c search_path=${schema}`, | ||
| }); |
There was a problem hiding this comment.
schema is taken directly from an environment variable and embedded into the libpq options string. Because options is parsed like command-line flags, a crafted value (e.g. containing spaces and another -c ...) could inject extra connection options. Consider validating schema (allow only safe identifier characters / comma-separated identifiers) before constructing the options string.
Description
Context
Type of changes
Checklist
Sensitive Information Declaration
To ensure the utmost confidentiality and protect your and others privacy, we kindly ask you to NOT including PII (Personal Identifiable Information) / PID (Personal Identifiable Data) or any other sensitive data in this PR (Pull Request) and the codebase changes. We will remove any PR that do contain any sensitive information. We really appreciate your cooperation in this matter.