Skip to content

Commit 67c8a19

Browse files
authored
Merge pull request #43 from sqliteai/docs/postgres-supabase-doc-updates
docs: update documentation for postgres and self-hosted supabase auth/rls
2 parents 2e4e946 + f6b9f33 commit 67c8a19

5 files changed

Lines changed: 264 additions & 42 deletions

File tree

docs/internal/postgres-flyio.md

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -289,17 +289,21 @@ const server = http.createServer(async (req, res) => {
289289
}
290290
291291
// Generate token
292-
// POST /token { "sub": "user-id", "role": "authenticated", "expiresIn": "24h" }
292+
// POST /token { "sub": "user-id", "role": "rls_role", "expiresIn": "24h" }
293293
if (req.method === "POST" && req.url === "/token") {
294294
try {
295295
const body = await parseBody(req);
296296
const sub = body.sub || "anonymous";
297-
const role = body.role || "authenticated";
297+
const role = body.role;
298298
const expiresIn = body.expiresIn || "24h";
299299
const claims = body.claims || {};
300300
301+
if (!role) {
302+
return respond(res, 400, { error: "role is required" });
303+
}
304+
301305
const token = jwt.sign(
302-
{ sub, role, aud: "authenticated", ...claims },
306+
{ sub, role, ...claims },
303307
JWT_SECRET,
304308
{ expiresIn, algorithm: "HS256" }
305309
);
@@ -403,17 +407,21 @@ const server = http.createServer(async (req, res) => {
403407
return res.end(jwksResponse);
404408
}
405409
406-
// POST /token { "sub": "user-id", "role": "authenticated", "expiresIn": "24h" }
410+
// POST /token { "sub": "user-id", "role": "rls_role", "expiresIn": "24h" }
407411
if (req.method === "POST" && req.url === "/token") {
408412
try {
409413
const body = await parseBody(req);
410414
const sub = body.sub || "anonymous";
411-
const role = body.role || "authenticated";
415+
const role = body.role;
412416
const expiresIn = body.expiresIn || "24h";
413417
const claims = body.claims || {};
414418
419+
if (!role) {
420+
return respond(res, 400, { error: "role is required" });
421+
}
422+
415423
const token = jwt.sign(
416-
{ sub, role, aud: "authenticated", iss: ISSUER, ...claims },
424+
{ sub, role, iss: ISSUER, ...claims },
417425
privateKey,
418426
{ expiresIn, algorithm: "RS256", keyid: KID }
419427
);
@@ -486,20 +494,64 @@ curl http://localhost:3002/.well-known/jwks.json
486494

487495
## Step 8: Generate a JWT token
488496

497+
Before generating JWTs for PostgreSQL, create the database role referenced by the token's `role` claim and grant it the permissions CloudSync needs.
498+
499+
### 8a. Create and grant the JWT role
500+
501+
Create the role:
502+
503+
```bash
504+
cd /data/cloudsync-postgres
505+
docker compose exec db psql -U postgres -d postgres -c "CREATE ROLE rls_role NOLOGIN;"
506+
```
507+
508+
Grant schema and table permissions on current and future tables:
509+
510+
```bash
511+
cd /data/cloudsync-postgres
512+
docker compose exec db psql -U postgres -d test_database_1 -c "GRANT USAGE ON SCHEMA public TO rls_role;"
513+
docker compose exec db psql -U postgres -d test_database_1 -c "GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO rls_role;"
514+
docker compose exec db psql -U postgres -d test_database_1 -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO rls_role;"
515+
docker compose exec db psql -U postgres -d test_database_1 -c "GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO rls_role;"
516+
docker compose exec db psql -U postgres -d test_database_1 -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO rls_role;"
517+
```
518+
519+
Allow the connection-string user to switch into that role:
520+
521+
```bash
522+
cd /data/cloudsync-postgres
523+
docker compose exec db psql -U postgres -d postgres -c "GRANT rls_role TO postgres;"
524+
```
525+
526+
Verify:
527+
528+
```bash
529+
cd /data/cloudsync-postgres
530+
docker compose exec db psql -U postgres -d postgres -c "SELECT rolname, rolsuper, rolcanlogin, rolbypassrls FROM pg_roles WHERE rolname = 'rls_role';"
531+
docker compose exec db psql -U postgres -d test_database_1 -c "\\ddp"
532+
```
533+
534+
If you want to test the exact session shape CloudSync uses:
535+
536+
```bash
537+
cd /data/cloudsync-postgres
538+
docker compose exec db psql -U postgres -d test_database_1 -c "BEGIN; SELECT set_config('request.jwt.claims', '{\"sub\":\"test-user-1\",\"role\":\"rls_role\"}', true); SET LOCAL ROLE rls_role; SELECT current_role, current_setting('request.jwt.claims', true); ROLLBACK;"
539+
```
540+
489541
**HS256 (shared secret):**
490542

491543
```bash
492544
curl -X POST http://localhost:3001/token \
493545
-H "Content-Type: application/json" \
494-
-d '{"sub": "user-1", "role": "authenticated"}'
546+
-d '{"sub": "user-1", "role": "rls_role"}'
495547
```
496548

497549
**RS256 (JWKS):**
498550

499551
```bash
500552
curl -X POST http://localhost:3002/token \
501553
-H "Content-Type: application/json" \
502-
-d '{"sub": "user-1", "role": "authenticated"}'
554+
-d '{"sub": "user-1", "role": "rls_role"}'
503555
```
504556

505557
Response (both):

docs/postgresql/quickstarts/postgres.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ On the **Client Integration** tab you'll find your **Database ID** and authentic
129129

130130
The fastest way to test CloudSync without per-user access control — no JWT setup needed.
131131

132+
With API key authentication, CloudSync uses the database role resolved from the API-key-authenticated connection when available; otherwise it falls back to the role from the connection string.
133+
132134
```sql
133135
SELECT cloudsync_network_init('<database-id>');
134136
SELECT cloudsync_network_set_apikey('<username>:<password>');
@@ -139,9 +141,22 @@ SELECT cloudsync_network_sync();
139141

140142
1. Set **Row Level Security** to **Yes, enforce RLS**
141143
2. Under **Authentication (JWT)**, click **Configure authentication** and choose:
142-
- **HMAC Secret (HS256):** Enter your JWT secret (or generate one: `openssl rand -base64 32`)
143-
- **JWKS Issuer Validation:** Enter the issuer base URL from your token's `iss` claim (e.g. `https://your-auth-domain`). CloudSync automatically fetches the JWKS document from `<issuer-url>/.well-known/jwks.json`
144-
3. In your client code:
144+
- **HMAC Secret (HS256):**
145+
- Enter your JWT secret (or generate one: `openssl rand -base64 32`)
146+
- Optionally add **Expected audiences**. When configured, a token's `aud` claim must contain at least one of the configured audience values.
147+
- **JWKS Issuer Validation:**
148+
- Enter the issuer base URL from your token's `iss` claim (for example `https://your-auth-domain`)
149+
- By default, CloudSync uses OIDC discovery: it requests `<issuer>/.well-known/openid-configuration` and reads the returned `jwks_uri`
150+
- Optionally set an **Explicit JWKS URI** to bypass OIDC discovery and use a specific JWKS endpoint directly. This must be a full HTTPS URI.
151+
- Optionally add **Expected audiences**. When configured, a token's `aud` claim must contain at least one of the configured audience values.
152+
3. CloudSync validates JWTs as follows:
153+
- **HS256:** uses the configured JWT secret
154+
- **JWKS:** uses the explicit `jwksUri` when provided; otherwise CloudSync requests `<issuer>/.well-known/openid-configuration` and reads `jwks_uri`
155+
- CloudSync does not fall back directly to `<issuer>/.well-known/jwks.json` when discovery is used
156+
4. For claim details and RLS examples, see:
157+
- [JWT Claims Reference](../reference/jwt-claims.md)
158+
- [RLS Reference](../reference/rls.md)
159+
5. In your client code:
145160
```sql
146161
SELECT cloudsync_network_init('<database-id>');
147162
SELECT cloudsync_network_set_token('<jwt-token>');

docs/postgresql/quickstarts/supabase-self-hosted.md

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ Follow [Supabase's Installing Supabase](https://supabase.com/docs/guides/self-ho
1313
```yaml
1414
db:
1515
# Supabase on PostgreSQL 15
16-
image: sqlitecloud/sqlite-sync-supabase:15.8.1.085
16+
image: sqlitecloud/sqlite-sync-supabase:15
1717
# instead of: public.ecr.aws/supabase/postgres:15.8.1.085
1818

1919
# OR Supabase on PostgreSQL 17
20-
image: sqlitecloud/sqlite-sync-supabase:17.6.1.071
20+
image: sqlitecloud/sqlite-sync-supabase:17
2121
# instead of: public.ecr.aws/supabase/postgres:17.6.1.071
2222
```
2323

24-
Use the tag that matches your Supabase Postgres base image exactly. Convenience tags `sqlitecloud/sqlite-sync-supabase:15` and `sqlitecloud/sqlite-sync-supabase:17` are also published, but the exact Supabase tag is the safest choice.
24+
Use the CloudSync image tag that matches your Supabase PostgreSQL major version. The published major tags `sqlitecloud/sqlite-sync-supabase:15` and `sqlitecloud/sqlite-sync-supabase:17` are the standard choice. Exact Supabase base-image tags may also be published for some releases, but they are optional and not required for normal setup.
2525

2626
### Add the CloudSync Init Script
2727

@@ -59,8 +59,8 @@ Follow [Supabase's Updating](https://supabase.com/docs/guides/self-hosting/docke
5959

6060
```bash
6161
# Update docker-compose.yml to use:
62-
# sqlitecloud/sqlite-sync-supabase:15.8.1.085
63-
# or sqlitecloud/sqlite-sync-supabase:17.6.1.071
62+
# sqlitecloud/sqlite-sync-supabase:15
63+
# or sqlitecloud/sqlite-sync-supabase:17
6464
docker compose pull
6565
docker compose down && docker compose up -d
6666
```
@@ -130,6 +130,8 @@ On the **Client Integration** tab you'll find your **Database ID** and authentic
130130

131131
The fastest way to test CloudSync without per-user access control — no JWT setup needed.
132132

133+
With API key authentication, CloudSync uses the database role resolved from the API-key-authenticated connection when available; otherwise it falls back to the role from the connection string.
134+
133135
```sql
134136
SELECT cloudsync_network_init('<database-id>');
135137
SELECT cloudsync_network_set_apikey('<username>:<password>');
@@ -140,9 +142,22 @@ SELECT cloudsync_network_sync();
140142

141143
1. Set **Row Level Security** to **Yes, enforce RLS**
142144
2. Under **Authentication (JWT)**, click **Configure authentication** and choose:
143-
- **HMAC Secret (HS256):** Enter your `JWT_SECRET` from Supabase's `.env`
144-
- **JWKS Issuer Validation:** Enter the issuer base URL from your token's `iss` claim (e.g. `https://your-auth-domain`). CloudSync automatically fetches the JWKS document from `<issuer-url>/.well-known/jwks.json`
145-
3. In your client code:
145+
- **HMAC Secret (HS256):**
146+
- Enter your `JWT_SECRET` from Supabase's `.env`
147+
- Optionally add **Expected audiences**. When configured, a token's `aud` claim must contain at least one of the configured audience values.
148+
- **JWKS Issuer Validation:**
149+
- Enter the issuer base URL from your token's `iss` claim (for example `https://your-auth-domain`)
150+
- By default, CloudSync uses OIDC discovery: it requests `<issuer>/.well-known/openid-configuration` and reads the returned `jwks_uri`
151+
- Optionally set an **Explicit JWKS URI** to bypass OIDC discovery and use a specific JWKS endpoint directly. This must be a full HTTPS URI.
152+
- Optionally add **Expected audiences**. When configured, a token's `aud` claim must contain at least one of the configured audience values.
153+
3. CloudSync validates JWTs as follows:
154+
- **HS256:** uses the configured JWT secret
155+
- **JWKS:** uses the explicit `jwksUri` when provided; otherwise CloudSync requests `<issuer>/.well-known/openid-configuration` and reads `jwks_uri`
156+
- CloudSync does not fall back directly to `<issuer>/.well-known/jwks.json` when discovery is used
157+
4. For claim details and RLS examples, see:
158+
- [JWT Claims Reference](../reference/jwt-claims.md)
159+
- [RLS Reference](../reference/rls.md)
160+
5. In your client code:
146161
```sql
147162
SELECT cloudsync_network_init('<database-id>');
148163
SELECT cloudsync_network_set_token('<jwt-token>');

0 commit comments

Comments
 (0)