@rebasepro/server-postgres 0.17.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -1
- package/README.md +31 -0
- package/dist/backup-argv.d.ts +19 -0
- package/dist/branch-argv.d.ts +28 -0
- package/dist/branch-prune.d.ts +77 -0
- package/dist/cli-collections-path.d.ts +31 -0
- package/dist/cli-errors.d.ts +123 -0
- package/dist/cli-flags.d.ts +101 -0
- package/dist/cli-helpers.d.ts +94 -10
- package/dist/collection-index-C7LopMyX.js +247 -0
- package/dist/collection-index-C7LopMyX.js.map +1 -0
- package/dist/databasePoolManager.d.ts +27 -0
- package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-CagOzEPS.js} +6 -6
- package/dist/{ensure-collection-policies-UCqgv_8c.js.map → ensure-collection-policies-CagOzEPS.js.map} +1 -1
- package/dist/{ensure-collection-tables-DgVixhX3.js → ensure-collection-tables-DbAhrjnG.js} +614 -52
- package/dist/ensure-collection-tables-DbAhrjnG.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.es.js +19307 -3059
- package/dist/index.es.js.map +1 -1
- package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-ClJoTboT.js} +53 -189
- package/dist/rls-enforcement-ClJoTboT.js.map +1 -0
- package/dist/schema/column-type-drift.d.ts +79 -0
- package/dist/schema/doctor.d.ts +42 -0
- package/dist/schema/ensure-collection-tables.d.ts +90 -0
- package/dist/schema/generate-next-step.d.ts +30 -0
- package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
- package/dist/schema/generated-schema-staleness.d.ts +37 -0
- package/dist/schema/introspect-db-queries.d.ts +34 -0
- package/dist/security/rls-enforcement.d.ts +63 -2
- package/dist/services/BranchService.d.ts +86 -1
- package/dist/services/collection-helpers.d.ts +1 -1
- package/dist/services/realtimeService.d.ts +30 -2
- package/dist/services/row-pipeline.d.ts +12 -1
- package/dist/utils/pg-error-utils.d.ts +3 -1
- package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
- package/package.json +32 -27
- package/dist/backup-service-DCk7KhhL.js +0 -9044
- package/dist/backup-service-DCk7KhhL.js.map +0 -1
- package/dist/collection-index-BRUg10H5.js +0 -5690
- package/dist/collection-index-BRUg10H5.js.map +0 -1
- package/dist/connection-GOKU3Hu5.js +0 -277
- package/dist/connection-GOKU3Hu5.js.map +0 -1
- package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
- package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
- package/dist/src-DiB5RP2Z.js +0 -541
- package/dist/src-DiB5RP2Z.js.map +0 -1
- package/dist/websocket-BZ4H5wUz.js +0 -8198
- package/dist/websocket-BZ4H5wUz.js.map +0 -1
- package/src/PostgresAdapter.ts +0 -91
- package/src/PostgresBackendDriver.ts +0 -1857
- package/src/PostgresBootstrapper.ts +0 -1157
- package/src/auth/ensure-tables.ts +0 -896
- package/src/auth/schema-version.ts +0 -260
- package/src/auth/services.ts +0 -1610
- package/src/backup/backup-cli.ts +0 -520
- package/src/backup/backup-cron.ts +0 -212
- package/src/backup/backup-logic.ts +0 -93
- package/src/backup/backup-service.ts +0 -485
- package/src/backup/index.ts +0 -12
- package/src/backup/pg-tools.ts +0 -403
- package/src/backup/retention.ts +0 -86
- package/src/cli-errors.ts +0 -265
- package/src/cli-helpers.ts +0 -569
- package/src/cli-output.ts +0 -43
- package/src/cli.ts +0 -1339
- package/src/collections/PostgresCollectionRegistry.ts +0 -108
- package/src/collections/buildRegistry.ts +0 -68
- package/src/collections/validate-relations.ts +0 -402
- package/src/connection.ts +0 -333
- package/src/data-transformer.ts +0 -845
- package/src/databasePoolManager.ts +0 -95
- package/src/history/HistoryService.ts +0 -254
- package/src/history/ensure-history-table.ts +0 -53
- package/src/index.ts +0 -19
- package/src/interfaces.ts +0 -60
- package/src/module-dir.ts +0 -7
- package/src/schema/atlas-argv.ts +0 -94
- package/src/schema/auth-schema.ts +0 -298
- package/src/schema/auth-users-columns.ts +0 -131
- package/src/schema/carved-out-migration.ts +0 -404
- package/src/schema/classify-change.ts +0 -436
- package/src/schema/collection-index.ts +0 -427
- package/src/schema/collections-schema-version.ts +0 -103
- package/src/schema/destructive-sql.ts +0 -94
- package/src/schema/doctor-cli.ts +0 -66
- package/src/schema/doctor-policy-checks.ts +0 -105
- package/src/schema/doctor.ts +0 -837
- package/src/schema/drizzle-ddl.ts +0 -33
- package/src/schema/dynamic-tables.test.ts +0 -302
- package/src/schema/dynamic-tables.ts +0 -293
- package/src/schema/ensure-collection-policies.ts +0 -198
- package/src/schema/ensure-collection-tables.test.ts +0 -518
- package/src/schema/ensure-collection-tables.ts +0 -1267
- package/src/schema/generate-drizzle-schema-logic.ts +0 -957
- package/src/schema/generate-drizzle-schema.ts +0 -143
- package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
- package/src/schema/generate-postgres-ddl.ts +0 -180
- package/src/schema/generate-schema-commit.ts +0 -267
- package/src/schema/generated-schema-staleness.ts +0 -284
- package/src/schema/introspect-db-constraints.ts +0 -385
- package/src/schema/introspect-db-inference.ts +0 -255
- package/src/schema/introspect-db-logic.ts +0 -1387
- package/src/schema/introspect-db-naming.ts +0 -15
- package/src/schema/introspect-db-project.ts +0 -78
- package/src/schema/introspect-db-queries.ts +0 -326
- package/src/schema/introspect-db-structure.ts +0 -670
- package/src/schema/introspect-db-types.ts +0 -56
- package/src/schema/introspect-db.ts +0 -257
- package/src/schema/introspect-runtime.test.ts +0 -260
- package/src/schema/introspect-runtime.ts +0 -327
- package/src/schema/non-sql-collections.test.ts +0 -131
- package/src/schema/rls-bootstrap-sql.ts +0 -288
- package/src/schema/search-column.ts +0 -643
- package/src/schema/test-schema.ts +0 -11
- package/src/schema/vector-index.ts +0 -491
- package/src/security/anonymous-grants.test.ts +0 -73
- package/src/security/policy-drift.test.ts +0 -435
- package/src/security/policy-drift.ts +0 -473
- package/src/security/rls-enforcement.ts +0 -505
- package/src/services/BranchService.ts +0 -326
- package/src/services/FetchService.ts +0 -2115
- package/src/services/PersistService.ts +0 -557
- package/src/services/RelationService.ts +0 -978
- package/src/services/RelationWriteService.ts +0 -653
- package/src/services/cdc/CdcListener.ts +0 -103
- package/src/services/cdc/junction-tables.ts +0 -91
- package/src/services/cdc/trigger-cdc.ts +0 -173
- package/src/services/channel-bus/ChannelBus.ts +0 -44
- package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
- package/src/services/channel-bus/index.ts +0 -114
- package/src/services/channel-history.ts +0 -424
- package/src/services/channel-presence.ts +0 -185
- package/src/services/collection-helpers.ts +0 -420
- package/src/services/dataService.ts +0 -256
- package/src/services/index.ts +0 -14
- package/src/services/junction-writes.ts +0 -295
- package/src/services/nested-path.ts +0 -145
- package/src/services/pg-notify-listener.ts +0 -151
- package/src/services/realtimeService.ts +0 -2545
- package/src/services/row-pipeline.ts +0 -240
- package/src/services/write-denial.ts +0 -55
- package/src/types.ts +0 -4
- package/src/utils/connection-string.ts +0 -58
- package/src/utils/drizzle-conditions.ts +0 -2743
- package/src/utils/pg-array-null-patch.ts +0 -42
- package/src/utils/pg-error-utils.ts +0 -391
- package/src/websocket.ts +0 -855
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Applying a bundle's RLS policies to a database at boot, idempotently.
|
|
3
|
-
*
|
|
4
|
-
* ## Why this exists
|
|
5
|
-
*
|
|
6
|
-
* {@link ensureCollectionTables} creates the collection *tables* a managed
|
|
7
|
-
* runtime boots against, but a table with row-level security disabled and no
|
|
8
|
-
* policies is not servable: authenticated requests run as the restricted
|
|
9
|
-
* `rebase_user` role, so a read with no `SELECT` policy returns nothing (a
|
|
10
|
-
* public collection answered 401) and a write with no `INSERT`/`UPDATE` policy
|
|
11
|
-
* is denied. The policies live in the collections' `securityRules`; nothing at
|
|
12
|
-
* boot applied them. `rebase db push` does — but it drives Atlas against a
|
|
13
|
-
* local `DATABASE_URL`, and a managed tenant's database is reachable only from
|
|
14
|
-
* inside the cluster, by the runtime that is already connected to it. So the
|
|
15
|
-
* runtime is the only thing that *can* apply them, and this is where it does.
|
|
16
|
-
*
|
|
17
|
-
* ## Why this is safe to run on every boot
|
|
18
|
-
*
|
|
19
|
-
* Every statement is idempotent: `ENABLE ROW LEVEL SECURITY` is a no-op once
|
|
20
|
-
* enabled, and each policy is a `DROP POLICY IF EXISTS` immediately followed by
|
|
21
|
-
* a `CREATE POLICY`, so re-applying asserts exactly the declared state. It adds
|
|
22
|
-
* and replaces; it never drops data. (It does not *reconcile* — a policy a
|
|
23
|
-
* previous push left behind under an old name is not removed here; that stays a
|
|
24
|
-
* `db push` / `db migrate` concern, alongside destructive schema changes.)
|
|
25
|
-
*
|
|
26
|
-
* Unlike table creation, a failure here is not fatal: RLS stays enabled, so a
|
|
27
|
-
* table whose policies could not be applied fails **closed** (denies) rather
|
|
28
|
-
* than leaking rows. One collection's policy failing (e.g. a rule that
|
|
29
|
-
* references a table a real migration has not created yet) must not crash-loop
|
|
30
|
-
* the whole deployment and take the other collections' working routes down with
|
|
31
|
-
* it. Failures are reported loudly and per-table so the operator can see
|
|
32
|
-
* exactly which collection is not yet servable and why.
|
|
33
|
-
*/
|
|
34
|
-
import { type CollectionConfig } from "@rebasepro/types";
|
|
35
|
-
import { planCollectionPolicies, type CollectionPolicyPlan } from "./generate-postgres-ddl-logic";
|
|
36
|
-
import { isGeneratedPolicyName } from "../security/policy-drift";
|
|
37
|
-
import { readExistingSchema, type Queryable } from "./ensure-collection-tables";
|
|
38
|
-
import { REBASE_USER_ROLE } from "../security/rls-enforcement";
|
|
39
|
-
|
|
40
|
-
export interface PolicyEnsureResult {
|
|
41
|
-
/** `CREATE POLICY` statements that ran successfully. */
|
|
42
|
-
policiesApplied: number;
|
|
43
|
-
/** Tables that had RLS enabled. */
|
|
44
|
-
tablesSecured: number;
|
|
45
|
-
/** Declared tables absent from the database — left to a real migration. */
|
|
46
|
-
skipped: { table: string; reason: string }[];
|
|
47
|
-
/**
|
|
48
|
-
* Tables that have RLS on but did not get every policy. They deny — RLS
|
|
49
|
-
* with no matching policy is deny-all — so they are safe but not servable.
|
|
50
|
-
*/
|
|
51
|
-
failures: { table: string; error: string }[];
|
|
52
|
-
/**
|
|
53
|
-
* Tables RLS could not be enabled on, whose DML grant was withdrawn instead.
|
|
54
|
-
*
|
|
55
|
-
* This state had no name, and that was the bug: `ENABLE ROW LEVEL SECURITY`
|
|
56
|
-
* failing was recorded as a `failure` and reported with the same "it stays
|
|
57
|
-
* locked (denies)" wording as a failed policy — but the two are opposites.
|
|
58
|
-
* A policy statement failing leaves RLS on and the table denying. `enableRls`
|
|
59
|
-
* failing leaves RLS *off*, and the schema-wide grant to the user role has
|
|
60
|
-
* already been made by `ensureRlsEnforcement`, so the table is readable and
|
|
61
|
-
* writable by every authenticated request with no row filtering at all.
|
|
62
|
-
*/
|
|
63
|
-
unsecured: { table: string; error: string; grantWithdrawn: boolean }[];
|
|
64
|
-
/**
|
|
65
|
-
* Generated policies removed because no current rule produces them.
|
|
66
|
-
*
|
|
67
|
-
* A policy's name embeds a hash of the rule's semantics, so editing a rule
|
|
68
|
-
* does not update a policy — it creates a new one and abandons the old.
|
|
69
|
-
* Postgres ORs permissive policies, so the abandoned one keeps granting:
|
|
70
|
-
* a `USING (true)` tightened to an owner check went on admitting everyone,
|
|
71
|
-
* forever, while the deploy logged success.
|
|
72
|
-
*
|
|
73
|
-
* `db push` reconciles this, and cannot reach a managed tenant's in-cluster
|
|
74
|
-
* database — which is the reason this module exists. So boot has to do it.
|
|
75
|
-
*/
|
|
76
|
-
orphansDropped: number;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const isCreatePolicy = (statement: string): boolean => /^\s*CREATE POLICY/i.test(statement);
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Bring the declared collections' RLS policies up to date. Returns what it did.
|
|
83
|
-
*
|
|
84
|
-
* Only tables that already exist are touched: the boot-time table creator runs
|
|
85
|
-
* first, so anything still missing is a table this additive path is not allowed
|
|
86
|
-
* to create (a junction, or a relation left to a migration). Enabling RLS on a
|
|
87
|
-
* non-existent table would error, so those are recorded as skipped, not failed.
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Remove generated policies on this table that the current plan does not
|
|
92
|
-
* produce.
|
|
93
|
-
*
|
|
94
|
-
* Scoped hard, because dropping a policy is destructive: only this one table,
|
|
95
|
-
* only names matching the generated `<table>_<op>_<sha1>` shape, and only names
|
|
96
|
-
* absent from the statements just applied. A hand-written policy, or one
|
|
97
|
-
* belonging to another table, is never touched — `isGeneratedPolicyName` is the
|
|
98
|
-
* same predicate `db push` uses to draw that line.
|
|
99
|
-
*/
|
|
100
|
-
async function dropOrphanedPoliciesOn(client: Queryable, plan: CollectionPolicyPlan): Promise<number> {
|
|
101
|
-
const expected = new Set<string>();
|
|
102
|
-
for (const statement of plan.policyStatements) {
|
|
103
|
-
const match = /^\s*CREATE POLICY\s+"([^"]+)"/i.exec(statement);
|
|
104
|
-
if (match) expected.add(match[1]);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const existing = await client.query<{ policyname: string }>(
|
|
108
|
-
`SELECT policyname FROM pg_policies WHERE schemaname = '${plan.schema.replace(/'/g, "''")}' ` +
|
|
109
|
-
`AND tablename = '${plan.table.replace(/'/g, "''")}'`
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
let dropped = 0;
|
|
113
|
-
for (const row of existing.rows) {
|
|
114
|
-
const name = row.policyname;
|
|
115
|
-
if (expected.has(name)) continue;
|
|
116
|
-
if (!isGeneratedPolicyName(name, plan.table)) continue;
|
|
117
|
-
await client.query(`DROP POLICY IF EXISTS "${name}" ON "${plan.schema}"."${plan.table}"`);
|
|
118
|
-
dropped++;
|
|
119
|
-
}
|
|
120
|
-
return dropped;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export async function ensureCollectionPolicies(
|
|
124
|
-
client: Queryable,
|
|
125
|
-
collections: CollectionConfig[],
|
|
126
|
-
log?: (message: string) => void
|
|
127
|
-
): Promise<PolicyEnsureResult> {
|
|
128
|
-
const result: PolicyEnsureResult = { policiesApplied: 0, tablesSecured: 0, skipped: [], failures: [], unsecured: [], orphansDropped: 0 };
|
|
129
|
-
|
|
130
|
-
const plans = planCollectionPolicies(collections);
|
|
131
|
-
if (plans.length === 0) return result;
|
|
132
|
-
|
|
133
|
-
const schemas = Array.from(new Set(plans.map(p => p.schema)));
|
|
134
|
-
const existing = await readExistingSchema(client, schemas);
|
|
135
|
-
|
|
136
|
-
for (const plan of plans) {
|
|
137
|
-
if (!existing.tables.has(plan.qualified)) {
|
|
138
|
-
result.skipped.push({
|
|
139
|
-
table: plan.qualified,
|
|
140
|
-
reason: "table is not present in the database; create it with `rebase db push` / `rebase db migrate`"
|
|
141
|
-
});
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Enabling RLS is its own step, because its failure is the opposite of
|
|
146
|
-
// every other failure here. Once RLS is on, anything that goes wrong
|
|
147
|
-
// afterwards leaves the table denying; while it is off, the grant made
|
|
148
|
-
// earlier in boot leaves the table wide open. Sharing one `try` meant
|
|
149
|
-
// the dangerous case was reported in the safe case's words.
|
|
150
|
-
try {
|
|
151
|
-
await client.query(plan.enableRls);
|
|
152
|
-
result.tablesSecured++;
|
|
153
|
-
} catch (err) {
|
|
154
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
155
|
-
// Take the privilege back rather than serve an unprotected table.
|
|
156
|
-
// This is the fail-closed step the old code assumed it already had:
|
|
157
|
-
// per-table, so one collection cannot take the rest of the
|
|
158
|
-
// deployment down, but leaving nothing readable without RLS.
|
|
159
|
-
let grantWithdrawn = false;
|
|
160
|
-
try {
|
|
161
|
-
await client.query(`REVOKE ALL PRIVILEGES ON ${plan.qualified} FROM ${REBASE_USER_ROLE}`);
|
|
162
|
-
grantWithdrawn = true;
|
|
163
|
-
} catch {
|
|
164
|
-
// Fall through: reported below with `grantWithdrawn: false`,
|
|
165
|
-
// which the caller escalates. There is nothing else this
|
|
166
|
-
// function can do to make the table safe.
|
|
167
|
-
}
|
|
168
|
-
result.unsecured.push({ table: plan.qualified,
|
|
169
|
-
error,
|
|
170
|
-
grantWithdrawn });
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
let created = 0;
|
|
176
|
-
for (const statement of plan.policyStatements) {
|
|
177
|
-
await client.query(statement);
|
|
178
|
-
if (isCreatePolicy(statement)) {
|
|
179
|
-
result.policiesApplied++;
|
|
180
|
-
created++;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const orphans = await dropOrphanedPoliciesOn(client, plan);
|
|
185
|
-
result.orphansDropped += orphans;
|
|
186
|
-
|
|
187
|
-
log?.(`${plan.qualified}: RLS enabled, ${created} policy(ies) applied` +
|
|
188
|
-
(orphans > 0 ? `, ${orphans} orphan(s) dropped` : ""));
|
|
189
|
-
} catch (err) {
|
|
190
|
-
result.failures.push({
|
|
191
|
-
table: plan.qualified,
|
|
192
|
-
error: err instanceof Error ? err.message : String(err)
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return result;
|
|
198
|
-
}
|