mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 18:19:05 +02:00
Fail closed for public tenant DB startup
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
587a61c64e
commit
5e6e2e6b43
2 files changed
+55
No files matched your search
@@ -216,6 +216,35 @@ describe("startServer feedback export wiring", () => {
|
||||
serverPort: 3210,
|
||||
});
|
||||
});
|
||||
|
||||
it("refuses authenticated public startup without an external database URL", async () => {
|
||||
loadConfigMock.mockReturnValue(buildTestConfig({
|
||||
deploymentExposure: "public",
|
||||
authBaseUrlMode: "explicit",
|
||||
authPublicBaseUrl: "https://tenant.example.com",
|
||||
databaseMode: "embedded-postgres",
|
||||
databaseUrl: undefined,
|
||||
}));
|
||||
|
||||
await expect(startServer()).rejects.toThrow(
|
||||
"authenticated public deployments require DATABASE_URL or config.database.connectionString",
|
||||
);
|
||||
expect(createDbMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("refuses authenticated public startup when DATABASE_URL is not a postgres URL", async () => {
|
||||
loadConfigMock.mockReturnValue(buildTestConfig({
|
||||
deploymentExposure: "public",
|
||||
authBaseUrlMode: "explicit",
|
||||
authPublicBaseUrl: "https://tenant.example.com",
|
||||
databaseUrl: "secret://paperclip-cloud/stacks/alpha/database/runtime-url",
|
||||
}));
|
||||
|
||||
await expect(startServer()).rejects.toThrow(
|
||||
"authenticated public deployments require DATABASE_URL to be a postgres/postgresql connection string",
|
||||
);
|
||||
expect(createDbMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("startServer authenticated auth origin setup", () => {
|
||||
|
||||
@@ -187,6 +187,31 @@ export async function startServer(): Promise<StartedServer> {
|
||||
return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1";
|
||||
}
|
||||
|
||||
function isPostgresConnectionString(connectionString: string): boolean {
|
||||
try {
|
||||
const parsed = new URL(connectionString);
|
||||
return parsed.protocol === "postgres:" || parsed.protocol === "postgresql:";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function assertCloudDatabaseContract(): void {
|
||||
if (config.deploymentMode !== "authenticated" || config.deploymentExposure !== "public") {
|
||||
return;
|
||||
}
|
||||
if (!config.databaseUrl) {
|
||||
throw new Error(
|
||||
"authenticated public deployments require DATABASE_URL or config.database.connectionString; refusing embedded PostgreSQL fallback",
|
||||
);
|
||||
}
|
||||
if (!isPostgresConnectionString(config.databaseUrl)) {
|
||||
throw new Error(
|
||||
"authenticated public deployments require DATABASE_URL to be a postgres/postgresql connection string",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function rewriteLocalUrlPort(rawUrl: string | undefined, port: number): string | undefined {
|
||||
if (!rawUrl) return undefined;
|
||||
try {
|
||||
@@ -270,6 +295,7 @@ export async function startServer(): Promise<StartedServer> {
|
||||
let startupDbInfo:
|
||||
| { mode: "external-postgres"; connectionString: string }
|
||||
| { mode: "embedded-postgres"; dataDir: string; port: number };
|
||||
assertCloudDatabaseContract();
|
||||
if (config.databaseUrl) {
|
||||
const migrationUrl = config.databaseMigrationUrl ?? config.databaseUrl;
|
||||
migrationSummary = await ensureMigrations(migrationUrl, "PostgreSQL");
|
||||
|
||||
Reference in new issue
Block a user