Skip to content

Commit b29f4b4

Browse files
committed
feat: validate supabase direct connections
1 parent 01d79ab commit b29f4b4

3 files changed

Lines changed: 34 additions & 46 deletions

File tree

src/server/http.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,24 @@ async function onSync(req: Request) {
2222
try {
2323
body = SyncRequest.parse(JSON.parse(bodyString));
2424
} catch (e: unknown) {
25-
console.log(e);
2625
if (e instanceof ZodError) {
27-
return new Response(e.message, { status: 400 });
26+
return Response.json(
27+
{
28+
kind: "error",
29+
type: "invalid_body",
30+
error: e.issues.map((issue) => issue.message).join("\n"),
31+
},
32+
{ status: 400 }
33+
);
2834
}
29-
return new Response("Invalid body", { status: 400 });
35+
return Response.json(
36+
{
37+
kind: "error",
38+
type: "unknown_error",
39+
error: String(e),
40+
},
41+
{ status: 400 }
42+
);
3043
}
3144
const { seed, schema, requiredRows, maxRows } = body;
3245
const span = trace.getActiveSpan();
@@ -45,21 +58,21 @@ async function onSync(req: Request) {
4558
span?.setAttribute("requiredRows", requiredRows);
4659
span?.setAttribute("db.host", url.hostname);
4760
let dbUrl: URL;
48-
try {
49-
dbUrl = new URL(body.db);
50-
} catch (e: unknown) {
51-
span?.setStatus({ code: SpanStatusCode.ERROR, message: "invalid_db_url" });
52-
if (e instanceof Error) {
53-
return Response.json(
54-
{ kind: "error", type: "invalid_db_url", error: e.message },
55-
{ status: 400 }
56-
);
57-
}
58-
return new Response("Invalid db url", { status: 400 });
59-
}
61+
// try {
62+
// dbUrl = new URL(body.db);
63+
// } catch (e: unknown) {
64+
// span?.setStatus({ code: SpanStatusCode.ERROR, message: "invalid_db_url" });
65+
// if (e instanceof Error) {
66+
// return Response.json(
67+
// { kind: "error", type: "invalid_db_url", error: e.message },
68+
// { status: 400 }
69+
// );
70+
// }
71+
// return new Response("Invalid db url", { status: 400 });
72+
// }
6073
let result: SyncResult;
6174
try {
62-
result = await syncer.syncWithUrl(dbUrl, schema, {
75+
result = await syncer.syncWithUrl(body.db, schema, {
6376
requiredRows,
6477
maxRows,
6578
seed,

src/server/sync.dto.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
import { z } from "zod/v4";
2+
import { Connectable } from "../sync/connectable.ts";
23

34
export const SyncRequest = z.object({
4-
// We shouldn't be doing separate validations on both the front end and here? Should probably consolidate
5-
db: z
6-
.string()
7-
.refine(
8-
(val) => val.startsWith("postgres://") || val.startsWith("postgresql://"),
9-
{
10-
message: "Must start with 'postgres://' or 'postgresql://'",
11-
}
12-
),
5+
db: z.string().transform(Connectable.transform),
136
seed: z.coerce.number().min(0).max(1).default(0),
147
schema: z.coerce.string().default("public"),
158
requiredRows: z.coerce.number().positive().default(2),

src/sync/syncer.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
import { PostgresSchemaLink } from "./schema.ts";
1414
import { withSpan } from "../otel.ts";
1515
import { SpanStatusCode } from "@opentelemetry/api";
16-
import { env } from "../env.ts";
16+
import { Connectable } from "./connectable.ts";
1717

1818
type SyncOptions = DependencyAnalyzerOptions;
1919

@@ -72,29 +72,11 @@ export class PostgresSyncer {
7272
constructor() {}
7373

7474
async syncWithUrl(
75-
url: URL,
75+
connectable: Connectable,
7676
schemaName: string,
7777
options: SyncOptions
7878
): Promise<SyncResult> {
79-
// we don't want to allow localhost access for hosted sync instances
80-
// to prevent users from connecting to our hosted db
81-
// (even though all our dbs should should be password protected)
82-
const isLocalhost =
83-
url.hostname === "localhost" ||
84-
// ipv4 localhost
85-
/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(url.hostname) ||
86-
// ipv6 localhost
87-
url.hostname === "[::1]";
88-
if (isLocalhost && env.HOSTED) {
89-
return {
90-
kind: "error",
91-
type: "postgres_connection_error",
92-
error: new Error(
93-
"Syncing to localhost is not allowed. Run the sync server locally to access your local database"
94-
),
95-
};
96-
}
97-
const urlString = url.toString();
79+
const urlString = connectable.toString();
9880
let sql = this.connections.get(urlString);
9981
if (!sql) {
10082
sql = postgres(urlString);

0 commit comments

Comments
 (0)