@polardbx/pxbaas 0.1.0-beta.1 → 0.1.0-beta.10
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 +224 -0
- package/dist/app/local-index.d.ts +70 -0
- package/dist/app/local-index.d.ts.map +1 -0
- package/dist/app/local-index.js +140 -0
- package/dist/app/local-index.js.map +1 -0
- package/dist/app/preflight.d.ts +24 -1
- package/dist/app/preflight.d.ts.map +1 -1
- package/dist/app/preflight.js +47 -11
- package/dist/app/preflight.js.map +1 -1
- package/dist/app/spec-lint.d.ts.map +1 -1
- package/dist/app/spec-lint.js +16 -0
- package/dist/app/spec-lint.js.map +1 -1
- package/dist/app/templates.d.ts +24 -0
- package/dist/app/templates.d.ts.map +1 -1
- package/dist/app/templates.js.map +1 -1
- package/dist/cli/help-guides.d.ts +17 -0
- package/dist/cli/help-guides.d.ts.map +1 -0
- package/dist/cli/help-guides.js +132 -0
- package/dist/cli/help-guides.js.map +1 -0
- package/dist/cli/root.d.ts.map +1 -1
- package/dist/cli/root.js +6 -0
- package/dist/cli/root.js.map +1 -1
- package/dist/commands/app/index.d.ts.map +1 -1
- package/dist/commands/app/index.js +687 -47
- package/dist/commands/app/index.js.map +1 -1
- package/dist/commands/completion/index.d.ts.map +1 -1
- package/dist/commands/completion/index.js +5 -1
- package/dist/commands/completion/index.js.map +1 -1
- package/dist/commands/data/index.d.ts +52 -0
- package/dist/commands/data/index.d.ts.map +1 -1
- package/dist/commands/data/index.js +201 -39
- package/dist/commands/data/index.js.map +1 -1
- package/dist/commands/db/index.d.ts +15 -0
- package/dist/commands/db/index.d.ts.map +1 -1
- package/dist/commands/db/index.js +200 -55
- package/dist/commands/db/index.js.map +1 -1
- package/dist/commands/functions/new.d.ts +4 -0
- package/dist/commands/functions/new.d.ts.map +1 -1
- package/dist/commands/functions/new.js +24 -9
- package/dist/commands/functions/new.js.map +1 -1
- package/dist/commands/gen/index.d.ts.map +1 -1
- package/dist/commands/gen/index.js +13 -10
- package/dist/commands/gen/index.js.map +1 -1
- package/dist/commands/guide/index.d.ts +8 -0
- package/dist/commands/guide/index.d.ts.map +1 -0
- package/dist/commands/guide/index.js +20 -0
- package/dist/commands/guide/index.js.map +1 -0
- package/dist/commands/instance/index.d.ts.map +1 -1
- package/dist/commands/instance/index.js +120 -0
- package/dist/commands/instance/index.js.map +1 -1
- package/dist/commands/login/index.d.ts.map +1 -1
- package/dist/commands/login/index.js +23 -63
- package/dist/commands/login/index.js.map +1 -1
- package/dist/commands/profile/import-env.d.ts +7 -2
- package/dist/commands/profile/import-env.d.ts.map +1 -1
- package/dist/commands/profile/import-env.js +141 -16
- package/dist/commands/profile/import-env.js.map +1 -1
- package/dist/commands/profile/index.d.ts.map +1 -1
- package/dist/commands/profile/index.js +16 -1
- package/dist/commands/profile/index.js.map +1 -1
- package/dist/commands/skill/index.d.ts.map +1 -1
- package/dist/commands/skill/index.js +89 -14
- package/dist/commands/skill/index.js.map +1 -1
- package/dist/commands/storage/index.d.ts.map +1 -1
- package/dist/commands/storage/index.js +209 -7
- package/dist/commands/storage/index.js.map +1 -1
- package/dist/commands/tenant/index.d.ts +19 -0
- package/dist/commands/tenant/index.d.ts.map +1 -0
- package/dist/commands/tenant/index.js +667 -0
- package/dist/commands/tenant/index.js.map +1 -0
- package/dist/commands/whoami/index.js +19 -0
- package/dist/commands/whoami/index.js.map +1 -1
- package/dist/credentials/credential-store.d.ts +30 -1
- package/dist/credentials/credential-store.d.ts.map +1 -1
- package/dist/credentials/credential-store.js +114 -12
- package/dist/credentials/credential-store.js.map +1 -1
- package/dist/credentials/env-file.d.ts +48 -7
- package/dist/credentials/env-file.d.ts.map +1 -1
- package/dist/credentials/env-file.js +91 -16
- package/dist/credentials/env-file.js.map +1 -1
- package/dist/credentials/index.d.ts +2 -0
- package/dist/credentials/index.d.ts.map +1 -1
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/index.js.map +1 -1
- package/dist/credentials/jwt-introspect.d.ts +7 -0
- package/dist/credentials/jwt-introspect.d.ts.map +1 -1
- package/dist/credentials/jwt-introspect.js.map +1 -1
- package/dist/credentials/masker.d.ts +3 -0
- package/dist/credentials/masker.d.ts.map +1 -1
- package/dist/credentials/masker.js +3 -0
- package/dist/credentials/masker.js.map +1 -1
- package/dist/credentials/profile.d.ts +12 -0
- package/dist/credentials/profile.d.ts.map +1 -1
- package/dist/credentials/profile.js +23 -0
- package/dist/credentials/profile.js.map +1 -1
- package/dist/credentials/tty-guard.d.ts +10 -0
- package/dist/credentials/tty-guard.d.ts.map +1 -1
- package/dist/credentials/tty-guard.js +32 -0
- package/dist/credentials/tty-guard.js.map +1 -1
- package/dist/db/apply-schema.d.ts +98 -11
- package/dist/db/apply-schema.d.ts.map +1 -1
- package/dist/db/apply-schema.js +247 -80
- package/dist/db/apply-schema.js.map +1 -1
- package/dist/db/introspect.d.ts +54 -0
- package/dist/db/introspect.d.ts.map +1 -0
- package/dist/db/introspect.js +140 -0
- package/dist/db/introspect.js.map +1 -0
- package/dist/db/sql-file.d.ts +55 -0
- package/dist/db/sql-file.d.ts.map +1 -1
- package/dist/db/sql-file.js +417 -0
- package/dist/db/sql-file.js.map +1 -1
- package/dist/instance/zero-whitelist.d.ts +39 -0
- package/dist/instance/zero-whitelist.d.ts.map +1 -0
- package/dist/instance/zero-whitelist.js +67 -0
- package/dist/instance/zero-whitelist.js.map +1 -0
- package/dist/main.js +26 -3
- package/dist/main.js.map +1 -1
- package/dist/skill/agents-md.d.ts +25 -0
- package/dist/skill/agents-md.d.ts.map +1 -0
- package/dist/skill/agents-md.js +62 -0
- package/dist/skill/agents-md.js.map +1 -0
- package/dist/skill/hosts.d.ts +31 -7
- package/dist/skill/hosts.d.ts.map +1 -1
- package/dist/skill/hosts.js +65 -7
- package/dist/skill/hosts.js.map +1 -1
- package/dist/skill/installer.d.ts +15 -3
- package/dist/skill/installer.d.ts.map +1 -1
- package/dist/skill/installer.js +62 -24
- package/dist/skill/installer.js.map +1 -1
- package/dist/tenant/bundle.d.ts +66 -0
- package/dist/tenant/bundle.d.ts.map +1 -0
- package/dist/tenant/bundle.js +132 -0
- package/dist/tenant/bundle.js.map +1 -0
- package/dist/tenant/guide.d.ts +50 -0
- package/dist/tenant/guide.d.ts.map +1 -0
- package/dist/tenant/guide.js +152 -0
- package/dist/tenant/guide.js.map +1 -0
- package/dist/tenant/probe.d.ts +65 -0
- package/dist/tenant/probe.d.ts.map +1 -0
- package/dist/tenant/probe.js +159 -0
- package/dist/tenant/probe.js.map +1 -0
- package/dist/tenant/verify.d.ts +37 -0
- package/dist/tenant/verify.d.ts.map +1 -0
- package/dist/tenant/verify.js +132 -0
- package/dist/tenant/verify.js.map +1 -0
- package/dist/version.d.ts +29 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +65 -0
- package/dist/version.js.map +1 -0
- package/package.json +3 -3
- package/skills/pxbaas-app-spec/SKILL.md +4 -2
- package/skills/pxbaas-app-spec/references/deploy-options.md +3 -2
- package/skills/pxbaas-app-spec/references/runtime-contract.md +2 -2
- package/skills/pxbaas-app-spec/references/templates-examples.md +7 -4
- package/skills/pxbaas-cli/SKILL.md +26 -8
- package/skills/pxbaas-cli/references/app-deploy.md +46 -3
- package/skills/pxbaas-cli/references/auth-and-credentials.md +30 -2
- package/skills/pxbaas-cli/references/data-postgrest.md +19 -3
- package/skills/pxbaas-cli/references/db-and-meta.md +17 -0
- package/skills/pxbaas-cli/references/multi-tenant-apps.md +35 -4
- package/skills/pxbaas-cli/references/provision.md +16 -1
- package/skills/pxbaas-cli/references/safety-and-two-phase.md +42 -13
- package/skills/pxbaas-cli/references/tenant-admin.md +122 -0
- package/skills/pxbaas-cli/references/troubleshooting.md +4 -1
- package/templates/webapp/csr-bff/db/rls.json +13 -0
- package/templates/webapp/csr-bff/db/schema.sql +8 -28
- package/templates/webapp/manifest.json +131 -8
- package/templates/webapp/react-vite-hono/backend/index.ts +21 -4
- package/templates/webapp/ssr-blog/db/rls.json +13 -0
- package/templates/webapp/ssr-blog/db/schema.sql +8 -28
- package/templates/webapp/ssr-hono/backend/index.ts +50 -31
- package/templates/webapp/ssr-shop/db/rls.json +16 -0
- package/templates/webapp/ssr-shop/db/schema.sql +12 -42
- package/dist/commands/login/login.d.ts +0 -3
- package/dist/commands/login/login.d.ts.map +0 -1
- package/dist/commands/login/login.js +0 -185
- package/dist/commands/login/login.js.map +0 -1
- /package/templates/webapp/vanilla-static/frontend/{app.js → dist/app.js} +0 -0
- /package/templates/webapp/vanilla-static/frontend/{index.html → dist/index.html} +0 -0
- /package/templates/webapp/vanilla-static/frontend/{style.css → dist/style.css} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Provisioning & Managing Zero Instances (provision / instance)
|
|
2
2
|
|
|
3
|
-
Covers: `provision` (alias `instance claim`) · `instance list` · `instance release` · `instance credentials` · `instance current`.
|
|
3
|
+
Covers: `provision` (alias `instance claim`) · `instance list` · `instance release` · `instance credentials` · `instance current` · `instance whitelist get|add`.
|
|
4
4
|
|
|
5
5
|
When a customer has no BaaS instance yet, `pxbaas provision` claims a **disposable, Supabase-compatible instance** from PolarDB-X Zero and binds it to a profile — fully automatic, no human TTY login.
|
|
6
6
|
|
|
@@ -70,6 +70,21 @@ pxbaas instance release <id> --confirm --keep-profile # release remotely, keep
|
|
|
70
70
|
- Without `--confirm` it only previews. Release removes the local profile/credentials too (unless `--keep-profile`).
|
|
71
71
|
- If the released instance was the **current** profile, no profile is active afterwards — switch with `pxbaas profile use <name>` or `pxbaas provision`.
|
|
72
72
|
|
|
73
|
+
## pxbaas instance whitelist — query / append IP allow-list (Zero instances only)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pxbaas instance whitelist get [--id <zeroId>] [--json] # show effective whitelist
|
|
77
|
+
pxbaas instance whitelist add <ips> [--id <zeroId>] [--dry-run] [--json] # append IPv4/CIDR
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- **Zero instances only.** Targets the current profile when it is `source: zero`, or an explicit `--id <sbx_*>`. On a non-Zero (login) profile it refuses with a clear error — the whitelist self-service API exists only for Zero-provisioned instances.
|
|
81
|
+
- **Auth**: derived locally from the stored dashboard password as `sha256hex("supabase:"+password)` and sent as `X-Studio-User` / `X-Studio-Hash`. The password and hash are never printed or persisted. Requires the instance to have been claimed on this machine (the password is stored at claim time; Zero has no credential re-read).
|
|
82
|
+
- **`add` is append-only and idempotent**: existing entries are kept, duplicates are not re-added, and there is no delete/replace — by design, so you can never accidentally clear the list and lock yourself out (the API also requires the request to come from an already-allowed IP). To shrink the list, contact the platform admin. `--dry-run` previews the parsed IPs without calling Zero.
|
|
83
|
+
- **`ips`** is a comma-separated IPv4/CIDR list (e.g. `9.9.9.9,10.0.0.0/24`); single request ≤ 50 entries, merged total ≤ 50. IPv6 is not supported.
|
|
84
|
+
- **IP gate (`IP_MISMATCH`, 403)**: the call must originate from the claiming IP or an already-whitelisted IP. If your egress IP changed since claim, you cannot self-add the new one from it — re-provision with `--whitelist-ip`, or run from an allowed IP. Tip: claim with a CIDR (e.g. `--whitelist-ip <your-egress>/24`) if your egress rotates within a range.
|
|
85
|
+
- Other codes: `ASSIGNMENT_EXPIRED` (403, past 15-day TTL → re-provision), `STUDIO_CREDENTIALS_INVALID` (403), `INSTANCE_NOT_ACCESSIBLE` (404), `INSTANCE_BUSY` / `CONCURRENT_UPDATE` (409, retry / re-`get` to reconcile), `WHITELIST_MODIFY_FAILED` (502, report the `cloudRequestId`), `RATE_LIMIT_EXCEEDED` (429).
|
|
86
|
+
- `get` output: `instanceId`, `groupName`, `whitelist` (effective entries; excludes the system `127.0.0.1` baseline), `source`, `expiresAt`, `updatedAt`.
|
|
87
|
+
|
|
73
88
|
## Capacity / rate limits — stop and inform (do NOT loop)
|
|
74
89
|
|
|
75
90
|
Zero is a shared pre-warmed pool with per-caller limits. On these errors, **stop, report, and let the user decide** — never retry in a loop:
|
|
@@ -2,28 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
This is the safety baseline for using `pxbaas`. **The CLI currently has NO built-in confirmation gate**, so the responsibility to confirm falls on YOU (the agent).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Which commands actually gate themselves
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **Implication:** do NOT assume the CLI will block a dangerous operation for you. **You are the confirmation gate.**
|
|
7
|
+
Gating landed after this page was first written, so it is now a per-command fact rather
|
|
8
|
+
than a global one. Verified against `--help` output:
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
| Command | Gate |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `db push <file>`, `app db-init <name>` | shows the full plan, then TTY confirm or `--yes`; without authorization **no statement runs** |
|
|
13
|
+
| `data delete <table>` | read-only preview of affected rows + exact request URL, then TTY confirm or `--yes`; `--json` keeps a `preview` field even when refused |
|
|
14
|
+
| `app remove`, `app destroy`, `app share revoke`, `instance release`, `skill remove` | require `--confirm`; without them the command returns a `dry-run` preview |
|
|
15
|
+
| `tenant create` | `--dry-run` plan, then TTY confirm or `--yes`; refuses a colliding tenant id unless `--reactivate` |
|
|
16
|
+
| `tenant repair-keys` | requires `--confirm` |
|
|
17
|
+
|
|
18
|
+
**No gate at all** — you are the confirmation layer for these:
|
|
19
|
+
|
|
20
|
+
| Command | Risk |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `db create-table`, `db add-column` | DDL, executes immediately (help text says so) |
|
|
23
|
+
| `db query "<write/DDL>"` | executes whatever the SQL says |
|
|
24
|
+
| `data insert`, `data update` | writes; `update` requires `--filter` but may still hit many rows |
|
|
25
|
+
| `rest` with POST/PATCH/PUT/DELETE | whatever the method does |
|
|
26
|
+
| `functions deploy`, `functions delete` | publishes/overwrites or deletes a live function, irreversible |
|
|
27
|
+
| `app deploy` | publishes live (preflight + spec lint run, but no interactive confirm) |
|
|
28
|
+
| `storage rm` / `mv` / `cp` overwrite | deletes, moves, or silently overwrites objects |
|
|
29
|
+
| `auth users create` / `delete` / `invite` | mutates users; `invite` sends real email |
|
|
30
|
+
| `profile rm`, `logout`, `tenant logout` | removes local credentials (config, not instance data) |
|
|
31
|
+
|
|
32
|
+
Read-only and always safe: `whoami` `status` `doctor` `profile ls` `tenant probe` `tenant ls`
|
|
33
|
+
`tenant handoffs` `db tables` `db schema` `gen types` `data select` `functions list` `storage ls`
|
|
34
|
+
`auth users list/get` `app list` `app url` `app capabilities` `app templates` `instance list/current`.
|
|
35
|
+
|
|
36
|
+
## Risk classification (what impact to expect)
|
|
12
37
|
|
|
13
38
|
| Command | Risk |
|
|
14
39
|
|---|---|
|
|
15
|
-
| `data delete <table> --filter ...` | deletes rows, irreversible |
|
|
40
|
+
| `data delete <table> --filter ...` | deletes rows, irreversible (gated: preview + `--yes`) |
|
|
16
41
|
| `data insert` / `data update` | writes / mutates data |
|
|
17
|
-
| `db create-table` / `db add-column` | schema change (DDL) |
|
|
42
|
+
| `db create-table` / `db add-column` | schema change (DDL), executes immediately |
|
|
18
43
|
| `db query "<write or DDL SQL>"` | depends on the SQL |
|
|
44
|
+
| `db push` / `app db-init` | DDL + RLS + seed; sequential, stops on first error, **never rolls back** |
|
|
19
45
|
| `functions deploy` | publishes / overwrites a live function |
|
|
20
|
-
| `functions delete <slug>` | deletes a live function, irreversible |
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
46
|
+
| `functions delete <slug>` | deletes a live function, irreversible; also revokes its share links |
|
|
47
|
+
| `app deploy` / `publish` / `rollback` / `unpublish` | changes what is live for users |
|
|
48
|
+
| `app remove` / `app destroy [--purge-db]` | deletes functions + mount; `--purge-db` also drops tables and RLS rows |
|
|
49
|
+
| `storage rm` / `mv` / `cp` (overwrite) | deletes / moves / overwrites objects; remote→remote `cp` is a **move** (source is removed) |
|
|
50
|
+
| `auth users delete` / `create` / `invite` | deletes / creates users; `invite` sends real email |
|
|
23
51
|
| `rest` POST/PATCH/DELETE/PUT | depends on method and path |
|
|
24
|
-
| `profile rm` / `logout` | removes local
|
|
25
|
-
|
|
26
|
-
|
|
52
|
+
| `profile rm` / `logout` / `tenant logout` | removes local credentials (also the stored keys, so re-login is required) |
|
|
53
|
+
| `tenant create` | provisions a database, a MySQL account + GRANTs, a storage bucket, RLS tables, Realtime registration; consumes quota |
|
|
54
|
+
| `tenant repair-keys` | re-signs NULL-keyed tenants from the existing `jwt_secret`; idempotent, **not** a rotation |
|
|
55
|
+
| `instance release` | destroys cloud resources and the local profile |
|
|
27
56
|
|
|
28
57
|
## Agent confirmation protocol (for destructive ops)
|
|
29
58
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Tenant Administration (platform-operator surface)
|
|
2
|
+
|
|
3
|
+
Covers: `tenant login` · `tenant logout` · `tenant probe` · `tenant ls` · `tenant create` · `tenant keys` · `tenant repair-keys` · `tenant handoffs`, and the tenant access bundle.
|
|
4
|
+
|
|
5
|
+
**Who this is for:** the operator of a **multi-tenant** PolarDB-X BaaS instance. Ordinary tenants never use this surface — they hold per-tenant API keys and use `db` / `data` / `app`. Never point a tenant at these commands.
|
|
6
|
+
|
|
7
|
+
## Credential model (read this first)
|
|
8
|
+
|
|
9
|
+
| | Data plane | Admin plane |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Secret | per-tenant `anon` + `service_role` JWT | instance dashboard HTTP Basic (`DASHBOARD_USERNAME` / `DASHBOARD_PASSWORD`) |
|
|
12
|
+
| Keyring account | `credentials/<profile>` | `admin/<profile>` |
|
|
13
|
+
| Used by | `db`, `data`, `storage`, `auth`, `functions`, `app` | `tenant *` |
|
|
14
|
+
|
|
15
|
+
Rules that hold in code, not convention:
|
|
16
|
+
|
|
17
|
+
- **The password never goes in argv.** `--dashboard-user`, `--dashboard-password`, `--admin-password` are refused by `assertNoKeyArgs`. Supply them via TTY prompt (masked) or via `--user-stdin --password-stdin` (two lines: username, then password; both flags are required together).
|
|
18
|
+
- **`DASHBOARD_*` found in an env file is NOT imported by `profile import`.** It is reported as present-but-not-imported. Only `--with-admin` binds it, and then only after a successful admin-channel probe.
|
|
19
|
+
- **Endpoint must be the Kong gateway.** Studio's `/api/platform/*` routes do not authenticate on their own (`apiWrapper` is called without `withAuth`); the only gate is Kong's dashboard `basic-auth`. Targeting a component port bypasses it and is refused at the prompt.
|
|
20
|
+
- Plaintext is never printed. Output carries fingerprints (`sha256:<8>...<4>`); the only sinks for plaintext keys are the keyring (`--as-profile`) and a `0600` access bundle (`--bundle`).
|
|
21
|
+
|
|
22
|
+
## Capability probe
|
|
23
|
+
|
|
24
|
+
`pxbaas tenant probe --json` answers, in one read-only call, whether the current profile can operate the admin plane:
|
|
25
|
+
|
|
26
|
+
| `status` | meaning | next move |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `admin` | admin channel works | `tenant ls`, then `create` / `keys` |
|
|
29
|
+
| `not-admin` | 401/403 from the gateway | these are not dashboard credentials |
|
|
30
|
+
| `single-tenant` | instance reports `multiTenantMode:false` | tenant administration does not apply |
|
|
31
|
+
| `metadata-unreadable` | credentials accepted but tenant metadata fails to load | platform-side schema drift — do not report "no tenants" |
|
|
32
|
+
| `unreachable` | network/endpoint failure | fix connectivity first |
|
|
33
|
+
|
|
34
|
+
Why `metadata-unreadable` exists: the list endpoint also returns `200 + tenants: []` when its own query fails. The probe disambiguates by asking for a sentinel tenant id — `404` means the metadata path is healthy (so the empty list is real), `500` means it is not.
|
|
35
|
+
|
|
36
|
+
## Command reference
|
|
37
|
+
|
|
38
|
+
| Command | Options (defaults) | Notes |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `tenant login` | `--endpoint <url>`, `--user <name>`, `--user-stdin`, `--password-stdin` | Probes **before** storing; a refused probe stores nothing. Creates an admin-only profile when none exists (it holds no tenant keys, so data-plane commands on it still ask for `pxbaas login`) |
|
|
41
|
+
| `tenant logout` | — | Removes dashboard credentials only; tenant keys untouched |
|
|
42
|
+
| `tenant probe` | — | Read-only |
|
|
43
|
+
| `tenant ls` | `--status <active\|suspended\|deleting\|deleted>` | Redacted rows: id, status, plan, db, plus `hasAnonKey` / `hasServiceRoleKey` booleans. A missing key shows a `repair-keys` hint |
|
|
44
|
+
| `tenant create [tenant-id]` | `--generate-id`, `--plan` (`free`), `--display-name`, `--retention-days <n>`, `--reactivate`, `--dry-run`, `--yes`, `--as-profile [name]`, `--bundle <file>`, `--intranet <url>`, `--overwrite` | Id must match `^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$` |
|
|
45
|
+
| `tenant keys <tenant-id>` | `--as-profile [name]`, `--bundle <file>`, `--intranet <url>`, `--overwrite` | Requires one destination; refuses to bundle when either key is NULL |
|
|
46
|
+
| `tenant repair-keys` | `--confirm` | Without `--confirm` prints which tenants are missing keys and changes nothing |
|
|
47
|
+
| `tenant handoffs` | — | Local ledger: tenant id, timestamps, fingerprints, bundle path + sha256. Never key material |
|
|
48
|
+
|
|
49
|
+
All of them accept `--json` and `--profile <name>`.
|
|
50
|
+
|
|
51
|
+
## Two-phase behaviour
|
|
52
|
+
|
|
53
|
+
| Command | Preview | Authorization |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| `tenant create` | `--dry-run` (shows plan + side effects) | TTY confirm, or `--yes`; without it **nothing is created** |
|
|
56
|
+
| `tenant repair-keys` | default (no `--confirm`) | `--confirm` |
|
|
57
|
+
| `tenant keys` / `ls` / `probe` / `handoffs` | n/a | read-only (writes are local: keyring / bundle file) |
|
|
58
|
+
|
|
59
|
+
`tenant create` exits non-zero (`process.exitCode = 2`) when post-create verification fails — a `201` from the platform is **not** proof of a usable tenant, because CREATE DATABASE / CREATE USER / GRANT / RLS tables / Realtime registration are non-fatal server-side steps. Verification runs `SELECT DATABASE()`, an `information_schema` table count, the RLS registry read, and reports `realtime_provisioned`.
|
|
60
|
+
|
|
61
|
+
## Danger: same-name create against a soft-deleted tenant
|
|
62
|
+
|
|
63
|
+
The platform's create path resurrects a `status='deleted'` row and **regenerates its `jwt_secret` and both API keys**. That silently invalidates whoever held the old keys and brings any surviving data back under new credentials. The CLI therefore refuses a colliding id by default:
|
|
64
|
+
|
|
65
|
+
- live tenant with the same id → refused, tells you to use `tenant keys <id>` instead
|
|
66
|
+
- soft-deleted tenant → refused unless you pass `--reactivate`
|
|
67
|
+
|
|
68
|
+
Never retry a failed create with a different flag combination hoping it will pass. Read the message.
|
|
69
|
+
|
|
70
|
+
## Tenant access bundle (handing keys to another human)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pxbaas tenant create acme --bundle ./acme.pxbaas.env # new tenant + bundle
|
|
74
|
+
pxbaas tenant keys acme --bundle ./acme.pxbaas.env # existing tenant + bundle
|
|
75
|
+
pxbaas tenant keys acme --as-profile # bind into this machine's keyring
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Admin credentials come from the active profile (`tenant login`), not from a per-command flag.
|
|
79
|
+
|
|
80
|
+
`--bundle` writes `0600` and refuses an existing path unless `--overwrite`. The file contains exactly:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
MULTI_TENANT_MODE=true
|
|
84
|
+
SUPABASE_URL=<gateway>
|
|
85
|
+
DEFAULT_TENANT_ID=<tenant>
|
|
86
|
+
TENANT_ANON_KEY=<key>
|
|
87
|
+
TENANT_SERVICE_ROLE_KEY=<key>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Built by explicit field list, so `jwt_secret`, `DASHBOARD_*` and `GLOBAL_*` cannot appear in it. It is directly consumable by the receiving user:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pxbaas profile import ./acme.pxbaas.env --name acme --json
|
|
94
|
+
pxbaas whoami --json # expect mode=multi-tenant, tenantId=acme
|
|
95
|
+
pxbaas doctor --json # Tenant Consistency must pass
|
|
96
|
+
pxbaas db tables --json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`create`/`keys` also print a copy-paste handoff note (import steps + both fingerprints) and append a line to `~/.baas/handoffs.json`. The receiver compares those fingerprints against `whoami` to prove nothing was swapped in transit.
|
|
100
|
+
|
|
101
|
+
**Treat a bundle as a long-lived plaintext credential.** Keys are signed with `exp ≈ 10 years` and the platform has **no revocation**: `PATCH /api/platform/tenants/[id]` does not accept `jwt_secret`, and HS256 tokens carry no `jti`, so re-signing keys does not invalidate old ones. Consequences for you as agent:
|
|
102
|
+
|
|
103
|
+
- never cat/read a bundle file into the conversation, never echo its contents, never commit it (add `*.pxbaas.env` to ignores)
|
|
104
|
+
- do not upload it to chat, ticket attachments, or shared drives; that channel choice is the operator's job
|
|
105
|
+
- tell the operator that distributing a bundle = distributing an irrevocable admin-grade credential for that tenant
|
|
106
|
+
|
|
107
|
+
## Gap guidance from `profile import`
|
|
108
|
+
|
|
109
|
+
When a multi-tenant env file lacks tenant-level keys, `profile import` now prints a diagnosis (which variables exist, whether each JWT carries `tenant_id`, whether `DASHBOARD_*` is present), states `Nothing was created and nothing was imported`, and routes to one of:
|
|
110
|
+
|
|
111
|
+
- **admin path** — when the file's dashboard credentials actually open the admin channel: `tenant login` → `tenant ls` → `tenant keys <id> --as-profile|--bundle`
|
|
112
|
+
- **request path** — otherwise: a checklist to send to the operator (tenant id + both keys, plus how to prove a key is tenant-level: its JWT payload must contain `tenant_id`)
|
|
113
|
+
|
|
114
|
+
The transient probe used for this routing is read-only and does **not** persist the dashboard credentials it borrowed from the file.
|
|
115
|
+
|
|
116
|
+
## Agent rules for this surface
|
|
117
|
+
|
|
118
|
+
1. **Never create a tenant on someone else's behalf without explicit confirmation** — it provisions a database, a MySQL account, GRANTs, a storage bucket and quota consumption.
|
|
119
|
+
2. **Prefer `tenant keys <id>` over `tenant create`**: most "I have no key" cases are an existing tenant whose key was never handed over. Run `tenant ls` first.
|
|
120
|
+
3. `tenant repair-keys` is the safe fix for a tenant visible with `hasAnonKey=false`; it is idempotent and re-signs from the existing `jwt_secret`. Do not present it as rotation.
|
|
121
|
+
4. Do not run `tenant create` with `--reactivate` unless the user has read the resurrection warning above.
|
|
122
|
+
5. There is no `suspend` / `resume` / `delete` / `quotas` in this surface yet — do not invent them; use Studio for tenant lifecycle beyond create.
|
|
@@ -57,7 +57,10 @@ pxbaas whoami --json # verify profile / tenant / role / key fingerprints
|
|
|
57
57
|
| MT: `/pg/query` 401 | it takes the **global** service key as both `Authorization` and `apikey`; Basic Auth and tenant keys are rejected | switch credential tier |
|
|
58
58
|
| MT: `403 Global credentials cannot access tenant resources` | global key used on a tenant-scoped endpoint | use that tenant's own anon/service key |
|
|
59
59
|
| MT: `403 Token tenant mismatch` | the key's `tenant_id` claim ≠ `X-Tenant-Id` | align them; never cross tenants |
|
|
60
|
-
| MT: `db query` 401 on a tenant profile |
|
|
60
|
+
| MT: `db query` 401 on a tenant profile | obsolete workaround — MT SQL no longer goes through `/pg/query`; something is wrong with the profile or the key | check `pxbaas doctor --json` (Tenant Consistency, JWT Expiry) and re-login with that tenant's per-tenant keys; `/pg/query` stays platform-admin only |
|
|
61
|
+
| `profile import` refuses a MT env file ("lacks tenant-level credentials") | the file only carries platform-global keys (JWT has no `tenant_id`) | follow the printed route: operator → `tenant login` then `tenant keys <id> --as-profile`; non-operator → send the printed checklist to the operator. Never create a tenant to work around a missing key |
|
|
62
|
+
| `tenant login` says "Not saved. ... could not open the tenant admin channel" | wrong credentials for the admin plane, or endpoint is not the Kong gateway | use the gateway URL (typically :8000); dashboard Basic creds are required — an API key never works here |
|
|
63
|
+
| `tenant probe` → `metadata-unreadable` | the credentials are fine but tenant metadata fails to load (schema drift on long-lived instances) | do not report "no tenants"; escalate to the platform operator |
|
|
61
64
|
| MT: RLS policies registered but have **no effect** | wrong `table_schema` (must be the tenant DB name, not `public`), or written into the tenant DB's unread `_mysqlrest_rls_*` copies | rewrite into the central `supabase._mysqlrest_rls_*`, then verify behaviourally (anon read / forged-owner write → 403) |
|
|
62
65
|
| `app publish` → `APP_MANIFEST_INVALID` | manifest `name` ≠ mount `appName`, or a v1 `mpa` manifest on a schemaVersion-2-only instance | align `name`; migrate the manifest (pxbaas-app-spec `deploy-options.md`) |
|
|
63
66
|
| `app publish` → `APP_SSR_PROBE_FAILED` | **platform-side**: cold tenant sandbox vs the probe budget (heavy `npm:` graph, ≈60 s) | warm the sandbox with a legacy function, then retry publish 3–5× |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Declarative RLS for the CSR/BFF blog webapp. Applied by `pxbaas app db-init` (single-tenant AND multi-tenant). table_schema is NEVER declared here: single-tenant binds it to --schema (default public); multi-tenant binds it server-side to the tenant db.",
|
|
3
|
+
"tables": [
|
|
4
|
+
{ "table": "posts", "rlsEnabled": true, "rlsForced": false }
|
|
5
|
+
],
|
|
6
|
+
"policies": [
|
|
7
|
+
{ "table": "posts", "name": "posts_select_anon", "role": "anon", "command": "SELECT", "using": "`status` = 'published'", "check": null, "permissive": true },
|
|
8
|
+
{ "table": "posts", "name": "posts_select_auth", "role": "authenticated", "command": "SELECT", "using": "`status` = 'published' OR author_id = {{request.jwt.claim.sub}}", "check": null, "permissive": true },
|
|
9
|
+
{ "table": "posts", "name": "posts_insert_auth", "role": "authenticated", "command": "INSERT", "using": null, "check": "author_id = {{request.jwt.claim.sub}}", "permissive": true },
|
|
10
|
+
{ "table": "posts", "name": "posts_update_auth", "role": "authenticated", "command": "UPDATE", "using": "author_id = {{request.jwt.claim.sub}}", "check": "author_id = {{request.jwt.claim.sub}}", "permissive": true },
|
|
11
|
+
{ "table": "posts", "name": "posts_delete_auth", "role": "authenticated", "command": "DELETE", "using": "author_id = {{request.jwt.claim.sub}}", "check": null, "permissive": true }
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
-- · 多租户实例:__SCHEMA__ = tenant_<tenant_id>
|
|
7
7
|
-- sed 's/__SCHEMA__/public/g' db/schema.sql
|
|
8
8
|
--
|
|
9
|
-
--
|
|
10
|
-
--
|
|
9
|
+
-- 执行通道(推荐):`pxbaas app db-init <name>` —— 单/多租户通用:
|
|
10
|
+
-- 单租户 __SCHEMA__→public 经 /pg/query;多租户 __SCHEMA__ 去限定经 /api/tenant-ddl
|
|
11
|
+
-- 用 per-tenant service_role key 执行(GRANT 仅本租户库)。本文件只含 DDL + 种子;
|
|
12
|
+
-- RLS 声明在同目录 db/rls.json,由 db-init 一并应用。
|
|
11
13
|
--
|
|
12
|
-
-- ⚠️ RLS 元数据落点(写错静默失效=RLS 关闭):永远写中心库 supabase._mysqlrest_rls_
|
|
13
|
-
--
|
|
14
|
+
-- ⚠️ RLS 元数据落点(写错静默失效=RLS 关闭):永远写中心库 supabase._mysqlrest_rls_*;
|
|
15
|
+
-- db-init 自动绑定 table_schema(单租户=--schema,多租户=服务端强绑),勿手写库名。
|
|
14
16
|
-- =============================================================================
|
|
15
17
|
|
|
16
18
|
CREATE TABLE IF NOT EXISTS `__SCHEMA__`.`posts` (
|
|
@@ -24,30 +26,8 @@ CREATE TABLE IF NOT EXISTS `__SCHEMA__`.`posts` (
|
|
|
24
26
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
25
27
|
);
|
|
26
28
|
|
|
27
|
-
-- ---- RLS
|
|
28
|
-
|
|
29
|
-
VALUES ('__SCHEMA__','posts',1,0);
|
|
30
|
-
|
|
31
|
-
-- ---- RLS 策略:anon 只读 published;authenticated 读 published 或本人;写改删限本人 ----
|
|
32
|
-
INSERT IGNORE INTO supabase._mysqlrest_rls_policies
|
|
33
|
-
(table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
|
|
34
|
-
VALUES ('__SCHEMA__','posts','posts_select_anon','anon','SELECT','`status` = ''published''',NULL,1);
|
|
35
|
-
INSERT IGNORE INTO supabase._mysqlrest_rls_policies
|
|
36
|
-
(table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
|
|
37
|
-
VALUES ('__SCHEMA__','posts','posts_select_auth','authenticated','SELECT',
|
|
38
|
-
'`status` = ''published'' OR author_id = {{request.jwt.claim.sub}}',NULL,1);
|
|
39
|
-
INSERT IGNORE INTO supabase._mysqlrest_rls_policies
|
|
40
|
-
(table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
|
|
41
|
-
VALUES ('__SCHEMA__','posts','posts_insert_auth','authenticated','INSERT',NULL,
|
|
42
|
-
'author_id = {{request.jwt.claim.sub}}',1);
|
|
43
|
-
INSERT IGNORE INTO supabase._mysqlrest_rls_policies
|
|
44
|
-
(table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
|
|
45
|
-
VALUES ('__SCHEMA__','posts','posts_update_auth','authenticated','UPDATE',
|
|
46
|
-
'author_id = {{request.jwt.claim.sub}}','author_id = {{request.jwt.claim.sub}}',1);
|
|
47
|
-
INSERT IGNORE INTO supabase._mysqlrest_rls_policies
|
|
48
|
-
(table_schema, table_name, policy_name, role_name, command, using_expr, check_expr, permissive)
|
|
49
|
-
VALUES ('__SCHEMA__','posts','posts_delete_auth','authenticated','DELETE',
|
|
50
|
-
'author_id = {{request.jwt.claim.sub}}',NULL,1);
|
|
29
|
+
-- ---- RLS 注册与策略:声明在同目录 db/rls.json,由 `pxbaas app db-init` 应用 ----
|
|
30
|
+
-- anon 只读 published;authenticated 读 published 或本人;写/改/删限本人(author_id = jwt.sub)。
|
|
51
31
|
|
|
52
32
|
-- ---- 种子文章(3 篇 published,幂等)----
|
|
53
33
|
INSERT INTO `__SCHEMA__`.`posts` (title, content, author_id, author_name, status)
|
|
@@ -3,16 +3,46 @@
|
|
|
3
3
|
"templates": [
|
|
4
4
|
{
|
|
5
5
|
"id": "spa",
|
|
6
|
+
"meta": {
|
|
7
|
+
"delivery": "legacy",
|
|
8
|
+
"render": "csr",
|
|
9
|
+
"frontend": "inline",
|
|
10
|
+
"backend": "none",
|
|
11
|
+
"session": "none",
|
|
12
|
+
"hasDb": false,
|
|
13
|
+
"coldStart": "light",
|
|
14
|
+
"purpose": "legacy-demo"
|
|
15
|
+
},
|
|
6
16
|
"description": "Self-contained single-page webapp: inline HTML/CSS/JS + a built-in JSON API via ?action=. Best for shareable demos.",
|
|
7
17
|
"entrypoint": "index.ts"
|
|
8
18
|
},
|
|
9
19
|
{
|
|
10
20
|
"id": "api",
|
|
21
|
+
"meta": {
|
|
22
|
+
"delivery": "legacy",
|
|
23
|
+
"render": "headless",
|
|
24
|
+
"frontend": "none",
|
|
25
|
+
"backend": "none",
|
|
26
|
+
"session": "none",
|
|
27
|
+
"hasDb": false,
|
|
28
|
+
"coldStart": "light",
|
|
29
|
+
"purpose": "legacy-demo"
|
|
30
|
+
},
|
|
11
31
|
"description": "Headless JSON backend (Deno.serve) with ?action= routing and CORS. Best for webhooks / API-only demos.",
|
|
12
32
|
"entrypoint": "index.ts"
|
|
13
33
|
},
|
|
14
34
|
{
|
|
15
35
|
"id": "mpa",
|
|
36
|
+
"meta": {
|
|
37
|
+
"delivery": "legacy",
|
|
38
|
+
"render": "static",
|
|
39
|
+
"frontend": "html",
|
|
40
|
+
"backend": "none",
|
|
41
|
+
"session": "none",
|
|
42
|
+
"hasDb": false,
|
|
43
|
+
"coldStart": "light",
|
|
44
|
+
"purpose": "legacy-demo"
|
|
45
|
+
},
|
|
16
46
|
"description": "Multi-page static webapp: real index.html/about.html/style.css/app.js served by path via the proxy static branch, plus a worker JSON API via ?action=. Best for traditional multi-page sites.",
|
|
17
47
|
"entrypoint": "index.ts",
|
|
18
48
|
"mode": "mpa",
|
|
@@ -34,13 +64,23 @@
|
|
|
34
64
|
},
|
|
35
65
|
{
|
|
36
66
|
"id": "vanilla-static",
|
|
37
|
-
"
|
|
38
|
-
|
|
67
|
+
"meta": {
|
|
68
|
+
"delivery": "webapp",
|
|
69
|
+
"render": "static",
|
|
70
|
+
"frontend": "vanilla",
|
|
71
|
+
"backend": "none",
|
|
72
|
+
"session": "none",
|
|
73
|
+
"hasDb": false,
|
|
74
|
+
"coldStart": "light",
|
|
75
|
+
"purpose": "validation"
|
|
76
|
+
},
|
|
77
|
+
"description": "Minimal text-only static app for validating the /apps mount and release lifecycle. Editable files live in frontend/dist, which is also the artifact directory deploy uploads.",
|
|
78
|
+
"entrypoint": "frontend/dist/index.html",
|
|
39
79
|
"mode": "webapp",
|
|
40
80
|
"files": [
|
|
41
|
-
"frontend/index.html",
|
|
42
|
-
"frontend/style.css",
|
|
43
|
-
"frontend/app.js"
|
|
81
|
+
"frontend/dist/index.html",
|
|
82
|
+
"frontend/dist/style.css",
|
|
83
|
+
"frontend/dist/app.js"
|
|
44
84
|
],
|
|
45
85
|
"frontend": {
|
|
46
86
|
"root": "frontend/dist",
|
|
@@ -52,6 +92,16 @@
|
|
|
52
92
|
},
|
|
53
93
|
{
|
|
54
94
|
"id": "react-vite",
|
|
95
|
+
"meta": {
|
|
96
|
+
"delivery": "webapp",
|
|
97
|
+
"render": "csr",
|
|
98
|
+
"frontend": "react",
|
|
99
|
+
"backend": "none",
|
|
100
|
+
"session": "none",
|
|
101
|
+
"hasDb": false,
|
|
102
|
+
"coldStart": "light",
|
|
103
|
+
"purpose": "starter"
|
|
104
|
+
},
|
|
55
105
|
"description": "React + Vite static app with HashRouter, relative base, plain CSS, and exact dependency versions.",
|
|
56
106
|
"entrypoint": "frontend/src/App.tsx",
|
|
57
107
|
"mode": "webapp",
|
|
@@ -75,6 +125,16 @@
|
|
|
75
125
|
},
|
|
76
126
|
{
|
|
77
127
|
"id": "react-vite-hono",
|
|
128
|
+
"meta": {
|
|
129
|
+
"delivery": "webapp",
|
|
130
|
+
"render": "csr",
|
|
131
|
+
"frontend": "react",
|
|
132
|
+
"backend": "hono",
|
|
133
|
+
"session": "none",
|
|
134
|
+
"hasDb": false,
|
|
135
|
+
"coldStart": "light",
|
|
136
|
+
"purpose": "starter"
|
|
137
|
+
},
|
|
78
138
|
"description": "React + Vite + HashRouter frontend with a Deno-served, platform-versioned Hono backend.",
|
|
79
139
|
"entrypoint": "backend/index.ts",
|
|
80
140
|
"mode": "webapp",
|
|
@@ -106,6 +166,16 @@
|
|
|
106
166
|
},
|
|
107
167
|
{
|
|
108
168
|
"id": "ssr-hono",
|
|
169
|
+
"meta": {
|
|
170
|
+
"delivery": "webapp",
|
|
171
|
+
"render": "ssr",
|
|
172
|
+
"frontend": "static-shell",
|
|
173
|
+
"backend": "hono",
|
|
174
|
+
"session": "none",
|
|
175
|
+
"hasDb": false,
|
|
176
|
+
"coldStart": "moderate",
|
|
177
|
+
"purpose": "starter"
|
|
178
|
+
},
|
|
109
179
|
"description": "SSR-shell webapp: Hono worker server-renders the page shell at /page (static-miss fallthrough, fallback=worker); browser hydrates and fetches data client-side. Single-file self-contained worker per D16.",
|
|
110
180
|
"entrypoint": "backend/index.ts",
|
|
111
181
|
"mode": "webapp",
|
|
@@ -131,6 +201,16 @@
|
|
|
131
201
|
},
|
|
132
202
|
{
|
|
133
203
|
"id": "ssr-auth",
|
|
204
|
+
"meta": {
|
|
205
|
+
"delivery": "webapp",
|
|
206
|
+
"render": "ssr",
|
|
207
|
+
"frontend": "static-shell",
|
|
208
|
+
"backend": "deno",
|
|
209
|
+
"session": "proxy-injected",
|
|
210
|
+
"hasDb": false,
|
|
211
|
+
"coldStart": "light",
|
|
212
|
+
"purpose": "reference"
|
|
213
|
+
},
|
|
134
214
|
"description": "SSR webapp with proxy-injected session (方案 C): the worker server-renders authenticated pages from trusted X-App-User-* headers; it never reads cookies, never sets Set-Cookie, and never puts tokens in URLs. Requires capabilities.sessionMode=proxy-injected-header.",
|
|
135
215
|
"entrypoint": "backend/index.ts",
|
|
136
216
|
"mode": "webapp",
|
|
@@ -156,6 +236,16 @@
|
|
|
156
236
|
},
|
|
157
237
|
{
|
|
158
238
|
"id": "ssr-blog",
|
|
239
|
+
"meta": {
|
|
240
|
+
"delivery": "webapp",
|
|
241
|
+
"render": "ssr",
|
|
242
|
+
"frontend": "static-shell",
|
|
243
|
+
"backend": "deno",
|
|
244
|
+
"session": "proxy-injected",
|
|
245
|
+
"hasDb": true,
|
|
246
|
+
"coldStart": "light",
|
|
247
|
+
"purpose": "reference"
|
|
248
|
+
},
|
|
159
249
|
"description": "Full-site SSR blog (zero npm deps) with proxy-injected session: the worker renders every page server-side from the instance internal base and reads identity ONLY from X-App-User-* headers. Requires capabilities.sessionMode=proxy-injected-header.",
|
|
160
250
|
"entrypoint": "backend/index.ts",
|
|
161
251
|
"mode": "webapp",
|
|
@@ -163,6 +253,7 @@
|
|
|
163
253
|
"README.md",
|
|
164
254
|
"backend/index.ts",
|
|
165
255
|
"db/schema.sql",
|
|
256
|
+
"db/rls.json",
|
|
166
257
|
"frontend/dist/index.html"
|
|
167
258
|
],
|
|
168
259
|
"frontend": {
|
|
@@ -182,13 +273,23 @@
|
|
|
182
273
|
},
|
|
183
274
|
"ssrProbePath": "/posts",
|
|
184
275
|
"nextSteps": [
|
|
185
|
-
"Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir
|
|
276
|
+
"Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>`. It reads db/schema.sql (DDL + seed) and db/rls.json (RLS registry), shows the full plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). Works in single-tenant (/pg/query) AND multi-tenant (/api/tenant-ddl with your per-tenant service_role key); the CLI never auto-runs DDL.",
|
|
186
277
|
"Requires capabilities.sessionMode=proxy-injected-header.",
|
|
187
278
|
"Data-plane base and anon key come from the platform-in SUPABASE_INTERNAL_URL / SUPABASE_ANON_KEY (edge-runtime MR #30497903); no plaintext key editing is needed."
|
|
188
279
|
]
|
|
189
280
|
},
|
|
190
281
|
{
|
|
191
282
|
"id": "csr-bff",
|
|
283
|
+
"meta": {
|
|
284
|
+
"delivery": "webapp",
|
|
285
|
+
"render": "csr",
|
|
286
|
+
"frontend": "html",
|
|
287
|
+
"backend": "deno",
|
|
288
|
+
"session": "proxy-injected",
|
|
289
|
+
"hasDb": true,
|
|
290
|
+
"coldStart": "light",
|
|
291
|
+
"purpose": "reference"
|
|
292
|
+
},
|
|
192
293
|
"description": "Front/back separated CSR blog: real static pages served by path plus a same-origin Deno BFF worker that proxies /api/* to the instance internal REST/Auth. Identity comes from proxy-injected X-App-User-Token (client Authorization is stripped on /apps).",
|
|
193
294
|
"entrypoint": "backend/index.ts",
|
|
194
295
|
"mode": "webapp",
|
|
@@ -196,6 +297,7 @@
|
|
|
196
297
|
"README.md",
|
|
197
298
|
"backend/index.ts",
|
|
198
299
|
"db/schema.sql",
|
|
300
|
+
"db/rls.json",
|
|
199
301
|
"frontend/dist/admin.html",
|
|
200
302
|
"frontend/dist/article.html",
|
|
201
303
|
"frontend/dist/blog.js",
|
|
@@ -222,13 +324,23 @@
|
|
|
222
324
|
"session": "proxy-injected"
|
|
223
325
|
},
|
|
224
326
|
"nextSteps": [
|
|
225
|
-
"Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir
|
|
327
|
+
"Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>`. It reads db/schema.sql (DDL + seed) and db/rls.json (RLS registry), shows the full plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). Works in single-tenant (/pg/query) AND multi-tenant (/api/tenant-ddl with your per-tenant service_role key); the CLI never auto-runs DDL.",
|
|
226
328
|
"Requires capabilities.sessionMode=proxy-injected-header.",
|
|
227
329
|
"The BFF forwards to the platform-injected SUPABASE_INTERNAL_URL with SUPABASE_ANON_KEY (edge-runtime MR #30497903); no plaintext key editing is needed."
|
|
228
330
|
]
|
|
229
331
|
},
|
|
230
332
|
{
|
|
231
333
|
"id": "ssr-shop",
|
|
334
|
+
"meta": {
|
|
335
|
+
"delivery": "webapp",
|
|
336
|
+
"render": "ssr",
|
|
337
|
+
"frontend": "react",
|
|
338
|
+
"backend": "hono",
|
|
339
|
+
"session": "browser-side",
|
|
340
|
+
"hasDb": true,
|
|
341
|
+
"coldStart": "moderate",
|
|
342
|
+
"purpose": "reference"
|
|
343
|
+
},
|
|
232
344
|
"description": "React 18 + Hono SSR shop: catalog/detail/search are fully server-rendered from MySQL; cart/checkout/orders hydrate in the browser. Browser-side session (localStorage + Authorization direct to /rest,/auth); order gating enforced by RLS.",
|
|
233
345
|
"entrypoint": "backend/index.ts",
|
|
234
346
|
"mode": "webapp",
|
|
@@ -236,6 +348,7 @@
|
|
|
236
348
|
"README.md",
|
|
237
349
|
"backend/index.ts",
|
|
238
350
|
"db/schema.sql",
|
|
351
|
+
"db/rls.json",
|
|
239
352
|
"frontend/dist/index.html"
|
|
240
353
|
],
|
|
241
354
|
"frontend": {
|
|
@@ -255,13 +368,23 @@
|
|
|
255
368
|
},
|
|
256
369
|
"ssrProbePath": "/shop",
|
|
257
370
|
"nextSteps": [
|
|
258
|
-
"Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir
|
|
371
|
+
"Initialize the database BEFORE deploy: run `pxbaas app db-init <name> --dir <appsDir>`. It reads db/schema.sql (DDL + seed) and db/rls.json (RLS: products public-read; orders and order_items owner-gated by buyer_email = jwt.email), shows the full plan, and applies it only after you authorize (--dry-run to preview, --yes for non-interactive). Works in single-tenant (/pg/query) AND multi-tenant (/api/tenant-ddl with your per-tenant service_role key); the CLI never auto-runs DDL.",
|
|
259
372
|
"Create the admin account (admin@shop.demo) via GoTrue signup if the admin whitelist is needed.",
|
|
260
373
|
"Data-plane base and anon key come from the platform-injected SUPABASE_INTERNAL_URL / SUPABASE_ANON_KEY (edge-runtime MR #30497903); no plaintext key editing is needed. Requires the instance to report react/react-dom in capabilities.ssrLibs."
|
|
261
374
|
]
|
|
262
375
|
},
|
|
263
376
|
{
|
|
264
377
|
"id": "ssr-frameworks",
|
|
378
|
+
"meta": {
|
|
379
|
+
"delivery": "webapp",
|
|
380
|
+
"render": "ssr",
|
|
381
|
+
"frontend": "static-shell",
|
|
382
|
+
"backend": "hono",
|
|
383
|
+
"session": "none",
|
|
384
|
+
"hasDb": false,
|
|
385
|
+
"coldStart": "heavy",
|
|
386
|
+
"purpose": "showcase"
|
|
387
|
+
},
|
|
265
388
|
"description": "Four-framework SSR showcase (React / Vue / Preact / Solid on Hono) rendering the same UI spec server-side, plus /compare. No data plane and no credentials; heaviest cold start of the set (~60s), so warm the tenant sandbox before publish.",
|
|
266
389
|
"entrypoint": "backend/index.ts",
|
|
267
390
|
"mode": "webapp",
|
|
@@ -10,10 +10,27 @@ app.use("*", async (context, next) => {
|
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
app.options("*", (context) => context.body(null, 204));
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
)
|
|
13
|
+
|
|
14
|
+
// Path contract (APP_WORKER_PATH_REWRITE off = current default): the proxy
|
|
15
|
+
// forwards /apps/{mount}/api/<x> to the worker as /<candidateSlug>/<x>, so a
|
|
16
|
+
// literal app.get("/health") never matches at runtime. Normalize the candidate
|
|
17
|
+
// slug prefix away and dispatch on the logical path (same convention as the
|
|
18
|
+
// ssr-shop / ssr-blog / csr-bff templates).
|
|
19
|
+
function norm(path: string): string {
|
|
20
|
+
return path.replace(/^\/app_[^/]+/, "") || path;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
app.get("*", (context) => {
|
|
24
|
+
const p = norm(new URL(context.req.url).pathname);
|
|
25
|
+
if (p === "/health" || p === "/api/health") {
|
|
26
|
+
return context.json({ ok: true, app: "{{SLUG}}" });
|
|
27
|
+
}
|
|
28
|
+
if (p === "/hello" || p === "/api/hello") {
|
|
29
|
+
return context.json({ message: "Hello from {{APP_NAME}}" });
|
|
30
|
+
}
|
|
31
|
+
return context.json({ code: "NOT_FOUND", message: "Route not found", path: p }, 404);
|
|
32
|
+
});
|
|
33
|
+
|
|
17
34
|
app.notFound((context) =>
|
|
18
35
|
context.json({ code: "NOT_FOUND", message: "Route not found" }, 404),
|
|
19
36
|
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Declarative RLS for the SSR blog webapp. Applied by `pxbaas app db-init` (single-tenant AND multi-tenant). table_schema is NEVER declared here: single-tenant binds it to --schema (default public); multi-tenant binds it server-side to the tenant db.",
|
|
3
|
+
"tables": [
|
|
4
|
+
{ "table": "posts_ssr", "rlsEnabled": true, "rlsForced": false }
|
|
5
|
+
],
|
|
6
|
+
"policies": [
|
|
7
|
+
{ "table": "posts_ssr", "name": "posts_ssr_select_anon", "role": "anon", "command": "SELECT", "using": "`status` = 'published'", "check": null, "permissive": true },
|
|
8
|
+
{ "table": "posts_ssr", "name": "posts_ssr_select_auth", "role": "authenticated", "command": "SELECT", "using": "`status` = 'published' OR author_id = {{request.jwt.claim.sub}}", "check": null, "permissive": true },
|
|
9
|
+
{ "table": "posts_ssr", "name": "posts_ssr_insert_auth", "role": "authenticated", "command": "INSERT", "using": null, "check": "author_id = {{request.jwt.claim.sub}}", "permissive": true },
|
|
10
|
+
{ "table": "posts_ssr", "name": "posts_ssr_update_auth", "role": "authenticated", "command": "UPDATE", "using": "author_id = {{request.jwt.claim.sub}}", "check": "author_id = {{request.jwt.claim.sub}}", "permissive": true },
|
|
11
|
+
{ "table": "posts_ssr", "name": "posts_ssr_delete_auth", "role": "authenticated", "command": "DELETE", "using": "author_id = {{request.jwt.claim.sub}}", "check": null, "permissive": true }
|
|
12
|
+
]
|
|
13
|
+
}
|