@supacloud/cli 0.8.0 → 0.9.1

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.
Files changed (2) hide show
  1. package/dist/index.js +47 -3
  2. package/package.json +2 -2
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 sql = `BEGIN; CREATE TABLE IF NOT EXISTS "${schema}"."${args.table}" (${args.columns}); ALTER TABLE "${schema}"."${args.table}" ENABLE ROW LEVEL SECURITY; CREATE POLICY "Enable ALL for authenticated" ON "${schema}"."${args.table}" FOR ALL TO authenticated USING (true) WITH CHECK (true); COMMIT;`;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,6 +34,6 @@
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/bun": "^1.3.14",
37
- "typescript": "^6.0.3"
37
+ "typescript": "^7.0.2"
38
38
  }
39
39
  }