Skip to content

Commit 89d16be

Browse files
committed
Protect system database from DDL operations
Restrict system token to SELECT/INSERT/UPDATE/DELETE only and block table/column modifications on the platform database (id 0000...).
1 parent abda25b commit 89d16be

4 files changed

Lines changed: 39 additions & 10 deletions

File tree

DEPLOY.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ Once the platform is running, use a **space alias** (goes through the API) for s
8585

8686
```bash
8787
bun run build
88-
ow <space> worker upload openworkers-api ./build
88+
ow infra worker upload openworkers-api ./build
8989
```
9090

91-
Where `<space>` is the target space (`dev`, `main`, `ps`, ...).
91+
Where `infra` is the target space alias (could be `dev`, `main`, `local`, ...).
9292

9393
Create the environment **before** the first upload so the project inherits it automatically. If the worker was already uploaded without an environment, `worker link` will cascade it to the project and all function workers.
9494

@@ -100,10 +100,10 @@ Secrets are prompted interactively (masked input) when value is omitted:
100100

101101
```bash
102102
# Variables (plain text)
103-
ow <space> env set openworkers-api-env APP_URL https://dash.example.com
103+
ow infra env set openworkers-api-env APP_URL https://dash.example.com
104104

105105
# Secrets (prompted interactively, not stored in shell history)
106-
ow <space> env set openworkers-api-env JWT_ACCESS_SECRET --secret
106+
ow infra env set openworkers-api-env JWT_ACCESS_SECRET --secret
107107
```
108108

109109
Bindings (`DATABASE`, `ASSETS`) are set with `ow infra env bind` during the initial setup (see step 5 above). They connect the worker to its database and assets storage at runtime.
@@ -133,17 +133,17 @@ In Docker mode, `POSTGATE_URL` and `POSTGATE_TOKEN` must be set in `.env` to con
133133
Workers that are uploaded with multiple routes/functions are automatically promoted to **projects**. Projects group related workers.
134134

135135
```bash
136-
ow <space> projects list # List all projects
137-
ow <space> projects delete my-app # Delete project and all its workers
136+
ow infra projects list # List all projects
137+
ow infra projects delete my-app # Delete project and all its workers
138138
```
139139

140140
To delete a worker that belongs to a project, delete the project instead:
141141

142142
```bash
143143
# This fails:
144-
ow <space> worker delete my-app
144+
ow infra worker delete my-app
145145
# → "Cannot delete main worker - delete the project instead"
146146

147147
# Do this instead:
148-
ow <space> projects delete my-app
148+
ow infra projects delete my-app
149149
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openworkers-api",
3-
"version": "1.6.0",
3+
"version": "1.6.1",
44
"license": "MIT",
55
"type": "module",
66
"repository": {

src/lib/services/db/database-tokens.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import type { DatabaseOperation } from '../../types/schemas/database-token.schem
77
const SYSTEM_TOKEN_NAME = '__system__';
88
const ALL_OPERATIONS: DatabaseOperation[] = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP'];
99

10+
function isProtectedDatabase(databaseId: string): boolean {
11+
return databaseId === '00000000-0000-0000-0000-000000000000';
12+
}
13+
1014
export interface DatabaseToken {
1115
id: string;
1216
databaseId: string;
@@ -116,14 +120,19 @@ async function createSystemToken(databaseId: string): Promise<string> {
116120
const tokenPrefix = fullToken.substring(0, 8);
117121
const tokenHash = await hashToken(fullToken);
118122

123+
const isProtected = isProtectedDatabase(databaseId);
124+
const allowedOperations: DatabaseOperation[] = isProtected
125+
? ['SELECT', 'INSERT', 'UPDATE', 'DELETE']
126+
: ALL_OPERATIONS;
127+
119128
await kysely
120129
.insertInto('databaseTokens')
121130
.values({
122131
databaseId: uuid(databaseId),
123132
name: SYSTEM_TOKEN_NAME,
124133
tokenHash,
125134
tokenPrefix,
126-
allowedOperations: textArray(ALL_OPERATIONS)
135+
allowedOperations: textArray(allowedOperations)
127136
})
128137
.onConflict((oc) => oc.columns(['databaseId', 'name']).doNothing())
129138
.execute();

src/lib/services/tables.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import * as db from './db/databases';
33
import type { ITableInfo, IColumnInfo, IColumnDefinition } from '../types';
44

55
export class TablesService {
6+
private isProtectedDatabase(databaseId: string): boolean {
7+
return databaseId === '00000000-0000-0000-0000-000000000000';
8+
}
9+
610
/**
711
* List all tables in a database schema
812
*/
@@ -91,6 +95,10 @@ export class TablesService {
9195
throw new Error('Schema not configured');
9296
}
9397

98+
if (this.isProtectedDatabase(databaseId)) {
99+
throw new Error('Cannot modify protected database');
100+
}
101+
94102
await sql(`SELECT create_tenant_table($1::uuid, $2, $3, $4::jsonb)`, [
95103
userId,
96104
dbConfig.schemaName,
@@ -117,6 +125,10 @@ export class TablesService {
117125
throw new Error('Schema not configured');
118126
}
119127

128+
if (this.isProtectedDatabase(databaseId)) {
129+
throw new Error('Cannot modify protected database');
130+
}
131+
120132
await sql(`SELECT drop_tenant_table($1::uuid, $2, $3)`, [userId, dbConfig.schemaName, tableName]);
121133
}
122134

@@ -138,6 +150,10 @@ export class TablesService {
138150
throw new Error('Schema not configured');
139151
}
140152

153+
if (this.isProtectedDatabase(databaseId)) {
154+
throw new Error('Cannot modify protected database');
155+
}
156+
141157
await sql(`SELECT add_tenant_column($1::uuid, $2, $3, $4::jsonb)`, [
142158
userId,
143159
dbConfig.schemaName,
@@ -164,6 +180,10 @@ export class TablesService {
164180
throw new Error('Schema not configured');
165181
}
166182

183+
if (this.isProtectedDatabase(databaseId)) {
184+
throw new Error('Cannot modify protected database');
185+
}
186+
167187
await sql(`SELECT drop_tenant_column($1::uuid, $2, $3, $4)`, [userId, dbConfig.schemaName, tableName, columnName]);
168188
}
169189
}

0 commit comments

Comments
 (0)