@rebasepro/studio 0.23.0 → 0.24.1-canary.g56af4a4
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/dist/{ApiExplorer-pYMZflL3.js → ApiExplorer-Bzt7Ye49.js} +18 -29
- package/dist/ApiExplorer-Bzt7Ye49.js.map +1 -0
- package/dist/ApiKeysView-Bqf6XYR1.js +1739 -0
- package/dist/ApiKeysView-Bqf6XYR1.js.map +1 -0
- package/dist/BackupsView-BhOHB6iP.js +354 -0
- package/dist/BackupsView-BhOHB6iP.js.map +1 -0
- package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-BHfwGTYc.js} +16 -10
- package/dist/BranchesView-BHfwGTYc.js.map +1 -0
- package/dist/{CronJobsView-7hjgSYId.js → CronJobsView-CMNK7eWO.js} +175 -116
- package/dist/CronJobsView-CMNK7eWO.js.map +1 -0
- package/dist/{JSEditor-qiuwmcvk.js → JSEditor-DIO2Mw8q.js} +230 -165
- package/dist/JSEditor-DIO2Mw8q.js.map +1 -0
- package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-C-bwDFIl.js} +66 -44
- package/dist/LogsExplorer-C-bwDFIl.js.map +1 -0
- package/dist/{RLSEditor-Djh43lBE.js → RLSEditor-jFD_d06f.js} +246 -84
- package/dist/RLSEditor-jFD_d06f.js.map +1 -0
- package/dist/{SQLEditor-TBw_K6CW.js → SQLEditor-D5Ci4pyW.js} +502 -600
- package/dist/SQLEditor-D5Ci4pyW.js.map +1 -0
- package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-D-rAqwul.js} +496 -227
- package/dist/SchemaVisualizer-D-rAqwul.js.map +1 -0
- package/dist/{StorageView-CMG1DOVX.js → StorageView-CPkDKbsX.js} +838 -451
- package/dist/StorageView-CPkDKbsX.js.map +1 -0
- package/dist/assets/dagre-layout.worker-BFnhAzuR.js.map +1 -0
- package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
- package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
- package/dist/components/ApiKeys/scope-words.d.ts +37 -0
- package/dist/components/ApiKeys/scopes.d.ts +113 -0
- package/dist/components/AuthSimulationSelector.d.ts +5 -1
- package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
- package/dist/components/RLSEditor/policyListing.d.ts +44 -0
- package/dist/components/SQLEditor/SQLEditor.d.ts +11 -0
- package/dist/components/SchemaVisualizer/dagre-layout.d.ts +32 -0
- package/dist/components/SchemaVisualizer/dagre-layout.worker.d.ts +13 -0
- package/dist/components/SchemaVisualizer/layout-runner.d.ts +23 -0
- package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +21 -11
- package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +44 -1
- package/dist/components/StorageView/useVisibleFileMetadata.d.ts +46 -0
- package/dist/components/load-failure.d.ts +1 -1
- package/dist/components/useRunAsUsers.d.ts +25 -0
- package/dist/dagre-layout.worker-j9NbH9Gb.js +27 -0
- package/dist/dagre-layout.worker-j9NbH9Gb.js.map +1 -0
- package/dist/index.es.js +16 -14
- package/dist/index.es.js.map +1 -1
- package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
- package/dist/load-failure-Bd8lFIO7.js.map +1 -0
- package/dist/sql_utils-BZD31xE3.js +478 -0
- package/dist/sql_utils-BZD31xE3.js.map +1 -0
- package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
- package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
- package/dist/utils/sql_utils.d.ts +150 -49
- package/package.json +9 -9
- package/dist/ApiExplorer-pYMZflL3.js.map +0 -1
- package/dist/ApiKeysView-qLSs47tw.js +0 -1239
- package/dist/ApiKeysView-qLSs47tw.js.map +0 -1
- package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
- package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
- package/dist/BackupsView-DYTAD6aW.js +0 -253
- package/dist/BackupsView-DYTAD6aW.js.map +0 -1
- package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
- package/dist/CronJobsView-7hjgSYId.js.map +0 -1
- package/dist/JSEditor-qiuwmcvk.js.map +0 -1
- package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
- package/dist/RLSEditor-Djh43lBE.js.map +0 -1
- package/dist/SQLEditor-TBw_K6CW.js.map +0 -1
- package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
- package/dist/StorageView-CMG1DOVX.js.map +0 -1
- package/dist/components/ApiKeys/permissions.d.ts +0 -66
- package/dist/load-failure-C6OMJSGC.js.map +0 -1
|
@@ -5,7 +5,7 @@ import { Alert, AlertTriangleIcon, Button, Chip, CircularProgress, DatabaseIcon,
|
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { RLS_JWT_SQL, RLS_ROLES_SQL, RLS_UID_SQL, isPostgresCollectionConfig, policy } from "@rebasepro/types";
|
|
7
7
|
import { getPolicyNamesForRule, getPolicyNamesForRules, getPolicyOperations } from "@rebasepro/utils";
|
|
8
|
-
import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, REBASE_USER_ROLE, getEffectiveSecurityRules, getJunctionSecurityRules, getTableName, policyToPostgres, policyToSecurityRule, resolveJunctionSpecs } from "@rebasepro/common";
|
|
8
|
+
import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, REBASE_USER_ROLE, compileRulePolicies, getEffectiveSecurityRules, getInjectedSecurityRules, getJunctionSecurityRules, getTableName, policyToPostgres, policyToSecurityRule, resolveJunctionSpecs } from "@rebasepro/common";
|
|
9
9
|
//#region src/components/RLSEditor/policy-presets.ts
|
|
10
10
|
/**
|
|
11
11
|
* What the RLS editor offers before the user types anything: the roles a `TO`
|
|
@@ -241,6 +241,7 @@ var PolicyEditor = ({ policy, schema, table, roleOptions, onSave, onCancel }) =>
|
|
|
241
241
|
})
|
|
242
242
|
]
|
|
243
243
|
})] }), /* @__PURE__ */ jsx(IconButton, {
|
|
244
|
+
"aria-label": t("help"),
|
|
244
245
|
size: "small",
|
|
245
246
|
onClick: () => setHelpOpen(true),
|
|
246
247
|
children: /* @__PURE__ */ jsx(HelpCircleIcon, { size: iconSize.smallest })
|
|
@@ -506,23 +507,20 @@ var PolicyEditor = ({ policy, schema, table, roleOptions, onSave, onCancel }) =>
|
|
|
506
507
|
}),
|
|
507
508
|
/* @__PURE__ */ jsxs("ul", {
|
|
508
509
|
className: "list-disc pl-5 space-y-1 text-sm text-text-secondary dark:text-text-secondary-dark",
|
|
509
|
-
children: [
|
|
510
|
-
/* @__PURE__ */
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
t("studio_policy_help_role_anon")
|
|
524
|
-
] })
|
|
525
|
-
]
|
|
510
|
+
children: [/* @__PURE__ */ jsxs("li", { children: [
|
|
511
|
+
/* @__PURE__ */ jsx("strong", { children: "public" }),
|
|
512
|
+
": ",
|
|
513
|
+
t("studio_policy_help_role_public")
|
|
514
|
+
] }), /* @__PURE__ */ jsxs("li", { children: [
|
|
515
|
+
/* @__PURE__ */ jsx("strong", { children: REBASE_USER_ROLE }),
|
|
516
|
+
": ",
|
|
517
|
+
t("studio_policy_help_role_rebase_user")
|
|
518
|
+
] })]
|
|
519
|
+
}),
|
|
520
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
521
|
+
variant: "body2",
|
|
522
|
+
className: "text-text-secondary dark:text-text-secondary-dark mt-1",
|
|
523
|
+
children: t("studio_policy_help_roles_not_users")
|
|
526
524
|
})
|
|
527
525
|
]
|
|
528
526
|
}),
|
|
@@ -690,7 +688,7 @@ async function saveSecurityRulesToCodebase(options) {
|
|
|
690
688
|
const headers = { "Content-Type": "application/json" };
|
|
691
689
|
const token = getAuthToken ? await getAuthToken() : null;
|
|
692
690
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
693
|
-
const response = await fetch(`${apiBase}/schema-editor/collection/save`, {
|
|
691
|
+
const response = await fetch(`${apiBase}/admin/schema-editor/collection/save`, {
|
|
694
692
|
method: "POST",
|
|
695
693
|
headers,
|
|
696
694
|
body: JSON.stringify({
|
|
@@ -814,7 +812,7 @@ function editedRule(rule, original, edited) {
|
|
|
814
812
|
result.operation = next.operation;
|
|
815
813
|
}
|
|
816
814
|
if (edited.permissive !== original.permissive) result.mode = next.mode;
|
|
817
|
-
if (!sameRoles(edited.roles, original.roles)) if (next.pgRoles) result.pgRoles = next.pgRoles;
|
|
815
|
+
if (!sameRoles$1(edited.roles, original.roles)) if (next.pgRoles) result.pgRoles = next.pgRoles;
|
|
818
816
|
else delete result.pgRoles;
|
|
819
817
|
if ((edited.qual || null) !== (original.qual || null) || (edited.with_check || null) !== (original.with_check || null)) {
|
|
820
818
|
delete result.ownerField;
|
|
@@ -828,12 +826,123 @@ function editedRule(rule, original, edited) {
|
|
|
828
826
|
}
|
|
829
827
|
return result;
|
|
830
828
|
}
|
|
831
|
-
function sameRoles(a, b) {
|
|
829
|
+
function sameRoles$1(a, b) {
|
|
832
830
|
const left = [...a ?? []].sort();
|
|
833
831
|
const right = [...b ?? []].sort();
|
|
834
832
|
return left.length === right.length && left.every((role, i) => role === right[i]);
|
|
835
833
|
}
|
|
836
834
|
//#endregion
|
|
835
|
+
//#region src/components/RLSEditor/policyListing.ts
|
|
836
|
+
var COMMANDS = {
|
|
837
|
+
select: "SELECT",
|
|
838
|
+
insert: "INSERT",
|
|
839
|
+
update: "UPDATE",
|
|
840
|
+
delete: "DELETE",
|
|
841
|
+
all: "ALL"
|
|
842
|
+
};
|
|
843
|
+
/** A compiled policy, in the shape `pg_policies` describes one. */
|
|
844
|
+
function asPostgresPolicy(compiled, tableName) {
|
|
845
|
+
return {
|
|
846
|
+
policyname: compiled.name,
|
|
847
|
+
tablename: tableName,
|
|
848
|
+
permissive: compiled.mode === "restrictive" ? "RESTRICTIVE" : "PERMISSIVE",
|
|
849
|
+
cmd: COMMANDS[compiled.operation],
|
|
850
|
+
roles: compiled.roles,
|
|
851
|
+
qual: compiled.using,
|
|
852
|
+
with_check: compiled.withCheck
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* The rule's own clauses, for a rule the compiler could not compile — an
|
|
857
|
+
* `existsIn` naming a collection the registry does not hold, say. Only ever a
|
|
858
|
+
* `code_only` row's content: nothing is compared against it.
|
|
859
|
+
*/
|
|
860
|
+
function uncompiledPolicy(rule, operation, name, tableName) {
|
|
861
|
+
return {
|
|
862
|
+
policyname: name,
|
|
863
|
+
tablename: tableName,
|
|
864
|
+
permissive: rule.mode === "restrictive" ? "RESTRICTIVE" : "PERMISSIVE",
|
|
865
|
+
cmd: COMMANDS[operation],
|
|
866
|
+
roles: [...rule.pgRoles ?? ["public"]],
|
|
867
|
+
qual: rule.using || null,
|
|
868
|
+
with_check: rule.withCheck || null
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
function sameRoles(a, b) {
|
|
872
|
+
const left = [...a].sort();
|
|
873
|
+
const right = [...b].sort();
|
|
874
|
+
return left.length === right.length && left.every((role, i) => role === right[i]);
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Where a live policy differs from the one the code compiles to.
|
|
878
|
+
*
|
|
879
|
+
* The same comparison `rebase doctor` makes: the `TO` list, the command, the
|
|
880
|
+
* mode, and whether each clause is there at all. Never the clause text —
|
|
881
|
+
* Postgres rewrites an expression when it stores it, so the text of a policy
|
|
882
|
+
* that matches its declaration exactly still differs from the declaration's.
|
|
883
|
+
* A clause that is missing is not a rewrite, though: a `USING` that is gone
|
|
884
|
+
* changes who the policy admits.
|
|
885
|
+
*/
|
|
886
|
+
function policyDrift(declared, live) {
|
|
887
|
+
const drift = [];
|
|
888
|
+
if (!sameRoles(declared.roles, live.roles)) drift.push("roles");
|
|
889
|
+
if (declared.cmd !== live.cmd) drift.push("command");
|
|
890
|
+
if (declared.permissive !== live.permissive) drift.push("mode");
|
|
891
|
+
if (declared.qual != null !== (live.qual != null)) drift.push("USING");
|
|
892
|
+
if (declared.with_check != null !== (live.with_check != null)) drift.push("WITH CHECK");
|
|
893
|
+
return drift;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* The policies of one table: every policy the database has, as the database
|
|
897
|
+
* has it, and every policy the code declares for the table that it does not.
|
|
898
|
+
*
|
|
899
|
+
* A policy in both keeps the database's fields — this list is what Postgres
|
|
900
|
+
* enforces — and carries the declaration beside them, with where the two
|
|
901
|
+
* differ.
|
|
902
|
+
*
|
|
903
|
+
* @param live The table's `pg_policies` rows.
|
|
904
|
+
* @param collection The collection the table belongs to, if any.
|
|
905
|
+
* @param collections Every collection, to resolve the ones a rule refers to.
|
|
906
|
+
*/
|
|
907
|
+
function listPolicies(live, collection, tableName, collections) {
|
|
908
|
+
const listed = /* @__PURE__ */ new Map();
|
|
909
|
+
for (const policy of live) listed.set(policy.policyname, {
|
|
910
|
+
...policy,
|
|
911
|
+
status: "live"
|
|
912
|
+
});
|
|
913
|
+
if (!collection || !isPostgresCollectionConfig(collection)) return [...listed.values()];
|
|
914
|
+
const resolveCollection = (slug) => collections.find((c) => c.slug === slug || getTableName(c) === slug);
|
|
915
|
+
const injectedNames = getPolicyNamesForRules(getInjectedSecurityRules(collection), tableName);
|
|
916
|
+
const livePolicies = new Map(live.map((policy) => [policy.policyname, policy]));
|
|
917
|
+
for (const rule of getEffectiveSecurityRules(collection)) {
|
|
918
|
+
const operations = getPolicyOperations(rule);
|
|
919
|
+
let compiled;
|
|
920
|
+
try {
|
|
921
|
+
compiled = compileRulePolicies(collection, rule, { resolveCollection });
|
|
922
|
+
} catch {
|
|
923
|
+
compiled = void 0;
|
|
924
|
+
}
|
|
925
|
+
getPolicyNamesForRule(rule, tableName).forEach((name, index) => {
|
|
926
|
+
const declared = compiled ? asPostgresPolicy(compiled[index], tableName) : void 0;
|
|
927
|
+
const generated = injectedNames.has(name);
|
|
928
|
+
const existing = livePolicies.get(name);
|
|
929
|
+
if (existing) listed.set(name, {
|
|
930
|
+
...existing,
|
|
931
|
+
status: "both",
|
|
932
|
+
declared,
|
|
933
|
+
drift: declared ? policyDrift(declared, existing) : void 0,
|
|
934
|
+
generated
|
|
935
|
+
});
|
|
936
|
+
else listed.set(name, {
|
|
937
|
+
...declared ?? uncompiledPolicy(rule, operations[index] ?? "all", name, tableName),
|
|
938
|
+
status: "code_only",
|
|
939
|
+
generated
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
return [...listed.values()];
|
|
944
|
+
}
|
|
945
|
+
//#endregion
|
|
837
946
|
//#region src/components/RLSEditor/RLSEditor.tsx
|
|
838
947
|
/**
|
|
839
948
|
* Whether `collection` is the one `db push` writes `table` for: the table
|
|
@@ -1158,10 +1267,9 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1158
1267
|
const jRows = extractRows(await databaseAdmin.executeSql(JUNCTION_TABLES_SQL));
|
|
1159
1268
|
setJunctionTableNames(new Set(jRows.map((r) => r.table_name).filter(Boolean)));
|
|
1160
1269
|
} catch {}
|
|
1161
|
-
if (sortedTables.length > 0
|
|
1162
|
-
const firstUserTable = sortedTables.find((t) => !REBASE_INTERNAL_SCHEMAS.includes(t.schemaName));
|
|
1163
|
-
|
|
1164
|
-
else setSelectedTable(`${sortedTables[0].schemaName}.${sortedTables[0].tableName}`);
|
|
1270
|
+
if (sortedTables.length > 0) {
|
|
1271
|
+
const firstUserTable = sortedTables.find((t) => !REBASE_INTERNAL_SCHEMAS.includes(t.schemaName)) ?? sortedTables[0];
|
|
1272
|
+
setSelectedTable((previous) => previous ?? `${firstUserTable.schemaName}.${firstUserTable.tableName}`);
|
|
1165
1273
|
}
|
|
1166
1274
|
} catch (e) {
|
|
1167
1275
|
console.error("RLS fetch error:", e);
|
|
@@ -1169,7 +1277,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1169
1277
|
} finally {
|
|
1170
1278
|
setIsLoading(false);
|
|
1171
1279
|
}
|
|
1172
|
-
}, [databaseAdmin
|
|
1280
|
+
}, [databaseAdmin]);
|
|
1173
1281
|
useEffect(() => {
|
|
1174
1282
|
setEditingPolicy(null);
|
|
1175
1283
|
}, [selectedTable]);
|
|
@@ -1213,41 +1321,55 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1213
1321
|
activeCollection,
|
|
1214
1322
|
junctionTableNames
|
|
1215
1323
|
]);
|
|
1324
|
+
/**
|
|
1325
|
+
* The junction a relation derives this table as, when it is one. Its
|
|
1326
|
+
* policies are Rebase's — derived from the endpoints' relations — and the
|
|
1327
|
+
* server re-creates them on every start, like a collection's. `null` when
|
|
1328
|
+
* the registry's collections carry no resolvable relations (serialized
|
|
1329
|
+
* configs without relation closures).
|
|
1330
|
+
*/
|
|
1331
|
+
const activeJunctionSpec = useMemo(() => {
|
|
1332
|
+
if (!activeTableData || activeCollection) return null;
|
|
1333
|
+
try {
|
|
1334
|
+
return resolveJunctionSpecs(collectionRegistry.collections ?? []).get(activeTableData.tableName) ?? null;
|
|
1335
|
+
} catch {
|
|
1336
|
+
return null;
|
|
1337
|
+
}
|
|
1338
|
+
}, [
|
|
1339
|
+
activeTableData,
|
|
1340
|
+
activeCollection,
|
|
1341
|
+
collectionRegistry.collections
|
|
1342
|
+
]);
|
|
1343
|
+
/**
|
|
1344
|
+
* Whether the server re-asserts this table's RLS on every start: it switches
|
|
1345
|
+
* RLS back on and re-creates every policy the project declares for it.
|
|
1346
|
+
*/
|
|
1347
|
+
const activeTableManaged = Boolean(activeCollection || activeJunctionSpec);
|
|
1348
|
+
/**
|
|
1349
|
+
* What Postgres enforces on the selected table, and what the code declares
|
|
1350
|
+
* that it does not have yet.
|
|
1351
|
+
*
|
|
1352
|
+
* A policy in both is listed with the *database's* fields, and the
|
|
1353
|
+
* declaration rides beside it with where the two differ (`listPolicies`).
|
|
1354
|
+
* The declaration's fields in their place would make a policy changed in
|
|
1355
|
+
* SQL look exactly like its declaration, and would show every rule written
|
|
1356
|
+
* as a structured condition — all of Rebase's own policies — with no USING.
|
|
1357
|
+
*/
|
|
1216
1358
|
const mergedPolicies = useMemo(() => {
|
|
1217
1359
|
if (!activeTableData) return [];
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
status
|
|
1223
|
-
|
|
1224
|
-
});
|
|
1225
|
-
if (activeCollection && isPostgresCollectionConfig(activeCollection)) getEffectiveSecurityRules(activeCollection).forEach((rule) => {
|
|
1226
|
-
const ops = getPolicyOperations(rule);
|
|
1227
|
-
getPolicyNamesForRule(rule, activeTableData.tableName).forEach((policyName, opIdx) => {
|
|
1228
|
-
policiesMap[policyName] = {
|
|
1229
|
-
policyname: policyName,
|
|
1230
|
-
tablename: activeTableData.tableName,
|
|
1231
|
-
permissive: (rule.mode || "permissive").toUpperCase(),
|
|
1232
|
-
cmd: (ops[opIdx] ?? rule.operation ?? "ALL").toUpperCase(),
|
|
1233
|
-
roles: [...rule.pgRoles ?? ["public"]],
|
|
1234
|
-
qual: rule.using || null,
|
|
1235
|
-
with_check: rule.withCheck || null,
|
|
1236
|
-
status: policiesMap[policyName] ? "both" : "code_only"
|
|
1237
|
-
};
|
|
1238
|
-
});
|
|
1239
|
-
});
|
|
1240
|
-
if (!activeCollection) try {
|
|
1241
|
-
const spec = resolveJunctionSpecs(collectionRegistry.collections ?? []).get(activeTableData.tableName);
|
|
1242
|
-
if (spec) {
|
|
1243
|
-
const generatedNames = getPolicyNamesForRules(getJunctionSecurityRules(spec), spec.table);
|
|
1244
|
-
for (const p of Object.values(policiesMap)) if (generatedNames.has(p.policyname)) p.status = "both";
|
|
1360
|
+
const listed = listPolicies(activeTableData.policies || [], activeCollection, activeTableData.tableName, collectionRegistry.collections ?? []);
|
|
1361
|
+
if (activeJunctionSpec) {
|
|
1362
|
+
const generatedNames = getPolicyNamesForRules(getJunctionSecurityRules(activeJunctionSpec), activeJunctionSpec.table);
|
|
1363
|
+
for (const p of listed) if (generatedNames.has(p.policyname)) {
|
|
1364
|
+
p.status = "both";
|
|
1365
|
+
p.generated = true;
|
|
1245
1366
|
}
|
|
1246
|
-
}
|
|
1247
|
-
return
|
|
1367
|
+
}
|
|
1368
|
+
return listed.sort((a, b) => a.policyname.localeCompare(b.policyname));
|
|
1248
1369
|
}, [
|
|
1249
1370
|
activeTableData,
|
|
1250
1371
|
activeCollection,
|
|
1372
|
+
activeJunctionSpec,
|
|
1251
1373
|
collectionRegistry.collections
|
|
1252
1374
|
]);
|
|
1253
1375
|
const rlsStats = useMemo(() => {
|
|
@@ -1274,6 +1396,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1274
1396
|
className: "flex h-full w-full bg-surface-card overflow-hidden text-text-primary dark:text-text-primary-dark",
|
|
1275
1397
|
children: [
|
|
1276
1398
|
/* @__PURE__ */ jsx(ResizablePanels, {
|
|
1399
|
+
dividerAriaLabel: t("resize_sidebar"),
|
|
1277
1400
|
orientation: "horizontal",
|
|
1278
1401
|
panelSizePercent: sidebarSize,
|
|
1279
1402
|
onPanelSizeChange: setSidebarSize,
|
|
@@ -1303,9 +1426,10 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1303
1426
|
className: "font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
|
|
1304
1427
|
children: t("studio_schema_tables")
|
|
1305
1428
|
}), /* @__PURE__ */ jsx(IconButton, {
|
|
1429
|
+
"aria-label": t("refresh"),
|
|
1306
1430
|
size: "small",
|
|
1307
1431
|
onClick: fetchRLSData,
|
|
1308
|
-
title: "
|
|
1432
|
+
title: t("refresh"),
|
|
1309
1433
|
children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
|
|
1310
1434
|
})]
|
|
1311
1435
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -1548,7 +1672,8 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1548
1672
|
onClick: () => setConfirmToggleRls({
|
|
1549
1673
|
table: activeTableData.tableName,
|
|
1550
1674
|
schema: activeTableData.schemaName,
|
|
1551
|
-
enabled: activeTableData.rlsEnabled
|
|
1675
|
+
enabled: activeTableData.rlsEnabled,
|
|
1676
|
+
managed: activeTableManaged
|
|
1552
1677
|
}),
|
|
1553
1678
|
children: activeTableData.rlsEnabled ? t("studio_rls_disable_rls") : t("studio_rls_enable_rls")
|
|
1554
1679
|
}),
|
|
@@ -1619,6 +1744,13 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1619
1744
|
return;
|
|
1620
1745
|
}
|
|
1621
1746
|
newRules = edit.rules;
|
|
1747
|
+
if (JSON.stringify(newRules) === JSON.stringify(existingRules)) {
|
|
1748
|
+
snackbarController.open({
|
|
1749
|
+
type: "info",
|
|
1750
|
+
message: t("studio_rls_nothing_changed")
|
|
1751
|
+
});
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1622
1754
|
}
|
|
1623
1755
|
try {
|
|
1624
1756
|
await saveSecurityRules(activeCollection.slug, newRules);
|
|
@@ -1631,31 +1763,40 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1631
1763
|
} catch (e) {
|
|
1632
1764
|
reportSaveFailure(e);
|
|
1633
1765
|
}
|
|
1634
|
-
} else
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1766
|
+
} else {
|
|
1767
|
+
if (editingPolicy !== "new" && editingPolicy.generated) {
|
|
1768
|
+
snackbarController.open({
|
|
1769
|
+
type: "error",
|
|
1770
|
+
message: t(POLICY_EDIT_REFUSALS.generated, { policy: editingPolicy.policyname })
|
|
1771
|
+
});
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
try {
|
|
1775
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
|
|
1776
|
+
const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
|
|
1777
|
+
const cmd = newPolicy.cmd || "ALL";
|
|
1778
|
+
const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
|
|
1779
|
+
const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
|
|
1780
|
+
let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
|
|
1781
|
+
sql += ` AS ${permissive}`;
|
|
1782
|
+
sql += ` FOR ${cmd}`;
|
|
1783
|
+
sql += ` TO ${roles}`;
|
|
1784
|
+
if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
|
|
1785
|
+
if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
|
|
1786
|
+
if (editingPolicy !== "new") sql = `DROP POLICY IF EXISTS ${sanitizeSqlIdentifier(editingPolicy.policyname)} ON ${qualifiedTable}; ${sql}`;
|
|
1787
|
+
await databaseAdmin.executeSql(sql);
|
|
1788
|
+
snackbarController.open({
|
|
1789
|
+
type: "success",
|
|
1790
|
+
message: `Policy "${newPolicy.policyname}" applied to the database`
|
|
1791
|
+
});
|
|
1792
|
+
setEditingPolicy(null);
|
|
1793
|
+
fetchRLSData();
|
|
1794
|
+
} catch (e) {
|
|
1795
|
+
snackbarController.open({
|
|
1796
|
+
type: "error",
|
|
1797
|
+
message: e instanceof Error ? e.message : String(e)
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1659
1800
|
}
|
|
1660
1801
|
},
|
|
1661
1802
|
onCancel: () => setEditingPolicy(null)
|
|
@@ -1666,6 +1807,19 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1666
1807
|
children: /* @__PURE__ */ jsxs("div", {
|
|
1667
1808
|
className: "max-w-4xl mx-auto flex flex-col gap-6",
|
|
1668
1809
|
children: [
|
|
1810
|
+
activeTableData && activeTableManaged && !(activeCollection && hasCodebase) && /* @__PURE__ */ jsx(Alert, {
|
|
1811
|
+
color: "info",
|
|
1812
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1813
|
+
className: "flex items-start gap-2",
|
|
1814
|
+
children: [/* @__PURE__ */ jsx(RefreshCwIcon, {
|
|
1815
|
+
size: 16,
|
|
1816
|
+
className: "shrink-0 mt-0.5"
|
|
1817
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1818
|
+
variant: "caption",
|
|
1819
|
+
children: t("studio_rls_reapplied_on_start", { table: activeTableData.tableName })
|
|
1820
|
+
})]
|
|
1821
|
+
})
|
|
1822
|
+
}),
|
|
1669
1823
|
activeTableData && activeTableCategory === "internal" && /* @__PURE__ */ jsx(Alert, {
|
|
1670
1824
|
color: "info",
|
|
1671
1825
|
children: /* @__PURE__ */ jsxs("div", {
|
|
@@ -1775,6 +1929,13 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1775
1929
|
children: t("studio_rls_unapplied")
|
|
1776
1930
|
})
|
|
1777
1931
|
}),
|
|
1932
|
+
policy.drift && policy.drift.length > 0 && /* @__PURE__ */ jsx(Tooltip, {
|
|
1933
|
+
title: t("studio_rls_drift_tooltip", { fields: policy.drift.join(", ") }),
|
|
1934
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1935
|
+
className: "px-1.5 py-0.5 rounded text-[10px] bg-orange-500/10 text-orange-600 border border-orange-500/20 shrink-0",
|
|
1936
|
+
children: `${t("studio_rls_drift")}: ${policy.drift.join(", ")}`
|
|
1937
|
+
})
|
|
1938
|
+
}),
|
|
1778
1939
|
policy.status === "live" && hasCodebase && /* @__PURE__ */ jsx(Tooltip, {
|
|
1779
1940
|
title: "This policy is live in the database but missing from your codebase schema.",
|
|
1780
1941
|
children: /* @__PURE__ */ jsx("div", {
|
|
@@ -1821,6 +1982,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1821
1982
|
title: t("studio_rls_delete"),
|
|
1822
1983
|
asChild: true,
|
|
1823
1984
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
1985
|
+
"aria-label": t("studio_rls_delete"),
|
|
1824
1986
|
size: "small",
|
|
1825
1987
|
onClick: () => setConfirmDropPolicy({
|
|
1826
1988
|
policyName: policy.policyname,
|
|
@@ -1891,7 +2053,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1891
2053
|
onAccept: applyToggleRls,
|
|
1892
2054
|
onCancel: () => setConfirmToggleRls(null),
|
|
1893
2055
|
title: confirmToggleRls?.enabled ? t("studio_rls_disable_confirm_title") : t("studio_rls_enable_confirm_title"),
|
|
1894
|
-
body: confirmToggleRls?.enabled ? t("studio_rls_disable_confirm_body", { table: confirmToggleRls
|
|
2056
|
+
body: confirmToggleRls?.enabled ? [t("studio_rls_disable_confirm_body", { table: confirmToggleRls.table }), confirmToggleRls.managed ? t("studio_rls_disable_reapplied", { table: confirmToggleRls.table }) : ""].filter(Boolean).join(" ") : t("studio_rls_enable_confirm_body", { table: confirmToggleRls?.table ?? "" })
|
|
1895
2057
|
}),
|
|
1896
2058
|
/* @__PURE__ */ jsx(ConfirmationDialog, {
|
|
1897
2059
|
open: confirmDropPolicy !== null,
|
|
@@ -1910,4 +2072,4 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1910
2072
|
//#endregion
|
|
1911
2073
|
export { RLSEditor };
|
|
1912
2074
|
|
|
1913
|
-
//# sourceMappingURL=RLSEditor-
|
|
2075
|
+
//# sourceMappingURL=RLSEditor-jFD_d06f.js.map
|