or3-provider-sqlite 0.0.1 → 0.0.3
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/README.md +20 -4
- package/dist/module.json +1 -1
- package/dist/runtime/server/admin/adapters/sync-sqlite.js +12 -34
- package/dist/runtime/server/admin/stores/sqlite-store.js +64 -9
- package/dist/runtime/server/auth/sqlite-auth-workspace-store.d.ts +16 -1
- package/dist/runtime/server/auth/sqlite-auth-workspace-store.js +114 -3
- package/dist/runtime/server/connect/sqlite-connect-store.d.ts +2 -0
- package/dist/runtime/server/connect/sqlite-connect-store.js +885 -0
- package/dist/runtime/server/db/migrate.js +62 -1
- package/dist/runtime/server/db/migrations/006_sync_snapshots.d.ts +10 -0
- package/dist/runtime/server/db/migrations/006_sync_snapshots.js +106 -0
- package/dist/runtime/server/db/migrations/007_device_cursor_ownership.d.ts +7 -0
- package/dist/runtime/server/db/migrations/007_device_cursor_ownership.js +5 -0
- package/dist/runtime/server/db/migrations/008_upload_intents.d.ts +4 -0
- package/dist/runtime/server/db/migrations/008_upload_intents.js +26 -0
- package/dist/runtime/server/db/migrations/009_or3_connect.d.ts +4 -0
- package/dist/runtime/server/db/migrations/009_or3_connect.js +91 -0
- package/dist/runtime/server/db/migrations/010_connect_user_code_hmac.d.ts +10 -0
- package/dist/runtime/server/db/migrations/010_connect_user_code_hmac.js +97 -0
- package/dist/runtime/server/db/migrations/011_connect_environment_scope_indexes.d.ts +4 -0
- package/dist/runtime/server/db/migrations/011_connect_environment_scope_indexes.js +26 -0
- package/dist/runtime/server/db/migrations/012_connect_credential_redelivery.d.ts +4 -0
- package/dist/runtime/server/db/migrations/012_connect_credential_redelivery.js +124 -0
- package/dist/runtime/server/db/migrations/013_connect_environment_lifecycle.d.ts +10 -0
- package/dist/runtime/server/db/migrations/013_connect_environment_lifecycle.js +261 -0
- package/dist/runtime/server/db/migrations/014_connect_retention_activation.d.ts +4 -0
- package/dist/runtime/server/db/migrations/014_connect_retention_activation.js +44 -0
- package/dist/runtime/server/db/migrations/015_rate_limits.d.ts +4 -0
- package/dist/runtime/server/db/migrations/015_rate_limits.js +18 -0
- package/dist/runtime/server/db/schema.d.ts +97 -0
- package/dist/runtime/server/plugins/register.js +27 -5
- package/dist/runtime/server/rate-limit/sqlite-provider.d.ts +7 -0
- package/dist/runtime/server/rate-limit/sqlite-provider.js +70 -0
- package/dist/runtime/server/sync/history-gc-policy.d.ts +13 -0
- package/dist/runtime/server/sync/history-gc-policy.js +8 -0
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.d.ts +14 -2
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.js +901 -67
- package/dist/runtime/server/webhooks/sqlite-webhook-store.d.ts +9 -0
- package/dist/runtime/server/webhooks/sqlite-webhook-store.js +521 -0
- package/package.json +9 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
const environmentColumns = await tableColumns(db, "connect_environments");
|
|
4
|
+
if (environmentColumns.has("lifecycle_claim_token")) return;
|
|
5
|
+
await dropConnectIndexes(db);
|
|
6
|
+
await sql`
|
|
7
|
+
ALTER TABLE connect_device_authorizations
|
|
8
|
+
RENAME TO connect_device_authorizations_before_lifecycle
|
|
9
|
+
`.execute(db);
|
|
10
|
+
await sql`
|
|
11
|
+
ALTER TABLE connect_environments
|
|
12
|
+
RENAME TO connect_environments_before_lifecycle
|
|
13
|
+
`.execute(db);
|
|
14
|
+
await createAuthorizationTable(db, true);
|
|
15
|
+
await createEnvironmentTable(db, true);
|
|
16
|
+
await sql`
|
|
17
|
+
INSERT INTO connect_device_authorizations (
|
|
18
|
+
id, device_code_hash, user_code_hash, status, host_json,
|
|
19
|
+
approved_user_id, approved_workspace_id, environment_id,
|
|
20
|
+
credential_ciphertext, credential_delivery_started_at,
|
|
21
|
+
credential_redeliver_until, expires_at, created_at, updated_at
|
|
22
|
+
)
|
|
23
|
+
SELECT
|
|
24
|
+
id, device_code_hash, user_code_hash, status, host_json,
|
|
25
|
+
approved_user_id, approved_workspace_id, environment_id,
|
|
26
|
+
credential_ciphertext, credential_delivery_started_at,
|
|
27
|
+
credential_redeliver_until, expires_at, created_at, updated_at
|
|
28
|
+
FROM connect_device_authorizations_before_lifecycle
|
|
29
|
+
`.execute(db);
|
|
30
|
+
await sql`
|
|
31
|
+
INSERT INTO connect_environments (
|
|
32
|
+
id, user_id, workspace_id, name, platform, architecture,
|
|
33
|
+
host_id, signing_public_key, noise_public_key, hostname,
|
|
34
|
+
tunnel_id, dns_record_id, control_token_hash,
|
|
35
|
+
access_credential_ciphertext, status, lifecycle_attempts,
|
|
36
|
+
lifecycle_next_attempt_at, last_seen_at, created_at, updated_at,
|
|
37
|
+
revoked_at
|
|
38
|
+
)
|
|
39
|
+
SELECT
|
|
40
|
+
id, user_id, workspace_id, name, platform, architecture,
|
|
41
|
+
host_id, signing_public_key, noise_public_key, hostname,
|
|
42
|
+
tunnel_id, dns_record_id, control_token_hash,
|
|
43
|
+
access_credential_ciphertext, status, 0, 0, last_seen_at,
|
|
44
|
+
created_at, updated_at, revoked_at
|
|
45
|
+
FROM connect_environments_before_lifecycle
|
|
46
|
+
`.execute(db);
|
|
47
|
+
await sql`
|
|
48
|
+
DROP TABLE connect_device_authorizations_before_lifecycle
|
|
49
|
+
`.execute(db);
|
|
50
|
+
await sql`DROP TABLE connect_environments_before_lifecycle`.execute(db);
|
|
51
|
+
}
|
|
52
|
+
export async function down(db) {
|
|
53
|
+
const environmentColumns = await tableColumns(db, "connect_environments");
|
|
54
|
+
if (!environmentColumns.has("lifecycle_claim_token")) return;
|
|
55
|
+
await dropConnectIndexes(db);
|
|
56
|
+
await sql`
|
|
57
|
+
ALTER TABLE connect_device_authorizations
|
|
58
|
+
RENAME TO connect_device_authorizations_with_lifecycle
|
|
59
|
+
`.execute(db);
|
|
60
|
+
await sql`
|
|
61
|
+
ALTER TABLE connect_environments
|
|
62
|
+
RENAME TO connect_environments_with_lifecycle
|
|
63
|
+
`.execute(db);
|
|
64
|
+
await createAuthorizationTable(db, false);
|
|
65
|
+
await createEnvironmentTable(db, false);
|
|
66
|
+
await sql`
|
|
67
|
+
INSERT INTO connect_device_authorizations (
|
|
68
|
+
id, device_code_hash, user_code_hash, status, host_json,
|
|
69
|
+
approved_user_id, approved_workspace_id, environment_id,
|
|
70
|
+
credential_ciphertext, credential_delivery_started_at,
|
|
71
|
+
credential_redeliver_until, expires_at, created_at, updated_at
|
|
72
|
+
)
|
|
73
|
+
SELECT
|
|
74
|
+
id, device_code_hash, user_code_hash,
|
|
75
|
+
CASE WHEN status = 'provisioning' THEN 'expired' ELSE status END,
|
|
76
|
+
host_json, approved_user_id, approved_workspace_id, environment_id,
|
|
77
|
+
credential_ciphertext, credential_delivery_started_at,
|
|
78
|
+
credential_redeliver_until, expires_at, created_at, updated_at
|
|
79
|
+
FROM connect_device_authorizations_with_lifecycle
|
|
80
|
+
`.execute(db);
|
|
81
|
+
await sql`
|
|
82
|
+
INSERT INTO connect_environments (
|
|
83
|
+
id, user_id, workspace_id, name, platform, architecture,
|
|
84
|
+
host_id, signing_public_key, noise_public_key, hostname,
|
|
85
|
+
tunnel_id, dns_record_id, control_token_hash,
|
|
86
|
+
access_credential_ciphertext, status, last_seen_at, created_at,
|
|
87
|
+
updated_at, revoked_at
|
|
88
|
+
)
|
|
89
|
+
SELECT
|
|
90
|
+
id, user_id, workspace_id, name, platform, architecture,
|
|
91
|
+
host_id, signing_public_key, noise_public_key, hostname,
|
|
92
|
+
tunnel_id, dns_record_id, control_token_hash,
|
|
93
|
+
access_credential_ciphertext,
|
|
94
|
+
CASE
|
|
95
|
+
WHEN status = 'provisioning' THEN 'error'
|
|
96
|
+
WHEN status = 'revoking' THEN 'revoked'
|
|
97
|
+
ELSE status
|
|
98
|
+
END,
|
|
99
|
+
last_seen_at, created_at, updated_at, revoked_at
|
|
100
|
+
FROM connect_environments_with_lifecycle
|
|
101
|
+
`.execute(db);
|
|
102
|
+
await sql`
|
|
103
|
+
DROP TABLE connect_device_authorizations_with_lifecycle
|
|
104
|
+
`.execute(db);
|
|
105
|
+
await sql`DROP TABLE connect_environments_with_lifecycle`.execute(db);
|
|
106
|
+
}
|
|
107
|
+
async function tableColumns(db, table) {
|
|
108
|
+
const result = await sql`
|
|
109
|
+
PRAGMA table_info(${sql.raw(table)})
|
|
110
|
+
`.execute(db);
|
|
111
|
+
return new Set(result.rows.map((column) => column.name));
|
|
112
|
+
}
|
|
113
|
+
async function createAuthorizationTable(db, lifecycle) {
|
|
114
|
+
const statuses = lifecycle ? "'pending', 'provisioning', 'approved', 'delivering', 'denied', 'consumed', 'expired'" : "'pending', 'approved', 'delivering', 'denied', 'consumed', 'expired'";
|
|
115
|
+
await sql`
|
|
116
|
+
CREATE TABLE connect_device_authorizations (
|
|
117
|
+
id TEXT PRIMARY KEY,
|
|
118
|
+
device_code_hash TEXT NOT NULL UNIQUE,
|
|
119
|
+
user_code_hash TEXT NOT NULL,
|
|
120
|
+
status TEXT NOT NULL CHECK (status IN (${sql.raw(statuses)})),
|
|
121
|
+
host_json TEXT NOT NULL,
|
|
122
|
+
approved_user_id TEXT,
|
|
123
|
+
approved_workspace_id TEXT,
|
|
124
|
+
environment_id TEXT,
|
|
125
|
+
credential_ciphertext TEXT,
|
|
126
|
+
credential_delivery_started_at INTEGER,
|
|
127
|
+
credential_redeliver_until INTEGER,
|
|
128
|
+
expires_at INTEGER NOT NULL,
|
|
129
|
+
created_at INTEGER NOT NULL,
|
|
130
|
+
updated_at INTEGER NOT NULL
|
|
131
|
+
)
|
|
132
|
+
`.execute(db);
|
|
133
|
+
await createAuthorizationIndexes(db);
|
|
134
|
+
}
|
|
135
|
+
async function createEnvironmentTable(db, lifecycle) {
|
|
136
|
+
if (!lifecycle) {
|
|
137
|
+
await sql`
|
|
138
|
+
CREATE TABLE connect_environments (
|
|
139
|
+
id TEXT PRIMARY KEY,
|
|
140
|
+
user_id TEXT NOT NULL,
|
|
141
|
+
workspace_id TEXT NOT NULL,
|
|
142
|
+
name TEXT NOT NULL,
|
|
143
|
+
platform TEXT NOT NULL,
|
|
144
|
+
architecture TEXT NOT NULL,
|
|
145
|
+
host_id TEXT,
|
|
146
|
+
signing_public_key TEXT,
|
|
147
|
+
noise_public_key TEXT,
|
|
148
|
+
hostname TEXT NOT NULL,
|
|
149
|
+
tunnel_id TEXT NOT NULL,
|
|
150
|
+
dns_record_id TEXT NOT NULL,
|
|
151
|
+
control_token_hash TEXT NOT NULL UNIQUE,
|
|
152
|
+
access_credential_ciphertext TEXT NOT NULL,
|
|
153
|
+
status TEXT NOT NULL CHECK (
|
|
154
|
+
status IN ('active', 'revoked', 'error')
|
|
155
|
+
),
|
|
156
|
+
last_seen_at INTEGER,
|
|
157
|
+
created_at INTEGER NOT NULL,
|
|
158
|
+
updated_at INTEGER NOT NULL,
|
|
159
|
+
revoked_at INTEGER
|
|
160
|
+
)
|
|
161
|
+
`.execute(db);
|
|
162
|
+
await createEnvironmentIndexes(db, false);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await sql`
|
|
166
|
+
CREATE TABLE connect_environments (
|
|
167
|
+
id TEXT PRIMARY KEY,
|
|
168
|
+
user_id TEXT NOT NULL,
|
|
169
|
+
workspace_id TEXT NOT NULL,
|
|
170
|
+
name TEXT NOT NULL,
|
|
171
|
+
platform TEXT NOT NULL,
|
|
172
|
+
architecture TEXT NOT NULL,
|
|
173
|
+
host_id TEXT,
|
|
174
|
+
signing_public_key TEXT,
|
|
175
|
+
noise_public_key TEXT,
|
|
176
|
+
authorization_id TEXT,
|
|
177
|
+
hostname TEXT NOT NULL,
|
|
178
|
+
tunnel_id TEXT NOT NULL,
|
|
179
|
+
dns_record_id TEXT NOT NULL,
|
|
180
|
+
control_token_hash TEXT NOT NULL UNIQUE,
|
|
181
|
+
access_credential_ciphertext TEXT NOT NULL,
|
|
182
|
+
tunnel_secret_ciphertext TEXT,
|
|
183
|
+
status TEXT NOT NULL CHECK (
|
|
184
|
+
status IN (
|
|
185
|
+
'provisioning', 'active', 'revoking', 'revoked', 'error'
|
|
186
|
+
)
|
|
187
|
+
),
|
|
188
|
+
lifecycle_attempts INTEGER NOT NULL DEFAULT 0,
|
|
189
|
+
lifecycle_next_attempt_at INTEGER NOT NULL DEFAULT 0,
|
|
190
|
+
lifecycle_claim_token TEXT,
|
|
191
|
+
lifecycle_claimed_until INTEGER,
|
|
192
|
+
provisioning_deadline_at INTEGER,
|
|
193
|
+
lifecycle_error TEXT,
|
|
194
|
+
last_seen_at INTEGER,
|
|
195
|
+
created_at INTEGER NOT NULL,
|
|
196
|
+
updated_at INTEGER NOT NULL,
|
|
197
|
+
revoked_at INTEGER
|
|
198
|
+
)
|
|
199
|
+
`.execute(db);
|
|
200
|
+
await createEnvironmentIndexes(db, true);
|
|
201
|
+
}
|
|
202
|
+
async function dropConnectIndexes(db) {
|
|
203
|
+
const names = [
|
|
204
|
+
"connect_authorizations_status_expiry",
|
|
205
|
+
"connect_authorizations_user_code",
|
|
206
|
+
"connect_authorizations_environment",
|
|
207
|
+
"connect_environments_user_status",
|
|
208
|
+
"connect_environments_workspace_status",
|
|
209
|
+
"connect_environments_user_workspace_status_updated",
|
|
210
|
+
"connect_environments_user_workspace_control_token",
|
|
211
|
+
"connect_environments_user_workspace_id",
|
|
212
|
+
"connect_environments_lifecycle_due"
|
|
213
|
+
];
|
|
214
|
+
for (const name of names) {
|
|
215
|
+
await sql`DROP INDEX IF EXISTS ${sql.raw(name)}`.execute(db);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function createAuthorizationIndexes(db) {
|
|
219
|
+
await sql`
|
|
220
|
+
CREATE INDEX connect_authorizations_status_expiry
|
|
221
|
+
ON connect_device_authorizations(status, expires_at)
|
|
222
|
+
`.execute(db);
|
|
223
|
+
await sql`
|
|
224
|
+
CREATE INDEX connect_authorizations_user_code
|
|
225
|
+
ON connect_device_authorizations(user_code_hash, created_at)
|
|
226
|
+
`.execute(db);
|
|
227
|
+
await sql`
|
|
228
|
+
CREATE INDEX connect_authorizations_environment
|
|
229
|
+
ON connect_device_authorizations(environment_id)
|
|
230
|
+
`.execute(db);
|
|
231
|
+
}
|
|
232
|
+
async function createEnvironmentIndexes(db, lifecycle) {
|
|
233
|
+
await sql`
|
|
234
|
+
CREATE INDEX connect_environments_user_status
|
|
235
|
+
ON connect_environments(user_id, status, updated_at)
|
|
236
|
+
`.execute(db);
|
|
237
|
+
await sql`
|
|
238
|
+
CREATE INDEX connect_environments_workspace_status
|
|
239
|
+
ON connect_environments(workspace_id, status, updated_at)
|
|
240
|
+
`.execute(db);
|
|
241
|
+
await sql`
|
|
242
|
+
CREATE INDEX connect_environments_user_workspace_status_updated
|
|
243
|
+
ON connect_environments(user_id, workspace_id, status, updated_at)
|
|
244
|
+
`.execute(db);
|
|
245
|
+
await sql`
|
|
246
|
+
CREATE INDEX connect_environments_user_workspace_control_token
|
|
247
|
+
ON connect_environments(user_id, workspace_id, control_token_hash)
|
|
248
|
+
`.execute(db);
|
|
249
|
+
await sql`
|
|
250
|
+
CREATE INDEX connect_environments_user_workspace_id
|
|
251
|
+
ON connect_environments(user_id, workspace_id, id)
|
|
252
|
+
`.execute(db);
|
|
253
|
+
if (lifecycle) {
|
|
254
|
+
await sql`
|
|
255
|
+
CREATE INDEX connect_environments_lifecycle_due
|
|
256
|
+
ON connect_environments(
|
|
257
|
+
status, lifecycle_next_attempt_at, lifecycle_claimed_until
|
|
258
|
+
)
|
|
259
|
+
`.execute(db);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
const columns = await sql`
|
|
4
|
+
PRAGMA table_info(connect_environments)
|
|
5
|
+
`.execute(db);
|
|
6
|
+
const names = new Set(columns.rows.map((column) => column.name));
|
|
7
|
+
if (!names.has("activation_deadline_at")) {
|
|
8
|
+
await sql`
|
|
9
|
+
ALTER TABLE connect_environments
|
|
10
|
+
ADD COLUMN activation_deadline_at INTEGER
|
|
11
|
+
`.execute(db);
|
|
12
|
+
}
|
|
13
|
+
if (!names.has("activation_claimed_at")) {
|
|
14
|
+
await sql`
|
|
15
|
+
ALTER TABLE connect_environments
|
|
16
|
+
ADD COLUMN activation_claimed_at INTEGER
|
|
17
|
+
`.execute(db);
|
|
18
|
+
}
|
|
19
|
+
if (!names.has("relay_authenticator")) {
|
|
20
|
+
await sql`
|
|
21
|
+
ALTER TABLE connect_environments
|
|
22
|
+
ADD COLUMN relay_authenticator TEXT
|
|
23
|
+
`.execute(db);
|
|
24
|
+
}
|
|
25
|
+
await sql`
|
|
26
|
+
CREATE INDEX IF NOT EXISTS connect_authorizations_status_updated
|
|
27
|
+
ON connect_device_authorizations(status, updated_at, id)
|
|
28
|
+
`.execute(db);
|
|
29
|
+
await sql`
|
|
30
|
+
CREATE INDEX IF NOT EXISTS connect_environments_status_updated
|
|
31
|
+
ON connect_environments(status, updated_at, id)
|
|
32
|
+
`.execute(db);
|
|
33
|
+
await sql`
|
|
34
|
+
CREATE INDEX IF NOT EXISTS connect_environments_activation_due
|
|
35
|
+
ON connect_environments(
|
|
36
|
+
status, activation_claimed_at, activation_deadline_at, id
|
|
37
|
+
)
|
|
38
|
+
`.execute(db);
|
|
39
|
+
}
|
|
40
|
+
export async function down(db) {
|
|
41
|
+
await sql`DROP INDEX IF EXISTS connect_environments_activation_due`.execute(db);
|
|
42
|
+
await sql`DROP INDEX IF EXISTS connect_environments_status_updated`.execute(db);
|
|
43
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_status_updated`.execute(db);
|
|
44
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
await sql`
|
|
4
|
+
CREATE TABLE IF NOT EXISTS rate_limits (
|
|
5
|
+
key TEXT PRIMARY KEY,
|
|
6
|
+
count INTEGER NOT NULL,
|
|
7
|
+
window_started_at INTEGER NOT NULL,
|
|
8
|
+
expires_at INTEGER NOT NULL
|
|
9
|
+
)
|
|
10
|
+
`.execute(db);
|
|
11
|
+
await sql`
|
|
12
|
+
CREATE INDEX IF NOT EXISTS rate_limits_expiry
|
|
13
|
+
ON rate_limits(expires_at)
|
|
14
|
+
`.execute(db);
|
|
15
|
+
}
|
|
16
|
+
export async function down(db) {
|
|
17
|
+
await sql`DROP TABLE IF EXISTS rate_limits`.execute(db);
|
|
18
|
+
}
|
|
@@ -70,6 +70,7 @@ export interface DeviceCursorsTable {
|
|
|
70
70
|
id: string;
|
|
71
71
|
workspace_id: string;
|
|
72
72
|
device_id: string;
|
|
73
|
+
owner_user_id: string | null;
|
|
73
74
|
last_seen_version: number;
|
|
74
75
|
updated_at: Generated<number>;
|
|
75
76
|
}
|
|
@@ -80,6 +81,8 @@ export interface TombstonesTable {
|
|
|
80
81
|
pk: string;
|
|
81
82
|
deleted_at: number;
|
|
82
83
|
clock: number;
|
|
84
|
+
hlc: string;
|
|
85
|
+
op_id: string;
|
|
83
86
|
server_version: number;
|
|
84
87
|
created_at: Generated<number>;
|
|
85
88
|
}
|
|
@@ -90,10 +93,98 @@ export interface SyncedEntityTable {
|
|
|
90
93
|
clock: number;
|
|
91
94
|
hlc: string;
|
|
92
95
|
device_id: string;
|
|
96
|
+
op_id: string;
|
|
93
97
|
deleted: Generated<number>;
|
|
94
98
|
created_at: Generated<number>;
|
|
95
99
|
updated_at: Generated<number>;
|
|
96
100
|
}
|
|
101
|
+
export interface SyncSnapshotsTable {
|
|
102
|
+
id: string;
|
|
103
|
+
workspace_id: string;
|
|
104
|
+
high_watermark: number;
|
|
105
|
+
tables_json: string;
|
|
106
|
+
created_at: number;
|
|
107
|
+
expires_at: number;
|
|
108
|
+
}
|
|
109
|
+
export interface SyncSnapshotItemsTable {
|
|
110
|
+
snapshot_id: string;
|
|
111
|
+
table_name: string;
|
|
112
|
+
pk: string;
|
|
113
|
+
kind: string;
|
|
114
|
+
payload_json: string | null;
|
|
115
|
+
clock: number;
|
|
116
|
+
hlc: string;
|
|
117
|
+
op_id: string;
|
|
118
|
+
server_deleted_at: number | null;
|
|
119
|
+
}
|
|
120
|
+
export interface UploadIntentsTable {
|
|
121
|
+
id: string;
|
|
122
|
+
workspace_id: string;
|
|
123
|
+
hash: string;
|
|
124
|
+
mime_type: string;
|
|
125
|
+
size_bytes: number;
|
|
126
|
+
reserved_bytes: number;
|
|
127
|
+
expires_at: number;
|
|
128
|
+
status: 'active' | 'consumed' | 'cancelled' | 'expired';
|
|
129
|
+
storage_id: string | null;
|
|
130
|
+
created_at: number;
|
|
131
|
+
consumed_at: number | null;
|
|
132
|
+
cancelled_at: number | null;
|
|
133
|
+
}
|
|
134
|
+
export interface ConnectDeviceAuthorizationsTable {
|
|
135
|
+
id: string;
|
|
136
|
+
device_code_hash: string;
|
|
137
|
+
user_code_hash: string;
|
|
138
|
+
status: 'pending' | 'provisioning' | 'approved' | 'delivering' | 'denied' | 'consumed' | 'expired';
|
|
139
|
+
host_json: string;
|
|
140
|
+
approved_user_id: string | null;
|
|
141
|
+
approved_workspace_id: string | null;
|
|
142
|
+
environment_id: string | null;
|
|
143
|
+
credential_ciphertext: string | null;
|
|
144
|
+
credential_delivery_started_at: number | null;
|
|
145
|
+
credential_redeliver_until: number | null;
|
|
146
|
+
expires_at: number;
|
|
147
|
+
created_at: number;
|
|
148
|
+
updated_at: number;
|
|
149
|
+
}
|
|
150
|
+
export interface ConnectEnvironmentsTable {
|
|
151
|
+
id: string;
|
|
152
|
+
user_id: string;
|
|
153
|
+
workspace_id: string;
|
|
154
|
+
name: string;
|
|
155
|
+
platform: string;
|
|
156
|
+
architecture: string;
|
|
157
|
+
host_id: string | null;
|
|
158
|
+
signing_public_key: string | null;
|
|
159
|
+
noise_public_key: string | null;
|
|
160
|
+
authorization_id: string | null;
|
|
161
|
+
hostname: string;
|
|
162
|
+
tunnel_id: string;
|
|
163
|
+
dns_record_id: string;
|
|
164
|
+
control_token_hash: string;
|
|
165
|
+
access_credential_ciphertext: string;
|
|
166
|
+
tunnel_secret_ciphertext: string | null;
|
|
167
|
+
status: 'provisioning' | 'active' | 'revoking' | 'revoked' | 'error';
|
|
168
|
+
lifecycle_attempts: number;
|
|
169
|
+
lifecycle_next_attempt_at: number;
|
|
170
|
+
lifecycle_claim_token: string | null;
|
|
171
|
+
lifecycle_claimed_until: number | null;
|
|
172
|
+
provisioning_deadline_at: number | null;
|
|
173
|
+
activation_deadline_at: number | null;
|
|
174
|
+
activation_claimed_at: number | null;
|
|
175
|
+
relay_authenticator: string | null;
|
|
176
|
+
lifecycle_error: string | null;
|
|
177
|
+
last_seen_at: number | null;
|
|
178
|
+
created_at: number;
|
|
179
|
+
updated_at: number;
|
|
180
|
+
revoked_at: number | null;
|
|
181
|
+
}
|
|
182
|
+
export interface RateLimitsTable {
|
|
183
|
+
key: string;
|
|
184
|
+
count: number;
|
|
185
|
+
window_started_at: number;
|
|
186
|
+
expires_at: number;
|
|
187
|
+
}
|
|
97
188
|
export interface AdminUsersTable {
|
|
98
189
|
user_id: string;
|
|
99
190
|
created_at: number;
|
|
@@ -116,6 +207,12 @@ export interface Or3SqliteDb {
|
|
|
116
207
|
change_log: ChangeLogTable;
|
|
117
208
|
device_cursors: DeviceCursorsTable;
|
|
118
209
|
tombstones: TombstonesTable;
|
|
210
|
+
sync_snapshots: SyncSnapshotsTable;
|
|
211
|
+
sync_snapshot_items: SyncSnapshotItemsTable;
|
|
212
|
+
upload_intents: UploadIntentsTable;
|
|
213
|
+
connect_device_authorizations: ConnectDeviceAuthorizationsTable;
|
|
214
|
+
connect_environments: ConnectEnvironmentsTable;
|
|
215
|
+
rate_limits: RateLimitsTable;
|
|
119
216
|
s_threads: SyncedEntityTable;
|
|
120
217
|
s_messages: SyncedEntityTable;
|
|
121
218
|
s_projects: SyncedEntityTable;
|
|
@@ -2,34 +2,55 @@ import { registerAuthWorkspaceStore } from "~~/server/auth/store/registry";
|
|
|
2
2
|
import { registerProviderAdminAdapter } from "~~/server/admin/providers/registry";
|
|
3
3
|
import { registerAdminStoreProvider } from "~~/server/admin/stores/registry";
|
|
4
4
|
import { registerSyncGatewayAdapter } from "~~/server/sync/gateway/registry";
|
|
5
|
+
import { registerWebhookStore } from "~~/server/utils/webhooks/store/registry";
|
|
6
|
+
import { registerConnectStore } from "~~/server/connect/store/registry";
|
|
7
|
+
import { registerRateLimitProvider } from "~~/server/utils/rate-limit/registry";
|
|
5
8
|
import { createSqliteAuthWorkspaceStore } from "../auth/sqlite-auth-workspace-store.js";
|
|
6
9
|
import { createSqliteSyncGatewayAdapter } from "../sync/sqlite-sync-gateway-adapter.js";
|
|
10
|
+
import { createSqliteWebhookStore } from "../webhooks/sqlite-webhook-store.js";
|
|
7
11
|
import {
|
|
8
12
|
createSqliteAdminUserStore,
|
|
9
13
|
createSqliteWorkspaceAccessStore,
|
|
10
14
|
createSqliteWorkspaceSettingsStore
|
|
11
15
|
} from "../admin/stores/sqlite-store.js";
|
|
12
16
|
import { sqliteSyncAdminAdapter } from "../admin/adapters/sync-sqlite.js";
|
|
17
|
+
import { createSqliteConnectStore } from "../connect/sqlite-connect-store.js";
|
|
13
18
|
import { getSqliteDb } from "../db/kysely.js";
|
|
14
19
|
import { runMigrations } from "../db/migrate.js";
|
|
20
|
+
import { sqliteRateLimitProvider } from "../rate-limit/sqlite-provider.js";
|
|
15
21
|
import { useRuntimeConfig } from "#imports";
|
|
16
22
|
const SQLITE_PROVIDER_ID = "sqlite";
|
|
17
23
|
export default defineNitroPlugin(async () => {
|
|
18
24
|
const config = useRuntimeConfig();
|
|
19
25
|
if (!config.auth?.enabled) return;
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
const syncSelected = config.sync?.enabled === true && config.sync?.provider === SQLITE_PROVIDER_ID;
|
|
27
|
+
const connectSelected = config.connect?.enabled === true && config.connect?.provider === SQLITE_PROVIDER_ID;
|
|
28
|
+
if (!syncSelected && !connectSelected) return;
|
|
22
29
|
const db = getSqliteDb();
|
|
23
|
-
await runMigrations(db);
|
|
24
30
|
registerAuthWorkspaceStore({
|
|
25
31
|
id: SQLITE_PROVIDER_ID,
|
|
26
32
|
order: 100,
|
|
27
33
|
create: createSqliteAuthWorkspaceStore
|
|
28
34
|
});
|
|
29
|
-
|
|
35
|
+
registerRateLimitProvider(SQLITE_PROVIDER_ID, sqliteRateLimitProvider);
|
|
36
|
+
if (syncSelected) {
|
|
37
|
+
registerSyncGatewayAdapter({
|
|
38
|
+
id: SQLITE_PROVIDER_ID,
|
|
39
|
+
order: 100,
|
|
40
|
+
create: createSqliteSyncGatewayAdapter
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (connectSelected) {
|
|
44
|
+
registerConnectStore({
|
|
45
|
+
id: SQLITE_PROVIDER_ID,
|
|
46
|
+
order: 100,
|
|
47
|
+
create: createSqliteConnectStore
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
registerWebhookStore({
|
|
30
51
|
id: SQLITE_PROVIDER_ID,
|
|
31
52
|
order: 100,
|
|
32
|
-
create:
|
|
53
|
+
create: createSqliteWebhookStore
|
|
33
54
|
});
|
|
34
55
|
registerAdminStoreProvider({
|
|
35
56
|
id: SQLITE_PROVIDER_ID,
|
|
@@ -45,4 +66,5 @@ export default defineNitroPlugin(async () => {
|
|
|
45
66
|
})
|
|
46
67
|
});
|
|
47
68
|
registerProviderAdminAdapter(sqliteSyncAdminAdapter);
|
|
69
|
+
await runMigrations(db);
|
|
48
70
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RateLimitConfig, RateLimitProvider, RateLimitResult, RateLimitStats } from '~~/server/utils/rate-limit/types';
|
|
2
|
+
export declare class SqliteRateLimitProvider implements RateLimitProvider {
|
|
3
|
+
readonly name = "sqlite";
|
|
4
|
+
checkAndRecord(key: string, config: RateLimitConfig): Promise<RateLimitResult>;
|
|
5
|
+
getStats(key: string, config: RateLimitConfig): Promise<RateLimitStats | null>;
|
|
6
|
+
}
|
|
7
|
+
export declare const sqliteRateLimitProvider: SqliteRateLimitProvider;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getRawDb } from "../db/kysely.js";
|
|
2
|
+
export class SqliteRateLimitProvider {
|
|
3
|
+
name = "sqlite";
|
|
4
|
+
async checkAndRecord(key, config) {
|
|
5
|
+
requireConfig(config);
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
const db = getRawDb();
|
|
8
|
+
return db.transaction(() => {
|
|
9
|
+
const row = db.prepare(
|
|
10
|
+
`SELECT count, window_started_at, expires_at
|
|
11
|
+
FROM rate_limits WHERE key = ?`
|
|
12
|
+
).get(key);
|
|
13
|
+
if (!row || row.expires_at <= now) {
|
|
14
|
+
db.prepare(
|
|
15
|
+
`INSERT INTO rate_limits (
|
|
16
|
+
key, count, window_started_at, expires_at
|
|
17
|
+
) VALUES (?, 1, ?, ?)
|
|
18
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
19
|
+
count = 1,
|
|
20
|
+
window_started_at = excluded.window_started_at,
|
|
21
|
+
expires_at = excluded.expires_at`
|
|
22
|
+
).run(key, now, now + config.windowMs);
|
|
23
|
+
return {
|
|
24
|
+
allowed: true,
|
|
25
|
+
remaining: config.maxRequests - 1
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (row.count >= config.maxRequests) {
|
|
29
|
+
return {
|
|
30
|
+
allowed: false,
|
|
31
|
+
remaining: 0,
|
|
32
|
+
retryAfterMs: Math.max(0, row.expires_at - now)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
db.prepare(
|
|
36
|
+
`UPDATE rate_limits SET count = count + 1 WHERE key = ?`
|
|
37
|
+
).run(key);
|
|
38
|
+
return {
|
|
39
|
+
allowed: true,
|
|
40
|
+
remaining: config.maxRequests - row.count - 1
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
}
|
|
44
|
+
async getStats(key, config) {
|
|
45
|
+
requireConfig(config);
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const row = getRawDb().prepare(
|
|
48
|
+
`SELECT count, window_started_at, expires_at
|
|
49
|
+
FROM rate_limits WHERE key = ?`
|
|
50
|
+
).get(key);
|
|
51
|
+
if (!row || row.expires_at <= now) {
|
|
52
|
+
return {
|
|
53
|
+
limit: config.maxRequests,
|
|
54
|
+
remaining: config.maxRequests,
|
|
55
|
+
resetMs: config.windowMs
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
limit: config.maxRequests,
|
|
60
|
+
remaining: Math.max(0, config.maxRequests - row.count),
|
|
61
|
+
resetMs: Math.max(0, row.expires_at - now)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export const sqliteRateLimitProvider = new SqliteRateLimitProvider();
|
|
66
|
+
function requireConfig(config) {
|
|
67
|
+
if (!Number.isSafeInteger(config.windowMs) || config.windowMs <= 0 || !Number.isSafeInteger(config.maxRequests) || config.maxRequests <= 0) {
|
|
68
|
+
throw new Error("Invalid SQLite rate-limit configuration");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync history retention is deliberately fail-closed.
|
|
3
|
+
*
|
|
4
|
+
* An operator cannot enable this with environment configuration. The policy
|
|
5
|
+
* may change only after snapshot-at-high-watermark bootstrap is implemented
|
|
6
|
+
* and verified for SQLite deployments.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SYNC_HISTORY_GC_POLICY: Readonly<{
|
|
9
|
+
enabled: true;
|
|
10
|
+
snapshotBootstrapVerified: true;
|
|
11
|
+
reason: "SQLite history GC is guarded by the verified snapshot-v1 retention contract.";
|
|
12
|
+
}>;
|
|
13
|
+
export declare function canRunSyncHistoryGc(): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const SYNC_HISTORY_GC_POLICY = Object.freeze({
|
|
2
|
+
enabled: true,
|
|
3
|
+
snapshotBootstrapVerified: true,
|
|
4
|
+
reason: "SQLite history GC is guarded by the verified snapshot-v1 retention contract."
|
|
5
|
+
});
|
|
6
|
+
export function canRunSyncHistoryGc() {
|
|
7
|
+
return SYNC_HISTORY_GC_POLICY.enabled && SYNC_HISTORY_GC_POLICY.snapshotBootstrapVerified;
|
|
8
|
+
}
|
|
@@ -6,13 +6,25 @@
|
|
|
6
6
|
* (BEGIN IMMEDIATE prevents concurrent server_version races).
|
|
7
7
|
*/
|
|
8
8
|
import type { H3Event } from 'h3';
|
|
9
|
-
import type { SyncGatewayAdapter } from '~~/server/sync/gateway/types';
|
|
10
|
-
import type { PullRequest, PullResponse, PushBatch, PushResult } from '~~/shared/sync/types';
|
|
9
|
+
import type { CanonicalStorageQueryRequest, CanonicalStorageQueryResponse, SyncGatewayAdapter, UploadIntentConsumptionRequest, UploadIntentReservationRequest } from '~~/server/sync/gateway/types';
|
|
10
|
+
import type { PullRequest, PullResponse, PushBatch, PushResult, SnapshotRequest, SnapshotResponse } from '~~/shared/sync/types';
|
|
11
11
|
export declare class SqliteSyncGatewayAdapter implements SyncGatewayAdapter {
|
|
12
12
|
id: string;
|
|
13
|
+
readonly capabilities: {
|
|
14
|
+
readonly snapshotBootstrap: "snapshot-v1";
|
|
15
|
+
readonly historyRetention: "snapshot-v1";
|
|
16
|
+
};
|
|
13
17
|
private get db();
|
|
18
|
+
reserveUploadIntent(event: H3Event, input: UploadIntentReservationRequest): Promise<void>;
|
|
19
|
+
consumeUploadIntent(event: H3Event, input: UploadIntentConsumptionRequest): Promise<void>;
|
|
20
|
+
cancelUploadIntent(event: H3Event, input: {
|
|
21
|
+
workspaceId: string;
|
|
22
|
+
intentId: string;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
queryCanonicalStorage(event: H3Event, input: CanonicalStorageQueryRequest): Promise<CanonicalStorageQueryResponse>;
|
|
14
25
|
push(event: H3Event, input: PushBatch): Promise<PushResult>;
|
|
15
26
|
pull(event: H3Event, input: PullRequest): Promise<PullResponse>;
|
|
27
|
+
snapshot(event: H3Event, input: SnapshotRequest): Promise<SnapshotResponse>;
|
|
16
28
|
updateCursor(event: H3Event, input: {
|
|
17
29
|
scope: {
|
|
18
30
|
workspaceId: string;
|