create-kontrolia-auth 2.2.2 → 2.3.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/README.md CHANGED
@@ -16,6 +16,12 @@ Eso hace, en orden:
16
16
  2. **Descarga** el repositorio en `./mi-app` e instala dependencias.
17
17
  3. **Base de datos** — dos caminos independientes:
18
18
  - _Ya tengo Supabase_ (Cloud o self-hosted): pegas URL + keys, sin Docker.
19
+ Si es un proyecto Supabase Cloud, además ofrece **configurar
20
+ automáticamente** los dos ajustes que solo viven en su Dashboard —
21
+ exponer el schema `kontrolia_auth` en la API y activar el Custom Access
22
+ Token Hook — vía la Management API de Supabase (pide un Personal Access
23
+ Token de tu cuenta, distinto a las keys del proyecto; no se guarda). Si
24
+ lo saltas, muestra las instrucciones manuales exactas.
19
25
  - _Crear uno nuevo self-hosted_: genera `docker/.env`, **levanta los
20
26
  contenedores por ti** (`docker compose up -d`) y aplica las migraciones
21
27
  en cuanto Postgres está listo.
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/steps/database.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAmE/E"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/steps/database.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAmDD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAiF/E"}
@@ -3,6 +3,49 @@ import { generateJwtSecret } from "../utils/secrets.js";
3
3
  import { generateSupabaseKeys } from "../utils/supabase-keys.js";
4
4
  import { writeEnvFile } from "../utils/files.js";
5
5
  import { textOrExit } from "../utils/prompts.js";
6
+ import { addExposedSchema, enableCustomAccessTokenHook, extractProjectRef } from "../utils/supabase-management-api.js";
7
+ /**
8
+ * Offers to configure the two Supabase Cloud dashboard-only settings
9
+ * (exposed schemas + Custom Access Token Hook) via the Management API
10
+ * instead of leaving them as manual steps — mirrors the Vercel deploy
11
+ * automation: a separate, more powerful "Personal Access Token" (account
12
+ * level, not the project's anon/service keys), captured with masked input
13
+ * and never persisted. Only offered for Supabase Cloud URLs (self-hosted
14
+ * projects and custom domains have no Management API to call). Returns
15
+ * which parts actually succeeded so the caller can fall back to a manual
16
+ * note for whichever one didn't.
17
+ */
18
+ async function tryAutoConfigureSupabaseCloud(supabaseUrl) {
19
+ const projectRef = extractProjectRef(supabaseUrl);
20
+ if (!projectRef)
21
+ return { schemaDone: false, hookDone: false };
22
+ const auto = await p.confirm({
23
+ message: "¿Quieres que configure automáticamente el schema expuesto y el Custom Access Token Hook en tu proyecto Supabase? " +
24
+ "Necesito un Personal Access Token de tu cuenta Supabase (distinto a las keys del proyecto que ya diste).",
25
+ initialValue: true,
26
+ });
27
+ if (p.isCancel(auto) || !auto)
28
+ return { schemaDone: false, hookDone: false };
29
+ p.note("1. Entra a supabase.com/dashboard/account/tokens\n" +
30
+ '2. Dale click a "Generate new token"\n' +
31
+ "3. Ponle un nombre, confirma y cópialo (solo se ve una vez)\n" +
32
+ "4. Pégalo aquí abajo", "Cómo conseguir el Personal Access Token de Supabase");
33
+ const managementToken = await p.password({
34
+ message: "Personal Access Token de Supabase (no se guarda, solo se usa ahora)",
35
+ validate: (value) => (value.trim() ? undefined : "Requerido"),
36
+ });
37
+ if (p.isCancel(managementToken))
38
+ return { schemaDone: false, hookDone: false };
39
+ const schemaSpinner = p.spinner();
40
+ schemaSpinner.start("Exponiendo el schema kontrolia_auth en la API de datos");
41
+ const schemaResult = await addExposedSchema(managementToken, projectRef, "kontrolia_auth");
42
+ schemaSpinner.stop(schemaResult.ok ? "Schema kontrolia_auth expuesto" : `Falló: ${schemaResult.error}`);
43
+ const hookSpinner = p.spinner();
44
+ hookSpinner.start("Activando el Custom Access Token Hook");
45
+ const hookResult = await enableCustomAccessTokenHook(managementToken, projectRef);
46
+ hookSpinner.stop(hookResult.ok ? "Hook activado" : `Falló: ${hookResult.error}`);
47
+ return { schemaDone: schemaResult.ok, hookDone: hookResult.ok };
48
+ }
6
49
  /**
7
50
  * Question A from the architecture plan: where does Postgres + GoTrue come
8
51
  * from? Two fully independent paths — neither assumes Docker.
@@ -32,7 +75,19 @@ export async function askDatabaseStep(repoRoot) {
32
75
  const anonKey = await textOrExit("Anon/public key");
33
76
  const serviceRoleKey = await textOrExit("Service role key (server-only, nunca la expongas al navegador)");
34
77
  const databaseUrl = await textOrExit("Connection string de Postgres", "postgres://postgres:...@db.tu-proyecto.supabase.co:5432/postgres");
35
- p.note("Si tu proyecto es Supabase Cloud, activa el Custom Access Token Hook manualmente en Dashboard → Authentication → Hooks → selecciona kontrolia_auth.custom_access_token_hook. No se puede automatizar por API.", "Paso manual pendiente");
78
+ const { schemaDone, hookDone } = await tryAutoConfigureSupabaseCloud(supabaseUrl);
79
+ if (!schemaDone || !hookDone) {
80
+ const pending = [];
81
+ if (!hookDone)
82
+ pending.push("Authentication → Hooks → activa kontrolia_auth.custom_access_token_hook.");
83
+ if (!schemaDone) {
84
+ pending.push("Project Settings → Data API → en \"Exposed schemas\" agrega kontrolia_auth (queda como: public, graphql_public, kontrolia_auth). " +
85
+ "Sin esto, crear una organización u otras operaciones fallan con \"Invalid schema: kontrolia_auth\".");
86
+ }
87
+ p.note("Si tu proyecto es Supabase Cloud (o self-hosted fuera de nuestro docker-compose), estos ajustes solo se pueden hacer " +
88
+ "desde el Dashboard — la API de Supabase no los expone del todo:\n\n" +
89
+ pending.map((line, i) => `${i + 1}. ${line}`).join("\n"), pending.length > 1 ? "Pasos manuales pendientes" : "Paso manual pendiente");
90
+ }
36
91
  return { mode, databaseUrl, supabaseUrl, anonKey, serviceRoleKey };
37
92
  }
38
93
  const jwtSecret = generateJwtSecret();
@@ -0,0 +1,28 @@
1
+ export interface ManagementApiResult {
2
+ ok: boolean;
3
+ /** Only present when ok is false. */
4
+ error?: string;
5
+ }
6
+ /**
7
+ * Extracts the project ref (subdomain) from a Supabase Cloud URL, e.g.
8
+ * "https://abcxyz.supabase.co" -> "abcxyz". Returns null for anything else
9
+ * (self-hosted, a custom domain) — those aren't reachable through Supabase's
10
+ * account-level Management API, which only knows about Cloud projects.
11
+ */
12
+ export declare function extractProjectRef(supabaseUrl: string): string | null;
13
+ /**
14
+ * Adds a schema to the project's exposed-schemas list (PostgREST's
15
+ * `db_schema`) if it isn't there already — idempotent, and preserves
16
+ * whatever schemas were already exposed instead of overwriting them, since
17
+ * `db_schema` is a single string representing the *entire* list, not a
18
+ * per-schema toggle.
19
+ */
20
+ export declare function addExposedSchema(token: string, projectRef: string, schema: string): Promise<ManagementApiResult>;
21
+ /**
22
+ * Enables the Custom Access Token Hook against
23
+ * kontrolia_auth.custom_access_token_hook — the function that injects
24
+ * organization_id/roles/permissions into the JWT. Cloud projects' database
25
+ * is always named "postgres", so the pg-functions:// URI is fixed.
26
+ */
27
+ export declare function enableCustomAccessTokenHook(token: string, projectRef: string): Promise<ManagementApiResult>;
28
+ //# sourceMappingURL=supabase-management-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supabase-management-api.d.ts","sourceRoot":"","sources":["../../src/utils/supabase-management-api.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGpE;AASD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+BtH;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgBjH"}
@@ -0,0 +1,84 @@
1
+ const CUSTOM_ACCESS_TOKEN_HOOK_URI = "pg-functions://postgres/kontrolia_auth/custom_access_token_hook";
2
+ /**
3
+ * Extracts the project ref (subdomain) from a Supabase Cloud URL, e.g.
4
+ * "https://abcxyz.supabase.co" -> "abcxyz". Returns null for anything else
5
+ * (self-hosted, a custom domain) — those aren't reachable through Supabase's
6
+ * account-level Management API, which only knows about Cloud projects.
7
+ */
8
+ export function extractProjectRef(supabaseUrl) {
9
+ const match = supabaseUrl.match(/^https:\/\/([a-z0-9-]+)\.supabase\.co\/?$/);
10
+ return match ? match[1] : null;
11
+ }
12
+ async function managementApiFetch(path, token, init) {
13
+ return fetch(`https://api.supabase.com/v1${path}`, {
14
+ ...init,
15
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...init?.headers },
16
+ });
17
+ }
18
+ /**
19
+ * Adds a schema to the project's exposed-schemas list (PostgREST's
20
+ * `db_schema`) if it isn't there already — idempotent, and preserves
21
+ * whatever schemas were already exposed instead of overwriting them, since
22
+ * `db_schema` is a single string representing the *entire* list, not a
23
+ * per-schema toggle.
24
+ */
25
+ export async function addExposedSchema(token, projectRef, schema) {
26
+ let getRes;
27
+ try {
28
+ getRes = await managementApiFetch(`/projects/${projectRef}/postgrest`, token);
29
+ }
30
+ catch (error) {
31
+ return { ok: false, error: error.message };
32
+ }
33
+ if (!getRes.ok) {
34
+ const body = (await getRes.json().catch(() => null));
35
+ return { ok: false, error: body?.message ?? `HTTP ${getRes.status} leyendo la config actual` };
36
+ }
37
+ const current = (await getRes.json());
38
+ const schemas = (current.db_schema ?? "public")
39
+ .split(",")
40
+ .map((s) => s.trim())
41
+ .filter(Boolean);
42
+ if (schemas.includes(schema))
43
+ return { ok: true };
44
+ schemas.push(schema);
45
+ let patchRes;
46
+ try {
47
+ patchRes = await managementApiFetch(`/projects/${projectRef}/postgrest`, token, {
48
+ method: "PATCH",
49
+ body: JSON.stringify({ db_schema: schemas.join(",") }),
50
+ });
51
+ }
52
+ catch (error) {
53
+ return { ok: false, error: error.message };
54
+ }
55
+ if (patchRes.ok)
56
+ return { ok: true };
57
+ const body = (await patchRes.json().catch(() => null));
58
+ return { ok: false, error: body?.message ?? `HTTP ${patchRes.status}` };
59
+ }
60
+ /**
61
+ * Enables the Custom Access Token Hook against
62
+ * kontrolia_auth.custom_access_token_hook — the function that injects
63
+ * organization_id/roles/permissions into the JWT. Cloud projects' database
64
+ * is always named "postgres", so the pg-functions:// URI is fixed.
65
+ */
66
+ export async function enableCustomAccessTokenHook(token, projectRef) {
67
+ let res;
68
+ try {
69
+ res = await managementApiFetch(`/projects/${projectRef}/config/auth`, token, {
70
+ method: "PATCH",
71
+ body: JSON.stringify({
72
+ hook_custom_access_token_enabled: true,
73
+ hook_custom_access_token_uri: CUSTOM_ACCESS_TOKEN_HOOK_URI,
74
+ }),
75
+ });
76
+ }
77
+ catch (error) {
78
+ return { ok: false, error: error.message };
79
+ }
80
+ if (res.ok)
81
+ return { ok: true };
82
+ const body = (await res.json().catch(() => null));
83
+ return { ok: false, error: body?.message ?? `HTTP ${res.status}` };
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kontrolia-auth",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "license": "MIT",
5
5
  "description": "Turnkey installer for KontrolIA Auth. `npx create-kontrolia-auth mi-app` checks requirements, downloads the repo, installs deps, and runs a wizard: (1) connect to an existing Supabase project or create a new self-hosted one via Docker; (2) where to deploy auth-server/admin-panel (Docker, Vercel, Railway, Render, Coolify). Also exposes `update`, `migrate` and `doctor` subcommands.",
6
6
  "keywords": [