Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,25 @@

## Authentication

Workers authenticate with ControlPlane using a dedicated secret key:
Workers authenticate with ControlPlane using the Supabase service role key (zero-config):

```
Env var: PGFLOW_SECRET_KEY
Header: apikey: <secret_key>
Env var: SUPABASE_SERVICE_ROLE_KEY (automatically available in Edge Functions)
Header: apikey: <service_role_key>
```

**Setup:**
1. Users generate a secret key in Supabase dashboard (Supabase Secrets)
2. Set `PGFLOW_SECRET_KEY` env var for both ControlPlane and Worker edge functions
3. Workers include `apikey` header in compilation requests
4. ControlPlane verifies `apikey` header matches `PGFLOW_SECRET_KEY` env var
- No setup required - both ControlPlane and Worker Edge Functions automatically have access to `SUPABASE_SERVICE_ROLE_KEY` via `Deno.env`
- Workers include `apikey` header in compilation requests
- ControlPlane verifies `apikey` header matches `SUPABASE_SERVICE_ROLE_KEY` env var

**ControlPlane Verification:**
```typescript
function verifyAuth(request: Request): boolean {
const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
if (!serviceRoleKey) return false; // Not configured - reject all
const apikey = request.headers.get('apikey');
const secretKey = Deno.env.get('PGFLOW_SECRET_KEY');
if (!secretKey) return false; // Not configured - reject all
return apikey === secretKey;
return apikey === serviceRoleKey;
}
```

Expand All @@ -66,7 +65,7 @@ Worker.start(MyFlow)
└── POST /flows/:slug/ensure-compiled
│ Body: { shape: workerShape, mode: 'development' | 'production' }
│ Headers: { apikey: PGFLOW_SECRET_KEY }
│ Headers: { apikey: SUPABASE_SERVICE_ROLE_KEY }
└── ControlPlane (Layer 1: Deployment Validation)
Expand Down Expand Up @@ -119,7 +118,7 @@ Worker.start(MyFlow)

```
POST /flows/:slug/ensure-compiled
Headers: { apikey: PGFLOW_SECRET_KEY }
Headers: { apikey: SUPABASE_SERVICE_ROLE_KEY }
Body: {
shape: FlowShape,
mode: 'development' | 'production'
Expand Down Expand Up @@ -718,7 +717,7 @@ Test-Driven Development order - write tests FIRST, then implement:
### Phase 7: ControlPlane Endpoint (~0.5 day)

**Order within phase:**
1. Add auth verification (check `PGFLOW_SECRET_KEY`)
1. Add auth verification (check `SUPABASE_SERVICE_ROLE_KEY`)
2. Add flow registry lookup (404 if not found)
3. Add Layer 1: TypeScript comparison (409 if worker≠ControlPlane)
4. Add Layer 2: SQL function call
Expand Down
13 changes: 6 additions & 7 deletions pkgs/edge-worker/src/control-plane/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ export type SqlFunction = (strings: TemplateStringsArray, ...values: any[]) => P
export interface ControlPlaneOptions {
/** SQL function for database operations (required for ensure-compiled endpoint) */
sql?: SqlFunction;
/** Secret key for authentication (required for ensure-compiled endpoint) */
secretKey?: string;
}

/**
Expand Down Expand Up @@ -195,12 +193,13 @@ function jsonResponse(data: unknown, status: number): Response {
}

/**
* Verifies authentication using apikey header
* Verifies authentication using apikey header against SUPABASE_SERVICE_ROLE_KEY env var
*/
function verifyAuth(request: Request, secretKey: string | undefined): boolean {
if (!secretKey) return false;
function verifyAuth(request: Request): boolean {
const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
if (!serviceRoleKey) return false;
const apikey = request.headers.get('apikey');
return apikey === secretKey;
return apikey === serviceRoleKey;
}

/**
Expand Down Expand Up @@ -249,7 +248,7 @@ async function handleEnsureCompiled(
}

// Verify authentication
if (!verifyAuth(request, options.secretKey)) {
if (!verifyAuth(request)) {
return jsonResponse(
{
error: 'Unauthorized',
Expand Down
Loading