fix(db): RDS-CA i poolen (app+migrator delar TLS)
This commit is contained in:
@@ -1,12 +1,33 @@
|
||||
import fs from "node:fs";
|
||||
import pg from "pg";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { readFileSync } from "node:fs";
|
||||
import * as schema from "./schema/index.js";
|
||||
|
||||
export type Database = ReturnType<typeof createDatabase>["db"];
|
||||
|
||||
let sharedPool: pg.Pool | undefined;
|
||||
|
||||
function resolveSsl(url: string): pg.PoolConfig["ssl"] | undefined {
|
||||
const caPath = process.env.DATABASE_CA_CERT;
|
||||
if (caPath && fs.existsSync(caPath)) {
|
||||
return { ca: fs.readFileSync(caPath, "utf8"), rejectUnauthorized: true };
|
||||
}
|
||||
if (/sslmode=(require|verify)/.test(url)) {
|
||||
return { rejectUnauthorized: false };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function stripSslmode(url: string): string {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
u.searchParams.delete("sslmode");
|
||||
return u.toString();
|
||||
} catch {
|
||||
return url.replace(/[?&]sslmode=[^&]+/, "").replace(/\?$/, "");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skapar en databasklient. API och worker delar mönster men äger varsin pool.
|
||||
* DATABASE_URL pekar på appens separata databas med egen minimalprivilegie-användare
|
||||
@@ -14,9 +35,9 @@ let sharedPool: pg.Pool | undefined;
|
||||
*/
|
||||
export function createDatabase(connectionString?: string) {
|
||||
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST !== undefined;
|
||||
const url = connectionString ?? (isTest ? process.env.TEST_DATABASE_URL : process.env.DATABASE_URL);
|
||||
const rawUrl = connectionString ?? (isTest ? process.env.TEST_DATABASE_URL : process.env.DATABASE_URL);
|
||||
|
||||
if (!url) {
|
||||
if (!rawUrl) {
|
||||
if (isTest) {
|
||||
throw new Error(
|
||||
"Sätt TEST_DATABASE_URL till en dedikerad testdatabas. Tester får aldrig använda DATABASE_URL.",
|
||||
@@ -25,26 +46,13 @@ export function createDatabase(connectionString?: string) {
|
||||
throw new Error("Missing DATABASE_URL. Set it in your .env or environment before starting the app.");
|
||||
}
|
||||
|
||||
// RDS SSL: om NODE_EXTRA_CA_CERTS är satt används det certifikatet explicit.
|
||||
// Detta krävs för node-postgres när sslmode=require och certifikatet inte finns
|
||||
// i systemets CA-butik (t.ex. AWS RDS regionala CA).
|
||||
let ssl: pg.PoolConfig["ssl"] | undefined;
|
||||
const extraCa = process.env.NODE_EXTRA_CA_CERTS;
|
||||
if (extraCa && url.includes("sslmode=")) {
|
||||
try {
|
||||
ssl = { ca: readFileSync(extraCa) };
|
||||
} catch {
|
||||
// Certifikatet kunde inte läsas – låt pg falla tillbaka till standardbeteende
|
||||
// så att felet blir tydligt vid anslutning.
|
||||
}
|
||||
}
|
||||
|
||||
const url = stripSslmode(rawUrl);
|
||||
const pool = new pg.Pool({
|
||||
connectionString: url,
|
||||
max: Number(process.env.DATABASE_POOL_MAX ?? 10),
|
||||
idleTimeoutMillis: 30_000,
|
||||
connectionTimeoutMillis: 10_000,
|
||||
...(ssl ? { ssl } : {}),
|
||||
ssl: resolveSsl(rawUrl),
|
||||
});
|
||||
|
||||
const db = drizzle(pool, { schema, casing: "snake_case" });
|
||||
|
||||
Reference in New Issue
Block a user