or3-provider-sqlite 0.0.2 → 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 +14 -1
- package/dist/module.json +1 -1
- 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 +19 -2
- 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 +57 -0
- package/dist/runtime/server/plugins/register.js +23 -8
- package/dist/runtime/server/rate-limit/sqlite-provider.d.ts +7 -0
- package/dist/runtime/server/rate-limit/sqlite-provider.js +70 -0
- package/package.json +5 -1
|
@@ -7,6 +7,13 @@ import * as m005 from "./migrations/005_admin_stores.js";
|
|
|
7
7
|
import * as m006 from "./migrations/006_sync_snapshots.js";
|
|
8
8
|
import * as m007 from "./migrations/007_device_cursor_ownership.js";
|
|
9
9
|
import * as m008 from "./migrations/008_upload_intents.js";
|
|
10
|
+
import * as m009 from "./migrations/009_or3_connect.js";
|
|
11
|
+
import * as m010 from "./migrations/010_connect_user_code_hmac.js";
|
|
12
|
+
import * as m011 from "./migrations/011_connect_environment_scope_indexes.js";
|
|
13
|
+
import * as m012 from "./migrations/012_connect_credential_redelivery.js";
|
|
14
|
+
import * as m013 from "./migrations/013_connect_environment_lifecycle.js";
|
|
15
|
+
import * as m014 from "./migrations/014_connect_retention_activation.js";
|
|
16
|
+
import * as m015 from "./migrations/015_rate_limits.js";
|
|
10
17
|
const REQUIRED_SCHEMA_TABLES = [
|
|
11
18
|
"users",
|
|
12
19
|
"auth_accounts",
|
|
@@ -28,7 +35,10 @@ const REQUIRED_SCHEMA_TABLES = [
|
|
|
28
35
|
"admin_workspace_settings",
|
|
29
36
|
"sync_snapshots",
|
|
30
37
|
"sync_snapshot_items",
|
|
31
|
-
"upload_intents"
|
|
38
|
+
"upload_intents",
|
|
39
|
+
"connect_device_authorizations",
|
|
40
|
+
"connect_environments",
|
|
41
|
+
"rate_limits"
|
|
32
42
|
];
|
|
33
43
|
const migrations = {
|
|
34
44
|
"001_init": m001,
|
|
@@ -38,7 +48,14 @@ const migrations = {
|
|
|
38
48
|
"005_admin_stores": m005,
|
|
39
49
|
"006_sync_snapshots": m006,
|
|
40
50
|
"007_device_cursor_ownership": m007,
|
|
41
|
-
"008_upload_intents": m008
|
|
51
|
+
"008_upload_intents": m008,
|
|
52
|
+
"009_or3_connect": m009,
|
|
53
|
+
"010_connect_user_code_hmac": m010,
|
|
54
|
+
"011_connect_environment_scope_indexes": m011,
|
|
55
|
+
"012_connect_credential_redelivery": m012,
|
|
56
|
+
"013_connect_environment_lifecycle": m013,
|
|
57
|
+
"014_connect_retention_activation": m014,
|
|
58
|
+
"015_rate_limits": m015
|
|
42
59
|
};
|
|
43
60
|
class StaticMigrationProvider {
|
|
44
61
|
async getMigrations() {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
await sql`
|
|
4
|
+
CREATE TABLE connect_device_authorizations (
|
|
5
|
+
id TEXT PRIMARY KEY,
|
|
6
|
+
device_code_hash TEXT NOT NULL UNIQUE,
|
|
7
|
+
user_code_hash TEXT NOT NULL,
|
|
8
|
+
status TEXT NOT NULL CHECK (
|
|
9
|
+
status IN (
|
|
10
|
+
'pending', 'provisioning', 'approved', 'delivering',
|
|
11
|
+
'denied', 'consumed', 'expired'
|
|
12
|
+
)
|
|
13
|
+
),
|
|
14
|
+
host_json TEXT NOT NULL,
|
|
15
|
+
approved_user_id TEXT,
|
|
16
|
+
approved_workspace_id TEXT,
|
|
17
|
+
environment_id TEXT,
|
|
18
|
+
credential_ciphertext TEXT,
|
|
19
|
+
credential_delivery_started_at INTEGER,
|
|
20
|
+
credential_redeliver_until INTEGER,
|
|
21
|
+
expires_at INTEGER NOT NULL,
|
|
22
|
+
created_at INTEGER NOT NULL,
|
|
23
|
+
updated_at INTEGER NOT NULL
|
|
24
|
+
)
|
|
25
|
+
`.execute(db);
|
|
26
|
+
await sql`
|
|
27
|
+
CREATE INDEX connect_authorizations_status_expiry
|
|
28
|
+
ON connect_device_authorizations(status, expires_at)
|
|
29
|
+
`.execute(db);
|
|
30
|
+
await sql`
|
|
31
|
+
CREATE INDEX connect_authorizations_user_code
|
|
32
|
+
ON connect_device_authorizations(user_code_hash, created_at)
|
|
33
|
+
`.execute(db);
|
|
34
|
+
await sql`
|
|
35
|
+
CREATE INDEX connect_authorizations_environment
|
|
36
|
+
ON connect_device_authorizations(environment_id)
|
|
37
|
+
`.execute(db);
|
|
38
|
+
await sql`
|
|
39
|
+
CREATE TABLE connect_environments (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
user_id TEXT NOT NULL,
|
|
42
|
+
workspace_id TEXT NOT NULL,
|
|
43
|
+
name TEXT NOT NULL,
|
|
44
|
+
platform TEXT NOT NULL,
|
|
45
|
+
architecture TEXT NOT NULL,
|
|
46
|
+
host_id TEXT,
|
|
47
|
+
signing_public_key TEXT,
|
|
48
|
+
noise_public_key TEXT,
|
|
49
|
+
authorization_id TEXT,
|
|
50
|
+
hostname TEXT NOT NULL,
|
|
51
|
+
tunnel_id TEXT NOT NULL,
|
|
52
|
+
dns_record_id TEXT NOT NULL,
|
|
53
|
+
control_token_hash TEXT NOT NULL UNIQUE,
|
|
54
|
+
access_credential_ciphertext TEXT NOT NULL,
|
|
55
|
+
tunnel_secret_ciphertext TEXT,
|
|
56
|
+
status TEXT NOT NULL CHECK (
|
|
57
|
+
status IN (
|
|
58
|
+
'provisioning', 'active', 'revoking', 'revoked', 'error'
|
|
59
|
+
)
|
|
60
|
+
),
|
|
61
|
+
lifecycle_attempts INTEGER NOT NULL DEFAULT 0,
|
|
62
|
+
lifecycle_next_attempt_at INTEGER NOT NULL DEFAULT 0,
|
|
63
|
+
lifecycle_claim_token TEXT,
|
|
64
|
+
lifecycle_claimed_until INTEGER,
|
|
65
|
+
provisioning_deadline_at INTEGER,
|
|
66
|
+
lifecycle_error TEXT,
|
|
67
|
+
last_seen_at INTEGER,
|
|
68
|
+
created_at INTEGER NOT NULL,
|
|
69
|
+
updated_at INTEGER NOT NULL,
|
|
70
|
+
revoked_at INTEGER
|
|
71
|
+
)
|
|
72
|
+
`.execute(db);
|
|
73
|
+
await sql`
|
|
74
|
+
CREATE INDEX connect_environments_user_status
|
|
75
|
+
ON connect_environments(user_id, status, updated_at)
|
|
76
|
+
`.execute(db);
|
|
77
|
+
await sql`
|
|
78
|
+
CREATE INDEX connect_environments_workspace_status
|
|
79
|
+
ON connect_environments(workspace_id, status, updated_at)
|
|
80
|
+
`.execute(db);
|
|
81
|
+
await sql`
|
|
82
|
+
CREATE INDEX connect_environments_lifecycle_due
|
|
83
|
+
ON connect_environments(
|
|
84
|
+
status, lifecycle_next_attempt_at, lifecycle_claimed_until
|
|
85
|
+
)
|
|
86
|
+
`.execute(db);
|
|
87
|
+
}
|
|
88
|
+
export async function down(db) {
|
|
89
|
+
await db.schema.dropTable("connect_environments").ifExists().execute();
|
|
90
|
+
await db.schema.dropTable("connect_device_authorizations").ifExists().execute();
|
|
91
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Purges legacy readable pairing phrases.
|
|
4
|
+
*
|
|
5
|
+
* Device authorizations are ten-minute, retryable setup records. Their former
|
|
6
|
+
* unkeyed hashes cannot be upgraded to the server-keyed HMAC without retaining
|
|
7
|
+
* the plaintext, so an upgrade deliberately invalidates in-flight records.
|
|
8
|
+
*/
|
|
9
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
10
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
const columns = await sql`
|
|
4
|
+
PRAGMA table_info(connect_device_authorizations)
|
|
5
|
+
`.execute(db);
|
|
6
|
+
if (!columns.rows.some((column) => column.name === "user_code_display")) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_status_expiry`.execute(db);
|
|
10
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_user_code`.execute(db);
|
|
11
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_environment`.execute(db);
|
|
12
|
+
await sql`
|
|
13
|
+
ALTER TABLE connect_device_authorizations
|
|
14
|
+
RENAME TO connect_device_authorizations_legacy
|
|
15
|
+
`.execute(db);
|
|
16
|
+
await createSecureAuthorizationTable(db);
|
|
17
|
+
await sql`DROP TABLE connect_device_authorizations_legacy`.execute(db);
|
|
18
|
+
}
|
|
19
|
+
export async function down(db) {
|
|
20
|
+
const columns = await sql`
|
|
21
|
+
PRAGMA table_info(connect_device_authorizations)
|
|
22
|
+
`.execute(db);
|
|
23
|
+
if (columns.rows.some((column) => column.name === "user_code_display")) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_status_expiry`.execute(db);
|
|
27
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_user_code`.execute(db);
|
|
28
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_environment`.execute(db);
|
|
29
|
+
await sql`
|
|
30
|
+
ALTER TABLE connect_device_authorizations
|
|
31
|
+
RENAME TO connect_device_authorizations_secure
|
|
32
|
+
`.execute(db);
|
|
33
|
+
await sql`
|
|
34
|
+
CREATE TABLE connect_device_authorizations (
|
|
35
|
+
id TEXT PRIMARY KEY,
|
|
36
|
+
device_code_hash TEXT NOT NULL UNIQUE,
|
|
37
|
+
user_code_hash TEXT NOT NULL,
|
|
38
|
+
user_code_display TEXT NOT NULL,
|
|
39
|
+
status TEXT NOT NULL CHECK (
|
|
40
|
+
status IN (
|
|
41
|
+
'pending', 'provisioning', 'approved',
|
|
42
|
+
'denied', 'consumed', 'expired'
|
|
43
|
+
)
|
|
44
|
+
),
|
|
45
|
+
host_json TEXT NOT NULL,
|
|
46
|
+
approved_user_id TEXT,
|
|
47
|
+
approved_workspace_id TEXT,
|
|
48
|
+
environment_id TEXT,
|
|
49
|
+
credential_ciphertext TEXT,
|
|
50
|
+
expires_at INTEGER NOT NULL,
|
|
51
|
+
created_at INTEGER NOT NULL,
|
|
52
|
+
updated_at INTEGER NOT NULL
|
|
53
|
+
)
|
|
54
|
+
`.execute(db);
|
|
55
|
+
await createAuthorizationIndexes(db);
|
|
56
|
+
await sql`DROP TABLE connect_device_authorizations_secure`.execute(db);
|
|
57
|
+
}
|
|
58
|
+
async function createSecureAuthorizationTable(db) {
|
|
59
|
+
await sql`
|
|
60
|
+
CREATE TABLE connect_device_authorizations (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
device_code_hash TEXT NOT NULL UNIQUE,
|
|
63
|
+
user_code_hash TEXT NOT NULL,
|
|
64
|
+
status TEXT NOT NULL CHECK (
|
|
65
|
+
status IN (
|
|
66
|
+
'pending', 'provisioning', 'approved', 'delivering',
|
|
67
|
+
'denied', 'consumed', 'expired'
|
|
68
|
+
)
|
|
69
|
+
),
|
|
70
|
+
host_json TEXT NOT NULL,
|
|
71
|
+
approved_user_id TEXT,
|
|
72
|
+
approved_workspace_id TEXT,
|
|
73
|
+
environment_id TEXT,
|
|
74
|
+
credential_ciphertext TEXT,
|
|
75
|
+
credential_delivery_started_at INTEGER,
|
|
76
|
+
credential_redeliver_until INTEGER,
|
|
77
|
+
expires_at INTEGER NOT NULL,
|
|
78
|
+
created_at INTEGER NOT NULL,
|
|
79
|
+
updated_at INTEGER NOT NULL
|
|
80
|
+
)
|
|
81
|
+
`.execute(db);
|
|
82
|
+
await createAuthorizationIndexes(db);
|
|
83
|
+
}
|
|
84
|
+
async function createAuthorizationIndexes(db) {
|
|
85
|
+
await sql`
|
|
86
|
+
CREATE INDEX connect_authorizations_status_expiry
|
|
87
|
+
ON connect_device_authorizations(status, expires_at)
|
|
88
|
+
`.execute(db);
|
|
89
|
+
await sql`
|
|
90
|
+
CREATE INDEX connect_authorizations_user_code
|
|
91
|
+
ON connect_device_authorizations(user_code_hash, created_at)
|
|
92
|
+
`.execute(db);
|
|
93
|
+
await sql`
|
|
94
|
+
CREATE INDEX connect_authorizations_environment
|
|
95
|
+
ON connect_device_authorizations(environment_id)
|
|
96
|
+
`.execute(db);
|
|
97
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
await sql`
|
|
4
|
+
CREATE INDEX connect_environments_user_workspace_status_updated
|
|
5
|
+
ON connect_environments(user_id, workspace_id, status, updated_at)
|
|
6
|
+
`.execute(db);
|
|
7
|
+
await sql`
|
|
8
|
+
CREATE INDEX connect_environments_user_workspace_control_token
|
|
9
|
+
ON connect_environments(user_id, workspace_id, control_token_hash)
|
|
10
|
+
`.execute(db);
|
|
11
|
+
await sql`
|
|
12
|
+
CREATE INDEX connect_environments_user_workspace_id
|
|
13
|
+
ON connect_environments(user_id, workspace_id, id)
|
|
14
|
+
`.execute(db);
|
|
15
|
+
}
|
|
16
|
+
export async function down(db) {
|
|
17
|
+
await sql`
|
|
18
|
+
DROP INDEX IF EXISTS connect_environments_user_workspace_id
|
|
19
|
+
`.execute(db);
|
|
20
|
+
await sql`
|
|
21
|
+
DROP INDEX IF EXISTS connect_environments_user_workspace_control_token
|
|
22
|
+
`.execute(db);
|
|
23
|
+
await sql`
|
|
24
|
+
DROP INDEX IF EXISTS connect_environments_user_workspace_status_updated
|
|
25
|
+
`.execute(db);
|
|
26
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
const columns = await authorizationColumns(db);
|
|
4
|
+
if (columns.has("credential_redeliver_until")) return;
|
|
5
|
+
await dropAuthorizationIndexes(db);
|
|
6
|
+
await sql`
|
|
7
|
+
ALTER TABLE connect_device_authorizations
|
|
8
|
+
RENAME TO connect_device_authorizations_before_redelivery
|
|
9
|
+
`.execute(db);
|
|
10
|
+
await createRedeliveryTable(db);
|
|
11
|
+
await sql`
|
|
12
|
+
INSERT INTO connect_device_authorizations (
|
|
13
|
+
id, device_code_hash, user_code_hash, status, host_json,
|
|
14
|
+
approved_user_id, approved_workspace_id, environment_id,
|
|
15
|
+
credential_ciphertext, expires_at, created_at, updated_at
|
|
16
|
+
)
|
|
17
|
+
SELECT
|
|
18
|
+
id, device_code_hash, user_code_hash, status, host_json,
|
|
19
|
+
approved_user_id, approved_workspace_id, environment_id,
|
|
20
|
+
credential_ciphertext, expires_at, created_at, updated_at
|
|
21
|
+
FROM connect_device_authorizations_before_redelivery
|
|
22
|
+
`.execute(db);
|
|
23
|
+
await sql`
|
|
24
|
+
DROP TABLE connect_device_authorizations_before_redelivery
|
|
25
|
+
`.execute(db);
|
|
26
|
+
}
|
|
27
|
+
export async function down(db) {
|
|
28
|
+
const columns = await authorizationColumns(db);
|
|
29
|
+
if (!columns.has("credential_redeliver_until")) return;
|
|
30
|
+
await dropAuthorizationIndexes(db);
|
|
31
|
+
await sql`
|
|
32
|
+
ALTER TABLE connect_device_authorizations
|
|
33
|
+
RENAME TO connect_device_authorizations_with_redelivery
|
|
34
|
+
`.execute(db);
|
|
35
|
+
await sql`
|
|
36
|
+
CREATE TABLE connect_device_authorizations (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
device_code_hash TEXT NOT NULL UNIQUE,
|
|
39
|
+
user_code_hash TEXT NOT NULL,
|
|
40
|
+
status TEXT NOT NULL CHECK (
|
|
41
|
+
status IN (
|
|
42
|
+
'pending', 'provisioning', 'approved',
|
|
43
|
+
'denied', 'consumed', 'expired'
|
|
44
|
+
)
|
|
45
|
+
),
|
|
46
|
+
host_json TEXT NOT NULL,
|
|
47
|
+
approved_user_id TEXT,
|
|
48
|
+
approved_workspace_id TEXT,
|
|
49
|
+
environment_id TEXT,
|
|
50
|
+
credential_ciphertext TEXT,
|
|
51
|
+
expires_at INTEGER NOT NULL,
|
|
52
|
+
created_at INTEGER NOT NULL,
|
|
53
|
+
updated_at INTEGER NOT NULL
|
|
54
|
+
)
|
|
55
|
+
`.execute(db);
|
|
56
|
+
await sql`
|
|
57
|
+
INSERT INTO connect_device_authorizations (
|
|
58
|
+
id, device_code_hash, user_code_hash, status, host_json,
|
|
59
|
+
approved_user_id, approved_workspace_id, environment_id,
|
|
60
|
+
credential_ciphertext, expires_at, created_at, updated_at
|
|
61
|
+
)
|
|
62
|
+
SELECT
|
|
63
|
+
id, device_code_hash, user_code_hash,
|
|
64
|
+
CASE WHEN status = 'delivering' THEN 'approved' ELSE status END,
|
|
65
|
+
host_json, approved_user_id, approved_workspace_id, environment_id,
|
|
66
|
+
credential_ciphertext, expires_at, created_at, updated_at
|
|
67
|
+
FROM connect_device_authorizations_with_redelivery
|
|
68
|
+
`.execute(db);
|
|
69
|
+
await createAuthorizationIndexes(db);
|
|
70
|
+
await sql`
|
|
71
|
+
DROP TABLE connect_device_authorizations_with_redelivery
|
|
72
|
+
`.execute(db);
|
|
73
|
+
}
|
|
74
|
+
async function authorizationColumns(db) {
|
|
75
|
+
const result = await sql`
|
|
76
|
+
PRAGMA table_info(connect_device_authorizations)
|
|
77
|
+
`.execute(db);
|
|
78
|
+
return new Set(result.rows.map((column) => column.name));
|
|
79
|
+
}
|
|
80
|
+
async function createRedeliveryTable(db) {
|
|
81
|
+
await sql`
|
|
82
|
+
CREATE TABLE connect_device_authorizations (
|
|
83
|
+
id TEXT PRIMARY KEY,
|
|
84
|
+
device_code_hash TEXT NOT NULL UNIQUE,
|
|
85
|
+
user_code_hash TEXT NOT NULL,
|
|
86
|
+
status TEXT NOT NULL CHECK (
|
|
87
|
+
status IN (
|
|
88
|
+
'pending', 'provisioning', 'approved', 'delivering',
|
|
89
|
+
'denied', 'consumed', 'expired'
|
|
90
|
+
)
|
|
91
|
+
),
|
|
92
|
+
host_json TEXT NOT NULL,
|
|
93
|
+
approved_user_id TEXT,
|
|
94
|
+
approved_workspace_id TEXT,
|
|
95
|
+
environment_id TEXT,
|
|
96
|
+
credential_ciphertext TEXT,
|
|
97
|
+
credential_delivery_started_at INTEGER,
|
|
98
|
+
credential_redeliver_until INTEGER,
|
|
99
|
+
expires_at INTEGER NOT NULL,
|
|
100
|
+
created_at INTEGER NOT NULL,
|
|
101
|
+
updated_at INTEGER NOT NULL
|
|
102
|
+
)
|
|
103
|
+
`.execute(db);
|
|
104
|
+
await createAuthorizationIndexes(db);
|
|
105
|
+
}
|
|
106
|
+
async function dropAuthorizationIndexes(db) {
|
|
107
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_status_expiry`.execute(db);
|
|
108
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_user_code`.execute(db);
|
|
109
|
+
await sql`DROP INDEX IF EXISTS connect_authorizations_environment`.execute(db);
|
|
110
|
+
}
|
|
111
|
+
async function createAuthorizationIndexes(db) {
|
|
112
|
+
await sql`
|
|
113
|
+
CREATE INDEX connect_authorizations_status_expiry
|
|
114
|
+
ON connect_device_authorizations(status, expires_at)
|
|
115
|
+
`.execute(db);
|
|
116
|
+
await sql`
|
|
117
|
+
CREATE INDEX connect_authorizations_user_code
|
|
118
|
+
ON connect_device_authorizations(user_code_hash, created_at)
|
|
119
|
+
`.execute(db);
|
|
120
|
+
await sql`
|
|
121
|
+
CREATE INDEX connect_authorizations_environment
|
|
122
|
+
ON connect_device_authorizations(environment_id)
|
|
123
|
+
`.execute(db);
|
|
124
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Kysely } from 'kysely';
|
|
2
|
+
/**
|
|
3
|
+
* Adds durable provisioning/revocation work with lease-based claiming.
|
|
4
|
+
*
|
|
5
|
+
* SQLite cannot widen CHECK constraints in place, so both Connect tables are
|
|
6
|
+
* rebuilt transactionally while preserving active environments and approved
|
|
7
|
+
* credentials.
|
|
8
|
+
*/
|
|
9
|
+
export declare function up(db: Kysely<unknown>): Promise<void>;
|
|
10
|
+
export declare function down(db: Kysely<unknown>): Promise<void>;
|
|
@@ -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
|
+
}
|