fix(settings): accept socket postgres urls in DATABASE_URL validation

Validate DATABASE_URL with pg's connection-string parser so unix-socket DSNs with empty authority hosts pass while malformed/non-postgres values are rejected.

Closes #106
This commit is contained in:
neon
2026-05-26 01:48:24 +00:00
parent 39bb872f7c
commit 78b99d0b78
4 changed files with 84 additions and 1 deletions
+3
View File
@@ -356,6 +356,9 @@ importers:
pg:
specifier: ^8.21.0
version: 8.21.0
pg-connection-string:
specifier: ^2.13.0
version: 2.13.0
pino-http:
specifier: ^11.0.0
version: 11.0.0
+1
View File
@@ -85,6 +85,7 @@
"passport-jwt": "^4.0.1",
"pdf-lib": "^1.17.1",
"pg": "^8.21.0",
"pg-connection-string": "^2.13.0",
"pino-http": "^11.0.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2",
+61
View File
@@ -0,0 +1,61 @@
import { validateEnv } from './env.validation';
const BASE_ENV = {
NODE_ENV: 'development',
JWT_SECRET: '1234567890abcdef',
};
describe('validateEnv', () => {
it('accepts common postgres URL formats used by existing setups', () => {
const urls = [
'postgres://bookorbit:bookorbit@localhost:5432/bookorbit',
'postgres://localhost',
'postgresql://bookorbit:bookorbit@db.internal:5432/bookorbit?sslmode=require',
];
for (const DATABASE_URL of urls) {
expect(() =>
validateEnv({
...BASE_ENV,
DATABASE_URL,
}),
).not.toThrow();
}
});
it('accepts a postgres socket connection string with query host and no authority host', () => {
expect(() =>
validateEnv({
...BASE_ENV,
DATABASE_URL: 'postgres://bookorbit:testpw%40bookorbit@/bookorbit?host=/run/postgresql&port=5432',
}),
).not.toThrow();
});
it('accepts a postgres connection string with localhost and query socket host', () => {
expect(() =>
validateEnv({
...BASE_ENV,
DATABASE_URL: 'postgres://bookorbit:testpw%40bookorbit@localhost/bookorbit?host=/run/postgresql&port=5432',
}),
).not.toThrow();
});
it('rejects malformed postgres connection strings', () => {
expect(() =>
validateEnv({
...BASE_ENV,
DATABASE_URL: 'postgres://bookorbit@/bookorbit?host=/run/postgresql&port=abc',
}),
).toThrow('DATABASE_URL must be a valid PostgreSQL connection string');
});
it('rejects non-postgres URLs', () => {
expect(() =>
validateEnv({
...BASE_ENV,
DATABASE_URL: 'https://example.com/database',
}),
).toThrow('DATABASE_URL must be a valid PostgreSQL connection string');
});
});
+19 -1
View File
@@ -1,8 +1,26 @@
import { parseIntoClientConfig } from 'pg-connection-string';
import { z } from 'zod';
function isValidPostgresConnectionString(value: string): boolean {
if (!value.trim()) {
return false;
}
if (!/^postgres(?:ql)?:\/\//i.test(value)) {
return false;
}
try {
parseIntoClientConfig(value);
return true;
} catch {
return false;
}
}
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL').optional(),
DATABASE_URL: z.string().refine(isValidPostgresConnectionString, 'DATABASE_URL must be a valid PostgreSQL connection string').optional(),
JWT_SECRET: z
.string()
.min(16, 'JWT_SECRET must be at least 16 characters')