mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
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:
Generated
+3
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user