Skip to content

Commit d911ddf

Browse files
committed
feat: add auth verification for control plane and workers (#444)
# Add authentication verification for Control Plane and Workers This PR adds a comprehensive authentication plan for pgflow's Control Plane and Worker functions, ensuring sensitive operations are properly protected. Key changes include: - Added `PLAN_auth-verification.md` detailing the authentication requirements and implementation approach - Added `PLAN_workers-start-command.md` for a future CLI command to start workers with proper authentication - Updated CLI to use `--secret-key` instead of `--publishable-key` for the compile command - Modified tests to reflect the authentication changes - Updated documentation to clarify authentication requirements The authentication model requires a Supabase service_role/secret key to protect sensitive operations like flow enumeration, compilation, and worker execution. This aligns with Supabase's recommended practices for server-side operations. For local development, the default anon key is used, while production deployments will require setting up a proper secret key as an Edge Function environment variable.
1 parent 6c96b03 commit d911ddf

File tree

16 files changed

+420
-50
lines changed

16 files changed

+420
-50
lines changed

.changeset/auto-compile-http-control.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,24 @@
33
'@pgflow/edge-worker': minor
44
---
55

6-
Add auto-compilation flow and HTTP control plane server for edge worker
6+
## @pgflow/edge-worker
7+
8+
- Add ControlPlane HTTP server for flow compilation (`ControlPlane.serve()`)
9+
- Support namespace imports for flow registration
10+
11+
## @pgflow/cli
12+
13+
### Breaking Changes
14+
15+
- `pgflow compile` now takes flow slug instead of file path
16+
- Compilation happens via HTTP to ControlPlane (local Deno no longer required)
17+
- Deprecate `--deno-json` flag (will be removed in v1.0)
18+
19+
### New Features
20+
21+
- `pgflow install` now scaffolds complete setup:
22+
- Creates `supabase/flows/` with example GreetUser flow
23+
- Creates `supabase/functions/pgflow/` Control Plane
24+
- Creates `supabase/functions/greet-user-worker/` example worker
25+
- Add `--control-plane-url` option to compile command
26+
- Dynamic version injection in generated deno.json files

PLAN_auth-verification.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# PLAN: Auth Verification for Control Plane & Workers
2+
3+
**Created**: 2025-11-28
4+
**Status**: Future Work
5+
**Related**: PLAN_control-plane-edge-worker-compilation.md
6+
7+
---
8+
9+
## Goal
10+
11+
Control plane and workers verify the secret key to protect sensitive operations.
12+
13+
## Supabase Key Context
14+
15+
Supabase is transitioning from JWT-based keys to new API keys:
16+
17+
| Old Name | New Name | Format | Use Case |
18+
|----------|----------|--------|----------|
19+
| `anon` | publishable key | `sb_publishable_...` | Client-side (RLS-restricted) |
20+
| `service_role` | secret key | `sb_secret_...` | Server-side (bypasses RLS) |
21+
22+
**Important notes:**
23+
- **CLI/local dev** (`supabase start`): Only has legacy `anon`/`service_role` keys
24+
- **Hosted Supabase**: Has both old and new key formats
25+
- **Edge Functions**: Have `SUPABASE_SERVICE_ROLE_KEY` env var available by default
26+
- **Timeline**: Legacy keys deprecated late 2026
27+
- **Reference**: https://supabase.com/docs/guides/api/api-keys
28+
29+
**Supabase docs explicitly list "periodic jobs, queue processors, topic subscribers" as use cases for secret/service_role keys.**
30+
31+
---
32+
33+
## Why Auth is Required
34+
35+
Both pgflow functions need **service_role/secret key** protection:
36+
37+
| Function | Why Secret Key Required |
38+
|----------|------------------------|
39+
| **Control Plane** (`/pgflow/`) | Enumerate flows, compile flows - dangerous operations |
40+
| **Workers** (`/greet-user-worker/`) | Full DB access, execute arbitrary handlers |
41+
42+
---
43+
44+
## Important: New Keys Require Manual Verification
45+
46+
From Supabase docs:
47+
> "Edge Functions only support JWT verification via the anon and service_role JWT-based API keys. You will need to use the --no-verify-jwt option when using publishable and secret keys. Implement your own apikey-header authorization logic inside the Edge Function code itself."
48+
49+
**Key is sent in `apikey` header (NOT Authorization)** - new secret keys are not JWTs.
50+
51+
---
52+
53+
## Implementation
54+
55+
### Code Changes
56+
57+
**Files to modify:**
58+
- `pkgs/edge-worker/src/ControlPlane.ts` (or equivalent)
59+
- `pkgs/edge-worker/src/EdgeWorker.ts` (or equivalent)
60+
61+
**Implementation:**
62+
```typescript
63+
// At the start of request handling
64+
const apiKey = req.headers.get('apikey');
65+
const expectedKey = Deno.env.get('PGFLOW_SECRET_KEY'); // custom env var set by user
66+
67+
if (!apiKey || apiKey !== expectedKey) {
68+
return new Response(JSON.stringify({ error: 'Invalid or missing secret key' }), {
69+
status: 401,
70+
});
71+
}
72+
```
73+
74+
### User Setup Required
75+
76+
For production with new secret keys:
77+
1. Create secret key in Supabase dashboard
78+
2. Store as Edge Function secret: `PGFLOW_SECRET_KEY`
79+
3. Pass same key to CLI: `pgflow compile --secret-key <key>`
80+
81+
### Documentation
82+
83+
**Add to docs:**
84+
- Auth model explanation (both functions need service_role key)
85+
- How to pass apikey header
86+
- Production deployment considerations
87+
88+
**Example:**
89+
```bash
90+
curl http://localhost:54321/functions/v1/greet-user-worker \
91+
-H "apikey: $PGFLOW_SECRET_KEY"
92+
```
93+
94+
---
95+
96+
## When to Implement
97+
98+
This should be implemented when:
99+
- Flow enumeration is added to control plane
100+
- Other sensitive operations are exposed
101+
- Before production deployments become common
102+
103+
---
104+
105+
## References
106+
107+
- Supabase API Keys docs: https://supabase.com/docs/guides/api/api-keys
108+
- Edge Function secrets: https://supabase.com/docs/guides/functions/secrets

PLAN_control-plane-edge-worker-compilation.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ class EdgeWorker {
193193
- Advisory locks in ControlPlane
194194
- Proper error messages with actionable fixes
195195
- Deployment mode detection (dev/prod)
196+
- **Auth verification for ControlPlane endpoints** (see PLAN_auth-verification.md)
197+
- Verify `apikey` header against `PGFLOW_SECRET_KEY` env var
198+
- Required for production deployments
199+
- Protects flow enumeration and compilation endpoints
196200

197201
---
198202

PLAN_workers-start-command.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# PLAN: pgflow workers start CLI Command
2+
3+
**Created**: 2025-11-28
4+
**Status**: Future Work
5+
**Related**: PLAN_auth-verification.md
6+
7+
---
8+
9+
## Goal
10+
11+
Provide a CLI command to start workers with proper authentication, eliminating the need for manual curl commands.
12+
13+
---
14+
15+
## Command Design
16+
17+
```bash
18+
# Uses SUPABASE_SERVICE_ROLE_KEY from env or .env
19+
pgflow workers start greet-user-worker
20+
21+
# Or explicit key
22+
pgflow workers start greet-user-worker --secret-key <key>
23+
24+
# Multiple workers
25+
pgflow workers start greet-user-worker payment-worker
26+
```
27+
28+
---
29+
30+
## Behavior
31+
32+
### Key Resolution
33+
34+
1. Check `--secret-key` flag
35+
2. Check `SUPABASE_SERVICE_ROLE_KEY` env var
36+
3. Check `.env` file in current directory
37+
4. Check `supabase/.env` file
38+
5. Error if no key found
39+
40+
### Request Handling
41+
42+
- Call worker endpoint with proper `apikey` header
43+
- Handle HTTP responses and errors
44+
- Display worker status/logs
45+
- Handle reconnection on disconnect
46+
47+
### URL Resolution
48+
49+
- Default: `http://localhost:54321/functions/v1/<worker-name>`
50+
- Support `--url` flag for custom endpoints
51+
- Support `SUPABASE_URL` env var for hosted
52+
53+
---
54+
55+
## Implementation
56+
57+
### Files to Create/Modify
58+
59+
- `pkgs/cli/src/commands/workers/start.ts` - New command
60+
- `pkgs/cli/src/commands/workers/index.ts` - Command group
61+
62+
### Command Structure
63+
64+
```typescript
65+
import { Command } from 'commander';
66+
67+
export const workersStartCommand = new Command('start')
68+
.description('Start a worker to process tasks')
69+
.argument('<worker...>', 'Worker function name(s)')
70+
.option('--secret-key <key>', 'Service role / secret key')
71+
.option('--url <url>', 'Supabase functions URL')
72+
.action(async (workers, options) => {
73+
const secretKey = resolveSecretKey(options);
74+
75+
for (const worker of workers) {
76+
await startWorker(worker, secretKey, options.url);
77+
}
78+
});
79+
80+
async function startWorker(name: string, secretKey: string, baseUrl?: string) {
81+
const url = `${baseUrl || getDefaultUrl()}/functions/v1/${name}`;
82+
83+
const response = await fetch(url, {
84+
headers: {
85+
'apikey': secretKey,
86+
},
87+
});
88+
89+
// Handle response, reconnection, etc.
90+
}
91+
```
92+
93+
### Key Resolution Function
94+
95+
```typescript
96+
function resolveSecretKey(options: { secretKey?: string }): string {
97+
// 1. CLI flag
98+
if (options.secretKey) return options.secretKey;
99+
100+
// 2. Env var
101+
if (process.env.SUPABASE_SERVICE_ROLE_KEY) {
102+
return process.env.SUPABASE_SERVICE_ROLE_KEY;
103+
}
104+
105+
// 3. .env file
106+
const envPath = findEnvFile();
107+
if (envPath) {
108+
const env = dotenv.parse(fs.readFileSync(envPath));
109+
if (env.SUPABASE_SERVICE_ROLE_KEY) {
110+
return env.SUPABASE_SERVICE_ROLE_KEY;
111+
}
112+
}
113+
114+
throw new Error('No secret key found. Provide --secret-key or set SUPABASE_SERVICE_ROLE_KEY');
115+
}
116+
```
117+
118+
---
119+
120+
## UX Considerations
121+
122+
### Output Format
123+
124+
```
125+
$ pgflow workers start greet-user-worker
126+
127+
Starting worker: greet-user-worker
128+
URL: http://localhost:54321/functions/v1/greet-user-worker
129+
Auth: Using SUPABASE_SERVICE_ROLE_KEY from environment
130+
131+
Worker started successfully. Press Ctrl+C to stop.
132+
```
133+
134+
### Error Messages
135+
136+
```
137+
$ pgflow workers start greet-user-worker
138+
139+
Error: No secret key found.
140+
141+
To fix this, either:
142+
1. Set SUPABASE_SERVICE_ROLE_KEY environment variable
143+
2. Add SUPABASE_SERVICE_ROLE_KEY to your .env file
144+
3. Use --secret-key flag: pgflow workers start greet-user-worker --secret-key <key>
145+
```
146+
147+
---
148+
149+
## When to Implement
150+
151+
After:
152+
- Auth verification is implemented (PLAN_auth-verification.md)
153+
- Basic worker functionality is stable
154+
- User feedback indicates need for easier worker management
155+
156+
---
157+
158+
## Future Enhancements
159+
160+
- `pgflow workers list` - List available workers
161+
- `pgflow workers status` - Show running workers
162+
- `pgflow workers stop` - Stop running workers
163+
- Watch mode for development
164+
- Multiple worker instances with `--concurrency` flag

pkgs/cli/__tests__/commands/compile/index.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('fetchFlowSQL', () => {
4343
const result = await fetchFlowSQL(
4444
'test_flow',
4545
'http://127.0.0.1:50621/functions/v1/pgflow',
46-
'test-publishable-key'
46+
'test-secret-key'
4747
);
4848

4949
expect(result).toEqual({
@@ -58,8 +58,8 @@ describe('fetchFlowSQL', () => {
5858
'http://127.0.0.1:50621/functions/v1/pgflow/flows/test_flow',
5959
{
6060
headers: {
61-
'Authorization': 'Bearer test-publishable-key',
62-
'apikey': 'test-publishable-key',
61+
'Authorization': 'Bearer test-secret-key',
62+
'apikey': 'test-secret-key',
6363
'Content-Type': 'application/json',
6464
},
6565
}
@@ -142,7 +142,7 @@ describe('fetchFlowSQL', () => {
142142
const result = await fetchFlowSQL(
143143
'test_flow',
144144
'http://127.0.0.1:50621',
145-
'test-publishable-key'
145+
'test-secret-key'
146146
);
147147

148148
expect(result).toHaveProperty('flowSlug');
@@ -224,7 +224,7 @@ describe('fetchFlowSQL', () => {
224224
await fetchFlowSQL(
225225
'my_complex_flow_123',
226226
'http://127.0.0.1:50621/functions/v1/pgflow',
227-
'test-publishable-key'
227+
'test-secret-key'
228228
);
229229

230230
expect(global.fetch).toHaveBeenCalledWith(
@@ -233,7 +233,7 @@ describe('fetchFlowSQL', () => {
233233
);
234234
});
235235

236-
it('should pass publishable key in Authorization header', async () => {
236+
it('should pass secret key in Authorization header', async () => {
237237
const mockResponse = {
238238
ok: true,
239239
status: 200,
@@ -248,13 +248,13 @@ describe('fetchFlowSQL', () => {
248248
await fetchFlowSQL(
249249
'test_flow',
250250
'http://127.0.0.1:50621/functions/v1/pgflow',
251-
'my-special-publishable-key'
251+
'my-special-secret-key'
252252
);
253253

254254
expect(global.fetch).toHaveBeenCalledWith(expect.any(String), {
255255
headers: {
256-
'Authorization': 'Bearer my-special-publishable-key',
257-
'apikey': 'my-special-publishable-key',
256+
'Authorization': 'Bearer my-special-secret-key',
257+
'apikey': 'my-special-secret-key',
258258
'Content-Type': 'application/json',
259259
},
260260
});
@@ -276,7 +276,7 @@ describe('fetchFlowSQL', () => {
276276
const result = await fetchFlowSQL(
277277
'empty_flow',
278278
'http://127.0.0.1:50621/functions/v1/pgflow',
279-
'test-publishable-key'
279+
'test-secret-key'
280280
);
281281

282282
expect(result.sql).toEqual([]);

pkgs/cli/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"cwd": "{projectRoot}",
6868
"readyWhen": "Serving functions on http://",
6969
"streamOutput": true,
70-
"command": "../../scripts/supabase-start-locked.sh . && ./scripts/sync-e2e-deps.sh && supabase functions serve --import-map supabase/functions/pgflow/deno.json"
70+
"command": "../../scripts/supabase-start-locked.sh . && ./scripts/sync-e2e-deps.sh && supabase functions serve --no-verify-jwt --import-map supabase/functions/pgflow/deno.json"
7171
}
7272
},
7373
"hang": {

0 commit comments

Comments
 (0)