@supacloud/cli 0.8.0 → 0.9.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/dist/index.js +47 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14777,7 +14777,9 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
14777
14777
|
user_id: exports_external.string().optional().describe("[get_auth_user] User UUID"),
|
|
14778
14778
|
limit: exports_external.number().optional().describe("[list_auth_users] Max users (default: 20)"),
|
|
14779
14779
|
name: exports_external.string().optional().describe("[apply_migration] Migration name"),
|
|
14780
|
-
columns: exports_external.string().optional().describe("[create_table_rls] Column definitions")
|
|
14780
|
+
columns: exports_external.string().optional().describe("[create_table_rls] Column definitions"),
|
|
14781
|
+
policy_mode: exports_external.enum(["deny_all", "owner"]).optional().describe("[create_table_rls] RLS policy mode (default: deny_all)"),
|
|
14782
|
+
owner_column: exports_external.string().optional().describe("[create_table_rls owner] UUID owner column matched to auth.uid()")
|
|
14781
14783
|
}, async (args) => {
|
|
14782
14784
|
const { action } = args;
|
|
14783
14785
|
const ref = projectRef || args.ref;
|
|
@@ -15053,9 +15055,15 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
15053
15055
|
case "create_table_rls": {
|
|
15054
15056
|
if (!args.table || !args.columns)
|
|
15055
15057
|
throw new Error("'table' and 'columns' required");
|
|
15056
|
-
const
|
|
15058
|
+
const qualifiedTable = `${quoteIdentifier(schema, "schema")}.${quoteIdentifier(args.table, "table")}`;
|
|
15059
|
+
const columns = validateColumnDefinitions(args.columns);
|
|
15060
|
+
const policyMode = args.policy_mode || "deny_all";
|
|
15061
|
+
if (policyMode !== "deny_all" && policyMode !== "owner")
|
|
15062
|
+
throw new Error("Invalid RLS policy mode");
|
|
15063
|
+
const policySql = buildRlsPolicySql(qualifiedTable, policyMode, args.owner_column);
|
|
15064
|
+
const sql = `BEGIN; CREATE TABLE IF NOT EXISTS ${qualifiedTable} (${columns}); ALTER TABLE ${qualifiedTable} ENABLE ROW LEVEL SECURITY; ${policySql} COMMIT;`;
|
|
15057
15065
|
const r = await execSql(sql);
|
|
15058
|
-
text = r.ok ? `✅ Table '${schema}.${args.table}' created with RLS` : `❌ Failed (${r.status}): ${JSON.stringify(r.data)}`;
|
|
15066
|
+
text = r.ok ? `✅ Table '${schema}.${args.table}' created with RLS (${policyMode === "owner" ? "auth.uid() owner policy" : "deny-all by default"})` : `❌ Failed (${r.status}): ${JSON.stringify(r.data)}`;
|
|
15059
15067
|
break;
|
|
15060
15068
|
}
|
|
15061
15069
|
default:
|
|
@@ -15064,6 +15072,42 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
|
|
|
15064
15072
|
return { content: [{ type: "text", text }] };
|
|
15065
15073
|
});
|
|
15066
15074
|
}
|
|
15075
|
+
function quoteIdentifier(value, label) {
|
|
15076
|
+
if (typeof value !== "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,62}$/.test(value)) {
|
|
15077
|
+
throw new Error(`Invalid ${label} identifier`);
|
|
15078
|
+
}
|
|
15079
|
+
return `"${value}"`;
|
|
15080
|
+
}
|
|
15081
|
+
function validateColumnDefinitions(value) {
|
|
15082
|
+
if (typeof value !== "string")
|
|
15083
|
+
throw new Error("Invalid column definitions");
|
|
15084
|
+
const columns = value.trim();
|
|
15085
|
+
if (!columns || columns.length > 16384)
|
|
15086
|
+
throw new Error("Invalid column definitions");
|
|
15087
|
+
if (/[;\0]/.test(columns) || /--|\/\*|\*\//.test(columns) || /\b(?:ALTER|CREATE|DROP|GRANT|REVOKE|TRUNCATE|COPY|CALL|DO)\b/i.test(columns)) {
|
|
15088
|
+
throw new Error("Unsafe column definitions");
|
|
15089
|
+
}
|
|
15090
|
+
return columns;
|
|
15091
|
+
}
|
|
15092
|
+
function buildRlsPolicySql(qualifiedTable, policyMode, ownerColumnValue) {
|
|
15093
|
+
const policyNames = [
|
|
15094
|
+
"Enable ALL for authenticated",
|
|
15095
|
+
"SupaCloud owner select",
|
|
15096
|
+
"SupaCloud owner insert",
|
|
15097
|
+
"SupaCloud owner update",
|
|
15098
|
+
"SupaCloud owner delete"
|
|
15099
|
+
];
|
|
15100
|
+
const dropPolicies = policyNames.map((name) => `DROP POLICY IF EXISTS "${name}" ON ${qualifiedTable};`).join(" ");
|
|
15101
|
+
if (policyMode === "deny_all")
|
|
15102
|
+
return dropPolicies;
|
|
15103
|
+
const ownerColumn = quoteIdentifier(ownerColumnValue, "owner column");
|
|
15104
|
+
const predicate = `auth.uid() IS NOT NULL AND auth.uid() = ${ownerColumn}`;
|
|
15105
|
+
return `${dropPolicies}
|
|
15106
|
+
CREATE POLICY "SupaCloud owner select" ON ${qualifiedTable} FOR SELECT TO authenticated USING (${predicate});
|
|
15107
|
+
CREATE POLICY "SupaCloud owner insert" ON ${qualifiedTable} FOR INSERT TO authenticated WITH CHECK (${predicate});
|
|
15108
|
+
CREATE POLICY "SupaCloud owner update" ON ${qualifiedTable} FOR UPDATE TO authenticated USING (${predicate}) WITH CHECK (${predicate});
|
|
15109
|
+
CREATE POLICY "SupaCloud owner delete" ON ${qualifiedTable} FOR DELETE TO authenticated USING (${predicate});`;
|
|
15110
|
+
}
|
|
15067
15111
|
function sqlString(value) {
|
|
15068
15112
|
return `'${value.replace(/'/g, "''")}'`;
|
|
15069
15113
|
}
|