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
|
@@ -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
|
+
}
|
|
@@ -131,6 +131,60 @@ export interface UploadIntentsTable {
|
|
|
131
131
|
consumed_at: number | null;
|
|
132
132
|
cancelled_at: number | null;
|
|
133
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
|
+
}
|
|
134
188
|
export interface AdminUsersTable {
|
|
135
189
|
user_id: string;
|
|
136
190
|
created_at: number;
|
|
@@ -156,6 +210,9 @@ export interface Or3SqliteDb {
|
|
|
156
210
|
sync_snapshots: SyncSnapshotsTable;
|
|
157
211
|
sync_snapshot_items: SyncSnapshotItemsTable;
|
|
158
212
|
upload_intents: UploadIntentsTable;
|
|
213
|
+
connect_device_authorizations: ConnectDeviceAuthorizationsTable;
|
|
214
|
+
connect_environments: ConnectEnvironmentsTable;
|
|
215
|
+
rate_limits: RateLimitsTable;
|
|
159
216
|
s_threads: SyncedEntityTable;
|
|
160
217
|
s_messages: SyncedEntityTable;
|
|
161
218
|
s_projects: SyncedEntityTable;
|
|
@@ -3,6 +3,8 @@ 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
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";
|
|
6
8
|
import { createSqliteAuthWorkspaceStore } from "../auth/sqlite-auth-workspace-store.js";
|
|
7
9
|
import { createSqliteSyncGatewayAdapter } from "../sync/sqlite-sync-gateway-adapter.js";
|
|
8
10
|
import { createSqliteWebhookStore } from "../webhooks/sqlite-webhook-store.js";
|
|
@@ -12,27 +14,39 @@ import {
|
|
|
12
14
|
createSqliteWorkspaceSettingsStore
|
|
13
15
|
} from "../admin/stores/sqlite-store.js";
|
|
14
16
|
import { sqliteSyncAdminAdapter } from "../admin/adapters/sync-sqlite.js";
|
|
17
|
+
import { createSqliteConnectStore } from "../connect/sqlite-connect-store.js";
|
|
15
18
|
import { getSqliteDb } from "../db/kysely.js";
|
|
16
19
|
import { runMigrations } from "../db/migrate.js";
|
|
20
|
+
import { sqliteRateLimitProvider } from "../rate-limit/sqlite-provider.js";
|
|
17
21
|
import { useRuntimeConfig } from "#imports";
|
|
18
22
|
const SQLITE_PROVIDER_ID = "sqlite";
|
|
19
23
|
export default defineNitroPlugin(async () => {
|
|
20
24
|
const config = useRuntimeConfig();
|
|
21
25
|
if (!config.auth?.enabled) return;
|
|
22
|
-
|
|
23
|
-
|
|
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;
|
|
24
29
|
const db = getSqliteDb();
|
|
25
|
-
await runMigrations(db);
|
|
26
30
|
registerAuthWorkspaceStore({
|
|
27
31
|
id: SQLITE_PROVIDER_ID,
|
|
28
32
|
order: 100,
|
|
29
33
|
create: createSqliteAuthWorkspaceStore
|
|
30
34
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
}
|
|
36
50
|
registerWebhookStore({
|
|
37
51
|
id: SQLITE_PROVIDER_ID,
|
|
38
52
|
order: 100,
|
|
@@ -52,4 +66,5 @@ export default defineNitroPlugin(async () => {
|
|
|
52
66
|
})
|
|
53
67
|
});
|
|
54
68
|
registerProviderAdminAdapter(sqliteSyncAdminAdapter);
|
|
69
|
+
await runMigrations(db);
|
|
55
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "or3-provider-sqlite",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/Saluana/or3-provider-sqlite.git"
|
|
7
|
+
},
|
|
4
8
|
"description": "SQLite sync and workspace store provider for OR3 Chat — lightweight self-hosted backend via Kysely.",
|
|
5
9
|
"license": "MIT",
|
|
6
10
|
"type": "module",
|