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":"
|
|
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"}
|
package/dist/steps/database.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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": [
|