@selvajs/supabase-provider 0.11.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/LICENSE +21 -0
- package/README.md +271 -0
- package/dist/auth/SupabaseAuthProvider.d.ts +81 -0
- package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -0
- package/dist/auth/SupabaseAuthProvider.js +383 -0
- package/dist/auth/SupabaseAuthProvider.js.map +1 -0
- package/dist/auth/__tests__/conformance.test.d.ts +2 -0
- package/dist/auth/__tests__/conformance.test.d.ts.map +1 -0
- package/dist/auth/__tests__/conformance.test.js +76 -0
- package/dist/auth/__tests__/conformance.test.js.map +1 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/data/SupabaseAuditQuery.d.ts +16 -0
- package/dist/data/SupabaseAuditQuery.d.ts.map +1 -0
- package/dist/data/SupabaseAuditQuery.js +83 -0
- package/dist/data/SupabaseAuditQuery.js.map +1 -0
- package/dist/data/SupabaseComputeServerStore.d.ts +26 -0
- package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -0
- package/dist/data/SupabaseComputeServerStore.js +227 -0
- package/dist/data/SupabaseComputeServerStore.js.map +1 -0
- package/dist/data/SupabaseDataProvider.d.ts +55 -0
- package/dist/data/SupabaseDataProvider.d.ts.map +1 -0
- package/dist/data/SupabaseDataProvider.js +114 -0
- package/dist/data/SupabaseDataProvider.js.map +1 -0
- package/dist/data/SupabaseDefinitionStore.d.ts +39 -0
- package/dist/data/SupabaseDefinitionStore.d.ts.map +1 -0
- package/dist/data/SupabaseDefinitionStore.js +419 -0
- package/dist/data/SupabaseDefinitionStore.js.map +1 -0
- package/dist/data/SupabaseEventSink.d.ts +17 -0
- package/dist/data/SupabaseEventSink.d.ts.map +1 -0
- package/dist/data/SupabaseEventSink.js +31 -0
- package/dist/data/SupabaseEventSink.js.map +1 -0
- package/dist/data/SupabaseInviteStore.d.ts +22 -0
- package/dist/data/SupabaseInviteStore.d.ts.map +1 -0
- package/dist/data/SupabaseInviteStore.js +159 -0
- package/dist/data/SupabaseInviteStore.js.map +1 -0
- package/dist/data/SupabaseOrgStore.d.ts +33 -0
- package/dist/data/SupabaseOrgStore.d.ts.map +1 -0
- package/dist/data/SupabaseOrgStore.js +455 -0
- package/dist/data/SupabaseOrgStore.js.map +1 -0
- package/dist/data/SupabaseProjectStore.d.ts +29 -0
- package/dist/data/SupabaseProjectStore.d.ts.map +1 -0
- package/dist/data/SupabaseProjectStore.js +328 -0
- package/dist/data/SupabaseProjectStore.js.map +1 -0
- package/dist/data/SupabaseShareLinkStore.d.ts +26 -0
- package/dist/data/SupabaseShareLinkStore.d.ts.map +1 -0
- package/dist/data/SupabaseShareLinkStore.js +150 -0
- package/dist/data/SupabaseShareLinkStore.js.map +1 -0
- package/dist/data/__tests__/compute-server-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/compute-server-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/compute-server-conformance.test.js +40 -0
- package/dist/data/__tests__/compute-server-conformance.test.js.map +1 -0
- package/dist/data/__tests__/definition-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/definition-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/definition-conformance.test.js +85 -0
- package/dist/data/__tests__/definition-conformance.test.js.map +1 -0
- package/dist/data/__tests__/event-sink-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/event-sink-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/event-sink-conformance.test.js +27 -0
- package/dist/data/__tests__/event-sink-conformance.test.js.map +1 -0
- package/dist/data/__tests__/invite-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/invite-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/invite-conformance.test.js +45 -0
- package/dist/data/__tests__/invite-conformance.test.js.map +1 -0
- package/dist/data/__tests__/org-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/org-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/org-conformance.test.js +29 -0
- package/dist/data/__tests__/org-conformance.test.js.map +1 -0
- package/dist/data/__tests__/project-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/project-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/project-conformance.test.js +57 -0
- package/dist/data/__tests__/project-conformance.test.js.map +1 -0
- package/dist/data/__tests__/share-link-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/share-link-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/share-link-conformance.test.js +88 -0
- package/dist/data/__tests__/share-link-conformance.test.js.map +1 -0
- package/dist/data/__tests__/test-helpers.d.ts +53 -0
- package/dist/data/__tests__/test-helpers.d.ts.map +1 -0
- package/dist/data/__tests__/test-helpers.js +175 -0
- package/dist/data/__tests__/test-helpers.js.map +1 -0
- package/dist/data/client.d.ts +44 -0
- package/dist/data/client.d.ts.map +1 -0
- package/dist/data/client.js +40 -0
- package/dist/data/client.js.map +1 -0
- package/dist/data/index.d.ts +12 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +11 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/pagination.d.ts +30 -0
- package/dist/data/pagination.d.ts.map +1 -0
- package/dist/data/pagination.js +53 -0
- package/dist/data/pagination.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts +25 -0
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts.map +1 -0
- package/dist/permissions/SupabasePlatformPermissionStore.js +153 -0
- package/dist/permissions/SupabasePlatformPermissionStore.js.map +1 -0
- package/dist/permissions/__tests__/conformance.test.d.ts +2 -0
- package/dist/permissions/__tests__/conformance.test.d.ts.map +1 -0
- package/dist/permissions/__tests__/conformance.test.js +27 -0
- package/dist/permissions/__tests__/conformance.test.js.map +1 -0
- package/dist/permissions/index.d.ts +2 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +2 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/storage/SupabaseStorageProvider.d.ts +67 -0
- package/dist/storage/SupabaseStorageProvider.d.ts.map +1 -0
- package/dist/storage/SupabaseStorageProvider.js +136 -0
- package/dist/storage/SupabaseStorageProvider.js.map +1 -0
- package/dist/storage/__tests__/conformance.test.d.ts +2 -0
- package/dist/storage/__tests__/conformance.test.d.ts.map +1 -0
- package/dist/storage/__tests__/conformance.test.js +149 -0
- package/dist/storage/__tests__/conformance.test.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/userProfile/SupabaseUserProfileProvider.d.ts +16 -0
- package/dist/userProfile/SupabaseUserProfileProvider.d.ts.map +1 -0
- package/dist/userProfile/SupabaseUserProfileProvider.js +148 -0
- package/dist/userProfile/SupabaseUserProfileProvider.js.map +1 -0
- package/dist/userProfile/__tests__/conformance.test.d.ts +2 -0
- package/dist/userProfile/__tests__/conformance.test.d.ts.map +1 -0
- package/dist/userProfile/__tests__/conformance.test.js +38 -0
- package/dist/userProfile/__tests__/conformance.test.js.map +1 -0
- package/dist/userProfile/index.d.ts +2 -0
- package/dist/userProfile/index.d.ts.map +1 -0
- package/dist/userProfile/index.js +2 -0
- package/dist/userProfile/index.js.map +1 -0
- package/package.json +75 -0
- package/src/auth/SupabaseAuthProvider.ts +464 -0
- package/src/auth/__tests__/conformance.test.ts +78 -0
- package/src/auth/index.ts +2 -0
- package/src/data/SupabaseAuditQuery.ts +103 -0
- package/src/data/SupabaseComputeServerStore.ts +262 -0
- package/src/data/SupabaseDataProvider.ts +125 -0
- package/src/data/SupabaseDefinitionStore.ts +504 -0
- package/src/data/SupabaseEventSink.ts +30 -0
- package/src/data/SupabaseInviteStore.ts +183 -0
- package/src/data/SupabaseOrgStore.ts +547 -0
- package/src/data/SupabaseProjectStore.ts +414 -0
- package/src/data/SupabaseShareLinkStore.ts +191 -0
- package/src/data/__tests__/compute-server-conformance.test.ts +40 -0
- package/src/data/__tests__/definition-conformance.test.ts +89 -0
- package/src/data/__tests__/event-sink-conformance.test.ts +32 -0
- package/src/data/__tests__/invite-conformance.test.ts +46 -0
- package/src/data/__tests__/org-conformance.test.ts +30 -0
- package/src/data/__tests__/project-conformance.test.ts +57 -0
- package/src/data/__tests__/share-link-conformance.test.ts +87 -0
- package/src/data/__tests__/test-helpers.ts +186 -0
- package/src/data/client.ts +86 -0
- package/src/data/index.ts +11 -0
- package/src/data/pagination.ts +68 -0
- package/src/index.ts +23 -0
- package/src/permissions/SupabasePlatformPermissionStore.ts +161 -0
- package/src/permissions/__tests__/conformance.test.ts +28 -0
- package/src/permissions/index.ts +1 -0
- package/src/storage/SupabaseStorageProvider.ts +177 -0
- package/src/storage/__tests__/conformance.test.ts +152 -0
- package/src/storage/index.ts +2 -0
- package/src/userProfile/SupabaseUserProfileProvider.ts +177 -0
- package/src/userProfile/__tests__/conformance.test.ts +39 -0
- package/src/userProfile/index.ts +1 -0
- package/supabase/.branches/_current_branch +1 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/config.toml +68 -0
- package/supabase/migrations/0001_initial.sql +1084 -0
- package/supabase/seed.sql +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Selva FelixBrunold VektorNode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# @selvajs/supabase-provider
|
|
2
|
+
|
|
3
|
+
Supabase (Auth + Postgres + Storage) implementation of the `@selvajs/platform` interfaces.
|
|
4
|
+
|
|
5
|
+
Runs the Selva backend against **either** a managed Supabase project (production) or a local Supabase CLI stack in Docker (development). Same code — different `SUPABASE_URL` and keys.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of contents
|
|
10
|
+
|
|
11
|
+
- [Quick start](#quick-start)
|
|
12
|
+
- [Environment variables](#environment-variables)
|
|
13
|
+
- [Wiring into `selva.config.ts`](#wiring-into-selvaconfigts)
|
|
14
|
+
- [Applying the schema](#applying-the-schema)
|
|
15
|
+
- [Development — local Supabase stack](#development--local-supabase-stack)
|
|
16
|
+
- [Production — hosted Supabase project](#production--hosted-supabase-project)
|
|
17
|
+
- [Running the conformance tests](#running-the-conformance-tests)
|
|
18
|
+
- [Architecture notes](#architecture-notes)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
1. Install the package in the workspace (already listed in `pnpm-workspace.yaml`).
|
|
25
|
+
2. Provision Supabase — either local (`npx supabase start`) or hosted (supabase.com).
|
|
26
|
+
3. Apply the migrations from `packages/providers/supabase/supabase/migrations/` (the local CLI does this automatically on `db reset`).
|
|
27
|
+
4. Copy the three env vars into your selva app `.env` (see below).
|
|
28
|
+
5. Edit [`selva.config.ts`](../../../selva.config.ts) at the repo root to swap the local provider for the Supabase provider.
|
|
29
|
+
6. `pnpm dev` — the selva app now reads and writes from Supabase.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Environment variables
|
|
34
|
+
|
|
35
|
+
All env vars are documented in [`packages/selva/.env.example`](../../selva/.env.example) — copy that file to `.env` and edit it. The Supabase provider needs `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and `SUPABASE_SERVICE_ROLE_KEY`; the optional bucket / private-URL / signup overrides are also listed there.
|
|
36
|
+
|
|
37
|
+
`SELVA_HMAC_KEY` is not used for sessions by the Supabase provider — those are Supabase JWTs. It's still consulted as the fallback secret for share-link / invite token hashing if `SHARE_LINK_SECRET` / `INVITE_TOKEN_SECRET` are unset. `SELVA_AT_REST_KEY` is local-provider-only and is ignored. Both are harmless if left set.
|
|
38
|
+
|
|
39
|
+
Rhino.Compute URL + API key are configured in `/admin/compute` and persisted in the `compute_config` table — unchanged by the provider choice.
|
|
40
|
+
|
|
41
|
+
### Finding the keys
|
|
42
|
+
|
|
43
|
+
**Local (`npx supabase status`):**
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
╭──────────────────────────────────────────────────╮
|
|
47
|
+
│ 🔑 Authentication Keys │
|
|
48
|
+
├─────────────┬────────────────────────────────────┤
|
|
49
|
+
│ Publishable │ sb_publishable_... │ ← SUPABASE_ANON_KEY
|
|
50
|
+
│ Secret │ sb_secret_... │ ← SUPABASE_SERVICE_ROLE_KEY
|
|
51
|
+
╰─────────────┴────────────────────────────────────╯
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Hosted:** Supabase Dashboard → **Project Settings** → **API**.
|
|
55
|
+
|
|
56
|
+
> **The S3-compat keys under the "Storage (S3)" section are NOT what you want.** Those are for external S3 tooling (rclone, etc.), not for `@supabase/supabase-js`.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Wiring into `selva.config.ts`
|
|
61
|
+
|
|
62
|
+
The repo's [`selva.config.ts`](../../selva.config.ts) is the single DI point — it picks which provider backs every interface. Switch between providers by changing imports:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { defineConfig } from '@selvajs/platform';
|
|
66
|
+
import * as supa from '@selvajs/supabase-provider';
|
|
67
|
+
|
|
68
|
+
export default defineConfig((env) => ({
|
|
69
|
+
tenancy: 'single' as const,
|
|
70
|
+
flags: {
|
|
71
|
+
ALLOW_CROSS_ORG_PUBLIC: false,
|
|
72
|
+
ALLOW_ORG_COMPUTE_OVERRIDE: false,
|
|
73
|
+
ALLOW_ORG_CREATION: false
|
|
74
|
+
},
|
|
75
|
+
auth: supa.SupabaseAuthProvider.fromEnv(env),
|
|
76
|
+
data: supa.SupabaseDataProvider.fromEnv(env),
|
|
77
|
+
storage: supa.SupabaseStorageProvider.fromEnv(env)
|
|
78
|
+
}));
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
To switch back to the local provider, swap the import and the three provider lines. See the commented example in [`selva.config.ts`](../../selva.config.ts).
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Applying the schema
|
|
86
|
+
|
|
87
|
+
The `supabase/migrations/` directory holds a single bootstrap file that installs the entire schema in one pass:
|
|
88
|
+
|
|
89
|
+
| File | What it installs |
|
|
90
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `0001_initial.sql` | Everything: `user_profiles` (+ auto-seed trigger), `orgs` / `org_members` / `projects` / `project_members` (+ RLS helpers), `definitions` / `definition_versions` (+ deletion-protection FKs and the atomic `increment_run_count` RPC), `invites` (+ `get_invite_by_token` RPC), `compute_servers` (+ per-org/instance defaults, spec §3 BYO compute), `share_links` (+ `try_increment_share_link_solve_count` RPC, spec §7), and the `selva-public` / `selva-private` storage policies. |
|
|
92
|
+
|
|
93
|
+
Future schema changes go in numbered files (`0002_…`, `0003_…`).
|
|
94
|
+
|
|
95
|
+
The `supabase/seed.sql` file creates the two storage buckets (`selva-public`, `selva-private`).
|
|
96
|
+
|
|
97
|
+
**Local:** `npx supabase db reset` applies everything on a fresh DB.
|
|
98
|
+
|
|
99
|
+
**Hosted:** link the CLI to your project, then push:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cd packages/providers/supabase
|
|
103
|
+
|
|
104
|
+
# One-time link. Reads the project-ref from Supabase Dashboard → Project Settings → General.
|
|
105
|
+
npx supabase link --project-ref <your-project-ref>
|
|
106
|
+
|
|
107
|
+
# Review what will be applied.
|
|
108
|
+
npx supabase db diff
|
|
109
|
+
|
|
110
|
+
# Apply migrations + seed.
|
|
111
|
+
npx supabase db push
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Alternatively you can copy each `.sql` file into Supabase Dashboard → **SQL Editor** and run them in order. The CLI path is strongly preferred — it's idempotent and version-controlled.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Development — local Supabase stack
|
|
119
|
+
|
|
120
|
+
Prerequisite: **Docker Desktop running.** First run pulls ~1 GB of images.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cd packages/providers/supabase
|
|
124
|
+
npx supabase start
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
This spins up Postgres (54322), GoTrue/Auth (54321), Storage, Studio (54323), and Mailpit (54324 — fake SMTP inbox for auth emails). Migrations and the bucket seed apply automatically.
|
|
128
|
+
|
|
129
|
+
Copy the printed **Publishable** and **Secret** keys into your `.env` at the selva app or repo root:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# selva app .env
|
|
133
|
+
SUPABASE_URL=http://127.0.0.1:54321
|
|
134
|
+
SUPABASE_ANON_KEY=sb_publishable_...
|
|
135
|
+
SUPABASE_SERVICE_ROLE_KEY=sb_secret_...
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Rhino.Compute server URL + API key are registered in `/admin/compute` after first boot.
|
|
139
|
+
|
|
140
|
+
Switch `selva.config.ts` to Supabase (snippet above), then:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pnpm dev
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Useful commands:**
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npx supabase status # show URL + keys again
|
|
150
|
+
npx supabase db reset # wipe DB + re-run migrations + seed
|
|
151
|
+
npx supabase stop # stop containers (keeps volumes)
|
|
152
|
+
npx supabase stop --no-backup # stop + wipe volumes
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Studio is a full admin UI at `http://127.0.0.1:54323`. Use it to inspect tables, rows, and RLS policies while developing.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Production — hosted Supabase project
|
|
160
|
+
|
|
161
|
+
1. Create a project on [supabase.com](https://supabase.com). Choose a region close to your selva app deployment.
|
|
162
|
+
2. Dashboard → **Project Settings** → **API**. Copy the **Project URL**, **Publishable** key, and **Secret** key.
|
|
163
|
+
3. Apply migrations via the CLI:
|
|
164
|
+
```bash
|
|
165
|
+
cd packages/providers/supabase
|
|
166
|
+
npx supabase link --project-ref <your-project-ref>
|
|
167
|
+
npx supabase db push
|
|
168
|
+
```
|
|
169
|
+
4. Create the two storage buckets by running `supabase/seed.sql` in the Dashboard → **SQL Editor** (the CLI's `db push` doesn't run the seed on a hosted project — it's dev-only).
|
|
170
|
+
5. Set the env vars on your selva app host (Vercel, Fly.io, Docker, etc.):
|
|
171
|
+
```
|
|
172
|
+
SUPABASE_URL=https://<project-ref>.supabase.co
|
|
173
|
+
SUPABASE_ANON_KEY=sb_publishable_...
|
|
174
|
+
SUPABASE_SERVICE_ROLE_KEY=sb_secret_...
|
|
175
|
+
```
|
|
176
|
+
After deploying, register your Rhino.Compute server URL (+ optional API key) via `/admin/compute`.
|
|
177
|
+
6. Deploy the selva app with `selva.config.ts` wired to `@selvajs/supabase-provider`.
|
|
178
|
+
7. Bootstrap the first user:
|
|
179
|
+
- Open `/setup` once — creates the first admin with `instance_admin`.
|
|
180
|
+
- Or manually: Dashboard → **Authentication** → **Add user**, then in the SQL Editor: `UPDATE public.user_profiles SET platform_permissions = ARRAY['instance_admin']::text[] WHERE user_id = '<uuid>';`
|
|
181
|
+
|
|
182
|
+
### Operational checklist
|
|
183
|
+
|
|
184
|
+
- **Backups.** Supabase handles daily backups on paid plans; verify restore works before relying on it.
|
|
185
|
+
- **Rotate keys** if the Secret key is ever logged or committed. Dashboard → **Project Settings** → **API** → **Reset service_role key**.
|
|
186
|
+
- **Row-level security is on for every table.** Disabling it on any table silently breaks the tenant boundary.
|
|
187
|
+
- **Email sender.** Dashboard → **Authentication** → **Email templates** — configure an SMTP provider before going live, otherwise password-reset emails route through Supabase's rate-limited dev sender.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Running the conformance tests
|
|
192
|
+
|
|
193
|
+
The package ships a full conformance suite that runs against a **live** Supabase stack. It proves the provider implements every interface the same way the local provider does.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
cd packages/providers/supabase
|
|
197
|
+
|
|
198
|
+
# 1. Make sure the stack is running.
|
|
199
|
+
npx supabase start
|
|
200
|
+
|
|
201
|
+
# 2. Create .env.test with the same three vars.
|
|
202
|
+
# (This is separate from your app's .env — it's gitignored and used only by tests.)
|
|
203
|
+
cat > .env.test <<EOF
|
|
204
|
+
SUPABASE_URL=http://127.0.0.1:54321
|
|
205
|
+
SUPABASE_ANON_KEY=<publishable key>
|
|
206
|
+
SUPABASE_SERVICE_ROLE_KEY=<secret key>
|
|
207
|
+
EOF
|
|
208
|
+
|
|
209
|
+
# 3. Run.
|
|
210
|
+
pnpm test
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
When `.env.test` is missing the suite skips with a single explanatory test.
|
|
214
|
+
|
|
215
|
+
The suite wipes every table and every auth user between tests — **do not point it at a production project.**
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Architecture notes
|
|
220
|
+
|
|
221
|
+
### Storage
|
|
222
|
+
|
|
223
|
+
Two buckets:
|
|
224
|
+
|
|
225
|
+
- **`selva-public`** (public) — covers, archives. `getPublicUrl` returns the direct CDN URL.
|
|
226
|
+
- **`selva-private`** (private) — `.gh` / `.ghx` files. `getPublicUrl` returns `/api/files/{path}` which the selva app's route handler must proxy after an auth check.
|
|
227
|
+
|
|
228
|
+
Images are transcoded to WebP (1200px cap, quality 85) via the shared `transcodeImageIfNeeded` helper from `@selvajs/platform/storage`. Same bytes out of both providers.
|
|
229
|
+
|
|
230
|
+
### Auth
|
|
231
|
+
|
|
232
|
+
`SupabaseAuthProvider.verifyLogin` wraps `supabase.auth.signInWithPassword`, returning the JWT directly as `sessionToken`. `verifyToken` calls `supabase.auth.getUser(token)` — GoTrue validates the signature for us. Platform permissions merge in from `user_profiles.platform_permissions` (auto-created by the signup trigger).
|
|
233
|
+
|
|
234
|
+
MFA methods on `IPasswordAuth` are currently undefined — MFA is deferred. If a user enrolls a factor via the Supabase dashboard, `signInWithPassword` returns an AAL1 session and routes that gate on AAL2 would fail; no route does today.
|
|
235
|
+
|
|
236
|
+
### Data + RLS
|
|
237
|
+
|
|
238
|
+
Every store goes through `ClientBundle.forRequest(ctx)`:
|
|
239
|
+
|
|
240
|
+
- `ctx.system` → service-role client (bypasses RLS)
|
|
241
|
+
- `ctx.adapterContext.sessionToken` → anon client with `Authorization: Bearer <jwt>` (RLS enforces per-user visibility)
|
|
242
|
+
- neither → service-role fallback (admin paths)
|
|
243
|
+
|
|
244
|
+
Helper SQL functions (`is_instance_admin`, `is_org_member`, `visible_project`, `has_org_permission`) are `SECURITY DEFINER` so they evaluate without looping through RLS.
|
|
245
|
+
|
|
246
|
+
### User profile
|
|
247
|
+
|
|
248
|
+
`user_profiles` is 1:1 with `auth.users`. The `handle_new_auth_user` trigger inserts a profile row on every signup so the provider never has to fence creation. `starDefinition` / `unstarDefinition` / `recordRun` do read-modify-write — OK for the expected scale; a SECURITY DEFINER RPC would close the race if concurrent starring ever matters.
|
|
249
|
+
|
|
250
|
+
### Atomic improvements over the local provider
|
|
251
|
+
|
|
252
|
+
- **`incrementRunCount`** uses a SQL function (`UPDATE … SET run_count = run_count + 1`) — atomic. The local provider does read-modify-write and can lose bumps under concurrent solves.
|
|
253
|
+
|
|
254
|
+
### Findings
|
|
255
|
+
|
|
256
|
+
Every abstraction-pressure point hit during implementation is logged in [FINDINGS.md](./FINDINGS.md). Short version: six items, none requiring platform contract changes beyond the Phase 0 set.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Status
|
|
261
|
+
|
|
262
|
+
Every interface from `@selvajs/platform` is implemented and exercised by the shared conformance suites against a live local Supabase stack:
|
|
263
|
+
|
|
264
|
+
- `SupabaseAuthProvider` (incl. MFA-ready `LoginResult`)
|
|
265
|
+
- `SupabaseStorageProvider`
|
|
266
|
+
- `SupabaseOrgStore`, `SupabaseProjectStore`, `SupabaseDefinitionStore` (atomic `incrementRunCount`), `SupabaseInviteStore`, `SupabaseShareLinkStore`, `SupabaseComputeServerStore`
|
|
267
|
+
- `SupabaseUserProfileProvider`
|
|
268
|
+
- `SupabasePlatformPermissionStore`
|
|
269
|
+
- `SupabaseDataProvider` — composition of the data stores
|
|
270
|
+
|
|
271
|
+
Run them with `pnpm test` (see [Running the conformance tests](#running-the-conformance-tests)).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { IAuthProvider, IEmailLinkAuth, IOAuthAuth, IPasswordAuth, AuthUser, UserManagementResult, ListOptions, Page } from '@selvajs/platform';
|
|
2
|
+
/**
|
|
3
|
+
* Auth backed by Supabase Auth (GoTrue).
|
|
4
|
+
*
|
|
5
|
+
* Identity + session lifecycle are fully delegated:
|
|
6
|
+
* - `verifyLogin` wraps `auth.signInWithPassword` — returns the access_token
|
|
7
|
+
* straight from Supabase as the session token.
|
|
8
|
+
* - `verifyToken` calls `auth.getUser(token)` against an anon client scoped
|
|
9
|
+
* to that bearer; GoTrue validates the JWT for us.
|
|
10
|
+
* - User management uses `auth.admin.*` with the service-role client.
|
|
11
|
+
*
|
|
12
|
+
* Identity-only — platform permissions live on `IPlatformPermissionStore`
|
|
13
|
+
* (`SupabasePlatformPermissionStore` reads `user_profiles.platform_permissions`)
|
|
14
|
+
* and profile state on `IUserProfileStore`.
|
|
15
|
+
*/
|
|
16
|
+
export interface SupabaseAuthProviderConfig {
|
|
17
|
+
supabaseUrl: string;
|
|
18
|
+
anonKey: string;
|
|
19
|
+
serviceRoleKey: string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether self-service signup is allowed via `passwordAuth.registerUser`.
|
|
22
|
+
* Default false — production deployments start with invite-only.
|
|
23
|
+
*/
|
|
24
|
+
enableSelfSignup?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* OAuth providers enabled in the Supabase dashboard (lowercased:
|
|
27
|
+
* "google", "github", …). Surfaced via `oauth.listProviders()` so the
|
|
28
|
+
* driving layer doesn't read provider-specific env vars itself. Defaults
|
|
29
|
+
* to an empty list.
|
|
30
|
+
*/
|
|
31
|
+
oauthProviders?: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* Allow `emailLink.sendMagicLink` to create new users on first request.
|
|
34
|
+
* Default true — matches Supabase's `signInWithOtp` default and gives
|
|
35
|
+
* fresh installs a working signup path. Set false for invite-only
|
|
36
|
+
* deployments; `sendMagicLink` for an unknown email returns
|
|
37
|
+
* `{ ok: false, reason: 'signup_disabled' }`.
|
|
38
|
+
*/
|
|
39
|
+
allowEmailLinkSignup?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare class SupabaseAuthProvider implements IAuthProvider {
|
|
42
|
+
readonly name = "Supabase Auth";
|
|
43
|
+
readonly passwordAuth: IPasswordAuth;
|
|
44
|
+
readonly oauth: IOAuthAuth;
|
|
45
|
+
readonly emailLink: IEmailLinkAuth;
|
|
46
|
+
private readonly admin;
|
|
47
|
+
private readonly anon;
|
|
48
|
+
private readonly anonKey;
|
|
49
|
+
private readonly supabaseUrl;
|
|
50
|
+
constructor(config: SupabaseAuthProviderConfig);
|
|
51
|
+
static fromEnv(env: Record<string, string | undefined>): SupabaseAuthProvider;
|
|
52
|
+
verifyToken(token: string): Promise<AuthUser | null>;
|
|
53
|
+
getUser(id: string): Promise<AuthUser | null>;
|
|
54
|
+
listUsers(opts?: ListOptions): Promise<Page<AuthUser> | null>;
|
|
55
|
+
createUser(email: string): Promise<AuthUser>;
|
|
56
|
+
deleteUser(id: string): Promise<UserManagementResult>;
|
|
57
|
+
disableUser(id: string): Promise<UserManagementResult>;
|
|
58
|
+
touchLastLogin(id: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Sign in via GoTrue. Returns the access token string and the hydrated
|
|
61
|
+
* user. Called by `SupabasePasswordAuth.verifyLogin` — kept on the
|
|
62
|
+
* provider class so it has access to the hydrate helper without plumbing.
|
|
63
|
+
*/
|
|
64
|
+
private signIn;
|
|
65
|
+
/**
|
|
66
|
+
* Map a GoTrue `User` to our identity-only `AuthUser`. Platform permissions
|
|
67
|
+
* live on `IPlatformPermissionStore` and profile fields (displayName,
|
|
68
|
+
* starred, recentRuns) on `IUserProfileStore` — neither is in AuthUser.
|
|
69
|
+
*/
|
|
70
|
+
private hydrate;
|
|
71
|
+
/**
|
|
72
|
+
* Expose the anon URL + key so other code (e.g. the same process's
|
|
73
|
+
* storage provider or per-request client factories) can build user-scoped
|
|
74
|
+
* clients without requiring a second config read.
|
|
75
|
+
*/
|
|
76
|
+
getAnonClientConfig(): {
|
|
77
|
+
supabaseUrl: string;
|
|
78
|
+
anonKey: string;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=SupabaseAuthProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupabaseAuthProvider.d.ts","sourceRoot":"","sources":["../../src/auth/SupabaseAuthProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EACb,cAAc,EACd,UAAU,EACV,aAAa,EACb,QAAQ,EAER,oBAAoB,EACpB,WAAW,EACX,IAAI,EACJ,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,0BAA0B;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,qBAAa,oBAAqB,YAAW,aAAa;IACzD,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IAEnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,MAAM,EAAE,0BAA0B;IA4B9C,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,oBAAoB;IAuBvE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAWpD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAM7C,SAAS,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IAkB7D,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAa5C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAcrD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAoBtD,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB/C;;;;OAIG;YACW,MAAM;IAUpB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAWf;;;;OAIG;IACH,mBAAmB,IAAI;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAG/D"}
|